Completed
Push — master ( 2142b8...901fc5 )
by cam
27:22 queued 18:57
created

jointures.php ➔ chercher_champ_dans_tables()   D

Complexity

Conditions 10
Paths 16

Size

Total Lines 29
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 16
nc 16
nop 4
dl 0
loc 29
rs 4.8196
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-2016                                                *
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
 * 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($regs[1]));
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 pour pouvoir etre elimine avec la jointure
175
			// en cas d'optimisation
176
			//$boucle->where[] = array("'='","'$obj'","sql_quote('$type')");
177
			$boucle->join["L$n"] =
178
				$echap ?
179
					array("'$id_table'", "'$j2'", "'$j1'", "'$obj='.sql_quote('$type')")
180
					:
181
					array($id_table, $j2, $j1, "$obj=" . sql_quote($type));
182
		} else {
183
			$boucle->join["L$n"] = $echap ? array("'$id_table'", "'$j'") : array($id_table, $j);
184
		}
185
		$boucle->from[$id_table = "L$n"] = $a[0];
186
	}
187
188
189
	// pas besoin de group by
190
	// (cf http://article.gmane.org/gmane.comp.web.spip.devel/30555)
191
	// si une seule jointure et sur une table avec primary key formee
192
	// de l'index principal et de l'index de jointure (non conditionnel! [6031])
193
	// et operateur d'egalite (http://trac.rezo.net/trac/spip/ticket/477)
194
195
	if ($pk = (isset($a[1]) && (count($boucle->from) == 2) && !$cond)) {
196
		$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...
197
	}
198
199
	// pas de group by 
200
	// si une seule jointure
201
	// et si l'index de jointure est une primary key a l'arrivee !
202
	if (!$pk
203
		and (count($boucle->from) == 2)
204
		and isset($a[1]['key']['PRIMARY KEY'])
205
		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...
206
	) {
207
		$pk = true;
208
	}
209
210
	// la clause Group by est en conflit avec ORDER BY, a completer
211
	$groups = liste_champs_jointures($nom, $desc, true);
212
	if (!$pk) {
213
		foreach ($groups as $id_prim) {
214
			$id_field = $nom . '.' . $id_prim;
215
			if (!in_array($id_field, $boucle->group)) {
216
				$boucle->group[] = $id_field;
217
			}
218
		}
219
	}
220
221
	$boucle->modificateur['lien'] = true;
222
223
	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...
224
}
225
226
/**
227
 * Condition suffisante pour qu'un Group-By ne soit pas nécéssaire
228
 *
229
 * À améliorer, notamment voir si calculer_select ne pourrait pas la réutiliser
230
 * lorsqu'on sait si le critere conditionnel est finalement present
231
 *
232
 * @param array $depart
233
 * @param array $arrivee
234
 * @param string|array $col
235
 * @return bool
236
 */
237
function nogroupby_if($depart, $arrivee, $col) {
238
	$pk = $arrivee['key']['PRIMARY KEY'];
239
	if (!$pk) {
240
		return false;
241
	}
242
	$id_primary = $depart['key']['PRIMARY KEY'];
243
	if (is_array($col)) {
244
		$col = implode(', *', $col);
245
	} // cas id_objet, objet
246
	return (preg_match("/^$id_primary, *$col$/", $pk) or
247
		preg_match("/^$col, *$id_primary$/", $pk));
248
}
249
250
/**
251
 * Lister les champs candidats a une jointure, sur une table
252
 * si un join est fourni dans la description, c'est lui qui l'emporte
253
 * sauf si cle primaire explicitement demandee par $primary
254
 *
255
 * sinon on construit une liste des champs a partir de la liste des cles de la table
256
 *
257
 * @uses split_key()
258
 * @param string $nom
259
 * @param array $desc
260
 * @param bool $primary
261
 * @return array
262
 */
263
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...
264
265
	static $nojoin = array('idx', 'maj', 'date', 'statut');
266
267
	// si cle primaire demandee, la privilegier
