|
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 |
|
|
|
|
|
|
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); |
|
|
|
|
|
|
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); |
|
|
|
|
|
|
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']) |
|
|
|
|
|
|
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"; |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
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)) { |
|
|
|
|
|
|
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))) { |
|
|
|
|
|
|
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 |
|
|
|
|
|
|
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 |
|
|
|
|
|
|
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
|
|
|
|
This check looks for the generic type
arrayas a return type and suggests a more specific type. This type is inferred from the actual code.