web-dev-qa-db-ja.com

特定のカテゴリの投稿に対してパーマリンク構造を作成する方法

私は複数のカテゴリを含む単一の著者ブログを作成しています。 1つの主なカテゴリーは「映画のレビュー」です。

過去にその映画を見たことがあり、過去に自分のサイトでそれをレビューしたことがあっても、作者は映画を見るたびに短いレビューを残します。

"Movie Reviews"カテゴリ(このカテゴリのみ)では、以下のようなパーマリンク構造を設定する必要があります。

  • /%category%/%postname% - %day %% monthnum %% year%/
  • /映画レビュー/ the-hateful-eight-01192016 /

これにより、同じ映画の各レビューに固有のURLが与えられます。

残りのカテゴリは単に/%postname%/を使用します。

私は数年前に私がこれと全く同じことをしたことに99%肯定的です、しかしそのサイトはもう活動的ではありません。 .orgフォーラム、またはWordPress Answers。

3
Travis Pflanz

私はあなたに「代替」アプローチを与えたいと思います。私はあなたがこれに従わないと確信しています、しかし私は読むのが面白いと思います。

OOP「ルーティング」アプローチ

WordPressでは、「かわいい」URLは「醜い」URLに一致します。

しかし、ほとんどのWebフレームワーク(PHPだけではありません)は「ルーティング」の概念を使用しています:URLを「アクション」(またはコントローラー)に一致させるためです。

OOPのアプローチを使って、この腐ったテクニックをWordPressに適用する方法を考えてみましょう。

インターフェース

まず最初に、ルートオブジェクトが何をすべきかを明確にするためにインターフェースを書きます。

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 = []);
}

とても簡単です。

詳細はドキュメントブロックを読んでください。

URLオブジェクト

URLを「一致させる」ためには、まずそれを知る必要があります。 WordPressは機能を提供したり、そのための方法ではありません。

add_query_arg() は、空の配列を渡して使用すると、十分に近くなります。

ただし、WordPressがサブフォルダにインストールされている場合は、サブフォルダのパスも返されます。一致させたいURLの一部ではないため、削除する必要があります。

スコープのオブジェクトを書きましょう。

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, '/');
    }
}

非常に簡単ですが、私は思います。

具体的なルートオブジェクト

これで、routeインターフェースを実装する具体的な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']}");
    }
}

それが一箇所にたくさんのコードであるならば申し訳ありませんが、それは何も「特別な」をしません。

特定のケースでは、インターフェイスが指示することを実行するだけです。

メソッドのいくつかの詳細:

  • matched()は正規表現を使ってURLを必要な形式と照合します。一致した日付が有効な日付であることも確認します。その過程で、それはpostnameオブジェクト変数を保存するので、どの投稿がマッチしたのかを知るために使うことができます。

  • getQueryArgs()はクエリvar "name"を返すだけです。投稿を見つけるにはそれで十分です。

  • getUrl()は与えられた引数の配列を組み合わせて、ルートにマッチするURLを作成します。まさにインターフェイスが望むもの。

右フック

もう終わりです。すべてのオブジェクトが揃ったので、今度はそれらを使用する必要があります。最初に必要なことは、リクエストを傍受するためのフックです。

正しい場所は 'do_parse_request' です。

そのフックにfalseを返すと、WordPressが書き換え規則を使ってURLを解析できないようにすることができます。さらに、フックは2番目の引数としてWPクラスのインスタンスを渡します。これを使用して必要なクエリ変数を設定し、一致したときにルートが提供できるようにします。

動作中

ルートを一致させるために必要なコード:

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);

それは理解するのがかなり簡単であるべきだと思います。

注意する2つのこと:

  • 私は'template_redirect'から "redirect_canonical"関数を削除しました。そうでなければWordPress(それは私たちのルートについて何も知らない)がその投稿を "canonical" URLにリダイレクトすることができます。

  • $wp->matched_ruleを予測可能なものに設定しました。これにより、routeオブジェクトを介してクエリ引数をいつ設定したかを知ることができます。

URLを生成する

ルートは機能していますが、ユーザーを自分のルートに誘導する必要があります。だから私たちはパーマリンクをフィルタリングする必要があります。 roteオブジェクトにはURLを生成するメソッドがあります。これをスコープに活用できます。

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);

このコードでは、 "movie-review"カテゴリを持つ投稿はすべて、Googleのルートに一致するURLを取得します。

最後の仕上げ

現時点では、 "映画のレビュー"カテゴリの投稿は2つの異なるURL、標準のものと、1つが私たちのルートに一致するもので見ることができます。

私たちはそれを防ぐべきです、それはとりわけSEOにとって非常に悪いです。

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();
    }
});

ルートが一致したときにWPクラスに設定した変数のおかげで、映画レビュー投稿がいつ標準のURLで表示されたかを認識できます。

それが起こったら、私達はちょうどルートURLにリダイレクトします。

ノート

主な質問は、「問題の価値はありますか?」です。私はこのアプローチはあなたが書き換えルールのアプローチで抱えているどんな問題にも苦しんでいないと言うことができます。

例えば、あなたは自由に管理者インターフェースで投稿スラグを変更することができます、そしてその特定のカテゴリーの投稿のためにまだ2つの異なるURL構造を持つことができます。

もちろん2つの関数が2つのフックにフックされているよりも多くのコードです。そしてより多くのコードは、決して良いことではありません。

ただし、このメソッドを導入した後は、より多くのルートを追加することがはるかに簡単になります。インターフェイスとそれに対応する「メカニズム」はすでに存在するため、クラスを作成するだけで済みます。

それで、質問に答えるために:おそらく no :たった1つのルートのためにそれはおそらくトラブルの価値がありません、しかし、あなたが少し持っているなら、多分それはします。

クールなライブラリが存在することを考慮してください このアプローチに統合することで、マッチングメカニズムをはるかに簡単かつ強力にすることができます。

ガッチャ

コードは 完全にテストされていない です。また、短い配列構文のため、PHP 5.4以降が必要です。

6
gmazzap

これが最善の解決策かどうかはわかりませんが、うまくいきます。

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 );

上記のコードはカテゴリ test への投稿をhttp://wpHomeURL/test/post-name-ddmmyyyy構造体への固定リンクにします。

今、あなたはこれを機能させるために書き換えルールを追加する必要があるでしょう。

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');

お役に立てれば!

6
тнє Sufi