268
	if ($primary && isset($desc['key']['PRIMARY KEY'])) {
269
		return split_key($desc['key']['PRIMARY KEY']);
270
	}
271
272
	// les champs declares explicitement pour les jointures
273
	if (isset($desc['join'])) {
274
		return $desc['join'];
275
	}
276
	/*elseif (isset($GLOBALS['tables_principales'][$nom]['join'])) return $GLOBALS['tables_principales'][$nom]['join'];
277
	elseif (isset($GLOBALS['tables_auxiliaires'][$nom]['join'])) return $GLOBALS['tables_auxiliaires'][$nom]['join'];*/
278
279
	// si pas de cle, c'est fichu
280
	if (!isset($desc['key'])) {
281
		return array();
282
	}
283
284
	// si cle primaire
285
	if (isset($desc['key']['PRIMARY KEY'])) {
286
		return split_key($desc['key']['PRIMARY KEY']);
287
	}
288
289
	// ici on se rabat sur les cles secondaires, 
290
	// en eliminant celles qui sont pas pertinentes (idx, maj)
291
	// si jamais le resultat n'est pas pertinent pour une table donnee,
292
	// il faut declarer explicitement le champ 'join' de sa description
293
294
	$join = array();
295
	foreach ($desc['key'] as $v) {
296
		$join = split_key($v, $join);
297
	}
298
	foreach ($join as $k) {
299
		if (in_array($k, $nojoin)) {
300
			unset($join[$k]);
301
		}
302
	}
303
304
	return $join;
305
}
306
307
/**
308
 * Eclater une cle composee en plusieurs champs
309
 *
310
 * @param string $v
311
 * @param array $join
312
 * @return array
313
 */
314
function split_key($v, $join = array()) {
315
	foreach (preg_split('/,\s*/', $v) as $k) {
316
		$join[$k] = $k;
317
	}
318
319
	return $join;
320
}
321
322
/**
323
 * Constuire la chaine de jointures, de proche en proche
324
 *
325
 * @uses liste_champs_jointures()
326
 * @uses trouver_champs_decomposes()
327
 *
328
 * @param objetc $boucle
329
 * @param array $depart
330
 *  sous la forme array(nom de la table, description)
331
 * @param array $arrivee
332
 *  sous la forme array(nom de la table, description)
333
 * @param array $vu
334
 *  tables deja vues dans la jointure, pour ne pas y repasser
335
 * @param array $milieu_exclus
336
 *  cles deja utilisees, pour ne pas les reutiliser
337
 * @param int $max_liens
338
 *  nombre maxi d'etapes
339
 * @return array
340
 */
341
function calculer_chaine_jointures(
342
	&$boucle,
343
	$depart,
344
	$arrivee,
345
	$vu = array(),
346
	$milieu_exclus = array(),
347
	$max_liens = 5
348
) {
349
	static $trouver_table;
350
	if (!$trouver_table) {
351
		$trouver_table = charger_fonction('trouver_table', 'base');
352
	}
353
354
	if (is_string($milieu_exclus)) {
355
		$milieu_exclus = array($milieu_exclus);
356
	}
357
	// quand on a exclus id_objet comme cle de jointure, il faut aussi exclure objet
358
	// faire une jointure sur objet tout seul n'a pas de sens
359
	if (in_array('id_objet', $milieu_exclus) and !in_array('objet', $milieu_exclus)) {
360
		$milieu_exclus[] = 'objet';
361
	}
362
363
	list($dnom, $ddesc) = $depart;
364
	list($anom, $adesc) = $arrivee;
365
	if (!count($vu)) {
366
		$vu[] = $dnom; // ne pas oublier la table de depart
367
		$vu[] = $anom; // ne pas oublier la table d'arrivee
368
	}
369
370
	$akeys = array();
371
	foreach ($adesc['key'] as $k) {
372
		// respecter l'ordre de $adesc['key'] pour ne pas avoir id_trad en premier entre autres...
373
		$akeys = array_merge($akeys, preg_split('/,\s*/', $k));
374
	}
375
376
	// enlever les cles d'arrivee exclues par l'appel
377
	$akeys = array_diff($akeys, $milieu_exclus);
378
379
	// cles candidates au depart
380
	$keys = liste_champs_jointures($dnom, $ddesc);
381
	// enlever les cles dde depart exclues par l'appel
382
	$keys = array_diff($keys, $milieu_exclus);
383
384
	$v = !$keys ? false : array_intersect(array_values($keys), $akeys);
385
386
	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...
387
		return array(array($dnom, array($adesc['table'], $adesc), array_shift($v)));
388
	}
