[SPIP] ~v3.2.3-->v3.2.4
[lhc/web/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-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 * API d'édition de liens
15 *
16 * Cette API gère la création, modification et suppressions de liens
17 * entre deux objets éditoriaux par l'intermédiaire de tables de liaison
18 * tel que spip_xx_liens.
19 *
20 * L'unicité est assurée dans les fonctions sur le trio (id_x, objet, id_objet)
21 * par défaut, ce qui correspond à la déclaration de clé primaire.
22 *
23 * Des rôles peuvent être déclarés pour des liaisons. À ce moment là,
24 * une colonne spécifique doit être présente dans la table de liens
25 * et l'unicité est alors assurée sur le quatuor (id_x, objet, id_objet, role)
26 * et la clé primaire adaptée en conséquence.
27 *
28 * @package SPIP\Core\Liens\API
29 */
30
31 if (!defined('_ECRIRE_INC_VERSION')) {
32 return;
33 }
34
35 // charger la gestion les rôles sur les objets
36 include_spip('inc/roles');
37
38
39 /**
40 * Teste l'existence de la table de liaison xxx_liens d'un objet
41 *
42 * @api
43 * @param string $objet
44 * Objet à tester
45 * @return array|bool
46 * - false si l'objet n'est pas associable.
47 * - array(clé primaire, nom de la table de lien) si associable
48 */
49 function objet_associable($objet) {
50 $trouver_table = charger_fonction('trouver_table', 'base');
51 $table_sql = table_objet_sql($objet);
52
53 $l = "";
54 if ($primary = id_table_objet($objet)
55 and $trouver_table($l = $table_sql . "_liens")
56 and !preg_match(',[^\w],', $primary)
57 and !preg_match(',[^\w],', $l)
58 ) {
59 return array($primary, $l);
60 }
61
62 spip_log("Objet $objet non associable : ne dispose pas d'une cle primaire $primary OU d'une table liens $l");
63
64 return false;
65 }
66
67 /**
68 * Associer un ou des objets à des objets listés
69 *
70 * `$objets_source` et `$objets_lies` sont de la forme
71 * `array($objet=>$id_objets,...)`
72 * `$id_objets` peut lui même être un scalaire ou un tableau pour une liste d'objets du même type
73 * ou de la forme `array("NOT", $id_objets)` pour une sélection par exclusion
74 *
75 * Les objets sources sont les pivots qui portent les liens
76 * et pour lesquels une table spip_xxx_liens existe
77 * (auteurs, documents, mots)
78 *
79 * On peut passer optionnellement une qualification du (des) lien(s) qui sera
80 * alors appliquée dans la foulée.
81 * En cas de lot de liens, c'est la même qualification qui est appliquée a tous
82 *
83 * @api
84 * @param array $objets_source
85 * @param array|string $objets_lies
86 * @param array $qualif
87 * @return bool|int
88 */
89 function objet_associer($objets_source, $objets_lies, $qualif = null) {
90 $modifs = objet_traiter_liaisons('lien_insert', $objets_source, $objets_lies, $qualif);
91
92 if ($qualif) {
93 objet_qualifier_liens($objets_source, $objets_lies, $qualif);
94 }
95
96 return $modifs; // pas d'erreur
97 }
98
99
100 /**
101 * Dissocier un (ou des) objet(s) des objets listés
102 *
103 * `$objets_source` et `$objets_lies` sont de la forme
104 * `array($objet=>$id_objets,...)`
105 * `$id_objets` peut lui-même être un scalaire ou un tableau pour une liste d'objets du même type
106 *
107 * Les objets sources sont les pivots qui portent les liens
108 * et pour lesquels une table spip_xxx_liens existe
109 * (auteurs, documents, mots)
110 *
111 * un * pour $objet, $id_objet permet de traiter par lot
112 * seul le type de l'objet source ne peut pas accepter de joker et doit etre explicite
113 *
114 * S'il y a des rôles possibles entre les 2 objets, et qu'aucune condition
115 * sur la colonne du rôle n'est transmise, on ne supprime que les liens
116 * avec le rôle par défaut. Si on veut supprimer tous les rôles,
117 * il faut spécifier $cond => array('role' => '*')
118 *
119 * @api
120 * @param array $objets_source
121 * @param array|string $objets_lies
122 * @param array|null $cond
123 * Condition du where supplémentaires
124 *
125 * À l'exception de l'index 'role' qui permet de sélectionner un rôle
126 * ou tous les rôles (*), en s'affranchissant du vrai nom de la colonne.
127 * @return bool|int
128 */
129 function objet_dissocier($objets_source, $objets_lies, $cond = null) {
130 return objet_traiter_liaisons('lien_delete', $objets_source, $objets_lies, $cond);
131 }
132
133
134 /**
135 * Qualifier le lien entre un (ou des) objet(s) et des objets listés
136 *
137 * $objets_source et $objets sont de la forme
138 * array($objet=>$id_objets,...)
139 * $id_objets peut lui meme etre un scalaire ou un tableau pour une liste d'objets du meme type
140 *
141 * Les objets sources sont les pivots qui portent les liens
142 * et pour lesquels une table spip_xxx_liens existe
143 * (auteurs, documents, mots)
144 *
145 * un * pour $objet,$id_objet permet de traiter par lot
146 * seul le type de l'objet source ne peut pas accepter de joker et doit etre explicite
147 *
148 * @api
149 * @param array $objets_source
150 * @param array|string $objets_lies
151 * @param array $qualif
152 * @return bool|int
153 */
154 function objet_qualifier_liens($objets_source, $objets_lies, $qualif) {
155 return objet_traiter_liaisons('lien_set', $objets_source, $objets_lies, $qualif);
156 }
157
158
159 /**
160 * Trouver les liens entre objets
161 *
162 * $objets_source et $objets sont de la forme
163 * array($objet=>$id_objets,...)
164 * $id_objets peut lui meme etre un scalaire ou un tableau pour une liste d'objets du meme type
165 *
166 * Les objets sources sont les pivots qui portent les liens
167 * et pour lesquels une table spip_xxx_liens existe
168 * (auteurs, documents, mots)
169 *
170 * un * pour $objet,$id_objet permet de traiter par lot
171 * seul le type de l'objet source ne peut pas accepter de joker et doit etre explicite
172 *
173 * renvoie une liste de tableaux decrivant chaque lien
174 * dans lequel objet_source et objet_lie sont aussi affectes avec l'id de chaque
175 * par facilite
176 * ex :
177 * array(
178 * array('id_document'=>23,'objet'=>'article','id_objet'=>12,'vu'=>'oui',
179 * 'document'=>23,'article'=>12)
180 * )
181 *
182 * @api
183 * @param array $objets_source Couples (objets_source => identifiants) (objet qui a la table de lien)
184 * @param array|string $objets_lies Couples (objets_lies => identifiants)
185 * @param array|null $cond Condition du where supplémentaires
186 * @return array
187 * Liste des trouvailles
188 */
189 function objet_trouver_liens($objets_source, $objets_lies, $cond = null) {
190 return objet_traiter_liaisons('lien_find', $objets_source, $objets_lies, $cond);
191 }
192
193
194 /**
195 * Nettoyer les liens morts vers des objets qui n'existent plus
196 *
197 * $objets_source et $objets sont de la forme
198 * array($objet=>$id_objets,...)
199 * $id_objets peut lui meme etre un scalaire ou un tableau pour une liste d'objets du meme type
200 *
201 * Les objets sources sont les pivots qui portent les liens
202 * et pour lesquels une table spip_xxx_liens existe
203 * (auteurs, documents, mots)
204 *
205 * un * pour $objet,$id_objet permet de traiter par lot
206 * seul le type de l'objet source ne peut pas accepter de joker et doit etre explicite
207 *
208 * @api
209 * @param array $objets_source
210 * @param array|string $objets_lies
211 * @return int
212 */
213 function objet_optimiser_liens($objets_source, $objets_lies) {
214 return objet_traiter_liaisons('lien_optimise', $objets_source, $objets_lies);
215 }
216
217
218 /**
219 * Dupliquer tous les liens entrant ou sortants d'un objet
220 * vers un autre (meme type d'objet, mais id different)
221 * si $types est fourni, seuls les liens depuis/vers les types listes seront copies
222 * si $exclure_types est fourni, les liens depuis/vers les types listes seront ignores
223 *
224 * @api
225 * @param string $objet
226 * @param int $id_source
227 * @param int $id_cible
228 * @param array $types
229 * @param array $exclure_types
230 * @return int
231 * Nombre de liens copiés
232 */
233 function objet_dupliquer_liens($objet, $id_source, $id_cible, $types = null, $exclure_types = null) {
234 include_spip('base/objets');
235 $tables = lister_tables_objets_sql();
236 $n = 0;
237 foreach ($tables as $table_sql => $infos) {
238 if (
239 (is_null($types) or in_array($infos['type'], $types))
240 and (is_null($exclure_types) or !in_array($infos['type'], $exclure_types))
241 ) {
242 if (objet_associable($infos['type'])) {
243 $liens = (($infos['type'] == $objet) ?
244 objet_trouver_liens(array($objet => $id_source), '*')
245 :
246 objet_trouver_liens(array($infos['type'] => '*'), array($objet => $id_source)));
247 foreach ($liens as $lien) {
248 $n++;
249 if ($infos['type'] == $objet) {
250 if (
251 (is_null($types) or in_array($lien['objet'], $types))
252 and (is_null($exclure_types) or !in_array($lien['objet'], $exclure_types))
253 ) {
254 objet_associer(array($objet => $id_cible), array($lien['objet'] => $lien[$lien['objet']]), $lien);
255 }
256 } else {
257 objet_associer(array($infos['type'] => $lien[$infos['type']]), array($objet => $id_cible), $lien);
258 }
259 }
260 }
261 }
262 }
263
264 return $n;
265 }
266
267 /**
268 * Fonctions techniques
269 * ne pas les appeler directement
270 */
271
272
273 /**
274 * Fonction générique qui
275 * applique une operation de liaison entre un ou des objets et des objets listés
276 *
277 * $objets_source et $objets_lies sont de la forme
278 * array($objet=>$id_objets,...)
279 * $id_objets peut lui meme etre un scalaire ou un tableau pour une liste d'objets du meme type
280 *
281 * Les objets sources sont les pivots qui portent les liens
282 * et pour lesquels une table spip_xxx_liens existe
283 * (auteurs, documents, mots)
284 *
285 * on peut passer optionnellement une qualification du (des) lien(s) qui sera
286 * alors appliquee dans la foulee.
287 * En cas de lot de liens, c'est la meme qualification qui est appliquee a tous
288 *
289 * @internal
290 * @param string $operation
291 * Nom de la fonction PHP qui traitera l'opération
292 * @param array $objets_source
293 * Liste de ou des objets source
294 * De la forme array($objet=>$id_objets,...), où $id_objets peut lui
295 * même être un scalaire ou un tableau pour une liste d'objets du même type
296 * @param array $objets_lies
297 * Liste de ou des objets liés
298 * De la forme array($objet=>$id_objets,...), où $id_objets peut lui
299 * même être un scalaire ou un tableau pour une liste d'objets du même type
300 * @param null|array $set
301 * Liste de coupels champs valeur, soit array(champs => valeur)
302 * En fonction des opérations il peut servir à différentes utilisations
303 * @return bool|int|array
304 */
305 function objet_traiter_liaisons($operation, $objets_source, $objets_lies, $set = null) {
306 // accepter une syntaxe minimale pour supprimer tous les liens
307 if ($objets_lies == '*') {
308 $objets_lies = array('*' => '*');
309 }
310 $modifs = 0; // compter le nombre de modifications
311 $echec = null;
312 foreach ($objets_source as $objet => $ids) {
313 if ($a = objet_associable($objet)) {
314 list($primary, $l) = $a;
315 if (!is_array($ids)) {
316 $ids = array($ids);
317 } elseif (reset($ids) == "NOT") {
318 // si on demande un array('NOT',...) => recuperer la liste d'ids correspondants
319 $where = lien_where($primary, $ids, '*', '*');
320 $ids = sql_allfetsel($primary, $l, $where);
321 $ids = array_map('reset', $ids);
322 }
323 foreach ($ids as $id) {
324 $res = $operation($objet, $primary, $l, $id, $objets_lies, $set);
325 if ($res === false) {
326 spip_log("objet_traiter_liaisons [Echec] : $operation sur $objet/$primary/$l/$id", _LOG_ERREUR);
327 $echec = true;
328 } else {
329 $modifs = ($modifs ? (is_array($res) ? array_merge($modifs, $res) : $modifs + $res) : $res);
330 }
331 }
332 } else {
333 $echec = true;
334 }
335 }
336
337 return ($echec ? false : $modifs); // pas d'erreur
338 }
339
340
341 /**
342 * Sous fonction insertion
343 * qui traite les liens pour un objet source dont la clé primaire
344 * et la table de lien sont fournies
345 *
346 * $objets et de la forme
347 * array($objet=>$id_objets,...)
348 *
349 * Retourne le nombre d'insertions réalisées
350 *
351 * @internal
352 * @param string $objet_source Objet source de l'insertion (celui qui a la table de liaison)
353 * @param string $primary Nom de la clé primaire de cet objet
354 * @param string $table_lien Nom de la table de lien de cet objet
355 * @param int $id Identifiant de l'objet sur lesquels on va insérer des liaisons
356 * @param array $objets Liste des liaisons à faire, de la forme array($objet=>$id_objets)
357 * @param array $qualif
358 * Liste des qualifications à appliquer (qui seront faites par lien_set()),
359 * dont on cherche un rôle à insérer également.
360 * Si l'objet dispose d'un champ rôle, on extrait des qualifications
361 * le rôle s'il est présent, sinon on applique le rôle par défaut.
362 * @return bool|int
363 * Nombre d'insertions faites, false si échec.
364 */
365 function lien_insert($objet_source, $primary, $table_lien, $id, $objets, $qualif) {
366 $ins = 0;
367 $echec = null;
368 if (is_null($qualif)) {
369 $qualif = array();
370 }
371
372 foreach ($objets as $objet => $id_objets) {
373 if (!is_array($id_objets)) {
374 $id_objets = array($id_objets);
375 }
376
377 // role, colonne, where par défaut
378 list($role, $colonne_role, $cond) =
379 roles_trouver_dans_qualif($objet_source, $objet, $qualif);
380
381 foreach ($id_objets as $id_objet) {
382 $objet = ($objet == '*') ? $objet : objet_type($objet); # securite
383
384 $insertions = array(
385 'id_objet' => $id_objet,
386 'objet' => $objet,
387 $primary => $id
388 );
389 // rôle en plus s'il est défini
390 if ($role) {
391 $insertions += array(
392 $colonne_role => $role
393 );
394 }
395 $args = array(
396 'table_lien' => $table_lien,
397 'objet_source' => $objet_source,
398 'id_objet_source' => $id,
399 'objet' => $objet,
400 'id_objet' => $id_objet,
401 'role' => $role,
402 'colonne_role' => $colonne_role,
403 'action' => 'insert',
404 );
405
406 // Envoyer aux plugins
407 $insertions = pipeline('pre_edition_lien',
408 array(
409 'args' => $args,
410 'data' => $insertions
411 )
412 );
413 $args['id_objet'] = $insertions['id_objet'];
414
415 $where = lien_where($primary, $id, $objet, $id_objet, $cond);
416
417 if ($id_objet = intval($insertions['id_objet'])
418 and !sql_getfetsel($primary, $table_lien, $where)
419 ) {
420
421 $e = sql_insertq($table_lien, $insertions);
422 if ($e !== false) {
423 $ins++;
424 lien_propage_date_modif($objet, $id_objet);
425 lien_propage_date_modif($objet_source, $id);
426 // Envoyer aux plugins
427 pipeline('post_edition_lien',
428 array(
429 'args' => $args,
430 'data' => $insertions
431 )
432 );
433 } else {
434 $echec = true;
435 }
436 }
437 }
438 }
439
440 return ($echec ? false : $ins);
441 }
442
443 /**
444 * Fabriquer la condition where en tenant compte des jokers *
445 *
446 * @internal
447 * @param string $primary Nom de la clé primaire
448 * @param int|string|array $id_source Identifiant de la clé primaire
449 * @param string $objet Nom de l'objet lié
450 * @param int|string|array $id_objet Identifiant de l'objet lié
451 * @param array $cond Conditions par défaut
452 * @return array Liste des conditions
453 */
454 function lien_where($primary, $id_source, $objet, $id_objet, $cond = array()) {
455 if ((!is_array($id_source) and !strlen($id_source))
456 or !strlen($objet)
457 or (!is_array($id_objet) and !strlen($id_objet))
458 ) {
459 return array("0=1");
460 } // securite
461
462 $not = "";
463 if (is_array($id_source) and reset($id_source) == "NOT") {
464 $not = array_shift($id_source);
465 $id_source = reset($id_source);
466 }
467
468 $where = $cond;
469
470 if ($id_source !== '*') {
471 $where[] = (is_array($id_source) ? sql_in(addslashes($primary), array_map('intval', $id_source),
472 $not) : addslashes($primary) . ($not ? "<>" : "=") . intval($id_source));
473 } elseif ($not) {
474 $where[] = "0=1";
475 } // idiot mais quand meme
476
477 $not = "";
478 if (is_array($id_objet) and reset($id_objet) == "NOT") {
479 $not = array_shift($id_objet);
480 $id_objet = reset($id_objet);
481 }
482
483 if ($objet !== '*') {
484 $where[] = "objet=" . sql_quote($objet);
485 }
486 if ($id_objet !== '*') {
487 $where[] = (is_array($id_objet) ? sql_in('id_objet', array_map('intval', $id_objet),
488 $not) : "id_objet" . ($not ? "<>" : "=") . intval($id_objet));
489 } elseif ($not) {
490 $where[] = "0=1";
491 } // idiot mais quand meme
492
493 return $where;
494 }
495
496 /**
497 * Sous fonction suppression
498 * qui traite les liens pour un objet source dont la clé primaire
499 * et la table de lien sont fournies
500 *
501 * $objets et de la forme
502 * array($objet=>$id_objets,...)
503 * un * pour $id,$objet,$id_objets permet de traiter par lot
504 *
505 * On supprime tous les liens entre les objets indiqués par défaut,
506 * sauf s'il y a des rôles déclarés entre ces 2 objets, auquel cas on ne
507 * supprime que les liaisons avec le role déclaré par défaut si rien n'est
508 * précisé dans $cond. Il faut alors passer $cond=array('role'=>'*') pour
509 * supprimer tous les roles, ou array('role'=>'un_role') pour un role précis.
510 *
511 * @internal
512 * @param string $objet_source
513 * @param string $primary
514 * @param string $table_lien
515 * @param int $id
516 * @param array $objets
517 * @param array|null $cond
518 * Conditions where par défaut.
519 * Un cas particulier est géré lorsque l'index 'role' est présent (ou absent)
520 * @return bool|int
521 */
522 function lien_delete($objet_source, $primary, $table_lien, $id, $objets, $cond = null) {
523
524 $retire = array();
525 $dels = 0;
526 $echec = false;
527 if (is_null($cond)) {
528 $cond = array();
529 }
530
531 foreach ($objets as $objet => $id_objets) {
532 $objet = ($objet == '*') ? $objet : objet_type($objet); # securite
533 if (!is_array($id_objets) or reset($id_objets) == "NOT") {
534 $id_objets = array($id_objets);
535 }
536 foreach ($id_objets as $id_objet) {
537 list($cond, $colonne_role, $role) = roles_creer_condition_role($objet_source, $objet, $cond);
538 // id_objet peut valoir '*'
539 $where = lien_where($primary, $id, $objet, $id_objet, $cond);
540
541 // lire les liens existants pour propager la date de modif
542 $select = "$primary,id_objet,objet";
543 if ($colonne_role) {
544 $select .= ",$colonne_role";
545 }
546 $liens = sql_allfetsel($select, $table_lien, $where);
547
548 // iterer sur les liens pour permettre aux plugins de gerer
549 foreach ($liens as $l) {
550
551 $args = array(
552 'table_lien' => $table_lien,
553 'objet_source' => $objet_source,
554 'id_objet_source' => $l[$primary],
555 'objet' => $l['objet'],
556 'id_objet' => $l['id_objet'],
557 'colonne_role' => $colonne_role,
558 'role' => ($colonne_role ? $l[$colonne_role] : ''),
559 'action' => 'delete',
560 );
561
562 // Envoyer aux plugins
563 $l = pipeline('pre_edition_lien',
564 array(
565 'args' => $args,
566 'data' => $l
567 )
568 );
569 $args['id_objet'] = $id_o = $l['id_objet'];
570
571 if ($id_o = intval($l['id_objet'])) {
572 $where = lien_where($primary, $l[$primary], $l['objet'], $id_o, $cond);
573 $e = sql_delete($table_lien, $where);
574 if ($e !== false) {
575 $dels += $e;
576 lien_propage_date_modif($l['objet'], $id_o);
577 lien_propage_date_modif($objet_source, $l[$primary]);
578 } else {
579 $echec = true;
580 }
581 $retire[] = array(
582 'source' => array($objet_source => $l[$primary]),
583 'lien' => array($l['objet'] => $id_o),
584 'type' => $l['objet'],
585 'role' => ($colonne_role ? $l[$colonne_role] : ''),
586 'id' => $id_o
587 );
588 // Envoyer aux plugins
589 pipeline('post_edition_lien',
590 array(
591 'args' => $args,
592 'data' => $l
593 )
594 );
595 }
596 }
597 }
598 }
599
600 pipeline('trig_supprimer_objets_lies', $retire);
601
602 return ($echec ? false : $dels);
603 }
604
605
606 /**
607 * Sous fonction optimisation
608 * qui nettoie les liens morts (vers un objet inexistant)
609 * 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 * @internal
617 * @param string $objet_source
618 * @param string $primary
619 * @param string $table_lien
620 * @param int $id
621 * @param array $objets
622 * @return bool|int
623 */
624 function lien_optimise($objet_source, $primary, $table_lien, $id, $objets) {
625 include_spip('genie/optimiser');
626 $echec = false;
627 $dels = 0;
628 foreach ($objets as $objet => $id_objets) {
629 $objet = ($objet == '*') ? $objet : objet_type($objet); # securite
630 if (!is_array($id_objets) or reset($id_objets) == "NOT") {
631 $id_objets = array($id_objets);
632 }
633 foreach ($id_objets as $id_objet) {
634 $where = lien_where($primary, $id, $objet, $id_objet);
635 # les liens vers un objet inexistant
636 $r = sql_select("DISTINCT objet", $table_lien, $where);
637 while ($t = sql_fetch($r)) {
638 $type = $t['objet'];
639 $spip_table_objet = table_objet_sql($type);
640 $id_table_objet = id_table_objet($type);
641 $res = sql_select("L.$primary AS id,L.id_objet",
642 // la condition de jointure inclue L.objet='xxx' pour ne joindre que les bonnes lignes
643 // du coups toutes les lignes avec un autre objet ont un id_xxx=NULL puisque LEFT JOIN
644 // il faut les eliminier en repetant la condition dans le where L.objet='xxx'
645 "$table_lien AS L
646 LEFT JOIN $spip_table_objet AS O
647 ON (O.$id_table_objet=L.id_objet AND L.objet=" . sql_quote($type) . ")",
648 "L.objet=" . sql_quote($type) . " AND O.$id_table_objet IS NULL");
649 // sur une cle primaire composee, pas d'autres solutions que de virer un a un
650 while ($row = sql_fetch($res)) {
651 $e = sql_delete($table_lien,
652 array("$primary=" . $row['id'], "id_objet=" . $row['id_objet'], "objet=" . sql_quote($type)));
653 if ($e != false) {
654 $dels += $e;
655 spip_log("Entree " . $row['id'] . "/" . $row['id_objet'] . "/$type supprimee dans la table $table_lien",
656 _LOG_INFO_IMPORTANTE);
657 }
658 }
659 }
660
661 # les liens depuis un objet inexistant
662 $table_source = table_objet_sql($objet_source);
663 // filtrer selon $id, $objet, $id_objet eventuellement fournis
664 // (en general '*' pour chaque)
665 $where = lien_where("L.$primary", $id, $objet, $id_objet);
666 $where[] = "O.$primary IS NULL";
667 $res = sql_select("L.$primary AS id",
668 "$table_lien AS L LEFT JOIN $table_source AS O ON L.$primary=O.$primary",
669 $where);
670 $dels += optimiser_sansref($table_lien, $primary, $res);
671 }
672 }
673
674 return ($echec ? false : $dels);
675 }
676
677
678 /**
679 * Sous fonction qualification
680 * qui traite les liens pour un objet source dont la clé primaire
681 * et la table de lien sont fournies
682 *
683 * $objets et de la forme
684 * array($objet=>$id_objets,...)
685 * un * pour $id,$objet,$id_objets permet de traiter par lot
686 *
687 * exemple :
688 * $qualif = array('vu'=>'oui');
689 *
690 * @internal
691 * @param string $objet_source Objet source de l'insertion (celui qui a la table de liaison)
692 * @param string $primary Nom de la clé primaire de cet objet
693 * @param string $table_lien Nom de la table de lien de cet objet
694 * @param int $id Identifiant de l'objet sur lesquels on va insérer des liaisons
695 * @param array $objets Liste des liaisons à faire, de la forme array($objet=>$id_objets)
696 * @param array $qualif
697 * Liste des qualifications à appliquer.
698 *
699 * Si l'objet dispose d'un champ rôle, on extrait des qualifications
700 * le rôle s'il est présent, sinon on applique les qualifications
701 * sur le rôle par défaut.
702 * @return bool|int
703 * Nombre de modifications faites, false si échec.
704 */
705 function lien_set($objet_source, $primary, $table_lien, $id, $objets, $qualif) {
706 $echec = null;
707 $ok = 0;
708 if (!$qualif) {
709 return false;
710 }
711 // nettoyer qualif qui peut venir directement d'un objet_trouver_lien :
712 unset($qualif[$primary]);
713 unset($qualif[$objet_source]);
714 if (isset($qualif['objet'])) {
715 unset($qualif[$qualif['objet']]);
716 }
717 unset($qualif['objet']);
718 unset($qualif['id_objet']);
719 foreach ($objets as $objet => $id_objets) {
720
721 // role, colonne, where par défaut
722 list($role, $colonne_role, $cond) =
723 roles_trouver_dans_qualif($objet_source, $objet, $qualif);
724
725 $objet = ($objet == '*') ? $objet : objet_type($objet); # securite
726 if (!is_array($id_objets) or reset($id_objets) == "NOT") {
727 $id_objets = array($id_objets);
728 }
729 foreach ($id_objets as $id_objet) {
730
731 $args = array(
732 'table_lien' => $table_lien,
733 'objet_source' => $objet_source,
734 'id_objet_source' => $id,
735 'objet' => $objet,
736 'id_objet' => $id_objet,
737 'role' => $role,
738 'colonne_role' => $colonne_role,
739 'action' => 'modifier',
740 );
741
742 // Envoyer aux plugins
743 $qualif = pipeline('pre_edition_lien',
744 array(
745 'args' => $args,
746 'data' => $qualif,
747 )
748 );
749 $args['id_objet'] = $id_objet;
750
751 $where = lien_where($primary, $id, $objet, $id_objet, $cond);
752 $e = sql_updateq($table_lien, $qualif, $where);
753
754 if ($e === false) {
755 $echec = true;
756 } else {
757 // Envoyer aux plugins
758 pipeline('post_edition_lien',
759 array(
760 'args' => $args,
761 'data' => $qualif
762 )
763 );
764 $ok++;
765 }
766 }
767 }
768
769 return ($echec ? false : $ok);
770 }
771
772 /**
773 * Sous fonction trouver
774 * qui cherche les liens pour un objet source dont la clé primaire
775 * et la table de lien sont fournies
776 *
777 * $objets et de la forme
778 * array($objet=>$id_objets,...)
779 * un * pour $id,$objet,$id_objets permet de traiter par lot
780 *
781 * Le tableau de condition peut avoir un index 'role' indiquant de
782 * chercher un rôle précis, ou * pour tous les roles (alors équivalent
783 * à l'absence de l'index)
784 *
785 * @internal
786 * @param string $objet_source
787 * @param string $primary
788 * @param string $table_lien
789 * @param int $id
790 * @param array $objets
791 * @param array|null $cond
792 * Condition du where par défaut
793 *
794 * On peut passer un index 'role' pour sélectionner uniquement
795 * le role défini dedans (et '*' pour tous les rôles).
796 * @return array
797 */
798 function lien_find($objet_source, $primary, $table_lien, $id, $objets, $cond = null) {
799 $trouve = array();
800 foreach ($objets as $objet => $id_objets) {
801 $objet = ($objet == '*') ? $objet : objet_type($objet); # securite
802 // gerer les roles s'il y en a dans $cond
803 list($cond) = roles_creer_condition_role($objet_source, $objet, $cond, true);
804 // lien_where prend en charge les $id_objets sous forme int ou array
805 $where = lien_where($primary, $id, $objet, $id_objets, $cond);
806 $liens = sql_allfetsel('*', $table_lien, $where);
807 // ajouter les entrees objet_source et objet cible par convenance
808 foreach ($liens as $l) {
809 $l[$objet_source] = $l[$primary];
810 $l[$l['objet']] = $l['id_objet'];
811 $trouve[] = $l;
812 }
813 }
814
815 return $trouve;
816 }
817
818 /**
819 * Propager la date_modif sur les objets dont un lien a été modifié
820 *
821 * @internal
822 * @param string $objet
823 * @param array|int $ids
824 */
825 function lien_propage_date_modif($objet, $ids) {
826 static $done = array();
827 $hash = md5($objet . serialize($ids));
828
829 // sql_updateq, peut être un rien lent.
830 // On évite de l'appeler 2 fois sur les mêmes choses
831 if (isset($done[$hash])) {
832 return;
833 }
834
835 $trouver_table = charger_fonction('trouver_table', 'base');
836
837 $table = table_objet_sql($objet);
838 if ($desc = $trouver_table($table)
839 and isset($desc['field']['date_modif'])
840 ) {
841 $primary = id_table_objet($objet);
842 $where = (is_array($ids) ? sql_in($primary, array_map('intval', $ids)) : "$primary=" . intval($ids));
843 sql_updateq($table, array('date_modif' => date('Y-m-d H:i:s')), $where);
844 }
845
846 $done[$hash] = true;
847 }