Completed
Push — master ( 2209cc...e2d0e1 )
by cam
05:55
created

editer_liens.php ➔ lien_set()   F

Complexity

Conditions 15
Paths 361

Size

Total Lines 85

Duplication

Lines 1
Ratio 1.18 %

Importance

Changes 0
Metric Value
cc 15
nc 361
nop 6
dl 1
loc 85
rs 2.7839
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[]|false.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
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
0 ignored issues
show
Documentation introduced by
Should the type for parameter $qualif not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
87
 * @return bool|int
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|integer|array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
88
 */
89
function objet_associer($objets_source, $objets_lies, $qualif = null) {
90
	$modifs = objet_traiter_liaisons('lien_insert', $objets_source, $objets_lies, $qualif);
0 ignored issues
show
Bug introduced by
It seems like $objets_lies defined by parameter $objets_lies on line 89 can also be of type string; however, objet_traiter_liaisons() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
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
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|integer|array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
128
 */
129
function objet_dissocier($objets_source, $objets_lies, $cond = null) {
130
	return objet_traiter_liaisons('lien_delete', $objets_source, $objets_lies, $cond);
0 ignored issues
show
Bug introduced by
It seems like $objets_lies defined by parameter $objets_lies on line 129 can also be of type string; however, objet_traiter_liaisons() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
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
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|integer|array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
153
 */
154
function objet_qualifier_liens($objets_source, $objets_lies, $qualif) {
155
	return objet_traiter_liaisons('lien_set', $objets_source, $objets_lies, $qualif);
0 ignored issues
show
Bug introduced by
It seems like $objets_lies defined by parameter $objets_lies on line 154 can also be of type string; however, objet_traiter_liaisons() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
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
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|integer|array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $objets_lies defined by parameter $objets_lies on line 189 can also be of type string; however, objet_traiter_liaisons() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
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
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|integer|array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
212
 */
213
function objet_optimiser_liens($objets_source, $objets_lies) {
214
	return objet_traiter_liaisons('lien_optimise', $objets_source, $objets_lies);
0 ignored issues
show
Bug introduced by
It seems like $objets_lies defined by parameter $objets_lies on line 213 can also be of type string; however, objet_traiter_liaisons() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
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
0 ignored issues
show
Documentation introduced by
Should the type for parameter $types not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
229
 * @param array $exclure_types
0 ignored issues
show
Documentation introduced by
Should the type for parameter $exclure_types not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
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) {
0 ignored issues
show
Bug introduced by
The expression $tables of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
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) {
0 ignored issues
show
Bug introduced by
The expression $liens of type boolean|integer|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
248
					$n++;
249
					if ($infos['type'] == $objet) {
250
						objet_associer(array($objet => $id_cible), array($lien['objet'] => $lien[$lien['objet']]), $lien);
251
					} else {
252
						objet_associer(array($infos['type'] => $lien[$infos['type']]), array($objet => $id_cible), $lien);
253
					}
254
				}
255
			}
256
		}
257
	}
258
259
	return $n;
260
}
261
262
/**
263
 * Fonctions techniques
264
 * ne pas les appeler directement
265
 */
266
267
268
/**
269
 * Fonction générique qui
270
 * applique une operation de liaison entre un ou des objets et des objets listés
271
 *
272
 * $objets_source et $objets_lies sont de la forme
273
 * array($objet=>$id_objets,...)
274
 * $id_objets peut lui meme etre un scalaire ou un tableau pour une liste d'objets du meme type
275
 *
276
 * Les objets sources sont les pivots qui portent les liens
277
 * et pour lesquels une table spip_xxx_liens existe
278
 * (auteurs, documents, mots)
279
 *
280
 * on peut passer optionnellement une qualification du (des) lien(s) qui sera
281
 * alors appliquee dans la foulee.
282
 * En cas de lot de liens, c'est la meme qualification qui est appliquee a tous
283
 *
284
 * @internal
285
 * @param string $operation
286
 *     Nom de la fonction PHP qui traitera l'opération
287
 * @param array $objets_source
288
 *     Liste de ou des objets source
289
 *     De la forme array($objet=>$id_objets,...), où $id_objets peut lui
290
 *     même être un scalaire ou un tableau pour une liste d'objets du même type
291
 * @param array $objets_lies
292
 *     Liste de ou des objets liés
293
 *     De la forme array($objet=>$id_objets,...), où $id_objets peut lui
294
 *     même être un scalaire ou un tableau pour une liste d'objets du même type
295
 * @param null|array $set
296
 *     Liste de coupels champs valeur, soit array(champs => valeur)
297
 *     En fonction des opérations il peut servir à différentes utilisations
298
 * @return bool|int|array
299
 */
