<?php
/**
 * Ce fichier est destine a permettre la surcharge de certaines methodes de
 * la classe om_dbform pour des besoins specifiques de l'application
 *
 * @package openmairie_exemple
 * @version SVN : $Id: om_dbform.class.php 6137 2016-03-09 10:42:13Z nhaye $
 */

/**
 *
 */
require_once PATH_OPENMAIRIE."om_dbform.class.php";

/**
 *
 */
class om_dbform extends dbForm {

    /**
     * État du chargement des actions des modules
     */
    protected $modules_actions_loaded = false;

    /**
     * Liste des métadonnées communes à l'ensemble des fichiers de l'application
     */
    var $metadata_global = array(
        "codeProduit" => "getCodeProduit",
    );

    /**
     * Renvoie une réprésentation "string" de l'objet
     *
     * @return string
     */
    public function to_string() {
        $ref = new \ReflectionClass($this);
        return $ref->getShortName().'#'.$this->getVal($this->clePrimaire);
    }

    /**
     * Surcharge pour déplacer la fonction 'init_record_data'
     * avant 'init_class_actions'
     */
    /*function constructeur($id, &$dnu1 = null, $dnu2 = null) {
        // Initialisation de la classe 'application'.
        $this->init_om_application();
        // Logger
        $this->addToLog(__METHOD__."()", VERBOSE_MODE);
        // @deprecated A supprimer
        // Ce raccourci rend la réalité du code difficilement lisible, il est
        // préférable de ne pas l'utiliser
        $this->db = $this->f->db;
        // Chargement de la configuration depuis un éventuel script form.inc
        $this->load_var_from_sql_forminc();
        // Initialise les attributs 'champs', 'longueurMax', 'type', 'flags' et
        // 'val' de la classe pour un enregistrement donné.
        $this->init_record_data($id);
        // Appel de la méthode de définition des tableaux d'actions.
        $this->init_class_actions();
    }*/

    /**
     * Surcharge pour déclencher un hook après l'initialisation de l'objet
     */
    /*function init_record_data($id) {
        parent::init_record_data($id);
        $data = array('id' => $id);
        $this->f->module_manager->run_hooks('init_record_data_post', $this, $data);
    }*/

    /**
     * Surcharge pour permettre l'ajout d'action par les modules
     * @return void
     */
    /*function init_class_actions() {
        $this->f->log(__METHOD__, 'BEGIN (class: '.get_class($this).')');
        $data = array();
        $this->f->module_manager->run_hooks('init_class_actions_pre', $this, $data);
        parent::init_class_actions();
        $this->load_actions_for_modules();
        $data = array();
        $this->f->module_manager->run_hooks('init_class_actions_post', $this, $data);
        $this->f->log(__METHOD__, 'END (class: '.get_class($this).')');
    }*/

    /**
     * Charge les actions des modules
     *
     * @return void
     */
    protected function load_actions_for_modules() {
        // si les modules sont désactivés on ne défini aucune action
        if (! $this->f->module_manager->enabled_for(get_class($this))) {
            //$this->f->log(__METHOD__, "'".get_class($this)."': module not enabled");
            return;
        }
        $this->f->log(__METHOD__, 'BEGIN (class: '.get_class($this).')');
        $trace = debug_backtrace();
        $caller = $trace[1];
        $caller_func = $caller['function'];
        $caller_class = $caller['class'] ?? null;
        $caller_method = ($caller_class ? $caller_class.'::' : '').$caller_func;
        $this->f->log(__METHOD__, "called by '$caller_method'");
        if (! $this->modules_actions_loaded) {
            $modules = $this->f->module_manager->get_modules_for_object($this);
            foreach($modules as $module) {
                $actions = $module->get_actions();
                $this->f->log(__METHOD__, ''.$module->to_string().': '.count($actions).' actions');
                foreach($actions as $key => $action) {
                    $this->class_actions[$key] = $action->to_array();
                    $action_debug = $this->class_actions[$key];
                    if (isset($action_debug['callback'][0]) && $action_debug['callback'][0] === $module) {
                        $action_debug['callback'][0] = $module->get_short_name();
                    }
                    $this->f->log(__METHOD__, "add class action '$key': ".var_export($action_debug, true));
                }
            }
            $this->modules_actions_loaded = true;
        }
        $this->f->log(__METHOD__, 'END (class: '.get_class($this).')');
    }

    /**
     * Ajoute au rendu HTML les scripts JS et styles provenant des modules
     *
     * @return void
     */
    protected function enqueue_scripts_and_styles_of_modules() {
        $this->f->log(__METHOD__, 'BEGIN (class: '.get_class($this).')');
        $modules = $this->f->module_manager->get_modules_for_object($this);
        $scripts_js_enqueued = array();
        $styles_css_enqueued = array();
        foreach($modules as $module) {
            $scripts_js = $module->get_header_scripts_js();
            foreach($scripts_js as $order => $path) {
                if (! in_array($path, $scripts_js_enqueued)) {
                    $this->f->addHTMLHeadJs(array($path), $order);
                    $scripts_js_enqueued[] = $path;
                }
            }
            $styles_css = $module->get_header_styles_css();
            foreach($styles_css as $order => $path) {
                if (! in_array($path, $styles_css_enqueued)) {
                    $this->f->addHTMLHeadCss(array($path), $order);
                    $styles_css_enqueued[] = $path;
                }
            }
        }
        $this->f->log(__METHOD__, 'END (class: '.get_class($this).')');
    }

    /**
     * Affiche en HTML les scripts JS et styles provenant des modules
     *
     * @param  string  $position  'top' ou 'bottom', pour ajouter en haut ou bas de page
     *
     * @return void
     */
    protected function display_scripts_and_styles_of_modules(string $position) {
        $this->f->log(__METHOD__, "($position) BEGIN (class: ".get_class($this).")");
        $html = '';
        $modules = $this->f->module_manager->get_modules_for_object($this);
        $scripts_js_enqueued = array();
        $styles_css_enqueued = array();
        foreach($modules as $module) {
            $scripts_js = $module->get_inline_scripts_js($position);
            foreach($scripts_js as $script_js) {
                if (is_array($script_js)) {
                    list($type, $file_or_script) = $script_js;
                    if ($type == 'file') {
                        if (! in_array($file_or_script, $scripts_js_enqueued)) {
                            $html .= '<script type="text/javascript" src="'.$file_or_script.'"></script>'."\n";
                            $scripts_js_enqueued[] = $file_or_script;
                        }
                    }
                    elseif ($type == 'script') {
                        $hash = sha1($file_or_script);
                        if (! in_array($hash, $scripts_js_enqueued)) {
                            $html .= '<script type="text/javascript">'.$file_or_script.'</script>'."\n";
                            $scripts_js_enqueued[] = $hash;
                        }
                    }
                }
                else {
                    if (! in_array($script_js, $scripts_js_enqueued)) {
                        $html .= '<script type="text/javascript" src="'.$script_js.'"></script>'."\n";
                        $scripts_js_enqueued[] = $script_js;
                    }
                }
            }
            $styles_css = $module->get_inline_styles_css($position);
            foreach($styles_css as $style_css) {
                if (is_array($style_css)) {
                    list($type, $file_or_script) = $style_css;
                    if ($type == 'file') {
                        if (! in_array($file_or_script, $styles_css_enqueued)) {
                            $html .= '<link rel="stylesheet" type="text/css" href="'.$file_or_script.'" />'."\n";
                            $styles_css_enqueued[] = $file_or_script;
                        }
                    }
                    elseif ($type == 'script') {
                        $hash = sha1($file_or_script);
                        if (! in_array($hash, $styles_css_enqueued)) {
                            $html .= '<style>'.$file_or_script.'</style>'."\n";
                            $styles_css_enqueued[] = $hash;
                        }
                    }
                }
                else {
                    if (! in_array($style_css, $styles_css_enqueued)) {
                        $html .= '<link rel="stylesheet" type="text/css" href="'.$style_css.'" />'."\n";
                        $styles_css_enqueued[] = $style_css;
                    }
                }
            }
        }
        if (! empty($html)) {
            echo $html;
        }
        $this->f->log(__METHOD__, "($position) END (class: ".get_class($this).")");
    }

    /**
     * Vue générique de callback pour les modules
     *
     *  @return void
     */
    public function view_callback() {
        $this->f->log(__METHOD__, 'BEGIN (class: '.get_class($this).')');
        $action = $this->f->get_submitted_get_value('action');
        if (! empty($action)) {
            $this->load_actions_for_modules();
            if (isset($this->class_actions[$action]['callback']) && is_callable($this->class_actions[$action]['callback'])) {
                return call_user_func($this->class_actions[$action]['callback'], $this);
            }
        }
        $this->f->log(__METHOD__, 'END (class: '.get_class($this).')');
    }

    /**
     * Surcharge pour tenir compte des actions définies par les modules
     *
     * @param  int|string  $action  Identifiant de l'action
     *
     * @return bool
     */
    public function is_portlet_action_defined($action) {
        $this->load_actions_for_modules();
        return parent::is_portlet_action_defined($action);
    }

    /**
     * Surcharge pour tenir compte des actions définies par les modules
     *
     * @param  int|string  $action  Identifiant de l'action
     *
     * @return bool
     */
    public function is_action_defined($action) {
        $this->load_actions_for_modules();
        return parent::is_action_defined($action);
    }

    /**
     * Surcharge pour tenir compte des actions définies par les modules
     *
     * @return array
     */
    public function get_class_actions() {
        $this->load_actions_for_modules();
        return parent::get_class_actions();
    }

    /**
     * Surcharge pour tenir compte des actions définies par les modules
     *
     * @param  int|string  $action  Identifiant de l'action
     * @param      string  $param   Clé de paramètre dont on veut la valeur
     *
     * @return mixed
     */
    public function get_action_param($action, $param) {
        $this->load_actions_for_modules();
        return parent::get_action_param($action, $param);
    }

    /**
     * Surcharge pour permettre aux modules d'exécuter un traitement
     *
     * @return void
     */
    function setValFAjout($val = array()) {
        $data = array('val' => &$val);
        $this->f->module_manager->run_hooks('setValFAjout_pre', $this, $data);
        parent::setValFAjout($val);
        $this->f->module_manager->run_hooks('setValFAjout_post', $this, $data);
    }

    /**
     * Surcharge pour permettre aux modules d'exécuter un traitement
     *
     * @return void
     */
    function setValF($val = array()) {
        $this->f->log(__METHOD__, 'BEGIN (class: '.get_class($this).')');
        $data = array('val' => &$val);
        $this->f->module_manager->run_hooks('setValF_pre', $this, $data);
        parent::setValF($val);
        $this->f->module_manager->run_hooks('setValF_post', $this, $data);
        $this->f->log(__METHOD__, 'END (class: '.get_class($this).')');
    }

    /**
     * Surcharge pour permettre aux modules d'exécuter un traitement
     *
     * @return void
     */
    function verifier($val = array(), &$dnu1 = null, $dnu2 = null) {
        $data = array('val' => &$val);
        $this->f->module_manager->run_hooks('verifier_pre', $this, $data);
        parent::verifier($val, $dnu1, $dnu2);
        $this->f->module_manager->run_hooks('verifier_post', $this, $data);
    }

