Completed
Push — master ( 0360e6...5191b0 )
by cam
04:33
created

editer_liens.php ➔ lien_optimise()   C

Complexity

Conditions 13
Paths 50

Size

Total Lines 55

Duplication

Lines 1
Ratio 1.82 %

Importance

Changes 0
Metric Value
cc 13
nc 50
nop 5
dl 1
loc 55
rs 6.6166
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
						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
396
			if (lien_triables($table_lien)) {
397
				$where = lien_where($primary, $id, $objet, $id_objet);
398
				// si il y a deja un lien pour ce couple (avec un autre role?) on reprend le meme rang si non nul
399
				if (!$rang = intval(sql_getfetsel('rang_lien', $table_lien, $where))) {
400
					$where = lien_where($primary, '*', $objet, $id_objet);
401
					$rang = intval(sql_getfetsel('max(rang_lien)', $table_lien, $where));
402
					// si aucun lien n'a de rang, on en introduit pas, on garde zero
403
					if ($rang>0) {
404
						$rang = intval($rang) + 1;
405
					}
406
				}
407
				$insertions['rang_lien'] = $rang;
408
			}
409
410
			$args = array(
411
				'table_lien' => $table_lien,
412
				'objet_source' => $objet_source,
413
				'id_objet_source' => $id,
414
				'objet' => $objet,
415
				'id_objet' => $id_objet,
416
				'role' => $role,
417
				'colonne_role' => $colonne_role,
418
				'action' => 'insert',
419
			);
420
421
			// Envoyer aux plugins
422
			$insertions = pipeline('pre_edition_lien',
423
				array(
424
					'args' => $args,
425
					'data' => $insertions
426
				)
427
			);
428
			$args['id_objet'] = $insertions['id_objet'];
429
430
			$where = lien_where($primary, $id, $objet, $id_objet, $cond);
431
432
			if (($id_objet = intval($insertions['id_objet']) or $objet=='site')
433
				and !sql_getfetsel($primary, $table_lien, $where)
434
			) {
435
436
				if (lien_triables($table_lien) and isset($insertions['rang_lien']) and intval($insertions['rang_lien'])) {
437
					// on decale les liens de rang_lien>=la valeur inseree pour faire la place
438
					$w = lien_where($primary, '*', $objet, $id_objet, array('rang_lien>='.intval($insertions['rang_lien']),"$primary!=".intval($id)));
439
					sql_update($table_lien, array('rang_lien'=>'rang_lien+1'), $w);
440
				}
441
442
				$e = sql_insertq($table_lien, $insertions);
443
				if ($e !== false) {
444
					$ins++;
445
					lien_propage_date_modif($objet, $id_objet);
446
					lien_propage_date_modif($objet_source, $id);
447
					// Envoyer aux plugins
448
					pipeline('post_edition_lien',
449
						array(
450
							'args' => $args,
451
							'data' => $insertions
452
						)
453
					);
454
				} else {
455
					$echec = true;
456
				}
457
			}
458
		}
459
	}
460
	// si on a fait des insertions, on reordonne les liens concernes
461
	if ($ins>0) {
462
		lien_ordonner($objet_source, $primary, $table_lien, $id, $objets);
463
	}
464
465
	return ($echec ? false : $ins);
466
}
467
468
469
/**
470
 * Reordonner les liens sur lesquels on est intervenus
471
 * @param string $objet_source
472
 * @param string $primary
473
 * @param string $table_lien
474
 * @param int $id
475
 * @param array|string $objets
476
 */
477
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...
478
	if (!lien_triables($table_lien)) {
479
		return;
480
	}
481
482
	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...
483
		if (!is_array($id_objets)) {
484
			$id_objets = array($id_objets);
485
		}
486
487
		foreach ($id_objets as $id_objet) {
488
			$objet = (($objet == '*') ? $objet : objet_type($objet)); # securite
489
490
			$where = lien_where($primary, '*', $objet, $id_objet);
491
			$liens = sql_allfetsel("$primary, id_objet, objet, rang_lien", $table_lien, $where, $primary,"rang_lien");
492
493
			$rangs = array_column($liens, 'rang_lien');
494
			if (count($rangs) and (max($rangs)>0 or min($rangs)<0)) {
495
				$rang = 1;
496
				foreach ($liens as $lien) {
497
					$where = lien_where($primary, $lien[$primary], $objet, $id_objet, array('rang_lien!='.intval($rang)));
498
					sql_updateq($table_lien, array('rang_lien' => $rang), $where);
499
					$rang++;
500
				}
501
			}
502
		}