300
function objet_traiter_liaisons($operation, $objets_source, $objets_lies, $set = null) {
301
	// accepter une syntaxe minimale pour supprimer tous les liens
302
	if ($objets_lies == '*') {
303
		$objets_lies = array('*' => '*');
304
	}
305
	$modifs = 0; // compter le nombre de modifications
306
	$echec = null;
307
	foreach ($objets_source as $objet => $ids) {
308
		if ($a = objet_associable($objet)) {
309
			list($primary, $l) = $a;
310
			if (!is_array($ids)) {
311
				$ids = array($ids);
312
			} elseif (reset($ids) == "NOT") {
313
				// si on demande un array('NOT',...) => recuperer la liste d'ids correspondants
314
				$where = lien_where($primary, $ids, '*', '*');
315
				$ids = sql_allfetsel($primary, $l, $where);
316
				$ids = array_map('reset', $ids);
317
			}
318
			foreach ($ids as $id) {
319
				$res = $operation($objet, $primary, $l, $id, $objets_lies, $set);
320
				if ($res === false) {
321
					spip_log("objet_traiter_liaisons [Echec] : $operation sur $objet/$primary/$l/$id", _LOG_ERREUR);
322
					$echec = true;
323
				} else {
324
					$modifs = ($modifs ? (is_array($res) ? array_merge($modifs, $res) : $modifs + $res) : $res);
325
				}
326
			}
327
		} else {
328
			$echec = true;
329
		}
330
	}
331
332
	return ($echec ? false : $modifs); // pas d'erreur
333
}
334
335
336
/**
337
 * Sous fonction insertion
338
 * qui traite les liens pour un objet source dont la clé primaire
339
 * et la table de lien sont fournies
340
 *
341
 * $objets et de la forme
342
 * array($objet=>$id_objets,...)
343
 *
344
 * Retourne le nombre d'insertions réalisées
345
 *
346
 * @internal
347
 * @param string $objet_source Objet source de l'insertion (celui qui a la table de liaison)
348
 * @param string $primary Nom de la clé primaire de cet objet
349
 * @param string $table_lien Nom de la table de lien de cet objet
350
 * @param int $id Identifiant de l'objet sur lesquels on va insérer des liaisons
351
 * @param array $objets Liste des liaisons à faire, de la forme array($objet=>$id_objets)
352
 * @param array $qualif
353
 *     Liste des qualifications à appliquer (qui seront faites par lien_set()),
354
 *     dont on cherche un rôle à insérer également.
355
 *     Si l'objet dispose d'un champ rôle, on extrait des qualifications
356
 *     le rôle s'il est présent, sinon on applique le rôle par défaut.
357
 * @return bool|int
358
 *     Nombre d'insertions faites, false si échec.
359
 */
