こんにちはjunです。会社でとてつもない量のCMSのデータをwordpressに移行する計画がありました。色々と課題がある中なんとかwordpressにデータをマイグレーションして再構築できたので共有したいと思います。
データが4万件もあるのでプログラム的にwordpressを操作する必要がありました。日本語で検索してもなかなかヒットしなかったので、少し苦労。 しかしCLIがわかって、使う関数を把握すれば意外と簡単です。
引越しの背景から説明するので、「ささっと移行手順を見せろや!」という人は「wordpress側の構築概要」から見てください。
2006年に構築されたサイトを弊社が受け持っており、その環境が古くなってきたので移行することになりました。PHP5、centos6というレガシーな環境で動いており非常に危なっかしい上に、ろくに保守もされてないのでページングとか表示もおかしい部分も出てきました。
移行するサイトはwordpressではない 別のCMSで構築されており、移行の際にはDBからデータを一回ダンプして加工してwordpressにマイグレーションをする必要がありました。 しかしそのデータは
というデータが膨大であり必然的にプログラム的にデータを移行する必要があります。とりあえず担当の方と移行するデータを精査しました。元々はコミュニティサイトとして使用していたのでユーザーが非常に多く、移行すべきアクティブなユーザーは100人程度だったのでユーザーはかなり減りました。しかし投稿は全部移行でした(泣
旧CMSでの「カテゴリー」はブログの種類に当てはまりました。ブログを管理、投稿する部署が異なっていることが判明し、投稿データにも categoruid
の様に区別するカラムがありました。さらに言えばユーザー情報にも紐づいています笑。
投稿データは旧CMSで結び付けられたユーザーID、カテゴリーIDの関係性を維持しながら移行する必要があります。 さらに投稿データには
[img]http://~~~~~[/img]
という様なそのCMS独自のタグが存在したので、wordpressに移行する前に正規表現で置換する必要がありました。(今回はその解説はしません。別途の記事で)
まとめると
という課題がありました。
今回の移行手順としては前準備に
この様にデータの加工をしてwordpressに入れ込む準備をしました。加工済みデータJSONとwordpressの関数を用いてこれらのデータをwordpressに移行しました。
移行の特に厄介だったのが旧CMSでは部署ごとにカテゴリーという名前でブログ種が分けられていたことです。wordpressのカテゴリーとは別の概念です。さらに管理ユーザーもそのカテゴリーで区別されていました。
そこで今回は wordpressをマルチサイト構成にして構築しました。 wordpressには一つのwordpressシステムを用いて複数の異なるブログを構築する機能があります。詳しくはこちらの公式を参照。マルチサイトにすることで複数のブログに分け、さらにそのブログごとにユーザーの割当が可能になります。
手順としては以下の通りです。
まずはローカルでの検証環境を作りましょう。失敗するとDBが結構汚れるのですぐにリセットできるdockerを用います。wordpress公式のdockerHubの通りにすれば簡単に構築できます。ディレクトリ構成は以下の通りです。
docker-wordpress/
|
|-scrips/
|-docker-compose.yml
scripts/ にはwordpressに挿入するためのPHPスクリプトを入れておくためのディレクトリです。最終的にこのwordpress dockerコンテナの中に入って、このスクリプトをコマンドで実行します。
docker-compose.yml は以下の通りです。(ほとんど公式と同じ。一部改修
version: '3.1'
services:
wordpress:
image: wordpress
restart: always
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
WORDPRESS_DB_PASSWORD: examplepass
WORDPRESS_DB_NAME: exampledb
volumes:
- ./scripts:/var/www/html/scripts
db:
image: mysql:5.7
restart: always
environment:
MYSQL_DATABASE: exampledb
MYSQL_USER: exampleuser
MYSQL_PASSWORD: examplepass
MYSQL_RANDOM_ROOT_PASSWORD: '1'
volumes:
- db_wp:/var/lib/mysql
volumes:
./scripts:
db_wp:
DBは永続化して、そしてスクリプトもローカルで編集してすぐに実行できる様にボリュームにマウントしておきます。これで準備完了です。
jun@MacBook-Pro docker-wordpress % docker-compose up -d
ブラウザを開いてlocalhost:8080にアクセスするとwordpressのインストール画面が開きます。DBの設定などは済んでいるので、初期ユーザーの設定だけで終わります。
それではまず旧CMSのカテゴリーにあたる、マルチサイトを機械的に作成しましょう。その前にwp-config.php
でやることがあります。以下の様なコードを追記してマルチサイト化を有効にします。
define('WP_ALLOW_MULTISITE', true);
有効にすると「ツール」から「サイトネットワークの設置」というメニューが出現します。これをクリックしてサイトネットワークの設定を行います。そして新しくコードを追記しろと言われるので以下の様にwp-config.php
と .htaccess
に追記します。
define('MULTISITE', true);
define('SUBDOMAIN_INSTALL', false);
define('DOMAIN_CURRENT_SITE', 'localhost');
define('PATH_CURRENT_SITE', '/');
define('SITE_ID_CURRENT_SITE', 1);
define('BLOG_ID_CURRENT_SITE', 1);
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
# add a trailing slash to /wp-admin
RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]
RewriteRule . index.php [L]
なお、今回はドメイン別ではなくサブディレクトリ形式のマルチサイト とします。
移行元のデータはすでにJSONにしてあります。以下の様な構成とします。
[
...
{
"id":"6",
"name":"サイトの名前",
"sub_title":"サイトのキャッチフレーズ的なもの",
"mailUser":"3::1543"
},
...
]
旧CMSからはこの様になっており、mailUserがこのカテゴリー(ブログ)を管理するユーザーです。wordpressのマルチサイトを作るには、初期管理ユーザーとサイト名があればとりあえず作れます。
そして追加スクリプトは以下の通りです。
<?php
require_once('../../wp-load.php');
$json = file_get_contents('/var/www/html/scripts/category/article_categpry.json');
$cats = json_decode($json,true);
$new_id = 2;
foreach($cats as $key=>&$val){
wpmu_create_blog('localhost','blog'.$key,$val['name'],1,array('blogdescription'=>$val['sub_title']));
if( is_wp_error( $return ) ) {
print_r($return->get_error_message()."\n");
continue;
}
$val['new_id']=$new_id;
$new_id++;
}
file_put_contents("/var/www/html/scripts/category/registered_article_categpry.json", json_encode($cats,JSON_UNESCAPED_UNICODE));
wpmu_create_blog
という関数を用いて作成します。wpmu_create_blog
を使用するためには上記のコード二行目にある require_once('../../wp-load.php');
が必要になります。 wp-load.php
を使用することでwordpress関数が使用できる様になります。引数は以下の様に取ります。
wpmu_create_blog(
ブログのドメイン(必須),
ブログのサブディレクトリ名(必須),
サイト名(必須),
管理ユーザーID(必須),
そのほかの情報(配列),
)
上記スクリプトは非常に単純です。JSONにある旧CMSに登録されたブログカテゴリー分だけforeachで回して関数を実行しているだけです。
しかしブログカテゴリーは後でユーザーと投稿を挿入する際に必要となるので、 「wordpressでのブログIDと以前のブログカテゴリーIDを対応させる」様にしておきます。下記の様に工夫をしておきます。
$new_id = 2; // 新しいブログIDの最初の値
foreach($cats as $key=>&$val){ //参照渡しをしておく
wpmu_create_blog('localhost','blog'.$key,$val['name'],1,array('blogdescription'=>$val['sub_title']));
if( is_wp_error( $return ) ) {
print_r($return->get_error_message()."\n");
continue;
}
// エラーが起きなかったら new_id という新しいカラムと共にwordpressのブログIDを記録
$val['new_id']=$new_id;
$new_id++;
}
// wordpressと旧CMSとの関係性を記録したJSONを出力
file_put_contents("/var/www/html/scripts/category/registered_article_categpry.json", json_encode($cats,JSON_UNESCAPED_UNICODE));
すると新しく作成されたJSONを見ると
[
...
{
"id":"6",
"name":"サイトの名前",
"sub_title":"サイトのキャッチフレーズ的なもの",
"mailUser":"3::1543",
"new_id":3
}
...
]
new_idというカラムとwordpressでのブログIDが入りました。こうすることで 「旧CMSでのブログカテゴリーID 6のものはwordpressではブログID 3」という関係性を保存できます。
以上でブログカテゴリーの移行は終了しました。訳70サイトもあるのでこんな感じ↓になります笑。
これらスクリプトはコマンドで実行します。dockerで管理しているので
docker exec -it {wordpressのコンテナ名} /bin/bash
この様にしてwordpressを立ち上げているコンテナに入って、コマンドを実行しにいきます。
root@0756d76ddde1:/var/www/html#
コンテナに入るとこの様にターミナルが変化します。リモートサーバーにsshでログインしたみたいな感じです。そしてdockerを立ち上げるときに scripts/
ディレクトリをボリュームしているのでそこに移動します。
root@0756d76ddde1:/var/www/html# cd scripts
root@0756d76ddde1:/var/www/html/scripts# ls
create_site.php
article_category.json
root@0756d76ddde1:/var/www/html/scripts# php create_site.php
上記の様にphpファイルを指定することでスクリプトが実行されます。
ではそれぞれのブログを作成したので次はユーザーを作成していきます。ユーザーは以下の様なJSONです。
[
...
{
"uid":"3",
"loginname":"webmaster",
"name":"お名前",
"email":"example@example.com",
"user_avatar":
"thumbnail.jpg",
}
...
]
groupid
は旧CMSの権限グループです。そしてユーザー追加スクリプトは以下の様になります。
<?php
require_once('../../wp-load.php');
$json = file_get_contents('/var/www/html/scripts/user/user.json');
$uesrs = json_decode($json,true);
$new_id = 2;
foreach($uesrs as &$val){
$role;
switch($val['groupid']){
case "1":
$role = 'administrator';
break;
case "2":
$role = 'contributor';
break;
case "3":
$role = 'contributor';
break;
case "4":
$role = 'administrator';
break;
case "5":
$role = 'contributor';
break;
case "6":
$role = 'administrator';
break;
case "7":
$role = 'administrator';
break;
}
$user = [
'user_pass'=>'PASS_WORD',
'user_login'=>$val['loginname'],
'user_email'=>$val['email'],
'display_name'=>$val['uid'],
'role'=>$role,
];
$return = wp_insert_user($user);
if( is_wp_error( $return ) ) {
print_r($return->get_error_message().':'.$val['loginname']."\n");
continue;
}
$val['new_id']=$new_id;
$new_id++;
}
file_put_contents("/var/www/html/scripts/user/registered_user.json", json_encode($uesrs,JSON_UNESCAPED_UNICODE));
ちょっと自分のためのコードがありますが、重要なのは wp_insert_user
という関数です。引数は連想配列で入れます。以下のキー名で配列にします。
$user = [
'user_pass'=>'PASS_WORD', // パスワード名
'user_login'=>$val['loginname'],// ログイン名(英数字でないといけない)
'user_email'=>$val['email'], // 登録アドレス
'display_name'=>$val['uid'], // 表示名(プロフ名)
'role'=>$role, // 権限キー
];
権限キーは文字列で指定します。私のコードでは旧CMSのIDを対応させています。そしてブログの時の様に旧データと新データのIDを対応させる様にします。
...
$return = wp_insert_user($user);
if( is_wp_error( $return ) ) {
print_r($return->get_error_message().':'.$val['loginname']."\n");
continue;
}
$val['new_id']=$new_id;
$new_id++;
}
file_put_contents("/var/www/html/scripts/user/registered_user.json", json_encode($uesrs,JSON_UNESCAPED_UNICODE));
ちなみにスクリプトを実行するときは is_wp_error( $return )
でエラーをキャッチできる様にしましょう。 なぜか私のデータには同じユーザーのデータがあったりなどで、「ユーザーがすでに登録されています」というエラーでIDがずれてしまうという事件がありました。そのためにキャッチできるスクリプトを入れておきましょう。
ユーザーのスクリプトを実行するとユーザーが作成され、新旧のIDを対応させたユーザーJSONファイルができました。これとブログカテゴリーのデータを用いて各ブログを管理するユーザーを割り当てていきます。
<?php
require_once('../../wp-load.php');
// wordpress user IDが入ったuserのファイル
$user_json = file_get_contents('/var/www/html/scripts/user/registered_user.json');
$uesrs = json_decode($user_json,true);
// wordpress blog IDが入ったブログカテゴリーのファイル
$cat_json = file_get_contents('/var/www/html/scripts/category/registered_article_categpry.json');
$cats = json_decode($cat_json,true);
foreach($cats as $cat_key=> $cat_val){
$old_user_ids =explode('::',$cat_val['mailUser']);
foreach($old_user_ids as $old_id){
$user = array_values(array_filter($uesrs,function($ele) use($old_id) {
return $ele['uid'] == $old_id && isset($ele['new_id']);
}));
if(!empty($user)){
$new_userid = $user[0]['new_id'];
$new_cat_id =intval($cat_val['new_id']);
add_user_to_blog($new_cat_id,$new_userid,'administrator');
}
}
}
私のデータの場合、ユーザーが複数人いたのでforeach
の中でさらにforeach
しています。マルチサイト の特定のブログに対してユーザーを割り当てるためには add_user_to_blog
を用います。
第一引数にユーザーID、第二引数に対象のブログID、第三には権限グループを指定することで簡単にブログに対してユーザーを割り当てられます。
最後に投稿を流し込みます。私が使用したデータは以下の様なデータになっています。
[
...
{
"id":"162",
"date":"2007-08-02",
"category_id":"6",
"uid":"1543",
"title":"タイトル",
"content":"ここにブログの内容がプレーンテキスト形式で入っています。"
},
...
]
uid
を元にユーザー(著者)と結び付け、category_id
を元にどのブログに投稿するのかを指定します。以下の様なスクリプトを書きました。
<?php
ini_set('memory_limit', '1024M');
require_once('../../wp-load.php');
// wordpress user IDが入ったuserのファイル
$user_json = file_get_contents('/var/www/html/scripts/user/registered_user.json');
$uesrs = json_decode($user_json,true);
// 旧CMSの投稿データ
$article_json = file_get_contents('/var/www/html/scripts/articles/article_replace.json');
$articles = json_decode($article_json,true);
// wordpress blog IDが入ったブログカテゴリーのファイル
$cat_json = file_get_contents('/var/www/html/scripts/category/registered_article_categpry.json');
$cats = json_decode($cat_json,true);
foreach($articles as $key => $a_val){
$old_cat_id = $a_val['category_id'];
if(empty($old_cat_id)) continue;
$cat = array_values(array_filter($cats,function($ele) use($old_cat_id) {
return $ele['id'] == $old_cat_id && isset($ele['new_id']);
}));
if(empty($cat)) continue;
$new_cat_id = $cat[0]['new_id'];
$old_user_id = $a_val['uid'];
$user = array_values(array_filter($uesrs,function($ele) use($old_user_id) {
return $ele['uid'] == $old_user_id && isset($ele['new_id']);
}));
$new_user_id = (empty($user))?1:$user[0]['new_id'];
if(!get_user_by('id',intval($new_user_id))) continue;
switch_to_blog($new_cat_id);
$new_post = array(
'post_title' => $a_val['title'],
'post_content' => $a_val['content'],
'post_status' => 'publish',
'post_date' => date($a_val['date']),
'post_author' => $new_user_id,
'post_type' =>'post',
);
wp_insert_post($new_post);
restore_current_blog();
}
4万件分のデータとなると非常にメモリを食うので ini_set
でメモリ上限を上げてあります。
投稿データからブログカテゴリーIDとユーザーIDを新しいwordpresの方と紐づけ、 wp_insert_post
を用いて記事を挿入します。 wp_insert_post
の気をつける点はデフォルトではblogid=1
のブログに記事を作成するということです。
そのためコードに switch_to_blog();
を追加して挿入対象のブログを切り替えています。この関数を使用するときはセットで restore_current_blog();
を使います。
挿入先のブログを切り替えて、wp_insert_post
を用いて投稿を挿入します。wp_insert_post
は引数に連想配列を入れます。 対応するキー名が決まっているので間違えない様にしましょう。
そして同じ様にターミナルでこのPHPを実行すればwordpressに投稿データが入ります。ちなみに4万件は15分かかりました。投稿したデータはエディタで普通に編集できますが、クラシックモードでの編集となります。画像などもきちんとタグとパスが生きていればきちんとレンダーされます。
大量のデータを入れたのにミスってしまったらdockerをリセットしましょう。コンテナーを削除してDBのボリュームも削除します。
docker volume rm VOLUME_NAME
そしてまた docker-compose up -d
を行うことで最初からやり直しができます。
以上が旧CMSからwordpressにデータを挿入する方法です。wordpressは wp-load.php
を読み込めばほとんどの関数を使用でき、ターミナルからも実行できます。 スクリプト自体も100行未満で思いつける簡単なものです。
もしプログラム的にwordpressを操作したい場合は日本語だと上手く出てこないので「wordpress how to ~~~ programmatically」と調べるといいです。私が調べたものですと以下の感じです。
ぶっちゃけ旧CMSからデータを引っ張ってきたり、適切に加工したり、構造を把握する方が大変でした。機会があればこのデータ移行の時に一番大変だった、正規表現により独自タグの置換も記事にしたいと思います。