get_field_definition($object_name, $name))) { // on le défini $val = array( 'extra_data_register_def' => ']', 'object_name' => $object_name, 'name' => $name, 'type' => $type, 'max_length' => $length, 'description' => $libelle, 'alias_champ_fusion' => ! empty($alias) ? $alias : 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->framework->remove_html_tags($field_def_inst->msg); $this->log(__METHOD__, $err_msg .' '.sprintf(__("Détail: %s."), $err_msg_detail), 'CRITICAL'); return $err_msg; } $this->log(__METHOD__, "ajout du champ dynamique ($object_name, $name)"); } return true; } /** * Enregistre une donnée « supplémentaire » en base de données. * * @param string $object_name Le nom de l'objet (ou sa classe) correspondant à sa table en BDD * @param array $object_ids La liste des identifiants des objets concernés * @param string $data_name Le nom de la valeur stockée (nom du champ ou de la clé) * @param string $data_type Le type de donnée à stocker (int, str, json, blob, timestamp, etc.) * @param mixed $data_value La valeur à stocker * * @return extra_data_result * @return array ([(, ), ...]|null, |null) * si l'opération a échouée le premier élément sera null, * et un message d'erreur indiquera la raison de l'échec, * sinon c'est l'inverse, le premier élément sera un tableau et il n'y * aura aucun message (null). */ public function store_data(string $object_name, array $object_ids, string $data_name, string $data_type, $data_value) { // récupération de la définition du champ $def_id = $this->get_field_definition_id($object_name, $data_name); if (empty($def_id)) { return new extra_data_result(null, sprintf( __("Erreur: aucune définition pour l'objet '%s' et la donnée '%s'"), $object_name, $data_name)); } // nombre d'objets concernés $object_ids_count = count($object_ids); // TODO implémenter le cas avec aucun identifiant d'objet (valeur de classe) $value_name = "value_$data_type"; // cas d'un identifiant unique d'objet if ($object_ids_count === 1) { // récupère l'instance de la valeur du champ dynamique $obj_id = $object_ids[0]; $obj_id_escaped = $this->framework->db->escapeSimple($obj_id); $sqlWhere = " definition_ref = $def_id AND object_id = '$obj_id_escaped'"; $field_val_inst = $this->framework->findObjectByCondition( 'extra_data_register_values', $sqlWhere); // la valeur existe if (! empty($field_val_inst)) { $val = $field_val_inst->get_array_val(); foreach($val as $key => $value) { if (strpos($key, 'value_') === 0) { $val[$key] = null; } } $val[$value_name] = $data_value; $this->log(__METHOD__, "updating value record with: ". var_export($val, true)); // modification de la valeur $field_val_inst->setParameter('maj', 1); if (! $field_val_inst->modifier($val)) { $this->log( __METHOD__, "failed to update value record for ". "'extra_data_register_values' with: ".var_export($val, true). ". Detail: ".$this->framework->remove_html_tags($field_val_inst->msg), 'CRITICAL'); return new extra_data_result(null, $this->framework->remove_html_tags($field_val_inst->msg)); } else { $val_id = $field_val_inst->getVal($field_val_inst->clePrimaire); $this->log(__METHOD__, "valeur modifiée (#$val_id, d:$def_id, obj:$object_name, ". "id:$obj_id, f:$data_name, v:$value_name)". ". Detail: ".$this->framework->remove_html_tags($field_val_inst->msg), 'INFO'); } // nouvelle valeur } else { $val = array( 'extra_data_register_values' => ']', $value_name => $data_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__, "adding value record with: ". var_export($val, true)); // ajout de la valeur $this->log(__METHOD__, "setting 'maj' = 0"); $field_val_inst->setParameter('maj', 0); if (! $field_val_inst->ajouter($val)) { $this->log( __METHOD__, "failed to add value record for ". "'extra_data_register_values' with: ".var_export($val, true). ". Detail: ".$this->framework->remove_html_tags($field_val_inst->msg), 'CRITICAL'); return array(null, $this->framework->remove_html_tags($field_val_inst->msg)); } 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:$data_name, v:$value_name).", 'INFO'); } $field_val_inst = $field_val_inst; } return new extra_data_result( array(array($obj_id, $val_id)), null); } // multiple objets concernés elseif ($object_ids_count > 1) { // prépare une requête SQL de modification multiple // qui insère les nouvelles données et en cas de conflit // modifie les données existantes $sql = "INSERT INTO extra_data_register_values ( extra_data_register_values, definition_ref, object_id, $value_name) VALUES "; foreach($object_ids as $obj_id) { $obj_id_escaped = $this->framework->db->escapeSimple($obj_id); $data_value_escaped = $this->framework->db->escapeSimple($data_value); if ($data_type != 'int') { $data_value_escaped = "'$data_value_escaped'"; } $sql .= " (nextval('extra_data_register_values'), $def_id, '$obj_id_escaped', $data_value_escaped),"; } $sql = preg_replace(',$', '', $sql); $sql .= " ON CONFLICT (definition_ref, object_id) DO UPDATE SET $value_name = EXCLUDED.$data_type RETURNING object_id, extra_data_register_values; "; $this->log(__METHOD__, "sql: $sql"); // exécute la requête SQL $qres = $this->framework->execute_db_query($sql, array('origin', __METHOD__)); // si quelque chose s'est mal passé if ($qres['code'] != 'OK') { // on renvoie le message d'erreur return array(null, $this->framework->remove_html_tags($qres['message'])); } // sinon on récupère la liste des identifiants des données $tuples = array(); foreach($qres['result'] as $qr) { $tuples[] = array($qr['object_id'], $qr['extra_data_register_values']); } return new extra_data_result($tuples, null); } return false; // TODO replace with an 'extra_data_result' or document the type } /** * Récupère une donnée « supplémentaire » en base de données. * * @param string $object_name Le nom de l'objet (ou sa classe) correspondant à sa table en BDD * @param array $object_ids La liste des identifiants des objets concernés * @param string $data_name Le nom de la valeur stockée (nom du champ ou de la clé) * @param string $data_type Le type de donnée à stocker (int, str, json, blob, timestamp, etc.) * * @return extra_data_result * @return array ([(, , ), ...]|null, |null) * si l'opération a échouée le premier élément sera null, * et un message d'erreur indiquera la raison de l'échec, * sinon c'est l'inverse, le premier élément sera un tableau et il n'y * aura aucun message (null). */ public function get_data(string $object_name, array $object_ids, string $data_name, string $data_type) { // récupération de la définition du champ $def_id = $this->get_field_definition_id($object_name, $data_name); if (empty($def_id)) { return new extra_data_result(null, sprintf( __("Erreur: aucune définition pour l'objet '%s' et la donnée '%s'"), $object_name, $data_name)); } // nombre d'objets concernés $object_ids_count = count($object_ids); // TODO implémenter le cas avec aucun identifiant d'objet (valeur de classe) $value_name = "value_$data_type"; // cas d'un identifiant unique d'objet if ($object_ids_count === 1) { // récupère l'instance de la valeur du champ dynamique $obj_id = $object_ids[0]; $obj_id_escaped = $this->framework->db->escapeSimple($obj_id); $sqlWhere = " definition_ref = $def_id AND object_id = '$obj_id_escaped'"; $field_val_inst = $this->framework->findObjectByCondition( 'extra_data_register_values', $sqlWhere); // la valeur existe if (! empty($field_val_inst)) { $val_id = $field_val_inst->getVal($field_val_inst->clePrimaire); $value = $field_val_inst->getVal($value_name); $this->framework->log(__METHOD__, "La valeur '#$val_id' pour l'objet '#$obj_id' (def: $def_id) vaut ($value_name): ".var_export($value, true)); $this->framework->log(__METHOD__, "champs: ".print_r($field_val_inst->champs, true)); $this->framework->log(__METHOD__, "val: ".print_r($field_val_inst->val, true)); return new extra_data_result( array(array($obj_id, $val_id, $value)), null); } $this->framework->log(__METHOD__, "La valeur pour l'objet '#$obj_id' (def: $def_id) n'existe pas"); // la valeur n'existe pas return new extra_data_result( array(array($obj_id, null, null)), null); } // multiple objets concernés elseif ($object_ids_count > 1) { /*if ($object_ids_count > 1000) { throw new ExtraDataException( __("Liste d'objets supérieure à 1000 éléments non supportée")); }*/ // prépare une requête SQL d'interrogation multiple $sql = " SELECT object_id, extra_data_register_values, $value_name FROM extra_data_register_values WHERE definition_ref = $def_id AND object_id IN ("; foreach($object_ids as $obj_id) { $obj_id_escaped = $this->framework->db->escapeSimple($obj_id); $sql .= "'$obj_id_escaped',"; } $sql = preg_replace(',$', '', $sql); $sql .= ");"; $qres = $this->framework->get_all_results_from_db_query($sql, array('origin' => __METHOD__)); // si quelque chose s'est mal passé if ($qres['code'] != 'OK') { // on renvoie le message d'erreur return new extra_data_result(null, $this->framework->remove_html_tags($qres['message'])); } // sinon on récupère la liste des identifiants des données $tuples = array(); foreach($qres['result'] as $qr) { $tuples[] = array( $qr['object_id'], $qr['extra_data_register_values'], $qr[$value_name]); } return new extra_data_result($tuples, null); } // aucun objet concerné return new extra_data_result(array(), null); } // TODO implémenter la mise à jour d'une donnée (puisqu'on fourni l'id lors du stockage) public function update_data(array $data_ids, string $data_type, $data_value) { throw new RuntimeException("Not implemented yet"); } // TODO implémenter la suppression de données public function delete_data(array $data_ids, string $data_type) { throw new RuntimeException("Not implemented yet"); } /** * Liste des données associées à un objet. * * @param string $object_name Le nom de l'objet (ou sa classe) correspondant à sa table en BDD * @param string $mode Si 'value' retourne les valeurs, * si 'label' retourne les libellés, * si 'type' retourne les types. * @param string $object_id L'identifiant de l'objet concerné * @param bool $use_champ_fusion Si 'true' utilise l'alias de champ de fusion à la place du nom de la donnée * * @return extra_data_result Contenant la liste des nom des données ou bien clé/valeur pour chaque donnée * @return array ([(, , ), ...]|null, |null) * si l'opération a échouée le premier élément sera null, * et un message d'erreur indiquera la raison de l'échec, * sinon c'est l'inverse, le premier élément sera un tableau et il n'y * aura aucun message (null). */ public function list_data(string $object_name, string $mode, string $object_id = '', bool $use_champ_fusion = false) { // s'assure que le mode est valide if (! in_array($mode, array('value', 'label', 'type'))) { $err_msg = sprintf(__("Mode '%s' invalide"), $mode); $this->framework->log(__METHOD__, $err_msg, 'ERROR'); return new extra_data_result(null, $err_msg); } // si on doit inclure les valeurs il faut que l'identifiant de l'objet soit non-vide if ($mode == 'value' && empty($object_id)) { $err_msg = "Identifiant d'objet vide alors que le listing doit inclure les valeurs des données"; $this->framework->log(__METHOD__, $err_msg, 'ERROR'); return new extra_data_result(null, $err_msg); } $sql = sprintf(" SELECT ".($use_champ_fusion ? "CASE WHEN d.alias_champ_fusion IS NOT NULL THEN d.alias_champ_fusion ELSE d.name END AS name" : "d.name END AS name").", ".($mode == 'label' ? "d.description" : "d.type")." AS $mode FROM ".DB_PREFIXE."extra_data_register_def AS d WHERE d.object_name = '%s'", $this->framework->db->escapeSimple($object_name)); // si on doit inclure les valeurs if ($mode == 'value') { $sql = sprintf(" SELECT ".($use_champ_fusion ? "CASE WHEN d.alias_champ_fusion IS NOT NULL THEN d.alias_champ_fusion ELSE d.name END AS name" : "d.name END AS name").", CASE WHEN d.type = 'int' THEN v.value_int::text WHEN d.type = 'str' THEN v.value_str WHEN d.type = 'text' THEN v.value_text WHEN d.type = 'json' THEN v.value_json::text WHEN d.type = 'blob' THEN v.value_blob::text WHEN d.type = 'timestamp' THEN v.value_timestamp::text END AS $mode FROM ".DB_PREFIXE."extra_data_register_def AS d LEFT JOIN ".DB_PREFIXE."extra_data_register_values AS v ON v.definition_ref = d.extra_data_register_def WHERE d.object_name = '%s' AND v.object_id = '%s'", $this->framework->db->escapeSimple($object_name), $this->framework->db->escapeSimple($object_id)); } $qres = $this->framework->get_all_results_from_db_query($sql, array('origin' => __METHOD__)); // si quelque chose s'est mal passé if ($qres['code'] != 'OK') { // on renvoie le message d'erreur return new extra_data_result(null, $this->framework->remove_html_tags($qres['message'])); } // produit le resultat $result = array(); foreach($qres['result'] as $r) { $result[$r['name']] = $r[$mode]; } return new extra_data_result($result, null); } /** * Renvoie un objet représentant la définition de la donnée, stockée en BDD. * * @param string $object_name L'objet concerné par cette donnée * @param string $data_name Le nom de la donnée * * @return boolean 'true' si le champ est déjà défini, 'false' sinon */ protected function get_field_definition(string $object_name, string $data_name) { $this->log(__METHOD__, 'BEGIN'); $data_name_escaped = $this->framework->db->escapeSimple($data_name); $sqlWhere = "name = '".$data_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; } /** * Renvoie l'identifiant de l'enregistrement en BDD de la définition du champ dynamique. * * @param string $object_name Le type d'objet auquel est rattaché le champ dynamique * @param string $name Le nom du champ dynamique * * @return string */ protected function get_field_definition_id(string $object_name, string $name) { if (! ($field_def_inst = $this->get_field_definition($object_name, $name))) { throw new RuntimeException( "Échec de la récupération de la définition du champ ". "($object_name, $name)"); } return $field_def_inst->getVal($field_def_inst->clePrimaire); } }