WordPress5.4.1でパーマリンクに「日付のみ」が使えなくなったので対策をしました

先日のWordPress5.4.1へのマイナーアップデートで、非常にショッキングな変更がありました。

パーマリンクに「日付のみ」の設定がされていた場合、投稿が表示されなくなったのです。

WordPressのサポートフォーラムに投稿があり、そこで経緯がわかります。

wordpress 5.4.1 からの障害について

このウェブサイトはありがたいことに多くのウェブサイトから各記事にリンクをしていただいています。
しかし、今回のマイナーアップデートで記事が表示されなくなってしまいました。

この事実に気づいたとき、慌てて自身のFacebookにヘルプを出したのですが、ありがたいことにすぐに上記のサポートフォーラムの投稿を教えていただき対応することができました。

どのような問題が起きたのか

記事のパーマリンクが日時のみで構成されている場合、5.4以前は記事が表示されていたのですが、5.4.1からはタイトルしか表示されません。

具体的には、以前は「https://www.cherrypieweb.com/weblog/20191103130856.php」でアクセスすると「WordCamp Tokyo 2019 に参加して・・・」の記事が表示されていたのですが、5.4.1にアップデートした後では記事のタイトルしか出てこなくなったのです。

WordPress制作者の方向けに言うと、5.4までは上記のURLでアクセスするとsingle.phpで表示されていたのに、5.4.1ではarchive.phpで表示されるようになったのです。

ちなみにこのウェブサイトのパーマリンク設定は、カスタム構造で下記のように設定していました。

/%category%/%year%%monthnum%%day%%hour%%minute%%second%.php

なぜそのような変更が行われたのか

「年月日時分秒」のパーマリンクにしていた場合、同一時刻に公開された複数の投稿が存在する可能性があるため適切ではないということと、その際に2件目以降に非公開投稿があった場合にその投稿がログインしていなくても見えてしまうという問題があるためです。

このウェブサイトは以前MovableTypeで作成しており、その当時から個々の記事のURLは公開された時刻の「年月日時分秒.php」を使っていました。
本来であれば記事のURLは、個別に英語で指定するのが良いらしいのですが、記事を作成するたびに考えるのは面倒です。
(特に、間違った英訳をURLにしたら役に立たないだけでなく恥ずかしいですよね)

また、URLに記事のIDを使うと、ウェブサイトのサーバー移転の際にIDが変わってしまうという問題があります。
(実は現在のエックスサーバーに落ち着くまでは、あちこちのレンタルサーバーを転々としていたのです)

個人ブログであれば、同一時刻に複数の記事を投稿することはありえません。
そこで、これが最善だろうと考えて「年月日時分秒」を採用していました。

でも、投稿者が何人もいるウェブサイトであれば、まったくの同一時刻に投稿されるという可能性もあるのです。

どのような対策をとるべきか

ウェブサイトにおいて、記事のURLを変更する(パーマリンク設定を変更する)と言うのは非常に大きな問題です。
検索によってアクセスされた際に「ページが見つかりません」となるだけでなく、これまで積み上げられてきた検索結果のページランクがリセットされてしまうわけで、サイトによっては死活問題です。

かといって、WordPressのアップデートを5.4で止めることはできません。

最善の方法は、「パーマリンク設定を変更し、旧URLから新URLに301リダイレクトをかける」ことです。

WordPressであれば「Redirection」プラグインを使用して管理画面から設定することができます。
公開している記事が数十件程度であれば、手間ではありますができないことはないでしょう。

しかし、本ウェブサイトのように公開されている記事が100を超えているような場合は、さすがに辛いです。

そこで、旧URLでアクセスされたら新URLにリダイレクトするコードを書いてみました。

新しいパーマリンク構造を決める

まず、新しいパーマリンク構造を決めます。

一般には「英語でスラッグを設定して、/%post_name% を使う」のが推奨されています。
現在の記事数が少なければすべての記事にスラッグを設定して上記のようにすればよいのですが、100を超える記事にスラッグを設定していくのは大変です。(スラッグを設定しないとタイトルが使用されるので、スラッグが日本語になってしまいます)

そこで本ウェブサイトでは、/%post_id% とすることにしました。
URLに記事の内容が推測できない文字列を使うのはSEO的に良くないという話もありますが、みなさん、検索結果から記事を選択するときにURLなんて見ませんよね?
何より、これまで「年月日時分秒」という文字列を使っていたんですから、何を今さら・・・です(笑)

旧URLから新URLを求めてリダイレクト

次に、旧URLから新URLを求める方法を考えます。

旧URLは「年月日時分秒」で構成されていますから、ここから投稿が特定できるはずです。
投稿が特定できればIDがわかりますから、新URLがわかります。
新URLがわかれば301リダイレクトをかけます。

というわけで、完成したコードがこちらになります。
まずは、functions.phpに書いてみました。

※下記のコードは本ウェブサイト以外で使用するとWordPressが機能しなくなる可能性があります。
ご自分の環境や設定に合わせて書き直し、テスト環境で十分にテストしてから使用してください。