503
	}
504
}
505
506
507
/**
508
 * Une table de lien est-elle triable ?
509
 * elle doit disposer d'un champ rang_lien pour cela
510
 * @param $table_lien
511
 * @return mixed
512
 */
513
function lien_triables($table_lien) {
514
	static $triables = array();
515
	if (!isset($triables[$table_lien])) {
516
		$trouver_table = charger_fonction('trouver_table', 'base');
517
		$desc = $trouver_table($table_lien);
518
		if ($desc and isset($desc['field']['rang_lien'])) {
519
			$triables[$table_lien] = true;
520
		}
521
		else {
522
			$triables[$table_lien] = false;
523
		}
524
	}
525
	return $triables[$table_lien];
526
}
527
528
529
/**
530
 * Fabriquer la condition where en tenant compte des jokers *
531
 *
532
 * @internal
533
 * @param string $primary Nom de la clé primaire
534
 * @param int|string|array $id_source Identifiant de la clé primaire
535
 * @param string $objet Nom de l'objet lié
536
 * @param int|string|array $id_objet Identifiant de l'objet lié
537
 * @param array $cond Conditions par défaut
538
 * @return array                        Liste des conditions
539
 */
540
function lien_where($primary, $id_source, $objet, $id_objet, $cond = array()) {
541
	if ((!is_array($id_source) and !strlen($id_source))
542
		or !strlen($objet)
543
		or (!is_array($id_objet) and !strlen($id_objet))
544
	) {
545
		return array("0=1");
546
	} // securite
547
548
	$not = "";
549
	if (is_array($id_source) and reset($id_source) == "NOT") {
550
		$not = array_shift($id_source);
551
		$id_source = reset($id_source);
552
	}
553
554
	$where = $cond;
555
556
	if ($id_source !== '*') {
557
		$where[] = (is_array($id_source) ? sql_in(addslashes($primary), array_map('intval', $id_source),
558
			$not) : addslashes($primary) . ($not ? "<>" : "=") . intval($id_source));
559
	} elseif ($not) {
560
		$where[] = "0=1";
561
	} // idiot mais quand meme
562
563
	$not = "";
564
	if (is_array($id_objet) and reset($id_objet) == "NOT") {
565
		$not = array_shift($id_objet);
566
		$id_objet = reset($id_objet);
567
	}
568
569
	if ($objet !== '*') {
570
		$where[] = "objet=" . sql_quote($objet);
571
	}
572
	if ($id_objet !== '*') {
573
		$where[] = (is_array($id_objet) ? sql_in('id_objet', array_map('intval', $id_objet),
574
			$not) : "id_objet" . ($not ? "<>" : "=") . intval($id_objet));
575
	} elseif ($not) {
576
		$where[] = "0=1";
577
	} // idiot mais quand meme
578
579
	return $where;
580
}
581
582
/**
583
 * Sous fonction suppression
584
 * qui traite les liens pour un objet source dont la clé primaire
585
 * et la table de lien sont fournies
586
 *
587
 * $objets et de la forme
588
 * array($objet=>$id_objets,...)
589
 * un * pour $id,$objet,$id_objets permet de traiter par lot
590
 *
591
 * On supprime tous les liens entre les objets indiqués par défaut,
592
 * sauf s'il y a des rôles déclarés entre ces 2 objets, auquel cas on ne
593
 * supprime que les liaisons avec le role déclaré par défaut si rien n'est
594
 * précisé dans $cond. Il faut alors passer $cond=array('role'=>'*') pour
595
 * supprimer tous les roles, ou array('role'=>'un_role') pour un role précis.
596
 *
597
 * @internal
598
 * @param string $objet_source
599
 * @param string $primary
600
 * @param string $table_lien
601
 * @param int $id
602
 * @param array $objets
603
 * @param array|null $cond
604
 *     Conditions where par défaut.
605
 *     Un cas particulier est géré lorsque l'index 'role' est présent (ou absent)
606
 * @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...
607
 */
