<?php
// +-------------------------------------------------+
// © 2002-2005 PMB Services / www.sigb.net pmb@sigb.net et contributeurs (voir www.sigb.net)
// +-------------------------------------------------+
// $Id: thesaurus.class.php,v 1.37.2.2.6.4 2026/06/19 12:38:21 qvarin Exp $

use Pmb\Common\Views\VueJsView;
use Pmb\Autorities\Orm\ThesaurusVersionOrm;
use Pmb\Autorities\Orm\ThesaurusChangelogOrm;
use Pmb\Autorities\Models\ThesaurusVersionModel;
use Pmb\Common\Helper\Request;

if (stristr($_SERVER['REQUEST_URI'], ".class.php")) {
    die("no access");
}

global $class_path;
require_once $class_path . "/noeuds.class.php";
require_once $class_path . "/categories.class.php";
require_once $class_path . "/synchro_rdf.class.php";
require_once $class_path . "/translation.class.php";

class thesaurus
{

    public $id_thesaurus = 0;                    //Identifiant du thesaurus
    public $libelle_thesaurus = '';
    public $active    = '1';
    public $opac_active = '1';
    public $langue_defaut = 'fr_FR';
    public $num_noeud_racine = 0;                 //Index du noeud racine du thesaurus
    public $num_noeud_orphelins = 0;             // Index Noeud orphelins du thesaurus
    public $num_noeud_nonclasses = 0;             // Index Noeud nonclasses du thesaurus
    public $version_enabled = 0;
    public $num_thesaurus_version = 0;
    protected $version_rights;
    protected $version_rights_all = 1;

    protected static $fullThesaurusList = null;
    protected static $thesaurusList = null;

    private static $instances = array();

    // Constructeur.
    public function __construct($id = 0)
    {
        $this->id_thesaurus = intval($id);
        if ($this->id_thesaurus > 0) {
            $this->load();
        }
    }

    static public function get_instance($id)
    {
        if (!isset(self::$instances[$id])) {
            self::$instances[$id] = new thesaurus($id);
        }
        return self::$instances[$id];
    }

    public function hasVersionEnabled()
    {
        return $this->version_enabled;
    }

    /**
     * Active le versionnage du thesaurus
     * activation uniquement (jamais de desactivation)
     *
     * @param integer $version_enabled (0|1)
     * @return void
     */
    public function setVersionEnabled(int $version_enabled)
    {
        if ($this->version_enabled) {
            return;
        }
        $this->version_enabled = ($version_enabled == 1) ? 1 : 0;
    }

    public function setVersionRightsAll(int $version_rights_all)
    {
        $this->version_rights_all = ($version_rights_all == 1) ? 1 : 0;
    }

    public function setVersionRights(array $version_rights)
    {
        $users = [1];
        foreach ($version_rights as $version_right) {
            $users[] = intval($version_right);
        }
        $users = array_unique($users);
        $this->version_rights = ' ' . implode(' ', $users) . ' ';
    }

    // charge le thesaurus a partir de la base.
    public function load()
    {
        $query = "select * from thesaurus where id_thesaurus = '" . $this->id_thesaurus . "' ";
        $result = pmb_mysql_query($query);
        if (!pmb_mysql_num_rows($result)) {
            $this->id_thesaurus = 0;
            return;
        }
        $obj = pmb_mysql_fetch_object($result);
        $this->id_thesaurus = $obj->id_thesaurus;
        $this->libelle_thesaurus = $obj->libelle_thesaurus;
        $this->version_enabled = intval($obj->version_enabled ?? 0);
        $this->num_thesaurus_version = intval($obj->num_thesaurus_version ?? 0);
        $this->active = $obj->active;
        $this->opac_active = $obj->opac_active;
        $this->langue_defaut = $obj->langue_defaut;
        $this->num_noeud_racine = $obj->num_noeud_racine;
        $this->version_rights = $obj->version_rights ?? ' 1 ';
        $this->version_rights_all = $obj->version_rights_all ?? 1;

        $query = "select id_noeud from noeuds where num_thesaurus = '" . $this->id_thesaurus . "' and autorite = 'ORPHELINS' ";
        $result = pmb_mysql_query($query);
        if (pmb_mysql_num_rows($result)) {
            $this->num_noeud_orphelins = pmb_mysql_result($result, 0, 0);
        } else {
            $this->num_noeud_orphelins = 0;
        }

        $query = "select id_noeud from noeuds where num_thesaurus = '" . $this->id_thesaurus . "' and autorite = 'NONCLASSES' ";
        $result = pmb_mysql_query($query);
        if (pmb_mysql_num_rows($result)) {
            $this->num_noeud_nonclasses = pmb_mysql_result($result, 0, 0);
        } else {
            $this->num_noeud_nonclasses = 0;
        }
    }

    protected function get_js_form()
    {
        global $thes_js_form;

        $js_form = $thes_js_form;
        if (($this->id_thesaurus) && static::hasCateg($this->id_thesaurus)) {
            $js_form = str_replace('!!thesaurus_as_categ!!', "oui", $js_form);
        } else {
            $js_form = str_replace('!!thesaurus_as_categ!!', "non", $js_form);
        }
        return $js_form;
    }