    /**
     * Surcharge pour permettre aux modules d'exécuter un traitement
     *
     * @return void
     */
    function verifierAjout($val = array(), &$dnu1 = null) {
        $data = array('val' => &$val);
        $this->f->module_manager->run_hooks('verifierAjout_pre', $this, $data);
        parent::verifierAjout($val, $dnu1);
        $this->f->module_manager->run_hooks('verifierAjout_post', $this, $data);
    }

    /**
     * Surcharge pour permettre aux modules d'exécuter un traitement
     *
     * @return boolean
     */
    function triggerajouter($id, &$dnu1 = null, $val = array(), $dnu2 = null) {
        $data = array('val' => &$val, 'id' => $id);
        if (! $this->f->module_manager->run_hooks('triggerajouter_pre', $this, $data)) {
            return false;
        }

        $res = parent::triggerajouter($id, $dnu1, $val);

        $data = array('val' => &$val, 'id' => $id, 'result' => &$res);
        if (! $this->f->module_manager->run_hooks('triggerajouter_post', $this, $data)) {
            return false;
        }
        return $res;
    }

    /**
     * Surcharge pour permettre aux modules d'exécuter un traitement
     *
     * @return boolean
     */
    function triggerajouterapres($id, &$dnu1 = null, $val = array(), $dnu2 = null) {

        // hack pour compenser le fait que la méthode ajouter n'affecte pas la clePrimaire à l'objet.
        $has_set_clePrimlaire = false;
        if (! isset($this->val[$this->clePrimaire])) {
            $has_set_clePrimlaire = true;
            $this->val[$this->clePrimaire] = $id;
        }

        $data = array('val' => &$val, 'id' => $id);
        if (! $this->f->module_manager->run_hooks('triggerajouterapres_pre', $this, $data)) {
            return false;
        }

        $res = parent::triggerajouterapres($id, $dnu1, $val);

        // Assigner les champs 'valF' à 'val' lorsque l'ajout a réussi;
        if ($res !== false) {
            foreach($this->champs as $index => $champ) {
                $this->val[$index] = isset($this->valF[$champ]) ? $this->valF[$champ] : null;
            }
        // Si on es dans le cadre du hack décrit ci-dessus, et que l'appel a la méthode parente a échoué,
        // on désaffecte la cle primaire de l'objet (précédemment affectée)
        } elseif ($has_set_clePrimlaire) {
            unset($this->val[$this->clePrimaire]);
        }

        $data = array('val' => &$val, 'id' => $id, 'result' => &$res);
        if (! $this->f->module_manager->run_hooks('triggerajouterapres_post', $this, $data)) {
            return false;
        }
        return $res;
    }

    /**
     * Surcharge pour permettre aux modules d'exécuter un traitement
     *
     * @return boolean
     */
    function triggermodifier($id, &$dnu1 = null, $val = array(), $dnu2 = null) {
        $data = array('val' => &$val, 'id' => $id);
        if (! $this->f->module_manager->run_hooks('triggermodifier_pre', $this, $data)) {
            return false;
        }

        $res = parent::triggermodifier($id, $dnu1, $val);

        $data = array('val' => &$val, 'id' => $id, 'result' => &$res);
        if (! $this->f->module_manager->run_hooks('triggermodifier_post', $this, $data)) {
            return false;
        }
        return $res;
    }

    /**
     * Surcharge pour permettre aux modules d'exécuter un traitement
     *
     * @return boolean
     */
    function triggermodifierapres($id, &$dnu1 = null, $val = array(), $dnu2 = null) {
        $data = array('val' => &$val, 'id' => $id);
        if (! $this->f->module_manager->run_hooks('triggermodifierapres_pre', $this, $data)) {
            return false;
        }

        $res = parent::triggermodifierapres($id, $dnu1, $val);

        $data = array('val' => &$val, 'id' => $id, 'result' => &$res);
        if (! $this->f->module_manager->run_hooks('triggermodifierapres_post', $this, $data)) {
            return false;
        }
        return $res;
    }

    /**
     * Surcharge pour permettre aux modules d'exécuter un traitement
     *
     * @return boolean
     */
    function triggersupprimer($id, &$dnu1 = null, $val = array(), $dnu2 = null) {
        $data = array('val' => &$val, 'id' => $id);
        if (! $this->f->module_manager->run_hooks('triggersupprimer_pre', $this, $data)) {
            return false;
        }

        $res = parent::triggersupprimer($id, $dnu1, $val);

        $data = array('val' => &$val, 'id' => $id, 'result' => &$res);
        if (! $this->f->module_manager->run_hooks('triggersupprimer_post', $this, $data)) {
            return false;
        }
        return $res;
    }

    /**
     * Surcharge pour permettre aux modules d'exécuter un traitement
     *
     * @return boolean
     */
    function triggersupprimerapres($id, &$dnu1 = null, $val = array(), $dnu2 = null) {
        $data = array('val' => &$val, 'id' => $id);
        if (! $this->f->module_manager->run_hooks('triggersupprimerapres_pre', $this, $data)) {
            return false;
        }

        $res = parent::triggersupprimerapres($id, $dnu1, $val);

        $data = array('val' => &$val, 'id' => $id, 'result' => &$res);
        if (! $this->f->module_manager->run_hooks('triggersupprimerapres_post', $this, $data)) {
            return false;
        }
        return $res;
    }

    /**
     * Surcharge pour permettre aux modules d'exécuter un traitement
     *
     * @return void
     */
    function setOnchange(&$form, $maj) {
        $this->f->log(__METHOD__, 'BEGIN (class: '.get_class($this).')');
        $data = array('form' => &$form, 'maj' => $maj);
        $this->f->module_manager->run_hooks('setOnchange_pre', $this, $data);
        parent::setOnchange($form, $maj);
        $this->f->module_manager->run_hooks('setOnchange_post', $this, $data);
        $this->f->log(__METHOD__, 'END (class: '.get_class($this).')');
    }

    /**
     * Ajoute du code Javascript dans l'attribut 'onChange' au début.
     * @param  object  $form     Formulaire auquel appartient le champ
     * @param  string  $champ    Champ à modifier
     * @param  string  $js_code  Le code Javascript à injecter
     */
    public function addOnChangeBefore($form, string $champ, string $js_code) {
        $form->onchange[$champ] = $js_code.";\n".$form->onchange[$champ];
    }

    /**
     * Ajoute du code Javascript dans l'attribut 'onChange' à la fin.
     * @param  object  $form     Formulaire auquel appartient le champ
     * @param  string  $champ    Champ à modifier
     * @param  string  $js_code  Le code Javascript à injecter
     */
    public function addOnChangeAfter($form, string $champ, string $js_code) {
        $form->onchange[$champ] .= ";\n".$js_code;
    }

    /**
     * Surcharge pour permettre aux modules d'exécuter un traitement
     *
     * @return void
     */
    function setType(&$form, $maj) {
        $this->f->log(__METHOD__, 'BEGIN (class: '.get_class($this).')');
        $data = array('form' => &$form, 'maj' => $maj);
        $this->f->module_manager->run_hooks('setType_pre', $this, $data);
        parent::setType($form, $maj);
        $this->f->module_manager->run_hooks('setType_post', $this, $data);
        $this->f->log(__METHOD__, 'END (class: '.get_class($this).')');
    }

    /**
     * Retourne le code produit défini dans le paramétrage
     * @return string code produit (OpenADS)
     */
    protected function getCodeProduit() {
        return $this->f->getParameter("ged_code_produit");
    }



    // {{{ Surcharge pour l'appel aux Placeholders et Infobules 
    // A retirer et integrer les lignes dans le Core
    //    $this->setPlaceholders($this->form, $maj);
    //    $this->setTooltips($this->form, $maj);
    /**
     * Configuration du formulaire (VIEW formulaire et VIEW sousformulaire).
     *
     * @param formulaire $form Instance formulaire.
     * @param integer $maj Identifant numérique de l'action.
     *
     * @return void
     */
    function setPlaceholders(&$form, $maj) {
    }

    /**
     * Configuration du formulaire (VIEW formulaire et VIEW sousformulaire).
     *
     * @param formulaire $form Instance formulaire.
     * @param integer $maj Identifant numérique de l'action.
     *
     * @return void
     */
    function setTooltips(&$form, $maj) {
    }