608
function lien_delete($objet_source, $primary, $table_lien, $id, $objets, $cond = null) {
609
610
	$retire = array();
611
	$dels = 0;
612
	$echec = false;
613
	if (is_null($cond)) {
614
		$cond = array();
615
	}
616
617
	foreach ($objets as $objet => $id_objets) {
618
		$objet = ($objet == '*') ? $objet : objet_type($objet); # securite
619 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...
620
			$id_objets = array($id_objets);
621
		}
622
		foreach ($id_objets as $id_objet) {
623
			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...
624
			// id_objet peut valoir '*'
625
			$where = lien_where($primary, $id, $objet, $id_objet, $cond);
626
627
			// lire les liens existants pour propager la date de modif
628
			$select = "$primary,id_objet,objet";
629
			if ($colonne_role) {
630
				$select .= ",$colonne_role";
631
			}
632
			$liens = sql_allfetsel($select, $table_lien, $where);
633
634
			// iterer sur les liens pour permettre aux plugins de gerer
635
			foreach ($liens as $l) {
636
637
				$args = array(
638
					'table_lien' => $table_lien,
639
					'objet_source' => $objet_source,
640
					'id_objet_source' => $l[$primary],
641
					'objet' => $l['objet'],
642
					'id_objet' => $l['id_objet'],
643
					'colonne_role' => $colonne_role,
644
					'role' => ($colonne_role ? $l[$colonne_role] : ''),
645
					'action' => 'delete',
646
				);
647
648
				// Envoyer aux plugins
649
				$l = pipeline('pre_edition_lien',
650
					array(
651
						'args' => $args,
652
						'data' => $l
653
					)
654
				);
655
				$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...
656
657
				if ($id_o = intval($l['id_objet']) or $l['objet'] == 'site') {
658
					$where = lien_where($primary, $l[$primary], $l['objet'], $id_o, $cond);
659
					$e = sql_delete($table_lien, $where);
660
					if ($e !== false) {
661
						$dels += $e;
662
						lien_propage_date_modif($l['objet'], $id_o);
663
						lien_propage_date_modif($objet_source, $l[$primary]);
664
					} else {
665
						$echec = true;
666
					}
667
					$retire[] = array(
668
						'source' => array($objet_source => $l[$primary]),
669
						'lien' => array($l['objet'] => $id_o),
670
						'type' => $l['objet'],
671
						'role' => ($colonne_role ? $l[$colonne_role] : ''),
672
						'id' => $id_o
673
					);
674
					// Envoyer aux plugins
675
					pipeline('post_edition_lien',
676
						array(
677
							'args' => $args,
678
							'data' => $l
679
						)
680
					);
681
				}
682
			}
683
		}
684
	}
685
	// si on a supprime des liens, on reordonne les liens concernes
686
	if ($dels) {
687
		lien_ordonner($objet_source, $primary, $table_lien, $id, $objets);
688
	}
689
690
	pipeline('trig_supprimer_objets_lies', $retire);
691
692
	return ($echec ? false : $dels);
693
}
694
695
696
/**
697
 * Sous fonction optimisation
698
 * qui nettoie les liens morts (vers un objet inexistant)
699
 * pour un objet source dont la clé primaire
700
 * et la table de lien sont fournies
701
 *
702
 * $objets et de la forme
703
 * array($objet=>$id_objets,...)
704
 * un * pour $id,$objet,$id_objets permet de traiter par lot
705
 *
706
 * @internal
707
 * @param string $objet_source
708
 * @param string $primary
709
 * @param string $table_lien
710
 * @param int $id
711
 * @param array $objets
712
 * @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...
713
 */