    public function get_content_form()
    {
        global $msg, $charset;
        global $include_path;
        global $lang;
        global $pmb_ark_activate;

        $interface_content_form = new interface_content_form(static::class);

        if ($this->id_thesaurus) {    //modification
            $interface_content_form->add_element('numero_thesaurus', '38')
                ->add_html_node($this->id_thesaurus);
        }
        $interface_content_form->add_element('libelle_thesaurus', '103')
            ->add_input_node('text', $this->libelle_thesaurus)
            ->set_class('saisie-80em')
            ->set_attributes(array('data-translation-fieldname' => 'libelle_thesaurus'));

        //Recuperation de la liste des langues definies pour l'interface
        $langages = new XMLlist("$include_path/messages/languages.xml", 1);
        $langages->analyser();
        $lg = $langages->table;

        //Recuperation de la liste des langues definies pour les thesaurus
        $thes_liste_trad = static::getTranslationsList();
        $lg1 = array();
        foreach ($thes_liste_trad as $item) {
            if ($lg[$item] != '') $lg1[$item] = $lg[$item];
        }

        if ($this->id_thesaurus) {    //modification
            $langue_defaut = htmlentities(addslashes($lg[$this->langue_defaut]), ENT_QUOTES, $charset);
        } else {    //creation
            $langue_defaut = "<select class='saisie-30em' id='langue_defaut' name='langue_defaut' >";
            foreach ($lg1 as $key => $value) {
                $langue_defaut .= "<option value='" . $key . "' ";
                if ($key == $lang) $langue_defaut .= " selected ";
                $langue_defaut .= " >" . htmlentities(addslashes($value), ENT_QUOTES, $charset) . "</option>";
            }
            $langue_defaut .= "</select>";
        }
        $interface_content_form->add_element('langue_defaut', 'thes_langue_defaut')
            ->add_html_node($langue_defaut);

        // Le thesaurus est versionnable s'il n'a pas encore de notices
        $has_notices  = thesaurus::hasNotices($this->id_thesaurus);

        if ($pmb_ark_activate && !$has_notices) {
            $interface_content_form->add_element('separateur')
                ->add_html_node('<hr />');

            // Checkbox versionnable : desactive si deja actif, categories en notices, ou thesaurus pas encore cree
            $versionnable_disabled = ($this->version_enabled || $has_notices) ? ['disabled' => 'disabled'] : [];
            $interface_content_form->add_element('thes_version_enabled')
                ->add_input_node('boolean', $this->version_enabled ? '1' : '0')
                ->set_id('thes_version_enabled')
                ->set_name('thes_version_enabled')
                ->set_label($msg['thes_versionnable'])
                ->set_class('saisie-30em')
                ->set_attributes($versionnable_disabled);

            if($this->version_enabled && $this->num_thesaurus_version) {
                $current_version = ThesaurusVersionOrm::findById($this->num_thesaurus_version);
                $current_version_num_label = htmlentities(sprintf($msg['thes_version_num_label'], $current_version->name, $current_version->num), ENT_QUOTES, $charset);
                $interface_content_form->add_element('thes_version_current', 'thes_version_current')
                    ->add_html_node($current_version_num_label);
            }

            $interface_content_form->add_element('thes_version_rights_all')
                ->add_input_node('boolean', $this->version_rights_all ? '1' : '0')
                ->set_id('thes_version_rights_all')
                ->set_name('thes_version_rights_all')
                ->set_label($msg['thes_version_rights_all'])
                ->set_class('saisie-30em');

            $interface_content_form->add_inherited_element('permissions_users', 'thes_version_rights', 'thes_version_rights_restrict_to')
                ->set_autorisations($this->version_rights)
                ->set_on_create($this->id_thesaurus ? 0 : 1);
        }
        return $interface_content_form->get_display();
    }

