wordpressでメンテナンスモード503プラグインを作成

wordpressは、テーマやプラグイン、本体の更新中にメンテナンスモードになります。このメンテナンスモードですが、通常はHTTPステータスコードの一つであるHTTP503のことを指します。「Service Unavailable」とも表記され、一時的にサーバへアクセスできず、サービスが利用できないという状態のことです。また、ステータスコードとはwebサーバからの応答のことです。いわばページの要求に対する返事のようなものです。

HTTP503の原因は?

大別すると、サーバへ大きな負荷がかかった時とメンテナンス時に分けられます。負荷によるものはアクセスの集中が原因で、負荷が軽減されると解消されます。メンテナンス時によるものはサーバ管理者が意図的に503を返すように設定しているのが原因です。503の5は、サーバ内部のエラーを意味しているため、500番台は全てサーバ内部のエラーとなります。いわゆる500エラーは一時的な負荷によるものではなく、例えばプログラムそのものに間違いがあって起こることが多いです。

wordpressでメンテナンス503プラグインを作成

wordpress_Management

このクラスのコンストラクタでは、ワードプレス管理メニューを表示するための権限者を判定し、「ワードプレス_ManagementScreen」と命名したコールバック関数を呼び出しています。これによってメンテナンスのメニューを追加しています。Maintenanceメソッドではメニューをクリックしたら表示されるコンテンツを記述しています。

Maintenance

このクラスのコンストラクタでは、メンテナンスのプラグインが有効化された時とすでに有効化されている時、削除された時の処理をそれぞれ定義しています。

ワードプレス_Activationメソッドでは、メンテナンスがはじめて有効化された時にデータベースにおいてテーブルを作成しています。

ワードプレス_AlreadyActivatedでは、既にメンテナンスのテーブルが存在する場合の処理をしています。このメソッドではレコードを一行しか使っていませんが、これはメンテナンスのプラグイン自体が単純な情報しか保存しないためです。複雑なものになると、例えば各ページへアクセスされるたびに、それに対応するレコードを追加していく、といった形などで利用します。

ワードプレス_dbslugは、ワードプレス_Managementクラスへユニークキーを渡しているのですが、メンテナンスのプラグインではレコードを一行しか使用しないため、本来はもっと簡単なコードでも実現できます。ただ、レコードを沢山使うケースのほうが多いため、あえてこのような書き方にしています。

ワープレ_MaintenanceModeはこのメンテナンスのプログラムの中心となる部分です。管理画面から503エラーのオンとオフを切り替えるためのフォームを記述しています。最後にexitとしているのは、ワープレの各投稿記事へのアクセスがあった場合、本来の記事を表示させず、用意した文字列を表示させた後、強制終了させるためです。

ワープレ_plugin_table_deleteは、メンテナンスのプラグインが削除されたら同時にデータベース内のメンテナンスのテーブルとオプション設定も削除するように処理しています。

wordpress_StatusCode

このクラスはもともとステータスコードを取得するために作りました。ついでに遊び心でURLからホスト名やIPアドレスも取得できるようにしています。本来は自身のワープレでのステータスコードを取得するものでしたが、どうせなら他のURLでも使えるようにしたほうが便利かな、と思いまして。複数のドメインを管理していて、何らかの理由でステータスコードを調べたい時には便利かも知れません。

CurlLoadは内外のサイトへアクセスしてデータを取得するためのメソッドです。ウェブページのデータを取得するだけならfile_get_contents関数もありますが、cURL関数のほうが色々な処理ができます。