360
function lien_insert($objet_source, $primary, $table_lien, $id, $objets, $qualif) {
361
	$ins = 0;
362
	$echec = null;
363
	if (is_null($qualif)) {
364
		$qualif = array();
365
	}
366
367
	foreach ($objets as $objet => $id_objets) {
368
		if (!is_array($id_objets)) {
369
			$id_objets = array($id_objets);
370
		}
371
372
		// role, colonne, where par défaut
373
		list($role, $colonne_role, $cond) =
374
			roles_trouver_dans_qualif($objet_source, $objet, $qualif);
375
376
		foreach ($id_objets as $id_objet) {
377
			$objet = (($objet == '*') ? $objet : objet_type($objet)); # securite
378
379
			$insertions = array(
380
				'id_objet' => $id_objet,
381
				'objet' => $objet,
382
				$primary => $id
383
			);
384
			// rôle en plus s'il est défini
385
			if ($role) {
386
				$insertions += array(
387
					$colonne_role => $role
388
				);
389
			}
390
391
			if (lien_triables($table_lien)) {
392
				$where = lien_where($primary, $id, $objet, $id_objet);
393
				// si il y a deja un lien pour ce couple (avec un autre role?) on reprend le meme rang si non nul
394
				if (!$rang = intval(sql_getfetsel('rang_lien', $table_lien, $where))) {
395
					$where = lien_where($primary, '*', $objet, $id_objet);
396
					$rang = intval(sql_getfetsel('max(rang_lien)', $table_lien, $where));
397
					// si aucun lien n'a de rang, on en introduit pas, on garde zero
398
					if ($rang>0) {
399
						$rang = intval($rang) + 1;
400
					}
401
				}
402
				$insertions['rang_lien'] = $rang;
403
			}
404
405
			$args = array(
406
				'table_lien' => $table_lien,
407
				'objet_source' => $objet_source,
408
				'id_objet_source' => $id,
409
				'objet' => $objet,
410
				'id_objet' => $id_objet,
411
				'role' => $role,
412
				'colonne_role' => $colonne_role,
413
				'action' => 'insert',
414
			);
415
416
			// Envoyer aux plugins
417
			$insertions = pipeline('pre_edition_lien',
418
				array(
419
					'args' => $args,
420
					'data' => $insertions
421
				)
422
			);
423
			$args['id_objet'] = $insertions['id_objet'];
424
425
			$where = lien_where($primary, $id, $objet, $id_objet, $cond);
426
427
			if ($id_objet = intval($insertions['id_objet'])
428
				and !sql_getfetsel($primary, $table_lien, $where)
429
			) {
430
431
				if (lien_triables($table_lien) and isset($insertions['rang_lien']) and intval($insertions['rang_lien'])) {
432
					// on decale les liens de rang_lien>=la valeur inseree pour faire la place
433
					$w = lien_where($primary, '*', $objet, $id_objet, array('rang_lien>='.intval($insertions['rang_lien']),"$primary!=".intval($id)));
434
					sql_update($table_lien, array('rang_lien'=>'rang_lien+1'), $w);
435
				}
436
437
				$e = sql_insertq($table_lien, $insertions);
438
				if ($e !== false) {
439
					$ins++;
440
					lien_propage_date_modif($objet, $id_objet);
441
					lien_propage_date_modif($objet_source, $id);
442
					// Envoyer aux plugins
443
					pipeline('post_edition_lien',
444
						array(
445
							'args' => $args,
446
							'data' => $insertions
447
						)
448
					);
449
				} else {
450
					$echec = true;
451
				}
452
			}
453
		}
454
	}
455
	// si on a fait des insertions, on reordonne les liens concernes
456
	if ($ins>0) {
457
		lien_ordonner($objet_source, $primary, $table_lien, $id, $objets);
458
	}
459
460
	return ($echec ? false : $ins);
461
}
462
463
464
/**
465
 * Reordonner les liens sur lesquels on est intervenus
466
 * @param string $objet_source
467
 * @param string $primary
468
 * @param string $table_lien
469
 * @param int $id
470
 * @param array|string $objets
471
 */
