Completed
Push — master ( 2389a5...e0e29f )
by cam
04:23
created

jointures.php ➔ fabrique_jointures()   D

Complexity

Conditions 18
Paths 168

Size

Total Lines 74

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
nc 168
nop 7
dl 0
loc 74
rs 4.3
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, Système de publication pour l'internet                           *
5
 *                                                                         *
6
 *  Copyright © avec tendresse depuis 2001                                 *
7
 *  Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James  *
8
 *                                                                         *
9
 *  Ce programme est un logiciel libre distribué sous licence GNU/GPL.     *
10
 *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
11
\***************************************************************************/
12
13
/**
14
 * Déduction automatique d'une chaîne de jointures
15
 *
16
 * @package SPIP\Core\Compilateur\Jointures
17
 **/
18
19
if (!defined('_ECRIRE_INC_VERSION')) {
20
	return;
21
}
22
23
24
/**
25
 * Décomposer un champ id_truc en (id_objet,objet,truc)
26
 *
27
 * Exemple : décompose id_article en (id_objet,objet,article)
28
 *
29
 * @param string $champ
30
 *     Nom du champ à décomposer
31
 * @return array|string
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[]|string.

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...
32
 *     Tableau si décomposable : 'id_objet', 'objet', Type de l'objet
33
 *     Chaine sinon : le nom du champ (non décomposable donc)
34
 */
35
function decompose_champ_id_objet($champ) {
36
	if (($champ !== 'id_objet') and preg_match(',^id_([a-z_]+)$,', $champ, $regs)) {
37
		return array('id_objet', 'objet', objet_type($champ));
38
	}
39
40
	return $champ;
41
}
42
43
/**
44
 * Mapping d'un champ d'une jointure en deux champs id_objet,objet si nécessaire
45
 *
46
 * Si le champ demandé existe dans la table, on l'utilise, sinon on
47
 * regarde si le champ se décompose en objet/id_objet et si la table
48
 * possède ces champs, et dans ce cas, on les retourne.
49
 *
50
 * @uses decompose_champ_id_objet()
51
 * @param string $champ Nom du champ à tester (ex. id_article)
52
 * @param array $desc Description de la table
53
 * @return array
54
 *     Liste du/des champs. Soit
55
 *     - array($champ), si le champ existe dans la table ou si on ne peut décomposer.
56
 *     - array(id_objet, objet), si le champ n'existe pas mais qu'on peut décomposer
57
 */
58
function trouver_champs_decomposes($champ, $desc) {
59
	if (!is_array($desc) // on ne se risque pas en conjectures si on ne connait pas la table
60
		or array_key_exists($champ, $desc['field'])
61
	) {
62
		return array($champ);
63
	}
64
	// si le champ se décompose, tester que les colonnes décomposées sont présentes
65
	if (is_array($decompose = decompose_champ_id_objet($champ))) {
66
		array_pop($decompose);
67
		if (count(array_intersect($decompose, array_keys($desc['field']))) == count($decompose)) {
68
			return $decompose;
69
		}
70
	}
71
72
	return array($champ);
73
}
74
75
76
/**
77
 * Calculer et construite une jointure entre $depart et $arrivee
78
 *
79
 * L'objet boucle est modifié pour compléter la requête.
80
 * La fonction retourne l'alias d'arrivée une fois la jointure construire,
81
 * en general un "Lx"
82
 *
83
 * @uses calculer_chaine_jointures()
84
 * @uses fabrique_jointures()
85
 *
86
 * @param Boucle $boucle
87
 *     Description de la boucle
88
 * @param array $depart
89
 *     Table de départ, sous la forme (nom de la table, description de la table)
90
 * @param array $arrivee
91
 *     Table d'arrivée, sous la forme (nom de la table, description de la table)
92
 * @param string $col
93
 *     Colonne cible de la jointure
94
 * @param bool $cond
95
 *     Flag pour savoir si le critère est conditionnel ou non
96
 * @param int $max_liens
97
 *     Nombre maximal de liaisons possibles pour trouver la jointure.
98
 * @return string
99
 *     Alias de la table de jointure (Lx)
100
 */