<?php
/*
Plugin Name: AA503
*/
class wordpress_Management {
function __construct() {
add_action('network_admin_menu',[$this, 'wordpress_ManagementScreen']);
add_action('admin_menu', [$this, 'wordpress_ManagementScreen']);
}
public function wordpress_ManagementScreen() {
add_menu_page('AA503','AA503', 'level_8', __FILE__, [$this, 'Maintenance'], '', 101);
}
public function Maintenance() {
global $wpdb,$slug;
// メンテナンス管理メニューをクリックした際に表示されるコンテンツ
$nt2=$nt=preg_replace('/\/$/', '', network_home_url('','https'));
$nt=$nt.$_SERVER['REQUEST_URI'];
$MaintenanceOBJ =new Maintenance;
$slug = $MaintenanceOBJ->wordpress_dbslug();
$this->TableName = $wpdb->prefix ."Maintenance";
$row_record = $wpdb->get_row("SELECT name,contents FROM $this->TableName WHERE name = $slug");
$contents = $row_record->contents;
$checked1=$checked2="";
if($contents=="on"){$checked1='checked="checked"';}elseif($contents=="off"){$checked2='checked="checked"';}
if(empty($_POST['url'])){
$_POST['url']=$nt2;
}else{
if(!preg_match('/^[0-9a-zA-Z.\/:\-_]+$/mu', $_POST['url'])){$_POST['url']="";}
}
$esc="htmlspecialchars";
echo <<< EOM
<div style="text-align:center;margin-top:100px;">
<form action="{$nt}" method="post">
<div style="margin-bottom:30px;">
<label><input type="radio" name="check" value="on" {$checked1} />503 ON</label>
<label><input type="radio" name="check" value="off" {$checked2} />503 OFF</label>
</div>
<input type="submit" value="メンテナンス切替" />
</form>
<div style="padding-top:50px;">ステータスコード</div>
<form action="{$nt}" method="post">URL<input type="text" size="40" name="url" value="{$esc($_POST['url'])}" /> <input type="submit" value="確認" /></form>
</div>
EOM;
echo '<div style="text-align:center;margin: 0 auto;padding-top:20px;">';
$statusOBJ = new wordpress_StatusCode;
echo '</div>';
}
}
new wordpress_Management;
new Maintenance;
class Maintenance{
function __construct(){
global $wpdb,$slug;
$this->TableName = $wpdb->prefix ."Maintenance";
register_uninstall_hook (__FILE__,['Maintenance','wordpress_plugin_table_delete']);
if($wpdb->get_var("SHOW TABLES LIKE '$this->TableName'") !== $this->TableName) {
register_activation_hook (__FILE__,[$this,'wordpress_Activation']);
}else{
$slug=1;
$this->wordpress_AlreadyActivated();
}
update_option( 'AA503', '1.0' );
}
public function wordpress_Activation(){
global $wpdb;
$sql = "CREATE TABLE " . $this->TableName . " (
name int NOT NULL,
contents text NOT NULL,
UNIQUE (name)
);";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
$wpdb->insert( $this->TableName,['name' => '1', 'contents' => 'off',]);
}
public function wordpress_AlreadyActivated() {
global $wpdb,$slug;
$this->TableName = $wpdb->prefix ."Maintenance";
$id_slug = $wpdb->get_var($wpdb->prepare("SELECT name FROM ". $this->TableName ." WHERE name = %s", $slug));
$row_record = $wpdb->get_row("SELECT name,contents FROM $this->TableName WHERE name = $slug");
$contents = $row_record->contents;
if(!empty($_POST["check"])=="on" || !empty($_POST["check"])=="off"){$contents=$_POST["check"];}
if($contents=="on"){$this->wordpress_MaintenanceMode();}
if ($id_slug==1) {
$wpdb->update( $this->TableName,['contents' => $contents,],['name' => $slug]);
} else {
$sql = "CREATE TABLE " . $this->TableName . " (
// PRIMARY KEYの前に半角スペースを二つ入れる
name int PRIMARY KEY NOT NULL,
contents text NOT NULL
);";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
$wpdb->insert( $this->TableName,['name' => '1', 'contents' => 'off',]);
}
$this->wordpress_dbslug();
}
public function wordpress_dbslug(){
global $slug;
return $slug;
}
public function wordpress_MaintenanceMode() {
add_action('get_header', function (){
if(!is_user_logged_in()){
header(' ', true, 503);
echo <<<EOT
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>wordpressメンテナンス</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head>
<body style="text-align:center;">
<div style="padding-top:50px;">
<h1 style="font-size:28px;">wordpressメンテナンス中</h1>
<h2><p style="font-size:21px;line-height:3em;">ご迷惑をおかけしています。<br />現在、メンテナンス中です。</p></h2>
</div>
</body>
</html>
EOT;
exit;
}
});
}
public function wordpress_plugin_table_delete(){
global $wpdb;
delete_option('AA503');
$sql = "DROP TABLE ".$wpdb->prefix ."Maintenance";
$wpdb->query($sql);
}
}
class wordpress_StatusCode {
public function __construct() {
if(preg_match('/^[0-9a-zA-Z.\/:\-_]+$/mu', $_POST["url"])){
$scode=$this->CurlLoad($_POST["url"]);
$_POST["url"]=$result="";
if(preg_match("/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/",$scode["primary_ip"])){
$result.='<tr><td>ホスト名</td><td>'.gethostbyaddr($scode["primary_ip"]).'</td></tr>';
$result.= '<tr><td>IPアドレス</td><td>'.$scode["primary_ip"].'</td></tr>';
$result.='<tr><td>ステータスコード </td><td>'.$scode["http_code"].'</td></tr>';
echo '<table style="text-align:left;margin: 0 auto;">'.$result.'</table>';
}
}
}
public function CurlLoad($url) {
$code= null;
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true,CURLOPT_USERAGENT => "spider",]);
curl_exec($ch);
$code= curl_getinfo($ch);
curl_close($ch);
return $code;
}
}
?>

まとめ

ワープレでメンテナンス表示することはあまりないように思いますが、コードをちょろっと弄る時なんかは、たまに503のステータスコードを表示させたい時があります。ただ、コードをいじってワープレがエラーを吐くと、メンテナンスのプラグイン自体も機能しなくなるリスクが高まります。従って、メンテナンスを利用できる場面といえば、cssやjavascript、記事などの変更や修正時でしょうか。ちなみにgethostbyaddr関数は、IPV6に対応していないようなので、IPV4用に処理しています。