472
function lien_ordonner($objet_source, $primary, $table_lien, $id, $objets) {
0 ignored issues
show
Unused Code introduced by
The parameter $objet_source is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
473
	if (!lien_triables($table_lien)) {
474
		return;
475
	}
476
477
	foreach ($objets as $objet => $id_objets) {
0 ignored issues
show
Bug introduced by
The expression $objets of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
478
		if (!is_array($id_objets)) {
479
			$id_objets = array($id_objets);
480
		}
481
482
		foreach ($id_objets as $id_objet) {
483
			$objet = (($objet == '*') ? $objet : objet_type($objet)); # securite
484
485
			$where = lien_where($primary, '*', $objet, $id_objet);
486
			$liens = sql_allfetsel("$primary, id_objet, objet, rang_lien", $table_lien, $where, $primary,"rang_lien");
487
488
			$rangs = array_column($liens, 'rang_lien');
489
			if (count($rangs) and (max($rangs)>0 or min($rangs)<0)) {
490
				$rang = 1;
491
				foreach ($liens as $lien) {
492
					$where = lien_where($primary, $lien[$primary], $objet, $id_objet, array('rang_lien!='.intval($rang)));
493
					sql_updateq($table_lien, array('rang_lien' => $rang), $where);
494
					$rang++;
495
				}
496
			}
497
		}
498
	}
499
}
500
501
502
/**
503
 * Une table de lien est-elle triable ?
504
 * elle doit disposer d'un champ rang_lien pour cela
505
 * @param $table_lien
506
 * @return mixed
507
 */
508
function lien_triables($table_lien) {
509
	static $triables = array();
510
	if (!isset($triables[$table_lien])) {
511
		$trouver_table = charger_fonction('trouver_table', 'base');
512
		$desc = $trouver_table($table_lien);
513
		if ($desc and isset($desc['field']['rang_lien'])) {
514
			$triables[$table_lien] = true;
515
		}
516
		else {
517
			$triables[$table_lien] = false;
518
		}
519
	}
520
	return $triables[$table_lien];
521
}
522
523
524
/**
525
 * Fabriquer la condition where en tenant compte des jokers *
526
 *
527
 * @internal
528
 * @param string $primary Nom de la clé primaire
529
 * @param int|string|array $id_source Identifiant de la clé primaire
530
 * @param string $objet Nom de l'objet lié
531
 * @param int|string|array $id_objet Identifiant de l'objet lié
532
 * @param array $cond Conditions par défaut
533
 * @return array                        Liste des conditions
534
 */
535
function lien_where($primary, $id_source, $objet, $id_objet, $cond = array()) {
536
	if ((!is_array($id_source) and !strlen($id_source))
537
		or !strlen($objet)
538
		or (!is_array($id_objet) and !strlen($id_objet))
539
	) {
540
		return array("0=1");
541
	} // securite
542
543
	$not = "";
544
	if (is_array($id_source) and reset($id_source) == "NOT") {
545
		$not = array_shift($id_source);
546
		$id_source = reset($id_source);
547
	}
548
549
	$where = $cond;
550
551
	if ($id_source !== '*') {
552
		$where[] = (is_array($id_source) ? sql_in(addslashes($primary), array_map('intval', $id_source),
553
			$not) : addslashes($primary) . ($not ? "<>" : "=") . intval($id_source));
554
	} elseif ($not) {
555
		$where[] = "0=1";
556
	} // idiot mais quand meme
557
558
	$not = "";
559
	if (is_array($id_objet) and reset($id_objet) == "NOT") {
560
		$not = array_shift($id_objet);
561
		$id_objet = reset($id_objet);
562
	}
563
564
	if ($objet !== '*') {
565
		$where[] = "objet=" . sql_quote($objet);
566
	}
567
	if ($id_objet !== '*') {
568
		$where[] = (is_array($id_objet) ? sql_in('id_objet', array_map('intval', $id_objet),
569
			$not) : "id_objet" . ($not ? "<>" : "=") . intval($id_objet));
570
	} elseif ($not) {
571
		$where[] = "0=1";
572
	} // idiot mais quand meme
573
574
	return $where;
575
}
576
577
/**
578
 * Sous fonction suppression
579
 * qui traite les liens pour un objet source dont la clé primaire
580
 * et la table de lien sont fournies
581
 *
582
 * $objets et de la forme
583
 * array($objet=>$id_objets,...)
584
 * un * pour $id,$objet,$id_objets permet de traiter par lot
585
 *
586
 * On supprime tous les liens entre les objets indiqués par défaut,
587
 * sauf s'il y a des rôles déclarés entre ces 2 objets, auquel cas on ne
588
 * supprime que les liaisons avec le role déclaré par défaut si rien n'est
589
 * précisé dans $cond. Il faut alors passer $cond=array('role'=>'*') pour
590
 * supprimer tous les roles, ou array('role'=>'un_role') pour un role précis.
591
 *
592
 * @internal
593
 * @param string $objet_source
594
 * @param string $primary
595
 * @param string $table_lien
596
 * @param int $id
597
 * @param array $objets
598
 * @param array|null $cond
599
 *     Conditions where par défaut.
600
 *     Un cas particulier est géré lorsque l'index 'role' est présent (ou absent)
601
 * @return bool|int
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
602
 */
