WordPress の自動アップグレードをしようとすると「このブログのプラグインを更新するための十分な権限がありません。」となってしまうので調査をしていたのですが、原因が判明しましたので記事にします。
まず、調査対象のバージョンは2.8.4です。2.6系から直接アップグレードしたもので、一番初めは2.0ME系でした。接頭辞は wp
です。
権限がないといわれてしまう原因ですが、そのまま権限が与えられていないためです。管理者で作業しているので普通はそんなことはありえないのですが、どうやらアップグレード時に行われるデータベースの更新に問題があったようです。
権限情報の記録場所
権限の情報はオプションとしてデータベース上に記録されていて、次のSQLで見つけることができます。
SELECT `option_name`,`option_value` FROM `wp_options` WHERE `option_name` = 'wp_user_roles';
このオプションの値はPHPでシリアライズしたもので、管理者や編集者といったロール(役割)ごとに権限を配列の形で格納しています。wp-includes/capabilities.php
の冒頭には、次のように書かれています。
* The role option is simple, the structure is organized by role name that store
* the name in value of the 'name' key. The capabilities are stored as an array
* in the value of the 'capability' key.
*
* <code>
* array (
* 'rolename' => array (
* 'name' => 'rolename',
* 'capabilities' => array()
* )
* )
* </code>
権限の記録は20行目の配列で、権限名がキーでその権限があるかどうかを示す真偽値が値となります。
現状の確認
さて、ここまでを踏まえて実際にどのように権限情報が記録されているかを確認します。PHPで表示させるプログラムを書いてみてもいいのですが、面倒なのでもっと手っ取り早い方法を使いました。
前述したSQLでオプション値を取得し、それをテキストエディタ(私は秀丸エディタを使いました)に貼り付けます。このままではすべてが1行に繋がってわかりにくいので、{ と } と ; の後ろにテキスト置換で改行を入れました。
こうすると、権限名と真偽値が交互になってわかりやすくなります。
準備ができたら権限名を探します。自動アップグレードのアドレスから、きっかけとなるコードは wp-admin/update-core.php
であることがわかりますので確認します。
冒頭の12行目に次のようにあります。
if ( ! current_user_can('update_plugins') )
wp_die(__('You do not have sufficient permissions to update plugins for this blog.'));
update_plugins
という名前の権限を確認して、権限がなければメッセージを表示して終了するという処理ですね。早速この権限名を先ほど準備したテキストデータで探しますと、見当たりません。この名前をキーとする配列要素がないということになりますので、PHPでは真偽値は false となります。
念のため新規に同じバージョンのインストールをしてみた場合で確認したところ、しっかりとこの権限については true となっていました。
なぜこうなったのか
こうなると原因が気になります。アップグレードスクリプトの権限処理をするところを探してみました。アップグレードインストールをするときのファイル名を基点にしてたどってみたところ、wp-admin/includes/upgrade.php に定義されている upgrade_all()
という関数の中で、WordPressデータベースのバージョンによってアップグレード用の関数を呼び出していることがわかりました。
/**
* Functions to be called in install and upgrade scripts.
*
* {@internal Missing Long Description}}
*
* @since unknown
*/
function upgrade_all() {
global $wp_current_db_version, $wp_db_version, $wp_rewrite;
$wp_current_db_version = __get_option('db_version');
// We are up-to-date. Nothing to do.
if ( $wp_db_version == $wp_current_db_version )
return;
// If the version is not set in the DB, try to guess the version.
if ( empty($wp_current_db_version) ) {
$wp_current_db_version = 0;
// If the template option exists, we have 1.5.
$template = __get_option('template');
if ( !empty($template) )
$wp_current_db_version = 2541;
}
if ( $wp_current_db_version < 6039 )
upgrade_230_options_table();
populate_options();
if ( $wp_current_db_version < 2541 ) {
upgrade_100();
upgrade_101();
upgrade_110();
upgrade_130();
}
if ( $wp_current_db_version < 3308 )
upgrade_160();
if ( $wp_current_db_version < 4772 )
upgrade_210();
if ( $wp_current_db_version < 4351 )
upgrade_old_slugs();
if ( $wp_current_db_version < 5539 )
upgrade_230();
if ( $wp_current_db_version < 6124 )
upgrade_230_old_tables();
if ( $wp_current_db_version < 7499 )
upgrade_250();
if ( $wp_current_db_version < 7796 )
upgrade_251();
if ( $wp_current_db_version < 7935 )
upgrade_252();
if ( $wp_current_db_version < 8201 )
upgrade_260();
if ( $wp_current_db_version < 8989 )
upgrade_270();
if ( $wp_current_db_version < 10360 )
upgrade_280();
maybe_disable_automattic_widgets();
update_option( 'db_version', $wp_db_version );
update_option( 'db_upgraded', true );
}
呼び出されている upgrade_***()
という関数の中では、さらに必要に応じて populate_roles_***()
という関数を呼び出しています。
この関数の定義は wp-admin/includes/schema.php
にあり、そこではまさにロールごとに権限の追加を行っていました。
次のSQLでデータベースバージョンを確認して呼び出されるはずの関数の処理を確認してみました。
SELECT `option_name`,`option_value` FROM `wp_options` WHERE `option_name` = 'db_version';
2.6の新規インストール環境では8201となっていましたので、upgrade_all()
の342~346行目が該当します。呼び出される関数の定義は次のとおり。
/**
* Execute changes made in WordPress 2.7.
*
* @since 2.7.0
*/
function upgrade_270() {
global $wpdb, $wp_current_db_version;
if ( $wp_current_db_version < 8980 )
populate_roles_270();
// Update post_date for unpublished posts with empty timestamp
if ( $wp_current_db_version < 8921 )
$wpdb->query( "UPDATE $wpdb->posts SET post_date = post_modified WHERE post_date = '0000-00-00 00:00:00'" );
}
/**
* Execute changes made in WordPress 2.8.
*
* @since 2.8.0
*/
function upgrade_280() {
global $wp_current_db_version;
if ( $wp_current_db_version < 10360 )
populate_roles_280();
}
/**
* Create and modify WordPress roles for WordPress 2.7.
*
* @since 2.7.0
*/
function populate_roles_270() {
$role =& get_role( 'administrator' );
if ( !empty( $role ) ) {
$role->add_cap( 'install_plugins' );
$role->add_cap( 'update_themes' );
}
}
/**
* Create and modify WordPress roles for WordPress 2.8.
*
* @since 2.8.0
*/
function populate_roles_280() {
$role =& get_role( 'administrator' );
if ( !empty( $role ) ) {
$role->add_cap( 'install_themes' );
}
}
populate_roles_***()
で追加している権限を確認したところ、3つとも登録されていました。今度はさかのぼって確認してみると、populate_roles_260()
で追加される2つの権限のみがありませんでした。しかもその1つは今回問題となっている update_plugins です。
どうやらここで失敗していたようです。
/**
* Create and modify WordPress roles for WordPress 2.6.
*
* @since 2.6.0
*/
function populate_roles_260() {
$role =& get_role( 'administrator' );
if ( !empty( $role ) ) {
$role->add_cap( 'update_plugins' );
$role->add_cap( 'delete_plugins' );
}
}
修復方法
原因まで判明したので、権限情報の修復を行います。直接書き換えてもいいのですが、間違えると面倒なので簡単なプログラムを作りました。
データを抽出して権限を追加し、再度シリアライズして表示するだけ。表示されたテキストデータをデータベース用の管理画面(ここのサーバーは phpMyAdmin が使えるのでそれを使いました)から直接反映させました。
このプログラムは自由に使っていただいてかまいませんが、何かあったときの補償はできませんので自己責任でお願いします。
<?php
header('Content-type: text/plain;charset=UTF-8');
$link = mysql_connect('データベースサーバー', 'ユーザー', 'パスワード');
if ( $link )
{
mysql_set_charset('utf8');
if ( mysql_select_db('データベース') )
{
$sql = "SELECT `option_name`,`option_value` FROM `wp_options` WHERE `option_name` = 'wp_user_roles'";
if ( $res = mysql_query($sql) )
{
if ( $rows = mysql_fetch_assoc($res) )
{
$rol_data = unserialize($rows['option_value']);
$rol_data['administrator']['capabilities']['update_plugins'] = true;
$rol_data['administrator']['capabilities']['delete_plugins'] = true;
print serialize($rol_data);
}
}
else
{
print mysql_error();
}
}
else
{
print mysql_error();
}
}
else
{
print mysql_error();
}
?>
というわけで、無事自動アップグレードの画面を拝むことができました。