    /**
     * VIEW - formulaire.
     *
     * @todo Changer l'attribut name du formulaire pour optimiser la gestion
     * des formulaires
     *
     * @return void
     */
    function formulaire() {
        $this->f->log(__METHOD__, 'BEGIN (class: '.get_class($this).')');
        // Marqueur permettant d'indiquer si le formulaire doit être affiché ou
        // non. Par exemple : si la soumission du formulaire n'est pas valide,
        // on veut afficher un message et un bouton retour et pas le formulaire.
        // ce marqueur nous permet de stocker l'information.
        $flag_do_not_display_form = false;
        // Récupération maj et crud
        $maj = $this->getParameter("maj");
        $crud = $this->get_action_crud($maj);

        $data = array('maj' => $maj, 'crud' => $crud);
        $this->f->module_manager->run_hooks('formulaire_pre', $this, $data);

        $this->enqueue_scripts_and_styles_of_modules();
        $this->display_scripts_and_styles_of_modules('top');

        // Ouverture de la balise form si pas en consultation
        if (($crud !== null && $crud !== 'read')
            || ($crud === null && $maj != 3)) {
            $this->f->layout->display__form_container__begin(array(
                "action" => $this->getDataSubmit(),
                "name" => "f1",
            ));
        }
        // Compatibilite anterieure - On decremente la variable validation
        $this->setParameter("validation", $this->getParameter("validation") - 1);
        // Instanciation de l'objet formulaire
        $this->form = $this->f->get_inst__om_formulaire(array(
            "validation" => $this->getParameter("validation"),
            "maj" => $maj,
            "champs" => $this->champs,
            "val" => $this->val,
            "max" => $this->longueurMax,
        ));
        //
        $this->form->setParameter("obj", $this->get_absolute_class_name());
        $this->form->setParameter("idx", $this->getParameter("idx"));
        $this->form->setParameter("maj", $this->getParameter("maj"));
        $this->form->setParameter("form_type", "form");
        // Valorisation des variables formulaires
        $this->setVal(
            $this->form, $maj,
            $this->getParameter("validation"),
            $this->f->db, null
        );
        $this->setType($this->form, $maj);
        $this->setLib($this->form, $maj);
        $this->setTaille($this->form, $maj);
        $this->setMax($this->form, $maj);
        $this->setSelect($this->form, $maj, $this->f->db, null);
        $this->setOnchange($this->form, $maj);
        $this->setPlaceholders($this->form, $maj);
        $this->setTooltips($this->form, $maj);
        $this->setOnkeyup($this->form, $maj);
        $this->setOnclick($this->form, $maj);
        $this->setGroupe($this->form, $maj);
        $this->setRegroupe($this->form, $maj);
        $this->setLayout($this->form, $maj);
        $this->setRequired($this->form, $maj);
        $this->set_form_specificity($this->form, $maj);
        //
        $this->form->recupererPostvar(
            $this->champs, $this->getParameter("validation"),
            $this->getParameter("postvar"), null
        );
        // Verification de l'accessibilité sur l'élément
        // Si l'utilisateur n'a pas accès à l'élément dans le contexte actuel
        // on arrête l'exécution du script
        $this->checkAccessibility();
        // Si le formulaire a été validé
        if ($this->getParameter("validation") > 0) {
            // Gestion de la fonction 'soumission multiple impossible'
            // Si la méthode valide la soumission alors on exécute le traitement.
            if ($this->form_resubmit_handle_valid_identifier() === true) {
                if ($this->post_treatment() === true) {
                    $this->redirect_to_back_link("formulaire");
                }
            } else {
                // On positionne le marqueur pour ne pas afficher le formulaire.
                $flag_do_not_display_form = true;
                // On prépare le message d'erreur
                $this->correct = false;
                $this->msg = "";
                $this->addToMessage(__("Opération illégale. Ce formulaire a déjà été soumis, il est impossible de le soumettre une seconde fois."));
            }
        }
        // Affichage du message avant d'afficher le formulaire
        $this->message();
        // Si le marqueur l'indique, on ne veut pas afficher le formulaire donc
        // on sort de la méthode.
        if ($flag_do_not_display_form === true) {
            $this->f->layout->display__form_controls_container__begin(array(
                "controls" => "top",
            ));
            $this->retour(
                $this->getParameter("premier"),
                $this->getParameter("recherche"),
                $this->getParameter("tricol")
            );
            $this->f->layout->display__form_controls_container__end();

            $data['flag_do_not_display_form'] = $flag_do_not_display_form;
            $this->f->module_manager->run_hooks('formulaire_post', $this, $data);

            return;
        }
        // Affichage du bouton retour et du bouton
        $this->f->layout->display__form_controls_container__begin(array(
            "controls" => "top",
        ));
        if (($crud !== null && $crud !== 'read')
            || ($crud === null && $maj != 3)) {
            // Affichage du bouton
            $this->bouton($maj);
        }
        $this->retour(
            $this->getParameter("premier"),
            $this->getParameter("recherche"),
            $this->getParameter("tricol")
        );
        $this->f->layout->display__form_controls_container__end();
        // Ouverture du conteneur de formulaire
        $this->form->entete();
        // Point d'entrée dans le formulaire pour ajout d'éléments spécifiques
        $this->form_specific_content_before_portlet_actions($maj);
        // Composition du tableau d'action à afficher dans le portlet
        $this->compose_portlet_actions();
        // Affichage du portlet d'actions s'il existe des actions
        if (!empty($this->user_actions)) {
            $this->form->afficher_portlet(
                $this->getParameter("idx"),
                $this->user_actions
            );
        }
        // Point d'entrée dans le formulaire pour ajout d'éléments spécifiques
        $this->form_specific_content_after_portlet_actions($maj);

        // Injection de "faux champs" pour pouvoir insérer des données où l'on souhaite
        $champs_with_fakes = $this->champs;
        if (method_exists($this, 'insert_fake_extra_data_field')) {
            $champs_with_fakes = $this->insert_fake_extra_data_field($this->champs, $this->form, $maj);
        }

        // Affichage du contenu du formulaire
        $this->form->afficher(
            $champs_with_fakes,
            $this->getParameter("validation"),
            null,
            $this->correct
        );
        // Point d'entrée dans le formulaire pour ajout d'éléments spécifiques
        $this->formSpecificContent($maj);
        // Fermeture du conteneur de formulaire
        $this->form->enpied();
        // Affichage du bouton et du bouton retour
        $this->f->layout->display__form_controls_container__begin(array(
            "controls" => "bottom",
        ));
        if (($crud !== null && $crud !== 'read')
            || ($crud === null && $maj != 3)) {
            // Gestion de la fonction 'soumission multiple impossible'
            $this->form_resubmit_handle_new_identifier();
            // Affichage du bouton
            $this->bouton($maj);
        }
        $this->retour(
            $this->getParameter("premier"),
            $this->getParameter("recherche"),
            $this->getParameter("tricol")
        );
        $this->f->layout->display__form_controls_container__end();
        // Fermeture de la balise form
        if (($crud !== null && $crud !== 'read')
            || ($crud === null && $maj != 3)) {
            $this->f->layout->display__form_container__end();
        }
        // Point d'entrée en dessous du formulaire pour ajout d'éléments spécifiques
        $this->afterFormSpecificContent();

        $this->display_scripts_and_styles_of_modules('bottom');

        $data['flag_do_not_display_form'] = $flag_do_not_display_form;
        $this->f->module_manager->run_hooks('formulaire_post', $this, $data);

        $this->f->log(__METHOD__, 'END (class: '.get_class($this).')');
    }

    /**
     * VIEW - sousformulaire.
     *
     * @return void
     */
    function sousformulaire() {
        $this->f->log(__METHOD__, 'BEGIN (class: '.get_class($this).')');
        // Marqueur permettant d'indiquer si le formulaire doit être affiché ou
        // non. Par exemple : si la soumission du formulaire n'est pas valide,
        // on veut afficher un message et un bouton retour et pas le formulaire.
        // ce marqueur nous permet de stocker l'information.
        $flag_do_not_display_form = false;
        // Récupération du mode de l'action et de sa clé
        $maj = $this->getParameter("maj");
        $crud = $this->get_action_crud($maj);

        $data = array('maj' => $maj, 'crud' => $crud);
        $this->f->module_manager->run_hooks('sousformulaire_pre', $this, $data);

        $this->display_scripts_and_styles_of_modules('top');

        $datasubmit = $this->getDataSubmitSousForm();
        // Ouverture de la balise form si pas en consultation
        if (($crud !== null && $crud !== 'read')
            || ($crud === null && $maj != 3)) {
            $this->f->layout->display__form_container__begin(array(
                "action" => "",
                "name" => "f2",
                "onsubmit" => "affichersform('".$this->getParameter("objsf")."', '".$datasubmit."', this);return false;",
            ));
        }
        // Compatibilite anterieure - On decremente la variable validation
        $this->setParameter("validation", $this->getParameter("validation") - 1);
        // Instanciation de l'objet formulaire
        $this->form = $this->f->get_inst__om_formulaire(array(
            "validation" => $this->getParameter("validation"),
            "maj" => $maj,
            "champs" => $this->champs,
            "val" => $this->val,
            "max" => $this->longueurMax,
        ));
        //
        $this->form->setParameter("obj", $this->get_absolute_class_name());
        $this->form->setParameter("idx", $this->getParameter("idx"));
        $this->form->setParameter("maj", $this->getParameter("maj"));
        $this->form->setParameter("idxformulaire", $this->getParameter("idxformulaire"));
        $this->form->setParameter("retourformulaire", $this->getParameter("retourformulaire"));
        $this->form->setParameter("form_type", "sousform");
        // Valorisation des variables formulaires
        $this->setValsousformulaire(
            $this->form, $maj,
            $this->getParameter("validation"),
            $this->getParameter("idxformulaire"),
            $this->getParameter("retourformulaire"),
            $this->getParameter("typeformulaire"),
            $this->f->db, null
        );
        $this->setType($this->form, $maj);
        $this->setLib($this->form, $maj);
        $this->setTaille($this->form, $maj);
        $this->setMax($this->form, $maj);
        $this->setSelect($this->form, $maj, $this->f->db, null);
        $this->setOnchange($this->form, $maj);
        $this->setPlaceholders($this->form, $maj);
        $this->setTooltips($this->form, $maj);
        $this->setOnkeyup($this->form, $maj);
        $this->setOnclick($this->form, $maj);
        $this->setGroupe($this->form, $maj);
        $this->setRegroupe($this->form, $maj);
        $this->setLayout($this->form, $maj);
        $this->setRequired($this->form, $maj);
        $this->set_form_specificity($this->form, $maj);
        //
        $this->form->recupererPostvarsousform(
            $this->champs, $this->getParameter("validation"),
            $this->getParameter("postvar"), null
        );
        // Verification de l'accessibilité sur l'élément
        // Si l'utilisateur n'a pas accès à l'élément dans le contexte actuel
        // on arrête l'exécution du script
        $this->checkAccessibility();
        // Si le formulaire a été validé
        if ($this->getParameter("validation") > 0) {
            // Gestion de la fonction 'soumission multiple impossible'
            // Si la méthode valide la soumission alors on exécute le traitement.
            if ($this->form_resubmit_handle_valid_identifier() === true) {
                if ($this->post_treatment() === true) {
                    $this->redirect_to_back_link("sousformulaire");
                }
            } else {
                // On positionne le marqueur pour ne pas afficher le formulaire.
                $flag_do_not_display_form = true;
                // On prépare le message d'erreur
                $this->correct = false;
                $this->msg = "";
                $this->addToMessage(__("Opération illégale. Ce formulaire a déjà été soumis, il est impossible de le soumettre une seconde fois."));
            }
        }
        // Affichage du message avant d'afficher le formulaire
        $this->message();
        // Si le marqueur l'indique, on ne veut pas afficher le formulaire donc
        // on sort de la méthode.
        if ($flag_do_not_display_form === true) {
            // Affichage du bouton retour
            $this->f->layout->display__form_controls_container__begin(array(
                "controls" => "top",
            ));
            $this->retoursousformulaire(
                $this->getParameter("idxformulaire"),
                $this->getParameter("retourformulaire"),
                $this->form->val,
                $this->getParameter("objsf"),
                $this->getParameter("premiersf"),
                $this->getParameter("tricolsf"),
                $this->getParameter("validation"),
                $this->getParameter("idx"),
                $maj,
                $this->getParameter("retour")
            );
            $this->f->layout->display__form_controls_container__end();

            $data['flag_do_not_display_form'] = $flag_do_not_display_form;
            $this->f->module_manager->run_hooks('sousformulaire_post', $this, $data);

            return;
        }
        // Affichage du bouton et du bouton retour
        $this->f->layout->display__form_controls_container__begin(array(
            "controls" => "top",
        ));
        if (($crud !== null && $crud !== 'read')
            || ($crud === null && $maj != 3)) {
            // Affichage du bouton
            $this->boutonsousformulaire(
                $datasubmit,
                $maj,
                $this->form->val
            );
        }
        $this->retoursousformulaire(
            $this->getParameter("idxformulaire"),
            $this->getParameter("retourformulaire"),
            $this->form->val,
            $this->getParameter("objsf"),
            $this->getParameter("premiersf"),
            $this->getParameter("tricolsf"),
            $this->getParameter("validation"),
            $this->getParameter("idx"),
            $maj,
            $this->getParameter("retour")
        );
        $this->f->layout->display__form_controls_container__end();
        // Ouverture du conteneur de formulaire
        $this->form->entete();
        // Point d'entrée dans le formulaire pour ajout d'éléments spécifiques
        $this->sousform_specific_content_before_portlet_actions($maj);
        // Composition du tableau d'action à afficher dans le portlet
        $this->compose_portlet_actions();
        // Affichage du portlet d'actions s'il existe des actions
        if (!empty($this->user_actions)) {
            $this->form->afficher_portlet(
                $this->getParameter("idx"),
                $this->user_actions,
                $this->getParameter("objsf")
            );
        }
        // Point d'entrée dans le formulaire pour ajout d'éléments spécifiques
        $this->sousform_specific_content_after_portlet_actions($maj);

        // Injection de "faux champs" pour pouvoir insérer des données où l'on souhaite
        $champs_with_fakes = $this->champs;
        if (method_exists($this, 'insert_fake_extra_data_field')) {
            $champs_with_fakes = $this->insert_fake_extra_data_field($this->champs, $this->form, $maj);
        }

        // Affichage du contenu du formulaire
        $this->form->afficher(
            $champs_with_fakes,
            $this->getParameter("validation"),
            null,
            $this->correct
        );
        // Point d'entrée dans le formulaire pour ajout d'éléments spécifiques
        $this->sousFormSpecificContent($maj);
        // Fermeture du conteneur de formulaire
        $this->form->enpied();
        // Affichage du bouton et du bouton retour
        $this->f->layout->display__form_controls_container__begin(array(
            "controls" => "bottom",
        ));
        if (($crud !== null && $crud !== 'read')
            || ($crud === null && $maj != 3)) {
            // Gestion de la fonction 'soumission multiple impossible'
            $this->form_resubmit_handle_new_identifier();
            // Affichage du bouton
            $this->boutonsousformulaire(
                $datasubmit,
                $maj,
                $this->form->val
            );
        }
        $this->retoursousformulaire(
            $this->getParameter("idxformulaire"),
            $this->getParameter("retourformulaire"),
            $this->form->val,
            $this->getParameter("objsf"),
            $this->getParameter("premiersf"),
            $this->getParameter("tricolsf"),
            $this->getParameter("validation"),
            $this->getParameter("idx"),
            $maj,
            $this->getParameter("retour")
        );
        $this->f->layout->display__form_controls_container__end();
        // Fermeture de la balise form
        if (($crud !== null && $crud !== 'read')
            || ($crud === null && $maj != 3)) {
            $this->f->layout->display__form_container__end();
        }
        // Point d'entrée en dessous du formulaire pour ajout d'éléments spécifiques
        $this->afterSousFormSpecificContent();

        $this->display_scripts_and_styles_of_modules('bottom');

        $data['flag_do_not_display_form'] = $flag_do_not_display_form;
        $this->f->module_manager->run_hooks('sousformulaire_post', $this, $data);

        $this->f->log(__METHOD__, 'END (class: '.get_class($this).')');
    }