714
function lien_optimise($objet_source, $primary, $table_lien, $id, $objets) {
715
	include_spip('genie/optimiser');
716
	$echec = false;
717
	$dels = 0;
718
	foreach ($objets as $objet => $id_objets) {
719
		$objet = ($objet == '*') ? $objet : objet_type($objet); # securite
720 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...
721
			$id_objets = array($id_objets);
722
		}
723
		foreach ($id_objets as $id_objet) {
724
			$where = lien_where($primary, $id, $objet, $id_objet);
725
			# les liens vers un objet inexistant
726
			$r = sql_select("DISTINCT objet", $table_lien, $where);
727
			while ($t = sql_fetch($r)) {
728
				$type = $t['objet'];
729
				$spip_table_objet = table_objet_sql($type);
730
				$id_table_objet = id_table_objet($type);
731
				$res = sql_select("L.$primary AS id,L.id_objet",
732
					// la condition de jointure inclue L.objet='xxx' pour ne joindre que les bonnes lignes
733
					// du coups toutes les lignes avec un autre objet ont un id_xxx=NULL puisque LEFT JOIN
734
					// il faut les eliminier en repetant la condition dans le where L.objet='xxx'
735
					"$table_lien AS L
736
									LEFT JOIN $spip_table_objet AS O
737
										ON (O.$id_table_objet=L.id_objet AND L.objet=" . sql_quote($type) . ")",
738
					"L.objet=" . sql_quote($type) . " AND O.$id_table_objet IS NULL");
739
				// sur une cle primaire composee, pas d'autres solutions que de virer un a un
740
				while ($row = sql_fetch($res)) {
741
					if ($primary === 'id_document' and $type === 'site' and !intval($row['id_objet'])) {
742
						continue; // gaffe, c'est le logo du site !
743
					}
744
					$e = sql_delete($table_lien,
745
						array("$primary=" . $row['id'], "id_objet=" . $row['id_objet'], "objet=" . sql_quote($type)));
746
					if ($e != false) {
747
						$dels += $e;
748
						spip_log("Entree " . $row['id'] . "/" . $row['id_objet'] . "/$type supprimee dans la table $table_lien",
749
							_LOG_INFO_IMPORTANTE);
750
					}
751
				}
752
			}
753
754
			# les liens depuis un objet inexistant
755
			$table_source = table_objet_sql($objet_source);
756
			// filtrer selon $id, $objet, $id_objet eventuellement fournis
757
			// (en general '*' pour chaque)
758
			$where = lien_where("L.$primary", $id, $objet, $id_objet);
759
			$where[] = "O.$primary IS NULL";
760
			$res = sql_select("L.$primary AS id",
761
				"$table_lien AS L LEFT JOIN $table_source AS O ON L.$primary=O.$primary",
762
				$where);
763
			$dels += optimiser_sansref($table_lien, $primary, $res);
764
		}
765
	}
766
767
	return ($echec ? false : $dels);
768
}
769
770
771
/**
772
 * Sous fonction qualification
773
 * qui traite les liens pour un objet source dont la clé primaire
774
 * et la table de lien sont fournies
775
 *
776
 * $objets et de la forme
777
 * array($objet=>$id_objets,...)
778
 * un * pour $id,$objet,$id_objets permet de traiter par lot
779
 *
780
 * exemple :
781
 * $qualif = array('vu'=>'oui');
782
 *
783
 * @internal
784
 * @param string $objet_source Objet source de l'insertion (celui qui a la table de liaison)
785
 * @param string $primary Nom de la clé primaire de cet objet
786
 * @param string $table_lien Nom de la table de lien de cet objet
787
 * @param int $id Identifiant de l'objet sur lesquels on va insérer des liaisons
788
 * @param array $objets Liste des liaisons à faire, de la forme array($objet=>$id_objets)
789
 * @param array $qualif
790
 *     Liste des qualifications à appliquer.
791
 *
792
 *     Si l'objet dispose d'un champ rôle, on extrait des qualifications
793
 *     le rôle s'il est présent, sinon on applique les qualifications
794
 *     sur le rôle par défaut.
795
 * @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...
796
 *     Nombre de modifications faites, false si échec.
797
 */
798
function lien_set($objet_source, $primary, $table_lien, $id, $objets, $qualif) {
799
	$echec = null;
800
	$ok = 0;
801
	$reordonner = false;
802
	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...
803
		return false;
804
	}
805
	// nettoyer qualif qui peut venir directement d'un objet_trouver_lien :
806
	unset($qualif[$primary]);
807
	unset($qualif[$objet_source]);
808
	if (isset($qualif['objet'])) {
809
		unset($qualif[$qualif['objet']]);
810
	}
811
	unset($qualif['objet']);
812
	unset($qualif['id_objet']);
813
	foreach ($objets as $objet => $id_objets) {
814
815
		// role, colonne, where par défaut
816
		list($role, $colonne_role, $cond) =
817
			roles_trouver_dans_qualif($objet_source, $objet, $qualif);
818
819
		$objet = ($objet == '*') ? $objet : objet_type($objet); # securite
820 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...
821
			$id_objets = array($id_objets);
822
		}
