b440f7b4eec948f129f5e1df54c0756be9b102ef
[lhc/web/www.git] / www / plugins / crayons / action / crayons_store.php
1 <?php
2 /**
3 * Crayons
4 * plugin for spip
5 * (c) Fil, toggg 2006-2013
6 * licence GPL
7 */
8
9 if (!defined('_ECRIRE_INC_VERSION')) {
10 return;
11 }
12
13 function verif_secu($w, $secu) {
14 return (
15 $secu == md5($GLOBALS['meta']['alea_ephemere'].'='.$w)
16 or
17 $secu == md5($GLOBALS['meta']['alea_ephemere_ancien'].'='.$w)
18 );
19 }
20
21 function post_crayons() {
22 $results = array();
23
24 if (isset($_POST['crayons']) and is_array($_POST['crayons'])) {
25 foreach ($_POST['crayons'] as $crayon) {
26 $name = $_POST['name_'.$crayon];
27 $content = array();
28 if ($_POST['fields_'.$crayon]) {
29 foreach (explode(',', $_POST['fields_'.$crayon]) as $field) {
30 // cas particulier d'un envoi de fichier
31 if (isset($_FILES['content_'.$crayon.'_'.$field])) {
32 if ($_FILES['content_'.$crayon.'_'.$field]['size'] > 0) {
33 $content[$field] = $_FILES['content_'.$crayon.'_'.$field];
34 } else {
35 $content[$field] = false;
36 }
37 // cf. valeur passee dans crayon->md5() : false ou filemtime() du logo
38 } else {
39 /**
40 * le changement de charset n'est plus necessaire
41 * depuis jquery 1.5 (feature non documentee de jquery!)
42 */
43 if (isset($_POST['content_'.$crayon.'_'.$field])) {
44 $content[$field] = is_array($_POST['content_'.$crayon.'_'.$field])
45 ?implode(',', $_POST['content_'.$crayon.'_'.$field])
46 :$_POST['content_'.$crayon.'_'.$field];
47 } else {
48 $content[$field] = null;
49 }
50 }
51 }
52 }
53
54 // Si les donnees POSTees ne correspondent pas a leur md5,
55 // il faut les traiter
56 if (isset($name)
57 and md5(serialize($content)) != $_POST['md5_'.$crayon]) {
58 if (!isset($_POST['secu_'.$crayon])
59 or verif_secu($name, $_POST['secu_' . $crayon])) {
60 $results[] = array($name, $content, $_POST['md5_'.$crayon], $crayon);
61 } else {
62 return false; // erreur secu
63 }
64 } else {
65 // cas inchange
66 $results[] = array($name, $content, false, $crayon);
67 }
68 }
69 }
70 return $results;
71 }
72
73
74 function crayons_store($options = array()) {
75 // permettre de surcharger les fonctions de recuperation des valeurs
76 // et de sauvegardes de celles-ci
77 $options = array_merge(array(
78 'f_get_valeur' => 'crayons_store_get_valeur',
79 'f_set_modifs' => 'crayons_store_set_modifs',
80 ), $options);
81
82 include_spip('inc/crayons');
83 $wdgcfg = wdgcfg();
84
85 $return = array('$erreur'=>'');
86
87 $postees = post_crayons();
88
89 $modifs = $updates = array();
90 if (!is_array($postees)) {
91 $return['$erreur'] = _U('crayons:donnees_mal_formatees');
92 } else {
93 foreach ($postees as $postee) {
94 if ($postee[2] !== false) {
95 $name = $postee[0];
96 $content = $postee[1];
97
98 if ($content && preg_match(_PREG_CRAYON, 'crayon '.$name, $regs)) {
99 list(,$crayon,$type,$modele,$id) = $regs;
100 $wid = $postee[3];
101
102 spip_log("autoriser('crayonner', $type, $id, null, array('modele' => $modele)", 'crayons_distant');
103 if (!autoriser('crayonner', $type, $id, null, array('modele' => $modele))) {
104 $return['$erreur'] =
105 "$type $id: " . _U('crayons:non_autorise');
106 } else {
107 // recuperer l'existant pour calculer son md5 et verifier
108 // qu'il n'a pas ete modifie entre-temps
109 $get_valeur = $options['f_get_valeur'];
110 $data = $get_valeur($content, $regs);
111
112 $md5 = md5(serialize($data));
113
114 // est-ce que le champ a ete modifie dans la base entre-temps ?
115 if ($md5 != $postee[2]) {
116 // si oui, la modif demandee correspond peut-etre
117 // a la nouvelle valeur ? dans ce cas on procede
118 // comme si "pas de modification", sinon erreur
119 if ($md5 != md5(serialize($content))) {
120 $return['$erreur'] = "$type $id $modele: " .
121 _U('crayons:modifie_par_ailleurs');
122 }
123 }
124
125 $modifs[] = array($type, $modele, $id, $content, $wid);
126
127 /* aiguillage pour verification de la saisie
128 Pour traitement ulterieur les fonctions de verifications doivent renvoyer $invalides :
129 $invalides[wid_champ]['msg'] -> message de saisie invalide
130 $invalides[wid_champ]['retour'] -> caracteres invalides */
131 $f = 'verifier_'.$type.'_'.$modele;
132 if (function_exists($f)) {
133 if (count($invalides = $f($modifs))) {
134 $return['$invalides'] = $invalides;
135 }
136 }
137 }
138 }
139 }
140 }
141 }
142
143 if (!$modifs and !$return['$erreur']) {
144 $return['$erreur'] = $wdgcfg['msgNoChange'] ? _U('crayons:pas_de_modification') : ' ';
145 $return['$annuler'] = true;
146 }
147
148 // un champ invalide ... ou rien ==> on ne fait rien !
149 if (isset($return['$invalides']) and $return['$invalides']) {
150 return $return;
151 }
152
153 // une quelconque erreur ... ou rien ==> on ne fait rien !
154 if (isset($return['$erreur']) and $return['$erreur']) {
155 return $return;
156 }
157
158 // on traite toutes les modifications
159 // en appelant la fonction adequate de traitement
160 $set_modifs = $options['f_set_modifs'];
161 $return = $set_modifs($modifs, $return);
162
163 // une quelconque erreur ... ou rien ==> on ne fait rien !
164 if ($return['$erreur']) {
165 return $return;
166 }
167
168 // et maintenant refaire l'affichage des crayons modifies
169 include_spip('inc/texte');
170 foreach ($modifs as $m) {
171 list($type, $modele, $id, $content, $wid) = $m;
172 $f = charger_fonction($type.'_'.$modele, 'vues', true)
173 or $f = charger_fonction($modele, 'vues', true)
174 or $f = charger_fonction($type, 'vues', true)
175 or $f = 'vues_dist';
176 $return[$wid] = $f($type, $modele, $id, $content, $wid);
177 }
178 return $return;
179 }
180
181 // recuperer une valeur en fonction des parametres recuperes
182 // cette fonction cherche une valeur d'une colonne d'une table SQL
183 function crayons_store_get_valeur($content, $regs) {
184 list(,$crayon,$type,$modele,$id) = $regs;
185 return valeur_colonne_table($type, array_keys($content), $id);
186 }
187
188 // stocke les valeurs envoyees dans des colonnes de table SQL
189 function crayons_store_set_modifs($modifs, $return) {
190 // sinon on bosse : toutes les modifs ont ete acceptees
191 // verifier qu'on a tout ce qu'il faut pour mettre a jour la base
192 // et regrouper les mises a jour par type/id
193 foreach ($modifs as $modif) {
194 list($type, $modele, $id, $content, $wid) = $modif;
195
196 $fun = '';
197 // si le crayon est un MODELE avec une fonction xxx_revision associee
198 // cas ou une fonction xxx_revision existe
199 if (function_exists($f = $type.'_'. $modele . '_revision')
200 or function_exists($f = $modele . '_revision')
201 or function_exists($f = $type . '_revision')) {
202 $fun = $f;
203 } elseif (function_exists('lister_tables_objets_sql')
204 and $tables_objet = lister_tables_objets_sql()
205 and isset($tables_objet[table_objet_sql($type)])) {
206 // si on est en SPIP 3+ et qu'on edite un objet editorial bien declare
207 // passer par l'API objet_modifier
208 $fun = 'crayons_objet_modifier';
209 } else {
210 // sinon spip < 3 (ou pas un objet edito)
211 // on teste les objets connus et on route sur les fonctions correspondantes
212 switch ($type) {
213 case 'article':
214 $fun = 'crayons_update_article';
215 break;
216 case 'breve':
217 include_spip('action/editer_breve');
218 $fun = 'revisions_breves';
219 break;
220 case 'forum':
221 include_spip('inc/forum');
222 $fun = 'enregistre_et_modifie_forum';
223 break;
224 case 'rubrique':
225 include_spip('action/editer_rubrique');
226 $fun = 'revisions_rubriques';
227 break;
228 case 'syndic':
229 case 'site':
230 include_spip('action/editer_site');
231 $fun = 'revisions_sites';
232 break;
233 case 'document':
234 include_spip('plugins/installer');
235 include_spip('inc/plugin');
236 if (spip_version_compare($GLOBALS['spip_version_branche'], '3.0.0alpha', '>=')) {
237 include_spip('action/editer_document');
238 $fun = 'document_modifier';
239 } else {
240 include_spip('inc/modifier');
241 $fun = 'revision_document';
242 }
243 break;
244 // cas geres de la maniere la plus standard
245 case 'auteur':
246 case 'mot':
247 case 'signature':
248 case 'petition':
249 default:
250 include_spip('inc/modifier');
251 $fun = 'revision_'.$type;
252 break;
253 }
254 }
255 // si on a pas reussi on passe par crayons_update() qui fera un update sql brutal
256 if (!$fun or !function_exists($fun)) {
257 $fun = 'crayons_update';
258 // $return['$erreur'] = "$type: " . _U('crayons:non_implemente');
259 // break;
260 }
261
262 if (!isset($updates[$type][$fun])) {
263 $updates[$type][$fun] = array();
264 }
265 if (!isset($updates[$type][$fun][$id])) {
266 $updates[$type][$fun][$id] = array('wdg'=>array(), 'chval'=>array());
267 }
268 // pour reaffecter le retour d'erreur sql au cas ou
269 $updates[$type][$fun][$id]['wdg'][] = $wid;
270 foreach ($content as $champtable => $val) {
271 $updates[$type][$fun][$id]['chval'][$champtable] = $val;
272 }
273 }
274
275 // il manque une fonction de mise a jour ==> on ne fait rien !
276 if ($return['$erreur']) {
277 return $return;
278 }
279
280 // hop ! mises a jour table par table et id par id
281 foreach ($updates as $type => $idschamps) {
282 foreach ($idschamps as $fun => $ids) {
283 foreach ($ids as $id => $champsvaleurs) {
284 /* cas particulier du logo dans un crayon complexe :
285 ce n'est pas un champ de la table */
286 if (isset($champsvaleurs['chval']['logo'])) {
287 spip_log('revision logo', 'crayons');
288 logo_revision($id, $champsvaleurs['chval'], $type, $champsvaleurs['wdg']);
289 unset($champsvaleurs['chval']['logo']);
290 }
291 if (count($champsvaleurs['chval'])) {
292 // -- revisions_articles($id_article, $c) --
293 spip_log("$fun($id ...)", 'crayons');
294 $updok = $fun($id, $champsvaleurs['chval'], $type, $champsvaleurs['wdg']);
295 // Renvoyer erreur si update base distante echoue,
296 // on ne regarde pas les updates base local car ils ne renvoient rien
297 list($distant,$table) = distant_table($type);
298 if ($distant and !$updok) {
299 $return['$erreur'] = "$type: " . _U('crayons:update_impossible');
300 }
301 }
302 }
303 }
304 }
305 return $return;
306 }
307
308 //
309 // VUE
310 //
311 function vues_dist($type, $modele, $id, $content, $wid) {
312 // pour ce qui a une {lang_select} par defaut dans la boucle,
313 // la regler histoire d'avoir la bonne typo dans le propre()
314 // NB: ceci n'a d'impact que sur le "par defaut" en bas
315 list($distant,$table) = distant_table($type);
316 if (colonne_table($type, 'lang')) {
317 $b = valeur_colonne_table($type, 'lang', $id);
318 lang_select($a = array_pop($b));
319 } else {
320 lang_select($a = $GLOBALS['meta']['langue_site']);
321 }
322
323 // chercher vues/article_toto.html
324 // sinon vues/toto.html
325 if (find_in_path(($fond = 'vues/' . $type . '_' . $modele) . '.html')
326 or find_in_path(($fond = 'vues/' . $modele) .'.html')
327 or find_in_path(($fond = 'vues/' . $type) .'.html')) {
328 $primary = (function_exists('id_table_objet')?id_table_objet($table):'id_' . $table);
329 $contexte = array(
330 $primary => $id,
331 'crayon_type' => $type,
332 'crayon_modele' => $modele,
333 'champ' => $modele,
334 'class' => _request('class_'.$wid),
335 'self' => _request('self'),
336 'lang' => $GLOBALS['spip_lang']
337 );
338 $contexte = array_merge($contexte, $content);
339 include_spip('public/assembler');
340 return recuperer_fond($fond, $contexte);
341 } else {
342 // vue par defaut
343 // Par precaution on va rechercher la valeur
344 // dans la base de donnees (meme si a priori la valeur est
345 // ce qu'on vient d'envoyer, il y a nettoyage des caracteres et
346 // eventuellement d'autres filtres de saisie...)
347 $bdd = valeur_colonne_table($type, $modele, $id);
348 if (count($bdd)) {
349 $valeur = array_pop($bdd);
350 } else {
351 // les champs n'ont pas ete retrouves dans la base
352 // ce qui signifie a priori que nous sommes en face d'une cle primaire compose
353 // et qu'un crayon a modifie un element de cette cle (c'est pas malin !)
354 // dans ce cas, on reaffiche a minima ce qu'on vient de publier
355 // mais il sera impossible de le reediter dans la foulee avec le meme crayon
356 // (car l'identifiant du crayon se base sur l'id).
357 // Il faudra donc recharger la page pour pouvoir reediter.
358 if (is_scalar($id)) {
359 $valeur = $content[$modele];
360 }
361 }
362
363 if ($valeur) {
364 // seul spip core sait rendre les donnees
365 if (function_exists('appliquer_traitement_champ')) {
366 $valeur = appliquer_traitement_champ($valeur, $modele, table_objet($table));
367 } else {
368 if (in_array($modele, array('chapo', 'texte', 'descriptif', 'ps', 'bio'))) {
369 $valeur = propre($valeur);
370 } else {
371 $valeur = typo($valeur);
372 }
373 }
374 }
375 return $valeur;
376 }
377 }
378
379
380 /**
381 * Fonction de mise a jour par API editer_objet
382 * @param $id
383 * @param $data
384 * @param $type
385 * @param $ref
386 * @return bool|mixed|string
387 */
388 function crayons_objet_modifier($id, $data, $type, $ref) {
389 if (include_spip('action/editer_objet')
390 and function_exists('objet_modifier')) {
391 return objet_modifier(objet_type($type), $id, $data);
392 }
393 // fallback
394 return crayons_update($id, $data, $type);
395 }
396
397 //
398 // Fonctions de mise a jour generique
399 //
400 function crayons_update($id, $colval = array(), $type = '') {
401 if (!$colval or !count($colval)) {
402 return false;
403 }
404 list($distant,$table) = distant_table($type);
405
406 if ($distant) {
407 list($nom_table, $where) = table_where($type, $id);
408 if (!$nom_table) {
409 return false;
410 }
411
412 $update = $sep = '';
413 foreach ($colval as $col => $val) {
414 $update .= $sep . '`' . $col . '`=' . _q($val);
415 $sep = ', ';
416 }
417
418 $a = spip_query($q = 'UPDATE `' . $nom_table . '` SET ' . $update . ' WHERE ' . $where, $distant);
419
420 #spip_log($q);
421 include_spip('inc/invalideur');
422 suivre_invalideur($cond, $modif = true);
423 } else {
424 // cle primaire composee : 3-4-rubrique
425 // calculer un where approprie
426 // et modifier sans passer par la fonction destinee aux tables principales
427 // on limite a SPIP 2 mini car sql_updateq n'est pas mappe dans les crayons_compat
428 if (is_scalar($id) and ($GLOBALS['spip_version_code'] >= '1.93')) {
429 list($nom_table, $where) = table_where($type, $id, true); // where sous forme de tableau
430 $a = sql_updateq($nom_table, $colval, $where);
431 } else {
432 // modification d'une table principale
433 include_spip('inc/modifier');
434 $a = modifier_contenu($type, $id, array(), $colval);
435 }
436 }
437 return $a;
438 }
439
440 //
441 // Fonctions de mise a jour
442 //
443 function crayons_update_article($id_article, $c = false) {
444 include_spip('action/editer_article');
445
446 // Enregistrer les nouveaux contenus
447 revisions_articles($id_article, $c);
448
449 // En cas de statut ou de id_rubrique
450 // NB: instituer_article veut id_parent, et pas id_rubrique !
451 if (isset($c['id_rubrique'])) {
452 $c['id_parent'] = $c['id_rubrique'];
453 unset($c['id_rubrique']);
454 }
455 instituer_article($id_article, $c);
456 }
457
458 /**
459 * Enregistre les modifications sur une configuration
460 * suite à un crayon sur une meta
461 *
462 * La colonne est toujours 'valeur' pour ces données.
463 * La donnée à enregistrer peut-être une sous partie de configuration.
464 * Si c'est le cas, on gère l'enregistrement via ecrire_config.
465 *
466 * @param string $a
467 * Nom ou clé de la meta (descriptif_site ou demo__truc pour demo/truc)
468 * @param bool|array $c
469 * Liste des champs modifiés
470 * Ici, 'valeur' normalement.
471 * @return void
472 **/
473 function revision_meta($a, $c = false) {
474 if (isset($c['valeur'])) {
475 // Certaines cles de configuration sont echapées ici (cf #EDIT_CONFIG{demo/truc})
476 $a = str_replace('__', '/', $a);
477 spip_log("meta '$a' = '$c[valeur]'", 'crayons');
478 // eviter de planter les vieux SPIP
479 if (false === strpos($a, '/')) {
480 ecrire_meta($a, $c['valeur']);
481 // SPIP 3 ou Bonux 2 ou CFG
482 } else {
483 include_spip('inc/config');
484 ecrire_config($a, $c['valeur']);
485 }
486 include_spip('inc/invalideur');
487 suivre_invalideur('meta');
488 }
489 }
490
491
492 // TODO:
493 // Ce modele est cense enregistrer les tags sous forme de ??
494 // une ligne dans un champ spip_articles.tags, et/ou des mots-cles...
495 function modeles_tags($id, $c) {
496 var_dump($id); #id_article
497 var_dump($c); # perturbant : ici on a array('id_article'=>'valeur envoyee')
498 }
499
500 function action_crayons_store_dist() {
501 return action_crayons_store_args();
502 }
503
504 // permettre de passer une autre fonction de stockage des informations
505 function action_crayons_store_args($store = 'crayons_store') {
506 header('Content-Type: text/plain; charset='.$GLOBALS['meta']['charset']);
507 lang_select($GLOBALS['auteur_session']['lang']);
508
509 $r = $store();
510
511 // Si on a ete appeles par jQuery, on renvoie tout, c'est le client
512 // crayons.js qui va traiter l'affichage du resultat et status
513 # Attention le test $_SERVER["HTTP_X_REQUESTED_WITH"] === "XMLHttpRequest"
514 # n'est pas bon car le cas d'un fichier uploade via iframe n'est pas detecte
515
516 // S'il y a une adresse de redirection, on renvoie vers elle
517 // En cas d'erreur il faudrait ajouter &err=... dans l'url ?
518 if (_request('redirect')) {
519 if (!$r['$erreur']
520 or $r['$annuler']) {
521 include_spip('inc/headers');
522 redirige_par_entete(_request('redirect'));
523 } else {
524 echo "<h4 class='status'>".$r['$erreur']."</h4>\n";
525
526 foreach ($r as $wid => $v) {
527 if ($wid !== '$erreur') {
528 echo "<div id='$wid'>$v</div><hr />\n";
529 }
530 }
531 echo "<a href='".quote_amp(_request('redirect'))."'>"
532 .quote_amp(_request('redirect'))
533 ."</a>\n";
534 }
535 } else {
536 // Cas normal : JSON
537 echo crayons_json_export($r);
538 }
539
540 exit;
541 }