    // }}}

    // {{{ SURCHARGES DES LIBELLES DES BOUTONS

                       /**
     * Cette methode permet d'afficher le bouton de validation du formulaire
     *
     * @param integer $maj Mode de mise a jour
     * @return void
     */
    function bouton($maj) {


        if (!$this->correct
            && $this->checkActionAvailability() == true) {


            //
            switch($maj) {
                case 0 :
                    $bouton = __("Ajouter");
                    break;
                case 1 :
                    $bouton = __("Modifier");
                    break;
                case 2 :
                    $bouton = __("Supprimer");
                    break;
                default :
                    // Actions specifiques
                    if ($this->get_action_param($maj, "button") != null) {
                        //
                        $bouton = $this->get_action_param($maj, "button");
                    } else {
                        //
                        $bouton = __("Valider");
                    }
                    break;
            }
            //
            $params = array(
                "value" => $bouton,
                "class" => "btn btn-primary",
            );
            //
            $this->f->layout->display_form_button($params);
        }

    }

    // }}}

    /**
     * Accesseur standard à une ressource.
     *
     * Cette méthode permet d'instancier la classe passée en paramètre selon
     * deux logiques différentes :
     *  - Cas n°1 : soit on veut instancier un objet en particulier de manière
     *    ponctuelle alors on passe le paramètre id qui correspond à
     *    l'identifiant de l'objet sur lequel on veut instancier la classe, et
     *    l'instanciation est effectuée et la ressource retournée.
     *  - Cas n°2 : soit on veut instancier un objet lié (clé étrangère) à
     *    l'objet courant et on ne passe donc pas de paramètre id, car il est
     *    récupéré directement sur l'objet courant (on peut éventuellement
     *    indiquer le nom du champ à récupérer par le paramètre field sinon
     *    c'est le nom de la classe qui est utilisé), et l'instanciation est
     *    effectuée et la ressource stockée puis retournée. Attention, si la
     *    ressource a déjà été stockée lors d'un appel précédent alors on la
     *    retourne sans réinstanciation.
     *
     * @param string $class Nom de la classe à instancier.
     * @param string|null $id Identifiant de l'objet à instancier.
     * @param string|null $field Nom du champ ou récupérer l'identifiant de
     *                    l'objet à instancier si différent du nom de la classe.
     *
     * @return resource
     */
    function get_inst_common($class, $id = null, $field = null) {
        //// Gestion du cas n°1 -> Instanciation ponctuelle
        // Si un identifiant est passé en paramètre
        if ($id !== null) {
            // Retour de l'instanciation
            return $this->f->get_inst__om_dbform(array(
                "obj" => $class,
                "idx" => $id,
            ));
        }

        //// Gestion du cas n°2 -> Instanciation liée à l'objet courant
        // On définit le nom de l'attribut dans lequel on va stocker la
        // ressource
        $var_name = "inst_".$class;
        // Si l'attribut n'existe pas ou est initialisé à null
        if (!isset($this->$var_name) || $this->$var_name === null) {
            // Si le paramètre field n'est pas passé en paramètre
            // alors on utilise le nom de la classe
            if ($field === null) {
                $field = $class;
            }
            // Stockage de l'instanciation dans l'attribut de l'objet courant
            $this->$var_name = $this->f->get_inst__om_dbform(array(
                "obj" => $class,
                "idx" => $this->getVal($field),
            ));
        }
        // Retour de l'instanciation
        return $this->$var_name;
    }

    // {{{ SUBSTITUTION_VARS

    /**
     * Récupération des valeurs des champs de fusion
     *
     * @return array         tableau associatif
     */
    function get_values_substitution_vars($om_collectivite_idx = null) {
        //
        $values = parent::get_values_substitution_vars($om_collectivite_idx);


        // Surcharge de la récupération des paramètres car dans openADS on ne gère 
        // pas le 'prefixe_edition_substitution_vars' géré par le framework.
        foreach ($this->f->getCollectivite($om_collectivite_idx) as $key => $value) {
            // XXX Spécificité SIG, un paramètre peut être de type tableau
            if (is_array($value)) {
                continue;
            }
            //
            $value = str_replace("\r\n", "<br/>", $value);
            $value = str_replace("\n", "<br/>", $value);
            $value = str_replace("\r", "<br/>", $value);
            $values[$key] = $value;
        }
        //
        //Date au format jour_de_la_semaine jour_du_mois mois_de_l'année
        //Ex. Lundi 12 Mars 2016
        $jourSemaine = array(
            __('Dimanche'),
            __('Lundi'),
            __('Mardi'),
            __('Mercredi'),
            __('Jeudi'),
            __('Vendredi'),
            __('Samedi'),
        );
        $moisAnnee = array(
            __('Janvier'),
            __('Fevrier'),
            __('Mars'),
            __('Avril'),
            __('Mai'),
            __('Juin'),
            __('Juillet'),
            __('Aout'),
            __('Septembre'),
            __('Octobre'),
            __('Novembre'),
            __('Decembre'),
        );
        //
        $values["aujourdhui"] = date("d/m/Y");
        $values["datecourrier"] = date("d/m/Y");
        $values["jourSemaine"] = sprintf(
            '%s %s %s %s',
            $jourSemaine[date('w')],
            date('d'),
            $moisAnnee[date('n') - 1],
            date('Y')
        );

        /**
         * GESTION SPECIFIQUE DES BORDEREAUX (om_etat)
         *
         * - &date_bordereau_debut
         * - &date_bordereau_fin
         * => $titre
         * => $corps
         */
        if (isset($_GET["obj"]) 
            && $this->f->starts_with($_GET["obj"], 'bordereau') === true) {

            (isset($_GET['date_bordereau_debut']) ? $date_bordereau_debut = $_GET["date_bordereau_debut"] : $date_bordereau_debut = "");
            (isset($_GET['date_bordereau_fin']) ? $date_bordereau_fin = $_GET["date_bordereau_fin"] : $date_bordereau_fin = "");

            // formatage des dates de début et de fin de bordereau en EN/US
            $date_bordereau_debut_en = substr($date_bordereau_debut,6,4)."-".substr($date_bordereau_debut,3,2)."-".substr($date_bordereau_debut,0,2);
            $date_bordereau_fin_en = substr($date_bordereau_fin,6,4)."-".substr($date_bordereau_fin,3,2)."-".substr($date_bordereau_fin,0,2);
            // gestion de l'absence de dates (contexte prévisualisation de l'état)
            if ($date_bordereau_debut_en == '--' || $date_bordereau_fin_en == '--') {
                // Dates volontairement irréalistes pour n'obtenir aucun résultat
                $date_bordereau_debut_en = '1212-12-12';
                $date_bordereau_fin_en = '1212-12-12';
            }
            //
            $values["date_bordereau_debut"] = $date_bordereau_debut;
            $values["date_bordereau_fin"] = $date_bordereau_fin;

            //
            if (isset($_GET['collectivite']) && $_SESSION['niveau'] == '2') {
                if (!is_numeric($_GET["collectivite"])) {
                    $values["departement"] = "";
                    $values["ville"] = "";
                    $values["commune"] = "Toutes";
                } else {
                    $collectivite_parameters = $this->f->getCollectivite($_GET["collectivite"]);
                    $values["departement"] = $collectivite_parameters["departement"];
                    $values["ville"] = $collectivite_parameters["ville"];
                    $values["commune"] = $collectivite_parameters["commune"];
                }
            }

        }

        /**
         * RAPPORT D'INSTRUCTION
         * - &rapport_instruction_consultation
         * => $titre
         * => $corps
         */
        //Récupéraion de la liste des consultations d'un dossier d'instruction pour 
        //l'édition du rapport d'instruction
        if (isset($_GET["obj"]) 
            && strcasecmp( $_GET['obj'], "rapport_instruction") == 0
            && file_exists("../app/rapport_instruction_consultation.php")) {
            //
            $consultations = "";
            include "../app/rapport_instruction_consultation.php";
            $values["rapport_instruction_consultation"] = $consultations;
        }

        // Si l'option du portail citoyen n'est pas activée
        if ($this->f->is_option_citizen_access_portal_enabled($om_collectivite_idx) !== true) {
            // Les valeurs ne sont pas affichées
            $values["acces_citoyen"] = "";
            $values["acces_citoyen_adresse"] = "";
        } else {
            $values["acces_citoyen"] = $this->f->getParameter('acces_citoyen');
            $values["acces_citoyen_adresse"] = $this->f->getParameter('acces_citoyen_adresse');
        }

        //
        return $values;
    }

