iterateur = 'DATA'; # designe la classe d'iterateur $b->show = array( 'field' => array( 'cle' => 'STRING', 'valeur' => 'STRING', '*' => 'ALL' // Champ joker * ) ); $b->select[] = '.valeur'; return $b; } /** * Itérateur DATA * * Pour itérer sur des données quelconques (transformables en tableau) */ class IterateurDATA implements Iterator { /** * tableau de donnees * * @var array */ protected $tableau = array(); /** * Conditions de filtrage * ie criteres de selection * * @var array */ protected $filtre = array(); /** * Cle courante * * @var null */ protected $cle = null; /** * Valeur courante * * @var null */ protected $valeur = null; /** * Erreur presente ? * * @var bool **/ public $err = false; /** * Calcul du total des elements * * @var int|null **/ public $total = null; /** * Constructeur * * @param $command * @param array $info */ public function __construct($command, $info = array()) { $this->type = 'DATA'; $this->command = $command; $this->info = $info; $this->select($command); } /** * Revenir au depart * * @return void */ public function rewind() { reset($this->tableau); $this->cle = key($this->tableau); $this->valeur = current($this->tableau); next($this->tableau); } /** * Déclarer les critères exceptions * * @return array */ public function exception_des_criteres() { return array('tableau'); } /** * Récupérer depuis le cache si possible * * @param string $cle * @return mixed */ protected function cache_get($cle) { if (!$cle) { return; } # utiliser memoization si dispo if (!function_exists('cache_get')) { return; } return cache_get($cle); } /** * Stocker en cache si possible * * @param string $cle * @param int $ttl * @param null|mixed $valeur * @return bool */ protected function cache_set($cle, $ttl, $valeur = null) { if (!$cle) { return; } if (is_null($valeur)) { $valeur = $this->tableau; } # utiliser memoization si dispo if (!function_exists('cache_set')) { return; } return cache_set($cle, array( 'data' => $valeur, 'time' => time(), 'ttl' => $ttl ), 3600 + $ttl); # conserver le cache 1h de plus que la validite demandee, # pour le cas ou le serveur distant ne reponde plus } /** * Aller chercher les données de la boucle DATA * * @throws Exception * @param array $command * @return void */ protected function select($command) { // l'iterateur DATA peut etre appele en passant (data:type) // le type se retrouve dans la commande 'from' // dans ce cas la le critere {source}, si present, n'a pas besoin du 1er argument if (isset($this->command['from'][0])) { if (isset($this->command['source']) and is_array($this->command['source'])) { array_unshift($this->command['source'], $this->command['sourcemode']); } $this->command['sourcemode'] = $this->command['from'][0]; } // cherchons differents moyens de creer le tableau de donnees // les commandes connues pour l'iterateur DATA // sont : {tableau #ARRAY} ; {cle=...} ; {valeur=...} // {source format, [URL], [arg2]...} if (isset($this->command['source']) and isset($this->command['sourcemode']) ) { $this->select_source(); } // Critere {liste X1, X2, X3} if (isset($this->command['liste'])) { $this->select_liste(); } if (isset($this->command['enum'])) { $this->select_enum(); } // Si a ce stade on n'a pas de table, il y a un bug if (!is_array($this->tableau)) { $this->err = true; spip_log("erreur datasource " . var_export($command, true)); } // {datapath query.results} // extraire le chemin "query.results" du tableau de donnees if (!$this->err and isset($this->command['datapath']) and is_array($this->command['datapath']) ) { $this->select_datapath(); } // tri {par x} if ($this->command['orderby']) { $this->select_orderby(); } // grouper les resultats {fusion /x/y/z} ; if ($this->command['groupby']) { $this->select_groupby(); } $this->rewind(); #var_dump($this->tableau); } /** * Aller chercher les donnees de la boucle DATA * depuis une source * {source format, [URL], [arg2]...} */ protected function select_source() { # un peu crado : avant de charger le cache il faut charger # les class indispensables, sinon PHP ne saura pas gerer # l'objet en cache ; cf plugins/icalendar # perf : pas de fonction table_to_array ! (table est deja un array) if (isset($this->command['sourcemode']) and !in_array($this->command['sourcemode'], array('table', 'array', 'tableau')) ) { charger_fonction($this->command['sourcemode'] . '_to_array', 'inc', true); } # le premier argument peut etre un array, une URL etc. $src = $this->command['source'][0]; # avons-nous un cache dispo ? $cle = null; if (is_string($src)) { $cle = 'datasource_' . md5($this->command['sourcemode'] . ':' . var_export($this->command['source'], true)); } $cache = $this->cache_get($cle); if (isset($this->command['datacache'])) { $ttl = intval($this->command['datacache']); } if ($cache and ($cache['time'] + (isset($ttl) ? $ttl : $cache['ttl']) > time()) and !(_request('var_mode') === 'recalcul' and include_spip('inc/autoriser') and autoriser('recalcul') ) ) { $this->tableau = $cache['data']; } else { try { # dommage que ca ne soit pas une option de yql_to_array... if ($this->command['sourcemode'] == 'yql') { if (!isset($ttl)) { $ttl = 3600; } } if (isset($this->command['sourcemode']) and in_array($this->command['sourcemode'], array('table', 'array', 'tableau')) ) { if (is_array($a = $src) or (is_string($a) and $a = str_replace('"', '"', $a) # fragile! and is_array($a = @unserialize($a))) ) { $this->tableau = $a; } } else { if (tester_url_absolue($src)) { include_spip('inc/distant'); $u = recuperer_page($src, false, false, _DATA_SOURCE_MAX_SIZE); if (!$u) { throw new Exception("404"); } if (!isset($ttl)) { $ttl = 24 * 3600; } } else { if (@is_dir($src)) { $u = $src; if (!isset($ttl)) { $ttl = 10; } } else { if (@is_readable($src) && @is_file($src)) { $u = spip_file_get_contents($src); if (!isset($ttl)) { $ttl = 10; } } else { $u = $src; if (!isset($ttl)) { $ttl = 10; } } } } if (!$this->err and $g = charger_fonction($this->command['sourcemode'] . '_to_array', 'inc', true) ) { $args = $this->command['source']; $args[0] = $u; if (is_array($a = call_user_func_array($g, $args))) { $this->tableau = $a; } } } if (!is_array($this->tableau)) { $this->err = true; } if (!$this->err and isset($ttl) and $ttl > 0) { $this->cache_set($cle, $ttl); } } catch (Exception $e) { $e = $e->getMessage(); $err = sprintf("[%s, %s] $e", $src, $this->command['sourcemode']); erreur_squelette(array($err, array())); $this->err = true; } } # en cas d'erreur, utiliser le cache si encore dispo if ($this->err and $cache ) { $this->tableau = $cache['data']; $this->err = false; } } /** * Retourne un tableau donne depuis un critère liste * * Critère `{liste X1, X2, X3}` * * @see critere_DATA_liste_dist() * **/ protected function select_liste() { # s'il n'y a qu'une valeur dans la liste, sans doute une #BALISE if (!isset($this->command['liste'][1])) { if (!is_array($this->command['liste'][0])) { $this->command['liste'] = explode(',', $this->command['liste'][0]); } else { $this->command['liste'] = $this->command['liste'][0]; } } $this->tableau = $this->command['liste']; } /** * Retourne un tableau donne depuis un critere liste * Critere {enum Xmin, Xmax} * **/ protected function select_enum() { # s'il n'y a qu'une valeur dans la liste, sans doute une #BALISE if (!isset($this->command['enum'][1])) { if (!is_array($this->command['enum'][0])) { $this->command['enum'] = explode(',', $this->command['enum'][0]); } else { $this->command['enum'] = $this->command['enum'][0]; } } if (count($this->command['enum']) >= 3) { $enum = range(array_shift($this->command['enum']), array_shift($this->command['enum']), array_shift($this->command['enum'])); } else { $enum = range(array_shift($this->command['enum']), array_shift($this->command['enum'])); } $this->tableau = $enum; } /** * extraire le chemin "query.results" du tableau de donnees * {datapath query.results} * **/ protected function select_datapath() { $base = reset($this->command['datapath']); if (strlen($base = ltrim(trim($base), "/"))) { $this->tableau = table_valeur($this->tableau, $base); if (!is_array($this->tableau)) { $this->tableau = array(); $this->err = true; spip_log("datapath '$base' absent"); } } } /** * Ordonner les resultats * {par x} * **/ protected function select_orderby() { $sortfunc = ''; $aleas = 0; foreach ($this->command['orderby'] as $tri) { // virer le / initial pour les criteres de la forme {par /xx} if (preg_match(',^\.?([/\w]+)( DESC)?$,iS', ltrim($tri, '/'), $r)) { $r = array_pad($r, 3, null); // tri par cle if ($r[1] == 'cle') { if (isset($r[2]) and $r[2]) { krsort($this->tableau); } else { ksort($this->tableau); } } # {par hasard} else { if ($r[1] == 'hasard') { $k = array_keys($this->tableau); shuffle($k); $v = array(); foreach ($k as $cle) { $v[$cle] = $this->tableau[$cle]; } $this->tableau = $v; } else { # {par valeur} if ($r[1] == 'valeur') { $tv = '%s'; } # {par valeur/xx/yy} ?? else { $tv = 'table_valeur(%s, ' . var_export($r[1], true) . ')'; } $sortfunc .= ' $a = ' . sprintf($tv, '$aa') . '; $b = ' . sprintf($tv, '$bb') . '; if ($a <> $b) return ($a ' . (!empty($r[2]) ? '>' : '<') . ' $b) ? -1 : 1;'; } } } } if ($sortfunc) { $sortfunc .= "\n return 0;"; uasort($this->tableau, function($aa, $bb) use ($sortfunc) { return eval($sortfunc); }); } } /** * Grouper les resultats * {fusion /x/y/z} * **/ protected function select_groupby() { // virer le / initial pour les criteres de la forme {fusion /xx} if (strlen($fusion = ltrim($this->command['groupby'][0], '/'))) { $vu = array(); foreach ($this->tableau as $k => $v) { $val = table_valeur($v, $fusion); if (isset($vu[$val])) { unset($this->tableau[$k]); } else { $vu[$val] = true; } } } } /** * L'iterateur est-il encore valide ? * * @return bool */ public function valid() { return !is_null($this->cle); } /** * Retourner la valeur * * @return null */ public function current() { return $this->valeur; } /** * Retourner la cle * * @return null */ public function key() { return $this->cle; } /** * Passer a la valeur suivante * * @return void */ public function next() { if ($this->valid()) { $this->cle = key($this->tableau); $this->valeur = current($this->tableau); next($this->tableau); } } /** * Compter le nombre total de resultats * * @return int */ public function count() { if (is_null($this->total)) { $this->total = count($this->tableau); } return $this->total; } } /* * Fonctions de transformation donnee => tableau */ /** * file -> tableau * * @param string $u * @return array */ function inc_file_to_array_dist($u) { return preg_split('/\r?\n/', $u); } /** * plugins -> tableau * * @return unknown */ function inc_plugins_to_array_dist() { include_spip('inc/plugin'); return liste_chemin_plugin_actifs(); } /** * xml -> tableau * * @param string $u * @return array */ function inc_xml_to_array_dist($u) { return @XMLObjectToArray(new SimpleXmlIterator($u)); } /** * * object -> tableau * * @param object $object The object to convert * @return array * */ function inc_object_to_array($object) { if (!is_object($object) && !is_array($object)) { return $object; } if (is_object($object)) { $object = get_object_vars($object); } return array_map('inc_object_to_array', $object); } /** * yql -> tableau * * @throws Exception * @param string $u * @return array|bool */ function inc_yql_to_array_dist($u) { define('_YQL_ENDPOINT', 'https://query.yahooapis.com/v1/public/yql?&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&q='); $v = recuperer_url($url = _YQL_ENDPOINT . urlencode($u) . '&format=json'); if (!$v['page'] or !$w = json_decode($v['page'], true) ) { throw new Exception('YQL: réponse vide ou mal formée'); } if (isset($w['error'])) { throw new Exception($w['error']['description']); } return inc_object_to_array($w); } /** * sql -> tableau * * @param string $u * @return array|bool */ function inc_sql_to_array_dist($u) { # sortir le connecteur de $u preg_match(',^(?:(\w+):)?(.*)$,S', $u, $v); $serveur = (string)$v[1]; $req = trim($v[2]); if ($s = sql_query($req, $serveur)) { $r = array(); while ($t = sql_fetch($s)) { $r[] = $t; } return $r; } return false; } /** * json -> tableau * * @param string $u * @return array|bool */ function inc_json_to_array_dist($u) { if (is_array($json = json_decode($u, true))) { return (array)$json; } } /** * csv -> tableau * * @param string $u * @return array|bool */ function inc_csv_to_array_dist($u) { include_spip('inc/csv'); list($entete, $csv) = analyse_csv($u); array_unshift($csv, $entete); include_spip('inc/charsets'); $i = 1; foreach ($entete as $k => $v) { if (trim($v) == "") { $v = "col" . $i; } // reperer des eventuelles cases vides if (is_numeric($v) and $v < 0) { $v = "__" . $v; } // ne pas risquer d'ecraser une cle numerique if (is_numeric($v)) { $v = "_" . $v; } // ne pas risquer d'ecraser une cle numerique $v = strtolower(preg_replace(',\W+,', '_', translitteration($v))); foreach ($csv as &$item) { $item[$v] = &$item[$k]; } $i++; } return $csv; } /** * RSS -> tableau * * @param string $u * @return array|bool */ function inc_rss_to_array_dist($u) { include_spip('inc/syndic'); if (is_array($rss = analyser_backend($u))) { $tableau = $rss; } return $tableau; } /** * atom, alias de rss -> tableau * * @param string $u * @return array|bool */ function inc_atom_to_array_dist($u) { $g = charger_fonction('rss_to_array', 'inc'); return $g($u); } /** * glob -> tableau * lister des fichiers selon un masque, pour la syntaxe cf php.net/glob * * @param string $u * @return array|bool */ function inc_glob_to_array_dist($u) { $a = glob($u, GLOB_MARK | GLOB_NOSORT | GLOB_BRACE ); return $a ? $a : array(); } /** * YAML -> tableau * * @param string $u * @return bool|array * @throws Exception */ function inc_yaml_to_array_dist($u) { include_spip('inc/yaml-mini'); if (!function_exists("yaml_decode")) { throw new Exception('YAML: impossible de trouver la fonction yaml_decode'); return false; } return yaml_decode($u); } /** * pregfiles -> tableau * lister des fichiers a partir d'un dossier de base et selon une regexp. * pour la syntaxe cf la fonction spip preg_files * * @param string $dir * @param string $regexp * @param int $limit * @return array|bool */ function inc_pregfiles_to_array_dist($dir, $regexp = -1, $limit = 10000) { return (array)preg_files($dir, $regexp, $limit); } /** * ls -> tableau * ls : lister des fichiers selon un masque glob * et renvoyer aussi leurs donnees php.net/stat * * @param string $u * @return array|bool */ function inc_ls_to_array_dist($u) { $glob = charger_fonction('glob_to_array', 'inc'); $a = $glob($u); foreach ($a as &$v) { $b = (array)@stat($v); foreach ($b as $k => $ignore) { if (is_numeric($k)) { unset($b[$k]); } } $b['file'] = preg_replace('`/$`','',$v) ; $v = array_merge( pathinfo($v), $b ); } return $a; } /** * Object -> tableau * * @param Object $object * @return array|bool */ function XMLObjectToArray($object) { $xml_array = array(); for ($object->rewind(); $object->valid(); $object->next()) { if (array_key_exists($key = $object->key(), $xml_array)) { $key .= '-' . uniqid(); } $vars = get_object_vars($object->current()); if (isset($vars['@attributes'])) { foreach ($vars['@attributes'] as $k => $v) { $xml_array[$key][$k] = $v; } } if ($object->hasChildren()) { $xml_array[$key][] = XMLObjectToArray( $object->current()); } else { $xml_array[$key][] = strval($object->current()); } } return $xml_array; }