823
		foreach ($id_objets as $id_objet) {
824
825
			$args = array(
826
				'table_lien' => $table_lien,
827
				'objet_source' => $objet_source,
828
				'id_objet_source' => $id,
829
				'objet' => $objet,
830
				'id_objet' => $id_objet,
831
				'role' => $role,
832
				'colonne_role' => $colonne_role,
833
				'action' => 'modifier',
834
			);
835
836
			// Envoyer aux plugins
837
			$qualif = pipeline('pre_edition_lien',
838
				array(
839
					'args' => $args,
840
					'data' => $qualif,
841
				)
842
			);
843
			$args['id_objet'] = $id_objet;
844
845
			if (lien_triables($table_lien) and isset($qualif['rang_lien'])) {
846
				if (intval($qualif['rang_lien'])) {
847
					// on decale les liens de rang_lien>=la valeur inseree pour faire la place
848
					$w = lien_where($primary, '*', $objet, $id_objet, array('rang_lien>='.intval($qualif['rang_lien']),"$primary!=".intval($id)));
849
					sql_update($table_lien, array('rang_lien'=>'rang_lien+1'), $w);
850
				}
851
				// tous les liens de même rôle recoivent le rang indiqué aussi
852
				if (roles_colonne($objet_source, $objet)) {
853
					$w = lien_where($primary, $id, $objet, $id_objet);
854
					sql_updateq($table_lien, array('rang_lien' => intval($qualif['rang_lien'])), $w);
855
				}
856
				$reordonner = true;
857
			}
858
859
			$where = lien_where($primary, $id, $objet, $id_objet, $cond);
860
			$e = sql_updateq($table_lien, $qualif, $where);
861
862
			if ($e === false) {
863
				$echec = true;
864
			} else {
865
				// Envoyer aux plugins
866
				pipeline('post_edition_lien',
867
					array(
868
						'args' => $args,
869
						'data' => $qualif
870
					)
871
				);
872
				$ok++;
873
			}
874
		}
875
	}
876
	// si on a fait des modif de rang, on reordonne les liens concernes
877
	if ($reordonner) {
878
		lien_ordonner($objet_source, $primary, $table_lien, $id, $objets);
879
	}
880
881
	return ($echec ? false : $ok);
882
}
883
884
/**
885
 * Sous fonction trouver
886
 * qui cherche les liens pour un objet source dont la clé primaire
887
 * et la table de lien sont fournies
888
 *
889
 * $objets et de la forme
890
 * array($objet=>$id_objets,...)
891
 * un * pour $id,$objet,$id_objets permet de traiter par lot
892
 *
893
 * Le tableau de condition peut avoir un index 'role' indiquant de
894
 * chercher un rôle précis, ou * pour tous les roles (alors équivalent
895
 * à l'absence de l'index)
896
 *
897
 * @internal
898
 * @param string $objet_source
899
 * @param string $primary
900
 * @param string $table_lien
901
 * @param int $id
902
 * @param array $objets
903
 * @param array|null $cond
904
 *     Condition du where par défaut
905
 *
906
 *     On peut passer un index 'role' pour sélectionner uniquement
907
 *     le role défini dedans (et '*' pour tous les rôles).
908
 * @return array
909
 */
910
function lien_find($objet_source, $primary, $table_lien, $id, $objets, $cond = null) {
911
	$trouve = array();
912
	foreach ($objets as $objet => $id_objets) {
913
		$objet = ($objet == '*') ? $objet : objet_type($objet); # securite
914
		// gerer les roles s'il y en a dans $cond
915
		list($cond) = roles_creer_condition_role($objet_source, $objet, $cond, true);
916
		// lien_where prend en charge les $id_objets sous forme int ou array
917
		$where = lien_where($primary, $id, $objet, $id_objets, $cond);
918
		$liens = sql_allfetsel('*', $table_lien, $where);
919
		// ajouter les entrees objet_source et objet cible par convenance
920
		foreach ($liens as $l) {
921
			$l[$objet_source] = $l[$primary];
922
			$l[$l['objet']] = $l['id_objet'];
923
			$trouve[] = $l;
924
		}
925
	}
926
927
	return $trouve;
928
}
929
930
/**
931
 * Propager la date_modif sur les objets dont un lien a été modifié
932
 *
933
 * @internal
934
 * @param string $objet
935
 * @param array|int $ids
936
 */
937
function lien_propage_date_modif($objet, $ids) {
938
	static $done = array();
939
	$hash = md5($objet . serialize($ids));
940
941
	// sql_updateq, peut être un rien lent.
942
	// On évite de l'appeler 2 fois sur les mêmes choses
943
	if (isset($done[$hash])) {
944
		return;
945
	}
946
947
	$trouver_table = charger_fonction('trouver_table', 'base');
948
949
	$table = table_objet_sql($objet);
950
	if ($desc = $trouver_table($table)
951
		and isset($desc['field']['date_modif'])
952
	) {
953
		$primary = id_table_objet($objet);
954
		$where = (is_array($ids) ? sql_in($primary, array_map('intval', $ids)) : "$primary=" . intval($ids));
955
		sql_updateq($table, array('date_modif' => date('Y-m-d H:i:s')), $where);
956
	}
957
958
	$done[$hash] = true;
959
}
960