603
function lien_delete($objet_source, $primary, $table_lien, $id, $objets, $cond = null) {
604
605
	$retire = array();
606
	$dels = 0;
607
	$echec = false;
608
	if (is_null($cond)) {
609
		$cond = array();
610
	}
611
612
	foreach ($objets as $objet => $id_objets) {
613
		$objet = ($objet == '*') ? $objet : objet_type($objet); # securite
614 View Code Duplication
		if (!is_array($id_objets) or reset($id_objets) == "NOT") {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
615
			$id_objets = array($id_objets);
616
		}
617
		foreach ($id_objets as $id_objet) {
618
			list($cond, $colonne_role, $role) = roles_creer_condition_role($objet_source, $objet, $cond);
0 ignored issues
show
Unused Code introduced by
The assignment to $role is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
619
			// id_objet peut valoir '*'
620
			$where = lien_where($primary, $id, $objet, $id_objet, $cond);
621
622
			// lire les liens existants pour propager la date de modif
623
			$select = "$primary,id_objet,objet";
624
			if ($colonne_role) {
625
				$select .= ",$colonne_role";
626
			}
627
			$liens = sql_allfetsel($select, $table_lien, $where);
628
629
			// iterer sur les liens pour permettre aux plugins de gerer
630
			foreach ($liens as $l) {
631
632
				$args = array(
633
					'table_lien' => $table_lien,
634
					'objet_source' => $objet_source,
635
					'id_objet_source' => $l[$primary],
636
					'objet' => $l['objet'],
637
					'id_objet' => $l['id_objet'],
638
					'colonne_role' => $colonne_role,
639
					'role' => ($colonne_role ? $l[$colonne_role] : ''),
640
					'action' => 'delete',
641
				);
642
643
				// Envoyer aux plugins
644
				$l = pipeline('pre_edition_lien',
645
					array(
646
						'args' => $args,
647
						'data' => $l
648
					)
649
				);
650
				$args['id_objet'] = $id_o = $l['id_objet'];
0 ignored issues
show
Unused Code introduced by
$id_o is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
651
652
				if ($id_o = intval($l['id_objet'])) {
653
					$where = lien_where($primary, $l[$primary], $l['objet'], $id_o, $cond);
654
					$e = sql_delete($table_lien, $where);
655
					if ($e !== false) {
656
						$dels += $e;
657
						lien_propage_date_modif($l['objet'], $id_o);
658
						lien_propage_date_modif($objet_source, $l[$primary]);
659
					} else {
660
						$echec = true;
661
					}
662
					$retire[] = array(
663
						'source' => array($objet_source => $l[$primary]),
664
						'lien' => array($l['objet'] => $id_o),
665
						'type' => $l['objet'],
666
						'role' => ($colonne_role ? $l[$colonne_role] : ''),
667
						'id' => $id_o
668
					);
669
					// Envoyer aux plugins
670
					pipeline('post_edition_lien',
671
						array(
672
							'args' => $args,
673
							'data' => $l
674
						)
675
					);
676
				}
677
			}
678
		}
679
	}
680
	// si on a supprime des liens, on reordonne les liens concernes
681
	if ($dels) {
682
		lien_ordonner($objet_source, $primary, $table_lien, $id, $objets);
683
	}
684
685
	pipeline('trig_supprimer_objets_lies', $retire);
686
687
	return ($echec ? false : $dels);
688
}
689
690
691
/**
692
 * Sous fonction optimisation
693
 * qui nettoie les liens morts (vers un objet inexistant)
694
 * pour un objet source dont la clé primaire
695
 * et la table de lien sont fournies
696
 *
697
 * $objets et de la forme
698
 * array($objet=>$id_objets,...)
699
 * un * pour $id,$objet,$id_objets permet de traiter par lot
700
 *
701
 * @internal
702
 * @param string $objet_source
703
 * @param string $primary
704
 * @param string $table_lien
705
 * @param int $id
706
 * @param array $objets
707
 * @return bool|int
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
708
 */
