Dépôt officiel du core SPIP Les plugins-dist faisant partie de la distribution SPIP sont présents dans https://git.spip.net/spip/[nom du plugin dist] https://www.spip.net
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

333 lines
11 KiB

14 years ago
  1. <?php
  2. /***************************************************************************\
  3. * SPIP, Système de publication pour l'internet *
  4. * *
  5. * Copyright © avec tendresse depuis 2001 *
  6. * Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James *
  7. * *
  8. * Ce programme est un logiciel libre distribué sous licence GNU/GPL. *
  9. * Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne. *
  10. \***************************************************************************/
  11. /**
  12. * Fonctions d'aides pour les fonctions d'objets de modification de contenus
  13. *
  14. * @package SPIP\Core\Objets\Modifications
  15. **/
  16. if (!defined('_ECRIRE_INC_VERSION')) {
  17. return;
  18. }
  19. /**
  20. * Collecte des champs postés
  21. *
  22. * Fonction générique pour la collecte des posts
  23. * dans action/editer_xxx
  24. *
  25. * @param array $white_list
  26. * Les champs à récupérer
  27. * @param array $black_list
  28. * Les champs à ignorer
  29. * @param array|null $set
  30. * array : Tableau des champs postés
  31. * null : Les champs sont obtenus par des _request() sur les noms de la white liste
  32. * @param bool $tous
  33. * true : Recupère tous les champs de white_list meme ceux n'ayant pas ete postés
  34. * @return array
  35. * Tableau des champs et valeurs collectées
  36. */
  37. function collecter_requests($white_list, $black_list = [], $set = null, $tous = false) {
  38. $c = $set;
  39. if (!$c) {
  40. $c = [];
  41. foreach ($white_list as $champ) {
  42. // on ne collecte que les champs reellement envoyes par defaut.
  43. // le cas d'un envoi de valeur NULL peut du coup poser probleme.
  44. $val = _request($champ);
  45. if ($tous or $val !== null) {
  46. $c[$champ] = $val;
  47. }
  48. }
  49. // on ajoute toujours la lang en saisie possible
  50. // meme si pas prevu au depart pour l'objet concerne
  51. if ($l = _request('changer_lang')) {
  52. $c['lang'] = $l;
  53. }
  54. }
  55. foreach ($black_list as $champ) {
  56. unset($c[$champ]);
  57. }
  58. return $c;
  59. }
  60. /**
  61. * Modifie le contenu d'un objet
  62. *
  63. * Fonction generique pour l'API de modification de contenu, qui se
  64. * charge entre autres choses d'appeler les pipelines pre_edition
  65. * et post_edition
  66. *
  67. * Attention, pour éviter des hacks on interdit des champs
  68. * (statut, id_secteur, id_rubrique, id_parent),
  69. * mais la securite doit étre assurée en amont
  70. *
  71. * @api
  72. * @param string $objet
  73. * Type d'objet
  74. * @param int $id_objet
  75. * Identifiant de l'objet
  76. * @param array $options
  77. * array data : tableau des donnees sources utilisees pour la detection de conflit ($_POST sinon fourni ou si nul)
  78. * array nonvide : valeur par defaut des champs que l'on ne veut pas vide
  79. * string date_modif : champ a mettre a date('Y-m-d H:i:s') s'il y a modif
  80. * string invalideur : id de l'invalideur eventuel
  81. * array champs : non documente (utilise seulement par inc/rechercher ?)
  82. * string action : action realisee, passee aux pipelines pre/post edition (par defaut 'modifier')
  83. * bool indexation : deprecie
  84. * @param array|null $c
  85. * Couples champ/valeur à modifier
  86. * @param string $serveur
  87. * Nom du connecteur à la base de données
  88. * @return bool|string
  89. * - false : Aucune modification, aucun champ n'est à modifier
  90. * - chaîne vide : Vide si tout s'est bien passé
  91. * - chaîne : Texte d'un message d'erreur
  92. */
  93. function objet_modifier_champs($objet, $id_objet, $options, $c = null, $serveur = '') {
  94. if (!$id_objet = intval($id_objet)) {
  95. spip_log('Erreur $id_objet non defini', 'warn');
  96. return _T('erreur_technique_enregistrement_impossible');
  97. }
  98. include_spip('inc/filtres');
  99. $table_objet = table_objet($objet, $serveur);
  100. $spip_table_objet = table_objet_sql($objet, $serveur);
  101. $id_table_objet = id_table_objet($objet, $serveur);
  102. $trouver_table = charger_fonction('trouver_table', 'base');
  103. $desc = $trouver_table($spip_table_objet, $serveur);
  104. // Appels incomplets (sans $c)
  105. if (!is_array($c)) {
  106. spip_log('erreur appel objet_modifier_champs(' . $objet . '), manque $c');
  107. return _T('erreur_technique_enregistrement_impossible');
  108. }
  109. // Securite : certaines variables ne sont jamais acceptees ici
  110. // car elles ne relevent pas de autoriser(xxx, modifier) ;
  111. // il faut passer par instituer_XX()
  112. // TODO: faut-il passer ces variables interdites
  113. // dans un fichier de description separe ?
  114. unset($c['statut']);
  115. unset($c['id_parent']);
  116. unset($c['id_rubrique']);
  117. unset($c['id_secteur']);
  118. // Gerer les champs non vides
  119. if (isset($options['nonvide']) and is_array($options['nonvide'])) {
  120. foreach ($options['nonvide'] as $champ => $sinon) {
  121. if (isset($c[$champ]) and $c[$champ] === '') {
  122. $c[$champ] = $sinon;
  123. }
  124. }
  125. }
  126. // N'accepter que les champs qui existent
  127. // TODO: ici aussi on peut valider les contenus
  128. // en fonction du type
  129. $champs = array_intersect_key($c, $desc['field']);
  130. // Nettoyer les valeurs
  131. $champs = array_map('corriger_caracteres', $champs);
  132. // On récupère l'état avant toute modification
  133. $row = sql_fetsel('*', $spip_table_objet, $id_table_objet . '=' . $id_objet);
  134. // Envoyer aux plugins
  135. $champs = pipeline(
  136. 'pre_edition',
  137. [
  138. 'args' => [
  139. 'table' => $spip_table_objet, // compatibilite
  140. 'table_objet' => $table_objet,
  141. 'spip_table_objet' => $spip_table_objet,
  142. 'desc' => $desc,
  143. 'type' => $objet,
  144. 'id_objet' => $id_objet,
  145. 'data' => $options['data'] ?? null,
  146. 'champs' => $options['champs'] ?? [], // [doc] c'est quoi ?
  147. 'champs_anciens' => $row, // état du contenu avant modif
  148. 'serveur' => $serveur,
  149. 'action' => $options['action'] ?? 'modifier'
  150. ],
  151. 'data' => $champs
  152. ]
  153. );
  154. if (!$champs) {
  155. return false;
  156. }
  157. // marquer le fait que l'objet est travaille par toto a telle date
  158. if ($GLOBALS['meta']['articles_modif'] != 'non') {
  159. include_spip('inc/drapeau_edition');
  160. signale_edition($id_objet, $GLOBALS['visiteur_session'], $objet);
  161. }
  162. // Verifier si les mises a jour sont pertinentes, datees, en conflit etc
  163. include_spip('inc/editer');
  164. if (!isset($options['data']) or is_null($options['data'])) {
  165. $options['data'] = &$_POST;
  166. }
  167. $conflits = controler_md5($champs, $options['data'], $objet, $id_objet, $serveur);
  168. // cas hypothetique : normalement inc/editer verifie en amont le conflit edition
  169. // et gere l'interface
  170. // ici on ne renvoie donc qu'un messsage d'erreur, au cas ou on y arrive quand meme
  171. if ($conflits) {
  172. return _T('titre_conflit_edition');
  173. }
  174. if ($champs) {
  175. // cas particulier de la langue : passer par instituer_langue_objet
  176. if (isset($champs['lang'])) {
  177. if ($changer_lang = $champs['lang']) {
  178. $id_rubrique = 0;
  179. if (isset($desc['field']['id_rubrique'])) {
  180. $parent = ($objet == 'rubrique') ? 'id_parent' : 'id_rubrique';
  181. $id_rubrique = sql_getfetsel($parent, $spip_table_objet, "$id_table_objet=" . intval($id_objet));
  182. }
  183. $instituer_langue_objet = charger_fonction('instituer_langue_objet', 'action');
  184. $champs['lang'] = $instituer_langue_objet($objet, $id_objet, $id_rubrique, $changer_lang, $serveur);
  185. }
  186. // on laisse 'lang' dans $champs,
  187. // ca permet de passer dans le pipeline post_edition et de journaliser
  188. // et ca ne gene pas qu'on refasse un sql_updateq dessus apres l'avoir
  189. // deja pris en compte
  190. }
  191. // la modif peut avoir lieu
  192. // faut-il ajouter date_modif ?
  193. if (
  194. !empty($options['date_modif'])
  195. and !isset($champs[$options['date_modif']])
  196. ) {
  197. $champs[$options['date_modif']] = date('Y-m-d H:i:s');
  198. }
  199. // allez on commit la modif
  200. sql_updateq($spip_table_objet, $champs, "$id_table_objet=" . intval($id_objet), $serveur);
  201. // on verifie si elle est bien passee
  202. $moof = sql_fetsel(
  203. array_keys($champs),
  204. $spip_table_objet,
  205. "$id_table_objet=" . intval($id_objet),
  206. [],
  207. [],
  208. '',
  209. [],
  210. $serveur
  211. );
  212. // si difference entre les champs, reperer les champs mal enregistres
  213. if ($moof != $champs) {
  214. $liste = [];
  215. foreach ($moof as $k => $v) {
  216. if (
  217. $v !== $champs[$k]
  218. // ne pas alerter si le champ est numerique est que les valeurs sont equivalentes
  219. and (!is_numeric($v) or intval($v) !== intval($champs[$k]))
  220. // ne pas alerter si le champ est date, qu'on a envoye une valeur vide et qu'on recupere une date nulle
  221. and (strlen($champs[$k]) or !in_array($v, ['0000-00-00 00:00:00', '0000-00-00']))
  222. ) {
  223. $liste[] = $k;
  224. $conflits[$k]['post'] = $champs[$k];
  225. $conflits[$k]['save'] = $v;
  226. // cas specifique MySQL+emoji : si l'un est la
  227. // conversion utf8_noplanes de l'autre alors c'est OK
  228. if (defined('_MYSQL_NOPLANES') && _MYSQL_NOPLANES) {
  229. include_spip('inc/charsets');
  230. if ($v == utf8_noplanes($champs[$k])) {
  231. array_pop($liste);
  232. }
  233. }
  234. }
  235. }
  236. // si un champ n'a pas ete correctement enregistre, loger et retourner une erreur
  237. // c'est un cas exceptionnel
  238. if (count($liste)) {
  239. spip_log(
  240. "Erreur enregistrement en base $objet/$id_objet champs :" . var_export($conflits, true),
  241. 'modifier.' . _LOG_CRITIQUE
  242. );
  243. return _T(
  244. 'erreur_technique_enregistrement_champs',
  245. ['champs' => "<i>'" . implode("'</i>,<i>'", $liste) . "'</i>"]
  246. );
  247. }
  248. }
  249. // Invalider les caches
  250. if (isset($options['invalideur']) and $options['invalideur']) {
  251. include_spip('inc/invalideur');
  252. if (is_array($options['invalideur'])) {
  253. array_map('suivre_invalideur', $options['invalideur']);
  254. } else {
  255. suivre_invalideur($options['invalideur']);
  256. }
  257. }
  258. // Notifications, gestion des revisions...
  259. // en standard, appelle |nouvelle_revision ci-dessous
  260. pipeline(
  261. 'post_edition',
  262. [
  263. 'args' => [
  264. 'table' => $spip_table_objet,
  265. 'table_objet' => $table_objet,
  266. 'spip_table_objet' => $spip_table_objet,
  267. 'desc' => $desc,
  268. 'type' => $objet,
  269. 'id_objet' => $id_objet,
  270. 'champs' => $options['champs'] ?? [], // [doc] kesako ?
  271. 'champs_anciens' => $row, // état du contenu avant modif
  272. 'serveur' => $serveur,
  273. 'action' => $options['action'] ?? 'modifier'
  274. ],
  275. 'data' => $champs
  276. ]
  277. );
  278. }
  279. // journaliser l'affaire
  280. // message a affiner :-)
  281. include_spip('inc/filtres_mini');
  282. $qui = '';
  283. if (!empty($GLOBALS['visiteur_session']['id_auteur'])) {
  284. $qui .= ' #id_auteur:' . $GLOBALS['visiteur_session']['id_auteur'] . '#';
  285. }
  286. if (!empty($GLOBALS['visiteur_session']['nom'])) {
  287. $qui .= ' #nom:' . $GLOBALS['visiteur_session']['nom'] . '#';
  288. }
  289. if ($qui == '') {
  290. $qui = '#ip:' . $GLOBALS['ip'] . '#';
  291. }
  292. journal(_L($qui . ' a édité ' . $objet . ' ' . $id_objet . ' (' . join(
  293. '+',
  294. array_diff(array_keys($champs), ['date_modif'])
  295. ) . ')'), [
  296. 'faire' => 'modifier',
  297. 'quoi' => $objet,
  298. 'id' => $id_objet
  299. ]);
  300. return '';
  301. }