web-dev-qa-db-fra.com

Comment créer une structure de lien permanent pour les publications dans une catégorie spécifique

Je construis un blog à auteur unique qui inclura plusieurs catégories. Une catégorie principale est "Critiques de films".

L'auteur laissera une brève critique chaque fois qu'il regarde un film - même s'il a déjà vu le film et l'a passé en revue sur son site.

Pour la catégorie "Critiques de films" (uniquement cette catégorie), je dois configurer une structure de lien permanent telle que:

  • /% catégorie% /% postname% -% jour %% monthnum %% année% /
  • / critique de film/the-haineux-huit-01192016 /

Cela donnera une URL unique à chaque critique du même film.

Le reste des catégories utilisera simplement /% postname% /

Je suis convaincu à 99% que j'ai fait la même chose il y a quelques années, mais ce site n'est plus actif, je n'ai rien à propos du processus dans mes notes et je ne semble pas pouvoir trouver de direction via la recherche Google, WordPress. Forums .org, ou réponses WordPress.

3
Travis Pflanz

Je veux vous donner une approche "alternative". Je suis presque sûr que vous ne suivrez pas cela, mais je pense que c'est intéressant à lire.

OOP "routage" approche

Dans WordPress, les "jolies" URL sont calculées en "vilaines".

Mais la plupart des frameworks Web (pas seulement PHP) utilisent le concept de "routage": pour faire correspondre une URL à une "action" (ou un contrôleur).

Je veux vous donner une idée de la façon d'appliquer cette technique de rotation à WordPress, en utilisant une approche OOP.

L'interface

Tout d'abord, nous allons écrire une interface pour clarifier ce qu'un objet route doit faire:

namespace MySite;

interface RouteInterface
{
    /**
     * Returns true when the route matches for the given url
     *
     * @return bool
     */
    public function matched();

    /**
     * Returns the WP_Query variables connected to the route for current url.
     * Should be empty if route not matched
     *
     * @return array
     */
    public function getQueryArgs();

    /**
     * Return the url for the route.
     * Variable parts of the url should be provided via $args param.
     *
     * @param array $args
     * @return string
     */
    public function getUrl(array $args = []);
}

Très simple.

Veuillez lire les blocs de documentation pour plus de détails.

L'objet url

Pour "faire correspondre" une URL, nous devons d’abord le savoir. WordPress ne fournit aucune fonction ou méthode pour cela.

add_query_arg() , lorsqu'il est utilisé en passant un tableau vide, est suffisamment proche.

Toutefois, lorsque WordPress est installé dans un sous-dossier, le chemin d'accès au sous-dossier est également renvoyé. Nous devons le supprimer, car il ne fait pas partie de l'URL à rechercher.

Ecrivons un objet pour le scope.

namespace MySite;

class WordPressUri
{

    public function getUrl()
    {
        $url = trim(esc_url_raw(add_query_arg([])), '/');
        // check if wp is in a sub folder
        $homePath = trim(parse_url(home_url(), PHP_URL_PATH), '/');
        // remove WordPress subfolder if any
        if ($homePath) {
           $url = preg_replace('~^('.preg_quote($homePath, '~').'){1}~', '', $url);
        }

        return trim($url, '/');
    }
}

Très simple encore, je pense.

L'objet d'itinéraire concret

Maintenant, nous avons tout pour commencer à écrire l'objet route concret qui implémente l'interface route.

namespace MySite;

final class MovieReviewRoute implements RouteInterface {

    const REGEX = '^movie-review/([\w]+)-([0-9]{2})([0-9]{2})([0-9]{4})$';

    private $uri;
    private $postname = '';

    public function __construct(WordPressUri $uri = null) {
        $this->uri = $uri ? : new WordPressUri();
    }

    public function matched() {
        $matches = [];
        if (preg_match(self::REGEX, $this->uri->getUrl(), $matches) !== 1) {
            return false;
        }
        list(, , $day, $month, $year) = array_map('intval', $matches);
        if (checkdate($month, $day, $year)) {
            $this->postname = $matches[1];
            return true;
        }
        return false;
    }

    public function getQueryArgs() {
        return $this->postname ? ['name' => $this->postname] : [];
    }

    public function getUrl(array $args = []) {
        // check if postname was given, or as alternative a post object / post id
        $post = empty($args['post']) ? '' : get_post($args['post']);
        $postname = empty($args['postname']) ? '' : $args['postname'];
        if ( ! $postname && $post instanceof \WP_Post) {
            $postname = $post->post_name;
        }
        // if no date given, use post date if post was given, or just today
        if (empty($args['date'])) {
            $timestamp = $post instanceof \WP_Post
                ? strtotime($post->post_date)
                : current_time('timestamp');
            $args['date'] = date('dmY', $timestamp);
        }
        return home_url("movie-review/{$postname}-{$args['date']}");
    }
}

Désolé si il y a beaucoup de code au même endroit, mais cela ne fait rien de "spécial".

Il suffit de faire ce que les interfaces dicte, pour le cas spécifique.

Quelques détails sur les méthodes:

  • matched() utilise une expression régulière pour faire correspondre l'url au format souhaité. Il vérifie également que la date correspondante est une date valide. Dans le processus, il enregistre la variable d'objet postname afin qu'il puisse être utilisé pour savoir quelle est la publication correspondante.

  • getQueryArgs() renvoie simplement la requête var "name", cela suffit pour trouver un post

  • getUrl() combine le tableau des arguments donnés pour créer une URL qui correspondrait à la route. Juste quelle interface veut.

Le bon crochet

