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