WordPressには記事を書いてから即時公開せず、特定の時間に記事を公開する予約投稿という機能があります。
が、それを設定しておいても何らかの原因で失敗することが多いようです。その対処法については、Google等で検索すると多数のサイトが発見できます。
ただ具体的・根本的な原因などの情報は非常に少ないように思いました。
というわけで、今回はその原因を究明するためWordPressのソースを読んでみました。
かなり開発者向けの記事だと思います。
WordPressのCRONとwp-cron.php
WordPressではCRONジョブ(毎時、毎日のように定期的にサーバーに何らかの動作を行わせる設定)を擬似的に行なう機能があります。
通常CRONジョブというとサーバーの設定で行います。
が、サーバーによってはそれが設定出来ない場合もあるので、WordPressがその機能を実装してくれているということですね。
とは言っても先程も記述した通り擬似的なものであり、通常のもののような「ある時間になったら自動的に実行する」というものではありません。「ジョブ設定時間を過ぎてからサイトにアクセスされた時に動作を実行する」という機能になります。
これを実現するのは「wp-cron.php」というファイルです。WordPressフォルダ直下にあります。
wp-cron.phpではどのようなことが行われているか?
では、wp-cron.phpの動作を見てみたいと思います。
まず、処理の流れとしては、以下の様な順序で行われています。
- 初期設定
- 設定されているCRONジョブの取得
- CRONジョブが別なCRONジョブによって既にロックされているかどうかのチェック
- CRONジョブの実際の処理
- CRONジョブのロックの解除
このように、流れとしては特に難しいものではありませんでした。
1 初期設定
初期設定部についてです。ところどころ省略しつつ、重要な部分を書いていきます。
冒頭には以下のコードがあり、管理画面での動作であるかどうかを判断しています。管理画面で記事が編集されたり、設定されたりした場合にはここで処理を止めています。
単純な画面遷移の場合はそのまま次の処理に流れていきます。
if ( !empty($_POST) || defined('DOING_AJAX') || defined('DOING_CRON') )
die();
以下のコードで、WordPressの設定を読み込んでいます。
if ( !defined('ABSPATH') ) {
/** Set up WordPress environment */
require_once('./wp-load.php');
}
更にその後には、別なCRONジョブによるロックをチェックする関数「_get_cron_lock()」が定義されています。ここで$_wp_using_ext_object_cacheというグローバル変数を使ったり、wp_cache_get関数でキャッシュからデータを取得しています。
function _get_cron_lock() {
global $_wp_using_ext_object_cache, $wpdb;
$value = 0;
if ( $_wp_using_ext_object_cache ) {
~
$value = wp_cache_get( 'doing_cron', 'transient', true );
} else {
~
予約投稿が失敗する事例の一つに、WP File Cacheなどのキャッシュ系プラグインを使っていることが原因となっていることがあるようですが、ここが原因になっている可能性があるかと思いました。具体的には現在は判りません。
2 設定されているCRONジョブの取得
設定されているCRONジョブの取得は以下のコードです。
if ( false === $crons = _get_cron_array() )
die();
「_get_cron_array()」という関数で取得し、$cronsに代入しています。この関数はwp-includesフォルダ内のcron.phpというファイルに記述されています。
この直下では、取得したCRONジョブの一番最初の設定時刻と現在時刻を比較し、まだ設定時刻になっていないなら処理を止める、という動作を行なっています。
3 CRONジョブが別なCRONジョブによって既にロックされているかどうかのチェック
この部分は少し難しい部分でした。
私としてはあまり耳慣れないものでしたが、「Transient API」というものを使用していました。(まだまだ修行不足・・・精進します。)
以前から存在はしていた機能のようですが、Google等で検索すると日本語サイトよりも海外サイトが最初に表示されますね。
「Transient API」というのは、簡単に言うとデータに対して名前と有効期限を設定し、データベースに保存するものです。
⇒ Transients API – WordPress Codex 日本語版
関数名を見ると大体の機能は想像つきますが、set_transient関数、get_transient関数、delete_transient関数などがあります。PHPのCookie関連関数に似ていますね。
ここでは細かい処理は割愛しますが、「get_transient( ‘doing_cron’);」「set_transient( ‘doing_cron’, $doing_wp_cron );」というような形で、doing_cronというトランジェントをキーにして、ロックのチェックを行なっています。更にロック処理にもなっています。
4 CRONジョブの実際の処理
上記のロックがされていなければ、実際のCRON処理に入ります。
CRONジョブのデータをvar_dump()で出力すると、以下の様になります。
[1373966431]=>
array(1) {
["publish_future_post"]=>
array(1) {
["0f83a1c581ea80c42222e04cd7b205a2"]=>
array(2) {
["schedule"]=>
bool(false)
["args"]=>
array(1) {
[0]=>
int(31)
}
}
}
}
処理時間が上位のキーになり、その中に配列として予約投稿を表す「publish_future_post」が含まれています。
「publish_future_post」の他には、「wp_version_check」「wp_scheduled_auto_draft_delete」など、バージョンチェック系の処理や下書き自動削除などの処理も存在しています。
「schedule」は、「wp_version_check」では「twicedaily」など、定期的な動作の場合に設定されています。予約投稿ではfalseになっています。
「args」の中の「int(31)」は、その投稿のIDです。
5 CRONジョブのロックの解除
最後には以下の様なコードで簡単にロックを解除しています。
if ( _get_cron_lock() == $doing_wp_cron )
delete_transient( 'doing_cron' );
ここで、先程の「set_transient( ‘doing_cron’, $doing_wp_cron );」に続いて「delete_transient( ‘doing_cron’ );」が出てきていますね。
さいごに
というわけで、wp-cron.phpを読んでみましたが、ここだけでは具体的に予約投稿が失敗する原因を突き止めることが出来ていません。
キャッシュ関連は「_get_cron_lock()」の部分なのではないかと推測していますが、明確には判りません。
というわけで、次回はwp-includes/cron.phpを読んでみたいと思います。
ピンバック: WordPressの予約投稿の仕組みを簡単に解説 / 予約投稿が失敗することもあるのがWordPress | stryhの日記 ~stryh/changelog