On a presque fini. Nous avons tous les objets, nous devons maintenant les utiliser. La première chose dont nous avons besoin est un crochet pour intercepter la demande.

Le bon endroit est 'do_parse_request' .

En renvoyant false sur ce crochet, nous pouvons empêcher WordPress d’analyser l’URL à l’aide de règles de réécriture. De plus, le hook transmet en second argument une instance de la classe WP: nous pouvons l'utiliser pour définir les variables de requête dont nous avons besoin, et que notre route peut fournir lorsqu'elles correspondent.

En action

Le code dont nous avons besoin pour correspondre à la route:

namespace MySite;

add_filter('do_parse_request', function ($bool, \WP $wp) {

    $movieRoute = new MovieReviewRoute();
    // if route matched, let's set query vars and stop WP to parse rules
    if ($movieRoute->matched()) {
        $wp->query_vars = $movieRoute->getQueryArgs();
        $wp->matched_rule = MovieReviewRoute::REGEX;

        // avoid WordPress to apply canonical redirect
        remove_action('template_redirect', 'redirect_canonical');

        // returning false WP will not parse the url
        return false;
    }

    return $bool;
}, 10, 2);

Je pense que cela devrait être assez facile à comprendre.

Deux choses à noter:

  • J'ai supprimé la fonction "redirect_canonical" de 'template_redirect', sinon WordPress (qui ignore tout de nos routes) peut rediriger le message vers son URL "canonique", c'est-à-dire celle qui est définie dans la structure standard permalien.

  • Je règle $wp->matched_rule sur quelque chose de prévisible: cela nous permet de savoir quand nous définissons des arguments de requête via notre objet route.

Générer des urls

La route fonctionne, mais nous devons envoyer les utilisateurs à notre route. Nous avons donc besoin de filtrer les permaliens. L'objet rote a une méthode qui génère l'URL, que nous pouvons utiliser pour l'étendue.

namespace MySite;

add_filter('post_link', function ($permalink, \WP_Post $post) {

    if (has_category('movie-review', $post)) {
        $movieRoute = new MovieReviewRoute();
        $permalink = $movieRoute->getUrl(['post' => $post]);
    }

    return $permalink;

}, 10, 2);

Avec ce code, toute publication de la catégorie "critique de film" aura l'URL correspondant à notre route.

Touches finales

Pour le moment, le message de la catégorie "critique de film" peut être visualisé avec 2 URL différentes, la standard et celle correspondant à notre itinéraire.

Nous devrions éviter cela, c'est très mauvais pour le référencement, entre autres.

namespace MySite;

add_action('template_redirect', function() {
    if (
        is_single()
        && has_category('movie-review', get_queried_object())
        && $GLOBALS['wp']->matched_rule !== MovieReviewRoute::REGEX
    ) {
        $movieRoute = new MovieReviewRoute();
        wp_redirect($movieRoute->getUrl(['post' => get_queried_object()]), 301);
        exit();
    }
});

Grâce à la variable que nous avons définie sur la classe WP lorsque notre route correspond, nous sommes en mesure de reconnaître le postage d'une critique de film avec une URL standard.

Si cela se produit, nous redirigeons simplement vers l'URL de la route.

Remarques

La question principale est: "Cela vaut-il la peine de trouble _"? Je peux dire que cette approche ne présente aucun problème avec l’approche de réécriture des règles.

Par exemple, vous pouvez modifier librement le slug de publication dans l'interface d'administration tout en conservant 2 structures d'URL différentes pour la publication dans cette catégorie spécifique.

Bien sûr, il y a plus de code que 2 fonctions accrochées à 2 crochets. Et plus de code, ce n'est jamais une bonne chose.

Cependant, vous devriez noter qu'après avoir mis cette méthode en place, il est beaucoup plus facile d'ajouter de plus en plus de routes. Il vous suffit d'écrire une classe, car l'interface et le "mécanisme" correspondant sont déjà présents.

Donc, pour répondre à la question: probablement no : cela ne vaut probablement pas la peine, mais si vous en avez quelques-uns, c'est peut-être le cas.

Considérez qu’il existe des bibliothèques cool que vous pouvez intégrer dans cette approche pour rendre le mécanisme de correspondance beaucoup plus simple et plus puissant.

Je t'ai eu

Le code est complètement non testé . Et il nécessite PHP 5.4+ à cause de la syntaxe de tableau courte.

6
gmazzap

Je ne suis pas sûr que ce soit la meilleure solution ou non, mais cela fonctionne:

function movie_review_permalink( $url, $post, $leavename ) {
    $category = get_the_category($post->ID); 
    if (  !empty($category) && $category[0]->slug == "test" ) { //change 'test' to your category slug
        $date=date_create($post->post_date);
        $my_date = date_format($date,"dmY");
        $url= trailingslashit( home_url('/'. $category[0]->slug .'/'. $post->post_name .'-'. $my_date .'/' ) );
    }
    return $url;
}
add_filter( 'post_link', 'movie_review_permalink', 10, 3 );

Le code ci-dessus rendra votre message permanent pour la structure de la catégorie test à http://wpHomeURL/test/post-name-ddmmyyyy.

Vous devez maintenant ajouter une règle de réécriture pour que cela fonctionne.

function movie_review_rewrite_rules( $wp_rewrite ) {
    $new_rules['^test/([^/]+)-([0-9]+)/?'] = 'index.php?name=$matches[1]'; //change 'test' to your category slug
    $wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
    return $wp_rewrite;
}
add_action('generate_rewrite_rules', 'movie_review_rewrite_rules');

J'espère que cela t'aides!

6
тнє Sufi