101
function calculer_jointure(&$boucle, $depart, $arrivee, $col = '', $cond = false, $max_liens = 5) {
102
	// les jointures minimales sont optimales :
103
	// on contraint le nombre d'etapes en l'augmentant
104
	// jusqu'a ce qu'on trouve une jointure ou qu'on atteigne la limite maxi 
105
	$max = 1;
106
	$res = false;
107
	$milieu_exclus = ($col ? $col : array());
108
	while ($max <= $max_liens and !$res) {
109
		$res = calculer_chaine_jointures($boucle, $depart, $arrivee, array(), $milieu_exclus, $max);
0 ignored issues
show
Bug introduced by
It seems like $boucle defined by parameter $boucle on line 101 can also be of type object<Boucle>; however, calculer_chaine_jointures() does only seem to accept object<objetc>, 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...
Bug introduced by
It seems like $milieu_exclus defined by $col ? $col : array() on line 107 can also be of type string; however, calculer_chaine_jointures() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
110
		$max++;
111
	}
112
	if (!$res) {
113
		return "";
114
	}
115
116
	list($nom, $desc) = $depart;
117
118
	return fabrique_jointures($boucle, $res, $cond, $desc, $nom, $col);
119
}
120
121
/**
122
 * Fabriquer une jointure à l'aide d'une liste descriptive d'étapes
123
 *
124
 * Ajoute
125
 * - la jointure dans le tableau $boucle->join,
126
 * - la table de jointure dans le from
127
 * - un modificateur 'lien'
128
 *
129
 * @uses nogroupby_if()
130
 * @uses liste_champs_jointures()
131
 *
132
 * @param Boucle $boucle
133
 *     Description de la boucle
134
 * @param array $res
135
 *     Chaîne des jointures
136
 *     $res = array(
137
 *         array(table_depart,array(table_arrivee,desc),jointure),
138
 *         ...
139
 *     )
140
 *     Jointure peut être un tableau pour les jointures sur champ decomposé
141
 *     array('id_article','id_objet','objet','article')
142
 *     array('id_objet','id_article','objet','article')
143
 * @param bool $cond
144
 *     Flag pour savoir si le critère est conditionnel ou non
145
 * @param array $desc
146
 *     Description de la table de départ
147
 * @param string $nom
148
 *     Nom de la table de départ
149
 * @param string $col
150
 *     Colonne cible de la jointure
151
 * @param bool $echap
152
 *     Écrire les valeurs dans boucle->join en les échappant ou non ?
153
 * @return string
154
 *     Alias de la table de jointure (Lx)
155
 */
