Tout savoir sur WordPress
Tutoriel WordPress

Ajouter un lien d’édition rapide vers le dernier article publié

Ce tutoriel montre la démarche à suivre pour ajouter un lien vers le dernier article publié dans le menu d’administration de chaque custom post type.

Je ne sais pas pour vous, mais il m’arrive souvent de retoucher le dernier article que j’ai publié. Que ce soit pour une faute d’orthographe, un problème d’encodage de caractères ou encore pour ajouter un élément suite à un commentaire pertinent, je dois souvent revenir sur mon dernier article publié pour l’éditer.

Dès lors, j’ai eu l’idée d’ajouter un lien d’édition rapide qui me permet en un clic d’accéder à la page d’édition de mon dernier article publié à partir du menu d’administration. J’ai ensuite amélioré l’astuce pour faire la même chose sur tous les custom post type disponibles.

L’origine de l’astuce

Voici comment tout a commencé, un petit bout de code qui ne fonctionne que pour le CPT “post” :
[pastacode lang=”php” message=”” highlight=”” provider=”manual”]


if( is_admin() ) {

function baw_fast_edit()  {
	
    global $wpdb, $submenu;
    // Récupération du dernier ID de l'article de type "post" publié
    $ID = $wpdb->get_var( 'SELECT ID FROM ' . $wpdb->posts . ' WHERE post_type="post" AND post_status="publish" ORDER BY ID DESC LIMIT 1' );
    
    // Hack du menu et ajout de mon élément.
    $submenu['edit.php'][] = array(
        'Editer le dernier article',
	'edit_posts',
	'edit.php?action=edit&post=' . $ID,
	'__return_null'
    );
}

add_action( 'admin_menu', 'baw_fast_edit' );
}

[/pastacode]
Je fais une requête qui me retourne le dernier ID de l’article publié du type “post”. Puis je hack le menu pour y ajouter directement un nouvel élément.

A noter que le fait de faire une requête de plus à chaque chargement de page est très léger et ne perturbe pas les performances du back-end.

La problématique : un seul CPT supporté

Mais voilà, ici, un seul type de post est supporté. Si cela vous suffit, tant mieux, mais si vous souhaitez avoir ça sur tous les custom post type, ça va se compliquer un peu.

Pourquoi ? Car nous allons avoir X custom post type et donc X ID. Je ne peux pas me permettre de faire X requêtes supplémentaires à chaque chargement de page ! Je vais devoir en faire une seule et la mettre en cache et gérer la mise à jour de ces IDs là.

Une requête personnalisée comme alternative

J’ai mis plus de temps que j’avais pensé à faire cette astuce. Les astuces que je propose ici sortent de ma tête et sont uniques. Ce n’est pas la peine de les chercher sur google.

En fait, je pensais utiliser la fonction wp_get_recent_posts() de WordPress. Elle permet de récupérer les derniers articles récents avec gestion de cache. Cependant, j’en ai besoin que pour un article par CPT et uniquement pour récupérer son ID. En utilisant wp_get_recent_posts(), je me retrouve avec un nombre de requêtes trop élevé puisque cette fonction fait appel aux fonctions get_posts() et get_post().

J’ai dû contourner ce problème et créer ma propre requête disponible ci-dessous :
[pastacode lang=”php” message=”” highlight=”” provider=”manual”]

if( is_admin() ) {

function baw_fast_edit( $only_query = false ) {
	
    global $wpdb, $submenu;
    
    // Si il y n'existe pas de cache ou que je force la query, je fais la query.
    if( $only_query || ( false===( $cpt_ids=get_transient( 'cpt_ids_cache' ) ) ) )
        $cpt_ids = $wpdb->get_results( 'SELECT DISTINCT p.post_type, (SELECT MAX(pp.ID) FROM ' . $wpdb->posts . ' pp WHERE p.post_type=pp.post_type AND pp.post_status="publish") AS ID FROM ' . $wpdb->posts . ' p', OBJECT_K );
        
        // Si j'ai demandé juste la query, je retourne le résutat, le reste est inutile.
	if( $only_query )
	    return $cpt_ids;
        
        // la boucle des CPT
	foreach( get_post_types( array( 'public' => true ), 'objects' ) as $cpt ):
        
            //  un slug différent pour le type "post"
	    $slug = $cpt->name!='post' ? 'edit.php?post_type=' . $cpt->name : 'edit.php';
    
            // Pas de lien si il n'y a pas d'ID et donc pas d'article encore pour ce type de post
	    if( $cpt_ids[$cpt->name]->ID>0 && current_user_can( $cpt->cap->edit_post, $cpt_ids[$cpt->name]->ID ) )
	        $submenu[$slug][] = array( sprintf( 
                    'Editer le dernier %s', $cpt->labels->singular_name ),
	            $cpt->cap->edit_posts,
	            sprintf( $cpt->_edit_link . '%s', $cpt_ids[$cpt->name]->ID, '&action=edit' ),
	            '__return_null'
	   );
    endforeach;
}
add_action( 'admin_menu', 'baw_fast_edit' );
}

[/pastacode]
Je l’avais dit, c’est autre chose …

Première différence, je passe un paramètre dans ma fonction pour la rendre réutilisable au lieu de recréer une seconde fonction qui fera quasiment la même chose, même requête.

Ensuite, je joue avec les transients pour voir si le cache existe ou je peux forcer la requête avec mon paramètre $only_query. Puis, je fais une boucle sur tous les types de posts “publics”, ceux qui sont logiquement visibles dans l’administration et qui ont donc un menu dédié.