    /**
     * Récupération des libellés des champs de fusion
     *
     * @return array         tableau associatif
     */
    function get_labels_substitution_vars($om_collectivite_idx = null) {
        //
        $labels = parent::get_labels_substitution_vars();
        //
        $labels["om_parametre"] = array();
        // Surcharge de la récupération des paramètres car dans openADS on ne gère 
        // pas le 'prefixe_edition_substitution_vars' géré par le framework.
        foreach ($this->f->getCollectivite($om_collectivite_idx) as $key => $value) {
            // XXX Spécificité SIG, un paramètre peut être de type tableau
            if (is_array($value)) {
                continue;
            }
            // On ne récupère pas les paramètres dont le préfixe fait partie
            // des éléments : ged_, erp_, id_, sig_, option_, qui n'ont pas vraiment
            // de raison de se trouver dans une édition pdf.
            // Attention ces éléments sont tout de même remplacés dans la méthode
            // 'get_values' pour la raison suivante : rétrocompatibilité.
            if (strstr($key, "ged_") !== false || strstr($key, "erp_") !== false 
                || strstr($key, "id_") !== false || strstr($key, "sig_") !== false
                || strstr($key, "option_") !== false ) {
                continue;
            }
            //
            switch ($key) {
                case 'adresse_SAU':
                    $label = __("L’adresse complète du service");
                    break;
                case 'adresse_direction_urbanisme':
                    $label = __("L’adresse complète du service instructeur ADS");
                    break;
                case 'adresse_direction_urbanisme_RAR':
                    $label = __("L’adresse complète du service instructeur ADS au format lettre");
                    break;
                case 'adresse_service_foncier':
                    $label = __("L’adresse complète du service instructeur FONCIER");
                    break;
                case 'commune':
                    $label = __("Code insee de la commune – valeur chiffrées sur 3 caractères");
                    break;
                case 'cp':
                    $label = __("Le code postal de la commune");
                    break;
                case 'delaville':
                    $label = __("Le nom de la collectivité");
                    break;
                case 'departement':
                    $label = __("Valeur sur 3 caractères chiffrés permettant d’identifier le département");
                    break;
                case 'dit_code__transmitted__platau':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'insee':
                    $label = __("Code INSEE (Format sur cinq caractères : 72064)");
                    break;
                case 'mention_depot_electronique_recepisse':
                    $label = __("Texte ajouté automatiquement uniquement sur les modèles de récépissé généré suite au dépôt dématérialisé d’un dossier pour informer le destinataire que ce document valant ARE n’est pas tamponné par l’administration");
                    break;
                case 'pvr_perimetre':
                    $label = __("Texte indiquant qu’une Délibération du Conseil Municipal a instauré un périmètre spécifique à l’intérieur duquel la Participation pour Voirie et Réseaux s’applique");
                    break;
                case 'pvr_principe':
                    $label = __("Texte indiquant qu’une Délibération du Conseil Municipal a instauré le principe de la Participation pour Voirie et Réseaux sur le territoire communal");
                    break;
                case 'rappels_procedure_taxation':
                    $label = __("Texte informant le(s) titulaire(s) des modalités pour déclarer à l’administration fiscale les surfaces soumises à taxation.");
                    break;
                case 'rappels_reglementaires_DIA':
                    $label = __("Texte positionné dans le bloc INFORMATIONS A LIRE ATTENTIVEMENT sur les courriers des dossiers DIA");
                    break;
                case 'rappels_reglementaires_decision_urba':
                    $label = __("Texte positionné dans le bloc INFORMATIONS A LIRE ATTENTIVEMENT sur les courriers de type décision des dossiers ADS");
                    break;
                case 'rappels_reglementaires_urba_1ermois':
                    $label = __("Texte positionné dans le bloc INFORMATIONS A LIRE ATTENTIVEMENT sur les courriers du 1er mois des dossiers ADS");
                    break;
                case 'rapport_instruction_analyse_reglementaire':
                    $label = __("Le modèle de rapport d’instruction par défaut");
                    break;
                case 'rapport_instruction_proposition_decision':
                    $label = __("La liste des propositions de décision que peut sélectionner l’agent en charge de l’instruction depuis le modèle de rapport d’instruction dématérialisé");
                    break;
                case 'region':
                    $label = __("Valeur sur 2 caractères chiffrés permettant d’identifier la région");
                    break;
                case 'ta_archeo':
                    $label = __("Le taux de la RAP (Redevance d’Archéologie Préventive)");
                    break;
                case 'ta_com':
                    $label = __("Le taux de la taxe d’aménagement – part communale");
                    break;
                case 'ta_dep':
                    $label = __("Le taux de la taxe d’aménagement – part départementale");
                    break;
                case 'titreelu':
                    $label = __("Le titre de l’élu (autorité compétente)");
                    break;
                case 'ville':
                    $label = __("Le nom de la collectivité");
                    break;
                case 'visa_plu':
                    $label = __("variable appelant le paramètre contenant le visa du document d’urbanisme de la commune ou de l’EPCI (document intercommunale)");
                    break;
                case 'platau_type_demande_annul_DP':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_annul_DPL':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_annul_DPMI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_annul_PA':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_annul_PC':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_annul_PD':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_annul_PI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_daact_DP':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_daact_DPL':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_daact_DPMI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_daact_PA':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_daact_PC':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_daact_PI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_doc_PA':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_doc_PC':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_doc_PI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dpc_Cua':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dpc_Cub':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dpc_DP':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dpc_DPL':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dpc_DPMI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dpc_PA':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dpc_PC':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dpc_PD':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dpc_PI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dps_Cua':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dps_Cub':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dps_DP':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dps_DPL':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dps_DPMI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dps_PA':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dps_PC':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dps_PD':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_dps_PI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_initial_Cua':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_initial_Cub':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_initial_DP':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_initial_DPL':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_initial_DPMI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_initial_PA':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_initial_PC':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_initial_PD':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_initial_PI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_modificatif_PA':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_modificatif_PC':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_modificatif_PI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_pro_Cua':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_pro_Cub':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_pro_DP':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_pro_DPL':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_pro_DPMI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_pro_PA':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_pro_PC':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_pro_PD':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_pro_PI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_retrait_Cua':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_retrait_Cub':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_retrait_DP':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_retrait_DPL':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_retrait_DPMI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_retrait_PA':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_retrait_PC':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_retrait_PD':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_retrait_PI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_transfert_DP':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_transfert_DPL':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_transfert_DPMI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_transfert_PA':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_transfert_PC':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_transfert_PD':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                case 'platau_type_demande_transfert_PI':
                    $label = __("Paramètre servant à l’interfaçage d’openADS avec PLAT’AU");
                    break;
                default:
                    $label = "-";
            }
            $labels["om_parametre"][$key] = $label;
        }
        ksort($labels["om_parametre"]);
        
        $labels["divers"]["aujourdhui"] = __("Date du jour (Format : 14/01/1978)");
        $labels["divers"]["datecourrier"] = __("Date du jour (Format : 14/01/1978)");
        $labels["divers"]["jourSemaine"] = __("Date du jour en lettre (Format : Samedi 14 Janvier 1978)");
        $labels["divers"]["acces_citoyen"] = __("Texte d'information concernant l'acces au portail citoyen pour les petitionnaires");
        $labels["divers"]["acces_citoyen_adresse"] = __("Lien vers le portail citoyen");
        
        if (isset($_GET["obj"]) 
            && strcasecmp( $_GET['obj'], "om_etat") == 0) {
            $labels["specifique"] = array(
                "rapport_instruction_consultation" => __("Rapport d'instruction uniquement - Liste des consultations du dossier"),
                "date_bordereau_debut" => __("Bordereau uniquement - Date de début du bordereau"),
                "date_bordereau_fin" => __("Bordereau uniquement - Date de fin du bordereau"),
            );
        }   
        $labels["specifique"]["contraintes"] = __("Liste de toutes les contraintes du dossier");
        $labels["specifique"]["contraintes(liste_groupe=g1,g2...;liste_ssgroupe=sg1,sg2...)"] = __("Les options liste_groupe et liste_ssgroupe sont optionnelles et peuvent contenir une valeur unique ou plusieurs valeurs separees par une virgule, sans espace.");
        $labels["specifique"]["contraintes(service_consulte=t)"] = __("L'option service_consulte permet d'ajouter une condition sur le champ du meme nom. Il peut prendre t (Oui) ou f (Non) comme valeur.");
        $labels["specifique"]["contraintes(affichage_sans_arborescence=t)"] = __("L'option affichage_sans_arborescence permet d'afficher une liste de contraintes sans leurs groupes et sous-groupes, et sans puces. Il peut prendre t (Oui) ou f (Non) comme valeur.");

        return $labels;
    }

    // }}} SUBSTITUTION_VARS

    /**
     * Cette methode permet d'effacer les messages de validation.
     */
    function cleanMessage() {
        //
        $this->msg = "";
    }


    /**
     * Retourne le booléen de la valeur pgsql passée en paramètre.
     * Si la valeur est 't' alors retourne true, si la valeur est 'f' false,
     * sinon null.
     *
     * @param string $pgsql_value Soit 't', soit 'f'.
     *
     * @return boolean ou null.
     */
    public function get_boolean_from_pgsql_value($pgsql_value) {
        //
        if ($pgsql_value === 't') {
            //
            return true;
        }
        //
        if ($pgsql_value === 'f') {
            //
            return false;
        }

        //
        return null;
    }


    /**
     * Retourne le booléen de la valeur d'affichage passée en paramètre.
     * Si la valeur est 'Oui' alors retourne true, si la valeur est 'Non' ou ''
     * alors retourne false, sinon retourne null.
     *
     * @param string $view_value Soit 'Oui', soit 'Non'.
     *
     * @return boolean ou null.
     */
    public function get_boolean_from_view_value($view_value) {
        //
        if (strtolower($view_value) === strtolower('Oui')) {
            //
            return true;
        }
        //
        if (strtolower($view_value) === strtolower('Non')
            || $view_value === '') {
            //
            return false;
        }

        //
        return null;
    }


    /**
     * Récupère la liste des valeurs de l'objet dans un tableau.
     *
     * @return array
     */
    public function get_array_val() {
        //
        $return = array();
        //
        foreach ($this->champs as $key => $champ) {
            $value = $this->getVal($champ);

            // Détection des champs date via métadonnées (si disponibles)
            if (isset($this->type[$key]) && $this->type[$key] === 'date') {

                // Conversion Y-m-d => d/m/Y si possible
                if (! empty($value)) {
                    $dt = DateTime::createFromFormat('Y-m-d', $value);
                    if ($dt !== false) {
                        $value = $dt->format('d/m/Y');
                    }
                }
            }

            $return[$champ] = $value;
        }

        //
        return $return;
    }


    /**
     * Affichage du message de validation
     * 
     * @return void
     */
    protected function display_msg() {
        $type = 'valid';
        if ($this->correct === false) {
            $type = 'error';
        }
        $this->f->displayMessage($type, $this->msg);
    }


