not; $boucle = &$boucles[$idb]; $id_parent = isset($exceptions_des_tables[$boucle->id_table]['id_parent']) ? $exceptions_des_tables[$boucle->id_table]['id_parent'] : 'id_parent'; $c = array("'='", "'$boucle->id_table."."$id_parent'", 0); $boucle->where[] = ($crit->not ? array("'NOT'", $c) : $c); } /** * Compile le critère {exclus} * * Exclut du résultat l’élément dans lequel on se trouve déjà * * @link http://www.spip.net/@exclus * * @param string $idb * Identifiant de la boucle * @param array $boucles * AST du squelette * @param array $crit * Paramètres du critère dans cette boucle * @return * AST complété de la gestion du critère **/ function critere_exclus_dist($idb, &$boucles, $crit){ $not = $crit->not; $boucle = &$boucles[$idb]; $id = $boucle->primary; if ($not OR !$id) return (array('zbug_critere_inconnu', array('critere' => $not.$crit->op))); $arg = kwote(calculer_argument_precedent($idb, $id, $boucles)); $boucle->where[] = array("'!='", "'$boucle->id_table."."$id'", $arg); } /** * Compile le critère {doublons} ou {unique} * * Ce critères enlève de la boucle les éléments déjà sauvegardés * dans un précédent critère {doublon} sur une boucle de même table. * * Il est possible de spécifier un nom au doublon tel que {doublons sommaire} * * @link http://www.spip.net/@doublons * * @param string $idb * Identifiant de la boucle * @param array $boucles * AST du squelette * @param array $crit * Paramètres du critère dans cette boucle * @return * AST complété de la gestion du critère **/ function critere_doublons_dist($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; $primary = $boucle->primary; // la table nécessite une clé primaire, non composée if (!$primary OR strpos($primary, ',')){ return (array('zbug_doublon_sur_table_sans_cle_primaire')); } $not = ($crit->not ? '' : 'NOT'); // le doublon s'applique sur un type de boucle (article) $nom = "'" . $boucle->type_requete. "'"; // compléter le nom avec un nom précisé {doublons nom} // on obtient $nom = "'article' . 'nom'" if (isset($crit->param[0])) { $nom .= "." . calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent); } // code qui déclarera l'index du stockage de nos doublons (pour éviter une notice PHP) $init_comment = "\n\n\t// Initialise le(s) critère(s) doublons\n"; $init_code = "\tif (!isset(\$doublons[\$d = $nom])) { \$doublons[\$d] = ''; }\n"; // on crée un sql_in avec la clé primaire de la table // et la collection des doublons déjà emmagasinés dans le tableau // $doublons et son index, ici $nom // debut du code "sql_in('articles.id_article', " $debut_in = "sql_in('".$boucle->id_table.'.'.$primary."', "; // lecture des données du doublon "$doublons[$doublon_index[] = " // Attention : boucle->doublons désigne une variable qu'on affecte $debut_doub = '$doublons[' . (!$not ? '' : ($boucle->doublons."[]= ")); // le debut complet du code des doublons $debut_doub = $debut_in . $debut_doub; // nom du doublon "('article' . 'nom')]" $fin_doub = "($nom)]"; // si on trouve un autre critère doublon, // on fusionne pour avoir un seul IN, et on s'en va ! foreach ($boucle->where as $k => $w) { if (strpos($w[0], $debut_doub)===0) { // fusionner le sql_in (du where) $boucle->where[$k][0] = $debut_doub . $fin_doub.' . '.substr($w[0], strlen($debut_in)); // fusionner l'initialisation (du hash) pour faire plus joli $x = strpos($boucle->hash, $init_comment); $len = strlen($init_comment); $boucle->hash = substr($boucle->hash, 0, $x + $len) . $init_code . substr($boucle->hash, $x + $len); return; } } // mettre l'ensemble dans un tableau pour que ce ne soit pas vu comme une constante $boucle->where[] = array($debut_doub . $fin_doub.", '".$not."')"); // déclarer le doublon s'il n'existe pas encore $boucle->hash .= $init_comment . $init_code; # la ligne suivante avait l'intention d'eviter une collecte deja faite # mais elle fait planter une boucle a 2 critere doublons: # {!doublons A}{doublons B} # (de http://article.gmane.org/gmane.comp.web.spip.devel/31034) # if ($crit->not) $boucle->doublons = ""; } /** * Compile le critère {lang_select} * * Permet de restreindre ou non une boucle en affichant uniquement * les éléments dans la langue en cours. Certaines boucles * tel que articles et rubriques restreignent par défaut sur la langue * en cours. * * Sans définir de valeur au critère, celui-ci utilise 'oui' comme * valeur par défaut. * * @param string $idb * Identifiant de la boucle * @param array $boucles * AST du squelette * @param array $crit * Paramètres du critère dans cette boucle * @return * AST complété de la gestion du critère **/ function critere_lang_select_dist($idb, &$boucles, $crit){ if (!isset($crit->param[1][0]) OR !($param = $crit->param[1][0]->texte)) $param = 'oui'; if ($crit->not) $param = ($param=='oui') ? 'non' : 'oui'; $boucle = &$boucles[$idb]; $boucle->lang_select = $param; } // {debut_xxx} // http://www.spip.net/@debut_ // http://doc.spip.org/@critere_debut_dist function critere_debut_dist($idb, &$boucles, $crit){ list($un, $deux) = $crit->param; $un = $un[0]->texte; $deux = $deux[0]->texte; if ($deux){ $boucles[$idb]->limit = 'intval($Pile[0]["debut'. $un. '"]) . ",'. $deux. '"'; } else calculer_critere_DEFAUT_dist($idb, $boucles, $crit); } // {pagination} // {pagination 20} // {pagination #ENV{pages,5}} etc // {pagination 20 #ENV{truc,chose}} pour utiliser la variable debut_#ENV{truc,chose} // http://www.spip.net/@pagination // http://doc.spip.org/@critere_pagination_dist function critere_pagination_dist($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; // definition de la taille de la page $pas = !isset($crit->param[0][0]) ? "''" : calculer_liste(array($crit->param[0][0]), array(), $boucles, $boucle->id_parent); if (!preg_match(_CODE_QUOTE, $pas, $r)){ $pas = "((\$a = intval($pas)) ? \$a : 10)"; } else { $r = intval($r[2]); $pas = strval($r ? $r : 10); } $type = !isset($crit->param[0][1]) ? "'$idb'" : calculer_liste(array($crit->param[0][1]), array(), $boucles, $boucle->id_parent); $debut = ($type[0]!=="'") ? "'debut'.$type" : ("'debut".substr($type, 1)); $boucle->modificateur['debut_nom'] = $type; $partie = // tester si le numero de page demande est de la forme '@yyy' 'isset($Pile[0]['.$debut.']) ? $Pile[0]['.$debut.'] : _request('.$debut.");\n" ."\tif(substr(\$debut_boucle,0,1)=='@'){\n" ."\t\t".'$debut_boucle = $Pile[0]['.$debut.'] = quete_debut_pagination(\''.$boucle->primary.'\',$Pile[0][\'@'.$boucle->primary.'\'] = substr($debut_boucle,1),'.$pas.',$iter);'."\n" ."\t\t".'$iter->seek(0);'."\n" ."\t}\n" ."\t".'$debut_boucle = intval($debut_boucle)'; $boucle->hash .= ' $command[\'pagination\'] = array((isset($Pile[0]['.$debut.']) ? $Pile[0]['.$debut.'] : null), ' . $pas . ');'; $boucle->total_parties = $pas; calculer_parties($boucles, $idb, $partie, 'p+'); // ajouter la cle primaire dans le select pour pouvoir gerer la pagination referencee par @id // sauf si pas de primaire, ou si primaire composee // dans ce cas, on ne sait pas gerer une pagination indirecte $t = $boucle->id_table.'.'.$boucle->primary; if ($boucle->primary AND !preg_match('/[,\s]/', $boucle->primary) AND !in_array($t, $boucle->select) ) $boucle->select[] = $t; } // {recherche} ou {recherche susan} // http://www.spip.net/@recherche // http://doc.spip.org/@critere_recherche_dist function critere_recherche_dist($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; if (isset($crit->param[0])) $quoi = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent); else $quoi = '(isset($Pile[0]["recherche"])?$Pile[0]["recherche"]:(isset($GLOBALS["recherche"])?$GLOBALS["recherche"]:""))'; $_modificateur = var_export($boucle->modificateur, true); $boucle->hash .= ' // RECHERCHE' .($crit->cond ? ' if (!strlen('.$quoi.')){ list($rech_select, $rech_where) = array("0 as points",""); } else' : '').' { $prepare_recherche = charger_fonction(\'prepare_recherche\', \'inc\'); list($rech_select, $rech_where) = $prepare_recherche('.$quoi.', "'.$boucle->id_table.'", "'.$crit->cond.'","'.$boucle->sql_serveur.'",'.$_modificateur.',"'.$boucle->primary.'"); } '; $t = $boucle->id_table.'.'.$boucle->primary; if (!in_array($t, $boucles[$idb]->select)) $boucle->select[] = $t; # pour postgres, neuneu ici // jointure uniquement sur le serveur principal // (on ne peut joindre une table d'un serveur distant avec la table des resultats du serveur principal) if (!$boucle->sql_serveur){ $boucle->join['resultats'] = array("'".$boucle->id_table."'", "'id'", "'".$boucle->primary."'"); $boucle->from['resultats'] = 'spip_resultats'; } $boucle->select[] = '$rech_select'; //$boucle->where[]= "\$rech_where?'resultats.id=".$boucle->id_table.".".$boucle->primary."':''"; // et la recherche trouve $boucle->where[] = '$rech_where?$rech_where:\'\''; } // {traduction} // http://www.spip.net/@traduction // (id_trad>0 AND id_trad=id_trad(precedent)) // OR id_article=id_article(precedent) // http://doc.spip.org/@critere_traduction_dist function critere_traduction_dist($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; $prim = $boucle->primary; $table = $boucle->id_table; $arg = kwote(calculer_argument_precedent($idb, 'id_trad', $boucles)); $dprim = kwote(calculer_argument_precedent($idb, $prim, $boucles)); $boucle->where[] = array("'OR'", array("'AND'", array("'='", "'$table.id_trad'", 0), array("'='", "'$table.$prim'", $dprim) ), array("'AND'", array("'>'", "'$table.id_trad'", 0), array("'='", "'$table.id_trad'", $arg) ) ); } // {origine_traduction} // (id_trad>0 AND id_article=id_trad) OR (id_trad=0) // http://www.spip.net/@origine_traduction // http://doc.spip.org/@critere_origine_traduction_dist function critere_origine_traduction_dist($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; $prim = $boucle->primary; $table = $boucle->id_table; $c = array("'OR'", array("'='", "'$table."."id_trad'", "'$table.$prim'"), array("'='", "'$table.id_trad'", "'0'") ); $boucle->where[] = ($crit->not ? array("'NOT'", $c) : $c); } // {meme_parent} // http://www.spip.net/@meme_parent // http://doc.spip.org/@critere_meme_parent_dist function critere_meme_parent_dist($idb, &$boucles, $crit){ global $exceptions_des_tables; $boucle = &$boucles[$idb]; $arg = kwote(calculer_argument_precedent($idb, 'id_parent', $boucles)); $id_parent = isset($exceptions_des_tables[$boucle->id_table]['id_parent']) ? $exceptions_des_tables[$boucle->id_table]['id_parent'] : 'id_parent'; $mparent = $boucle->id_table.'.'.$id_parent; if ($boucle->type_requete=='rubriques' OR isset($exceptions_des_tables[$boucle->id_table]['id_parent'])){ $boucle->where[] = array("'='", "'$mparent'", $arg); } // le cas FORUMS est gere dans le plugin forum, dans la fonction critere_FORUMS_meme_parent_dist() else { return (array('zbug_critere_inconnu', array('critere' => $crit->op.' '.$boucle->type_requete))); } } /** * Sélectionne dans une boucle les éléments appartenant à une branche d'une rubrique * * Calcule une branche d'une rubrique et conditionne la boucle avec. * Cherche l'identifiant de la rubrique dans les boucles parentes ou par jointure * et calcule la liste des identifiants de rubrique de toute la branche * * @link http://www.spip.net/@branche * * @param string $idb * Identifiant de la boucle * @param array $boucles * AST du squelette * @param array $crit * Paramètres du critère dans cette boucle * @return * AST complété de la condition where au niveau de la boucle, * restreignant celle ci aux rubriques de la branche **/ function critere_branche_dist($idb, &$boucles, $crit){ $not = $crit->not; $boucle = &$boucles[$idb]; $arg = calculer_argument_precedent($idb, 'id_rubrique', $boucles); //Trouver une jointure $champ = "id_rubrique"; $desc = $boucle->show; //Seulement si necessaire if (!array_key_exists($champ, $desc['field'])){ $cle = trouver_jointure_champ($champ, $boucle); $trouver_table = charger_fonction("trouver_table", "base"); $desc = $trouver_table($boucle->from[$cle]); if (count(trouver_champs_decomposes($champ, $desc))>1){ $decompose = decompose_champ_id_objet($champ); $champ = array_shift($decompose); $boucle->where[] = array("'='", _q($cle.".".reset($decompose)), '"'.sql_quote(end($decompose)).'"'); } } else $cle = $boucle->id_table; $c = "sql_in('$cle".".$champ', calcul_branche_in($arg)" .($not ? ", 'NOT'" : '').")"; $boucle->where[] = !$crit->cond ? $c : ("($arg ? $c : ".($not ? "'0=1'" : "'1=1'").')'); } // {logo} liste les objets qui ont un logo // http://doc.spip.org/@critere_logo_dist function critere_logo_dist($idb, &$boucles, $crit){ $not = $crit->not; $boucle = &$boucles[$idb]; $c = "sql_in('". $boucle->id_table.'.'.$boucle->primary ."', lister_objets_avec_logos('".$boucle->primary."'), '')"; if ($crit->cond) $c = "($arg ? $c : 1)"; if ($not) $boucle->where[] = array("'NOT'", $c); else $boucle->where[] = $c; } // c'est la commande SQL "GROUP BY" // par exemple // http://doc.spip.org/@critere_fusion_dist function critere_fusion_dist($idb, &$boucles, $crit){ if ($t = isset($crit->param[0])){ $t = $crit->param[0]; if ($t[0]->type=='texte'){ $t = $t[0]->texte; if (preg_match("/^(.*)\.(.*)$/", $t, $r)){ $t = table_objet_sql($r[1]); $t = array_search($t, $boucles[$idb]->from); if ($t) $t .= '.'.$r[2]; } } else { $t = '".' .calculer_critere_arg_dynamique($idb, $boucles, $t) .'."'; } } if ($t){ $boucles[$idb]->group[] = $t; if (!in_array($t, $boucles[$idb]->select)) $boucles[$idb]->select[] = $t; } else return (array('zbug_critere_inconnu', array('critere' => $crit->op.' ?'))); } // c'est la commande SQL "COLLATE" // qui peut etre appliquee sur les order by, group by, where like ... // http://doc.spip.org/@critere_collecte_dist function critere_collecte_dist($idb, &$boucles, $crit){ if (isset($crit->param[0])){ $_coll = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent); $boucle = $boucles[$idb]; $boucle->modificateur['collate'] = "($_coll ?' COLLATE '.$_coll:'')"; $n = count($boucle->order); if ($n && (strpos($boucle->order[$n-1], 'COLLATE')===false)) $boucle->order[$n-1] .= " . ".$boucle->modificateur['collate']; } else return (array('zbug_critere_inconnu', array('critere' => $crit->op." ".count($boucles[$idb]->order)))); } // http://doc.spip.org/@calculer_critere_arg_dynamique function calculer_critere_arg_dynamique($idb, &$boucles, $crit, $suffix = ''){ $boucle = $boucles[$idb]; $alt = "('".$boucle->id_table.'.\' . $x'.$suffix.')'; $var = '$champs_'.$idb; $desc = (strpos($boucle->in, "static $var =")!==false); if (!$desc){ $desc = $boucle->show['field']; $desc = implode(',', array_map('_q', array_keys($desc))); $boucles[$idb]->in .= "\n\tstatic $var = array(".$desc.");"; } if ($desc) $alt = "(in_array(\$x, $var) ? $alt :(\$x$suffix))"; $arg = calculer_liste($crit, array(), $boucles, $boucle->id_parent); return "((\$x = preg_replace(\"/\\W/\",'', $arg)) ? $alt : '')"; } // Tri : {par xxxx} // http://www.spip.net/@par // http://doc.spip.org/@critere_par_dist function critere_par_dist($idb, &$boucles, $crit){ return critere_parinverse($idb, $boucles, $crit); } // http://doc.spip.org/@critere_parinverse function critere_parinverse($idb, &$boucles, $crit, $sens = ''){ global $exceptions_des_jointures; $boucle = &$boucles[$idb]; if ($crit->not) $sens = $sens ? "" : " . ' DESC'"; $collecte = (isset($boucle->modificateur['collecte'])) ? " . ".$boucle->modificateur['collecte'] : ""; foreach ($crit->param as $tri){ $order = $fct = ""; // en cas de fonction SQL // tris specifies dynamiquement if ($tri[0]->type!='texte'){ // calculer le order dynamique qui verifie les champs $order = calculer_critere_arg_dynamique($idb, $boucles, $tri, $sens); // et si ce n'est fait, ajouter un champ 'hasard' // pour supporter 'hasard' comme tri dynamique $par = "rand()"; $parha = $par." AS hasard"; if (!in_array($parha, $boucle->select)) $boucle->select[] = $parha; } else { $par = array_shift($tri); $par = $par->texte; // par multi champ if (preg_match(",^multi[\s]*(.*)$,", $par, $m)){ $champ = trim($m[1]); // par multi L1.champ if (strpos($champ, '.')) { $cle = ''; // par multi champ (champ sur une autre table) } elseif (!array_key_exists($champ, $boucle->show['field'])){ $cle = trouver_jointure_champ($champ, $boucle); // par multi champ (champ dans la table en cours) } else { $cle = $boucle->id_table; } if ($cle) { $cle .= '.'; } $texte = $cle.$champ; $boucle->select[] = "\".sql_multi('".$texte."', \$GLOBALS['spip_lang']).\""; $order = "'multi'"; // par num champ(, suite) } else if (preg_match(",^num (.*)$,m", $par, $m)) { $champ = trim($m[1]); // par num L1.champ if (strpos($champ, '.')) { $cle = ''; // par num champ (champ sur une autre table) } elseif (!array_key_exists($champ, $boucle->show['field'])){ $cle = trouver_jointure_champ($champ, $boucle); // par num champ (champ dans la table en cours) } else { $cle = $boucle->id_table; } if ($cle) { $cle .= '.'; } $texte = '0+'. $cle . $champ; $suite = calculer_liste($tri, array(), $boucles, $boucle->id_parent); if ($suite!=="''") $texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')"." . \""; $as = 'num'.($boucle->order ? count($boucle->order) : ""); $boucle->select[] = $texte." AS $as"; $order = "'$as'"; } else { if (!preg_match(",^".CHAMP_SQL_PLUS_FONC.'$,is', $par, $match)){ return (array('zbug_critere_inconnu', array('critere' => $crit->op." $par"))); } else { if (count($match)>2){ $par = substr($match[2], 1, -1); $fct = $match[1]; } // par hasard if ($par=='hasard'){ $par = "rand()"; $boucle->select[] = $par." AS alea"; $order = "'alea'"; } // par titre_mot ou type_mot voire d'autres else if (isset($exceptions_des_jointures[$par])){ list($table, $champ) = $exceptions_des_jointures[$par]; $order = critere_par_joint($table, $champ, $boucle, $idb); if (!$order) return (array('zbug_critere_inconnu', array('critere' => $crit->op." $par"))); } else if ($par=='date' AND $desc = $boucle->show AND $desc['date'] ){ $m = $desc['date']; $order = "'".$boucle->id_table.".".$m."'"; } // par champ. Verifier qu'ils sont presents. elseif (preg_match("/^([^,]*)\.(.*)$/", $par, $r)) { // cas du tri sur champ de jointure explicite $t = array_search($r[1], $boucle->from); if (!$t){ $t = trouver_jointure_champ($r[2], $boucle, array($r[1])); } if (!$t){ return (array('zbug_critere_inconnu', array('critere' => $crit->op." $par"))); } else $order = "'".$t.'.'.$r[2]."'"; } else { $desc = $boucle->show; if ($desc['field'][$par]) $par = $boucle->id_table.".".$par; // sinon tant pis, ca doit etre un champ synthetise (cf points) $order = "'$par'"; } } } } if (preg_match('/^\'([^"]*)\'$/', $order, $m)){ $t = $m[1]; if (strpos($t, '.') AND !in_array($t, $boucle->select)){ $boucle->select[] = $t; } } else $sens = ''; if ($fct){ if (preg_match("/^\s*'(.*)'\s*$/", $order, $r)) $order = "'$fct(".$r[1].")'"; else $order = "'$fct(' . $order . ')'"; } $t = $order.$collecte.$sens; if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r)) $t = $r[1].$r[2]; $boucle->order[] = $t; } } // http://doc.spip.org/@critere_par_joint function critere_par_joint($table, $champ, &$boucle, $idb){ $t = array_search($table, $boucle->from); if (!$t) $t = trouver_jointure_champ($champ, $boucle); return !$t ? '' : ("'".$t.'.'.$champ."'"); } // {inverse} // http://www.spip.net/@inverse // http://doc.spip.org/@critere_inverse_dist function critere_inverse_dist($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; // Classement par ordre inverse if ($crit->not) critere_parinverse($idb, $boucles, $crit); else { $order = "' DESC'"; // Classement par ordre inverse fonction eventuelle de #ENV{...} if (isset($crit->param[0])){ $critere = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent); $order = "(($critere)?' DESC':'')"; } $n = count($boucle->order); if (!$n){ if (isset($boucle->default_order[0])) $boucle->default_order[0] .= ' . " DESC"'; else $boucle->default_order[] = ' DESC'; } else { $t = $boucle->order[$n-1]." . $order"; if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r)) $t = $r[1].$r[2]; $boucle->order[$n-1] = $t; } } } // http://doc.spip.org/@critere_agenda_dist function critere_agenda_dist($idb, &$boucles, $crit){ $params = $crit->param; if (count($params)<1) return (array('zbug_critere_inconnu', array('critere' => $crit->op." ?"))); $parent = $boucles[$idb]->id_parent; // les valeurs $date et $type doivent etre connus a la compilation // autrement dit ne pas etre des champs $date = array_shift($params); $date = $date[0]->texte; $type = array_shift($params); $type = $type[0]->texte; $annee = $params ? array_shift($params) : ""; $annee = "\n".'sprintf("%04d", ($x = '. calculer_liste($annee, array(), $boucles, $parent). ') ? $x : date("Y"))'; $mois = $params ? array_shift($params) : ""; $mois = "\n".'sprintf("%02d", ($x = '. calculer_liste($mois, array(), $boucles, $parent). ') ? $x : date("m"))'; $jour = $params ? array_shift($params) : ""; $jour = "\n".'sprintf("%02d", ($x = '. calculer_liste($jour, array(), $boucles, $parent). ') ? $x : date("d"))'; $annee2 = $params ? array_shift($params) : ""; $annee2 = "\n".'sprintf("%04d", ($x = '. calculer_liste($annee2, array(), $boucles, $parent). ') ? $x : date("Y"))'; $mois2 = $params ? array_shift($params) : ""; $mois2 = "\n".'sprintf("%02d", ($x = '. calculer_liste($mois2, array(), $boucles, $parent). ') ? $x : date("m"))'; $jour2 = $params ? array_shift($params) : ""; $jour2 = "\n".'sprintf("%02d", ($x = '. calculer_liste($jour2, array(), $boucles, $parent). ') ? $x : date("d"))'; $boucle = &$boucles[$idb]; $date = $boucle->id_table.".$date"; $quote_end = ",'".$boucle->sql_serveur."','text'"; if ($type=='jour') $boucle->where[] = array("'='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("sql_quote($annee . $mois . $jour$quote_end)")); elseif ($type=='mois') $boucle->where[] = array("'='", "'DATE_FORMAT($date, \'%Y%m\')'", ("sql_quote($annee . $mois$quote_end)")); elseif ($type=='semaine') $boucle->where[] = array("'AND'", array("'>='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("date_debut_semaine($annee, $mois, $jour)")), array("'<='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("date_fin_semaine($annee, $mois, $jour)"))); elseif (count($crit->param)>2) $boucle->where[] = array("'AND'", array("'>='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("sql_quote($annee . $mois . $jour$quote_end)")), array("'<='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("sql_quote($annee2 . $mois2 . $jour2$quote_end)"))); // sinon on prend tout } // http://doc.spip.org/@calculer_critere_parties function calculer_critere_parties($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; $a1 = $crit->param[0]; $a2 = $crit->param[1]; $op = $crit->op; list($a11, $a12) = calculer_critere_parties_aux($idb, $boucles, $a1); list($a21, $a22) = calculer_critere_parties_aux($idb, $boucles, $a2); if (($op==',') && (is_numeric($a11) && (is_numeric($a21)))){ $boucle->limit = $a11.','.$a21; } else { $boucle->total_parties = ($a21!='n') ? $a21 : $a22; $partie = ($a11!='n') ? $a11 : $a12; $mode = (($op=='/') ? '/' : (($a11=='n') ? '-' : '+').(($a21=='n') ? '-' : '+')); // cas simple {0,#ENV{truc}} compilons le en LIMIT : if ($a11!=='n' AND $a21!=='n' AND $mode=="++" AND $op==','){ $boucle->limit = (is_numeric($a11)?"'$a11'":$a11) .".','." .(is_numeric($a21)?"'$a21'":$a21); } else calculer_parties($boucles, $idb, $partie, $mode); } } // // Code specifique aux criteres {pagination}, {1,n} {n/m} etc // function calculer_parties(&$boucles, $id_boucle, $debut, $mode){ $total_parties = $boucles[$id_boucle]->total_parties; preg_match(",([+-/p])([+-/])?,", $mode, $regs); list(, $op1, $op2) = array_pad($regs, 3, null); $nombre_boucle = "\$Numrows['$id_boucle']['total']"; // {1/3} if ($op1=='/'){ $pmoins1 = is_numeric($debut) ? ($debut-1) : "($debut-1)"; $totpos = is_numeric($total_parties) ? ($total_parties) : "($total_parties ? $total_parties : 1)"; $fin = "ceil(($nombre_boucle * $debut )/$totpos) - 1"; $debut = !$pmoins1 ? 0 : "ceil(($nombre_boucle * $pmoins1)/$totpos);"; } else { // cas {n-1,x} if ($op1=='-') $debut = "$nombre_boucle - $debut;"; // cas {x,n-1} if ($op2=='-'){ $fin = '$debut_boucle + '.$nombre_boucle.' - ' .(is_numeric($total_parties) ? ($total_parties+1) : ($total_parties.' - 1')); } else { // {x,1} ou {pagination} $fin = '$debut_boucle' .(is_numeric($total_parties) ? (($total_parties==1) ? "" : (' + '.($total_parties-1))) : ('+'.$total_parties.' - 1')); } // {pagination}, gerer le debut_xx=-1 pour tout voir if ($op1=='p'){ $debut .= ";\n \$debut_boucle = ((\$tout=(\$debut_boucle == -1))?0:(\$debut_boucle))"; $debut .= ";\n \$debut_boucle = max(0,min(\$debut_boucle,floor(($nombre_boucle-1)/($total_parties))*($total_parties)))"; $fin = "(\$tout ? $nombre_boucle : $fin)"; } } // Notes : // $debut_boucle et $fin_boucle sont les indices SQL du premier // et du dernier demandes dans la boucle : 0 pour le premier, // n-1 pour le dernier ; donc total_boucle = 1 + debut - fin // Utiliser min pour rabattre $fin_boucle sur total_boucle. $boucles[$id_boucle]->mode_partie = "\n\t" .'$debut_boucle = '.$debut.";\n " .'$fin_boucle = min('.$fin.", \$Numrows['$id_boucle']['total'] - 1);\n " .'$Numrows[\''.$id_boucle."']['grand_total'] = \$Numrows['$id_boucle']['total'];\n " .'$Numrows[\''.$id_boucle.'\']["total"] = max(0,$fin_boucle - $debut_boucle + 1);' ."\n\tif (\$debut_boucle>0 AND \$debut_boucle < \$Numrows['$id_boucle']['grand_total'] AND \$iter->seek(\$debut_boucle,'continue'))\n\t\t\$Numrows['$id_boucle']['compteur_boucle'] = \$debut_boucle;\n\t"; $boucles[$id_boucle]->partie = " if (\$Numrows['$id_boucle']['compteur_boucle'] <= \$debut_boucle) continue; if (\$Numrows['$id_boucle']['compteur_boucle']-1 > \$fin_boucle) break;"; } // http://doc.spip.org/@calculer_critere_parties_aux function calculer_critere_parties_aux($idb, &$boucles, $param){ if ($param[0]->type!='texte'){ $a1 = calculer_liste(array($param[0]), array('id_mere' => $idb), $boucles, $boucles[$idb]->id_parent); preg_match(',^ *(-([0-9]+))? *$,', $param[1]->texte, $m); return array("intval($a1)", ($m[2] ? $m[2] : 0)); } else { preg_match(',^ *(([0-9]+)|n) *(- *([0-9]+)? *)?$,', $param[0]->texte, $m); $a1 = $m[1]; if (!@$m[3]) return array($a1, 0); elseif ($m[4]) return array($a1, $m[4]); else return array($a1, calculer_liste(array($param[1]), array(), $boucles[$idb]->id_parent, $boucles)); } } /** * Compile les critères d'une boucle * * Cette fonction d'aiguillage cherche des fonctions spécifiques déclarées * pour chaque critère demandé, dans l'ordre ci-dessous : * * - critere_{serveur}_{table}_{critere}, sinon avec _dist * - critere_{serveur}_{critere}, sinon avec _dist * - critere_{table}_{critere}, sinon avec _dist * - critere_{critere}, sinon avec _dist * - critere_defaut, sinon avec _dist * * Émet une erreur de squelette si un critère retourne une erreur. * * @param string $idb * Identifiant de la boucle * @param array $boucles * AST du squelette * @return string|array * string : Chaine vide sans erreur * array : Erreur sur un des critères **/ function calculer_criteres($idb, &$boucles){ $msg = ''; $boucle = $boucles[$idb]; $table = strtoupper($boucle->type_requete); $serveur = strtolower($boucle->sql_serveur); $defaut = charger_fonction('DEFAUT', 'calculer_critere'); // s'il y avait une erreur de syntaxe, propager cette info if (!is_array($boucle->criteres)) return array(); foreach ($boucle->criteres as $crit){ $critere = $crit->op; // critere personnalise ? if ( (!$serveur OR ((!function_exists($f = "critere_".$serveur."_".$table."_".$critere)) AND (!function_exists($f = $f."_dist")) AND (!function_exists($f = "critere_".$serveur."_".$critere)) AND (!function_exists($f = $f."_dist")) ) ) AND (!function_exists($f = "critere_".$table."_".$critere)) AND (!function_exists($f = $f."_dist")) AND (!function_exists($f = "critere_".$critere)) AND (!function_exists($f = $f."_dist")) ){ // fonction critere standard $f = $defaut; } // compile le critere $res = $f($idb, $boucles, $crit); // Gestion centralisee des erreurs pour pouvoir propager if (is_array($res)){ $msg = $res; erreur_squelette($msg, $boucle); } } return $msg; } /** * Madeleine de Proust, revision MIT-1958 sqq, revision CERN-1989 * hum, c'est kwoi cette fonxion ? on va dire qu'elle desemberlificote les guillemets... * * http://doc.spip.org/@kwote * * @param string $lisp * @param string $serveur * @param string $type * @return string */ function kwote($lisp, $serveur='', $type=''){ if (preg_match(_CODE_QUOTE, $lisp, $r)) return $r[1]."\"".sql_quote(str_replace(array("\\'", "\\\\"), array("'", "\\"), $r[2]),$serveur,$type)."\""; else return "sql_quote($lisp)"; } // Si on a une liste de valeurs dans #ENV{x}, utiliser la double etoile // pour faire par exemple {id_article IN #ENV**{liste_articles}} // http://doc.spip.org/@critere_IN_dist function critere_IN_dist($idb, &$boucles, $crit){ $r = calculer_critere_infixe($idb, $boucles, $crit); if (!$r){ return (array('zbug_critere_inconnu', array('critere' => $crit->op." ?"))); } list($arg, $op, $val, $col, $where_complement) = $r; $in = critere_IN_cas($idb, $boucles, $crit->not ? 'NOT' : ($crit->exclus ? 'exclus' : ''), $arg, $op, $val, $col); // inserer la condition; exemple: {id_mot ?IN (66, 62, 64)} $where = $in; if ($crit->cond){ $pred = calculer_argument_precedent($idb, $col, $boucles); $where = array("'?'", $pred, $where, "''"); if ($where_complement) // condition annexe du type "AND (objet='article')" $where_complement = array("'?'", $pred, $where_complement, "''"); } if ($crit->exclus) if (!preg_match(",^L[0-9]+[.],", $arg)) $where = array("'NOT'", $where); else // un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete // c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent $where = array("'NOT'", array("'IN'", "'".$boucles[$idb]->id_table.".".$boucles[$idb]->primary."'", array("'SELF'", "'".$boucles[$idb]->id_table.".".$boucles[$idb]->primary."'", $where))); $boucles[$idb]->where[] = $where; if ($where_complement) // condition annexe du type "AND (objet='article')" $boucles[$idb]->where[] = $where_complement; } // http://doc.spip.org/@critere_IN_cas function critere_IN_cas($idb, &$boucles, $crit2, $arg, $op, $val, $col){ static $num = array(); $descr = $boucles[$idb]->descr; $cpt = &$num[$descr['nom']][$descr['gram']][$idb]; $var = '$in'.$cpt++; $x = "\n\t$var = array();"; foreach ($val as $k => $v){ if (preg_match(",^(\n//.*\n)?'(.*)'$,", $v, $r)){ // optimiser le traitement des constantes if (is_numeric($r[2])) $x .= "\n\t$var"."[]= $r[2];"; else $x .= "\n\t$var"."[]= ".sql_quote($r[2]).";"; } else { // Pour permettre de passer des tableaux de valeurs // on repere l'utilisation brute de #ENV**{X}, // c'est-a-dire sa traduction en ($PILE[0][X]). // et on deballe mais en rajoutant l'anti XSS $x .= "\n\tif (!(is_array(\$a = ($v))))\n\t\t$var"."[]= \$a;\n\telse $var = array_merge($var, \$a);"; } } $boucles[$idb]->in .= $x; // inserer le tri par defaut selon les ordres du IN ... // avec une ecriture de type FIELD qui degrade les performances (du meme ordre qu'un rexgexp) // et que l'on limite donc strictement aux cas necessaires : // si ce n'est pas un !IN, et si il n'y a pas d'autre order dans la boucle if (!$crit2){ $boucles[$idb]->default_order[] = "((!sql_quote($var) OR sql_quote($var)===\"''\") ? 0 : ('FIELD($arg,' . sql_quote($var) . ')'))"; } return "sql_in('$arg',sql_quote($var)".($crit2=='NOT' ? ",'NOT'" : "").")"; } /** * {where} * tout simplement, pour faire le pont entre php et squelettes * * @param $idb * @param $boucles * @param $crit */ function critere_where_dist($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; if (isset($crit->param[0])) $_where = calculer_liste($crit->param[0], array(), $boucles, $boucle->id_parent); else $_where = '@$Pile[0]["where"]'; if ($crit->cond) $_where = "(($_where) ? ($_where) : '')"; if ($crit->not) $_where = "array('NOT',$_where)"; $boucle->where[] = $_where; } /** * Un critere pour gerer un champ de tri qui peut etre modifie dynamiquement * par la balise #TRI * * {tri [champ_par_defaut][,sens_par_defaut][,nom_variable]} * champ_par_defaut : un champ de la table sql * sens_par_defaut : -1 ou inverse pour decroissant, 1 ou direct pour croissant * peut etre un tableau pour preciser des sens par defaut associes a chaque champ * exemple : array('titre'=>1,'date'=>-1) pour trier par defaut * les titre croissant et les dates decroissantes * dans ce cas, quand un champ est utilise pour le tri et n'est pas present dans le tableau * c'est la premiere valeur qui est utilisee * nom_variable : nom de la variable utilisee (par defaut tri_nomboucle) * * {tri titre} * {tri titre,inverse} * {tri titre,-1} * {tri titre,-1,truc} * * le critere {tri} s'utilise conjointement avec la balise #TRI dans la meme boucle * pour generer les liens qui permettent de changer le critere de tri et le sens du tri * * Exemple d'utilisation * * *

#TRI{titre,'Trier par titre'} | #TRI{date,'Trier par date'}

*
    * *
  • #TITRE - [(#DATE|affdate_jourcourt)]
  • * *
*
* * NB : * contraitement a {par ...} {tri} ne peut prendre qu'un seul champ, * mais il peut etre complete avec {par ...} pour indiquer des criteres secondaires * * ex : * {tri num titre}{par titre} permet de faire un tri sur le rang (modifiable dynamiquement) * avec un second critere sur le titre en cas d'egalite des rang * * @param unknown_type $idb * @param unknown_type $boucles * @param unknown_type $crit */ function critere_tri_dist($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; // definition du champ par defaut $_champ_defaut = !isset($crit->param[0][0]) ? "''" : calculer_liste(array($crit->param[0][0]), array(), $boucles, $boucle->id_parent); $_sens_defaut = !isset($crit->param[1][0]) ? "1" : calculer_liste(array($crit->param[1][0]), array(), $boucles, $boucle->id_parent); $_variable = !isset($crit->param[2][0]) ? "'$idb'" : calculer_liste(array($crit->param[2][0]), array(), $boucles, $boucle->id_parent); $_tri = "((\$t=(isset(\$Pile[0]['tri'.$_variable]))?\$Pile[0]['tri'.$_variable]:$_champ_defaut)?tri_protege_champ(\$t):'')"; $_sens_defaut = "(is_array(\$s=$_sens_defaut)?(isset(\$s[\$st=$_tri])?\$s[\$st]:reset(\$s)):\$s)"; $_sens = "((intval(\$t=(isset(\$Pile[0]['sens'.$_variable]))?\$Pile[0]['sens'.$_variable]:$_sens_defaut)==-1 OR \$t=='inverse')?-1:1)"; $boucle->modificateur['tri_champ'] = $_tri; $boucle->modificateur['tri_sens'] = $_sens; $boucle->modificateur['tri_nom'] = $_variable; // faut il inserer un test sur l'existence de $tri parmi les champs de la table ? // evite des erreurs sql, mais peut empecher des tri sur jointure ... $boucle->hash .= " \$senstri = ''; \$tri = $_tri; if (\$tri){ \$senstri = $_sens; \$senstri = (\$senstri<0)?' DESC':''; }; "; $boucle->select[] = "\".tri_champ_select(\$tri).\""; $boucle->order[] = "tri_champ_order(\$tri,\$command['from']).\$senstri"; } # Criteres de comparaison // http://doc.spip.org/@calculer_critere_DEFAUT function calculer_critere_DEFAUT_dist($idb, &$boucles, $crit){ // double cas particulier {0,1} et {1/2} repere a l'analyse lexicale if (($crit->op==",") OR ($crit->op=='/')) return calculer_critere_parties($idb, $boucles, $crit); $r = calculer_critere_infixe($idb, $boucles, $crit); if (!$r){ # // on produit une erreur seulement si le critere n'a pas de '?' # if (!$crit->cond) { return (array('zbug_critere_inconnu', array('critere' => $crit->op))); # } } else calculer_critere_DEFAUT_args($idb, $boucles, $crit, $r); } function calculer_critere_DEFAUT_args($idb, &$boucles, $crit, $args){ list($arg, $op, $val, $col, $where_complement) = $args; $where = array("'$op'", "'$arg'", $val[0]); // inserer la negation (cf !...) if ($crit->not) $where = array("'NOT'", $where); if ($crit->exclus) if (!preg_match(",^L[0-9]+[.],", $arg)) $where = array("'NOT'", $where); else // un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete // c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent $where = array("'NOT'", array("'IN'", "'".$boucles[$idb]->id_table.".".$boucles[$idb]->primary."'", array("'SELF'", "'".$boucles[$idb]->id_table.".".$boucles[$idb]->primary."'", $where))); // inserer la condition (cf {lang?}) // traiter a part la date, elle est mise d'office par SPIP, if ($crit->cond){ $pred = calculer_argument_precedent($idb, $col, $boucles); if ($col=="date" OR $col=="date_redac"){ if ($pred=="\$Pile[0]['".$col."']"){ $pred = "(\$Pile[0]['{$col}_default']?'':$pred)"; } } if ($op=='=' AND !$crit->not) $where = array("'?'", "(is_array($pred))", critere_IN_cas($idb, $boucles, 'COND', $arg, $op, array($pred), $col), $where); $where = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where); if ($where_complement) // condition annexe du type "AND (objet='article')" $where_complement = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where_complement); } $boucles[$idb]->where[] = $where; if ($where_complement) // condition annexe du type "AND (objet='article')" $boucles[$idb]->where[] = $where_complement; } // http://doc.spip.org/@calculer_critere_infixe function calculer_critere_infixe($idb, &$boucles, $crit){ global $table_criteres_infixes; global $exceptions_des_jointures, $exceptions_des_tables; $boucle = &$boucles[$idb]; $type = $boucle->type_requete; $table = $boucle->id_table; $desc = $boucle->show; $col_vraie = null; list($fct, $col, $op, $val, $args_sql) = calculer_critere_infixe_ops($idb, $boucles, $crit); $col_alias = $col; $where_complement = false; // Cas particulier : id_enfant => utiliser la colonne id_objet if ($col=='id_enfant') $col = $boucle->primary; // Cas particulier : id_parent => verifier les exceptions de tables if (in_array($col,array('id_parent','id_secteur')) AND isset($exceptions_des_tables[$table][$col])) $col = $exceptions_des_tables[$table][$col]; // et possibilite de gerer un critere secteur sur des tables de plugins (ie forums) else if (($col=='id_secteur') AND ($critere_secteur = charger_fonction("critere_secteur_$type", "public", true))){ $table = $critere_secteur($idb, $boucles, $val, $crit); } // cas id_article=xx qui se mappe en id_objet=xx AND objet=article // sauf si exception declaree : sauter cette etape else if ( !isset($exceptions_des_jointures[table_objet_sql($table)][$col]) AND !isset($exceptions_des_jointures[$col]) AND count(trouver_champs_decomposes($col, $desc))>1 ){ $e = decompose_champ_id_objet($col); $col = array_shift($e); $where_complement = primary_doublee($e, $table); } // Cas particulier : expressions de date else if ($c = calculer_critere_infixe_date($idb, $boucles, $col)){ list($col,$col_vraie) = $c; $table = ''; } else if (preg_match('/^(.*)\.(.*)$/', $col, $r)){ list(, $table, $col) = $r; $col_alias = $col; $trouver_table = charger_fonction('trouver_table','base'); if ($desc = $trouver_table($table, $boucle->sql_serveur) AND isset($desc['field'][$col]) AND $cle = array_search($desc['table'],$boucle->from)) $table = $cle; else { $table = trouver_jointure_champ($col, $boucle, array($table), ($crit->cond OR $op!='=')); } #$table = calculer_critere_externe_init($boucle, array($table), $col, $desc, ($crit->cond OR $op!='='), true); if (!$table) return ''; } elseif (@!array_key_exists($col, $desc['field']) // Champ joker * des iterateurs DATA qui accepte tout AND @!array_key_exists('*', $desc['field']) ) { $r = calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table); if (!$r) return ''; list($col, $col_alias, $table, $where_complement, $desc) = $r; } $col_vraie = ($col_vraie?$col_vraie:$col); // Dans tous les cas, // virer les guillemets eventuels autour d'un int (qui sont refuses par certains SQL) et passer dans sql_quote avec le type si connu // et int sinon si la valeur est numerique // sinon introduire le vrai type du champ si connu dans le sql_quote (ou int NOT NULL sinon) // Ne pas utiliser intval, PHP tronquant les Bigint de SQL if ($op=='=' OR in_array($op, $table_criteres_infixes)){ // defaire le quote des int et les passer dans sql_quote avec le bon type de champ si on le connait, int sinon // prendre en compte le debug ou la valeur arrive avec un commentaire PHP en debut if (preg_match(",^\\A(\s*//.*?$\s*)?\"'(-?\d+)'\"\\z,ms", $val[0], $r)) $val[0] = $r[1].'"'.sql_quote($r[2],$boucle->sql_serveur,(isset($desc['field'][$col_vraie])?$desc['field'][$col_vraie]:'int NOT NULL')).'"'; // sinon expliciter les // sql_quote(truc) en sql_quote(truc,'',type) // sql_quote(truc,serveur) en sql_quote(truc,serveur,type) // sans toucher aux // sql_quote(truc,'','varchar(10) DEFAULT \'oui\' COLLATE NOCASE') // sql_quote(truc,'','varchar') elseif (preg_match('/\Asql_quote[(](.*?)(,[^)]*?)?(,[^)]*(?:\(\d+\)[^)]*)?)?[)]\s*\z/ms', $val[0], $r) // si pas deja un type AND (!isset($r[3]) OR !$r[3])) { $r = $r[1] .((isset($r[2]) AND $r[2]) ? $r[2] : ",''") .",'".(isset($desc['field'][$col_vraie])?addslashes($desc['field'][$col_vraie]):'int NOT NULL')."'"; $val[0] = "sql_quote($r)"; } } // Indicateur pour permettre aux fonctionx boucle_X de modifier // leurs requetes par defaut, notamment le champ statut // Ne pas confondre champs de la table principale et des jointures if ($table===$boucle->id_table){ $boucles[$idb]->modificateur['criteres'][$col_vraie] = true; if ($col_alias!=$col_vraie) $boucles[$idb]->modificateur['criteres'][$col_alias] = true; } // ajout pour le cas special d'une condition sur le champ statut: // il faut alors interdire a la fonction de boucle // de mettre ses propres criteres de statut // http://www.spip.net/@statut (a documenter) // garde pour compatibilite avec code des plugins anterieurs, mais redondant avec la ligne precedente if ($col=='statut') $boucles[$idb]->statut = true; // inserer le nom de la table SQL devant le nom du champ if ($table){ if ($col[0]=="`") $arg = "$table.".substr($col, 1, -1); else $arg = "$table.$col"; } else $arg = $col; // inserer la fonction SQL if ($fct) $arg = "$fct($arg$args_sql)"; return array($arg, $op, $val, $col_alias, $where_complement); } function calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table){ global $exceptions_des_jointures; $where = ''; $calculer_critere_externe = 'calculer_critere_externe_init'; // gestion par les plugins des jointures tordues // pas automatiques mais necessaires $table_sql = table_objet_sql($table); if (isset($exceptions_des_jointures[$table_sql]) AND is_array($exceptions_des_jointures[$table_sql]) AND ( isset($exceptions_des_jointures[$table_sql][$col]) OR isset($exceptions_des_jointures[$table_sql]['']) ) ){ $t = $exceptions_des_jointures[$table_sql]; $index = isset($t[$col]) ? $t[$col] : (isset($t['']) ? $t[''] : array()); if (count($index)==3) list($t, $col, $calculer_critere_externe) = $index; elseif (count($index)==2) { list($t, $col) = $t[$col]; } elseif (count($index)==1) { list($calculer_critere_externe) = $index; $t = $table; } else $t = ''; // jointure non declaree. La trouver. } elseif (isset($exceptions_des_jointures[$col])) list($t, $col) = $exceptions_des_jointures[$col]; else $t = ''; // jointure non declaree. La trouver. // ici on construit le from pour fournir $col en piochant dans les jointures // si des jointures explicites sont fournies, on cherche d'abord dans celles ci // permet de forcer une table de lien quand il y a ambiguite // // alors que produit la meme chose que $table = ""; if ($boucle->jointures_explicites){ $jointures_explicites = explode(' ', $boucle->jointures_explicites); $table = $calculer_critere_externe($boucle, $jointures_explicites, $col, $desc, ($crit->cond OR $op!='='), $t); } // et sinon on cherche parmi toutes les jointures declarees if (!$table) { $table = $calculer_critere_externe($boucle, $boucle->jointures, $col, $desc, ($crit->cond OR $op!='='), $t); } if (!$table) return ''; // il ne reste plus qu'a trouver le champ dans les from list($nom, $desc) = trouver_champ_exterieur($col, $boucle->from, $boucle); if (count(trouver_champs_decomposes($col, $desc))>1){ $col_alias = $col; // id_article devient juste le nom d'origine $e = decompose_champ_id_objet($col); $col = array_shift($e); $where = primary_doublee($e, $table); } return array($col, $col_alias, $table, $where, $desc); } // Ne pas appliquer sql_quote lors de la compilation, // car on ne connait pas le serveur SQL, donc s'il faut \' ou '' // http://doc.spip.org/@primary_doublee function primary_doublee($decompose, $table){ $e1 = reset($decompose); $e2 = "sql_quote('".end($decompose)."')"; return array("'='", "'$table.".$e1."'", $e2); } /** * Champ hors table, ca ne peut etre qu'une jointure. * On cherche la table du champ et on regarde si elle est deja jointe * Si oui et qu'on y cherche un champ nouveau, pas de jointure supplementaire * Exemple: criteres {titre_mot=...}{type_mot=...} * Dans les 2 autres cas ==> jointure * (Exemple: criteres {type_mot=...}{type_mot=...} donne 2 jointures * pour selectioner ce qui a exactement ces 2 mots-cles. * * http://doc.spip.org/@calculer_critere_externe_init * * @param $boucle * @param $joints * @param $col * @param $desc * @param $cond * @param bool|string $checkarrivee * @return mixed|string */ function calculer_critere_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false){ // si on demande un truc du genre spip_mots // avec aussi spip_mots_liens dans les jointures dispo // et qu'on est la // il faut privilegier la jointure directe en 2 etapes spip_mots_liens, spip_mots if ($checkarrivee AND is_string($checkarrivee) AND $a = table_objet($checkarrivee) AND in_array($a.'_liens', $joints) ){ if ($res = calculer_lien_externe_init($boucle, $joints, $col, $desc, $cond, $checkarrivee)) { return $res; } } foreach ($joints as $joint){ if ($arrivee = trouver_champ_exterieur($col, array($joint), $boucle, $checkarrivee)){ $t = array_search($arrivee[0], $boucle->from); // transformer eventuellement id_xx en (id_objet,objet) $cols = trouver_champs_decomposes($col, $arrivee[1]); if ($t){ $joindre = false; foreach ($cols as $col){ $c = '/\b'.$t.".$col".'\b/'; if (trouver_champ($c, $boucle->where)) $joindre = true; else { // mais ca peut etre dans le FIELD pour le Having $c = "/FIELD.$t".".$col,/"; if (trouver_champ($c, $boucle->select)) $joindre = true; } } if (!$joindre) return $t; } if ($res = calculer_jointure($boucle, array($boucle->id_table, $desc), $arrivee, $cols, $cond, 1)) { return $res; } } } return ''; } /** * Generer directement une jointure via une table de lien spip_xxx_liens * pour un critere {id_xxx} * @param $boucle * @param $joints * @param $col * @param $desc * @param $cond * @param bool $checkarrivee * @return string */ function calculer_lien_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false){ $primary_arrivee = id_table_objet($checkarrivee); $intermediaire = trouver_champ_exterieur($primary_arrivee, $joints, $boucle, $checkarrivee."_liens"); $arrivee = trouver_champ_exterieur($col, $joints, $boucle, $checkarrivee); if (!$intermediaire OR !$arrivee) return ''; $res = fabrique_jointures($boucle, array( array($boucle->id_table, $intermediaire, array(id_table_objet($desc['table_objet']), 'id_objet', 'objet', $desc['type'])), array(reset($intermediaire), $arrivee, $primary_arrivee) ) , $cond, $desc, $boucle->id_table, array($col)); return $res; } // http://doc.spip.org/@trouver_champ function trouver_champ($champ, $where){ if (!is_array($where)) return preg_match($champ, $where); else { foreach ($where as $clause){ if (trouver_champ($champ, $clause)) return true; } return false; } } // determine l'operateur et les operandes // http://doc.spip.org/@calculer_critere_infixe_ops function calculer_critere_infixe_ops($idb, &$boucles, $crit){ // cas d'une valeur comparee a elle-meme ou son referent if (count($crit->param)==0){ $op = '='; $col = $val = $crit->op; if (preg_match('/^(.*)\.(.*)$/', $col, $r)) $val = $r[2]; // Cas special {lang} : aller chercher $GLOBALS['spip_lang'] if ($val=='lang') $val = array(kwote('$GLOBALS[\'spip_lang\']')); else { $defaut = null; if ($val=='id_parent') { // Si id_parent, comparer l'id_parent avec l'id_objet // de la boucle superieure.... faudrait verifier qu'il existe // pour eviter l'erreur SQL $val = $boucles[$idb]->primary; // mais si pas de boucle superieure, prendre id_parent dans l'env $defaut = "\$Pile[0]['id_parent']"; } elseif ($val=='id_enfant'){ // Si id_enfant, comparer l'id_objet avec l'id_parent // de la boucle superieure $val = 'id_parent'; } elseif ($crit->cond AND ($col=="date" OR $col=="date_redac")){ // un critere conditionnel sur date est traite a part // car la date est mise d'office par SPIP, $defaut = "(\$Pile[0]['{$col}_default']?'':\$Pile[0]['".$col."'])"; } $val = calculer_argument_precedent($idb, $val, $boucles, $defaut); $val = array(kwote($val)); } } else { // comparaison explicite // le phraseur impose que le premier param soit du texte $params = $crit->param; $op = $crit->op; if ($op=='==') $op = 'REGEXP'; $col = array_shift($params); $col = $col[0]->texte; $val = array(); $desc = array('id_mere' => $idb); $parent = $boucles[$idb]->id_parent; // Dans le cas {x=='#DATE'} etc, defaire le travail du phraseur, // celui ne sachant pas ce qu'est un critere infixe // et a fortiori son 2e operande qu'entoure " ou ' if (count($params)==1 AND count($params[0]==3) AND $params[0][0]->type=='texte' AND @$params[0][2]->type=='texte' AND ($p = $params[0][0]->texte)==$params[0][2]->texte AND (($p=="'") OR ($p=='"')) AND $params[0][1]->type=='champ' ){ $val[] = "$p\\$p#".$params[0][1]->nom_champ."\\$p$p"; } else foreach ((($op!='IN') ? $params : calculer_vieux_in($params)) as $p){ $a = calculer_liste($p, $desc, $boucles, $parent); if (strcasecmp($op,'IN')==0) $val[] = $a; else $val[] = kwote($a, $boucles[$idb]->sql_serveur, 'char'); // toujours quoter en char ici } } $fct = $args_sql = ''; // fonction SQL ? if (preg_match('/^(.*)'.SQL_ARGS.'$/', $col, $m)){ $fct = $m[1]; preg_match('/^\(([^,]*)(.*)\)$/', $m[2], $a); $col = $a[1]; if (preg_match('/^(\S*)(\s+AS\s+.*)$/i', $col, $m)){ $col = $m[1]; $args_sql = $m[2]; } $args_sql .= $a[2]; ; } return array($fct, $col, $op, $val, $args_sql); } // compatibilite ancienne version // http://doc.spip.org/@calculer_vieux_in function calculer_vieux_in($params){ $deb = $params[0][0]; $k = count($params)-1; $last = $params[$k]; $j = count($last)-1; $last = $last[$j]; $n = isset($last->texte) ? strlen($last->texte) : 0; if (!((isset($deb->texte[0]) AND $deb->texte[0]=='(') && (isset($last->texte[$n-1]) AND $last->texte[$n-1]==')'))) return $params; $params[0][0]->texte = substr($deb->texte, 1); // attention, on peut avoir k=0,j=0 ==> recalculer $last = $params[$k][$j]; $n = strlen($last->texte); $params[$k][$j]->texte = substr($last->texte, 0, $n-1); $newp = array(); foreach ($params as $v){ if ($v[0]->type!='texte') $newp[] = $v; else { foreach (explode(',', $v[0]->texte) as $x){ $t = new Texte; $t->texte = $x; $newp[] = array($t); } } } return $newp; } // http://doc.spip.org/@calculer_critere_infixe_date function calculer_critere_infixe_date($idb, &$boucles, $col){ if (!preg_match(",^((age|jour|mois|annee)_relatif|date|mois|annee|jour|heure|age)(_[a-z]+)?$,", $col, $regs)) return ''; $boucle = $boucles[$idb]; $table = $boucle->show; if (!$table['date'] && !isset($GLOBALS['table_date'][$table['id_table']])) return ''; $pred = $date_orig = isset($GLOBALS['table_date'][$table['id_table']])? $GLOBALS['table_date'][$table['id_table']] : $table['date']; $col = $regs[1]; if (isset($regs[3]) AND $suite = $regs[3]){ # Recherche de l'existence du champ date_xxxx, # si oui choisir ce champ, sinon choisir xxxx if ($table['field']["date$suite"]) $date_orig = 'date'.$suite; else $date_orig = substr($suite, 1); $pred = $date_orig; } else if (isset($regs[2]) AND $rel = $regs[2]) $pred = 'date'; $date_compare = "\"' . normaliser_date(". calculer_argument_precedent($idb, $pred, $boucles). ") . '\""; $col_vraie = $date_orig; $date_orig = $boucle->id_table.'.'.$date_orig; switch ($col) { case 'date': $col = $date_orig; break; case 'jour': $col = "DAYOFMONTH($date_orig)"; break; case 'mois': $col = "MONTH($date_orig)"; break; case 'annee': $col = "YEAR($date_orig)"; break; case 'heure': $col = "DATE_FORMAT($date_orig, \\'%H:%i\\')"; break; case 'age': $col = calculer_param_date("NOW()", $date_orig); $col_vraie = "";// comparer a un int (par defaut) break; case 'age_relatif': $col = calculer_param_date($date_compare, $date_orig); $col_vraie = "";// comparer a un int (par defaut) break; case 'jour_relatif': $col = "LEAST(TO_DAYS(".$date_compare.")-TO_DAYS(". $date_orig."), DAYOFMONTH(".$date_compare. ")-DAYOFMONTH(".$date_orig.")+30.4368*(MONTH(". $date_compare.")-MONTH(".$date_orig. "))+365.2422*(YEAR(".$date_compare.")-YEAR(". $date_orig.")))"; $col_vraie = "";// comparer a un int (par defaut) break; case 'mois_relatif': $col = "MONTH(".$date_compare.")-MONTH(". $date_orig.")+12*(YEAR(".$date_compare. ")-YEAR(".$date_orig."))"; $col_vraie = "";// comparer a un int (par defaut) break; case 'annee_relatif': $col = "YEAR(".$date_compare.")-YEAR(". $date_orig.")"; $col_vraie = "";// comparer a un int (par defaut) break; } return array($col,$col_vraie); } // http://doc.spip.org/@calculer_param_date function calculer_param_date($date_compare, $date_orig){ if (preg_match(",'\" *\.(.*)\. *\"',", $date_compare, $r)){ $init = "'\" . (\$x = $r[1]) . \"'"; $date_compare = '\'$x\''; } else $init = $date_compare; return "LEAST((UNIX_TIMESTAMP(". $init. ")-UNIX_TIMESTAMP(". $date_orig. "))/86400,\n\tTO_DAYS(". $date_compare. ")-TO_DAYS(". $date_orig. "),\n\tDAYOFMONTH(". $date_compare. ")-DAYOFMONTH(". $date_orig. ")+30.4368*(MONTH(". $date_compare. ")-MONTH(". $date_orig. "))+365.2422*(YEAR(". $date_compare. ")-YEAR(". $date_orig. ")))"; } /** * (DATA){source mode, "xxxxxx", arg, arg, arg} * @param string $idb * @param object $boucles * @param object $crit */ function critere_DATA_source_dist($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; $args = array(); foreach ($crit->param as &$param) array_push($args, calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent)); $boucle->hash .= ' $command[\'sourcemode\'] = '.array_shift($args).";\n"; $boucle->hash .= ' $command[\'source\'] = array('.join(', ', $args).");\n"; } /** * (DATA){datasource "xxxxxx", mode} <= deprecated * @param string $idb * @param object $boucles * @param object $crit */ function critere_DATA_datasource_dist($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; $boucle->hash .= ' $command[\'source\'] = array('.calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent).'); $command[\'sourcemode\'] = '.calculer_liste($crit->param[1], array(), $boucles, $boucles[$idb]->id_parent).';'; } /** * (DATA){datacache} * @param string $idb * @param object $boucles * @param object $crit */ function critere_DATA_datacache_dist($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; $boucle->hash .= ' $command[\'datacache\'] = '.calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent).';'; } /** * Pour passer des arguments a un iterateur non-spip * (php:xxxIterator){args argument1, argument2, argument3} * * @param string $idb * @param object $boucles * @param object $crit */ function critere_php_args_dist($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; $boucle->hash .= '$command[\'args\']=array();'; foreach ($crit->param as $param){ $boucle->hash .= ' $command[\'args\'][] = '.calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent).';'; } } /** * Passer une liste de donnees a l'iterateur DATA * (DATA){liste X1, X2, X3} * * @param string $idb * @param object $boucles * @param object $crit */ function critere_DATA_liste_dist($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; $boucle->hash .= "\n\t".'$command[\'liste\'] = array();'."\n"; foreach ($crit->param as $param){ $boucle->hash .= "\t".'$command[\'liste\'][] = '.calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent).";\n"; } } /** * Passer un enum min max a l'iterateur DATA * (DATA){enum Xmin, Xmax} * * @param string $idb * @param object $boucles * @param object $crit */ function critere_DATA_enum_dist($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; $boucle->hash .= "\n\t".'$command[\'enum\'] = array();'."\n"; foreach ($crit->param as $param){ $boucle->hash .= "\t".'$command[\'enum\'][] = '.calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent).";\n"; } } /** * Extraire un chemin d'un tableau de donnees * (DATA){datapath query.results} * * @param string $idb * @param object $boucles * @param object $crit */ function critere_DATA_datapath_dist($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; foreach ($crit->param as $param){ $boucle->hash .= ' $command[\'datapath\'][] = '.calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent).';'; } } /** * le critere {si ...} applicable a toutes les boucles * * @param string $idb * @param object $boucles * @param object $crit */ function critere_si_dist($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; // il faut initialiser 1 fois le tableau a chaque appel de la boucle // (par exemple lorsque notre boucle est appelee dans une autre boucle) // mais ne pas l'initialiser n fois si il y a n criteres {si } dans la boucle ! $boucle->hash .= "\n\tif (!isset(\$si_init)) { \$command['si'] = array(); \$si_init = true; }\n"; if ($crit->param){ foreach ($crit->param as $param){ $boucle->hash .= "\t\$command['si'][] = " . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ";\n"; } // interdire {si 0} aussi ! } else { $boucle->hash .= '$command[\'si\'][] = 0;'; } } /** * {tableau #XX} pour compatibilite ascendante boucle POUR * ... preferer la notation {datasource #XX,table} * * @param string $idb * @param object $boucles * @param object $crit */ function critere_POUR_tableau_dist($idb, &$boucles, $crit){ $boucle = &$boucles[$idb]; $boucle->hash .= ' $command[\'source\'] = array('.calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent).'); $command[\'sourcemode\'] = \'table\';'; } /** * Trouver toutes les objets qui ont des enfants (les noeuds de l'arbre) * {noeud} * {!noeud} retourne les feuilles * * @global array $exceptions_des_tables * @param string $idb * @param array $boucles * @param Object $crit */ function critere_noeud_dist($idb, &$boucles, $crit){ global $exceptions_des_tables; $not = $crit->not; $boucle = &$boucles[$idb]; $primary = $boucle->primary; if (!$primary OR strpos($primary, ',')){ erreur_squelette(_T('zbug_doublon_sur_table_sans_cle_primaire'), "BOUCLE$idb"); return; } $table = $boucle->type_requete; $table_sql = table_objet_sql(objet_type($table)); $id_parent = isset($exceptions_des_tables[$boucle->id_table]['id_parent']) ? $exceptions_des_tables[$boucle->id_table]['id_parent'] : 'id_parent'; $in = "IN"; $where = array("'IN'", "'$boucle->id_table."."$primary'", "'('.sql_get_select('$id_parent', '$table_sql').')'"); if ($not) $where = array("'NOT'", $where); $boucle->where[] = $where; } /** * Trouver toutes les objets qui n'ont pas d'enfants (les feuilles de l'arbre) * {feuille} * {!feuille} retourne les noeuds * * @global array $exceptions_des_tables * @param string $idb * @param array $boucles * @param Object $crit */ function critere_feuille_dist($idb, &$boucles, $crit){ $not = $crit->not; $crit->not = $not ? false : true; critere_noeud_dist($idb, $boucles, $crit); $crit->not = $not; } ?>