Dans cette boucle je crée un $slug. En effet, entre le type “post” et tous les autres, seul le type “post” a le slug “edit.php”. Les autres CPT auront l’adresse : “edit.php?post_type=le_type”.

Enfin, encore dans la boucle, j’ai un peu modifié le texte pour pouvoir gérer tous les CPT grâce à un sprintf et son %s. La capacité d’édition est gérée et non plus edit_posts. Même chose pour le lien d’édition qui peut être modifié lors de la déclaration du CPT (Custom Post type).

Mise à jour du lien

[pastacode lang=”php” message=”” highlight=”” provider=”manual”]


if( is_admin() ) {

function transition_post_status_for_fast_edit( $new_status=null, $old_status=null, $post=null ) {
	
    if( ( $new_status==null && $old_status==null && $post==null && !EMPTY_TRASH_DAYS ) || 
	( ( $new_status == 'publish' || $old_status == 'publish' ) &&
	    array_key_exists( $post->post_type, get_post_types( array( 'public' => true ), 'array' ) ) )
    )  
	set_transient( 'cpt_ids_cache', baw_fast_edit( true ), 60*60*24*7 ); // = 7 jours
}
add_action('transition_post_status', 'transition_post_status_for_fast_edit', 10, 3 );
add_action('deleted_post', 'transition_post_status_for_fast_edit', 10, 0 );
}

[/pastacode]

Une fois de plus, je vais utiliser le hook transition_post_status afin de mettre à jour seulement au besoin ces IDs. Remarquez que j’ai déclaré les paramètres avec des valeurs par défaut. Je vais réutiliser la fonction en cas de suppression d’un article qui ne passerait pas par la corbeille. En effet, si on supprime le dernier article, l’ID serait incorrect.

Je commence par vérifier si tous les paramètres sont passés à NULL et que la constante EMPTY_TRASH_DAYS vaut faux (ou 0). Dans ce cas, c’est une suppression sans corbeille qui a été demandée.

Dans le cas contraire, si le nouveau status devient “publish” ou que l’ancien status était “publish”, alors je dois mettre à jour les IDs. Cela signifie qu’un nouvel article a été publié ou qu’un article publié a été mis en corbeille ou brouillon ou relecture, etc..

De plus, je vérifie que le CPT mis à jour est bien un CPT visible dans l’admin. Il est inutile de refresh le cache si je mets à jour une entrée de menu (qui est aussi un CPT).

Enfin, si tout cela est ok, je lance ma requête qui force le refresh du cache. J’ai mis 7 jours de cache, mais j’aurais pu en mettre aussi 1 ou 300. C’est uniquement là pour le jour où vous décidez de ne plus utiliser ça : la valeur en base se supprime (je suis sympa non ?).

Pour terminer l’explication, nous avons utilisé 2 hooks. Le fameux transition_post_status qui m’est utile pour les changements de status des articles et deleted_post qui intervient après la suppression d’un article en base.

PS : Vous n’avez pas compris quelquechose ? Demandez le moi, je serais ravi de vous expliquer encore :)

Cet article a été mis à jour il y a 4305 jours - Il n'est peut être plus à jour !

Article écrit par Julio P.

Fondateur de SecuPress, l’extension de sécurité WordPress, Julio Potier est un Expert WordPress, un Formateur Expérimenté et un Consultant en Sécurité Web.

Il aime partager ses compétences et ses réflexions sur WordPress, donnant des conférences partout dans le monde.

10 Commentaires

  1. Rien à voir avec l’astuce du jour mais avec mon avatar, qui vient de gravatar. Il y a longtemps que j’ai changé d’avatar et même supprimé sur gravatar la bêtise qu’on voit actuellement. Sais-tu pourquoi c’est encore cet avatar que je vois ici ? (Ailleurs je vois bien mon dernier avatar.)

  2. Bonjour,

    tant qu’on est dans les questions, c’est avec un plugin spécial que tu met en bas la barre noire en bas pour se connecter ou s’inscrire au blog ?

    Amicalement
    Patrick

  3. @Cristophe : Facile, remplace “Editer le dernier article” par “Dernier brouillon” et remplace aussi le mot “publish” par “draft” partout où il apparait dans les 2 fonctions.
    Il faudra bien sûr supprimer le transient “cpt_ids_cache” de la table “wp_options” (il s’agit de “_transient_cpt_ids_cache” et “_transient_timeout_cpt_ids_cache” dans la table pour être précis) ou alors changer les brouillons dans un autre status, puis les repasser en brouillon.
    Si tu veux les 2 liens en même temps, ça va être un peu plus compliqué, mais demande si tu veux, je ferais un pastebin.
    See you et merci

  4. Hello,

    Excellente astuce :)

    De mon côté j’ai simplement rajouté un lien vers mes brouillons, je me moquais un peu d’avoir toujours tous mes articles listés.
    Par contre le lien “dernier brouillon” c’est vraiment pas bête :)

    Merci Julio !

  5. J’ai copié-collé telles quelles les deux fonctions dans le functions.php du thème… page blanche !
    J’ai changé de thème pour mettre twenty eleven, ajouté les deux fonctions… page blanche aussi.
    J’ai désactivé toutes les extensions… page blanche aussi.
    Là je ne vois pas ce que je pourrais faire. Y aurait-il une bêtise que je ne vois pas ?

  6. Ça fonctionne ! Blog de test et “vrai blog” impeccables. J’ai modifié pour avoir le dernier brouillon, je vais gagner 3 secondes par jour ! d:-) Mais c’est bien pratique.

  7. Effectivement, il y a une erreur :
    $cpt_ids[$cpt->name]->ID->0
    doit être
    $cpt_ids[$cpt->name]->ID>0
    Merci à toi !