156
function fabrique_jointures(&$boucle, $res, $cond = false, $desc = array(), $nom = '', $col = '', $echap = true) {
157
	static $num = array();
158
	$id_table = "";
159
	$cpt = &$num[$boucle->descr['nom']][$boucle->descr['gram']][$boucle->id_boucle];
160
	foreach ($res as $cle => $r) {
161
		list($d, $a, $j) = $r;
162
		if (!$id_table) {
163
			$id_table = $d;
164
		}
165
		$n = ++$cpt;
166
		if (is_array($j)) { // c'est un lien sur un champ du type id_objet,objet,'article'
167
			list($j1, $j2, $obj, $type) = $j;
168
			// trouver de quel cote est (id_objet,objet)
169
			if ($j1 == "id_$obj") {
170
				$obj = "$id_table.$obj";
171
			} else {
172
				$obj = "L$n.$obj";
173
			}
174
			// le where complementaire est envoye dans la jointure et dans le where
175
			// on utilise une clé qui le relie a la jointure pour que l'optimiseur
176
			// sache qu'il peut enlever ce where si il enleve la jointure
177
			$boucle->where["JOIN-L$n"] =
178
				$echap ?
179
					array("'='","'$obj'","sql_quote('$type')")
180
					:
181
					array("=","$obj",sql_quote($type));
182
			$boucle->join["L$n"] =
183
				$echap ?
184
					array("'$id_table'", "'$j2'", "'$j1'", "'$obj='.sql_quote('$type')")
185
					:
186
					array($id_table, $j2, $j1, "$obj=" . sql_quote($type));
187
		} else {
188
			$boucle->join["L$n"] = $echap ? array("'$id_table'", "'$j'") : array($id_table, $j);
189
		}
190
		$boucle->from[$id_table = "L$n"] = $a[0];
191
	}
192
193
194
	// pas besoin de group by
195
	// (cf http://article.gmane.org/gmane.comp.web.spip.devel/30555)
196
	// si une seule jointure et sur une table avec primary key formee
197
	// de l'index principal et de l'index de jointure (non conditionnel! [6031])
198
	// et operateur d'egalite (https://core.spip.net/issues/477)
199
200
	if ($pk = (isset($a[1]) && (count($boucle->from) == 2) && !$cond)) {
201
		$pk = nogroupby_if($desc, $a[1], $col);
0 ignored issues
show
Bug introduced by
The variable $a does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
202
	}
203
204
	// pas de group by 
205
	// si une seule jointure
206
	// et si l'index de jointure est une primary key a l'arrivee !
207
	if (!$pk
208
		and (count($boucle->from) == 2)
209
		and isset($a[1]['key']['PRIMARY KEY'])
210
		and ($j == $a[1]['key']['PRIMARY KEY'])
0 ignored issues
show
Bug introduced by
The variable $j does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
211
	) {
212
		$pk = true;
213
	}
214
215
	// la clause Group by est en conflit avec ORDER BY, a completer
216
	$groups = liste_champs_jointures($nom, $desc, true);
217
	if (!$pk) {
218
		foreach ($groups as $id_prim) {
219
			$id_field = $nom . '.' . $id_prim;
220
			if (!in_array($id_field, $boucle->group)) {
221
				$boucle->group[] = $id_field;
222
			}
223
		}
224
	}
225
226
	$boucle->modificateur['lien'] = true;
227
228
	return "L$n";
0 ignored issues
show
Bug introduced by
The variable $n does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
229
}
230
231
/**
232
 * Condition suffisante pour qu'un Group-By ne soit pas nécéssaire
233
 *
234
 * À améliorer, notamment voir si calculer_select ne pourrait pas la réutiliser
235
 * lorsqu'on sait si le critere conditionnel est finalement present
236
 *
237
 * @param array $depart
238
 * @param array $arrivee
239
 * @param string|array $col
240
 * @return bool
241
 */
242
function nogroupby_if($depart, $arrivee, $col) {
243
	$pk = $arrivee['key']['PRIMARY KEY'];
244
	if (!$pk) {
245
		return false;
246
	}
247
	$id_primary = $depart['key']['PRIMARY KEY'];
248
	if (is_array($col)) {
249
		$col = implode(', *', $col);
250
	} // cas id_objet, objet
251
	return (preg_match("/^$id_primary, *$col$/", $pk) or
252
		preg_match("/^$col, *$id_primary$/", $pk));
253
}
254
255
/**
256
 * Lister les champs candidats a une jointure, sur une table
257
 * si un join est fourni dans la description, c'est lui qui l'emporte
258
 * sauf si cle primaire explicitement demandee par $primary
259
 *
260
 * sinon on construit une liste des champs a partir de la liste des cles de la table
261
 *
262
 * @uses split_key()
263
 * @param string $nom
264
 * @param array $desc
265
 * @param bool $primary
266
 * @return array
267
 */
