b5c2e35c3c2ee969bc98754b553f3f73d9637957
4 /***************************************************************************\
5 * SPIP, Systeme de publication pour l'internet *
7 * Copyright (c) 2001-2019 *
8 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
10 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
11 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
12 \***************************************************************************/
14 if (!defined('_ECRIRE_INC_VERSION')) {
19 * Fabrique d'iterateur
20 * permet de charger n'importe quel iterateur IterateurXXX
21 * fourni dans le fichier iterateurs/xxx.php
25 public static function create($iterateur, $command, $info = null) {
27 // cas des SI {si expression} analises tres tot
28 // pour eviter le chargement de tout iterateur
29 if (isset($command['si'])) {
30 foreach ($command['si'] as $si) {
32 // $command pour boucle SQL peut generer des erreurs de compilation
33 // s'il est transmis alors qu'on est dans un iterateur vide
34 return new IterDecorator(new EmptyIterator(), array(), $info);
39 // chercher un iterateur PHP existant (par exemple dans SPL)
40 // (il faudrait passer l'argument ->sql_serveur
41 // pour etre certain qu'on est sur un "php:")
42 if (class_exists($iterateur)) {
43 $a = isset($command['args']) ?
$command['args'] : array();
45 // permettre de passer un Iterateur directement {args #ITERATEUR} :
46 // si on recoit deja un iterateur en argument, on l'utilise
47 if (count($a) == 1 and is_object($a[0]) and is_subclass_of($a[0], 'Iterator')) {
50 // sinon, on cree un iterateur du type donne
52 // arguments de creation de l'iterateur...
57 $iter = new $iterateur();
60 $iter = new $iterateur($a[0]);
63 $iter = new $iterateur($a[0], $a[1]);
66 $iter = new $iterateur($a[0], $a[1], $a[2]);
69 $iter = new $iterateur($a[0], $a[1], $a[2], $a[3]);
72 } catch (Exception
$e) {
73 spip_log("Erreur de chargement de l'iterateur $iterateur");
74 spip_log($e->getMessage());
75 $iter = new EmptyIterator();
79 // chercher la classe d'iterateur
81 // definie dans le fichier iterateurs/xxx.php
82 $class = "Iterateur" . $iterateur;
83 if (!class_exists($class)) {
84 if (!include_spip("iterateur/" . strtolower($iterateur))
85 or !class_exists($class)
87 die("Iterateur $iterateur non trouvé");
88 // si l'iterateur n'existe pas, on se rabat sur le generique
89 # $iter = new EmptyIterator();
92 $iter = new $class($command, $info);
95 return new IterDecorator($iter, $command, $info);
100 class IterDecorator
extends FilterIterator
{
104 * Conditions de filtrage
105 * ie criteres de selection
109 protected $filtre = array();
112 * Fonction de filtrage compilee a partir des criteres de filtre
116 protected $func_filtre = null;
119 * Critere {offset, limit}
124 protected $offset = null;
125 protected $limit = null;
128 * nombre d'elements recuperes depuis la position 0,
129 * en tenant compte des filtres
133 protected $fetched = 0;
136 * Y a t'il une erreur ?
140 protected $err = false;
143 * Drapeau a activer en cas d'echec
144 * (select SQL errone, non chargement des DATA, etc)
146 public function err() {
147 if (method_exists($this->iter
, 'err')) {
148 return $this->iter
->err();
150 if (property_exists($this->iter
, 'err')) {
151 return $this->iter
->err
;
157 public function __construct(Iterator
$iter, $command, $info) {
158 parent
::__construct($iter);
159 parent
::rewind(); // remettre a la premiere position (bug? connu de FilterIterator)
161 // recuperer l'iterateur transmis
162 $this->iter
= $this->getInnerIterator();
163 $this->command
= $command;
168 // chercher la liste des champs a retourner par
169 // fetch si l'objet ne les calcule pas tout seul
170 if (!method_exists($this->iter
, 'fetch')) {
171 $this->calculer_select();
172 $this->calculer_filtres();
175 // emptyIterator critere {si} faux n'a pas d'erreur !
176 if (isset($this->iter
->err
)) {
177 $this->err
= $this->iter
->err
;
180 // pas d'init a priori, le calcul ne sera fait qu'en cas de besoin (provoque une double requete souvent inutile en sqlite)
181 //$this->total = $this->count();
185 // calcule les elements a retournes par fetch()
186 // enleve les elements inutiles du select()
188 private function calculer_select() {
189 if ($select = &$this->command
['select']) {
190 foreach ($select as $s) {
195 $this->select
[] = $s;
200 // recuperer la valeur d'une balise #X
201 // en fonction des methodes
202 // et proprietes disponibles
203 public function get_select($nom) {
204 if (is_object($this->iter
)
205 and method_exists($this->iter
, $nom)
208 return $this->iter
->$nom();
209 } catch (Exception
$e) {
210 // #GETCHILDREN sur un fichier de DirectoryIterator ...
211 spip_log("Methode $nom en echec sur " . get_class($this->iter
));
212 spip_log("Cela peut être normal : retour d'une ligne de resultat ne pouvant pas calculer cette methode");
218 if (property_exists($this->iter, $nom)) {
219 return $this->iter->$nom;
221 // cle et valeur par defaut
222 // ICI PLANTAGE SI ON NE CONTROLE PAS $nom
223 if (in_array($nom, array('cle', 'valeur'))
224 and method_exists($this, $nom)
226 return $this->$nom();
229 // Par defaut chercher en xpath dans la valeur()
230 return table_valeur($this->valeur(), $nom, null);
234 private function calculer_filtres() {
236 // Issu de calculer_select() de public/composer L.519
237 // TODO: externaliser...
239 // retirer les criteres vides:
240 // {X ?} avec X absent de l'URL
241 // {par #ENV{X}} avec X absent de l'URL
242 // IN sur collection vide (ce dernier devrait pouvoir etre fait a la compil)
243 if ($where = &$this->command
['where']) {
244 foreach ($where as $k => $v) {
246 if ((count($v) >= 2) && ($v[0] == 'REGEXP') && ($v[2] == "'.*'")) {
248 } elseif ((count($v) >= 2) && ($v[0] == 'LIKE') && ($v[2] == "'%'")) {
251 $op = $v[0] ?
$v[0] : $v;
256 if ((!$op) or ($op == 1) or ($op == '0=0')) {
259 // traiter {cle IN a,b} ou {valeur !IN a,b}
260 // prendre en compte le cas particulier de sous-requetes
261 // produites par sql_in quand plus de 255 valeurs passees a IN
262 if (preg_match_all(',\s+IN\s+(\(.*\)),', $op, $s_req)) {
264 foreach ($s_req[1] as $key => $val) {
265 $req .= trim($val, '(,)') . ',';
267 $req = '(' . rtrim($req, ',') . ')';
269 if (preg_match(',^\(\(([\w/]+)(\s+NOT)?\s+IN\s+(\(.*\))\)(?:\s+(AND|OR)\s+\(([\w/]+)(\s+NOT)?\s+IN\s+(\(.*\))\))*\)$,',
271 $this->ajouter_filtre($regs[1], 'IN', strlen($req) ?
$req : $regs[3], $regs[2]);
272 unset($op, $where[$k]);
275 foreach ($where as $k => $v) {
276 // 3 possibilites : count($v) =
277 // * 1 : {x y} ; on recoit $v[0] = y
278 // * 2 : {x !op y} ; on recoit $v[0] = 'NOT', $v[1] = array() // array du type {x op y}
279 // * 3 : {x op y} ; on recoit $v[0] = 'op', $v[1] = x, $v[2] = y
281 // 1 : forcement traite par un critere, on passe
282 if (!$v or count($v) == 1) {
285 if (count($v) == 2 and is_array($v[1])) {
286 $this->ajouter_filtre($v[1][1], $v[1][0], $v[1][2], 'NOT');
288 if (count($v) == 3) {
289 $this->ajouter_filtre($v[1], $v[0], $v[2]);
295 if (isset($this->command
['limit']) and $this->command
['limit']) {
296 $limit = explode(',', $this->command
['limit']);
297 $this->offset
= $limit[0];
298 $this->limit
= $limit[1];
301 // Creer la fonction de filtrage sur $this
303 $filtres = 'return (' . join(') AND (', $this->filtre
) . ');';
304 $this->func_filtre
= function () use ($filtres) {
305 return eval($filtres);
311 protected function ajouter_filtre($cle, $op, $valeur, $not = false) {
312 if (method_exists($this->iter
, 'exception_des_criteres')) {
313 if (in_array($cle, $this->iter
->exception_des_criteres())) {
317 // TODO: analyser le filtre pour refuser ce qu'on ne sait pas traiter ?
318 # mais c'est normalement deja opere par calculer_critere_infixe()
319 # qui regarde la description 'desc' (en casse reelle d'ailleurs : {isDir=1}
320 # ne sera pas vu si l'on a defini desc['field']['isdir'] pour que #ISDIR soit present.
321 # il faudrait peut etre definir les 2 champs isDir et isdir... a reflechir...
323 # if (!in_array($cle, array('cle', 'valeur')))
326 $a = '$this->get_select(\'' . $cle . '\')';
330 if ($op == 'REGEXP') {
331 $filtre = 'match(' . $a . ', ' . str_replace('\"', '"', $valeur) . ')';
335 $valeur = str_replace(array('\"', '_', '%'), array('"', '.', '.*'), preg_quote($valeur));
336 $filtre = 'match(' . $a . ', ' . $valeur . ')';
343 $filtre = 'in_array(' . $a . ', array' . $valeur . ')';
346 if (!in_array($op, array('<', '<=', '>', '>='))) {
347 spip_log('operateur non reconnu ' . $op); // [todo] mettre une erreur de squelette
356 $filtre = $a . $op . str_replace('\"', '"', $valeur);
360 $filtre = "!($filtre)";
364 $this->filtre
[] = $filtre;
369 public function next() {
379 public function rewind() {
386 # Extension SPIP des iterateurs PHP
388 * type de l'iterateur
395 * parametres de l'iterateur
402 * infos de compilateur
409 * position courante de l'iterateur
413 protected $pos = null;
416 * nombre total resultats dans l'iterateur
420 protected $total = null;
423 * nombre maximal de recherche pour $total
424 * si l'iterateur n'implemente pas de fonction specifique
426 protected $max = 100000;
430 * Liste des champs a inserer dans les $row
431 * retournes par ->fetch()
433 protected $select = array();
437 * aller a la position absolue n,
438 * comptee depuis le debut
442 * @param string $continue
445 * success or fail if not implemented
447 public function seek($n = 0, $continue = null) {
448 if ($this->func_filtre
or !method_exists($this->iter
, 'seek') or !$this->iter
->seek($n)) {
449 $this->seek_loop($n);
458 * aller a la position $n en parcourant
459 * un par un tous les elements
461 private function seek_loop($n) {
462 if ($this->pos
> $n) {
466 while ($this->pos
< $n and $this->valid()) {
474 * Avancer de $saut pas
480 public function skip($saut, $max = null) {
481 // pas de saut en arriere autorise pour cette fonction
482 if (($saut = intval($saut)) <= 0) {
485 $seek = $this->pos +
$saut;
486 // si le saut fait depasser le maxi, on libere la resource
489 $max = $this->count();
492 if ($seek >= $max or $seek >= $this->count()) {
493 // sortie plus rapide que de faire next() jusqu'a la fin !
505 * Renvoyer un tableau des donnees correspondantes
506 * a la position courante de l'iterateur
507 * en controlant si on respecte le filtre
508 * Appliquer aussi le critere {offset,limit}
512 public function fetch() {
513 if (method_exists($this->iter
, 'fetch')) {
514 return $this->iter
->fetch();
517 while ($this->valid()
520 or (isset($this->offset
) and $this->fetched++
< $this->offset
)
525 if (!$this->valid()) {
529 if (isset($this->limit
)
530 and $this->fetched
> $this->offset +
$this->limit
536 foreach ($this->select
as $nom) {
537 $r[$nom] = $this->get_select($nom);
545 // retourner la cle pour #CLE
546 public function cle() {
550 // retourner la valeur pour #VALEUR
551 public function valeur() {
552 # attention PHP est mechant avec les objets, parfois il ne les
553 # clone pas proprement (directoryiterator sous php 5.2.2)
554 # on se rabat sur la version __toString()
555 if (is_object($v = $this->current())) {
556 if (method_exists($v, '__toString')) {
557 $v = $v->__toString();
567 * Accepte-t-on l'entree courante lue ?
568 * On execute les filtres pour le savoir.
570 public function accept() {
571 if ($f = $this->func_filtre
) {
579 * liberer la ressource
583 public function free() {
584 if (method_exists($this->iter
, 'free')) {
587 $this->pos
= $this->total
= 0;
593 * Compter le nombre total de resultats
598 public function count() {
599 if (is_null($this->total
)) {
600 if (method_exists($this->iter
, 'count')
601 and !$this->func_filtre
603 return $this->total
= $this->iter
->count();
605 // compter les lignes et rembobiner
607 $pos = $this->pos
; // sauver la position
609 while ($this->fetch() and $total < $this->max
) {
613 $this->total
= $total;