389
390
	// regarder si l'on a (id_objet,objet) au depart et si on peut le mapper sur un id_xx
391
	if (count(array_intersect(array('id_objet', 'objet'), $keys)) == 2) {
392
		// regarder si l'une des cles d'arrivee peut se decomposer en 
393
		// id_objet,objet
394
		// si oui on la prend
395
		foreach ($akeys as $key) {
396
			$v = decompose_champ_id_objet($key);
397 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...
398
				$objet = array_shift($v); // objet,'article'
399
				array_unshift($v, $key); // id_article,objet,'article'
400
				array_unshift($v, $objet); // id_objet,id_article,objet,'article'
401
				return array(array($dnom, array($adesc['table'], $adesc), $v));
402
			}
403
		}
404
	} else {
405
		// regarder si l'une des cles de depart peut se decomposer en 
406
		// id_objet,objet a l'arrivee
407
		// si oui on la prend
408
		foreach ($keys as $key) {
409
			if (count($v = trouver_champs_decomposes($key, $adesc)) > 1) {
410 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...
411
					$v = decompose_champ_id_objet($key); // id_objet,objet,'article'
412
					array_unshift($v, $key); // id_article,id_objet,objet,'article'
413
					return array(array($dnom, array($adesc['table'], $adesc), $v));
414
				}
415
			}
416
		}
417
	}
418
	// si l'on voulait une jointure direct, c'est rate !
419
	if ($max_liens <= 1) {
420
		return array();
421
	}
422
423
	// sinon essayer de passer par une autre table
424
	$new = $vu;
425
	foreach ($boucle->jointures as $v) {
426
		if ($v
427
			and !in_array($v, $vu)
428
			and $def = $trouver_table($v, $boucle->sql_serveur)
429
			and !in_array($def['table_sql'], $vu)
430
		) {
431
			// ne pas tester les cles qui sont exclues a l'appel
432
			// ie la cle de la jointure precedente
433
			$test_cles = $milieu_exclus;
434
			$new[] = $v;
435
			$max_iter = 50; // securite
436
			while (count($jointure_directe_possible = calculer_chaine_jointures($boucle, $depart, array($v, $def), $vu,
437
					$test_cles, 1))
438
				and $max_iter--) {
439
				$jointure_directe_possible = reset($jointure_directe_possible);
440
				$milieu = end($jointure_directe_possible);
441
				$exclure_fin = $milieu_exclus;
442
				if (is_string($milieu)) {
443
					$exclure_fin[] = $milieu;
444
					$test_cles[] = $milieu;
445
				} else {
446
					$exclure_fin = array_merge($exclure_fin, $milieu);
447
					$test_cles = array_merge($test_cles, $milieu);
448
				}
449
				// essayer de rejoindre l'arrivee a partir de cette etape intermediaire
450
				// sans repasser par la meme cle milieu, ni une cle deja vue !
451
				$r = calculer_chaine_jointures($boucle, array($v, $def), $arrivee, $new, $exclure_fin, $max_liens - 1);
452
				if ($r) {
453
					array_unshift($r, $jointure_directe_possible);
454
455
					return $r;
456
				}
457
			}
458
		}
459
	}
460
461
	return array();
462
}
463
464
/**
465
 * applatit les cles multiples
466
 * redondance avec split_key() ? a mutualiser
467
 *
468
 * @param $keys
469
 * @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...
470
 */