    /**
     * Ajouter une liaison NàN entre deux tables.
     *
     * @param string $table_l Table de liaison.
     * @param string $table_f Table cible.
     * @param string $field   Champ de la table à liée.
     * @param string $field_f Champ de la table laison qui est associé à la table cible.
     * Ce champs n'est utile que si la table de liaison lie une table à elle même.
     * @param array $specific_values   valeurs supplémentaires à intégrer à l'enregistrement
     * @param string increment_order   Nom du champ d'ordre dans la table de liaison.
     *
     * @return integer Nombre de lien crée
     */
    protected function ajouter_liaisons_table_nan($table_l, $table_f, $field, $values=null, $field_f = null, $specific_values = array(), $increment_order = null) {

        $multiple_values = array();

        // Si de valeurs sont passées en paramètre on les utilise en priorité
        if ($values != null) {
            $multiple_values = $values;
            // Si ce n'est pas un tableau
            if (!is_array($values)) {
                $multiple_values = explode(";", $multiple_values);
            }
        }  else {
            // Sinon on fait la récupération des données du select multiple à partir des paramètres POST
            $postvar = $this->getParameter("postvar");
            if (isset($postvar[$field])
                && is_array($postvar[$field])) {
                $multiple_values = $postvar[$field];
            }
        }

        // Ajout des liaisons
        $nb_liens = 0;
        // Boucle sur la liste des valeurs sélectionnées
        foreach ($multiple_values as $value) {
            // Test si la valeur par défaut est sélectionnée
            if ($value == "") {
                continue;
            }
            // Pour les cas où une table est liée à elle même on récupère
            // le nom du champs fourni en paramètre. Si rien n'a été spécifié
            // alors le nom du champs est celui de la table cible.
            if (empty($field_f)) {
                $field_f = $table_f;
            }
            // On compose les données de l'enregistrement
            $donnees = array(
                $this->clePrimaire => $this->valF[$this->clePrimaire],
                $field_f => $value,
                $table_l => "",
            );
            // Ajoute les valeurs supplémentaires à intégrer à l'enregistrement
            if (is_array($specific_values) && ! empty($specific_values)) {
                $donnees = array_merge($donnees, $specific_values);
            }
            // Ajoute un ordre à chaque lien ajouté
            if ($increment_order !== null && empty($increment_order) === false) {
                $donnees = array_merge($donnees, array($increment_order => $nb_liens+1));
            }
            // On ajoute l'enregistrement
            $obj_l = $this->f->get_inst__om_dbform(array(
                "obj" => $table_l,
                "idx" => "]",
            ));
            $obj_l->ajouter($donnees);
            // On compte le nombre d'éléments ajoutés
            $nb_liens++;
        }
        //
        return $nb_liens;
    }


    /**
     * Supprimer les liens d'une table NàN.
     *
     * @param string $table Table de liaison.
     * @param string $conditions conditions supplémentaires en sql permettant de déterminer les
     * élément à supprimer (ex: $conditions = 'AND plop < 3)
     *
     * @return void
     */
    protected function supprimer_liaisons_table_nan($table, string $conditions = '') {
        // Suppression de tous les enregistrements correspondants à l'id 
        // de l'objet instancié en cours dans la table NaN.
        // Si des conditions supplémentaires sont passées en paramètres elles seront ajoutées à la requête
        $qres = $this->f->execute_db_query(
            sprintf(
                'DELETE FROM %1$s%2$s WHERE %3$s = %4$s %5$s',
                DB_PREFIXE,
                $table,
                $this->clePrimaire,
                !empty($this->getVal($this->clePrimaire)) && is_string($this->getVal($this->clePrimaire)) ? "'".$this->getVal($this->clePrimaire)."'" : $this->getVal($this->clePrimaire),
                $conditions
            ),
            array(
                'origin' => __METHOD__,
            )
        );
    }


    /**
     * Récupère l'instance de dossier.
     *
     * @param string $dossier Identifiant du dossier d'instruction.
     *
     * @return object
     */
    protected function get_inst_dossier($dossier = null) {
        //
        return $this->get_inst_common("dossier", $dossier);
    }


    /**
     * Cette méthode permet de récupérer le code de division correspondant
     * au dossier sur lequel on se trouve.
     *
     * @return string Code de la division du dossier en cours
     */
    function getDivisionFromDossier($dossier = null) {

        // Cette méthode peut être appelée plusieurs fois lors d'une requête.
        // Pour éviter de refaire le traitement de recherche de la division
        // alors on vérifie si nous ne l'avons pas déjà calculé.
        if (isset($this->_division_from_dossier) === true &&
            $this->_division_from_dossier != NULL) {
            // Logger
            $this->addToLog("getDivisionFromDossier(): retour de la valeur déjà calculée - '".$this->_division_from_dossier."'", EXTRA_VERBOSE_MODE);
            // On retourne la valeur déjà calculée
            return $this->_division_from_dossier;
        }
        if ($dossier === null) {
            // Test sur le mode et le contexte du formulaire
            if ($this->_absolute_class_name !== 'dossier_instruction'
                && ($this->getParameter("retourformulaire") == "dossier"
                || $this->f->contexte_dossier_instruction())) {
                // Si on se trouve en mode AJOUT (seul mode où l'enregistrement
                // n'existe pas en base de données) ET que nous nous trouvons
                // dans le contexte d'un dossier d'instruction alors on récupère
                // le numéro de dossier depuis le paramètre 'idxformulaire'
                $dossier = $this->getParameter("idxformulaire");
            } elseif ($this->getParameter("retourformulaire") == "lot") {
                $inst_lot = $this->f->get_inst__om_dbform(array(
                    "obj" => "lot",
                    "idx" => $this->getParameter("idxformulaire"),
                ));
                $dossier = $inst_lot->getVal("dossier");
            } else {
                // Sinon on récupère le numéro de dossier dans le champs dossier de
                // l'enregistrement (en base de données)
                $dossier = $this->getVal("dossier");
            }
        }

        // On requête la division du dossier
        $inst_dossier_instruction = $this->f->get_inst__om_dbform(array(
            "obj" => "dossier_instruction",
            "idx" => $dossier,
        ));
        $this->_division_from_dossier = $inst_dossier_instruction->getVal("division");
        // On retourne la valeur retournée
        return $this->_division_from_dossier;
    }


    /**
     * Vérifie que l'utilisateur est instructeur et qu'il est de la division du
     * dossier.
     *
     * @return,  boolean true/false
     */
    function is_instructeur_from_division_dossier() {
        if ($this->f->isUserInstructeur() === true &&
            $this->f->om_utilisateur["division"] == $this->getDivisionFromDossier()) {
            return true;
        }
        return false;
    }


    /**
     * Si le dossier d'instruction auquel est rattachée l'enregistrement est 
     * cloturé, on affiche pas les liens du portlet.
     *
     * @return boolean true si non cloturé false sinon
     */
    function is_dossier_instruction_not_closed() {
        $idxformulaire = $this->getParameter("idxformulaire");
        //Si le dossier d'instruction auquel est rattachée la consultation est 
        //cloturé, on affiche pas les liens du portlet
        if ($idxformulaire != '' && $this->f->contexte_dossier_instruction()){
            if ($this->f->getStatutDossier($idxformulaire) != "cloture"){
                return true;
            }
        }
        return false;
    }

    /**
     * Initialisation des valeurs des champs HTML <select>
     *
     * @param formulaire $form formulaire
     * @param null &$dnu1 @deprecated Ancienne ressource de base de données.
     * @param int $maj type d action (0:ajouter, 1:modifier, etc.)
     * @param null $dnu2 @deprecated Ancien marqueur de débogage.
     * @param string $field nom du champ <select> a initialiser
     * @param string $sql requete de selection des valeurs du <select>
     * @param string $sql_by_id requete de selection valeur par identifiant
     * @param string $om_validite permet de définir si l'objet lié est affecté par une date de validité
     * @param string $multiple permet d'utiliser cette méthode pour configurer l'affichage de select_multiple (widget)
     * @param string $field_name traduction du champ
     * @param bool   $display_placeholder Permet d'afficher ou non le texte de remplacement de la valeur vide
     */
    function init_select(&$form = null, &$dnu1 = null, $maj = null, $dnu2 = null, $field = "", $sql = "",
                         $sql_by_id = "", $om_validite = false, $multiple = false,
                         $field_name = '', $display_placeholder = true) {

        // Si aucune traduction du champ fournie
        if ($field_name === '') {
            $field_name = __($field);
        }

        // Renomme la collectivité en service sur l'option est activée
        if ($field === 'om_collectivite'
            && $this->f->is_option_renommer_collectivite_enabled() === true) {
            //
            $field_name = sprintf('%s %s',__("le"), __("service"));
        }

        // Récupération du mode de l'action
        $crud = $this->get_action_crud($maj);

        // MODES AJOUTER, MODIFIER ET RECHERCHE AVANCÉE
        if (($crud === 'create' || $crud === 'update' || $crud == 'search')
            || ($crud === null && ($maj == 0 || $maj == 1 || $maj == 999))) {
            // Exécution de la requête
            $res = $this->f->db->query($sql);
            // Logger
            $this->addToLog(__METHOD__."(): db->query(\"".$sql."\");", VERBOSE_MODE);
            // Vérification d'une éventuelle erreur de base de données
            $this->f->isDatabaseError($res);
            // Initialisation du select
            $contenu = array();
            $contenu[0][0] = '';
            $contenu[1][0] = $display_placeholder === true ? sprintf('%s %s', __('choisir'), $field_name) : '';
            //
            $k=1;
            while($row =& $res->fetchRow()){
                $contenu[0][$k] = $row[0];
                $contenu[1][$k] = $row[1];
                $k++;
            }

            // Si en mode "modifier" et si la gestion des dates de validité est activée
            if (($crud === 'update' || ($crud === null && $maj == 1))
                && $om_validite == true) {
                $field_values = array();
                // Dans le cas d'un select_multiple
                if ($multiple == true) {
                    $field_values = explode(";", $this->form->val[$field]);
                }
                // Dans le cas d'un select simple
                else {
                    $field_values = array($this->form->val[$field],);
                }
                // S'il y a une ou plusieurs valeurs
                if (!empty($field_values) && $field_values[0] != '') {
                    // pour chacune d'entre elles
                    foreach ($field_values as $field_value) {
                        // si elle manque au contenu du select
                        if (!in_array($field_value, $contenu[0])) {
                            // on l'ajoute
                            $this->getSelectOldValue($form, $maj, $this->f->db, $contenu,
                                                     $sql_by_id, $field, $field_value);
                        }
                    }
                }
                // S'il n'y a pas de valeur c'est que soit :
                // - aucune valeur n'est présaisie en première validation,
                // - le formulaire a été validé en erreur.
                // C'est ce dernier cas qui nous intéresse afin de ne pas perdre
                // dans le contenu une valeur invalide pourtant sélectionnée.
                // Si elle n'a pas été sélectionnée elle est dans tous les cas
                // perdue, il faut recharger le formulaire pour la récupérer.
                else {
                    // On vérifie si le formulaire est vide : si oui
                    // cela signifie que le formulaire a été validé en erreur
                    $empty = true;
                    foreach ($this->form->val as $f => $value) {
                        if (!empty($value)) {
                            $empty = false;
                        }
                    }
                    // Déclaration des valeurs postées
                    $field_posted_values = array();
                    // Dans le cas d'un select_multiple avec des valeurs postées
                    if ($multiple == true && isset($_POST[$field])) {
                        $field_posted_values = $_POST[$field];
                    }
                    // Dans le cas d'un select simple avec une valeur postée
                    elseif (isset($_POST[$field])) {
                        $field_posted_values = array($_POST[$field],);
                    }
                    // S'il y a une ou plusieurs valeurs postées
                    // et que le formulaire a déjà été validé
                    if ($empty == true && !empty($field_posted_values) && $field_posted_values[0] != '') {
                        // pour chacune d'entre elles
                        foreach ($field_posted_values as $field_posted_value) {
                            // si elle manque au contenu du select
                            if (!in_array($field_posted_value, $contenu[0])) {
                                // on l'ajoute
                                $this->getSelectOldValue($form, $maj, $this->f->db, $contenu,
                                                         $sql_by_id, $field, $field_posted_value);
                            }
                        }
                    }
                }
            }
            // Initialisation des options du select dans le formulaire
            $form->setSelect($field, $contenu);
            // Logger
            $this->addToLog(__METHOD__."(): form->setSelect(\"".$field."\", ".print_r($contenu, true).");", EXTRA_VERBOSE_MODE);
        }

        // MODE SUPPRIMER, CONSULTER ET ACTIONS SPECIFIQUES SANS CRUD
        if (($crud === 'delete' || $crud === 'read')
            || ($crud === null && $maj >= 2 && $maj != 999)) {
            // Initialisation du select
            $contenu[0][0] = '';
            $contenu[1][0] = '';

            if (isset($this->form->val[$field]) &&
                !empty($this->form->val[$field]) && $sql_by_id) {
                // Dans le cas d'un select_multiple
                if ($multiple == true) {
                    // Permet de gérer le cas ou les clés primaires sont alphanumériques
                    $val_field = "'".str_replace(";", "','",$this->form->val[$field])."'";
                } else {
                    $val_field = $this->form->val[$field];
                }
                // ajout de l'identifiant recherche a la requete
                $sql_by_id = str_replace('<idx>', $val_field, $sql_by_id);
                // Exécution de la requête
                $res = $this->f->db->query($sql_by_id);
                // Logger
                $this->addToLog(__METHOD__."(): db->query(".$sql_by_id.");", VERBOSE_MODE);
                // Vérification d'une éventuelle erreur de base de données
                $this->f->isDatabaseError($res);
                // Affichage de la première ligne d'aide à la saisie
                $row =& $res->fetchRow();
                $contenu[0][0] = $row[0];
                $contenu[1][0] = $row[1];
                //
                $k=1;
                while($row =& $res->fetchRow()){
                    $contenu[0][$k] = $row[0];
                    $contenu[1][$k] = $row[1];
                    $k++;
                }
            }

            $form->setSelect($field, $contenu);
            // Logger
            $this->addToLog(__METHOD__."(): form->setSelect(\"".$field."\", ".print_r($contenu, true).");", EXTRA_VERBOSE_MODE);
        }
    }