268
function liste_champs_jointures($nom, $desc, $primary = false) {
0 ignored issues
show
Unused Code introduced by
The parameter $nom 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...
269
270
	static $nojoin = array('idx', 'maj', 'date', 'statut');
271
272
	// si cle primaire demandee, la privilegier
273
	if ($primary && isset($desc['key']['PRIMARY KEY'])) {
274
		return split_key($desc['key']['PRIMARY KEY']);
275
	}
276
277
	// les champs declares explicitement pour les jointures
278
	if (isset($desc['join'])) {
279
		return $desc['join'];
280
	}
281
	/*elseif (isset($GLOBALS['tables_principales'][$nom]['join'])) return $GLOBALS['tables_principales'][$nom]['join'];
282
	elseif (isset($GLOBALS['tables_auxiliaires'][$nom]['join'])) return $GLOBALS['tables_auxiliaires'][$nom]['join'];*/
283
284
	// si pas de cle, c'est fichu
285
	if (!isset($desc['key'])) {
286
		return array();
287
	}
288
289
	// si cle primaire
290
	if (isset($desc['key']['PRIMARY KEY'])) {
291
		return split_key($desc['key']['PRIMARY KEY']);
292
	}
293
294
	// ici on se rabat sur les cles secondaires, 
295
	// en eliminant celles qui sont pas pertinentes (idx, maj)
296
	// si jamais le resultat n'est pas pertinent pour une table donnee,
297
	// il faut declarer explicitement le champ 'join' de sa description
298
299
	$join = array();
300
	foreach ($desc['key'] as $v) {
301
		$join = split_key($v, $join);
302
	}
303
	foreach ($join as $k) {
304
		if (in_array($k, $nojoin)) {
305
			unset($join[$k]);
306
		}
307
	}
308
309
	return $join;
310
}
311
312
/**
313
 * Eclater une cle composee en plusieurs champs
314
 *
315
 * @param string $v
316
 * @param array $join
317
 * @return array
318
 */
319
function split_key($v, $join = array()) {
320
	foreach (preg_split('/,\s*/', $v) as $k) {
321 View Code Duplication
		if (strpos($k, '(') !== false) {
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...
322
			$k = explode('(', $k);
323
			$k = trim(reset($k));
324
		}
325
		$join[$k] = $k;
326
	}
327
	return $join;
328
}
329
330
/**
331
 * Constuire la chaine de jointures, de proche en proche
332
 *
333
 * @uses liste_champs_jointures()
334
 * @uses trouver_champs_decomposes()
335
 *
336
 * @param objetc $boucle
337
 * @param array $depart
338
 *  sous la forme array(nom de la table, description)
339
 * @param array $arrivee
340
 *  sous la forme array(nom de la table, description)
341
 * @param array $vu
342
 *  tables deja vues dans la jointure, pour ne pas y repasser
343
 * @param array $milieu_exclus
344
 *  cles deja utilisees, pour ne pas les reutiliser
345
 * @param int $max_liens
346
 *  nombre maxi d'etapes
347
 * @return array
348
 */
349
function calculer_chaine_jointures(
350
	&$boucle,
351
	$depart,
352
	$arrivee,
353
	$vu = array(),
354
	$milieu_exclus = array(),
355
	$max_liens = 5
356
) {
357
	static $trouver_table;
358
	if (!$trouver_table) {
359
		$trouver_table = charger_fonction('trouver_table', 'base');
360
	}
361
362
	if (is_string($milieu_exclus)) {
363
		$milieu_exclus = array($milieu_exclus);
364
	}
365
	// quand on a exclus id_objet comme cle de jointure, il faut aussi exclure objet
366
	// faire une jointure sur objet tout seul n'a pas de sens
367
	if (in_array('id_objet', $milieu_exclus) and !in_array('objet', $milieu_exclus)) {
368
		$milieu_exclus[] = 'objet';
369
	}
370
371
	list($dnom, $ddesc) = $depart;
372
	list($anom, $adesc) = $arrivee;
373
	if (!count($vu)) {
374
		$vu[] = $dnom; // ne pas oublier la table de depart
375
		$vu[] = $anom; // ne pas oublier la table d'arrivee
376
	}
377
378
	$akeys = array();
379
	foreach ($adesc['key'] as $k) {
380
		// respecter l'ordre de $adesc['key'] pour ne pas avoir id_trad en premier entre autres...
381
		$akeys = array_merge($akeys, preg_split('/,\s*/', $k));
382
	}
383
384
	// enlever les cles d'arrivee exclues par l'appel
385
	$akeys = array_diff($akeys, $milieu_exclus);
386
387
	// cles candidates au depart
388
	$keys = liste_champs_jointures($dnom, $ddesc);
389
	// enlever les cles dde depart exclues par l'appel
390
	$keys = array_diff($keys, $milieu_exclus);
391
392
	$v = !$keys ? false : array_intersect(array_values($keys), $akeys);
393
394
	if ($v) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $v 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...
395
		return array(array($dnom, array($adesc['table'], $adesc), array_shift($v)));
396
	}
