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"); $this->setup_extra_data($field_name, $object_name); $this->log(__METHOD__, 'END'); } /** * 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 setup_extra_data(string $name, string $object_name) { $this->log(__METHOD__, 'BEGIN'); $this->log(__METHOD__, "ajout du champ dynamique ($object_name, $name)", 'INFO'); $length = $this->get_field_length(); $libelle = $this->get_field_description(); $alias = $this->get_field_alias() ; $ret = $this->framework->get_extra_data_store()->setup_data( $object_name, $name, $this->get_value_type(), $length, $libelle, false, false, $alias ); if (is_string($ret)) { $this->log(__METHOD__, $ret, 'ERROR'); if (! empty($this->object)) { $this->object->correct = false; $this->object->addtoMessage( __("Erreur de l'initialisation des données complémentaires"). '. '.__("Détails: ").$ret); } } $this->log(__METHOD__, 'END'); return true; } /** * 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"); $data_type = $this->get_value_type(); $field_len = $this->get_field_length(); // objet associé à ce module $object = $this->object; $object_name = $this->get_object_name(); $obj_id = $object->getVal($object->clePrimaire); $field_name = $this->get_field_name(); $value_name = $this->get_field_value_name($field_name, $object_name); // 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': $i = count($object->champs); $object->champs[$i] = $field_name; $object->longueurMax[$i] = $field_len; $object->type[$i] = $data_type; $object->flags[$i] = ''; $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': $data['form']->setType($field_name, $data_type); if ($data['maj'] == 3) { $data['form']->setType($field_name, 'hiddenstatic'); } $this->log(__METHOD__, "($hook) set form field '$field_name' type '$data_type' (maj: ".var_export($data['maj'], true).")"); break; // vérifie la valeur du champ /*case $object_name.'_verifier_post': if (isset($data['val'])) { 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"); $posted_value = $this->cast_field_value( $this->framework->get_submitted_post_value($field_name) ); // get_field_value_from_db // récupère l'instance de la valeur du champ dynamique $ret = $this->framework->get_extra_data_store()->get_data( $object_name, array($obj_id), $field_name, $data_type ); $this->log(__METHOD__, "get_data() return: ".var_export($ret, true)); if (is_object($ret) && is_array($ret->items)) { $db_value = $this->cast_field_value($ret->items[0][2]); // la valeur est différente de celle stockée en BDD // la valeur existe if ($db_value != null && $posted_value !== $db_value) { // modification de la valeur /*foreach($ret 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->framework->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 = $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->framework->remove_html_tags($this->field_val_inst->msg), 'INFO'); } */ // nouvelle valeur } else { $ret = $this->framework->get_extra_data_store()->store_data( $object_name, array($obj_id), $field_name, $data_type, $posted_value ); if (! empty($ret->error_message)) { $error_message = sprintf( __("Échec lors de l'enregistrement de la donnée complémentaire '%s'"), $field_name).'. '.__("Détail: ").$ret->error_message; $this->object->correct = false; $this->object->addToMessage($error_message); $this->log(__METHOD__, $error_message); $this->log(__METHOD__, 'END'); return false; } } $object->addToMessage(sprintf( __("Donnée complé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(); $data_type = $this->get_data_type($field_name); // récupère l'instance de la valeur du champ dynamique $ret = $this->framework->get_extra_data_store()->get_data( 'instruction', array($obj_id), $field_name, $data_type ); $this->log(__METHOD__, "get_data() to delete return: ".var_export($ret, true), 'INFO'); if (is_object($ret) && is_array($ret->items) && isset($ret->items[0][2])) { $data_ids = array($ret_get_data->items[0][1]); $ret_delete_data = $this->framework->get_extra_data_store()->delete_data($data_ids, $data_type); if (! empty($ret_delete_data->error_message)) { $error_message = sprintf( __("Échec lors de la supression de la donnée complémentaire '%s'"), $field_name).'. '.__("Détail: ").$ret_delete_data->error_message; $this->object->correct = false; $this->object->addToMessage($error_message); $this->log(__METHOD__, $error_message); $this->log(__METHOD__, 'END'); return false; } } $object->addToMessage(sprintf( __("Donnée complémentaire '%s' supprimée"), $field_name)); break; // assigne la valeur par défaut du champ case $object_name.'_set_form_default_values_post': $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_desc = $this->get_field_description(); $data['form']->setFieldset($field_name, 'DF', __("Données complémentaires")); break; // défini le libellé du champ case $object_name.'_setLib_post': $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_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': $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'])) { $def_ref = $$this->framework->get_extra_data_store()->get_field_definition_id($object_name, $field_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'); return true; } /** * 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 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'] ?? 'text'; } /** * 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'] ?? $this->get_field_name(); } /** * 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 = 255; $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: '.$this->get_field_name(); } /** * 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 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 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; } $data_type = $this->get_value_type(); $obj_id = $object->getVal($object->clePrimaire); $field_name = $this->get_field_name(); // récupère l'instance de la valeur du champ dynamique $ret = $this->framework->get_extra_data_store()->get_data( $object_name, array($obj_id), $field_name, $data_type ); $this->log(__METHOD__, "get_data() return: ".var_export($ret, true)); if (is_object($ret) && is_array($ret->items) && isset($ret->items[0][2])) { // [0] first item, [2] the value return $this->cast_field_value($ret->items[0][2]); } return null; } }