709
function lien_optimise($objet_source, $primary, $table_lien, $id, $objets) {
710
	include_spip('genie/optimiser');
711
	$echec = false;
712
	$dels = 0;
713
	foreach ($objets as $objet => $id_objets) {
714
		$objet = ($objet == '*') ? $objet : objet_type($objet); # securite
715 View Code Duplication
		if (!is_array($id_objets) or reset($id_objets) == "NOT") {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
716
			$id_objets = array($id_objets);
717
		}
718
		foreach ($id_objets as $id_objet) {
719
			$where = lien_where($primary, $id, $objet, $id_objet);
720
			# les liens vers un objet inexistant
721
			$r = sql_select("DISTINCT objet", $table_lien, $where);
722
			while ($t = sql_fetch($r)) {
723
				$type = $t['objet'];
724
				$spip_table_objet = table_objet_sql($type);
725
				$id_table_objet = id_table_objet($type);
726
				$res = sql_select("L.$primary AS id,L.id_objet",
727
					// la condition de jointure inclue L.objet='xxx' pour ne joindre que les bonnes lignes
728
					// du coups toutes les lignes avec un autre objet ont un id_xxx=NULL puisque LEFT JOIN
729
					// il faut les eliminier en repetant la condition dans le where L.objet='xxx'
730
					"$table_lien AS L
731
									LEFT JOIN $spip_table_objet AS O
732
										ON (O.$id_table_objet=L.id_objet AND L.objet=" . sql_quote($type) . ")",
733
					"L.objet=" . sql_quote($type) . " AND O.$id_table_objet IS NULL");
734
				// sur une cle primaire composee, pas d'autres solutions que de virer un a un
735
				while ($row = sql_fetch($res)) {
736
					$e = sql_delete($table_lien,
737
						array("$primary=" . $row['id'], "id_objet=" . $row['id_objet'], "objet=" . sql_quote($type)));
738
					if ($e != false) {
739
						$dels += $e;
740
						spip_log("Entree " . $row['id'] . "/" . $row['id_objet'] . "/$type supprimee dans la table $table_lien",
741
							_LOG_INFO_IMPORTANTE);
742
					}
743
				}
744
			}
745
746
			# les liens depuis un objet inexistant
747
			$table_source = table_objet_sql($objet_source);
748
			// filtrer selon $id, $objet, $id_objet eventuellement fournis
749
			// (en general '*' pour chaque)
750
			$where = lien_where("L.$primary", $id, $objet, $id_objet);
751
			$where[] = "O.$primary IS NULL";
752
			$res = sql_select("L.$primary AS id",
753
				"$table_lien AS L LEFT JOIN $table_source AS O ON L.$primary=O.$primary",
754
				$where);
755
			$dels += optimiser_sansref($table_lien, $primary, $res);
756
		}
757
	}
758
759
	return ($echec ? false : $dels);
760
}
761
762
763
/**
764
 * Sous fonction qualification
765
 * qui traite les liens pour un objet source dont la clé primaire
766
 * et la table de lien sont fournies
767
 *
768
 * $objets et de la forme
769
 * array($objet=>$id_objets,...)
770
 * un * pour $id,$objet,$id_objets permet de traiter par lot
771
 *
772
 * exemple :
773
 * $qualif = array('vu'=>'oui');
774
 *
775
 * @internal
776
 * @param string $objet_source Objet source de l'insertion (celui qui a la table de liaison)
777
 * @param string $primary Nom de la clé primaire de cet objet
778
 * @param string $table_lien Nom de la table de lien de cet objet
779
 * @param int $id Identifiant de l'objet sur lesquels on va insérer des liaisons
780
 * @param array $objets Liste des liaisons à faire, de la forme array($objet=>$id_objets)
781
 * @param array $qualif
782
 *     Liste des qualifications à appliquer.
783
 *
784
 *     Si l'objet dispose d'un champ rôle, on extrait des qualifications
785
 *     le rôle s'il est présent, sinon on applique les qualifications
786
 *     sur le rôle par défaut.
787
 * @return bool|int
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|integer.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
788
 *     Nombre de modifications faites, false si échec.
789
 */
