function calculer_chaine_jointures(&$boucle, $depart, $arrivee, $vu=array(), $milieu_exclus = array(), $max_liens = 5) { static $trouver_table; if (!$trouver_table) $trouver_table = charger_fonction('trouver_table', 'base'); if (is_string($milieu_exclus)) $milieu_exclus = array($milieu_exclus); list($dnom,$ddesc) = $depart; list($anom,$adesc) = $arrivee; if (!count($vu)) $vu[] = $dnom; // ne pas oublier la table de depart $akeys = $adesc['key']; if ($v = $akeys['PRIMARY KEY']) { unset($akeys['PRIMARY KEY']); $akeys = array_merge(preg_split('/,\s*/', $v), $akeys); } // enlever les cles d'arrivee exclues par l'appel $akeys = array_diff($akeys,$milieu_exclus); // cles candidates au depart $keys = liste_champs_jointures($dnom,$ddesc); // enlever les cles dde depart exclues par l'appel $keys = array_diff($keys,$milieu_exclus); $v = !$keys ? false : array_intersect(array_values($keys), $akeys); if ($v) return array(array($dnom, array($adesc['table'],$adesc), array_shift($v))); // regarder si l'on a (id_objet,objet) au depart et si on peut le mapper sur un id_xx if (count(array_intersect(array('id_objet','objet'),$keys))==2){ // regarder si l'une des cles d'arrivee peut se decomposer en // id_objet,objet // si oui on la prend foreach($akeys as $key){ $v = decompose_champ_id_objet($key); if (is_array($v)){ $objet = array_shift($v);// objet,'article' array_unshift($v,$key); // id_article,objet,'article' array_unshift($v,$objet); // id_objet,id_article,objet,'article' return array(array($dnom, array($adesc['table'],$adesc), $v)); } } } else { // regarder si l'une des cles de depart peut se decomposer en // id_objet,objet a l'arrivee // si oui on la prend foreach($keys as $key){ if (count($v = trouver_champs_decomposes($key,$adesc))>1){ if (count($v)==count(array_intersect($v, $akeys))) $v = decompose_champ_id_objet($key); // id_objet,objet,'article' array_unshift($v,$key); // id_article,id_objet,objet,'article' return array(array($dnom, array($adesc['table'],$adesc), $v)); } } } // si l'on voulait une jointure direct, c'est rate ! if ($max_liens<=1) return array(); // sinon essayer de passer par une autre table $new = $vu; foreach($boucle->jointures as $v) { if ($v && (!in_array($v,$vu)) && ($def = $trouver_table($v, $boucle->sql_serveur))) { // ne pas tester les cles qui sont exclues a l'appel // ie la cle de la jointure precedente $test_cles = $milieu_exclus; $new[] = $v; $max_iter = 50; // securite while (count($jointure_directe_possible = calculer_chaine_jointures($boucle,$depart,array($v, $def),$vu,$test_cles,1)) AND $max_iter--) { $jointure_directe_possible = reset($jointure_directe_possible); $milieu = end($jointure_directe_possible); if (is_string($milieu)) $test_cles[] = $milieu; else $test_cles = array_merge($test_cles,$milieu); // essayer de rejoindre l'arrivee a partir de cette etape intermediaire // sans repasser par la meme cle milieu $r = calculer_chaine_jointures($boucle, array($v, $def), $arrivee, $new, $milieu,$max_liens-1); if ($r) { array_unshift($r, $jointure_directe_possible); return $r; } } } } return array(); }
/** * Décrit un critère non déclaré explicitement * * Décrit un critère non déclaré comme {id_article} {id_article>3} en * retournant un tableau de l'analyse si la colonne (ou l'alias) existe vraiment. * * Ajoute au passage pour chaque colonne utilisée (alias et colonne véritable) * un modificateur['criteres'][colonne]. * * S'occupe de rechercher des exceptions, tel que * - les id_parent, id_enfant, id_secteur, * - des colonnes avec des exceptions déclarées, * - des critères de date (jour_relatif, ...), * - des critères sur tables jointes explicites (mots.titre), * - des critères sur tables de jointure non explicite (id_mot sur une boucle articles...) * * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return array|string * Liste si on trouve le champ : * - string $arg * Opérande avant l'opérateur : souvent la colonne d'application du critère, parfois un calcul * plus complexe dans le cas des dates. * - string $op * L'opérateur utilisé, tel que '=' * - string[] $val * Liste de codes PHP obtenant les valeurs des comparaisons (ex: id_article sur la boucle parente) * Souvent (toujours ?) un tableau d'un seul élément. * - $col_alias * - $where_complement * * Chaîne vide si on ne trouve pas le champ... **/ 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]) or isset($exceptions_des_tables[$table][$col]) and is_string($exceptions_des_tables[$table][$col])) { $col = $exceptions_des_tables[$table][$col]; } else { if ($col == 'id_secteur' and $critere_secteur = charger_fonction("critere_secteur_{$type}", "public", true)) { $table = $critere_secteur($idb, $boucles, $val, $crit); } 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); } 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'])) 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') . '"'; } elseif (preg_match('/\\Asql_quote[(](.*?)(,[^)]*?)?(,[^)]*(?:\\(\\d+\\)[^)]*)?)?[)]\\s*\\z/ms', $val[0], $r) 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); }
/** * 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 ''; }
function calculer_critere_externe_init(&$boucle, $joints, $col, $desc, $eg, $checkarrivee = false) { $cle = trouver_champ_exterieur($col, $joints, $boucle, $checkarrivee); if (!$cle) return ''; $t = array_search($cle[0], $boucle->from); // transformer eventuellement id_xx en (id_objet,objet) $cols = trouver_champs_decomposes($col,$cle[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; } return calculer_jointure($boucle, array($boucle->id_table, $desc), $cle, $cols, $eg); }
/** * Constuire la chaine de jointures, de proche en proche * * http://doc.spip.org/@calculer_chaine_jointures * * @param objetc $boucle * @param array $depart * sous la forme array(nom de la table, description) * @param array $arrivee * sous la forme array(nom de la table, description) * @param array $vu * tables deja vues dans la jointure, pour ne pas y repasser * @param array $milieu_exclus * cles deja utilisees, pour ne pas les reutiliser * @param int $max_liens * nombre maxi d'etapes * @return array */ function calculer_chaine_jointures(&$boucle, $depart, $arrivee, $vu = array(), $milieu_exclus = array(), $max_liens = 5) { static $trouver_table; if (!$trouver_table) { $trouver_table = charger_fonction('trouver_table', 'base'); } if (is_string($milieu_exclus)) { $milieu_exclus = array($milieu_exclus); } // quand on a exclus id_objet comme cle de jointure, il faut aussi exclure objet // faire une jointure sur objet tout seul n'a pas de sens if (in_array('id_objet', $milieu_exclus) and !in_array('objet', $milieu_exclus)) { $milieu_exclus[] = 'objet'; } list($dnom, $ddesc) = $depart; list($anom, $adesc) = $arrivee; if (!count($vu)) { $vu[] = $dnom; // ne pas oublier la table de depart $vu[] = $anom; // ne pas oublier la table d'arrivee } $akeys = array(); foreach ($adesc['key'] as $k) { // respecter l'ordre de $adesc['key'] pour ne pas avoir id_trad en premier entre autres... $akeys = array_merge($akeys, preg_split('/,\\s*/', $k)); } // enlever les cles d'arrivee exclues par l'appel $akeys = array_diff($akeys, $milieu_exclus); // cles candidates au depart $keys = liste_champs_jointures($dnom, $ddesc); // enlever les cles dde depart exclues par l'appel $keys = array_diff($keys, $milieu_exclus); $v = !$keys ? false : array_intersect(array_values($keys), $akeys); if ($v) { return array(array($dnom, array($adesc['table'], $adesc), array_shift($v))); } // regarder si l'on a (id_objet,objet) au depart et si on peut le mapper sur un id_xx if (count(array_intersect(array('id_objet', 'objet'), $keys)) == 2) { // regarder si l'une des cles d'arrivee peut se decomposer en // id_objet,objet // si oui on la prend foreach ($akeys as $key) { $v = decompose_champ_id_objet($key); if (is_array($v)) { $objet = array_shift($v); // objet,'article' array_unshift($v, $key); // id_article,objet,'article' array_unshift($v, $objet); // id_objet,id_article,objet,'article' return array(array($dnom, array($adesc['table'], $adesc), $v)); } } } else { // regarder si l'une des cles de depart peut se decomposer en // id_objet,objet a l'arrivee // si oui on la prend foreach ($keys as $key) { if (count($v = trouver_champs_decomposes($key, $adesc)) > 1) { if (count($v) == count(array_intersect($v, $akeys))) { $v = decompose_champ_id_objet($key); // id_objet,objet,'article' array_unshift($v, $key); // id_article,id_objet,objet,'article' return array(array($dnom, array($adesc['table'], $adesc), $v)); } } } } // si l'on voulait une jointure direct, c'est rate ! if ($max_liens <= 1) { return array(); } // sinon essayer de passer par une autre table $new = $vu; foreach ($boucle->jointures as $v) { if ($v and !in_array($v, $vu) and $def = $trouver_table($v, $boucle->sql_serveur) and !in_array($def['table_sql'], $vu)) { // ne pas tester les cles qui sont exclues a l'appel // ie la cle de la jointure precedente $test_cles = $milieu_exclus; $new[] = $v; $max_iter = 50; // securite while (count($jointure_directe_possible = calculer_chaine_jointures($boucle, $depart, array($v, $def), $vu, $test_cles, 1)) and $max_iter--) { $jointure_directe_possible = reset($jointure_directe_possible); $milieu = end($jointure_directe_possible); $exclure_fin = $milieu_exclus; if (is_string($milieu)) { $exclure_fin[] = $milieu; $test_cles[] = $milieu; } else { $exclure_fin = array_merge($exclure_fin, $milieu); $test_cles = array_merge($test_cles, $milieu); } // essayer de rejoindre l'arrivee a partir de cette etape intermediaire // sans repasser par la meme cle milieu, ni une cle deja vue ! $r = calculer_chaine_jointures($boucle, array($v, $def), $arrivee, $new, $exclure_fin, $max_liens - 1); if ($r) { array_unshift($r, $jointure_directe_possible); return $r; } } } } return array(); }