object) || get_class($this->object) !== 'instruction') { return; }*/ $this->log(__METHOD__, 'BEGIN'); $field_name = $this->get_field_name(); $object_name = $this->get_object_name(); $this->log(__METHOD__, "field_name: $field_name"); $this->log(__METHOD__, "object_name: $object_name"); // si le champ n'a pas été défini dans la table de définition des champs dynamiques if (! ($field_def_inst = $this->get_field_definition($field_name, $object_name))) { // on le défini $field_def_inst = $this->save_field_definition($field_name, $object_name); } $this->field_def_inst = $field_def_inst; $this->log(__METHOD__, 'END'); } /** * Renvoie 'true' si le champ est déjà défini dans la table des champs dynamiques, * 'false' sinon. * * @param string $name Le nom du champ recherché * @param string $object_name L'objet concerné par cette donnée (optionnel) * * @return boolean 'true' si le champ est déjà défini, 'false' sinon */ protected function get_field_definition(string $name, ?string $object_name = null) { $this->log(__METHOD__, 'BEGIN'); $name_escaped = $this->framework->db->escapeSimple($name); $sqlWhere = "name = '".$name_escaped."'"; if (! empty($object_name)) { $object_name_escaped = $this->framework->db->escapeSimple($object_name); $sqlWhere .= " AND object_name = '".$object_name_escaped."'"; } $this->log(__METHOD__, "sql WHERE: $sqlWhere"); $ret = $this->framework->findObjectByCondition('extra_data_register_def', $sqlWhere); if (! empty($ret)) { $this->log(__METHOD__, 'return: '.get_class($ret).'#'.$ret->getVal($ret->clePrimaire)); } else { $this->log(__METHOD__, 'return: '.var_export($ret, true)); } $this->log(__METHOD__, 'END'); return $ret; } /** * Retire les balises HTML d'un message * * @param string $html_msg Le message avec des balises HTML * * @return string Le message sans les balises HTML */ protected function remove_html_tags(string $html_msg) { return strip_tags( str_replace('..', '.', str_replace('. . ', '. ', str_replace(array('
', '
', '
'), '. ', $html_msg)))); } /** * Ajout la définition du champ dans la table des champs dynamiques. * * @param string $name Le nom du champ recherché * @param string $object_name L'objet concerné par cette donnée (optionnel) * * @return void */ protected function save_field_definition(string $name, string $object_name) { $this->log(__METHOD__, 'BEGIN'); $val = array( 'extra_data_register_def' => ']', 'name' => $name, 'object_name' => $object_name, 'type' => $this->get_value_type(), 'max_length' => $this->get_field_length(), 'description' => $this->get_field_description(), 'alias_champ_fusion' => $this->params['alias_champ_fusion'] ?? null ); $field_def_inst = $this->framework->get_inst__om_dbform( array('obj' => 'extra_data_register_def', 'idx' => ']')); $this->log(__METHOD__, "ajoute le champ dynamique ($object_name, $name) avec: ".var_export($val, true)); if (! ($res = $field_def_inst->ajouter($val))) { $err_msg = sprintf( __("Échec de l'enregistrement de la définition du champ dynamique '%s'."), $name); $err_msg_detail = $this->remove_html_tags($field_def_inst->msg); if (! empty($object = $this->object)) { $object->correct = false; $object->addToMessage($err_msg .' '.sprintf(__("Détail: %s."), $err_msg_detail)); } else { $err_msg .= " dans 'extra_data_register_def' ($object_name, $name)."; $this->log(__METHOD__, $err_msg .' '.sprintf(__("Détail: %s."), $err_msg_detail), 'CRITICAL'); throw new RuntimeException($err_msg .' '.sprintf(__("Détail: %s."), $err_msg_detail)); } } $this->log(__METHOD__, "ajout du champ dynamique ($object_name, $name)", 'INFO'); $ret = $field_def_inst; $this->log(__METHOD__, 'return: '.get_class($field_def_inst).'#'.$field_def_inst->getVal($field_def_inst->clePrimaire)); $this->log(__METHOD__, 'END'); return $ret; } /** * Renvoie le nom de la colonne « virtuelle » pour ce champ ajouté * * @return string */ protected function get_virt_column_name() { return 'xd__'.$this->get_object_name().'__'.$this->get_field_name(); } /** * Fonction principale du module, qui est appelée à chaque déclenchement d'un "hook". * Il revient à cette fonction de savoir à quel moment faire quoi, en filtrant sur le * "hook" passé en paramètre. * * @param string $hook Le nom du "hook" (préfixé par le nom de l'objet) * @param array $data Les données du contexte * * @return void */ public function main(string $hook, array &$data = array()) { $this->log(__METHOD__, "($hook) BEGIN"); $object_name = $this->get_object_name(); // objet associé à ce module $object = $this->object; // object non-vide if (! empty($object)) { $this->log(__METHOD__, "($hook) object: ".get_class($object). "#".$object->getVal($object->clePrimaire)); switch($hook) { // ajoute le champ à la classe/vue de l'objet //case $object_name.'_init_record_data_post': case $object_name.'_formulaire_pre': case $object_name.'_sousformulaire_pre': $field_name = $this->get_field_name(); $value_type = $this->get_value_type(); $field_len = $this->get_field_length(); $i = count($object->champs); $object->champs[$i] = $field_name; $object->longueurMax[$i] = $field_len; $object->type[$i] = $value_type; $object->flags[$i] = ''; $value = $this->cast_field_value( $this->get_field_value($field_name, $object_name, $object)); $object->val[$i] = $value; $this->log(__METHOD__, "($hook) added champ '$field_name'"); break; // défini le type du champ dans le formulaire case $object_name.'_setType_post': $field_name = $this->get_field_name(); $field_type = $this->get_field_type(); $data['form']->setType($field_name, $field_type); if ($data['maj'] == 3) { $data['form']->setType($field_name, 'hiddenstatic'); } $this->log(__METHOD__, "($hook) set form field '$field_name' type '$field_type' (maj: ".var_export($data['maj'], true).")"); break; // vérifie la valeur du champ case $object_name.'_verifier_post': if (isset($data['val'])) { $field_name = $this->get_field_name(); if (! isset($data['val'][$field_name]) || empty($data['val'][$field_name])) { $this->log(__METHOD__, "($hook) 'val.$field_name is not defined or empty"); $object->correct = false; $object->addToMessage(sprintf(__("Le champ '%s' ne peut pas être vide"), $field_name)); } else { $cb = $this->get_field_validator($object); if (is_callable($cb)) { $cb_data = array( 'field_name' => $field_name, 'object_name' => $object_name, 'object' => &$object, 'hook' => $hook, 'data' => &$data); $res = call_user_func_array($cb, $cb_data); if ($res !== true) { $object->correct = false; $err_msg = $res; if (! is_string($res)) { $err_msg = sprintf(__("Le champ '%s' est invalide"), $field_name); } $object->addToMessage($err_msg); } } elseif (intval($data['val'][$field_name]) < 0) { $this->log(__METHOD__, "($hook) 'val.$field_name': ".var_export($data['val'][$field_name], true)); $object->correct = false; $object->addToMessage(sprintf(__("Le champ '%s' ne peut pas avoir une valeur inférieure à zéro"), $field_name)); } } } else { $this->log(__METHOD__, "($hook) 'val' is not defined"); } break; // enregistre la valeur en base de données case $object_name.'_triggerajouterapres_post': case $object_name.'_triggermodifierapres_post': $this->log(__METHOD__, "($hook) BEGIN"); $field_name = $this->get_field_name(); $posted_value = $this->cast_field_value( $this->framework->get_submitted_post_value($field_name)); $db_value = $this->cast_field_value( $this->get_field_value_from_db($field_name, $object_name, $object)); // la valeur est différente de celle stockée en BDD if ($posted_value !== $db_value) { // récupère l'instance de la valeur du champ dynamique if (is_null($this->field_val_inst)) { $this->field_val_inst = $this->get_field_value_instance( $field_name, $object_name, $object); } $value_name = $this->get_field_value_name($field_name, $object_name); // la valeur existe if (! empty($this->field_val_inst)) { $val = $this->field_val_inst->get_array_val(); foreach($val as $key => $value) { if (strpos($key, 'value_') === 0) { $val[$key] = null; } } $val[$value_name] = $posted_value; $this->log(__METHOD__, "($hook) updating value record with: ". var_export($val, true)); // modification de la valeur $this->field_val_inst->setParameter('maj', 1); if (! $this->field_val_inst->modifier($val)) { $object->correct = $this->field_val_inst->correct; $object->addToMessage($this->field_val_inst->msg); $this->log( __METHOD__, "($hook) failed to update value record for ". "'extra_data_register_values' with: ".var_export($val, true). ". Detail: ".$this->remove_html_tags($this->field_val_inst->msg), 'CRITICAL'); /*throw new RuntimeException( "Échec de l'enregistrement de la valeur du champ ". "($object_name, $field_name)");*/ } else { $obj_id = $object->getVal($object->clePrimaire); $val_id = $this->field_val_inst->getVal($this->field_val_inst->clePrimaire); $def_id = $this->field_val_inst->getVal('definition_ref'); $this->log(__METHOD__, "valeur modifiée (#$val_id, d:$def_id, obj:$object_name, ". "id:$obj_id, f:$field_name, v:$value_name)". ". Detail: ".$this->remove_html_tags($this->field_val_inst->msg), 'INFO'); } // nouvelle valeur } else { $def_id = $this->get_field_definition_id($field_name, $object_name); $obj_id = $object->getVal($object->clePrimaire); $val = array( 'extra_data_register_values' => ']', $value_name => $posted_value, 'definition_ref' => $def_id, 'object_id' => $obj_id); $field_val_inst = $this->framework->get_inst__om_dbform( array('obj' => 'extra_data_register_values', 'idx' => ']')); $this->log(__METHOD__, "($hook) adding value record with: ". var_export($val, true)); // ajout de la valeur $this->log(__METHOD__, "($hook) setting 'maj' = 0"); $field_val_inst->setParameter('maj', 0); if (! $field_val_inst->ajouter($val)) { $object->correct = $field_val_inst->correct; $object->addToMessage($field_val_inst->msg); $this->log( __METHOD__, "($hook) failed to add value record for ". "'extra_data_register_values' with: ".var_export($val, true). ". Detail: ".$this->remove_html_tags($this->field_val_inst->msg), 'CRITICAL'); /*throw new RuntimeException( "Échec de l'enregistrement de la valeur du champ ". "($object_name, $field_name)");*/ } else { $val_id = $field_val_inst->getVal($field_val_inst->clePrimaire); $this->log(__METHOD__, "valeur ajoutée (#$val_id, d:$def_id, obj:$object_name, ". "id:$obj_id, f:$field_name, v:$value_name)". ". Detail: ".$this->remove_html_tags($field_val_inst->msg), 'INFO'); } $this->field_val_inst = $field_val_inst; } $object->addToMessage(sprintf( __("Donnée supplémentaire '%s' enregistrée"), $field_name)); } break; // retire la valeur de la base de données case $object_name.'_triggersupprimerapres_post': $this->log(__METHOD__, "($hook) BEGIN"); $field_name = $this->get_field_name(); if (is_null($this->field_val_inst)) { $this->field_val_inst = $this->get_field_value_instance( $field_name, $object_name, $object); } if (! empty($this->field_val_inst)) { $clePrimaire = $this->field_val_inst->clePrimaire; $id = $this->field_val_inst->getVal($clePrimaire); $val = array($clePrimaire => $id); $def_id = $this->field_val_inst->getVal('definition_ref'); $obj_id = $this->field_val_inst->getVal('object_id'); // suppression de la valeur $this->log(__METHOD__, "($hook) setting 'maj' = 2"); $this->field_val_inst->setParameter('maj', 2); if (! $this->field_val_inst->supprimer($val)) { $this->log( __METHOD__, "($hook) failed to remove value record for ". "'extra_data_register_values' with: ".var_export($val, true). ". Detail: ".$this->remove_html_tags($this->field_val_inst->msg), 'CRITICAL'); /*throw new RuntimeException( "Échec de la suppression de la valeur du champ ". "($object_name, $field_name)");*/ } else { $this->log(__METHOD__, "valeur supprimée (#$id, d:$def_id, obj:$object_name, ". "id:$obj_id, f:$field_name, v:$value_name)". ". Detail: ".$this->remove_html_tags($this->field_val_inst->msg), 'INFO'); } $object->addToMessage(sprintf( __("Donnée supplémentaire '%s' supprimée"), $field_name)); } break; // assigne la valeur par défaut du champ case $object_name.'_set_form_default_values_post': $field_name = $this->get_field_name(); $value = $this->cast_field_value( $this->get_field_value($field_name, $object_name, $object)); $data['form']->setVal($field_name, $value); $this->log(__METHOD__, "($hook) setVal: ".var_export($value, true)); break; // défini l'emplacement du champ dans le formulaire case $object_name.'_setLayout_post': $field_name = $this->get_field_name(); $field_desc = $this->get_field_description(); $data['form']->setFieldset($field_name, 'DF', __("Données supplémentaires")); break; // défini le libellé du champ case $object_name.'_setLib_post': $field_name = $this->get_field_name(); $field_desc = $this->get_field_description(); $data['form']->setLib($field_name, $field_desc); break; // défini la longueur maximale du champ case $object_name.'_setMax_post': $field_name = $this->get_field_name(); $field_len = $this->get_field_length(); if (! empty($field_len)) { $data['form']->setMax($field_name, $field_len); } break; // déclare le champ de fusion case $object_name.'_modify_edition_merge_fields_values_post': $field_name = $this->get_field_name(); $value = $this->cast_field_value( $this->get_field_value($field_name, $object_name, $object)); $field_alias = $this->get_field_alias(); $data['values'][$field_alias] = $value; break; // action par défaut default: break; } } // listing (objet vide) else { $this->log(__METHOD__, "($hook) object: ".var_export($object, true)); switch($hook) { // ajoute le champ au listing // et à la recherche case $object_name.'_table_init_pre': if (isset($data['table']) && ! empty($data['table'])) { $field_name = $this->get_field_name(); $value_name = $this->get_field_value_name($field_name, $object_name); $def_ref = $this->get_field_definition_id($field_name, $object_name); $virt_col_name = $this->get_virt_column_name(); // le champ est ajouté en tant que colonne du listing de l'objet if ($show_in_listing = ($this->params['show_in_listing'] ?? false)) { $this->log(__METHOD__, "($hook) showing field '$field_name' in listing ?: ".var_export($show_in_listing, true)); $new_champAffiche = "$virt_col_name.".$value_name.' as "'.$field_name.'"'; // si le champ dynamique n'est pas déjà affiché if (! in_array($new_champAffiche, $data['table']->champAffiche)) { $data['table']->champAffiche[] = $new_champAffiche; } } // le champ est « recherchable » ou utilisé dans le listing if ($searchable = ($this->params['searchable'] ?? false) || $show_in_listing) { $new_champRecherche = "$virt_col_name.".$value_name.' as "'.$field_name.'"'; // si le champ dynamique n'est pas déjà recherché if (! in_array($new_champRecherche, $data['table']->champRecherche)) { $data['table']->champRecherche[] = $new_champRecherche; $obj = $this->framework->get_submitted_get_value('obj'); if ($obj === 'dossier_instruction') { $obj = 'dossier'; } $object_table = $obj; $object_cle_primaire = $object_table; $object_join_equality_on = "$object_table.$object_cle_primaire::varchar"; $this->log(__METHOD__, "object_join_equality_on: $object_join_equality_on"); // recherche une option de type 'search' $search_option_index = null; $search_option = null; foreach($data['table']->options as $index => $option) { if (! isset($option['type']) || $option['type'] != 'search') continue; $search_option = $data['table']->options[$index]; $search_option_index = $index; break; } // recherche avancée if (! empty($search_option)) { $this->log(__METHOD__, "($hook) mode recherche avancée"); if (! isset($search_option['absolute_object']) || empty($search_option['absolute_object'])) { throw new RuntimeException("Mode recherche avancée et 'absolute_object' non spécifié"); } $object_table = $search_option['absolute_object']; $object_cle_primaire = $object_table; $object_join_equality_on = "$object_table.$object_cle_primaire::varchar"; $this->log(__METHOD__, "object_join_equality_on: $object_join_equality_on"); $field_desc = $this->get_field_description(); $field_type = $this->get_field_type(); $field_length = $this->get_field_length(); $sql_cast = ''; switch($this->get_value_type()) { case 'int': $sql_cast = '::varchar'; break; } $table_option_to_add = array( 'libelle' => $field_desc, 'type' => ($field_type == 'textarea' ? 'text' : $field_type), 'table' => $virt_col_name, 'colonne' => $value_name, 'taille' => 30, 'max' => ($field_length >= 50 ? 50 : $field_length), ); $data['table']->options[$search_option_index]['advanced'][$field_name] = $table_option_to_add; $this->log(__METHOD__, "added: options[$search_option_index]". "['advanced'][$field_name] = ". var_export($table_option_to_add, true)); } /* // recherche avancée activée $av_search_enabled = $data['table']->isAdvancedSearchEnabled(); // recherche saisie par l'utilisateur $user_search_submit = $this->framework->get_submitted_post_value( 'advanced-search-submit'); $av_search_prep = $data['table']->_rechercheAvanceeFaite; // recherche simple (ou recherche avancée mais l'utilisateur // n'a pas encore saisi de critères) if (! $av_search_enabled || is_null($user_search_submit) || ! $av_search_prep) { $this->log(__METHOD__, "($hook) mode recherche simple"); }*/ // ajout de la jointure sur la table des valeurs $join = "LEFT JOIN ".DB_PREFIXE."extra_data_register_values AS $virt_col_name\n ". " ON $virt_col_name.definition_ref = $def_ref AND $virt_col_name.object_id = $object_join_equality_on\n \n"; $data['table']->table .= "\n ".$join; $this->log(__METHOD__, "added join: $join"); } } } break; } } $this->log(__METHOD__, 'END'); } /** * Renvoie le nom du champ dynamique, tel que défini dans les paramètres ou avec une * valeur par défaut. * * @return string */ protected function get_field_name() { return $this->params['field_name'] ?? 'extra_data'; } /** * Renvoie le nom de l'objet auquel le champ dynamique est rattaché. * Si ce nom est défini dans les paramètres du module, alors il a la précédence. * Ensuite si un objet métier (om_dbform) a été associé à ce module, alors celui-ci est revoyé. * Enfin, si un objet "lien" est associé à ce module, alors c'est le nom de l'objet défini * dans l'objet "lien" qui est utilisé. * * @return string */ protected function get_object_name() { // le nom renseigné dans le paramétrage a précédence $object_name = $this->params['object_name'] ?? null; $this->log(__METHOD__, "object_name[params]: $object_name"); if (! empty($object_name)) return $object_name; // puis le nom de la classe de l'objet métier rattaché à ce module if (! empty($this->object)) { $object_name = get_class($this->object); $this->log(__METHOD__, "object_name[object]: $object_name"); if (! empty($object_name)) return $object_name; } // enfin le nom de l'objet défini dans l'object "lien" du module if (! empty($this->object_link_instance)) { $object_name = $this->object_link_instance->getVal('object_name'); $this->log(__METHOD__, "object_name[object_link]: $object_name"); if (! empty($object_name)) return $object_name; } return $object_name; } /** * Renvoie le type de donnée du champ dynamique, tel que défini dans les paramètres ou * avec une valeur par défaut. * * @return string */ protected function get_value_type() { return $this->params['type'] ?? 'int'; } /** * Renvoie le type du champ dynamique, tel que défini dans les paramètres ou avec une * valeur par défaut. * * @return string */ protected function get_field_type() { return $this->params['field_type'] ?? 'text'; } /** * Renvoie le nom du champ de fusion du champ dynamique, tel que défini dans les paramètres * ou avec une valeur par défaut. * * @return string */ protected function get_field_alias() { return $this->params['alias_champ_fusion'] ?? 'extra_data'; } /** * Renvoie la longueur maximale du champ dynamique, telle que défini dans les paramètres * ou avec une valeur par défaut. * * @return int */ protected function get_field_length() { $len = $this->params['max_length'] ?? null; if (empty($len)) { $len = 100; $value_type = $this->get_value_type(); if ($value_type == 'int') { $len = 5; } } return intval($len); } /** * Renvoie la description du champ dynamique, telle que défini dans les paramètres ou avec * une valeur par défaut. * * @return string */ protected function get_field_description() { return $this->params['description'] ?? 'extra_data'; } /** * Renvoie une fonction de validation du champ dynamique, telle que défini dans les * paramètres ou avec une valeur par défaut. * * @param om_dbform $object L'objet métier auquel est rattaché le champ dynamique * * @return array|callable */ protected function get_field_validator(?om_dbform $object = null) { $cb = $this->params['validator_cb'] ?? null; if (is_array($cb) && count($cb) == 2) { if($cb[0] == 'module') { $cb[0] = $this; $this->log(__METHOD__, "replacing 'module' by the actual module instance"); } elseif($cb[0] == 'object') { $cb[0] = $object; $this->log(__METHOD__, "replacing 'object' by the actual object instance"); } } return $cb; } /** * Renvoie l'identifiant de l'enregistrement en BDD de la définition du champ dynamique. * * @param string $name Le nom du champ dynamique * @param string $object_name Le type d'objet auquel est rattaché le champ dynamique * * @return string */ protected function get_field_definition_id(string $name, string $object_name) { if (empty($this->field_def_inst)) { if (! ($field_def_inst = $this->get_field_definition($name, $object_name))) { throw new RuntimeException( "Échec de la récupération de la définition du champ ". "($object_name, $name)"); } $this->field_def_inst = $field_def_inst; } return $this->field_def_inst->getVal($this->field_def_inst->clePrimaire); } /** * Renvoie une instance de l'enregistrement en BDD de la valeur du champ dynamique. * * @param string $name Le nom du champ dynamique * @param string $object_name Le type d'objet auquel est rattaché le champ dynamique * @param om_dbform $object L'objet métier auquel est rattaché le champ dynamique * * @return om_dbform|null */ protected function get_field_value_instance(string $name, string $object_name, om_dbform $object) { if (is_null($this->field_val_inst)) { $def_id = $this->get_field_definition_id($name, $object_name); $obj_id = $object->getVal($object->clePrimaire); $obj_id_escaped = $this->framework->db->escapeSimple($obj_id); $sqlWhere = " definition_ref = $def_id AND object_id = '$obj_id_escaped'"; $field_value_inst = $this->framework->findObjectByCondition( 'extra_data_register_values', $sqlWhere); $this->field_val_inst = $field_value_inst; } return $this->field_val_inst; } /** * Renvoie le nom de la colonne en BDD contenant la valeur du champ dynamique. * * @param string $name Le nom du champ dynamique * @param string $object_name Le type d'objet auquel est rattaché le champ dynamique * * @return string */ protected function get_field_value_name(string $name, string $object_name) { $value_type = $this->get_value_type(); return "value_$value_type"; } /** * Renvoie la valeur après conversion de son type (cast). * * @param mixed $value La valeur dans un type quelconque (généralement 'string') * * @return mixed La valeur après conversion de son type (cast). */ protected function cast_field_value($value) { $value_type = $this->get_value_type(); if (is_null($value)) { return null; } switch($value_type) { case 'int': $value = intval($value); break; } return $value; } /** * Renvoie la valeur en BDD du champ dynamique. * * @param string $name Le nom du champ dynamique * @param string $object_name Le type d'objet auquel est rattaché le champ dynamique * @param om_dbform $object L'objet métier auquel est rattaché le champ dynamique * * @return mixed La valeur récupérée en BDD (non "castée" vers son type) */ protected function get_field_value_from_db(string $name, string $object_name, om_dbform $object) { $field_val_inst = $this->get_field_value_instance( $name, $object_name, $object); if (! is_null($field_val_inst)) { $value_type = $this->get_value_type(); $value_name = $this->get_field_value_name($name, $object_name); return $field_val_inst->getVal($value_name); } return null; } /** * Renvoie soit la valeur postée (formulaire) du champ dynamique, soit la valeur en BDD. * * @param string $name Le nom du champ dynamique * @param string $object_name Le type d'objet auquel est rattaché le champ dynamique * @param om_dbform $object L'objet métier auquel est rattaché le champ dynamique * * @return mixed La valeur récupérée en BDD (non "castée" vers son type) */ protected function get_field_value(string $name, string $object_name, om_dbform $object) { $posted_value = $this->framework->get_submitted_post_value($name); if (! is_null($posted_value)) { return $posted_value; } return $this->get_field_value_from_db($name, $object_name, $object); } /** * Valide la valeur du champ dynamique. * * Renvoie un message si la valeur n'est pas valide, sinon 'true' (bool). * * @return bool|string */ public function test_valid( string $field_name, string $object_name, om_dbform $object, string $hook, array &$data) { $this->log(__METHOD__, "($field_name, $object_name, $hook) BEGIN"); if (intval($data['val'][$field_name]) > 20) { return sprintf( __("Le champ '%s' ne peut avoir une valeur supérieure à 20 (exemple d'erreur)"), $field_name); } return true; } }