790
function lien_set($objet_source, $primary, $table_lien, $id, $objets, $qualif) {
791
	$echec = null;
792
	$ok = 0;
793
	$reordonner = false;
794
	if (!$qualif) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $qualif of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
795
		return false;
796
	}
797
	// nettoyer qualif qui peut venir directement d'un objet_trouver_lien :
798
	unset($qualif[$primary]);
799
	unset($qualif[$objet_source]);
800
	if (isset($qualif['objet'])) {
801
		unset($qualif[$qualif['objet']]);
802
	}
803
	unset($qualif['objet']);
804
	unset($qualif['id_objet']);
805
	foreach ($objets as $objet => $id_objets) {
806
807
		// role, colonne, where par défaut
808
		list($role, $colonne_role, $cond) =
809
			roles_trouver_dans_qualif($objet_source, $objet, $qualif);
810
811
		$objet = ($objet == '*') ? $objet : objet_type($objet); # securite
812 View Code Duplication
		if (!is_array($id_objets) or reset($id_objets) == "NOT") {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
813
			$id_objets = array($id_objets);
814
		}
815
		foreach ($id_objets as $id_objet) {
816
817
			$args = array(
818
				'table_lien' => $table_lien,
819
				'objet_source' => $objet_source,
820
				'id_objet_source' => $id,
821
				'objet' => $objet,
822
				'id_objet' => $id_objet,
823
				'role' => $role,
824
				'colonne_role' => $colonne_role,
825
				'action' => 'modifier',
826
			);
827
828
			// Envoyer aux plugins
829
			$qualif = pipeline('pre_edition_lien',
830
				array(
831
					'args' => $args,
832
					'data' => $qualif,
833
				)
834
			);
835
			$args['id_objet'] = $id_objet;
836
837
			if (lien_triables($table_lien) and isset($qualif['rang_lien'])) {
838
				if (intval($qualif['rang_lien'])) {
839
					// on decale les liens de rang_lien>=la valeur inseree pour faire la place
840
					$w = lien_where($primary, '*', $objet, $id_objet, array('rang_lien>='.intval($qualif['rang_lien']),"$primary!=".intval($id)));
841
					sql_update($table_lien, array('rang_lien'=>'rang_lien+1'), $w);
842
				}
843
				// tous les liens de même rôle recoivent le rang indiqué aussi
844
				if (roles_colonne($objet_source, $objet)) {
845
					$w = lien_where($primary, $id, $objet, $id_objet);
846
					sql_updateq($table_lien, array('rang_lien' => intval($qualif['rang_lien'])), $w);
847
				}
848
				$reordonner = true;
849
			}
850
851
			$where = lien_where($primary, $id, $objet, $id_objet, $cond);
852
			$e = sql_updateq($table_lien, $qualif, $where);
853
854
			if ($e === false) {
855
				$echec = true;
856
			} else {
857
				// Envoyer aux plugins
858
				pipeline('post_edition_lien',
859
					array(
860
						'args' => $args,
861
						'data' => $qualif
862
					)
863
				);
864
				$ok++;
865
			}
866
		}
867
	}
