Example #1
0
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();
}
Example #2
0
/**
 * 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);
}
Example #3
0
/**
 * 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 '';
}
Example #4
0
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);

}
Example #5
0
/**
 * 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();
}