[SPIP] ~v3.0.20-->v3.0.25
[lhc/web/clavette_www.git] / www / ecrire / inc / editer.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2016 *
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 if (!defined('_ECRIRE_INC_VERSION')) return;
14 include_spip('base/abstract_sql');
15
16 // http://doc.spip.org/@formulaires_editer_objet_traiter
17 function formulaires_editer_objet_traiter($type, $id='new', $id_parent=0, $lier_trad=0, $retour='', $config_fonc='articles_edit_config', $row=array(), $hidden=''){
18
19 $res = array();
20 // eviter la redirection forcee par l'action...
21 set_request('redirect');
22 if ($action_editer = charger_fonction("editer_$type",'action',true)) {
23 list($id,$err) = $action_editer($id);
24 }
25 else {
26 $action_editer = charger_fonction("editer_objet",'action');
27 list($id,$err) = $action_editer($id,$type);
28 }
29 $id_table_objet = id_table_objet($type);
30 $res[$id_table_objet] = $id;
31 if ($err OR !$id){
32 $res['message_erreur'] = ($err?$err:_T('erreur'));
33 }
34 else{
35 // Un lien de trad a prendre en compte
36 if ($lier_trad){
37 // referencer la traduction
38 $referencer_traduction = charger_fonction('referencer_traduction','action');
39 $referencer_traduction($type, $id, $lier_trad);
40 // dupliquer tous les liens sauf les auteurs : le nouvel auteur est celui qui traduit
41 // cf API editer_liens
42 include_spip('action/editer_liens');
43 objet_dupliquer_liens($type,$lier_trad,$id,null,array('auteur'));
44 }
45
46 $res['message_ok'] = _T('info_modification_enregistree');
47 if ($retour) {
48 if (strncmp($retour,'javascript:',11)==0){
49 $res['message_ok'] .= '<script type="text/javascript">/*<![CDATA[*/'.substr($retour,11).'/*]]>*/</script>';
50 $res['editable'] = true;
51 }
52 else
53 $res['redirect'] = parametre_url($retour,$id_table_objet,$id);
54 }
55 }
56 return $res;
57 }
58
59 // http://doc.spip.org/@formulaires_editer_objet_verifier
60 function formulaires_editer_objet_verifier($type,$id='new', $oblis = array()){
61 $erreurs = array();
62 if (intval($id)) {
63 $conflits = controler_contenu($type,$id);
64 if ($conflits AND count($conflits)) {
65 foreach($conflits as $champ=>$conflit) {
66 if (!isset($erreurs[$champ])) { $erreurs[$champ] = ''; }
67 $erreurs[$champ] .= _T("alerte_modif_info_concourante")."<br /><textarea readonly='readonly' class='forml'>".$conflit['base']."</textarea>";
68 }
69 }
70 }
71 foreach($oblis as $obli) {
72 $value = _request($obli);
73 if (is_null($value) OR !(is_array($value)?count($value):strlen($value))) {
74 if (!isset($erreurs[$obli])) { $erreurs[$obli] = ''; }
75 $erreurs[$obli] .= _T("info_obligatoire");
76 }
77 }
78 return $erreurs;
79 }
80
81 // http://doc.spip.org/@formulaires_editer_objet_charger
82 function formulaires_editer_objet_charger($type, $id='new', $id_parent=0, $lier_trad=0, $retour='', $config_fonc='articles_edit_config', $row=array(), $hidden=''){
83 $table_objet = table_objet($type);
84 $table_objet_sql = table_objet_sql($type);
85 $id_table_objet = id_table_objet($type);
86 $new = !is_numeric($id);
87 // Appel direct dans un squelette
88 if (!$row) {
89 if (!$new OR $lier_trad) {
90 if ($select = charger_fonction("precharger_" . $type, 'inc', true))
91 $row = $select($id, $id_parent, $lier_trad);
92 else $row = sql_fetsel('*',$table_objet_sql,$id_table_objet."=".intval($id));
93 if (!$new)
94 $md5 = controles_md5($row);
95 }
96 if (!$row) {
97 $trouver_table = charger_fonction('trouver_table','base');
98 if ($desc = $trouver_table($table_objet))
99 foreach($desc['field'] as $k=>$v) $row[$k]='';
100 }
101 }
102
103 // Gaffe: sans ceci, on ecrase systematiquement l'article d'origine
104 // (et donc: pas de lien de traduction)
105 $id = ($new OR $lier_trad)
106 ? 'oui'
107 : $row[$id_table_objet];
108 $row[$id_table_objet] = $id;
109
110 $contexte = $row;
111 if (strlen($id_parent) && is_numeric($id_parent) && (!isset($contexte['id_parent']) OR $new)){
112 if (!isset($contexte['id_parent'])) unset($contexte['id_rubrique']);
113 $contexte['id_parent']=$id_parent;
114 }
115 elseif (!isset($contexte['id_parent'])){
116 // id_rubrique dans id_parent si possible
117 if (isset($contexte['id_rubrique'])) {
118 $contexte['id_parent'] = $contexte['id_rubrique'];
119 unset($contexte['id_rubrique']);
120 }
121 else{
122 $contexte['id_parent'] = '';
123 }
124 if (!$contexte['id_parent']
125 AND $preselectionner_parent_nouvel_objet = charger_fonction("preselectionner_parent_nouvel_objet","inc",true))
126 $contexte['id_parent'] = $preselectionner_parent_nouvel_objet($type,$row);
127 }
128
129 if ($config_fonc)
130 $contexte['config'] = $config = $config_fonc($contexte);
131 if (!isset($config['lignes'])) $config['lignes'] = 0;
132 $att_text = " class='textarea' "
133 . " rows='"
134 . ($config['lignes'] +15)
135 . "' cols='40'";
136 if (isset($contexte['texte']))
137 list($contexte['texte'],$contexte['_texte_trop_long']) = editer_texte_recolle($contexte['texte'],$att_text);
138
139 // on veut conserver la langue de l'interface ;
140 // on passe cette donnee sous un autre nom, au cas ou le squelette
141 // voudrait l'exploiter
142 if (isset($contexte['lang'])) {
143 $contexte['langue'] = $contexte['lang'];
144 unset($contexte['lang']);
145 }
146
147 $contexte['_hidden'] = "<input type='hidden' name='editer_$type' value='oui' />\n" .
148 (!$lier_trad ? '' :
149 ("\n<input type='hidden' name='lier_trad' value='" .
150 $lier_trad .
151 "' />" .
152 "\n<input type='hidden' name='changer_lang' value='" .
153 $config['langue'] .
154 "' />"))
155 . $hidden
156 . (isset($md5) ? $md5 : '');
157
158
159 if (isset($contexte['extra']))
160 $contexte['extra'] = unserialize($contexte['extra']);
161 // preciser que le formulaire doit passer dans un pipeline
162 $contexte['_pipeline'] = array('editer_contenu_objet',array('type'=>$type,'id'=>$id));
163
164 // preciser que le formulaire doit etre securise auteur/action
165 // n'est plus utile lorsque l'action accepte l'id en argument direct
166 // on le garde pour compat
167 $contexte['_action'] = array("editer_$type",$id);
168
169 return $contexte;
170 }
171
172 //
173 // Gestion des textes trop longs (limitation brouteurs)
174 // utile pour les textes > 32ko
175
176 // http://doc.spip.org/@coupe_trop_long
177 function coupe_trop_long($texte){
178 $aider = charger_fonction('aider', 'inc');
179 if (strlen($texte) > 28*1024) {
180 $texte = str_replace("\r\n","\n",$texte);
181 $pos = strpos($texte, "\n\n", 28*1024); // coupe para > 28 ko
182 if ($pos > 0 and $pos < 32 * 1024) {
183 $debut = substr($texte, 0, $pos)."\n\n<!--SPIP-->\n";
184 $suite = substr($texte, $pos + 2);
185 } else {
186 $pos = strpos($texte, " ", 28*1024); // sinon coupe espace
187 if (!($pos > 0 and $pos < 32 * 1024)) {
188 $pos = 28*1024; // au pire (pas d'espace trouv'e)
189 $decalage = 0; // si y'a pas d'espace, il ne faut pas perdre le caract`ere
190 } else {
191 $decalage = 1;
192 }
193 $debut = substr($texte,0,$pos + $decalage); // Il faut conserver l'espace s'il y en a un
194 $suite = substr($texte,$pos + $decalage);
195 }
196 return (array($debut,$suite));
197 }
198 else
199 return (array($texte,''));
200 }
201
202 // http://doc.spip.org/@editer_texte_recolle
203 function editer_texte_recolle($texte, $att_text)
204 {
205 if ((strlen($texte)<29*1024)
206 OR (include_spip('inc/layer') AND ($GLOBALS['browser_name']!="MSIE")) )
207 return array($texte,"");
208
209 include_spip('inc/barre');
210 $textes_supplement = "<br /><span style='color: red'>"._T('info_texte_long')."</span>\n";
211 $nombre = 0;
212
213 while (strlen($texte)>29*1024) {
214 $nombre ++;
215 list($texte1,$texte) = coupe_trop_long($texte);
216 $textes_supplement .= "<br />" .
217 "<textarea id='texte$nombre' name='texte_plus[$nombre]'$att_text>$texte1</textarea>\n";
218 }
219 return array($texte,$textes_supplement);
220 }
221
222 /**
223 * auto-renseigner le titre si il n'existe pas
224 *
225 * @param $champ_titre
226 * @param $champs_contenu
227 * @param int $longueur
228 */
229 function titre_automatique($champ_titre,$champs_contenu,$longueur=null){
230 if (!_request($champ_titre)){
231 $titrer_contenu = charger_fonction('titrer_contenu','inc');
232 if (!is_null($longueur))
233 $t = $titrer_contenu($champs_contenu,null,$longueur);
234 else
235 $t = $titrer_contenu($champs_contenu);
236 if ($t)
237 set_request($champ_titre,$t);
238 }
239 }
240
241 /**
242 * Determiner un titre automatique,
243 * a partir des champs textes de contenu
244 *
245 * @param array $champs_contenu
246 * liste des champs contenu textuels
247 * @param array|null $c
248 * tableau qui contient les valeurs des champs de contenu
249 * si null on utilise les valeurs du POST
250 * @param int $longueur
251 * longueur de coupe
252 * @return string
253 */
254 function inc_titrer_contenu_dist($champs_contenu, $c=null, $longueur=50){
255 // trouver un champ texte non vide
256 $t = "";
257 foreach($champs_contenu as $champ){
258 if ($t = _request($champ,$c))
259 break;
260 }
261
262 if ($t){
263 include_spip('inc/texte_mini');
264 $t = couper($t,$longueur,"...");
265 }
266
267 return $t;
268 }
269
270 // Produit la liste des md5 d'un tableau de donnees, sous forme
271 // de inputs html
272 // http://doc.spip.org/@controles_md5
273 function controles_md5($data, $prefixe='ctr_', $format='html'){
274 if (!is_array($data))
275 return false;
276
277 $ctr = array();
278 foreach ($data as $key => $val) {
279 $m = md5($val);
280 $k = $prefixe.$key;
281
282 switch ($format) {
283 case 'html':
284 $ctr[$k] = "<input type='hidden' value='$m' name='$k' />";
285 break;
286 default:
287 $ctr[$k] = $m;
288 break;
289 }
290 }
291
292 if ($format == 'html')
293 return "\n\n<!-- controles md5 -->\n".join("\n", $ctr)."\n\n";
294 else
295 return $ctr;
296 }
297
298 // http://doc.spip.org/@controler_contenu
299 function controler_contenu($type, $id, $options=array(), $c=false, $serveur='') {
300 include_spip('inc/filtres');
301
302 $table_objet = table_objet($type);
303 $spip_table_objet = table_objet_sql($type);
304 $id_table_objet = id_table_objet($type);
305 $trouver_table = charger_fonction('trouver_table', 'base');
306 $desc = $trouver_table($table_objet, $serveur);
307
308 // Appels incomplets (sans $c)
309 if (!is_array($c)) {
310 foreach($desc['field'] as $champ=>$ignore)
311 if(_request($champ))
312 $c[$champ] = _request($champ);
313 }
314
315 // Securite : certaines variables ne sont jamais acceptees ici
316 // car elles ne relevent pas de autoriser(article, modifier) ;
317 // il faut passer par instituer_XX()
318 // TODO: faut-il passer ces variables interdites
319 // dans un fichier de description separe ?
320 unset($c['statut']);
321 unset($c['id_parent']);
322 unset($c['id_rubrique']);
323 unset($c['id_secteur']);
324
325 // Gerer les champs non vides
326 if (isset($options['nonvide']) AND is_array($options['nonvide']))
327 foreach ($options['nonvide'] as $champ => $sinon)
328 if ($c[$champ] === '')
329 $c[$champ] = $sinon;
330
331 // N'accepter que les champs qui existent
332 // TODO: ici aussi on peut valider les contenus
333 // en fonction du type
334 $champs = array();
335 foreach($desc['field'] as $champ => $ignore)
336 if (isset($c[$champ]))
337 $champs[$champ] = $c[$champ];
338
339 // Nettoyer les valeurs
340 $champs = array_map('corriger_caracteres', $champs);
341
342 // Envoyer aux plugins
343 $champs = pipeline('pre_edition',
344 array(
345 'args' => array(
346 'table' => $spip_table_objet, // compatibilite
347 'table_objet' => $table_objet,
348 'spip_table_objet' => $spip_table_objet,
349 'type' =>$type,
350 'id_objet' => $id,
351 'champs' => isset($options['champs'])?$options['champs']:array(), // [doc] c'est quoi ?
352 'action' => 'controler'
353 ),
354 'data' => $champs
355 )
356 );
357
358 if (!$champs) return false;
359
360 // Verifier si les mises a jour sont pertinentes, datees, en conflit etc
361 $conflits = controler_md5($champs, $_POST, $type, $id, $serveur, isset($options['prefix'])?$options['prefix']:'ctr_');
362
363 return $conflits;
364 }
365
366 // Controle la liste des md5 envoyes, supprime les inchanges,
367 // signale les modifies depuis telle date
368 // http://doc.spip.org/@controler_md5
369 function controler_md5(&$champs, $ctr, $type, $id, $serveur, $prefix = 'ctr_') {
370 $table_objet = table_objet($type);
371 $spip_table_objet = table_objet_sql($type);
372 $id_table_objet = id_table_objet($type);
373
374 // Controle des MD5 envoyes
375 // On elimine les donnees non modifiees par le formulaire (mais
376 // potentiellement modifiees entre temps par un autre utilisateur)
377 foreach ($champs as $key => $val) {
378 if (isset($ctr[$prefix.$key]) AND $m = $ctr[$prefix.$key]) {
379 if ($m == md5($val))
380 unset ($champs[$key]);
381 }
382 }
383 if (!$champs) return;
384
385 // On veut savoir si notre modif va avoir un impact
386 // par rapport aux donnees contenues dans la base
387 // (qui peuvent etre differentes de celles ayant servi a calculer le ctr)
388 $s = sql_fetsel(array_keys($champs), $spip_table_objet, "$id_table_objet=$id", $serveur);
389 $intact = true;
390 foreach ($champs as $ch => $val)
391 $intact &= ($s[$ch] == $val);
392 if ($intact) return;
393
394 // Detection de conflits :
395 // On verifie si notre modif ne provient pas d'un formulaire
396 // genere a partir de donnees modifiees dans l'intervalle ; ici
397 // on compare a ce qui est dans la base, et on bloque en cas
398 // de conflit.
399 $ctrh = $ctrq = $conflits = array();
400 foreach (array_keys($champs) as $key) {
401 if (isset($ctr[$prefix.$key]) AND $m = $ctr[$prefix.$key]) {
402 $ctrh[$key] = $m;
403 $ctrq[] = $key;
404 }
405 }
406 if ($ctrq) {
407 $ctrq = sql_fetsel($ctrq, $spip_table_objet, "$id_table_objet=$id", $serveur);
408 foreach ($ctrh as $key => $m) {
409 if ($m != md5($ctrq[$key])
410 AND $champs[$key] !== $ctrq[$key]) {
411 $conflits[$key] = array(
412 'base' => $ctrq[$key],
413 'post' => $champs[$key]
414 );
415 unset($champs[$key]); # stocker quand meme les modifs ?
416 }
417 }
418 }
419
420 return $conflits;
421 }
422
423 // http://doc.spip.org/@display_conflit_champ
424 function display_conflit_champ($x) {
425 if (strstr($x, "\n") OR strlen($x)>80)
426 return "<textarea style='width:99%; height:10em;'>".entites_html($x)."</textarea>\n";
427 else
428 return "<input type='text' size='40' style='width:99%' value=\"".entites_html($x)."\" />\n";
429 }
430
431 // http://doc.spip.org/@signaler_conflits_edition
432 function signaler_conflits_edition($conflits, $redirect='') {
433 include_spip('inc/minipres');
434 include_spip('inc/revisions');
435 include_spip('afficher_diff/champ');
436 include_spip('inc/suivi_versions');
437 include_spip('inc/diff');
438 foreach ($conflits as $champ=>$a) {
439 // probleme de stockage ou conflit d'edition ?
440 $base = isset($a['save']) ? $a['save'] : $a['base'];
441
442 $diff = new Diff(new DiffTexte);
443 $n = preparer_diff($a['post']);
444 $o = preparer_diff($base);
445 $d = propre_diff(
446 afficher_para_modifies(afficher_diff($diff->comparer($n,$o))));
447
448 $titre = isset($a['save']) ? _L('Echec lors de l\'enregistrement du champ @champ@', array('champ' => $champ)) : $champ;
449
450 $diffs[] = "<h2>$titre</h2>\n"
451 . "<h3>"._T('info_conflit_edition_differences')."</h3>\n"
452 . "<div style='max-height:8em; overflow: auto; width:99%;'>".$d."</div>\n"
453 . "<h4>"._T('info_conflit_edition_votre_version')."</h4>"
454 . display_conflit_champ($a['post'])
455 . "<h4>"._T('info_conflit_edition_version_enregistree')."</h4>"
456 . display_conflit_champ($base);
457 }
458
459 if ($redirect) {
460 $id = uniqid(rand());
461 $redirect = "<form action='$redirect' method='get'
462 id='$id'
463 style='float:".$GLOBALS['spip_lang_right']."; margin-top:2em;'>\n"
464 .form_hidden($redirect)
465 ."<input type='submit' value='"._T('icone_retour')."' />
466 </form>\n";
467
468 // pour les documents, on est probablement en ajax : il faut ajaxer
469 if (_AJAX)
470 $redirect .= '<script type="text/javascript">'
471 .'setTimeout(function(){$("#'.$id.'")
472 .ajaxForm({target:$("#'.$id.'").parent()});
473 }, 200);'
474 ."</script>\n";
475
476 }
477
478 echo minipres(
479 _T('titre_conflit_edition'),
480
481 '<style>
482 .diff-para-deplace { background: #e8e8ff; }
483 .diff-para-ajoute { background: #d0ffc0; color: #000; }
484 .diff-para-supprime { background: #ffd0c0; color: #904040; text-decoration: line-through; }
485 .diff-deplace { background: #e8e8ff; }
486 .diff-ajoute { background: #d0ffc0; }
487 .diff-supprime { background: #ffd0c0; color: #802020; text-decoration: line-through; }
488 .diff-para-deplace .diff-ajoute { background: #b8ffb8; border: 1px solid #808080; }
489 .diff-para-deplace .diff-supprime { background: #ffb8b8; border: 1px solid #808080; }
490 .diff-para-deplace .diff-deplace { background: #b8b8ff; border: 1px solid #808080; }
491 </style>'
492 .'<p>'._T('info_conflit_edition_avis_non_sauvegarde').'</p>'
493 .'<p>'._T('texte_conflit_edition_correction').'</p>'
494 ."<div style='text-align:".$GLOBALS['spip_lang_left'].";'>"
495 . join("\n",$diffs)
496 ."</div>\n"
497
498 . $redirect
499 );
500 }
501
502 ?>