868
	// si on a fait des modif de rang, on reordonne les liens concernes
869
	if ($reordonner) {
870
		lien_ordonner($objet_source, $primary, $table_lien, $id, $objets);
871
	}
872
873
	return ($echec ? false : $ok);
874
}
875
876
/**
877
 * Sous fonction trouver
878
 * qui cherche les liens pour un objet source dont la clé primaire
879
 * et la table de lien sont fournies
880
 *
881
 * $objets et de la forme
882
 * array($objet=>$id_objets,...)
883
 * un * pour $id,$objet,$id_objets permet de traiter par lot
884
 *
885
 * Le tableau de condition peut avoir un index 'role' indiquant de
886
 * chercher un rôle précis, ou * pour tous les roles (alors équivalent
887
 * à l'absence de l'index)
888
 *
889
 * @internal
890
 * @param string $objet_source
891
 * @param string $primary
892
 * @param string $table_lien
893
 * @param int $id
894
 * @param array $objets
895
 * @param array|null $cond
896
 *     Condition du where par défaut
897
 *
898
 *     On peut passer un index 'role' pour sélectionner uniquement
899
 *     le role défini dedans (et '*' pour tous les rôles).
900
 * @return array
901
 */
902
function lien_find($objet_source, $primary, $table_lien, $id, $objets, $cond = null) {
903
	$trouve = array();
904
	foreach ($objets as $objet => $id_objets) {
905
		$objet = ($objet == '*') ? $objet : objet_type($objet); # securite
906
		// gerer les roles s'il y en a dans $cond
907
		list($cond) = roles_creer_condition_role($objet_source, $objet, $cond, true);
908
		// lien_where prend en charge les $id_objets sous forme int ou array
909
		$where = lien_where($primary, $id, $objet, $id_objets, $cond);
910
		$liens = sql_allfetsel('*', $table_lien, $where);
911
		// ajouter les entrees objet_source et objet cible par convenance
912
		foreach ($liens as $l) {
913
			$l[$objet_source] = $l[$primary];
914
			$l[$objet] = $l['id_objet'];
915
			$trouve[] = $l;
916
		}
917
	}
918
919
	return $trouve;
920
}
921
922
/**
923
 * Propager la date_modif sur les objets dont un lien a été modifié
924
 *
925
 * @internal
926
 * @param string $objet
927
 * @param array|int $ids
928
 */
929
function lien_propage_date_modif($objet, $ids) {
930
	static $done = array();
931
	$hash = md5($objet . serialize($ids));
932
933
	// sql_updateq, peut être un rien lent.
934
	// On évite de l'appeler 2 fois sur les mêmes choses
935
	if (isset($done[$hash])) {
936
		return;
937
	}
938
939
	$trouver_table = charger_fonction('trouver_table', 'base');
940
941
	$table = table_objet_sql($objet);
942
	if ($desc = $trouver_table($table)
943
		and isset($desc['field']['date_modif'])
944
	) {
945
		$primary = id_table_objet($objet);
946
		$where = (is_array($ids) ? sql_in($primary, array_map('intval', $ids)) : "$primary=" . intval($ids));
947
		sql_updateq($table, array('date_modif' => date('Y-m-d H:i:s')), $where);
948
	}
949
950
	$done[$hash] = true;
951
}
952