397
398
	// regarder si l'on a (id_objet,objet) au depart et si on peut le mapper sur un id_xx
399
	if (count(array_intersect(array('id_objet', 'objet'), $keys)) == 2) {
400
		// regarder si l'une des cles d'arrivee peut se decomposer en 
401
		// id_objet,objet
402
		// si oui on la prend
403
		foreach ($akeys as $key) {
404
			$v = decompose_champ_id_objet($key);
405 View Code Duplication
			if (is_array($v)) {
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...
406
				$objet = array_shift($v); // objet,'article'
407
				array_unshift($v, $key); // id_article,objet,'article'
408
				array_unshift($v, $objet); // id_objet,id_article,objet,'article'
409
				return array(array($dnom, array($adesc['table'], $adesc), $v));
410
			}
411
		}
412
	} else {
413
		// regarder si l'une des cles de depart peut se decomposer en 
414
		// id_objet,objet a l'arrivee
415
		// si oui on la prend
416
		foreach ($keys as $key) {
417
			if (count($v = trouver_champs_decomposes($key, $adesc)) > 1) {
418 View Code Duplication
				if (count($v) == count(array_intersect($v, $akeys))) {
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...
419
					$v = decompose_champ_id_objet($key); // id_objet,objet,'article'
420
					array_unshift($v, $key); // id_article,id_objet,objet,'article'
421
					return array(array($dnom, array($adesc['table'], $adesc), $v));
422
				}
423
			}
424
		}
425
	}
426
	// si l'on voulait une jointure direct, c'est rate !
427
	if ($max_liens <= 1) {
428
		return array();
429
	}
430
431
	// sinon essayer de passer par une autre table
432
	$new = $vu;
433
	foreach ($boucle->jointures as $v) {
434
		if ($v
435
			and !in_array($v, $vu)
436
			and $def = $trouver_table($v, $boucle->sql_serveur)
437
			and !in_array($def['table_sql'], $vu)
438
		) {
439
			// ne pas tester les cles qui sont exclues a l'appel
440
			// ie la cle de la jointure precedente
441
			$test_cles = $milieu_exclus;
442
			$new[] = $v;
443
			$max_iter = 50; // securite
444
			while (count($jointure_directe_possible = calculer_chaine_jointures($boucle, $depart, array($v, $def), $vu,
445
					$test_cles, 1))
446
				and $max_iter--) {
447
				$jointure_directe_possible = reset($jointure_directe_possible);
448
				$milieu = end($jointure_directe_possible);
449
				$exclure_fin = $milieu_exclus;
450
				if (is_string($milieu)) {
451
					$exclure_fin[] = $milieu;
452
					$test_cles[] = $milieu;
453
				} else {
454
					$exclure_fin = array_merge($exclure_fin, $milieu);
455
					$test_cles = array_merge($test_cles, $milieu);
456
				}
457
				// essayer de rejoindre l'arrivee a partir de cette etape intermediaire
458
				// sans repasser par la meme cle milieu, ni une cle deja vue !
459
				$r = calculer_chaine_jointures($boucle, array($v, $def), $arrivee, $new, $exclure_fin, $max_liens - 1);
460
				if ($r) {
461
					array_unshift($r, $jointure_directe_possible);
462
463
					return $r;
464
				}
465
			}
466
		}
467
	}
468
469
	return array();
470
}
471
472
/**
473
 * applatit les cles multiples
474
 * redondance avec split_key() ? a mutualiser
475
 *
476
 * @param $keys
477
 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<integer|string>.

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...
478
 */