    /**
     * Récupère la configuration du filestorage dans son intégralité ou
     * seulement le paramètre précisé.
     *
     * @param mixed $key Nom du paramètre de la configuration à récupérer.
     *
     * @return mixed Tableau de la configuration ou null.
     */
    public function get_filestorage_config($key=null) {
        // Vérifie que la configuration du filestorage existe
        if (is_array($this->f->filestorage_config) !== true) {
            //
            return null;
        }

        // Si le paramètre de la configuration n'est pas précisé
        if ($key === null) {
            // Retourne toute la configuration
            return $this->f->filestorage_config;
        }

        // Si le paramètre de la configuration n'existe pas
        if (array_key_exists($key, $this->f->filestorage_config) !== true) {
            //
            return null;
        }

        //
        return $this->f->filestorage_config[$key];
    }


    /**
     * Cette méthode appelée dans le triggermodifierapres() permet de contrôler
     * si le connecteur filestorage demande l'exécution de méthode de
     * traitement.
     *
     * @param array $values Liste des valeurs pour la comparaison.
     *
     * @return boolean
     */
    public function post_update_metadata(array $values) {
        // Récupère la configuration du traitement des métadonnées
        $metadata_handlers = $this->get_filestorage_config('metadata_handlers');
        // Vérifie la configuration du traitement des métadonnées
        if ($metadata_handlers === null
            || is_array($metadata_handlers) !== true
            || array_key_exists($this->table, $metadata_handlers) !== true) {
            //
            return true;
        }

        // Liste des méthodes à exécuter pour l'objet
        $methods = $metadata_handlers[$this->table];

        // Parcours les méthodes à exécuter, configurées dans le connecteur
        // filestorage
        foreach ($methods as $method) {
            //
            if (method_exists($this, $method) === true) {
                //
                $treatment = $this->$method($values);
                //
                if ($treatment !== true) {
                    //
                    return false;
                }
            }
        }

        //
        return true;
    }


    /**
     * Vérifie que l'utilsateur connecté soit lié à la collectivité de niveau 2
     * (multi) ou que l'objet instancié soit lié à une collectivité de niveau 1
     * (mono).
     * Cette méthode fonctionne seulement avec les objets ayant un champ
     * "om_collectivite" et dans un mode différent de l'ajout.
     *
     * @return boolean
     */
    public function is_user_multi_or_is_object_mono() {
        //
        if ($this->f->has_collectivite_multi() === true
            || $this->f->isCollectiviteMono($this->getVal("om_collectivite")) === true) {
            //
            return true;
        }

        //
        return false;
    }

    /**
     * Compose le nom de la séquence.
     *
     * @param string $datc  Code du type de DA
     * @param string $annee Année sur deux caractères
     * @param string $dep   Code du département sur trois caractères
     * @param string $com   Code du la commune sur trois caractères
     *
     * @return string  Nom de la séquence
     */
    public function compose_sequence_name($datc, $annee, $dep, $com) {
        /**
         * On vérifie la validité des trois paramètres.
         */
        // Vérification du code du type de dossier d'autorisation.
        if (!is_string($datc)) {
            // Logger
            $this->addToLog(
                __METHOD__."(): parametre datc = '".var_export($datc, true)."' invalide",
                DEBUG_MODE
            );
            // Stop le traitement
            return false;
        }

        // Vérification de l'année.
        if ($annee == null
            || !is_numeric($annee)
            || strlen($annee) > 2) {
            // Logger
            $this->addToLog(
                __METHOD__."(): champ annee = '".var_export($annee, true)."' invalide",
                DEBUG_MODE
            );
            // Stop le traitement
            return false;
        }

        // Vérification du code département.
        if ($dep == null
                || ((!is_numeric($dep) || (intval($dep) == 0 && $dep !== '000'))
                    // exceptions pour la Corse
                    && ! in_array(strtoupper($dep), array('02A', '02B')))
                || strlen($dep) > 3) {
            // Logger
            $this->addToLog(
                __METHOD__."(): om_parametre departement = '".var_export($dep, true)."' invalide",
                DEBUG_MODE
            );
            // Stop le traitement
            return false;
        }
        
        // Vérification du code commune.
        if ($com == null
            || !is_numeric($com)
            || (intval($com) == 0 && $com !== '000')
            || strlen($com) > 3) {
            // Logger
            $this->addToLog(
                __METHOD__."(): om_parametre commune = '".var_export($com, true)."' invalide",
                DEBUG_MODE
            );
            // Stop le traitement
            return false;
        }

        /**
         * On compose les identifiants qui composent la séquence.
         */
        // Clé unique.
        // Exemple : pc_15_013_055
        $unique_key = sprintf('%s_%s_%s_%s', strtolower($datc), $annee, $dep, $com);
        // Nom de la table représentant la séquence pour appel via la méthode
        // database::nextId() qui prend un nom de séquence sans son suffixe
        // '_seq'.
        // Exemple : openads.dossier_pc_15_013_055
        $table_name = sprintf('%sdossier_%s', DB_PREFIXE, $unique_key);
        // Nom de la séquence avec son suffixe 'seq'.
        // Exemple : openads.dossier_pc_15_013_055_seq
        $sequence_name = sprintf('%s_seq', $table_name);

        return strtolower($sequence_name);
    }

    /**
     * Méthode générique d'ajout d'un enregistrement simple.
     *
     * @param string $table Nom de la table
     * @param array  $data  Liste des données du nouvel enregistrement
     *
     * @return boolean
     */
    public function add_simple_record($table, array $data) {
        //
        $inst = $this->f->get_inst__om_dbform(array(
            "obj" => $table,
            "idx" => "]",
        ));
        //
        $val = array();
        // Initialisation de la clé primaire
        $val[$table] = "";
        if (is_array($data) === true) {
            foreach ($data as $key => $value) {
                $val[$key] = $value;
            }
        }
        //
        $add = $inst->ajouter($val);
        //
        return $add;
    }

    /**
     * Ajout de toutes les liaisons de l'enregistrement instancié dans la table
     * de lien
     *
     * @param string $link_table   Nom de la table de liaison
     * @param string $table_linked Nom de la table cible de la liaison
     * @param array  $datas        Liste des liens à réaliser
     *
     * @return mixed Retourne false en cas d'erreur, sinon le nombre de liaison
     */
    public function add_related_table($link_table, $table_linked, array $datas) {
        //
        $cpt = 0;
        foreach ($datas as $data) {
            //
            if ($data !== "" && $data !== null) {
                //
                $val = array(
                    $this->clePrimaire => $this->valF[$this->clePrimaire],
                    $table_linked => $data
                );
                //
                $add = $this->add_simple_record($link_table, $val);
                // En cas d'erreur, le traitement est stoppé
                if ($add === false) {
                    return false;
                }
                //
                $cpt++;
            }
        }
        //
        return $cpt;
    }

    /**
     * Supprime toutes les liaisons de l'enregistrement instancié dans la table
     * de lien.
     *
     * @param string $link_table Nom de la table de liaison
     *
     * @return boolean
     */
    public function delete_related_table($link_table) {
        $id_related_table = $this->getVal($this->clePrimaire);
        if (! empty($this->valF) && ! empty($this->valF[$this->clePrimaire])) {
            $id_related_table = $this->valF[$this->clePrimaire];
        }
        //
        $sql = sprintf("DELETE FROM %s%s WHERE %s = %s",
            DB_PREFIXE,
            $link_table,
            $this->clePrimaire,
            $id_related_table
        );
        $res = $this->f->db->query($sql);
        $this->addToLog(__METHOD__."(): db->query(\"".$sql."\");", VERBOSE_MODE);
        //
        $this->f->isDatabaseError($res);
        //
        return true;
    }

    /**
     * TREATMENT - Traitement des tables de liaison.
     * Suppression des toutes les liaisons d'un enregistrement en début de
     * chaque traitement.
     *
     * @param string $link_table   Nom de la table de liaison
     * @param string $table_linked Nom de la table cible de la liaison
     * @param string $link_field   Nom du champ qui représente la table de
     *                             liaison
     *
     * @return mixed Retourne false en cas d'erreur, sinon le nombre de liaison
     */
    public function handle_related_table($link_table, $table_linked, $link_field) {
        //
        $this->begin_treatment(__METHOD__);

        // Suppression de toutes les liaisons
        $delete = $this->delete_related_table($link_table);

        // Récupère la valeur postée du champ fictif
        $datas = $this->f->get_submitted_post_value($link_field);

        //
        if (is_array($datas) !== true || count($datas) === 0) {
            //
            return $this->end_treatment(__METHOD__, false);
        }

        // Ajout de toutes les liaisons
        $cpt = $this->add_related_table($link_table, $table_linked, $datas);

        // Retourne le nombre de liaison mise à jour ou ajoutée
        return $this->end_treatment(__METHOD__, $cpt);
    }

