Completed
Push — master ( e2d0e1...f4ed28 )
by cam
06:55
created

editer_liens.php ➔ objet_dupliquer_liens()   C

Complexity

Conditions 14
Paths 11

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
nc 11
nop 5
dl 0
loc 33
rs 6.2666
c 0
b 0
f 0

How to fix   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'])
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'])) {
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
					$e = sql_delete($table_lien,
742
						array("$primary=" . $row['id'], "id_objet=" . $row['id_objet'], "objet=" . sql_quote($type)));
743
					if ($e != false) {
744
						$dels += $e;
745
						spip_log("Entree " . $row['id'] . "/" . $row['id_objet'] . "/$type supprimee dans la table $table_lien",
746
							_LOG_INFO_IMPORTANTE);
747
					}
748
				}
749
			}
750
751
			# les liens depuis un objet inexistant
752
			$table_source = table_objet_sql($objet_source);
753
			// filtrer selon $id, $objet, $id_objet eventuellement fournis
754
			// (en general '*' pour chaque)
755
			$where = lien_where("L.$primary", $id, $objet, $id_objet);
756
			$where[] = "O.$primary IS NULL";
757
			$res = sql_select("L.$primary AS id",
758
				"$table_lien AS L LEFT JOIN $table_source AS O ON L.$primary=O.$primary",
759
				$where);
760
			$dels += optimiser_sansref($table_lien, $primary, $res);
761
		}
762
	}
763
764
	return ($echec ? false : $dels);
765
}
766
767
768
/**
769
 * Sous fonction qualification
770
 * qui traite les liens pour un objet source dont la clé primaire
771
 * et la table de lien sont fournies
772
 *
773
 * $objets et de la forme
774
 * array($objet=>$id_objets,...)
775
 * un * pour $id,$objet,$id_objets permet de traiter par lot
776
 *
777
 * exemple :
778
 * $qualif = array('vu'=>'oui');
779
 *
780
 * @internal
781
 * @param string $objet_source Objet source de l'insertion (celui qui a la table de liaison)
782
 * @param string $primary Nom de la clé primaire de cet objet
783
 * @param string $table_lien Nom de la table de lien de cet objet
784
 * @param int $id Identifiant de l'objet sur lesquels on va insérer des liaisons
785
 * @param array $objets Liste des liaisons à faire, de la forme array($objet=>$id_objets)
786
 * @param array $qualif
787
 *     Liste des qualifications à appliquer.
788
 *
789
 *     Si l'objet dispose d'un champ rôle, on extrait des qualifications
790
 *     le rôle s'il est présent, sinon on applique les qualifications
791
 *     sur le rôle par défaut.
792
 * @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...
793
 *     Nombre de modifications faites, false si échec.
794
 */
795
function lien_set($objet_source, $primary, $table_lien, $id, $objets, $qualif) {
796
	$echec = null;
797
	$ok = 0;
798
	$reordonner = false;
799
	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...
800
		return false;
801
	}
802
	// nettoyer qualif qui peut venir directement d'un objet_trouver_lien :
803
	unset($qualif[$primary]);
804
	unset($qualif[$objet_source]);
805
	if (isset($qualif['objet'])) {
806
		unset($qualif[$qualif['objet']]);
807
	}
808
	unset($qualif['objet']);
809
	unset($qualif['id_objet']);
810
	foreach ($objets as $objet => $id_objets) {
811
812
		// role, colonne, where par défaut
813
		list($role, $colonne_role, $cond) =
814
			roles_trouver_dans_qualif($objet_source, $objet, $qualif);
815
816
		$objet = ($objet == '*') ? $objet : objet_type($objet); # securite
817 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...
818
			$id_objets = array($id_objets);
819
		}
