+/**
+ * Calculs pour le critère `{par sinum champ}` qui ordonne les champs avec numéros en premier
+ *
+ * Ajoute au SELECT la valeur 'sinum' qui vaut 0 si le champ a un numéro, 1 s'il n'en a pas.
+ * Ainsi `{par sinum titre, num titre, titre}` mettra les éléments sans numéro en fin de liste,
+ * contrairement à `{par num titre, titre}` seulement.
+ *
+ * @see calculer_critere_par_expression_num() pour le critère `{par num champ}`
+ * @uses calculer_critere_par_champ()
+ *
+ * @param string $idb Identifiant de la boucle
+ * @param array $boucles AST du squelette
+ * @param Critere $crit Paramètres du critère dans cette boucle
+ * @param array $tri Paramètre en cours du critère
+ * @param string $champ Texte suivant l'expression ('titre' dans {par sinum titre})
+ * @return string Clause pour le Order by
+ */
+function calculer_critere_par_expression_sinum($idb, &$boucles, $crit, $tri, $champ) {
+ $_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
+ if (is_array($_champ)) {
+ return array('zbug_critere_inconnu', array('critere' => $crit->op . " sinum $champ"));
+ }
+ $boucle = &$boucles[$idb];
+ $texte = '0+' . $_champ;
+ $suite = calculer_liste($tri, array(), $boucles, $boucle->id_parent);
+ if ($suite !== "''") {
+ $texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')" . " . \"";
+ }
+ $as = 'sinum' . ($boucle->order ? count($boucle->order) : "");
+ $boucle->select[] = 'CASE (' . $texte . ') WHEN 0 THEN 1 ELSE 0 END AS ' . $as;
+ $order = "'$as'";
+ return $order;
+}
+
+
+/**
+ * Calculs pour le critère `{par multi champ}` qui extrait la langue en cours dans les textes
+ * ayant des balises `<multi>` (polyglottes)
+ *
+ * Ajoute le calcul du texte multi extrait dans le SELECT de la boucle.
+ * Il ne peut y avoir qu'un seul critère de tri `multi` par boucle.
+ *
+ * @uses calculer_critere_par_champ()
+ * @param string $idb Identifiant de la boucle
+ * @param array $boucles AST du squelette
+ * @param Critere $crit Paramètres du critère dans cette boucle
+ * @param array $tri Paramètre en cours du critère
+ * @param string $champ Texte suivant l'expression ('titre' dans {par multi titre})
+ * @return string Clause pour le Order by
+ */
+function calculer_critere_par_expression_multi($idb, &$boucles, $crit, $tri, $champ) {
+ $_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
+ if (is_array($_champ)) {
+ return array('zbug_critere_inconnu', array('critere' => $crit->op . " multi $champ"));
+ }
+ $boucle = &$boucles[$idb];
+ $boucle->select[] = "\".sql_multi('" . $_champ . "', \$GLOBALS['spip_lang']).\"";
+ $order = "'multi'";
+ return $order;
+}
+
+/**
+ * Retourne le champ de tri demandé en ajoutant éventuellement les jointures nécessaires à la boucle.
+ *
+ * - si le champ existe dans la table, on l'utilise
+ * - si c'est une exception de jointure, on l'utilise (et crée la jointure au besoin)
+ * - si c'est un champ dont la jointure est déjà présente on la réutilise
+ * - si c'est un champ dont la jointure n'est pas présente, on la crée.
+ *
+ * @param string $idb Identifiant de la boucle
+ * @param array $boucles AST du squelette
+ * @param Critere $crit Paramètres du critère dans cette boucle
+ * @param string $par Nom du tri à analyser ('champ' ou 'table.champ')
+ * @param bool $raw Retourne le champ pour le compilateur ("'alias.champ'") ou brut ('alias.champ')
+ * @return array|string
+ */
+function calculer_critere_par_champ($idb, &$boucles, $crit, $par, $raw = false) {
+ $boucle = &$boucles[$idb];
+
+ // le champ existe dans la table, pas de souci (le plus commun)
+ if (isset($desc['field'][$par])) {
+ $par = $boucle->id_table . "." . $par;
+ }
+ // le champ est peut être une jointure
+ else {
+ $table = $table_alias = false; // toutes les tables de jointure possibles
+ $champ = $par;
+
+ // le champ demandé est une exception de jointure {par titre_mot}
+ if (isset($GLOBALS['exceptions_des_jointures'][$par])) {
+ list($table, $champ) = $GLOBALS['exceptions_des_jointures'][$par];
+ } // la table de jointure est explicitement indiquée {par truc.muche}
+ elseif (preg_match("/^([^,]*)\.(.*)$/", $par, $r)) {
+ list(, $table, $champ) = $r;
+ $table_alias = $table; // c'est peut-être un alias de table {par L1.titre}
+ $table = table_objet_sql($table);
+ }
+
+ // Si on connait la table d'arrivée, on la demande donc explicitement
+ // Sinon on cherche le champ dans les tables possibles de jointures
+ // Si la table est déjà dans le from, on la réutilise.
+ if ($infos = chercher_champ_dans_tables($champ, $boucle->from, $boucle->sql_serveur, $table)) {
+ $par = $infos['alias'] . "." . $champ;
+ } elseif (
+ $boucle->jointures_explicites
+ and $alias = trouver_jointure_champ($champ, $boucle, explode(' ', $boucle->jointures_explicites), false, $table)
+ ) {
+ $par = $alias . "." . $champ;
+ } elseif ($alias = trouver_jointure_champ($champ, $boucle, $boucle->jointures, false, $table)) {
+ $par = $alias . "." . $champ;
+ // en spécifiant directement l'alias {par L2.titre} (situation hasardeuse tout de même)
+ } elseif (
+ $table_alias
+ and isset($boucle->from[$table_alias])
+ and $infos = chercher_champ_dans_tables($champ, $boucle->from, $boucle->sql_serveur, $boucle->from[$table_alias])
+ ) {
+ $par = $infos['alias'] . "." . $champ;
+ } elseif ($table) {
+ // On avait table + champ, mais on ne les a pas trouvés
+ return array('zbug_critere_inconnu', array('critere' => $crit->op . " $par"));
+ } else {
+ // Sinon tant pis, ca doit etre un champ synthetise (cf points)
+ }
+ }
+
+ return $raw ? $par : "'$par'";
+}
+
+/**
+ * Retourne un champ de tri en créant une jointure
+ * si la table n'est pas présente dans le from de la boucle.
+ *
+ * @deprecated
+ * @param string $table Table du champ désiré
+ * @param string $champ Champ désiré
+ * @param Boucle $boucle Boucle en cours de compilation
+ * @return string Champ pour le compilateur si trouvé, tel que "'alias.champ'", sinon vide.
+ */
+function critere_par_joint($table, $champ, &$boucle) {
+ $t = array_search($table, $boucle->from);
+ if (!$t) {
+ $t = trouver_jointure_champ($champ, $boucle);
+ }
+ return !$t ? '' : ("'" . $t . '.' . $champ . "'");
+}
+
+/**
+ * Compile le critère `{inverse}` qui inverse l'ordre utilisé par le précédent critère `{par}`
+ *
+ * Accèpte un paramètre pour déterminer le sens : `{inverse #X}` utilisera un tri croissant (ASC)
+ * si la valeur retournée par `#X` est considérée vrai (`true`),
+ * le sens contraire (DESC) sinon.
+ *
+ * @example
+ * - `{par date}{inverse}`, équivalent à `{!par date}`
+ * - `{par date}{inverse #ENV{sens}}` utilise la valeur d'environnement sens pour déterminer le sens.
+ *
+ * @critere
+ * @see critere_par_dist() Le critère `{par}`
+ * @link http://www.spip.net/5530
+ * @uses critere_parinverse()
+ *
+ * @param string $idb Identifiant de la boucle
+ * @param array $boucles AST du squelette
+ * @param Critere $crit Paramètres du critère dans cette boucle
+ */
+function critere_inverse_dist($idb, &$boucles, $crit) {