[SPIP] v3.2.1-->v3.2.2
[lhc/web/www.git] / www / ecrire / inc / editer.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2019 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
12
13 /**
14 * Fonctions d'aide à l'édition d'objets éditoriaux.
15 *
16 * @package SPIP\Core\Edition
17 **/
18
19 if (!defined('_ECRIRE_INC_VERSION')) {
20 return;
21 }
22 include_spip('base/abstract_sql');
23
24 /**
25 * Effectue les traitements d'un formulaire d'édition d'objet éditorial
26 *
27 * Exécute une action d'édition spécifique au type d'objet s'il elle existe
28 * (fonction action_editer_$type), sinon exécute l'action générique
29 * d'édition d'objet (action_editer_objet_dist())
30 *
31 * Si une traduction était demandée, crée le lien avec l'objet qui est
32 * traduit.
33 *
34 * @api
35 * @see action_editer_objet_dist()
36 *
37 * @param string $type
38 * Type d'objet
39 * @param int|string $id
40 * Identifiant de l'objet à éditer, 'new' pour un nouvel objet
41 * @param int $id_parent
42 * Identifiant de l'objet parent
43 * @param int $lier_trad
44 * Identifiant de l'objet servant de source à une nouvelle traduction
45 * @param string $retour
46 * URL de redirection après les traitements
47 * @param string $config_fonc
48 * Nom de fonction appelée au chargement permettant d'ajouter des
49 * valeurs de configurations dans l'environnement du formulaire
50 * @param array $row
51 * Ligne SQL de l'objet édité, si connu.
52 * En absence, les données sont chargées depuis l'objet en base s'il existe
53 * ou depuis l'objet source d'une traduction si c'est un nouvel objet
54 * (et une traduction).
55 * @param string $hidden
56 * Contenu HTML ajouté en même temps que les champs cachés (input hidden)
57 * du formulaire.
58 * @return array
59 * Retour des traitements.
60 **/
61 function formulaires_editer_objet_traiter(
62 $type,
63 $id = 'new',
64 $id_parent = 0,
65 $lier_trad = 0,
66 $retour = '',
67 $config_fonc = 'articles_edit_config',
68 $row = array(),
69 $hidden = ''
70 ) {
71
72 $res = array();
73 // eviter la redirection forcee par l'action...
74 set_request('redirect');
75 if ($action_editer = charger_fonction("editer_$type", 'action', true)) {
76 list($id, $err) = $action_editer($id);
77 } else {
78 $action_editer = charger_fonction('editer_objet', 'action');
79 list($id, $err) = $action_editer($id, $type);
80 }
81 $id_table_objet = id_table_objet($type);
82 $res[$id_table_objet] = $id;
83 if ($err or !$id) {
84 $res['message_erreur'] = ($err ? $err : _T('erreur'));
85 } else {
86 // Un lien de trad a prendre en compte
87 if ($lier_trad) {
88 // referencer la traduction
89 $referencer_traduction = charger_fonction('referencer_traduction', 'action');
90 $referencer_traduction($type, $id, $lier_trad);
91 // actions de recopie de champs / liens sur le nouvel objet créé
92 $completer_traduction = charger_fonction('completer_traduction', 'inc');
93 $err = $completer_traduction($type, $id, $lier_trad);
94 if ($err) {
95 $res['message_erreur'] = $err;
96 return $res;
97 }
98 }
99
100 $res['message_ok'] = _T('info_modification_enregistree');
101 if ($retour) {
102 if (strncmp($retour, 'javascript:', 11) == 0) {
103 $res['message_ok'] .= '<script type="text/javascript">/*<![CDATA[*/' . substr($retour, 11) . '/*]]>*/</script>';
104 $res['editable'] = true;
105 } else {
106 $res['redirect'] = parametre_url($retour, $id_table_objet, $id);
107 }
108 }
109 }
110
111 return $res;
112 }
113
114 /**
115 * Teste les erreurs de validation d'un formulaire d'édition d'objet éditorial
116 *
117 * La fonction teste que :
118 * - il n'y a pas de conflit d'édition sur un ou plusieurs champs (c'est à
119 * dire que personne d'autre n'a modifié le champ entre le moment où on
120 * a saisi et le moment où on a validé le formulaire
121 * - tous les champs obligatoires (listés dans $oblis) sont remplis.
122 *
123 * @api
124 *
125 * @param string $type
126 * Type d'objet
127 * @param int|string $id
128 * Identifiant de l'objet à éditer, 'new' pour un nouvel objet
129 * @param array $oblis
130 * Liste de champs obligatoires : ils doivent avoir un contenu posté.
131 * @return array
132 * Tableau des erreurs
133 **/
134 function formulaires_editer_objet_verifier($type, $id = 'new', $oblis = array()) {
135 $erreurs = array();
136 if (intval($id)) {
137 $conflits = controler_contenu($type, $id);
138 if ($conflits and count($conflits)) {
139 foreach ($conflits as $champ => $conflit) {
140 if (!isset($erreurs[$champ])) {
141 $erreurs[$champ] = '';
142 }
143 $erreurs[$champ] .= _T('alerte_modif_info_concourante') . "<br /><textarea readonly='readonly' class='forml'>" . $conflit['base'] . '</textarea>';
144 }
145 }
146 }
147 foreach ($oblis as $obli) {
148 $value = _request($obli);
149 if (is_null($value) or !(is_array($value) ? count($value) : strlen($value))) {
150 if (!isset($erreurs[$obli])) {
151 $erreurs[$obli] = '';
152 }
153 $erreurs[$obli] .= _T('info_obligatoire');
154 }
155 }
156
157 return $erreurs;
158 }
159
160 /**
161 * Construit les valeurs de chargement d'un formulaire d'édition d'objet éditorial
162 *
163 * La fonction calcule les valeurs qui seront transmises à l'environnement
164 * du formulaire pour son affichage. Ces valeurs sont les champs de l'objet
165 * éditorial d'une part, mais aussi d'autres calculant la clé d'action,
166 * les pipelines devant faire transiter le contenu HTML du formulaire,
167 * ainsi que différents champs cachés utilisés ensuite dans les traitements.
168 *
169 * Lorsqu'une création d'objet est demandée, ou lorsqu'on demande une traduction
170 * d'un autre, la fonction tente de précharger le contenu de l'objet en
171 * utilisant une fonction inc_precharger_{type}_dist permettant par exemple
172 * de remplir le contenu avec du texte, notamment avec la traduction source.
173 *
174 * @api
175 *
176 * @param string $type
177 * Type d'objet
178 * @param int|string $id
179 * Identifiant de l'objet à éditer, 'new' pour un nouvel objet
180 * @param int $id_parent
181 * Identifiant de l'objet parent
182 * @param int $lier_trad
183 * Identifiant de l'objet servant de source à une nouvelle traduction
184 * @param string $retour
185 * URL de redirection après les traitements
186 * @param string $config_fonc
187 * Nom de fonction appelée au chargement permettant d'ajouter des
188 * valeurs de configurations dans l'environnement du formulaire
189 * @param array $row
190 * Ligne SQL de l'objet édité, si connu.
191 * En absence, les données sont chargées depuis l'objet en base s'il existe
192 * ou depuis l'objet source d'une traduction si c'est un nouvel objet
193 * (et une traduction).
194 * @param string $hidden
195 * Contenu HTML ajouté en même temps que les champs cachés (input hidden)
196 * du formulaire.
197 * @return array
198 * Environnement du formulaire.
199 **/
200 function formulaires_editer_objet_charger(
201 $type,
202 $id = 'new',
203 $id_parent = 0,
204 $lier_trad = 0,
205 $retour = '',
206 $config_fonc = 'articles_edit_config',
207 $row = array(),
208 $hidden = ''
209 ) {
210 $table_objet = table_objet($type);
211 $table_objet_sql = table_objet_sql($type);
212 $id_table_objet = id_table_objet($type);
213 $new = !is_numeric($id);
214 // Appel direct dans un squelette
215 if (!$row) {
216 if (!$new or $lier_trad) {
217 if ($select = charger_fonction('precharger_' . $type, 'inc', true)) {
218 $row = $select($id, $id_parent, $lier_trad);
219 } else {
220 $row = sql_fetsel('*', $table_objet_sql, $id_table_objet . '=' . intval($id));
221 }
222 if (!$new) {
223 $md5 = controles_md5($row);
224 }
225 }
226 if (!$row) {
227 $row = array();
228 $trouver_table = charger_fonction('trouver_table', 'base');
229 if ($desc = $trouver_table($table_objet)) {
230 foreach ($desc['field'] as $k => $v) {
231 $row[$k] = '';
232 }
233 }
234 }
235 }
236
237 // Gaffe: sans ceci, on ecrase systematiquement l'article d'origine
238 // (et donc: pas de lien de traduction)
239 $id = ($new or $lier_trad)
240 ? 'oui'
241 : $row[$id_table_objet];
242 $row[$id_table_objet] = $id;
243
244 $contexte = $row;
245 if (strlen($id_parent) && is_numeric($id_parent) && (!isset($contexte['id_parent']) or $new)) {
246 if (!isset($contexte['id_parent'])) {
247 unset($contexte['id_rubrique']);
248 }
249 $contexte['id_parent'] = $id_parent;
250 } elseif (!isset($contexte['id_parent'])) {
251 // id_rubrique dans id_parent si possible
252 if (isset($contexte['id_rubrique'])) {
253 $contexte['id_parent'] = $contexte['id_rubrique'];
254 unset($contexte['id_rubrique']);
255 } else {
256 $contexte['id_parent'] = '';
257 }
258 if (!$contexte['id_parent']
259 and $preselectionner_parent_nouvel_objet = charger_fonction('preselectionner_parent_nouvel_objet', 'inc', true)
260 ) {
261 $contexte['id_parent'] = $preselectionner_parent_nouvel_objet($type, $row);
262 }
263 }
264
265 $config = array();
266 if ($config_fonc) {
267 $contexte['config'] = $config = $config_fonc($contexte);
268 }
269 $config = $config + array(
270 'lignes' => 0,
271 'langue' => '',
272 );
273
274 $att_text = " class='textarea' "
275 . " rows='"
276 . ($config['lignes'] + 15)
277 . "' cols='40'";
278 if (isset($contexte['texte'])) {
279 list($contexte['texte'], $contexte['_texte_trop_long']) = editer_texte_recolle($contexte['texte'], $att_text);
280 }
281
282 // on veut conserver la langue de l'interface ;
283 // on passe cette donnee sous un autre nom, au cas ou le squelette
284 // voudrait l'exploiter
285 if (isset($contexte['lang'])) {
286 $contexte['langue'] = $contexte['lang'];
287 unset($contexte['lang']);
288 }
289
290 $contexte['_hidden'] = "<input type='hidden' name='editer_$type' value='oui' />\n" .
291 (!$lier_trad ? '' :
292 ("\n<input type='hidden' name='lier_trad' value='" .
293 $lier_trad .
294 "' />" .
295 "\n<input type='hidden' name='changer_lang' value='" .
296 $config['langue'] .
297 "' />"))
298 . $hidden
299 . (isset($md5) ? $md5 : '');
300
301 // preciser que le formulaire doit passer dans un pipeline
302 $contexte['_pipeline'] = array('editer_contenu_objet', array('type' => $type, 'id' => $id));
303
304 // preciser que le formulaire doit etre securise auteur/action
305 // n'est plus utile lorsque l'action accepte l'id en argument direct
306 // on le garde pour compat
307 $contexte['_action'] = array("editer_$type", $id);
308
309 return $contexte;
310 }
311
312 /**
313 * Gestion des textes trop longs (limitation brouteurs)
314 * utile pour les textes > 32ko
315 *
316 * @param string $texte
317 * @return array
318 */
319 function coupe_trop_long($texte) {
320 $aider = charger_fonction('aider', 'inc');
321 if (strlen($texte) > 28 * 1024) {
322 $texte = str_replace("\r\n", "\n", $texte);
323 $pos = strpos($texte, "\n\n", 28 * 1024); // coupe para > 28 ko
324 if ($pos > 0 and $pos < 32 * 1024) {
325 $debut = substr($texte, 0, $pos) . "\n\n<!--SPIP-->\n";
326 $suite = substr($texte, $pos + 2);
327 } else {
328 $pos = strpos($texte, ' ', 28 * 1024); // sinon coupe espace
329 if (!($pos > 0 and $pos < 32 * 1024)) {
330 $pos = 28 * 1024; // au pire (pas d'espace trouv'e)
331 $decalage = 0; // si y'a pas d'espace, il ne faut pas perdre le caract`ere
332 } else {
333 $decalage = 1;
334 }
335 $debut = substr($texte, 0, $pos + $decalage); // Il faut conserver l'espace s'il y en a un
336 $suite = substr($texte, $pos + $decalage);
337 }
338
339 return (array($debut, $suite));
340 } else {
341 return (array($texte, ''));
342 }
343 }
344
345 /**
346 * Formater un `$texte` dans `textarea`
347 *
348 * @param string $texte
349 * @param string $att_text
350 * @return array
351 */
352 function editer_texte_recolle($texte, $att_text) {
353 if ((strlen($texte) < 29 * 1024)
354 or (include_spip('inc/layer') and ($GLOBALS['browser_name'] != 'MSIE'))
355 ) {
356 return array($texte, '');
357 }
358
359 include_spip('inc/barre');
360 $textes_supplement = "<br /><span style='color: red'>" . _T('info_texte_long') . "</span>\n";
361 $nombre = 0;
362
363 while (strlen($texte) > 29 * 1024) {
364 $nombre++;
365 list($texte1, $texte) = coupe_trop_long($texte);
366 $textes_supplement .= '<br />' .
367 "<textarea id='texte$nombre' name='texte_plus[$nombre]'$att_text>$texte1</textarea>\n";
368 }
369
370 return array($texte, $textes_supplement);
371 }
372
373 /**
374 * auto-renseigner le titre si il n'existe pas
375 *
376 * @param $champ_titre
377 * @param $champs_contenu
378 * @param int $longueur
379 */
380 function titre_automatique($champ_titre, $champs_contenu, $longueur = null) {
381 if (!_request($champ_titre)) {
382 $titrer_contenu = charger_fonction('titrer_contenu', 'inc');
383 if (!is_null($longueur)) {
384 $t = $titrer_contenu($champs_contenu, null, $longueur);
385 } else {
386 $t = $titrer_contenu($champs_contenu);
387 }
388 if ($t) {
389 set_request($champ_titre, $t);
390 }
391 }
392 }
393
394 /**
395 * Déterminer un titre automatique,
396 * à partir des champs textes de contenu
397 *
398 * Les textes et le titre sont pris dans les champs postés (via `_request()`)
399 * et le titre calculé est de même affecté en tant que champ posté.
400 *
401 * @param array $champs_contenu
402 * Liste des champs contenu textuels
403 * @param array|null $c
404 * tableau qui contient les valeurs des champs de contenu
405 * si `null` on utilise les valeurs du POST
406 * @param int $longueur
407 * Longueur de coupe du texte
408 * @return string
409 */
410 function inc_titrer_contenu_dist($champs_contenu, $c = null, $longueur = 50) {
411 // trouver un champ texte non vide
412 $t = '';
413 foreach ($champs_contenu as $champ) {
414 if ($t = _request($champ, $c)) {
415 break;
416 }
417 }
418
419 if ($t) {
420 include_spip('inc/texte_mini');
421 $t = couper($t, $longueur, '...');
422 }
423
424 return $t;
425 }
426
427 /**
428 * Calcule des clés de contrôles md5 d'un tableau de données.
429 *
430 * Produit la liste des md5 d'un tableau de données, normalement un
431 * tableau des colonnes/valeurs d'un objet éditorial.
432 *
433 * @param array $data
434 * Couples (colonne => valeur). La valeur est un entier ou un texte.
435 * @param string $prefixe
436 * Préfixe à appliquer sur les noms des clés de contrôles, devant le
437 * nom de la colonne
438 * @param string $format
439 * - html : Retourne les contrôles sous forme de input hidden pour un formulaire
440 * - autre : Retourne le tableau ('$prefixe$colonne => md5)
441 * @return bool|string|array
442 * - false si pas $data n'est pas un tableau
443 * - string (avec format html) : contrôles dans des input hidden
444 * - array sinon couples ('$prefixe$colonne => md5)
445 **/
446 function controles_md5($data, $prefixe = 'ctr_', $format = 'html') {
447 if (!is_array($data)) {
448 return false;
449 }
450
451 $ctr = array();
452 foreach ($data as $key => $val) {
453 $m = md5($val);
454 $k = $prefixe . $key;
455
456 switch ($format) {
457 case 'html':
458 $ctr[$k] = "<input type='hidden' value='$m' name='$k' />";
459 break;
460 default:
461 $ctr[$k] = $m;
462 break;
463 }
464 }
465
466 if ($format == 'html') {
467 return "\n\n<!-- controles md5 -->\n" . join("\n", $ctr) . "\n\n";
468 } else {
469 return $ctr;
470 }
471 }
472
473 /**
474 * Contrôle les contenus postés d'un objet en vérifiant qu'il n'y a pas
475 * de conflit d'édition
476 *
477 * Repère les conflits d'édition sur un ou plusieurs champs. C'est à
478 * dire lorsqu'une autre personne a modifié le champ entre le moment où on
479 * a édité notre formulaire et le moment où on a validé le formulaire
480 *
481 * @param string $type
482 * Type d'objet
483 * @param int $id
484 * Identifiant de l'objet
485 * @param array $options
486 * Tableau d'options. Accèpte les index :
487 * - nonvide : Couples (colonne => valeur par défaut). Tous les champs
488 * postés qui sont vides, s'il y en a dans cette option, sont remplacés
489 * par la valeur indiquée
490 * - prefix : Préfixe des clés de contrôles ('ctr_' par défaut). Une clé
491 * de controle tel que 'ctr_titre' contient le md5 du titre au moment
492 * de l'édition.
493 * @param array|bool $c
494 * Tableau de couples (colonne=>valeur) à tester.
495 * Non renseigné, la fonction prend toutes les colonne de l'objet via
496 * _request()
497 * @param string $serveur
498 * Nom du connecteur de base de données
499 * @return bool|null|array
500 * False si aucun champ posté.
501 * Null si aucune modification sur les champs.
502 * Tableau vide si aucun de conflit d'édition.
503 * Tableau (clé => tableau du conflit). L'index est la colonne en conflit,
504 * la valeur un tableau avec 2 index :
505 * - base : le contenu du champ en base
506 * - post : le contenu posté
507 **/
508 function controler_contenu($type, $id, $options = array(), $c = false, $serveur = '') {
509 include_spip('inc/filtres');
510
511 $table_objet = table_objet($type);
512 $spip_table_objet = table_objet_sql($type);
513 $trouver_table = charger_fonction('trouver_table', 'base');
514 $desc = $trouver_table($table_objet, $serveur);
515
516 // Appels incomplets (sans $c)
517 if (!is_array($c)) {
518 foreach ($desc['field'] as $champ => $ignore) {
519 if (_request($champ)) {
520 $c[$champ] = _request($champ);
521 }
522 }
523 }
524
525 // Securite : certaines variables ne sont jamais acceptees ici
526 // car elles ne relevent pas de autoriser(article, modifier) ;
527 // il faut passer par instituer_XX()
528 // TODO: faut-il passer ces variables interdites
529 // dans un fichier de description separe ?
530 unset($c['statut']);
531 unset($c['id_parent']);
532 unset($c['id_rubrique']);
533 unset($c['id_secteur']);
534
535 // Gerer les champs non vides
536 if (isset($options['nonvide']) and is_array($options['nonvide'])) {
537 foreach ($options['nonvide'] as $champ => $sinon) {
538 if ($c[$champ] === '') {
539 $c[$champ] = $sinon;
540 }
541 }
542 }
543
544 // N'accepter que les champs qui existent
545 // [TODO] ici aussi on peut valider les contenus en fonction du type
546 $champs = array();
547 foreach ($desc['field'] as $champ => $ignore) {
548 if (isset($c[$champ])) {
549 $champs[$champ] = $c[$champ];
550 }
551 }
552
553 // Nettoyer les valeurs
554 $champs = array_map('corriger_caracteres', $champs);
555
556 // Envoyer aux plugins
557 $champs = pipeline(
558 'pre_edition',
559 array(
560 'args' => array(
561 'table' => $spip_table_objet, // compatibilite
562 'table_objet' => $table_objet,
563 'spip_table_objet' => $spip_table_objet,
564 'type' => $type,
565 'id_objet' => $id,
566 'champs' => isset($options['champs']) ? $options['champs'] : array(), // [doc] c'est quoi ?
567 'action' => 'controler',
568 'serveur' => $serveur,
569 ),
570 'data' => $champs
571 )
572 );
573
574 if (!$champs) {
575 return false;
576 }
577
578 // Verifier si les mises a jour sont pertinentes, datees, en conflit etc
579 $conflits = controler_md5($champs, $_POST, $type, $id, $serveur, isset($options['prefix']) ? $options['prefix'] : 'ctr_');
580
581 return $conflits;
582 }
583
584
585 /**
586 * Contrôle la liste des md5 envoyés, supprime les inchangés,
587 * signale les modifiés depuis telle date
588 *
589 * @param array $champs
590 * Couples des champs saisis dans le formulaire (colonne => valeur postée)
591 * @param array $ctr
592 * Tableau contenant les clés de contrôles. Couples (clé => md5)
593 * @param string $type
594 * Type d'objet
595 * @param int $id
596 * Identifiant de l'objet
597 * @param string $serveur
598 * Nom du connecteur de base de données
599 * @param string $prefix
600 * Préfixe des clés de contrôles : le nom du champ est préfixé de cette valeur
601 * dans le tableau $ctr pour retrouver son md5.
602 * @return null|array
603 * Null si aucun champ ou aucune modification sur les champs
604 * Tableau vide si aucune erreur de contrôle.
605 * Tableau (clé => tableau du conflit). L'index est la colonne en conflit,
606 * la valeur un tableau avec 2 index :
607 * - base : le contenu du champ en base
608 * - post : le contenu posté
609 **/
610 function controler_md5(&$champs, $ctr, $type, $id, $serveur, $prefix = 'ctr_') {
611 $spip_table_objet = table_objet_sql($type);
612 $id_table_objet = id_table_objet($type);
613
614 // Controle des MD5 envoyes
615 // On elimine les donnees non modifiees par le formulaire (mais
616 // potentiellement modifiees entre temps par un autre utilisateur)
617 foreach ($champs as $key => $val) {
618 if (isset($ctr[$prefix . $key]) and $m = $ctr[$prefix . $key]) {
619 if (is_scalar($val) and $m == md5($val)) {
620 unset($champs[$key]);
621 }
622 }
623 }
624 if (!$champs) {
625 return;
626 }
627
628 // On veut savoir si notre modif va avoir un impact
629 // par rapport aux donnees contenues dans la base
630 // (qui peuvent etre differentes de celles ayant servi a calculer le ctr)
631 $s = sql_fetsel(array_keys($champs), $spip_table_objet, "$id_table_objet=$id", $serveur);
632 $intact = true;
633 foreach ($champs as $ch => $val) {
634 $intact &= ($s[$ch] == $val);
635 }
636 if ($intact) {
637 return;
638 }
639
640 // Detection de conflits :
641 // On verifie si notre modif ne provient pas d'un formulaire
642 // genere a partir de donnees modifiees dans l'intervalle ; ici
643 // on compare a ce qui est dans la base, et on bloque en cas
644 // de conflit.
645 $ctrh = $ctrq = $conflits = array();
646 foreach (array_keys($champs) as $key) {
647 if (isset($ctr[$prefix . $key]) and $m = $ctr[$prefix . $key]) {
648 $ctrh[$key] = $m;
649 $ctrq[] = $key;
650 }
651 }
652 if ($ctrq) {
653 $ctrq = sql_fetsel($ctrq, $spip_table_objet, "$id_table_objet=$id", $serveur);
654 foreach ($ctrh as $key => $m) {
655 if ($m != md5($ctrq[$key])
656 and $champs[$key] !== $ctrq[$key]
657 ) {
658 $conflits[$key] = array(
659 'base' => $ctrq[$key],
660 'post' => $champs[$key]
661 );
662 unset($champs[$key]); # stocker quand meme les modifs ?
663 }
664 }
665 }
666
667 return $conflits;
668 }
669
670 /**
671 * Afficher le contenu d'un champ selon sa longueur
672 * soit dans un `textarea`, soit dans un `input`
673 *
674 * @param string $x
675 * texte à afficher
676 * @return string
677 */
678 function display_conflit_champ($x) {
679 if (strstr($x, "\n") or strlen($x) > 80) {
680 return "<textarea style='width:99%; height:10em;'>" . entites_html($x) . "</textarea>\n";
681 } else {
682 return "<input type='text' size='40' style='width:99%' value=\"" . entites_html($x) . "\" />\n";
683 }
684 }
685
686 /**
687 * Signaler une erreur entre 2 saisies d'un champ
688 *
689 * @uses preparer_diff()
690 * @uses propre_diff()
691 * @uses afficher_para_modifies()
692 * @uses afficher_diff()
693 * @uses minipres()
694 *
695 * @param array $conflits
696 * Valeur des champs en conflit
697 * @param string $redirect
698 * @return string
699 */
700 function signaler_conflits_edition($conflits, $redirect = '') {
701 include_spip('inc/minipres');
702 include_spip('inc/revisions');
703 include_spip('afficher_diff/champ');
704 include_spip('inc/suivi_versions');
705 include_spip('inc/diff');
706 $diffs = array();
707 foreach ($conflits as $champ => $a) {
708 // probleme de stockage ou conflit d'edition ?
709 $base = isset($a['save']) ? $a['save'] : $a['base'];
710
711 $diff = new Diff(new DiffTexte);
712 $n = preparer_diff($a['post']);
713 $o = preparer_diff($base);
714 $d = propre_diff(afficher_para_modifies(afficher_diff($diff->comparer($n, $o))));
715
716 $titre = isset($a['save']) ? _L(
717 'Echec lors de l\'enregistrement du champ @champ@',
718 array('champ' => $champ)
719 ) : $champ;
720
721 $diffs[] = "<h2>$titre</h2>\n"
722 . '<h3>' . _T('info_conflit_edition_differences') . "</h3>\n"
723 . "<div style='max-height:8em; overflow: auto; width:99%;'>" . $d . "</div>\n"
724 . '<h4>' . _T('info_conflit_edition_votre_version') . '</h4>'
725 . display_conflit_champ($a['post'])
726 . '<h4>' . _T('info_conflit_edition_version_enregistree') . '</h4>'
727 . display_conflit_champ($base);
728 }
729
730 if ($redirect) {
731 $id = uniqid(rand());
732 $redirect = "<form action='$redirect' method='get'
733 id='$id'
734 style='float:" . $GLOBALS['spip_lang_right'] . "; margin-top:2em;'>\n"
735 . form_hidden($redirect)
736 . "<input type='submit' value='" . _T('icone_retour') . "' />
737 </form>\n";
738
739 // pour les documents, on est probablement en ajax : il faut ajaxer
740 if (_AJAX) {
741 $redirect .= '<script type="text/javascript">'
742 . 'setTimeout(function(){$("#' . $id . '")
743 .ajaxForm({target:$("#' . $id . '").parent()});
744 }, 200);'
745 . "</script>\n";
746 }
747 }
748
749 echo minipres(
750 _T('titre_conflit_edition'),
751 '<style>
752 .diff-para-deplace { background: #e8e8ff; }
753 .diff-para-ajoute { background: #d0ffc0; color: #000; }
754 .diff-para-supprime { background: #ffd0c0; color: #904040; text-decoration: line-through; }
755 .diff-deplace { background: #e8e8ff; }
756 .diff-ajoute { background: #d0ffc0; }
757 .diff-supprime { background: #ffd0c0; color: #802020; text-decoration: line-through; }
758 .diff-para-deplace .diff-ajoute { background: #b8ffb8; border: 1px solid #808080; }
759 .diff-para-deplace .diff-supprime { background: #ffb8b8; border: 1px solid #808080; }
760 .diff-para-deplace .diff-deplace { background: #b8b8ff; border: 1px solid #808080; }
761 </style>'
762 . '<p>' . _T('info_conflit_edition_avis_non_sauvegarde') . '</p>'
763 . '<p>' . _T('texte_conflit_edition_correction') . '</p>'
764 . "<div style='text-align:" . $GLOBALS['spip_lang_left'] . ";'>"
765 . join("\n", $diffs)
766 . "</div>\n"
767
768 . $redirect
769 );
770 }