Recherche ajax avec les formulaires SPIP

16 November 2017

Le but de ce formulaire est une recherche dynamique : a chaque nouvelle lettre dans l'input la recherche s’affine.

La bonne nouvelle c’est qu’on peut faire cela nativement avec les fonctions présentent dans SPIP.

Dans un premier temps, il faut créer un fichier formulaires/recherche_ajax.php. Il va mettre à jour le contexte du formulaire en lui passant un critère de recherche {recherche}.

<?php

if (!defined('_ECRIRE_INC_VERSION')) {
    return;
}

function formulaires_recherche_ajax_charger_dist($ajax_bloc) {
    return array(
        'ajax_bloc' => $ajax_bloc,
        'recherche_'.$ajax_bloc => _request('recherche_'.$ajax_bloc),
        'lang' => $lang,
    );
}

Ensuite un peu de JavaScript et d’HTML pour recharger un bloc ajax, dans un fichier {formulaires/recherche_ajax.html}. Ici, j’ai simplement repris le formulaire de recherche de SPIP en modifiant le {name} de l’input de recherche.

<div class="formulaire_spip formulaire_recherche[ (#ENV{class})]" id="formulaire_recherche">
    <form action="[(#ENV{action})]" method="get">
        <div class="editer-groupe">
            [(#ENV{action}|form_hidden)]
            [<input type="hidden" name="lang" value="(#ENV{lang})" />]
            <label for="#ENV{_id_champ}"><:info_rechercher_02:></label>
            <input type="[(#HTML5|?{search,text})]" class="search text" name="recherche_#ENV{ajax_bloc}" id="#ENV{_id_champ}"[ value="(#ENV{recherche})"] />
            <input type="submit" class="submit" value="&gt;&gt;" title="<:info_rechercher:>" />
        </div>
    </form>
</div>

Dans le même fichier, à la fin, un peu de JavaScript pour mettre à jour le bloc_ajax (a savoir un squelette avec la boucle de recherche). Pour plus d’information, sur le fonctionnement d’Ajax avec SPIP, la page de [la documentation officiel->https://www.spip.net/fr_article3753.html] est bien faite.

 // Chaque input déclenche la recherche ajax
 $("#recherche_#ENV{ajax_bloc}").on("input", function () {
     ajaxReload(
         "#ENV{ajax_bloc}",
         {
             args: {
                 // Envoyer le contenu du formulaire dans le
                 // contexte de recherche SPIP
                 recherche: $(this).val()
             }
         }
     );
 });

Dans le bloc ajax (un fichier inclure/grille_article), je place simplement ma boucle de recherche :

[(#SET{defaut_tri,#ARRAY{
    titre,1,
    num titre,1,
    date,#ENV{date_sens,-1},
    id_article,1,
    points,-1
 }})]

<B_article_ajax>
    #ANCRE_PAGINATION
    <BOUCLE_article_ajax(ARTICLES){recherche ?}{tri #ENV{par,#ENV{recherche}|?{points,num titre}},#GET{defaut_tri}}{pagination}>
        <INCLURE{fond=inclure/article_resume, id_article} />
    </BOUCLE_article_ajax>
    [<p class="pagination">(#PAGINATION)</p>]
</B_article_ajax>

Il ne reste qu’à appeler tout cela :

[(#FORMULAIRE_RECHERCHE_AJAX{grille_article})]

<INCLURE{fond=inclure/grille_article, env, ajax=grille_article} />

Pour en savoir plus sur les formulaires de SPIP, rien ne vaut la documentation de spip.net :).


Éxecuter du code quand une commande est payée

16 June 2017

Lorsque l’on utilise le plugin commande et que l’on souhaite exécuter une peu de code lorsqu’une commande est payée, il suffit d’utiliser ce petit pipeline :

function prefixe_post_edition($flux) {
    if ($flux['args']['table'] == table_objet_sql('commande')
        and $flux['args']['action'] == 'instituer'
        and $flux['args']['statut_ancien'] == 'encours'
        and $flux['data']['statut'] == 'paye'
    ) {
        $id_commande = $flux['args']['id_objet'];
        // Coder ici
    }
    return $flux;
}

Ajouter un nouveau type d'auteur à #SPIP

27 January 2017

Dans le fichier config/mes_options.php :

<?php
$GLOBALS['liste_des_statuts']['inscrit'] = 'inscrit';

// Placer la corbeille à la fin du tableau
$backup_corbeille = $GLOBALS['liste_des_statuts']['texte_statut_poubelle'];
unset($GLOBALS['liste_des_statuts']['texte_statut_poubelle']);
$GLOBALS['liste_des_statuts']['texte_statut_poubelle'] = $backup_corbeille;

La clé du tableau $GLOBALS['liste_des_statuts'] sert également de chaîne de langue pour traduire le statut.
La valeur sera quand à elle enregistrée dans la base de donnée comme statut d’auteur.

Il y a aussi la constante AUTEURS_MIN_REDAC qui détermine si le statut fait partie des des visiteurs ou non.

<?php
define('AUTEURS_MIN_REDAC', "0minirezo,1comite,5poubelle");

Exectuer du code quand un auteur est ajouté à #SPIP

17 January 2017

Lorsqu’on ajoute un auteur, on a parfois envie d’automatiser certaine action : ajout automatique dans une zone d’accès restreint, ajout de mots-clés, ou tout simplement envoyer un mail.

Du coup, voici un pipeline qui fera le travail ! Il pourrait y avoir un effet indésirable cependant : lorsqu’un auteur est ressuscité de la corbeille, le pipeline se déclenche à nouveau.
Si c’est un problème, le pipeline post_insertion peut aussi être utilisé. Il contient cependant beaucoup moins d’information que post_edition.

<?php
function prefix_post_edition($flux) {
    if ($flux['args']['table'] == table_objet_sql('auteur')
        and $flux['args']['action'] == 'instituer'
        and $flux['args']['statut_ancien'] == '5poubelle'
    ) {
        $id_auteur = $flux['args']['id_objet'];
        $statut = $flux['data']['statut'];
        // Coder ici !
    }

    return $flux;
}

Une fonction bash pour chercher dans les fichiers de langue #SPIP

14 January 2017

Une petite fonction bash qui va chercher dans les fichiers de langue de SPIP après un mot en particulier.
Toujours pratique pour ne pas déclarer 10 fois la même chaîne de langue.

Il suffit d’ajouter ceci dans votre fichier .bashrc.

# Recherche dans les fichiers de langue SPIP
search_spip_lang () {
    LANG=${2:-"fr"} # On cherche par défaut dans le français. Mais le deuxième paramètre est la langue
    ag -G "lang/(.*)_$LANG.php" -- "$1" .
}

Un second paramètre peut être utiliser pour chercher dans une langue précise (sinon, il cherche en français).

Exemple :

Recherche en français du mot “spip”.

search_spip_lang spip

Recherche en anglais du mot “spip”.

search_spip_lang spip en

Ici j’utilise la commande ag pour chercher dans les fichiers. Il suffit de l’installer :

sudo apt install silversearcher-ag

Ack pourrait sans doute aussi faire le travail, mais ag est plus rapide :)


Executer du code lorsqu’un article #SPIP est publié

11 January 2017

Petit pipeline SPIP cool : quand un article SPIP est publié, on exécute le code.

Pratique par exemple pour envoyer une notification spéciale.

<?php
function prefix_post_edition($flux) {
    if ($flux['args']['table'] == table_objet_sql('article')
        and $flux['args']['action'] == 'instituer'
        and $flux['args']['statut_ancien'] != 'publie'
        and $flux['data']['statut'] == 'publie'
    ) {
        $id_article = $flux['args']['id_objet'];
        // Coder ici !
}

Autoriser un auteur s'il est présent dans la zone d'une rubrique #SPIP (acces restreint)

8 January 2017

Une petite fonction d’autorisation sympas : autoriser un auteur (peu importe son statut) à publier dans une rubrique, s’il est dans la zone de cette rubrique. :

<?php
function autoriser_accesrestreint_publiedans($faire, $type, $id, $qui, $opt) {
    include_spip('accesrestreint_fonctions');
    // On récupère les zones de la rubrique
    $zones = accesrestreint_zones_rubrique_et_hierarchie($id);
    foreach ($zones as $zone) {
        // Est-ce que l'auteur appartient à la zone ?
        if (accesrestreint_acces_zone($zone, $qui['id_auteur'])) {
            return true;
        }
    }

    return false;
}

Ensuite, on peut surcharger les autorisations de SPIP pour qu’elles tiennent compte de cette fonction :

<?php
/**
 * Surcharge de l'autorisation de publication dans les rubriques. On va prendre
 * en compte l'accès restreint. Si un auteur est dans la zone de la rubrique, on
 * l'autorise à publier
 */
function autoriser_rubrique_publierdans($faire, $type, $id, $qui, $opt) {
    if (autoriser('publiedans', 'accesrestreint', $id, $qui, $opt)) {
        return true;
    }

    return autoriser_rubrique_publierdans_dist($faire, $type, $id, $qui, $opt);
}

/**
 * Surcharge de l'autorisation de création d'article pour prendre en compte
 * l'autorisation d'accesrestreint
 */
function autoriser_rubrique_creerarticledans($faire, $type, $id, $qui, $opt) {

    if (autoriser('publiedans', 'accesrestreint', $id, $qui, $opt)) {
        return true;
    }

    return autoriser_rubrique_creerarticledans_dist($faire, $type, $id, $qui, $opt);
}


Trouver toutes les rubriques enfants d'une rubrique #SPIP

20 November 2016

[Edit] : Comme signalé dans un commentaire, il existe bien une fonction native qui s’occupe de cela.

Il est parfois intéressant d’avoir la liste complètes des sous-rubriques d’une rubrique.

Par exemple, pour conditionner l’affichage d’un élément à une rubrique et tous ces enfants.

Il n’existe pas de fonction SPIP pour faire cela (ou en tout cas je ne l’ai pas trouvée). Voici donc un peu de code pour régler le problème.
C’est un collègue qui est à l’origine de cette fonction, je l’ai adaptée.

Attention, il faut PHP >= 5.5 ou installer le plugin SPIP bonux, à cause de la fonction array_column.

<?php
/**
 * Retrouver les enfants directs d'une rubrique.
 *
 * @param int $id_rubrique
 * @access public
 * @return array tableau contenant les id_rubrique direct.
 */
function trouver_enfant($id_rubrique) {
    include_spip('base/abstract_sql');
    $sous_rubriques = sql_allfetsel('id_rubrique', 'spip_rubriques', 'id_parent='.intval($id_rubrique));
    return array_column($sous_rubriques, 'id_rubrique');
}

/**
 * Trouver les sous-rubriques d'une rubrique.
 *
 * @param int $id_rubrique l'id de la rubrique
 * @return array une liste d'id_rubrique
 */
function trouver_enfants($id_rubrique) {

    // Trouver la première série de sous-rubrique.
    $sous_rubriques = trouver_enfant($id_rubrique);

    // S'il n'y a pas de sous rubriques, on sort de la boucle avec juste la rubrique de base
    if (count($sous_rubriques) === 0) {
        return array($id_rubrique);
    } else {
        // On cherche en profondeur.
        foreach ($sous_rubriques as $id_sous_rubrique) {
            $recur = trouver_enfant($id_sous_rubrique);

            // Il y a des enfants, on les ajoute à la liste des sous rubrique
            // déjà trouvée.
            if ($recur) {
                $sous_rubriques = array_merge($sous_rubriques, $recur);
            }
        }

        // Placer la rubrique initiale en première position.
        array_unshift($sous_rubriques, $id_rubrique);

        return $sous_rubriques;
    }
}

Formulaire générique d'édition de méta #SPIP

10 November 2016

L’idée derrière ce formulaire est assez simple, pouvoir gérer n’importe quelle valeur de la table spip_metas et la modifiée. Si elle n’existe pas, la méta sera crée.

Il convient de prendre en compte qu’il y a 2 types de méta dans spip :

  • les métas simples, le casier ne contient qu’une seul valeur .
  • les métas plus complexent qui sont des tableaux php sérialisé.

Cela couvre un besoin similaire à Identité Extra, mais de manière plus générique.

L’idée finale serait d’avoir un plugin de gestion des métas du site (et donc des options des plugins) plus générique et plus facile d’accès. Je trouve qu’il pourrait être plus simple de déclarer des options de plugin sans avoir a déclaré systématiquement un CVT complet.

Fichier formulaires/editer_meta.html :

<div class="formulaire_spip formulaire_#FORM">

    [<p class="reponse_formulaire reponse_formulaire_erreur">
    (#ENV*{message_erreur})
    </p>]
    [<p class="reponse_formulaire reponse_formulaire_ok">
        (#ENV*{message_ok})
    </p>]

    [(#EDITABLE|oui)
    <form action="#ENV{action}" method="post">
        #ACTION_FORMULAIRE{#ENV{action}}

        #GENERER_SAISIES{#ENV{_saisies}}

        <p class="boutons">
            <input type="submit" class="submit" value="<:pass_ok:>" />
        </p>
    </form>
    ]
</div>

fichier formulaires/editer_meta.php :

<?php
if (!defined('_ECRIRE_INC_VERSION')) {
    return;
}

/**
 * Fonction saisie pour le formulaire d'édition générique de méta
 *
 * @param string $casier nom du meta casier
 * @param string $meta nom de l'éventuel sous-méta
 * @param array $options tableau d'options :
 *
 * ```php
 * $options_defaut = array(
 *      'type_saisie' => 'input',
 *      'label' => 'label'
 * );
 * ```
 *
 * @access public
 * @return array
 */
function formulaires_editer_meta_saisies_dist($casier, $meta = null, $options = array()) {

    // option par défaut
    $options_defaut = array(
        'type_saisie' => 'input',
        'label' => 'label'
    );
    // Fusionner les options par défaut avec les options de l'utilisateur
    $options = array_merge($options_defaut, $options);

    // Récupérer le meta casier et créer sa saisie
    $casier = ($meta) ? $casier.'/'.$meta : $casier;
    $saisies = array(
        array(
            'saisie' => $options['type_saisie'],
            'options' => array(
                'nom' => $casier,
                'label' => $options['label']
            )
        )
    );

    return $saisies;
}

/**
 * Fonction charger pour le formulaire d'édition générique de méta
 *
 * @param string $casier nom du meta casier
 * @param string $meta nom de l'éventuel sous-méta
 * @param array $options tableau d'options :
 *
 * ```php
 * $options_defaut = array(
 *      'type_saisie' => 'input',
 *      'label' => 'label'
 * );
 * ```
 *
 * @access public
 * @return array
 */
function formulaires_editer_meta_charger_dist($casier, $meta = null, $options = array()) {
    // Récupérer le casier
    $casier = ($meta) ? $casier.'/'.$meta : $casier;

    // Lire la configuration
    include_spip('inc/config');
    $contexte = lire_config($casier, array());

    // Dans le cas d'une saisie simple, on s'assure de renvoyer un tableau
    // valide pour la fonction charger
    if (!is_array($contexte)) {
        $contexte = array($casier => $contexte);
    }

    return $contexte;
}

/**
 * Fonction traiter pour le formulaire d'édition générique de méta
 *
 * @param string $casier nom du meta casier
 * @param string $meta nom de l'éventuel sous-méta
 * @param array $options tableau d'options :
 *
 * ```php
 * $options_defaut = array(
 *      'type_saisie' => 'input',
 *      'label' => 'label'
 * );
 * ```
 *
 * @access public
 * @return array
 */
function formulaires_editer_meta_traiter_dist($casier, $meta = null, $options = array()) {

    // Enregistrer la méta
    include_spip('inc/config');

    // Récupérer l'ancienne configuration du casier
    $old_config = lire_config($casier);
    if (is_array($old_config)) {
        // Dans le cas d'une méta complexe, on va fusionner les fusionner avec
        // la nouvelle
        $new_config = _request($casier);
        $new_config = array_merge($old_config, $new_config);
    } else {
        // Cas d'une saisie simple, on enregistre simplement la meta
        $new_config = _request($casier);
    }

    // Ecrire la nouvelle méta
    $ecrire = ecrire_config($casier, $new_config);

    // Retour du formulaire
    if ($ecrire) {
        return array(
            'editable' => true,
            'message_ok' => _T('info_modification_enregistree')
        );
    } else {
        return array(
            'editable' => true,
            'message_erreur' => _T('erreur')
        );
    }
}

Trouver la traduction d'un objet #SPIP

15 September 2016

Une fonction bien pratique quand on travaille avec des squelettes multilingues. Avant j’utilisais un assemblage de boucles et de critères, un peu illisible.

Cette fonction simplifie grandement le travail en retrouvant automatiquement une traduction en fonction d’un couple id_objet/objet.
Cela devrait fonctionner avec n’importe quel objet utilisant le système de traduction de SPIP.

<?php
/**
 * Récupère la traduction d'un objet via la langue et l'id_objet
 *
 * @param int $id_objet
 * @param string $objet
 * @param string $lang
 * @access public
 * @return int
 */
function objet_trouver_traduction($id_objet, $objet, $lang) {

    // Pas de lang, on sort
    if (!isset($lang)) {
        return $id_objet;
    }

    // Table
    $table = table_objet_sql($objet);
    $cle_primaire = id_table_objet($objet);
    // On récupère l'éventuelle traduction
    $id_traduction = sql_getfetsel(
        $cle_primaire,
        $table,
        array(
            'id_trad='.intval($id_objet),
            'lang='.sql_quote($lang),
            'statut='.sql_quote('publie')
        )
    );

    return $id_traduction;
}

function article_trouver_traduction($id_article, $lang) {
    return objet_trouver_traduction($id_article, 'article', $lang);
}

// Si le plugin Traduction entre rubriques est activé
// http://contrib.spip.net/Traduction-des-rubriques
function rubrique_trouver_traduction($id_article, $lang) {
    return objet_trouver_traduction($id_article, 'rubrique', $lang);
}

Je l’utilise surtout pour les liens directs dans les squelettes :

<a href="[(#URL_ARTICLE{#EVAL{_ID_ARTICLE_CONTACT}|article_trouver_traduction{#ENV{lang}}})]" class="button large">
    Nous contacter
</a>