add_action( 'after_setup_theme', 'redirect_new_permalink' );
function redirect_new_permalink() {
    // Get URL
    $path_parts = $_SERVER["HTTP_HOST"].$_SERVER["REQUEST_URI"];
    $path_parts = pathinfo($path_parts);
    $filename = $path_parts['filename'];
    if (preg_match("/^[0-9]{14}$/", $filename)) { // permalink is old
        // Get post
        $targetpost = get_posts( array(
            'posts_per_page' => 1,
            'compare' => '=',
            'date_query' => array(
                array(
                    'year'   => substr($filename, 0, 4),
                    'month'  => substr($filename, 4, 2),
                    'day'    => substr($filename, 6, 2),
                    'hour'   => substr($filename, 8, 2),
                    'minute' => substr($filename, 10, 2),
                    'second' => substr($filename, 12, 2),
                    ),
                ),
        ) );
        // Redirect to new permalink
        if ($targetpost) {
            $redirectpost = (empty($_SERVER['HTTPS']) ? 'http://' : 'https://').$_SERVER["HTTP_HOST"].'/'.$targetpost[0]->ID;
            $location = 'Location: '.$redirectpost;
            header( "HTTP/1.1 301 Moved Permanently" ); 
            header( $location ); 
            exit;
        }
    }
}

URLにはカテゴリーが含まれている場合がありますので、PHPのpathinfo関数でウェブページの拡張子を除いた部分を取り出します。
旧URLでアクセスされていない場合はスキップさせないといけませんので、取り出した部分が14桁の数字のみで構成されているかどうかをチェックします。

14桁の数字であれば、分割することで「年」「月」「日」「時」「分」「秒」がとりだせます。
これを使って、get_posts() で投稿を特定します。
私のウェブサイトでは同一時刻に複数の投稿は存在しませんが、念のため、posts_per_page = 1 としておきます。

投稿が特定出来たら、新URLを組み立てて、PHPのheader関数で301リダイレクトを行います。

header関数はHTMLコードが出力されていない状態でないと機能しませんので、アクションフックはなるべく早いタイミングのものを使用します。
テーマで最初に使えるアクションフックは after_setup_theme ですので、これを使用します。

functions.phpを保存して、旧URLにアクセスしてみます。
ブラウザのアドレスバーが新URLに変わって記事が表示されたらOKです。
(そのまえに、パーマリンク設定を /%post_id% にしておくのを忘れないでください)

must-useプラグイン化してみよう

上記のコードで動作するようになりましたが、この動作はテーマに依存してはいけませんから、テーマ内のfunctions.phpに書くものではありません。
このような動作はプラグイン化する必要があります。
また、なるべく早いタイミングで動作させたいので、must-useプラグイン化してみます。

まずはプラグイン化

funcitons.phpに書いたコードをプラグインにするのは非常に簡単です。
新規ファイルを作成し、最初にプラグインとしての必須事項をコメントに書いたあとで、上記のコードを記述します。
(最低限、Plugin Name さえ記述されていればプラグインとして動作します)

<?php
/*-------------------------------------------
Plugin Name: Redirect New Permalink
Plugin URI: https://www.cherrypieweb.com/
Description: if access by old permalink then redirect new permalink.
Version: 1.0
Author: Masahiko Kawai
Author URI: https://www.cherrypieweb.com/
License: GPL2

old permalink: $post_date (/category/yyyymmddhhmmss.php)
new permalink: $post_id
-------------------------------------------*/
add_action( 'after_setup_theme', 'redirect_new_permalink' );
function redirect_new_permalink() {
・・・

/wp-content/pluginsディレクトリにサブディレクトリredirect-new-permalinkを作成し、そこに上記のファイルを、redirect-new-permalink.phpという名前で保存します。(名前はプラグインに使用できる形であれば任意で結構です)
正しくつくられていれば、WordPressの管理画面で「プラグイン > インストール済プラグイン」メニューを開くと、Redirect New Permalink というプラグインが表示されます。
初期状態では無効になっていますので、有効化すれば動作します。

must-useプラグインとは?

must-useプラグインは、mu-pluginsディレクトリに配置された特別なプラグインです。
配置されるとすぐに有効化され、管理画面から無効にできません。
また、通常のプラグインよりも先にロードされます。
今回の動作は何よりも先に動作してほしいので、must-useプラグインにしたいと思います。

ますは、先ほど有効化したRedirect New Permalinkプラグインを無効にします。

次に、プラグインフォルダ内のredirect-new-permalink.phpを、/wp-content/mu-pluginsディレクトリにコピーします。
mu-pluginsディレクトリが存在していない場合は作成しましょう。
サブフォルダを作らずにphpファイルだけをコピーします。

WordPressの管理画面で「プラグイン > インストール済プラグイン」メニューを開くと、上部に「必須(1)」というタブが表示されます。
ここをクリックすると、Redirect New Permalinkプラグインが有効化されているのがわかります。

必須プラグインにすると、アクションフックをmuplugins_loadedに変更できます。
このアクションフックは最も早いタイミングで実行されますので、変更しておきます。

add_action( 'muplugins_loaded', 'redirect_new_permalink' );

must-useプラグイン化したものが動作することが確認出来たら、無効にしたRedirect New Permalinkプラグインは削除しておきましょう。

WordPressで商売することの怖さと情報収集の重要性

今回のパーマリンクに対するWordPressのコア部分の修正は、脆弱性につながるものであるためマイナーアップデートで行われましたが、特に重要なものだと認識されていないのか大きくアナウンスされていないようです。

しかし、私のように日付(時刻)ベースでパーマリンクを設定していた人は少なからずおられたようで、WordPress.orgのサポートフォーラムにいち早く投稿があり的確な返信が寄せられていました。
その情報があったために、私もすぐに対応することができました。

ただ、修正理由が正当であるとはいえ自動アップデートでこのようなクリティカルな仕様変更をされてしまうと、もし請負案件でこの不具合が発生していたらクライアントとの契約次第では損害が発生しかねないと思います。

久しぶりにWordPressで商売することの怖さを味わった出来事でした。

コメントを残す