479
function trouver_cles_table($keys) {
480
	$res = array();
481
	foreach ($keys as $v) {
482
		if (!strpos($v, ",")) {
483
			$res[$v] = 1;
484
		} else {
485
			foreach (preg_split("/\s*,\s*/", $v) as $k) {
486
				$res[$k] = 1;
487
			}
488
		}
489
	}
490
491
	return array_keys($res);
492
}
493
494
495
/**
496
 * Indique si une colonne (ou plusieurs colonnes) est présente dans l'une des tables indiquée.
497
 *
498
 * @param string|array $cle
499
 *     Nom de la ou des colonnes à trouver dans les tables indiquées
500
 * @param array $tables
501
 *     Liste de noms de tables ou des couples (alias => nom de table).
502
 *     - `$boucle->from` (alias => nom de table) : les tables déjà utilisées dans une boucle
503
 *     - `$boucle->jointures` : les tables utilisables en tant que jointure
504
 *     - `$boucle->jointures_explicites` les jointures explicitement indiquées à l'écriture de la boucle
505
 * @param string $connect
506
 *     Nom du connecteur SQL
507
 * @param bool|string $checkarrivee
508
 *     false : peu importe la table, si on trouve le/les champs, c'est bon.
509
 *     string : nom de la table où on veut trouver le champ.
510
 * @return array|false
511
 *     false : on n'a pas trouvé
512
 *     array : infos sur la table trouvée. Les clés suivantes sont retournés :
513
 *     - 'desc' : tableau de description de la table,
514
 *     - 'table' : nom de la table
515
 *     - 'alias' : alias utilisé pour la table (si pertinent. ie: avec `$boucle->from` transmis par exemple)
516
 */
517
function chercher_champ_dans_tables($cle, $tables, $connect, $checkarrivee = false) {
518
	static $trouver_table = '';
519
	if (!$trouver_table) {
520
		$trouver_table = charger_fonction('trouver_table', 'base');
521
	}
522
523
	if (!is_array($cle)) {
524
		$cle = array($cle);
525
	}
526
527
	foreach ($tables as $k => $table) {
528
		if ($table && $desc = $trouver_table($table, $connect)) {
529
			if (isset($desc['field'])
530
				// verifier que toutes les cles cherchees sont la
531
				and (count(array_intersect($cle, array_keys($desc['field']))) == count($cle))
532
				// si on sait ou on veut arriver, il faut que ca colle
533
				and ($checkarrivee == false || $checkarrivee == $desc['table'])
534
			) {
535
				return array(
536
					'desc' => $desc,
537
					'table' => $desc['table'],
538
					'alias' => $k,
539
				);
540
			}
541
		}
542
	}
543
544
	return false;
545
}
546
547
/**
548
 * Cherche une colonne (ou plusieurs colonnes) dans les tables de jointures
549
 * possibles indiquées.
550
 *
551
 * @uses chercher_champ_dans_tables()
552
 * @uses decompose_champ_id_objet()
553
 * @uses liste_champs_jointures()
554
 *
555
 * @param string|array $cle
556
 *     Nom de la ou des colonnes à trouver dans les tables de jointures
557
 * @param array $joints
558
 *     Liste des jointures possibles (ex: $boucle->jointures ou $boucle->jointures_explicites)
559
 * @param Boucle $boucle
560
 *     Description de la boucle
561
 * @param bool|string $checkarrivee
562
 *     false : peu importe la table, si on trouve le/les champs, c'est bon.
563
 *     string : nom de la table jointe où on veut trouver le champ.
564
 * @return array|string
565
 *     chaîne vide : on n'a pas trouvé
566
 *     liste si trouvé : nom de la table, description de la table, clé(s) de la table
567
 */