    /**
     * Récupère l'identifiant de l'enregistrement par rapport aux arguments.
     *
     * @param string $idx_name        Nom du champ de l'identifiant
     * @param string $table           Nom de la table
     * @param string $condition_field Nom du champ pour la condition
     * @param string $condition_value Valeur du champ de condition
     *
     * @return mixed Résultat de la requête ou null
     */
    public function get_idx_by_args($idx_name, $table, $condition_field, $condition_value) {

        // Retourne null si aucune valeur de condition transmise
        if ($condition_value === '' || $condition_value === null) {
            return null;
        }
        //
        $args = array(
            'idx_name' => $idx_name,
            'table' => $table,
            'condition_field' => $condition_field,
            'condition_value' => $condition_value,
        );
        //
        return $this->f->get_idx_by_args($args);
    }

    /**
     *  Vérifie qu'une chaîne est au format json
     * 
     * @param  string  $text chaîne à analyser
     * @return boolean       vrai si formatée json
     */
    public function isJson($text) {
        json_decode($text);
        return (json_last_error() == JSON_ERROR_NONE);
    }

    /**
     * Surcharge pour assigner les champs 'valF' à 'val' lorsque l'ajout a réussi;
     * et pour déclencher un hook après l'ajout de l'objet.
     */
    function ajouter($val = array(), &$dnu1 = null, $dnu2 = null) {
        $res = parent::ajouter($val, $dnu1, $dnu2);

        $data = array('val' => &$val, 'res' => &$res);
        if (! $this->f->module_manager->run_hooks('ajouter_post', $this, $data)) {
            return false;
        }
        return $res;
    }

    /*
     * Surcharge pour déclencher un hook avant/après la modification de l'objet.
     */
    function modifier($val = array(), &$dnu1 = null, $dnu2 = null) {
        $data = array('val' => &$val);
        if (! $this->f->module_manager->run_hooks('modifier_pre', $this, $data)) {
            return false;
        }

        $res = parent::modifier($val, $dnu1, $dnu2);

        $data['res'] = &$res;
        if (! $this->f->module_manager->run_hooks('modifier_post', $this, $data)) {
            return false;
        }
        return $res;
    }

    /*
     * Surcharge pour déclencher un hook avant/après la suppression de l'objet.
     */
    function supprimer($val = array(), &$dnu1 = null, $dnu2 = null) {
        $data = array('val' => &$val);
        if (! $this->f->module_manager->run_hooks('supprimer_pre', $this, $data)) {
            return false;
        }

        $res = parent::supprimer($val, $dnu1, $dnu2);

        $data['res'] = &$res;
        if (! $this->f->module_manager->run_hooks('supprimer_post', $this, $data)) {
            return false;
        }
        return $res;
    }

    /**
     * (surcharge pour permettre d'assigner une méthode à un objet tiers)
     *
     * Méthode permettant de produire les métadonnées associées à un champ.
     *
     * @param  string  $champ  Champ sur lequel on récupère les métadonnées
     *
     * @return array|null  Métadonnées du champ, ou null si une erreur survient
     */
    function getMetadata($champ) {

        // Initialisation du tableau de métadonnées
        $metadata = array();

        // Sources globale et locale des métadonnées
        $md_sources = array();
        if(isset($this->metadata_global) && !empty($this->metadata_global)) {
            $md_sources[] = $this->metadata_global;
        }
        if(isset($this->metadata[$champ]) && !empty($this->metadata[$champ])) {
            $md_sources[] = $this->metadata[$champ];
        }

        // Pour chaque source on associé la valeur retounrée par la fonction à la clé
        foreach($md_sources as $md_source) {
            foreach($md_source as $key => $callable) {
                // c'est une méthode locale à l'objet courant
                if (is_string($callable) && method_exists($this, $callable)) {
                    $metadata[$key] = $this->$callable($champ);
                }
                // c'est un callable
                elseif(is_callable($callable)) {
                    $metadata[$key] = call_user_func($callable, $champ);
                }
            }
        }
        return $metadata;
    }

    protected function getDossier($champ = null) {
        return $this->getVal('dossier');
    }

    protected function getDossierObject() {
        return $this->f->findObjectById('dossier', $this->getDossier(null));
    }

    protected function getDossierDateDecision($champ = null) {
        return $this->getDossierObject()->getVal('date_decision');
    }

    protected function getDossierAutorisationTypeDetaille($champ = null) {
        return $this->getDossierObject()->getVal('dossier_autorisation_type_detaille');
    }

    protected function getDossierInstructionTypeLibelle($champ = null) {
        $ditId = $this->getDossierObject()->getVal('dossier_instruction_type');
        if (! empty($ditId)) {
            $dit = $this->f->findObjectById('dossier_instruction_type', $ditId);
            if (! empty($dit)) {
                return $dit->getVal('libelle');
            }
        }
        return null;
    }

    protected function getDossierCommuneObject() {
        if ($this->f->is_option_dossier_commune_enabled()) {
            $communeId = $this->getDossierObject()->getVal('commune');
            if (!empty($communeId)) {
                return $this->f->findObjectById('commune', $communeId);
            }
        }
        return null;
    }

    protected function getDossierRegion($champ = null) {
        $commune = $this->getDossierCommuneObject();
        if (! empty($commune)) {
            return $commune->getVal('reg');
        }
        else {
            $params = $this->f->getCollectivite($this->getDossierObject()->getVal('om_collectivite'));
            if (! empty($params) && isset($params['region'])) {
                return $params['region'];
            }
        }
        return null;
    }

    protected function getDossierDepartement($champ = null) {
        $commune = $this->getDossierCommuneObject();
        if (! empty($commune)) {
            return strtoupper($commune->getVal('dep'));
        }
        else {
            $params = $this->f->getCollectivite($this->getDossierObject()->getVal('om_collectivite'));
            if (! empty($params) && isset($params['departement'])) {
                return strtoupper($params['departement']);
            }
        }
        return null;
    }

    protected function getDossierCommune($champ = null) {
        $commune = $this->getDossierCommuneObject();
        if (! empty($commune)) {
            return $commune->getVal('com');
        }
        else {
            $params = $this->f->getCollectivite($this->getDossierObject()->getVal('om_collectivite'));
            if (! empty($params) && isset($params['commune'])) {
                return $params['commune'];
            }
        }
        return null;
    }

    protected function getDossierAnnee($champ = null) {
        return $this->getDossierObject()->getVal('annee');
    }

    protected function getDossierDivision($champ = null) {
        $instructeurId = $this->getDossierObject()->getVal('instructeur');
        if (! empty($instructeurId)) {
            $instructeur = $this->f->findObjectById('instructeur', $instructeurId);
            if (! empty($instructeur)) {
                $divisionId = $instructeur->getVal('division');
                if (! empty($divisionId)) {
                    $division = $this->f->findObjectById('division', $divisionId);
                    if (! empty($division)) {
                        $directionId = $division->getVal('direction');
                        if (! empty($directionId)) {
                            $direction = $this->f->findObjectById('direction', $directionId);
                            if (! empty($direction)) {
                                return $direction->getVal('libelle');
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    protected function getDossierServiceOrCollectivite($champ = null) {
        $dossier = $this->getDossierObject();
        if (! empty($dossier)) {
            $collectiviteId = $dossier->getVal('om_collectivite');
            if (! empty($collectiviteId)) {
                $collectivite = $this->f->findObjectById('om_collectivite', $collectiviteId);
                if (! empty($collectivite)) {
                    return $collectivite->getVal('libelle');
                }
            }
        }
        return null;
    }

    protected function getDocumentType($champ = null) {
        if (in_array('libelle', $this->champs)) {
            return get_class($this).':'.$this->getVal('libelle');
        }
        return get_class($this);
    }

    /**
     * Surcharge pour permettre de lier l'objet courant à l'édition
     */
    function compute_pdf_output($type, $obj, $collectivite = null, $idx = null, $params = null) {
        // Initialisation de la variable de retour
        $res =  array(
            "pdf_output" => "",
            "filename" => "",
        );
        //
        if ($type == "lettretype") {
            //
            $script = "pdflettretype";
        } elseif ($type == "etat") {
            //
            $script = "pdfetat";
        } else {
            //
            return $res;
        }
        //
        $_GET['output'] = "string";
        $_GET['obj'] = $obj;
        // Paramétrage du filigrane
        if (isset($params['watermark']) && $params['watermark'] == true) {
            $_GET['watermark'] = 'true';
        }
        if (isset($params['specific'])
            && is_array($params['specific'])
            && count($params['specific']) > 0) {
            $_GET["specific"] = $params['specific'];
        }
        if (is_null($idx)) {
            $_GET['idx'] = $this->getVal($this->clePrimaire);
        } else {
            $_GET['idx'] = $idx;
        }
        //
        $om_edition = $this->f->get_inst__om_edition();
        $om_edition->set_object_linked($this);
        $pdfedition = call_user_func(array($om_edition, "view_".$script), $collectivite);
        //
        return $pdfedition;
    }

    /**
     * Surcharge pour permettre de lier l'objet courant à l'édition
     */
    function expose_pdf_output($pdf_output, $filename) {
        $om_edition = $this->f->get_inst__om_edition();
        $om_edition->set_object_linked($this);
        $om_edition->expose_pdf_output($pdf_output, $filename);
    }

    /**
     * Permet de modifier les champs de fusion d'une édition depuis un module
     *
     * @param  object  $edition  L'édition concernée
     * @param  array   $values   Les champs de fusion de l'édition
     *
     * @return void
     */
    public function modify_edition_merge_fields_values($edition, array &$values) {
        $values_keys = array_keys($values);

        $data = array('edition' => $edition, 'values' => &$values);
        $this->f->module_manager->run_hooks('modify_edition_merge_fields_values_post', $this, $data);

        $new_values_keys = array_keys($values);
        $removed_values_keys = array_diff($values_keys, $new_values_keys);
        if (! empty($removed_values_keys)) {
            $this->f->log(__METHOD__, "removed keys: ".implode(',', $removed_values_keys));
        }
        $added_values_keys = array_diff($new_values_keys, $values_keys);
        if (! empty($added_values_keys)) {
            $this->f->log(__METHOD__, "added keys: ".implode(',', $added_values_keys));
        }
        if (empty($removed_values_keys) && empty($added_values_keys)) {
            $this->f->log(__METHOD__, "no key removed or added");
        }
    }

    /**
     * Méthode appelée par le module_manager lors de la récupération des modules de cet objet.
     * Permet d'ajouer des modules en plus de ceux récupérés par défaut par le module_manager.
     *
     * @param  bool  $only_for_class   Ne renvoie que les modules pour la classe de l'objet
     *
     * @return array  Liste de modules supplémentaires pour cet objet
     */
    public function get_modules_append(bool $only_for_class = false, array $data = array() ) {
        return array();
    }

    /**
     * Méthode appelée par le module_manager lors de la récupération des modules de cet objet.
     * Permet de définir les modules à la place de ceux récupérés par défaut par le module_manager.
     *
     * @param  bool  $only_for_class   Ne renvoie que les modules pour la classe de l'objet
     *
     * @return array|null  Liste de modules pour cet objet, ou 'null' pour indiquer que
     * cette méthode ne remplace pas les modules par défaut (un 'array' vide voudrait dire
     * qu'il n'y a aucun module, ce qui est un remplacement valide)
     */
    public function get_modules_override(bool $only_for_class = false) {
        return null;
    }
}