471
function trouver_cles_table($keys) {
472
	$res = array();
473
	foreach ($keys as $v) {
474
		if (!strpos($v, ",")) {
475
			$res[$v] = 1;
476
		} else {
477
			foreach (preg_split("/\s*,\s*/", $v) as $k) {
478
				$res[$k] = 1;
479
			}
480
		}
481
	}
482
483
	return array_keys($res);
484
}
485
486
487
/**
488
 * Indique si une colonne (ou plusieurs colonnes) est présente dans l'une des tables indiquée.
489
 *
490
 * @param string|array $cle
491
 *     Nom de la ou des colonnes à trouver dans les tables indiquées
492
 * @param array $tables
493
 *     Liste de noms de tables ou des couples (alias => nom de table).
494
 *     - `$boucle->from` (alias => nom de table) : les tables déjà utilisées dans une boucle
495
 *     - `$boucle->jointures` : les tables utilisables en tant que jointure
496
 *     - `$boucle->jointures_explicites` les jointures explicitement indiquées à l'écriture de la boucle
497
 * @param string $connect
498
 *     Nom du connecteur SQL
499
 * @param bool|string $checkarrivee
500
 *     false : peu importe la table, si on trouve le/les champs, c'est bon.
501
 *     string : nom de la table où on veut trouver le champ.
502
 * @return array|false
503
 *     false : on n'a pas trouvé
504
 *     array : infos sur la table trouvée. Les clés suivantes sont retournés :
505
 *     - 'desc' : tableau de description de la table,
506
 *     - 'table' : nom de la table
507
 *     - 'alias' : alias utilisé pour la table (si pertinent. ie: avec `$boucle->from` transmis par exemple)
508
 */
509
function chercher_champ_dans_tables($cle, $tables, $connect, $checkarrivee = false) {
510
	static $trouver_table = '';
511
	if (!$trouver_table) {
512
		$trouver_table = charger_fonction('trouver_table', 'base');
513
	}
514
515
	if (!is_array($cle)) {
516
		$cle = array($cle);
517
	}
518
519
	foreach ($tables as $k => $table) {
520
		if ($table && $desc = $trouver_table($table, $connect)) {
521
			if (isset($desc['field'])
522
				// verifier que toutes les cles cherchees sont la
523
				and (count(array_intersect($cle, array_keys($desc['field']))) == count($cle))
524
				// si on sait ou on veut arriver, il faut que ca colle
525
				and ($checkarrivee == false || $checkarrivee == $desc['table'])
526
			) {
527
				return array(
528
					'desc' => $desc,
529
					'table' => $desc['table'],
530
					'alias' => $k,
531
				);
532
			}
533
		}
534
	}
535
536
	return false;
537
}
538
539
/**
540
 * Cherche une colonne (ou plusieurs colonnes) dans les tables de jointures
541
 * possibles indiquées.
542
 *
543
 * @uses chercher_champ_dans_tables()
544
 * @uses decompose_champ_id_objet()
545
 * @uses liste_champs_jointures()
546
 *
547
 * @param string|array $cle
548
 *     Nom de la ou des colonnes à trouver dans les tables de jointures
549
 * @param array $joints
550
 *     Liste des jointures possibles (ex: $boucle->jointures ou $boucle->jointures_explicites)
551
 * @param Boucle $boucle
552
 *     Description de la boucle
553
 * @param bool|string $checkarrivee
554
 *     false : peu importe la table, si on trouve le/les champs, c'est bon.
555
 *     string : nom de la table jointe où on veut trouver le champ.
556
 * @return array|string
557
 *     chaîne vide : on n'a pas trouvé
558
 *     liste si trouvé : nom de la table, description de la table, clé(s) de la table
559
 */