568
function trouver_champ_exterieur($cle, $joints, &$boucle, $checkarrivee = false) {
569
570
	// support de la recherche multi champ :
571
	// si en seconde etape on a decompose le champ id_xx en id_objet,objet
572
	// on reentre ici soit en cherchant une table les 2 champs id_objet,objet
573
	// soit une table avec les 3 champs id_xx, id_objet, objet
574
	if (!is_array($cle)) {
575
		$cle = array($cle);
576
	}
577
578
	if ($infos = chercher_champ_dans_tables($cle, $joints, $boucle->sql_serveur, $checkarrivee)) {
579
		return array($infos['table'], $infos['desc'], $cle);
580
	}
581
582
	// au premier coup, on essaye de decomposer, si possible
583
	if (count($cle) == 1
584
		and $c = reset($cle)
585
		and is_array($decompose = decompose_champ_id_objet($c))
586
	) {
587
588
		$desc = $boucle->show;
589
590
		// cas 1 : la cle id_xx est dans la table de depart
591
		// -> on cherche uniquement id_objet,objet a l'arrivee
592
		if (isset($desc['field'][$c])) {
593
			$cle = array();
594
			$cle[] = array_shift($decompose); // id_objet
595
			$cle[] = array_shift($decompose); // objet
596
			return trouver_champ_exterieur($cle, $joints, $boucle, $checkarrivee);
597
		}
598
		// cas 2 : la cle id_xx n'est pas dans la table de depart
599
		// -> il faut trouver une cle de depart zzz telle que
600
		// id_objet,objet,zzz soit a l'arrivee
601
		else {
602
			$depart = liste_champs_jointures((isset($desc['table']) ? $desc['table'] : ''), $desc);
603
			foreach ($depart as $d) {
604
				$cle = array();
605
				$cle[] = array_shift($decompose); // id_objet
606
				$cle[] = array_shift($decompose); // objet
607
				$cle[] = $d;
608
				if ($ext = trouver_champ_exterieur($cle, $joints, $boucle, $checkarrivee)) {
609
					return $ext;
610
				}
611
			}
612
		}
613
	}
614
615
	return "";
616
}
617
618
/**
619
 * Cherche a ajouter la possibilite d'interroger un champ sql dans une boucle.
620
 * 
621
 * Cela construira les jointures necessaires
622
 * si une possibilite est trouve et retournera le nom de
623
 * l'alias de la table contenant ce champ
624
 * (L2 par exemple pour 'spip_mots AS L2' dans le FROM),
625
 *
626
 * @uses trouver_champ_exterieur()
627
 * @uses calculer_jointure()
628
 *
629
 * @param string $champ
630
 *    Nom du champ cherche (exemple id_article)
631
 * @param object $boucle
632
 *    Informations connues de la boucle
633
 * @param array $jointures
0 ignored issues
show
Documentation introduced by
Should the type for parameter $jointures not be false|array? 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...
634
 *    Liste des tables parcourues (articles, mots) pour retrouver le champ sql
635
 *    et calculer la jointure correspondante.
636
 *    En son absence et par defaut, on utilise la liste des jointures connues
637
 *    par SPIP pour la table en question ($boucle->jointures)
638
 * @param bool $cond
639
 *     flag pour savoir si le critere est conditionnel ou non
640
 * @param bool|string $checkarrivee
641
 *     false : peu importe la table, si on trouve le/les champs, c'est bon.
642
 *     string : nom de la table jointe où on veut trouver le champ.
643
 *
644
 * @return string
645
 */
646
function trouver_jointure_champ($champ, &$boucle, $jointures = false, $cond = false, $checkarrivee = false) {
647
	if ($jointures === false) {
648
		$jointures = $boucle->jointures;
649
	}
650
	// TODO : aberration, on utilise $jointures pour trouver le champ
651
	// mais pas poour construire la jointure ensuite
652
	$arrivee = trouver_champ_exterieur($champ, $jointures, $boucle, $checkarrivee);
653
	if ($arrivee) {
654
		$desc = $boucle->show;
655
		array_pop($arrivee); // enlever la cle en 3eme argument
656
		$cle = calculer_jointure($boucle, array($desc['id_table'], $desc), $arrivee, '', $cond);
657
		if ($cle) {
658
			return $cle;
659
		}
660
	}
661
	spip_log("trouver_jointure_champ: $champ inconnu");
662
663
	return '';
664
}
665