[SPIP] ~v3.0.20-->v3.0.25
[lhc/web/clavette_www.git] / www / ecrire / action / editer_liens.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 /**
14 * API d'édition de liens
15 *
16 * @package SPIP\Liens\API
17 */
18
19 if (!defined('_ECRIRE_INC_VERSION')) return;
20
21
22
23
24 /**
25 * Teste l'existence de la table de liaison xxx_liens d'un objet
26 *
27 * @api
28 * @param string $objet
29 * Objet à tester
30 * @return array|bool
31 * - false si l'objet n'est pas associable.
32 * - array(clé primaire, nom de la table de lien) si associable
33 */
34 function objet_associable($objet){
35 $trouver_table = charger_fonction('trouver_table','base');
36 $table_sql = table_objet_sql($objet);
37
38 $l="";
39 if ($primary = id_table_objet($objet)
40 AND $trouver_table($l = $table_sql."_liens")
41 AND !preg_match(',[^\w],',$primary)
42 AND !preg_match(',[^\w],',$l))
43 return array($primary,$l);
44
45 spip_log("Objet $objet non associable : ne dispose pas d'une cle primaire $primary OU d'une table liens $l");
46 return false;
47 }
48
49 /**
50 * Associer un ou des objets à des objets listés
51 *
52 * $objets_source et $objets_lies sont de la forme
53 * array($objet=>$id_objets,...)
54 * $id_objets peut lui meme etre un scalaire ou un tableau pour une liste d'objets du meme type
55 * ou de la forme array("NOT",$id_objets) pour une selection par exclusion
56 *
57 * Les objets sources sont les pivots qui portent les liens
58 * et pour lesquels une table spip_xxx_liens existe
59 * (auteurs, documents, mots)
60 *
61 * on peut passer optionnellement une qualification du (des) lien(s) qui sera
62 * alors appliquee dans la foulee.
63 * En cas de lot de liens, c'est la meme qualification qui est appliquee a tous
64 *
65 * @api
66 * @param array $objets_source
67 * @param array|string $objets_lies
68 * @param array $qualif
69 * @return bool|int
70 */
71 function objet_associer($objets_source, $objets_lies, $qualif = null){
72 $modifs = objet_traiter_liaisons('lien_insert', $objets_source, $objets_lies);
73
74 if ($qualif)
75 objet_qualifier_liens($objets_source, $objets_lies, $qualif);
76
77 return $modifs; // pas d'erreur
78 }
79
80
81 /**
82 * Dissocier un (ou des) objet(s) des objets listés
83 *
84 * $objets_source et $objets sont de la forme
85 * array($objet=>$id_objets,...)
86 * $id_objets peut lui meme etre un scalaire ou un tableau pour une liste d'objets du meme type
87 *
88 * Les objets sources sont les pivots qui portent les liens
89 * et pour lesquels une table spip_xxx_liens existe
90 * (auteurs, documents, mots)
91 *
92 * un * pour $objet,$id_objet permet de traiter par lot
93 * seul le type de l'objet source ne peut pas accepter de joker et doit etre explicite
94 *
95 * @api
96 * @param array $objets_source
97 * @param array|string $objets_lies
98 * @return bool|int
99 */
100 function objet_dissocier($objets_source,$objets_lies){
101 return objet_traiter_liaisons('lien_delete',$objets_source,$objets_lies);
102 }
103
104
105
106 /**
107 * Qualifier le lien entre un (ou des) objet(s) et des objets listés
108 *
109 * $objets_source et $objets sont de la forme
110 * array($objet=>$id_objets,...)
111 * $id_objets peut lui meme etre un scalaire ou un tableau pour une liste d'objets du meme type
112 *
113 * Les objets sources sont les pivots qui portent les liens
114 * et pour lesquels une table spip_xxx_liens existe
115 * (auteurs, documents, mots)
116 *
117 * un * pour $objet,$id_objet permet de traiter par lot
118 * seul le type de l'objet source ne peut pas accepter de joker et doit etre explicite
119 *
120 * @api
121 * @param array $objets_source
122 * @param array|string $objets_lies
123 * @param array $qualif
124 * @return bool|int
125 */
126 function objet_qualifier_liens($objets_source,$objets_lies,$qualif){
127 return objet_traiter_liaisons('lien_set',$objets_source,$objets_lies,$qualif);
128 }
129
130
131 /**
132 * Trouver les liens entre objets
133 *
134 * $objets_source et $objets sont de la forme
135 * array($objet=>$id_objets,...)
136 * $id_objets peut lui meme etre un scalaire ou un tableau pour une liste d'objets du meme type
137 *
138 * Les objets sources sont les pivots qui portent les liens
139 * et pour lesquels une table spip_xxx_liens existe
140 * (auteurs, documents, mots)
141 *
142 * un * pour $objet,$id_objet permet de traiter par lot
143 * seul le type de l'objet source ne peut pas accepter de joker et doit etre explicite
144 *
145 * renvoie une liste de tableaux decrivant chaque lien
146 * dans lequel objet_source et objet_lie sont aussi affectes avec l'id de chaque
147 * par facilite
148 * ex :
149 * array(
150 * array('id_document'=>23,'objet'=>'article','id_objet'=>12,'vu'=>'oui',
151 * 'document'=>23,'article'=>12)
152 * )
153 *
154 * @api
155 * @param array $objets_source
156 * @param array|string $objets_lies
157 * @return array
158 */
159 function objet_trouver_liens($objets_source,$objets_lies){
160 return objet_traiter_liaisons('lien_find',$objets_source,$objets_lies);
161 }
162
163
164 /**
165 * Nettoyer les liens morts vers des objets qui n'existent plus
166 *
167 * $objets_source et $objets sont de la forme
168 * array($objet=>$id_objets,...)
169 * $id_objets peut lui meme etre un scalaire ou un tableau pour une liste d'objets du meme type
170 *
171 * Les objets sources sont les pivots qui portent les liens
172 * et pour lesquels une table spip_xxx_liens existe
173 * (auteurs, documents, mots)
174 *
175 * un * pour $objet,$id_objet permet de traiter par lot
176 * seul le type de l'objet source ne peut pas accepter de joker et doit etre explicite
177 *
178 * @api
179 * @param array $objets_source
180 * @param array|string $objets_lies
181 * @return int
182 */
183 function objet_optimiser_liens($objets_source,$objets_lies){
184 return objet_traiter_liaisons('lien_optimise',$objets_source,$objets_lies);
185 }
186
187
188 /**
189 * Dupliquer tous les liens entrant ou sortants d'un objet
190 * vers un autre (meme type d'objet, mais id different)
191 * si $types est fourni, seuls les liens depuis/vers les types listes seront copies
192 * si $exclure_types est fourni, les liens depuis/vers les types listes seront ignores
193 *
194 * @api
195 * @param string $objet
196 * @param int $id_source
197 * @param int $id_cible
198 * @param array $types
199 * @param array $exclure_types
200 * @return int
201 * Nombre de liens copiés
202 */
203 function objet_dupliquer_liens($objet,$id_source,$id_cible,$types=null,$exclure_types=null){
204 include_spip('base/objets');
205 $tables = lister_tables_objets_sql();
206 $n = 0;
207 foreach($tables as $table_sql => $infos){
208 if (
209 (is_null($types) OR in_array($infos['type'],$types))
210 AND (is_null($exclure_types) OR !in_array($infos['type'],$exclure_types))
211 ){
212 if (objet_associable($infos['type'])){
213 $liens = (($infos['type']==$objet)?
214 objet_trouver_liens(array($objet=>$id_source),'*')
215 :
216 objet_trouver_liens(array($infos['type']=>'*'),array($objet=>$id_source)));
217 foreach($liens as $lien){
218 $n++;
219 if ($infos['type']==$objet){
220 objet_associer(array($objet=>$id_cible),array($lien['objet']=>$lien[$lien['objet']]),$lien);
221 }
222 else {
223 objet_associer(array($infos['type']=>$lien[$infos['type']]),array($objet=>$id_cible),$lien);
224 }
225 }
226 }
227 }
228 }
229 return $n;
230 }
231
232 /**
233 * Fonctions techniques
234 * ne pas les appeler directement
235 */
236
237
238 /**
239 * Fonction générique qui
240 * applique une operation de liaison entre un ou des objets et des objets listés
241 * $objets_source et $objets_lies sont de la forme
242 * array($objet=>$id_objets,...)
243 * $id_objets peut lui meme etre un scalaire ou un tableau pour une liste d'objets du meme type
244 *
245 * Les objets sources sont les pivots qui portent les liens
246 * et pour lesquels une table spip_xxx_liens existe
247 * (auteurs, documents, mots)
248 *
249 * on peut passer optionnellement une qualification du (des) lien(s) qui sera
250 * alors appliquee dans la foulee.
251 * En cas de lot de liens, c'est la meme qualification qui est appliquee a tous
252 *
253 * @internal
254 * @param string $operation
255 * @param array $objets_source
256 * @param array $objets_lies
257 * @param array $set
258 * @return bool|int|array
259 */
260 function objet_traiter_liaisons($operation,$objets_source,$objets_lies, $set = null){
261 // accepter une syntaxe minimale pour supprimer tous les liens
262 if ($objets_lies=='*') $objets_lies = array('*'=>'*');
263 $modifs = 0; // compter le nombre de modifications
264 $echec = null;
265 foreach($objets_source as $objet=>$ids){
266 if ($a = objet_associable($objet)) {
267 list($primary,$l) = $a;
268 if (!is_array($ids))
269 $ids = array($ids);
270 elseif(reset($ids)=="NOT"){
271 // si on demande un array('NOT',...) => recuperer la liste d'ids correspondants
272 $where = lien_where($primary,$ids,'*','*');
273 $ids = sql_allfetsel($primary,$l,$where);
274 $ids = array_map('reset',$ids);
275 }
276 foreach($ids as $id) {
277 $res = $operation($objet,$primary,$l,$id,$objets_lies,$set);
278 if ($res===false) {
279 spip_log("objet_traiter_liaisons [Echec] : $operation sur $objet/$primary/$l/$id",_LOG_ERREUR);
280 $echec = true;
281 }
282 else
283 $modifs=($modifs?(is_array($res)?array_merge($modifs,$res):$modifs+$res):$res);
284 }
285 }
286 else
287 $echec = true;
288 }
289
290 return ($echec?false:$modifs); // pas d'erreur
291 }
292
293
294 /**
295 * Sous fonction insertion
296 * qui traite les liens pour un objet source dont la clé primaire
297 * et la table de lien sont fournies
298 *
299 * $objets et de la forme
300 * array($objet=>$id_objets,...)
301 *
302 * Retourne le nombre d'insertions realisees
303 *
304 * @internal
305 * @param string $objet_source
306 * @param string $primary
307 * @param sgring $table_lien
308 * @param int $id
309 * @param array $objets
310 * @return bool|int
311 */
312 function lien_insert($objet_source,$primary,$table_lien,$id,$objets) {
313 $ins = 0;
314 $echec = null;
315 foreach($objets as $objet => $id_objets){
316 if (!is_array($id_objets)) $id_objets = array($id_objets);
317 foreach($id_objets as $id_objet) {
318 $objet = ($objet=='*')?$objet:objet_type($objet); # securite
319 // Envoyer aux plugins
320 $id_objet = pipeline('pre_edition_lien',
321 array(
322 'args' => array(
323 'table_lien' => $table_lien,
324 'objet_source' => $objet_source,
325 'id_objet_source' => $id,
326 'objet' => $objet,
327 'id_objet' => $id_objet,
328 'action'=>'insert',
329 ),
330 'data' => $id_objet
331 )
332 );
333 if ($id_objet=intval($id_objet)
334 AND !sql_getfetsel(
335 $primary,
336 $table_lien,
337 array('id_objet='.intval($id_objet), 'objet='.sql_quote($objet), $primary.'='.intval($id))))
338 {
339
340 $e = sql_insertq($table_lien, array('id_objet' => $id_objet, 'objet'=>$objet, $primary=>$id));
341 if ($e!==false) {
342 $ins++;
343 lien_propage_date_modif($objet,$id_objet);
344 lien_propage_date_modif($objet_source,$id);
345 // Envoyer aux plugins
346 pipeline('post_edition_lien',
347 array(
348 'args' => array(
349 'table_lien' => $table_lien,
350 'objet_source' => $objet_source,
351 'id_objet_source' => $id,
352 'objet' => $objet,
353 'id_objet' => $id_objet,
354 'action'=>'insert',
355 ),
356 'data' => $id_objet
357 )
358 );
359 }
360 else
361 $echec = true;
362 }
363 }
364 }
365 return ($echec?false:$ins);
366 }
367
368 /**
369 * Fabriquer la condition where en tenant compte des jokers *
370 *
371 * @internal
372 * @param string $primary
373 * @param int|string|array $id_source
374 * @param string $objet
375 * @param int|string|array $id_objet
376 * @return array
377 */
378 function lien_where($primary, $id_source, $objet, $id_objet){
379 if ((!is_array($id_source) AND !strlen($id_source))
380 OR !strlen($objet)
381 OR (!is_array($id_objet) AND !strlen($id_objet)))
382 return array("0=1"); // securite
383
384 $not="";
385 if (is_array($id_source) AND reset($id_source)=="NOT"){
386 $not = array_shift($id_source);
387 $id_source = reset($id_source);
388 }
389 $where = array();
390 if ($id_source!=='*')
391 $where[] = (is_array($id_source)?sql_in(addslashes($primary),array_map('intval',$id_source),$not):addslashes($primary) . ($not?"<>":"=") . intval($id_source));
392 elseif ($not)
393 $where[] = "0=1"; // idiot mais quand meme
394
395 $not="";
396 if (is_array($id_objet) AND reset($id_objet)=="NOT"){
397 $not = array_shift($id_objet);
398 $id_objet = reset($id_objet);
399 }
400
401 if ($objet!=='*')
402 $where[] = "objet=".sql_quote($objet);
403 if ($id_objet!=='*')
404 $where[] = (is_array($id_objet)?sql_in('id_objet',array_map('intval',$id_objet),$not):"id_objet" . ($not?"<>":"=") . intval($id_objet));
405 elseif ($not)
406 $where[] = "0=1"; // idiot mais quand meme
407
408 return $where;
409 }
410
411 /**
412 * Sous fonction suppression
413 * qui traite les liens pour un objet source dont la clé primaire
414 * et la table de lien sont fournies
415 *
416 * $objets et de la forme
417 * array($objet=>$id_objets,...)
418 * un * pour $id,$objet,$id_objets permet de traiter par lot
419 *
420 * @internal
421 * @param string $objet_source
422 * @param string $primary
423 * @param sgring $table_lien
424 * @param int $id
425 * @param array $objets
426 * @return bool|int
427 */
428 function lien_delete($objet_source,$primary,$table_lien,$id,$objets){
429 $retire = array();
430 $dels = 0;
431 $echec = false;
432 foreach($objets as $objet => $id_objets){
433 $objet = ($objet=='*')?$objet:objet_type($objet); # securite
434 if (!is_array($id_objets) OR reset($id_objets)=="NOT") $id_objets = array($id_objets);
435 foreach($id_objets as $id_objet) {
436 // id_objet peut valoir '*'
437 $where = lien_where($primary, $id, $objet, $id_objet);
438 // lire les liens existants pour propager la date de modif
439 $liens = sql_allfetsel("$primary,id_objet,objet",$table_lien,$where);
440 // iterer sur les liens pour permettre aux plugins de gerer
441 foreach($liens as $l){
442 // Envoyer aux plugins
443 $id_o = pipeline('pre_edition_lien',
444 array(
445 'args' => array(
446 'table_lien' => $table_lien,
447 'objet_source' => $objet_source,
448 'id_objet_source' => $l[$primary],
449 'objet' => $l['objet'],
450 'id_objet' => $l['id_objet'],
451 'action'=>'delete',
452 ),
453 'data' => $l['id_objet']
454 )
455 );
456 if ($id_o=intval($id_o)){
457 $where = lien_where($primary, $l[$primary], $l['objet'], $id_o);
458 $e = sql_delete($table_lien, $where);
459 if ($e!==false){
460 $dels+=$e;
461 lien_propage_date_modif($l['objet'],$id_o);
462 lien_propage_date_modif($objet_source,$l[$primary]);
463 }
464 else
465 $echec = true;
466 $retire[] = array('source'=>array($objet_source=>$l[$primary]),'lien'=>array($l['objet']=>$id_o),'type'=>$l['objet'],'id'=>$id_o);
467 // Envoyer aux plugins
468 pipeline('post_edition_lien',
469 array(
470 'args' => array(
471 'table_lien' => $table_lien,
472 'objet_source' => $objet_source,
473 'id_objet_source' => $l[$primary],
474 'objet' => $l['objet'],
475 'id_objet' => $id_o,
476 'action'=>'delete',
477 ),
478 'data' => $id_o
479 )
480 );
481 }
482 }
483 }
484 }
485 pipeline('trig_supprimer_objets_lies',$retire);
486
487 return ($echec?false:$dels);
488 }
489
490
491 /**
492 * Sous fonction optimisation
493 * qui nettoie les liens morts (vers un objet inexistant)
494 * pour un objet source dont la clé primaire
495 * et la table de lien sont fournies
496 *
497 * $objets et de la forme
498 * array($objet=>$id_objets,...)
499 * un * pour $id,$objet,$id_objets permet de traiter par lot
500 *
501 * @internal
502 * @param string $objet_source
503 * @param string $primary
504 * @param sgring $table_lien
505 * @param int $id
506 * @param array $objets
507 * @return bool|int
508 */
509 function lien_optimise($objet_source,$primary,$table_lien,$id,$objets){
510 include_spip('genie/optimiser');
511 $echec = false;
512 $dels = 0;
513 foreach($objets as $objet => $id_objets){
514 $objet = ($objet=='*')?$objet:objet_type($objet); # securite
515 if (!is_array($id_objets) OR reset($id_objets)=="NOT") $id_objets = array($id_objets);
516 foreach($id_objets as $id_objet) {
517 $where = lien_where($primary, $id, $objet, $id_objet);
518 # les liens vers un objet inexistant
519 $r = sql_select("DISTINCT objet",$table_lien,$where);
520 while ($t = sql_fetch($r)){
521 $type = $t['objet'];
522 $spip_table_objet = table_objet_sql($type);
523 $id_table_objet = id_table_objet($type);
524 $res = sql_select("L.$primary AS id,L.id_objet",
525 // la condition de jointure inclue L.objet='xxx' pour ne joindre que les bonnes lignes
526 // du coups toutes les lignes avec un autre objet ont un id_xxx=NULL puisque LEFT JOIN
527 // il faut les eliminier en repetant la condition dans le where L.objet='xxx'
528 "$table_lien AS L
529 LEFT JOIN $spip_table_objet AS O
530 ON (O.$id_table_objet=L.id_objet AND L.objet=".sql_quote($type).")",
531 "L.objet=".sql_quote($type)." AND O.$id_table_objet IS NULL");
532 // sur une cle primaire composee, pas d'autres solutions que de virer un a un
533 while ($row = sql_fetch($res)){
534 $e = sql_delete($table_lien, array("$primary=".$row['id'],"id_objet=".$row['id_objet'],"objet=".sql_quote($type)));
535 if ($e!=false){
536 $dels+=$e;
537 spip_log("Entree ".$row['id']."/".$row['id_objet']."/$type supprimee dans la table $table_lien",_LOG_INFO_IMPORTANTE);
538 }
539 }
540 }
541
542 # les liens depuis un objet inexistant
543 $table_source = table_objet_sql($objet_source);
544 // filtrer selon $id, $objet, $id_objet eventuellement fournis
545 // (en general '*' pour chaque)
546 $where = lien_where("L.$primary", $id, $objet, $id_objet);
547 $where[] = "O.$primary IS NULL";
548 $res = sql_select("L.$primary AS id",
549 "$table_lien AS L LEFT JOIN $table_source AS O ON L.$primary=O.$primary",
550 $where);
551 $dels+= optimiser_sansref($table_lien, $primary, $res);
552 }
553 }
554 return ($echec?false:$dels);
555 }
556
557
558 /**
559 * Sous fonction qualification
560 * qui traite les liens pour un objet source dont la clé primaire
561 * et la table de lien sont fournies
562 *
563 * $objets et de la forme
564 * array($objet=>$id_objets,...)
565 * un * pour $id,$objet,$id_objets permet de traiter par lot
566 *
567 * exemple :
568 * $qualif = array('vu'=>'oui');
569 *
570 * @internal
571 * @param string $objet_source
572 * @param string $primary
573 * @param sgring $table_lien
574 * @param int $id
575 * @param array $objets
576 * @param array $qualif
577 * @return bool|int
578 */
579 function lien_set($objet_source,$primary,$table_lien,$id,$objets,$qualif){
580 $echec = null;
581 $ok = 0;
582 if (!$qualif)
583 return false;
584 // nettoyer qualif qui peut venir directement d'un objet_trouver_lien :
585 unset($qualif[$primary]);
586 unset($qualif[$objet_source]);
587 if (isset($qualif['objet'])) {
588 unset($qualif[$qualif['objet']]);
589 }
590 unset($qualif['objet']);
591 unset($qualif['id_objet']);
592 foreach($objets as $objet => $id_objets){
593 $objet = ($objet=='*')?$objet:objet_type($objet); # securite
594 if (!is_array($id_objets) OR reset($id_objets)=="NOT") $id_objets = array($id_objets);
595 foreach($id_objets as $id_objet) {
596 $where = lien_where($primary, $id, $objet, $id_objet);
597 $e = sql_updateq($table_lien,$qualif,$where);
598 if ($e===false)
599 $echec = true;
600 else
601 $ok++;
602 }
603 }
604 return ($echec?false:$ok);
605 }
606
607 /**
608 * Sous fonction trouver
609 * qui cherche les liens pour un objet source dont la clé primaire
610 * et la table de lien sont fournies
611 *
612 * $objets et de la forme
613 * array($objet=>$id_objets,...)
614 * un * pour $id,$objet,$id_objets permet de traiter par lot
615 *
616 *
617 * @internal
618 * @param string $objet_source
619 * @param string $primary
620 * @param sgring $table_lien
621 * @param int $id
622 * @param array $objets
623 * @return array
624 */
625 function lien_find($objet_source,$primary,$table_lien,$id,$objets){
626 $trouve = array();
627 foreach($objets as $objet => $id_objets){
628 $objet = ($objet=='*')?$objet:objet_type($objet); # securite
629 // lien_where prend en charge les $id_objets sous forme int ou array
630 $where = lien_where($primary, $id, $objet, $id_objets);
631 $liens = sql_allfetsel('*',$table_lien,$where);
632 // ajouter les entrees objet_source et objet cible par convenance
633 foreach($liens as $l) {
634 $l[$objet_source] = $l[$primary];
635 $l[$objet] = $l['id_objet'];
636 $trouve[] = $l;
637 }
638 }
639 return $trouve;
640 }
641
642 /**
643 * Propager la date_modif sur les objets dont un lien a été modifié
644 *
645 * @internal
646 * @param string $objet
647 * @param array|int $ids
648 */
649 function lien_propage_date_modif($objet,$ids){
650 $trouver_table = charger_fonction('trouver_table','base');
651
652 $table = table_objet_sql($objet);
653 if ($desc = $trouver_table($table)
654 AND isset($desc['field']['date_modif'])){
655 $primary = id_table_objet($objet);
656 $where = (is_array($ids)?sql_in($primary, array_map('intval',$ids)):"$primary=".intval($ids));
657 sql_updateq($table, array('date_modif'=>date('Y-m-d H:i:s')), $where);
658 }
659 }
660 ?>