560
function trouver_champ_exterieur($cle, $joints, &$boucle, $checkarrivee = false) {
561
562
	// support de la recherche multi champ :
563
	// si en seconde etape on a decompose le champ id_xx en id_objet,objet
564
	// on reentre ici soit en cherchant une table les 2 champs id_objet,objet
565
	// soit une table avec les 3 champs id_xx, id_objet, objet
566
	if (!is_array($cle)) {
567
		$cle = array($cle);
568
	}
569
570
	if ($infos = chercher_champ_dans_tables($cle, $joints, $boucle->sql_serveur, $checkarrivee)) {
571
		return array($infos['table'], $infos['desc'], $cle);
572
	}
573
574
	// au premier coup, on essaye de decomposer, si possible
575
	if (count($cle) == 1
576
		and $c = reset($cle)
577
		and is_array($decompose = decompose_champ_id_objet($c))
578
	) {
579
580
		$desc = $boucle->show;
581
582
		// cas 1 : la cle id_xx est dans la table de depart
583
		// -> on cherche uniquement id_objet,objet a l'arrivee
584
		if (isset($desc['field'][$c])) {
585
			$cle = array();
586
			$cle[] = array_shift($decompose); // id_objet
587
			$cle[] = array_shift($decompose); // objet
588
			return trouver_champ_exterieur($cle, $joints, $boucle, $checkarrivee);
589
		}
590
		// cas 2 : la cle id_xx n'est pas dans la table de depart
591
		// -> il faut trouver une cle de depart zzz telle que
592
		// id_objet,objet,zzz soit a l'arrivee
593
		else {
594
			$depart = liste_champs_jointures((isset($desc['table']) ? $desc['table'] : ''), $desc);
595
			foreach ($depart as $d) {
596
				$cle = array();
597
				$cle[] = array_shift($decompose); // id_objet
598
				$cle[] = array_shift($decompose); // objet
599
				$cle[] = $d;
600
				if ($ext = trouver_champ_exterieur($cle, $joints, $boucle, $checkarrivee)) {
601
					return $ext;
602
				}
603
			}
604
		}
605
	}
606
607
	return "";
608
}
609
610
/**
611
 * Cherche a ajouter la possibilite d'interroger un champ sql dans une boucle.
612
 * 
613
 * Cela construira les jointures necessaires
614
 * si une possibilite est trouve et retournera le nom de
615
 * l'alias de la table contenant ce champ
616
 * (L2 par exemple pour 'spip_mots AS L2' dans le FROM),
617
 *
618
 * @uses trouver_champ_exterieur()
619
 * @uses calculer_jointure()
620
 *
621
 * @param string $champ
622
 *    Nom du champ cherche (exemple id_article)
623
 * @param object $boucle
624
 *    Informations connues de la boucle
625
 * @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...
626
 *    Liste des tables parcourues (articles, mots) pour retrouver le champ sql
627
 *    et calculer la jointure correspondante.
628
 *    En son absence et par defaut, on utilise la liste des jointures connues
629
 *    par SPIP pour la table en question ($boucle->jointures)
630
 * @param bool $cond
631
 *     flag pour savoir si le critere est conditionnel ou non
632
 * @param bool|string $checkarrivee
633
 *     false : peu importe la table, si on trouve le/les champs, c'est bon.
634
 *     string : nom de la table jointe où on veut trouver le champ.
635
 *
636
 * @return string
637
 */
638
function trouver_jointure_champ($champ, &$boucle, $jointures = false, $cond = false, $checkarrivee = false) {
639
	if ($jointures === false) {
640
		$jointures = $boucle->jointures;
641
	}
642
	// TODO : aberration, on utilise $jointures pour trouver le champ
643
	// mais pas poour construire la jointure ensuite
644
	$arrivee = trouver_champ_exterieur($champ, $jointures, $boucle, $checkarrivee);
645
	if ($arrivee) {
646
		$desc = $boucle->show;
647
		array_pop($arrivee); // enlever la cle en 3eme argument
648
		$cle = calculer_jointure($boucle, array($desc['id_table'], $desc), $arrivee, '', $cond);
649
		if ($cle) {
650
			return $cle;
651
		}
652
	}
653
	spip_log("trouver_jointure_champ: $champ inconnu");
654
655
	return '';
656
}
657