    /**
     * Retourne le HTML + JS de la barre de progression de duplication (approche polling).
     *
     * @param string $urlWs URL de base du webservice REST (ex: http://.../rest.php/autorities/)
     * @return string
     */
    private function getDuplicateProgressHtml(string $urlWs): string
    {
        global $msg;

        // json_encode garantit l'echappement correct pour JS (newlines, guillemets, etc.)
        $jUrlWs        = json_encode($urlWs);
        $jIdThes       = (int) $this->id_thesaurus;
        $jNoeuds       = json_encode($msg['thes_dup_phase_noeuds'] ?? 'Nodes');
        $jCategories   = json_encode($msg['thes_dup_phase_categories'] ?? 'Categories');
        $jVoirAussi    = json_encode($msg['thes_dup_phase_voir_aussi'] ?? 'See also');
        $jArk          = json_encode($msg['thes_dup_phase_ark'] ?? 'ARK');
        $jDone         = json_encode($msg['thes_dup_done'] ?? 'Duplication complete.');
        $jError        = json_encode($msg['thes_dup_error'] ?? 'Error during duplication.');

        return <<<'NOINTERP'
<div id="thesaurus-duplication-panel">
    <div id="thesaurus-duplication-totals"></div>
    <div id="thesaurus-duplication-bars"></div>
    <div id="thesaurus-duplication-msg"></div>
</div>
NOINTERP
        . '<script>(function(){' . "\n"
        . 'var URL_WS=' . $jUrlWs . ';' . "\n"
        . 'var ID_THES=' . $jIdThes . ';' . "\n"
        . 'var PHASES={noeuds:' . $jNoeuds . ',categories:' . $jCategories . ',voir_aussi:' . $jVoirAussi . ',ark:' . $jArk . '};' . "\n"
        . 'var LBL_DONE=' . $jDone . ';' . "\n"
        . 'var LBL_ERROR=' . $jError . ';' . "\n"
        . <<<'JS'
var totals={},bars={};
function el(id){return document.getElementById(id);}
function clearPanel(){
    el('thesaurus-duplication-totals').innerHTML='';
    el('thesaurus-duplication-bars').innerHTML='';
    el('thesaurus-duplication-msg').textContent='';
    el('thesaurus-duplication-msg').classList.remove('thesaurus-duplication-msg--error');
    totals={};bars={};
}
function showTotals(t){
    totals=t;
    el('thesaurus-duplication-totals').innerHTML=Object.keys(PHASES).map(function(p){
        return '<strong>'+PHASES[p]+'</strong> : '+(t[p]||0);
    }).join(' &nbsp;|&nbsp; ');
    Object.keys(PHASES).forEach(function(p){buildBar(p,PHASES[p]);});
}
function buildBar(phase,label){
    var w=document.createElement('div');
    w.className='thesaurus-duplication-bar-wrap';
    w.innerHTML='<div class="thesaurus-duplication-bar-label">'+label+' : <span id="thesaurus-duplication-lbl-'+phase+'">0 / ?</span></div>'
        +'<div class="thesaurus-duplication-bar-track">'
        +'<div id="thesaurus-duplication-bar-'+phase+'"></div></div>';
    el('thesaurus-duplication-bars').appendChild(w);
    bars[phase]={fill:document.getElementById('thesaurus-duplication-bar-'+phase),lbl:document.getElementById('thesaurus-duplication-lbl-'+phase)};
}
function updateBar(phase,done,total){
    var b=bars[phase];if(!b)return;
    var pct=total>0?Math.round(done*100/total):100;
    b.fill.style.width=pct+'%';
    b.lbl.textContent=done+' / '+total;
}
function showError(msg){
    var e=el('thesaurus-duplication-msg');
    if(e){
        e.classList.add('thesaurus-duplication-msg--error');
        e.textContent=msg;
    }
}
async function post(idThesaurus,data){
    var r=await fetch(URL_WS+'thesaurus/'+idThesaurus+'/duplicate',{
        method:'POST',
        body:new URLSearchParams({data:JSON.stringify(data)})
    });
    if(!r.ok)throw new Error('HTTP '+r.status);
    var j=await r.json();
    if(j.error)throw new Error(j.errorMessage||LBL_ERROR);
    return j;
}
var state={};
async function runPhase(idThesaurus,phase,total){
    if(total===0){updateBar(phase,0,0);return;}
    var offset=0;
    while(true){
        var r=await post(idThesaurus,{step:phase,state:state,offset:offset});
        if(r.state)state=r.state;
        offset=r.nextOffset;
        updateBar(phase,offset,total);
        if(!r.hasMore)break;
    }
}
window.pmbThesaurusDuplicate=async function(idThesaurus){
    var panel=el('thesaurus-duplication-panel');
    var btn=el('duplicate_button');
    if(!panel)return;
    if(btn)btn.disabled=true;
    panel.style.display='block';
    clearPanel();
    try{
        var s=await post(idThesaurus,{step:'start'});
        state=s.state;
        if(s.newName){el('thesaurus-duplication-totals').innerHTML='<em>'+s.newName+'</em>';}
        showTotals(s.totals);
        await runPhase(idThesaurus,'noeuds',s.totals.noeuds);
        await post(idThesaurus,{step:'paths',state:state});
        await runPhase(idThesaurus,'categories',s.totals.categories);
        await runPhase(idThesaurus,'voir_aussi',s.totals.voir_aussi);
        await runPhase(idThesaurus,'ark',s.totals.ark);
        var f=await post(idThesaurus,{step:'finish',state:state});
        el('thesaurus-duplication-msg').textContent=(s.newName?s.newName+' â€” ':'')+LBL_DONE;
        setTimeout(function(){
            window.location.href='./autorites.php?categ=categories&sub=thes&id_thes='+f.newId;
        },1200);
    }catch(e){
        showError(e.message||LBL_ERROR);
        if(btn)btn.disabled=false;
    }
};
}());</script>
JS;
    }

    /**
     * Retourne le HTML + JS du panneau de progression de suppression asynchrone.
     * Le bouton delete_button est intercepte pour lancer directement la suppression
     * sans navigation vers sub=thes_delete.
     *
     * @param string $urlWs URL de base du webservice REST (ex: http://.../rest.php/autorities/)
     * @return string
     */
    public function getDeleteProgressHtml(string $urlWs): string
    {
        global $msg;

        $jUrlWs  = json_encode($urlWs);
        $jIdThes = (int) $this->id_thesaurus;
        $jLabel  = json_encode($msg['thes_del_phase_noeuds'] ?? 'Nodes');
        $jDone   = json_encode($msg['thes_del_done']  ?? 'Deletion complete.');
        $jError  = json_encode($msg['thes_del_error'] ?? 'Error during deletion.');

        return <<<'NOINTERP'
<div id="thesaurus-deletion-panel">
    <div id="thesaurus-deletion-bar-wrap"></div>
    <div id="thesaurus-deletion-msg"></div>
</div>
NOINTERP
            . '<script>(function(){' . "\n"
            . 'var URL_WS=' . $jUrlWs . ';' . "\n"
            . 'var ID_THES=' . $jIdThes . ';' . "\n"
            . 'var LBL_NOEUDS=' . $jLabel . ';' . "\n"
            . 'var LBL_DONE=' . $jDone . ';' . "\n"
            . 'var LBL_ERROR=' . $jError . ';' . "\n"
            . <<<'JS'
var total=0,deleted=0;
function el(id){return document.getElementById(id);}
function buildBar(){
    var w=el('thesaurus-deletion-bar-wrap');
    w.innerHTML='<div class="thesaurus-deletion-bar-label">'+LBL_NOEUDS
        +' : <span id="thesaurus-deletion-lbl">0 / ?</span></div>'
        +'<div class="thesaurus-deletion-bar-track"><div id="thesaurus-deletion-bar"></div></div>';
}
function updateBar(done,tot){
    var bar=el('thesaurus-deletion-bar'),lbl=el('thesaurus-deletion-lbl');
    if(!bar||!lbl)return;
    var pct=tot>0?Math.round(done*100/tot):100;
    bar.style.width=pct+'%';
    lbl.textContent=done+' / '+tot;
}
function showError(m){
    var e=el('thesaurus-deletion-msg');
    if(e){
        e.classList.add('thesaurus-deletion-msg--error');
        e.textContent=m;
    }
}
async function post(data){
    var r=await fetch(URL_WS+'thesaurus/'+ID_THES+'/delete',{
        method:'POST',
        body:new URLSearchParams({data:JSON.stringify(data)})
    });
    if(!r.ok)throw new Error('HTTP '+r.status);
    var j=await r.json();
    if(j.error)throw new Error(j.errorMessage||LBL_ERROR);
    return j;
}
window.pmbThesaurusDelete=async function(){
    var panel=el('thesaurus-deletion-panel');
    var btn=el('delete_button');
    if(!panel)return;
    if(btn)btn.disabled=true;
    panel.style.display='block';
    buildBar();
    try{
        var s=await post({step:'start'});
        total=s.total;
        updateBar(0,total);
        while(true){
            var r=await post({step:'noeuds'});
            deleted+=r.deleted;
            updateBar(deleted,total);
            if(!r.hasMore)break;
        }
        await post({step:'finish'});
        el('thesaurus-deletion-msg').textContent=LBL_DONE;
        setTimeout(function(){
            window.location.href='./autorites.php?categ=categories&sub=thes&id_thes=-1';
        },1200);
    }catch(e){
        showError(e.message||LBL_ERROR);
        if(btn)btn.disabled=false;
    }
};
(function(){
    var b=el('delete_button');
    if(b){
        b.onclick=function(){
            if(typeof confirm_delete==='function'&&!confirm_delete())return;
            window.pmbThesaurusDelete();
        };
    }
}());
}());</script>
JS;
    }

    public function get_form()
    {
        global $msg;
        global $pmb_ark_activate;

        $form = $this->get_js_form();

        $interface_form = new interface_autorites_form('thes_form');
        if (!$this->id_thesaurus) {
            $interface_form->set_label($msg['thes_creation']);
        } else {
            $interface_form->set_label($msg['thes_modification']);
        }
        $interface_form->set_object_id($this->id_thesaurus)
            ->set_confirm_delete_msg($msg['confirm_suppr'])
            ->set_content_form($this->get_content_form())
            ->set_table_name('thesaurus')
            ->set_field_focus('libelle_thesaurus')
            ->set_duplicable($this->id_thesaurus ? 1 : 0);
        $form .= $interface_form->get_display();

        if ($pmb_ark_activate && $this->version_enabled) {
            global $pmb_url_base;
            $view = new VueJsView('autorites/versionsThesaurus', [
                'id_thesaurus'    => $this->id_thesaurus,
                'versions'        => ThesaurusVersionModel::getVersionsTree($this->id_thesaurus),
                'url_webservice'  => $pmb_url_base . 'rest.php/autorities/',
            ]);
            $form .= $view->render();
        }

        if ($this->id_thesaurus) {
            global $pmb_url_base;
            $form .= $this->getDuplicateProgressHtml($pmb_url_base . 'rest.php/autorities/');
            $form .= $this->getDeleteProgressHtml($pmb_url_base . 'rest.php/autorities/');
        }

        return $form;
    }

    // enregistre le thesaurus en base.
    public function save()
    {
        global $msg;
        global $pmb_synchro_rdf;

        if ($this->libelle_thesaurus == '') {
            die("Erreur de creation thesaurus");
        }
        if ($this->langue_defaut == '') {
            $this->langue_defaut = 'fr_FR';
        }
        if ($this->id_thesaurus) {
            // mise a jour thesaurus

            // Detecte la premiere activation du versionnage (avant UPDATE)
            $activating_versionnage = false;
            if ($this->version_enabled) {
                $q_check = "SELECT version_enabled FROM thesaurus WHERE id_thesaurus = '" . $this->id_thesaurus . "'";
                $r_check = pmb_mysql_query($q_check);
                $row_check = pmb_mysql_fetch_object($r_check);
                $activating_versionnage = !intval($row_check->version_enabled);
            }

            $q = "update thesaurus set libelle_thesaurus = '" . $this->libelle_thesaurus . "' ";
            $q .= ", active = '" . $this->active . "' ";
            $q .= ", opac_active = '" . $this->opac_active . "' ";
            $q .= ", langue_defaut = '" . $this->langue_defaut . "' ";
            $q .= ", version_enabled = IF(version_enabled = 1, 1, '" . intval($this->version_enabled) . "') ";
            $q .= ", version_rights_all = '" . $this->version_rights_all . "' ";
            $q .= ", version_rights = '" . $this->version_rights . "' ";
            $q .= "where id_thesaurus = '" . $this->id_thesaurus . "' ";
            pmb_mysql_query($q);

            // Creation de la version initiale lors de la premiere activation du versionnage
            if ($activating_versionnage) {
                $this->createInitialVersion();
            }

            //Traductions
            $translation = new translation($this->id_thesaurus, 'thesaurus');
            $translation->update("libelle_thesaurus");
        } else {

            //creation thesaurus
            $q = "insert into thesaurus set libelle_thesaurus = '" . $this->libelle_thesaurus . "' ";
            $q .= ", active = '1' ";
            $q .= ", opac_active = '1' ";
            $q .= ", langue_defaut = '" . $this->langue_defaut . "' ";
            $q .= ", version_rights_all = " . $this->version_rights_all . " ";
            $q .= ", version_rights = '" . $this->version_rights . "' ";
            pmb_mysql_query($q);
            $this->id_thesaurus = pmb_mysql_insert_id();


            //creation noeud racine
            $noeud = new noeuds();
            $noeud->autorite = 'TOP';
            $noeud->num_parent = 0;
            $noeud->num_renvoi_voir = 0;
            $noeud->visible = '0';
            $noeud->num_thesaurus = $this->id_thesaurus;
            $noeud->save();

            $this->num_noeud_racine = $noeud->id_noeud;

            //rattachement noeud racine au thesaurus
            $q = "update thesaurus set num_noeud_racine = '" . $this->num_noeud_racine . "' ";
            $q .= "where id_thesaurus = '" . $this->id_thesaurus . "' ";
            pmb_mysql_query($q);

            //creation noeud orphelins
            $noeud = new noeuds();
            $noeud->autorite = 'ORPHELINS';
            $noeud->num_parent = $this->num_noeud_racine;
            $noeud->num_renvoi_voir = 0;
            $noeud->visible = '0';
            $noeud->num_thesaurus = $this->id_thesaurus;
            $noeud->save();
            $this->num_noeud_orphelins = $noeud->id_noeud;

            //Creation categorie orphelins langue par defaut
            $categ = new categories($this->num_noeud_orphelins, $this->langue_defaut);
            $categ->libelle_categorie = $msg["thes_orphelins"];
            $categ->save();

            //creation noeud non classes;
            $noeud = new noeuds();
            $noeud->autorite = 'NONCLASSES';
            $noeud->num_parent = $this->num_noeud_racine;
            $noeud->num_renvoi_voir = 0;
            $noeud->visible = '0';
            $noeud->num_thesaurus = $this->id_thesaurus;
            $noeud->save();
            $this->num_noeud_nonclasses = $noeud->id_noeud;

            //Creation categorie non classes langue par defaut
            $categ = new categories($this->num_noeud_nonclasses, $this->langue_defaut);
            $categ->libelle_categorie = $msg["thes_non_classes"];
            $categ->save();

            //Traductions
            $translation = new translation($this->id_thesaurus, 'thesaurus');
            $translation->update("libelle_thesaurus");

            // Creation version initiale
            if ($this->version_enabled) {

                $q = "UPDATE thesaurus SET version_enabled = 1 WHERE id_thesaurus = '" . $this->id_thesaurus . "'";
                pmb_mysql_query($q);
                $this->createInitialVersion();
            }
        }
        //mise a jour dans la base rdf
        if ($pmb_synchro_rdf) {
            $synchro_rdf = new synchro_rdf();
            $synchro_rdf->updateAuthority($this->id_thesaurus, 'thesaurus');
        }
    }

    // supprime le thesaurus.
    public static function delete($id_thes = 0)
    {
        global $pmb_synchro_rdf;

        $id_thes = intval($id_thes);
        if (!$id_thes) {
            return false;
        }

        if ($pmb_synchro_rdf) {
            $synchro_rdf = new synchro_rdf();
        }

        $thesaurus = thesaurus::get_instance($id_thes);
        $versionEnabled = $thesaurus->hasVersionEnabled();
        if ($versionEnabled) {
            // Si on a un thesaurus versionnable, on le desactive pour ne pas logger toutes les suppressions des noeuds
            // Car elle seront supprime par la suite.

            $query = "UPDATE thesaurus SET version_enabled=0 WHERE id_thesaurus=" . $id_thes;
            pmb_mysql_query($query);

            $thesaurus->version_enabled = 0;
        }

        $q = "select id_noeud from noeuds where num_thesaurus = '" . $id_thes . "' ";
        $r = pmb_mysql_query($q);
        while ($row = pmb_mysql_fetch_row($r)) {
            noeuds::delete($row[0]);
            if ($pmb_synchro_rdf) {
                $synchro_rdf->delConcept($row[0]);
            }
        }

        // Supprime le changelog et les versions liees a ce thesaurus
        if ($versionEnabled) {
            $versions = ThesaurusVersionOrm::find('num_thesaurus', $id_thes);
            foreach ($versions as $version) {
                ThesaurusChangelogOrm::deleteWhere('num_version', $version->id_version);
            }
            ThesaurusVersionOrm::deleteWhere('num_thesaurus', $id_thes);
        }

        $q = "delete from thesaurus where id_thesaurus = '" . $id_thes . "' ";
        $r = pmb_mysql_query($q);

        translation::delete($id_thes, "thesaurus", "libelle_thesaurus");

        if ($pmb_synchro_rdf) {
            $synchro_rdf->delThesaurusDefinition($id_thes);
        }
    }

    //Retourne un objet thesaurus a partir de l'ID d'un de ses noeuds
    public static function getByEltId($id_noeud)
    {
        $id_noeud = intval($id_noeud);
        $q = "select num_thesaurus from noeuds where id_noeud = '" . $id_noeud . "' ";
        $r = pmb_mysql_query($q);
        if (pmb_mysql_num_rows($r) == 0) return NULL;
        return thesaurus::get_instance(pmb_mysql_result($r, 0, 0));
    }

    //Indique si un thesaurus possede des categories autres que les categories de base (TOP, ORPHELINS, NONCLASSES)
    public static function hasCateg($id_thes = 0)
    {
        $id_thes = intval($id_thes);
        $q = "select count(1) from noeuds where num_thesaurus = '" . $id_thes . "' ";
        $r = pmb_mysql_query($q);
        if (pmb_mysql_result($r, 0, 0) > 3) return TRUE;
        else return FALSE;
    }

    //Indique si un thesaurus est utilise pour les notices
    public static function hasNotices($id_thes = 0)
    {
        $id_thes = intval($id_thes);
        $q = "select count(1) from notices_categories, noeuds where noeuds.num_thesaurus = '" . $id_thes . "' ";
        $q .= "and noeuds.id_noeud = notices_categories.num_noeud ";
        $r = pmb_mysql_query($q);
        if (pmb_mysql_result($r, 0, 0) != 0) return TRUE;
        else return FALSE;
    }

    //Retourne un tableau des langues affichees dans les thesaurus
    public static function getTranslationsList()
    {
        $q = "select valeur_param from parametres where type_param = 'thesaurus' and sstype_param = 'liste_trad' ";
        $r = pmb_mysql_query($q);
        $a = explode(',', pmb_mysql_result($r, 0, 0));
        return $a;
    }

    public static function get_thesaurus_defaut()
    {
        global $thesaurus_defaut;

        $thesaurus_defaut = intval($thesaurus_defaut);
        if ($thesaurus_defaut) {
            $query = "select * from thesaurus where id_thesaurus = '" . $thesaurus_defaut . "' ";
            $result = pmb_mysql_query($query);
            if (!pmb_mysql_num_rows($result)) {
                $thesaurus_defaut = 0;
            }
        }
        return $thesaurus_defaut;
    }

    //recuperation du thesaurus session
    public static function getSessionThesaurusId()
    {
        global $thesaurus_mode_pmb;
        global $deflt_thesaurus;

        if (!isset($_SESSION["id_thesau"])) {

            //choix du thesaurus a afficher si l'on est pas deja dans un thesaurus
            //thesaurus par defaut de l'application en mode monothesaurus
            //thesaurus par defaut de l'utilisateur en mode multithesaurus

            switch ($thesaurus_mode_pmb) {    // gestion niveau thesaurus
                case "0":                    // Mono thesaurus
                    $id_thes = static::get_thesaurus_defaut();
                    $_SESSION["id_thesau"] = $id_thes;
                    break;
                case "1":                    // Multi thesaurus
                    if (!$deflt_thesaurus) {
                        $id_thes = static::get_thesaurus_defaut();
                    } else {
                        $id_thes = $deflt_thesaurus;
                    }
                    $_SESSION["id_thesau"] = $id_thes;
                    break;

                default:                    //mal defini -> Mono thesaurus
                    $id_thes = static::get_thesaurus_defaut();
                    $_SESSION["id_thesau"] = $id_thes;
                    break;
            }
        }
        return $_SESSION["id_thesau"];
    }

    //definition du thesaurus session
    public static function setSessionThesaurusId($id_thes)
    {
        $_SESSION["id_thesau"] = $id_thes;
    }


    //recuperation du thesaurus session pour les notices
    public static function getNoticeSessionThesaurusId()
    {
        global $thesaurus_mode_pmb;
        global $deflt_thesaurus;

        if (!isset($_SESSION["notice_id_thes"]) || !$_SESSION["notice_id_thes"]) {

            //choix du thesaurus a afficher si l'on est pas deja dans un thesaurus
            //thesaurus par defaut de l'application en mode monothesaurus
            //thesaurus par defaut de l'utilisateur en mode multithesaurus

            switch ($thesaurus_mode_pmb) {    // gestion niveau thesaurus
                case "0":                    // Mono thesaurus
                    $id_thes = static::get_thesaurus_defaut();
                    $_SESSION["notice_id_thes"] = $id_thes;
                    break;
                case "1":                    // Multi thesaurus
                    if (!$deflt_thesaurus) {
                        $id_thes = static::get_thesaurus_defaut();
                    } else {
                        $id_thes = $deflt_thesaurus;
                    }
                    $_SESSION["notice_id_thes"] = $id_thes;
                    break;
                default:                    //mal defini -> Mono thesaurus
                    $id_thes = static::get_thesaurus_defaut();
                    $_SESSION["notice_id_thes"] = $id_thes;
                    break;
            }
        }
        return $_SESSION["notice_id_thes"];
    }


    //definition du thesaurus session pour les notices
    public static function setNoticeSessionThesaurusId($id_thes)
    {
        $_SESSION["notice_id_thes"] = $id_thes;
    }


    //retourne le libelle du thesaurus
    public function getLibelle($id_thes = 0)
    {
        if (!$id_thes) {
            return $this->libelle_thesaurus;
        } else {
            $q = "select libelle_thesaurus from thesaurus where id_thesaurus = '" . $id_thes . "' limit 1";
            $r = pmb_mysql_query($q);
            return pmb_mysql_result($r, 0, 0);
        }
    }


    /**
     *  Retourne une liste des thesaurus et de leur proprietes
     *
     * @return [
     *     id_thesaurus => [
     *         id_thesaurus        => 'Identifiant thesaurus',
     *         libelle_thesaurus   => 'Nom du Thesaurus',
     *         langue_defaut       => 'Langue par defaut (fr_FR, ...)',
     *         active              => 'Active (O/1)',
     *         opac_active         => 'Active en OPAC (O/1)',
     *         num_noeud_racine    => 'Identifiant noeud racine',
     *         thesaurus_order     => 'Ordre d'affichage',
     *         version_enabled     => 'Versionnement actif (O/1)',
     *         version_rights      => 'Droits de versionnement',
     *         version_rights_all  => 'Droits de versionnement pour tous',
     *         version_name        => 'Nom de la version',
     *         version_num         => 'Numéro de la version',
     *     ]
     * ]
     */
    public static function getFullThesaurusList()
    {
        if (!is_null(static::$fullThesaurusList)) {
            return static::$fullThesaurusList;
        }
        static::$fullThesaurusList = [];
        $q = "SELECT thesaurus.*, thesaurus_version.num as version_num, thesaurus_version.name as version_name
        FROM thesaurus LEFT JOIN thesaurus_version ON thesaurus.num_thesaurus_version = thesaurus_version.id_version";
        $r = pmb_mysql_query($q);
        if (!pmb_mysql_num_rows($r)) {
            return static::$fullThesaurusList;
        }
        while ($row = pmb_mysql_fetch_assoc($r)) {
            static::$fullThesaurusList[$row['id_thesaurus']] = $row;
        }
        return static::$fullThesaurusList;
    }


    /**
     * Retourne une liste simplifiee des thesaurus triee par libelle
     *
     * @param string $restrict : liste d'id de thesaurus separes par une virgule
     * @return [
     *     id_thesaurus => 'libelle_thesaurus'
     * ]
     */
    public static function getThesaurusList(string $restrict = "")
    {
        global $msg;
        if (is_null(static::$thesaurusList)) {
            static::$thesaurusList = [];
            static::getFullThesaurusList();

            foreach (static::$fullThesaurusList as $k => $v) {
                $version = '';
                if($v['version_enabled']) {
                    $version = sprintf($msg['thes_version_num_label_in_selector'], $v['version_name'], $v['version_num']);
                }
                static::$thesaurusList[$k] = $v['libelle_thesaurus'] . $version;
            }
        }

        $restrict = preg_replace("/[^0-9|,]/", "", $restrict);
        $restrict = !empty($restrict) ? explode(',', $restrict) : [];

        // Restriction aux thesaurus definis en parametres
        $thesaurusList = [];
        if( empty($restrict) ) {
            $thesaurusList = static::$thesaurusList;
        } else {
            foreach( $restrict as $v ) {
                if( !empty(static::$thesaurusList[$v]) ) {
                    $thesaurusList[$v] = static::$thesaurusList[$v];
                }
            }
        }

        // Tri alpha
        asort($thesaurusList, SORT_STRING|SORT_FLAG_CASE);

        return $thesaurusList;
    }

    public function get_translated_libelle_thesaurus()
    {
        return translation::get_translated_text($this->id_thesaurus, 'thesaurus', 'libelle_thesaurus',  $this->libelle_thesaurus);
    }

    //retourne un selecteur de thesaurus
    public static function getSelector($selected, $base_url = '')
    {
        global $msg, $charset;
        global $thesaurus_mode_pmb;

        $liste_thesaurus = static::getThesaurusList();
        $selector = '';

        if ($thesaurus_mode_pmb != 0) {     //la liste des thesaurus n'est pas affichee en mode monothesaurus
            $selector = "<select class='saisie-30em' name='id_thes' ";
            $selector .= "onchange = \"document.location = '" . $base_url . "&id_thes='+this.value; \">";
            foreach ($liste_thesaurus as $id_thesaurus => $libelle_thesaurus) {
                $selector .= "<option value='" . $id_thesaurus . "' ";;
                if ($id_thesaurus == $selected) $selector .= " selected";
                $selector .= ">" . htmlentities($libelle_thesaurus, ENT_QUOTES, $charset) . "</option>";
            }
            $selector .= "<option value=-1 ";
            if ($selected == -1) $selector .= "selected ";
            $selector .= ">" . htmlentities($msg['thes_all'], ENT_QUOTES, $charset) . "</option>";
            $selector .= "</select>&nbsp;";
        }
        return $selector;
    }

    //retourne un selecteur de thesaurus dans le formulaire d'une categorie
    public static function getSelectorCategoryForm($selected, $base_url = '', $on_create = true)
    {
        global $charset;
        global $thesaurus_mode_pmb;

        if (
            (Request::isOnCategoryForm() || Request::isFromCategoryForm() || Request::isFromSelectorCategoryForm()) &&
            thesaurus::isVersionable(thesaurus::getSessionThesaurusId())
        ) {
            // On est sur le formulaire de creation de categorie pour le thesaurus versionnable
            $restrict = '';
        } else {
            $query = 'SELECT id_thesaurus FROM thesaurus WHERE version_enabled != 1';
            $result = pmb_mysql_query($query);

            $restrict = [];
            if (pmb_mysql_num_rows($result)) {
                while ($row = pmb_mysql_fetch_assoc($result)) {
                    $restrict[] = $row['id_thesaurus'];
                }
                pmb_mysql_free_result($result);
            }
            $restrict = implode(',', $restrict);
        }

        $liste_thesaurus = static::getThesaurusList($restrict);
        $selector = '';

        if ($thesaurus_mode_pmb != 0) {     //la liste des thesaurus n'est pas affichee en mode monothesaurus
            $selector = "<select class='saisie-30em' id='category_id_thes' name='id_thes' ";
            if (!$on_create) {
                $selector .= "disabled='disabled' style='background-color:#ccc' ";
            }
            $selector .= "onchange = \"if(confirm(pmbDojo.messages.getMessage('grid', 'category_change_thesaurus_confirm'))) {unload_off();document.location = '" . $base_url . "&id_thes='+this.value;} \" backbone='yes'>";
            foreach ($liste_thesaurus as $id_thesaurus => $libelle_thesaurus) {
                $selector .= "<option value='" . $id_thesaurus . "' ";;
                if ($id_thesaurus == $selected) $selector .= " selected";
                $selector .= ">" . htmlentities($libelle_thesaurus, ENT_QUOTES, $charset) . "</option>";
            }
            $selector .= "</select>&nbsp;";
        }
        return $selector;
    }

    /**
     * Cree la version initiale du thesaurus lors de la premiere activation du versionnage.
     * Insere une ligne dans thesaurus_version et met a jour num_thesaurus_version.
     *
     * @return void
     */
    private function createInitialVersion()
    {
        global $msg;
        $q_version = "INSERT INTO thesaurus_version SET
            num_thesaurus = '" . $this->id_thesaurus . "',
            num_parent_version = 0,
            name = '" . addslashes($msg['thes_version_principale']) . "',
            num = '" . addslashes($msg['thes_version_principale_num']) . "',
            is_current = 1";
        pmb_mysql_query($q_version);
        $new_version_id = pmb_mysql_insert_id();
        $q_link = "UPDATE thesaurus SET num_thesaurus_version = '" . $new_version_id . "' WHERE id_thesaurus = '" . $this->id_thesaurus . "'";
        pmb_mysql_query($q_link);
        $this->num_thesaurus_version = $new_version_id;
        $this->createInitialChangelog();
    }

    /**
     * Cree le changelog initial pour la version courante.
     * Supprime les entrees existantes et insere les noeuds et categories dans l'ordre de creation.
     *
     * @return void
     */
    private function createInitialChangelog()
    {
        global $PMBusername;

        if (!$this->version_enabled || !$this->num_thesaurus_version) {
            return;
        }

        $id_version = intval($this->num_thesaurus_version);

        // Supprime les entrees de changelog existantes pour la version courante
        pmb_mysql_query("DELETE FROM thesaurus_changelog WHERE num_version = '" . $id_version . "'");

        // Noeuds et categories : ordre de creation par id_noeud
        $q = "SELECT
        noeuds.id_noeud,
        noeuds.autorite as autorite,
        noeuds.num_parent,
        noeuds.num_renvoi_voir,
        noeuds.visible,
        noeuds.authority_import_denied,
        noeuds.not_use_in_indexation,
        categories.langue,
        categories.libelle_categorie,
        categories.note_application,
        categories.comment_public,
        categories.comment_voir,
        ark.identifier
        FROM noeuds
        LEFT JOIN categories ON noeuds.id_noeud = categories.num_noeud
        LEFT JOIN authorities ON authorities.num_object = categories.num_noeud AND authorities.type_object = " . AUT_TABLE_CATEG . "
        LEFT JOIN ark_entity_pmb on ark_entity_pmb.entity_id = authorities.id_authority AND ark_entity_pmb.entity_type= " . TYPE_CATEGORY . "
        LEFT JOIN ark on ark.id = ark_entity_pmb.num_ark
        WHERE noeuds.num_thesaurus = '" . $this->id_thesaurus . "'
        ORDER BY FIELD(noeuds.autorite, 'NONCLASSES', 'ORPHELINS', 'TOP') DESC,
                 noeuds.id_noeud ASC,
                 langue ASC";

        $r = pmb_mysql_query($q);
        while ($row = pmb_mysql_fetch_object($r)) {

            // On ignore les renvois vers des noeuds hors du thesaurus courant
            $num_renvoi_voir = 0;
            $filtered_num_renvoi_voir = $this->filterNodesInThesaurus([$row->num_renvoi_voir]);
            if (!empty($filtered_num_renvoi_voir)) {
                $num_renvoi_voir = $filtered_num_renvoi_voir[0];
            }

            $options = json_encode([
                'node_ark'                  => $row->identifier ?? '',
                'node_id'                   => (int)$row->id_noeud,
                'authority'                 => $row->autorite,
                'visible'                   => (int)$row->visible,
                'authority_import_denied'   => (int)$row->authority_import_denied,
                'not_use_in_indexation'     => (int)$row->not_use_in_indexation,
                'language'                  => $row->langue ?? '',
                'label'                     => $row->libelle_categorie ?? '',
                'note'                      => $row->note_application ?? '',
                'comment'                   => $row->comment_public ?? '',
                // Noeud Parent
                'parent_id'                 => (int)$row->num_parent,
                'parent_ark'                => categories::getArkIdentifier($row->num_parent),
                // Noeud Renvoi voir
                'see_id'                    => (int)$num_renvoi_voir,
                'see_ark'                   => categories::getArkIdentifier($num_renvoi_voir),
            ]);
            pmb_mysql_query("INSERT INTO thesaurus_changelog (user_name, num_version, command, type, options, rollback)
                VALUES ('". addslashes($PMBusername) ."', '" . $id_version . "', 'CREATE', NULL, '" . addslashes($options) . "', NULL)");
        }

        // Voir aussi : ordre par noeud origine, destination, langue
        // On ignore les voir_aussi vers d'autres thesaurus
        $q = "SELECT
            va.*
            FROM voir_aussi va
            JOIN noeuds noeuds_orig ON noeuds_orig.id_noeud = va.num_noeud_orig
            JOIN noeuds noeuds_dest ON noeuds_dest.id_noeud = va.num_noeud_dest
            WHERE noeuds_orig.num_thesaurus = '" . $this->id_thesaurus . "'
            AND noeuds_dest.num_thesaurus = '" . $this->id_thesaurus . "'
            ORDER BY va.num_noeud_orig ASC, va.num_noeud_dest ASC, va.langue ASC";

        $r = pmb_mysql_query($q);
        while ($row = pmb_mysql_fetch_object($r)) {
            $options = json_encode([
                'node_ark'       => categories::getArkIdentifier($row->num_noeud_orig),
                'node_id'        => (int)$row->num_noeud_orig,
                'see_also_id'    => (int)$row->num_noeud_dest,
                'see_also_ark'   => categories::getArkIdentifier($row->num_noeud_dest),
            ]);
            pmb_mysql_query("INSERT INTO thesaurus_changelog (user_name, num_version, command, type, options, rollback)
                VALUES ('". addslashes($PMBusername) ."', '" . $id_version . "', 'ADD', 'see_also', '" . addslashes($options) . "', NULL)");
        }
    }

    /**
     * Ajoute une entree de changelog pour la creation d'un terme.
     *
     * @param array $options Options du terme
     * @return void
     */
    public function createTermInChangelog(array $options): void
    {
        global $PMBusername;

        $options['node_ark'] = $options['node_ark'] ?? '';
        $options['node_id'] = $options['node_id'] ?? 0;
        if (!$options['node_id']) {
            return;
        }

        $options['authority'] = $options['authority'] ?? '';
        $options['visible'] = $options['visible'] ?? '';
        $options['authority_import_denied'] = $options['authority_import_denied'] ?? 0;
        $options['not_use_in_indexation'] = $options['not_use_in_indexation'] ?? '';
        $options['language'] = $options['language'] ?? $this->langue_defaut;
        $options['label'] = $options['label'] ?? '';
        $options['note'] = $options['note'] ?? '';
        $options['comment'] = $options['comment'] ?? '';
        // Noeud Parent
        $options['parent_id'] = $options['parent_id'] ?? 0;
        $options['parent_ark'] = $options['parent_ark'] ?? '';
        // Noeud Renvoi voir
        $options['see_id'] = $options['see_id'] ?? 0;
        $options['see_ark'] = $options['see_ark'] ?? '';

        $q = "INSERT INTO thesaurus_changelog (user_name, num_version, command, type, options, rollback)
            VALUES ('". addslashes($PMBusername) ."', '" . $this->num_thesaurus_version . "', 'CREATE', NULL, '" . addslashes(json_encode($options)) . "', NULL)";
        pmb_mysql_query($q);
    }

    /**
     * Ajoute une entree de changelog pour la mise a jour d'un terme.
     *
     * @param string $type Type de mise a jour (label, comment, note, autorite, see, parent, not_use_in_indexation)
     * @param array $options Options du terme
     * @param array $rollback Options de rollback
     * @return void
     */
    public function updateTermInChangelog(string $type, array $options, array $rollback): void
    {
        global $PMBusername;

        $type = in_array($type, ['label', 'comment', 'note', 'autorite', 'see', 'parent', 'not_use_in_indexation']) ? $type : '';
        if (!$type) {
            return;
        }
        $options['node_ark'] = $options['node_ark'] ?? '';
        $options['node_id'] = $options['node_id'] ?? 0;
        if (!$options['node_id']) {
            return;
        }

        $q = "INSERT INTO thesaurus_changelog (user_name, num_version, command, type, options, rollback)
            VALUES ('". addslashes($PMBusername) ."', '" . $this->num_thesaurus_version . "', 'UPDATE', '" . $type . "', '" . addslashes(json_encode($options)) . "', '" . addslashes(json_encode($rollback)) . "')";
        pmb_mysql_query($q);
    }

    /**
     * Ajoute une entree de changelog pour la suppression d'un terme.
     *
     * @param string $type Type de suppression (see_also)
     * @param array $options Options du terme
     * @param array $rollback Options de rollback
     * @return void
     */
    public function removeTermInChangelog(string $type, array $options, array $rollback): void
    {
        global $PMBusername;

        $type = in_array($type, ['see_also']) ? $type : '';
        if (!$type) {
            return;
        }
        $options['node_ark'] = $options['node_ark'] ?? '';
        $options['node_id'] = $options['node_id'] ?? 0;
        if (!$options['node_id']) {
            return;
        }

        $q = "INSERT INTO thesaurus_changelog (user_name, num_version, command, type, options, rollback)
            VALUES ('". addslashes($PMBusername) ."', '" . $this->num_thesaurus_version . "', 'REMOVE', '" . $type . "', '" . addslashes(json_encode($options)) . "', '" . addslashes(json_encode($rollback)) . "')";
        pmb_mysql_query($q);
    }

    /**
     * Ajoute une entree de changelog pour l'ajout d'un terme.
     *
     * @param string $type Type d'ajout (see_also)
     * @param array $options Options du terme
     * @param array $rollback Options de rollback
     * @return void
     */
    public function addTermInChangelog(string $type, array $options, array $rollback): void
    {
        global $PMBusername;

        $type = in_array($type, ['see_also']) ? $type : '';
        if (!$type) {
            return;
        }
        $options['node_ark'] = $options['node_ark'] ?? '';
        $options['node_id'] = $options['node_id'] ?? 0;
        if (!$options['node_id']) {
            return;
        }

        $q = "INSERT INTO thesaurus_changelog (user_name, num_version, command, type, options, rollback)
            VALUES ('". addslashes($PMBusername) ."', '" . $this->num_thesaurus_version . "', 'ADD', '" . $type . "', '" . addslashes(json_encode($options)) . "', '" . addslashes(json_encode($rollback)) . "')";
        pmb_mysql_query($q);
    }

    /**
     * Ajoute une entree de changelog pour la suppression d'un terme.
     *
     * @param array $options Options du terme
     * @param array $rollback Options de rollback
     * @return void
     */
    public function deleteTermInChangelog(array $options, array $rollback): void
    {
        global $PMBusername;

        $options['node_ark'] = $options['node_ark'] ?? '';
        $options['node_id'] = $options['node_id'] ?? 0;
        if (!$options['node_id']) {
            return;
        }
        $q = "INSERT INTO thesaurus_changelog (user_name, num_version, command, type, options, rollback)
            VALUES ('". addslashes($PMBusername) ."', '" . $this->num_thesaurus_version . "', 'DELETE', '', '" . addslashes(json_encode($options)) . "', '" . addslashes(json_encode($rollback)) . "')";
        pmb_mysql_query($q);
    }

    /**
     * Ajoute une entree de changelog pour le remplacement d'un terme.
     *
     * @param array $options Options du terme
     * @param array $rollback Options de rollback
     * @return void
     */
    public function replaceTermInChangelog(array $options, array $rollback): void
    {
        global $PMBusername;

        $options['node_ark'] = $options['node_ark'] ?? '';
        $options['node_id'] = $options['node_id'] ?? 0;
        if (!$options['node_id']) {
            return;
        }

        $q = "INSERT INTO thesaurus_changelog (user_name, num_version, command, type, options, rollback)
            VALUES ('". addslashes($PMBusername) ."', '" . $this->num_thesaurus_version . "', 'REPLACE', '', '" . addslashes(json_encode($options)) . "', '" . addslashes(json_encode($rollback)) . "')";
        pmb_mysql_query($q);
    }

    /**
     * Filtre les IDs de noeuds pour ne retourner que ceux presents dans le thesaurus.
     *
     * @param array $nodeIds IDs des noeuds a filtrer
     * @return array IDs des noeuds qui sont presents dans le thesaurus
     */
    public function filterNodesInThesaurus(array $nodeIds): array
    {
        $nodeIds = array_filter($nodeIds, function ($id) {
            return intval($id) > 0;
        });
        if (empty($nodeIds)) {
            return [];
        }
        $q = "SELECT id_noeud FROM noeuds WHERE num_thesaurus = " . $this->id_thesaurus . " AND id_noeud IN (" . implode(',', $nodeIds) . ")";
        $result = pmb_mysql_query($q);
        $filteredIds = [];
        while ($row = pmb_mysql_fetch_assoc($result)) {
            $filteredIds[] = $row['id_noeud'];
        }
        return $filteredIds;
    }

    /**
     * Indique si un thesaurus est versionnable.
     *
     * @param integer $id ID du thesaurus
     * @return boolean
     */
    public static function isVersionable(int $id): bool
    {
        $query = "SELECT 1 FROM thesaurus WHERE version_enabled = 1 AND id_thesaurus = " . $id;
        $result = pmb_mysql_query($query);
        return 1 == pmb_mysql_result($result, 0);
    }
}
