ページのエクスポートに対するLUXCMの考え方
ページ選択によるエクスポート — カテゴリーのないページを扱う方法
私は、「ページ選択」モードを「投稿選択」モードの単なるコピーにはしなかった。
見た目はどちらもチェックボックスで項目を選ぶ画面だが、ページと投稿では、データの構造が根本的に異なる。
投稿は「カテゴリー」という多対多の分類体系でまとめられる。一方、ページにはそもそもカテゴリーという概念がない。
その代わり、ページは親子関係、つまり階層構造によって整理される。この違いを無視して投稿と同じ方法でページを移行すると、移行後のページがばらばらになり、元のサイト構造が失われてしまう。

ページでは「親」がそのままサイト構造になる
WordPressでは、投稿は「カテゴリー」という独立した分類体系で管理されるが、ページではこのタクソノミーを使わない。
代わりに、各ページはpost_parentというフィールドで別のページを親として指定できる。こうして作られる親子関係が、そのサイトの「情報構造」そのものになる。
UICのページ階層に関する解説では、親ページの設定がなぜ重要なのかが明確に説明されている。親ページを指定すると「パンくずナビゲーションが自動的に生成される」だけでなく、編集者にとっても「バックエンド上でのコンテンツ整理」が可能になるという。
ここで私は、「ページ選択」を「カテゴリー別の投稿選択」と同じロジックでは作れないことを確認した。
この関係まで移行できなければ、移行後のページは一つひとつを見る限り問題がないように見えても、サイト全体では行き場を失った断片の集まりになってしまう。
コメントで得た教訓をページにもそのまま適用した
私はこのシリーズの第1回で、WordPressコアのTracチケット #8019を取り上げたことがある。
コメントをインポートする際、WordPressはIDの衝突を避けるために新しいIDを割り当てる。しかし、「このコメントは誰への返信なのか」を示す親IDを更新しなかったため、返信の構造が壊れてしまうという問題だ。
ページの親子関係にも、まったく同じ落とし穴があることは分かっていた。元のサイトで「会社概要」ページのIDが12で、その下の「沿革」ページがpost_parent = 12として保存されていたとしても、移行先で「会社概要」ページが新たに作成されれば、そのIDは12ではなく、まったく別の番号になる。
「沿革」ページが古いIDである12を親として持ち続けていれば、その関係は移行先サイトのどのページも指し示せない、無効な参照になってしまう。
そこで、ページのインポート処理を2段階に分けた。
第1段階では、選択されたページをすべていったん作成し、それぞれのページが持っていた「旧親ID」を記録しておく。
この時点では、まだ親ページを紐づけない。親ページ自体が移行先でどのIDになるのか、まだ分からないからだ。
すべてのページを作成し終えたら、第2段階で「旧ID → 新ID」の対応表を参照する。各ページの旧親IDをこの表から探し出し、新しいIDに置き換えてから、親子関係を再接続する。
これは、Trac #8019でコメントに適用された解決策とまったく同じ原理だ。関係を移行するには、参照先のIDが変わることを前提に対応表を作り、後からその表を使って関係をつなぎ直さなければならない。
正直に伝えておくべき一つの限界
ここで、隠さずに伝えておくべきことが一つある。
この対応付けが機能するのは、「親ページも同じインポート処理に含まれている場合」に限られる。
たとえば、ユーザーが「沿革」ページだけを選択し、親である「会社概要」ページを選択しなかった場合、対応表には「会社概要」の旧IDに対応する新IDが存在しない。
この場合、「沿革」ページは親との紐づけが復元されないまま、階層の最上位、つまり親なしのページとして作成される。
これはバグではなく、論理的に当然の結果だ。存在しない親に接続する方法はないからである。私はこれをエラーとして扱い、インポート全体を停止するのではなく、静かに最上位ページとして作成する仕様にした。
メニューはページとは別の構造であることも、明確にしておく必要がある
もう一つ、押さえておきたい点がある。
WordPress.orgのサポートフォーラムにあるあるスレッドは、この違いを正確に示している。
このユーザーは、「会社概要」の下に「私たちの学校」や「カリキュラム」といった子ページを作り、ナビゲーションメニューも同じ構造にしたいと考えていた。
しかし、ユーザーが使っていたツールの開発者は、「階層型メニュー(親項目とサブメニュー項目を含む)のインポートは、標準ではサポートされていない」と回答している。

post_parentフィールドでページ間の親子関係を移行することと、WordPressのナビゲーションメニュー(wp_nav_menu)に登録されたメニュー項目の階層構造を移行することは、別のデータを扱う作業だからだ。私は、この違いをユーザーに正確に伝える必要があると考えている。
LUX Content Migrationのページ選択モードが復元するのは、ページそのものの親子関係、つまりpost_parentフィールドである。
この関係が復元されれば、パンくずや「このページの子ページ一覧」のように、WordPressがページ階層を直接参照して生成する機能は正常に動作する。ただし、管理者が「外観」→「メニュー」で手動設定したナビゲーションメニューは、ページ階層とは別のデータとして保存される。そのため、これはこのプラグインの対象外だ。
メニューは、移行先サイトでページを移した後、ユーザー自身が手動で、または改めて構成する必要がある。この部分まで、あたかもプラグインがすべて処理してくれるかのように説明するつもりはない。
構造を整理すると
ページは、投稿とは異なる種類のデータだ。
カテゴリーというタグで分類されるのではなく、親子関係によってサイトの構造そのものを形作っている。
私は、この違いを無視してページ選択モードを作ったわけではない。
ただし、この方式を成立させるには、親ページも同じ処理に含まれていなければならない。また、メニュー構造はページ階層とはまったく別のデータであり、このプラグインが代わりに復元するものではないことも、明確にしておきたい。
このプラグインにできることと、できないことを正確に区別して伝えるほうが、実際に階層構造を持つサイトを移行しようとしている人にとって、より役に立つと私は考えている。
参考資料
- WordPress Core Tracチケット #8019 — WXRインポート時のコメントID処理エラーによるスレッド構造の消失
- UIC RED — Importance of Parent Pages (Site Hierarchy)
- WordPress.orgサポートフォーラム — How would I import pages with parents pages and a menu?