820
		foreach ($id_objets as $id_objet) {
821
822
			$args = array(
823
				'table_lien' => $table_lien,
824
				'objet_source' => $objet_source,
825
				'id_objet_source' => $id,
826
				'objet' => $objet,
827
				'id_objet' => $id_objet,
828
				'role' => $role,
829
				'colonne_role' => $colonne_role,
830
				'action' => 'modifier',
831
			);
832
833
			// Envoyer aux plugins
834
			$qualif = pipeline('pre_edition_lien',
835
				array(
836
					'args' => $args,
837
					'data' => $qualif,
838
				)
839
			);
840
			$args['id_objet'] = $id_objet;
841
842
			if (lien_triables($table_lien) and isset($qualif['rang_lien'])) {
843
				if (intval($qualif['rang_lien'])) {
844
					// on decale les liens de rang_lien>=la valeur inseree pour faire la place
845
					$w = lien_where($primary, '*', $objet, $id_objet, array('rang_lien>='.intval($qualif['rang_lien']),"$primary!=".intval($id)));
846
					sql_update($table_lien, array('rang_lien'=>'rang_lien+1'), $w);
847
				}
848
				// tous les liens de même rôle recoivent le rang indiqué aussi
849
				if (roles_colonne($objet_source, $objet)) {
850
					$w = lien_where($primary, $id, $objet, $id_objet);
851
					sql_updateq($table_lien, array('rang_lien' => intval($qualif['rang_lien'])), $w);
852
				}
853
				$reordonner = true;
854
			}
855
856
			$where = lien_where($primary, $id, $objet, $id_objet, $cond);
857
			$e = sql_updateq($table_lien, $qualif, $where);
858
859
			if ($e === false) {
860
				$echec = true;
861
			} else {
862
				// Envoyer aux plugins
863
				pipeline('post_edition_lien',
864
					array(
865
						'args' => $args,
866
						'data' => $qualif
867
					)
868
				);
869
				$ok++;
870
			}
871
		}
872
	}
873
	// si on a fait des modif de rang, on reordonne les liens concernes
874
	if ($reordonner) {
875
		lien_ordonner($objet_source, $primary, $table_lien, $id, $objets);
876
	}
877
878
	return ($echec ? false : $ok);
879
}
880
881
/**
882
 * Sous fonction trouver
883
 * qui cherche les liens pour un objet source dont la clé primaire
884
 * et la table de lien sont fournies
885
 *
886
 * $objets et de la forme
887
 * array($objet=>$id_objets,...)
888
 * un * pour $id,$objet,$id_objets permet de traiter par lot
889
 *
890
 * Le tableau de condition peut avoir un index 'role' indiquant de
891
 * chercher un rôle précis, ou * pour tous les roles (alors équivalent
892
 * à l'absence de l'index)
893
 *
894
 * @internal
895
 * @param string $objet_source
896
 * @param string $primary
897
 * @param string $table_lien
898
 * @param int $id
899
 * @param array $objets
900
 * @param array|null $cond
901
 *     Condition du where par défaut
902
 *
903
 *     On peut passer un index 'role' pour sélectionner uniquement
904
 *     le role défini dedans (et '*' pour tous les rôles).
905
 * @return array
906
 */
907
function lien_find($objet_source, $primary, $table_lien, $id, $objets, $cond = null) {
908
	$trouve = array();
909
	foreach ($objets as $objet => $id_objets) {
910
		$objet = ($objet == '*') ? $objet : objet_type($objet); # securite
911
		// gerer les roles s'il y en a dans $cond
912
		list($cond) = roles_creer_condition_role($objet_source, $objet, $cond, true);
913
		// lien_where prend en charge les $id_objets sous forme int ou array
914
		$where = lien_where($primary, $id, $objet, $id_objets, $cond);
915
		$liens = sql_allfetsel('*', $table_lien, $where);
916
		// ajouter les entrees objet_source et objet cible par convenance
917
		foreach ($liens as $l) {
918
			$l[$objet_source] = $l[$primary];
919
			$l[$l['objet']] = $l['id_objet'];
920
			$trouve[] = $l;
921
		}
922
	}
923
924
	return $trouve;
925
}
926
927
/**
928
 * Propager la date_modif sur les objets dont un lien a été modifié
929
 *
930
 * @internal
931
 * @param string $objet
932
 * @param array|int $ids
933
 */
934
function lien_propage_date_modif($objet, $ids) {
935
	static $done = array();
936
	$hash = md5($objet . serialize($ids));
937
938
	// sql_updateq, peut être un rien lent.
939
	// On évite de l'appeler 2 fois sur les mêmes choses
940
	if (isset($done[$hash])) {
941
		return;
942
	}
943
944
	$trouver_table = charger_fonction('trouver_table', 'base');
945
946
	$table = table_objet_sql($objet);
947
	if ($desc = $trouver_table($table)
948
		and isset($desc['field']['date_modif'])
949
	) {
950
		$primary = id_table_objet($objet);
951
		$where = (is_array($ids) ? sql_in($primary, array_map('intval', $ids)) : "$primary=" . intval($ids));
952
		sql_updateq($table, array('date_modif' => date('Y-m-d H:i:s')), $where);
953
	}
954
955
	$done[$hash] = true;
956
}
957