公開済みページの投稿メタ(カスタムフィールド)データが「プレビュー」によって削除される件について対処してみた。
オプショナルアウトプットの処理変更で、プラグイン「WP Theme ShapeShifter Extensions」のバージョンが既に1.0.21にまで跳ね上がったんですが、送信するデータの数を調節するのにフィルターフックの存在を知りませんでしたので、結局JSONと連想配列で何とかしたのですが、ウィジェットエリアの解説ページに漸くウィジェットを出力できると思ったのですが、新たな問題に気付くことになりました。
リンク先の方法では、大量のデータ数をFORM送信する際、制限により送信できない問題を、テキスト形式にして1つのINPUTタグに収めて送信数を減らすという方法を取り、受け取る側でそれを連想配列に展開して、都合のいいように保存する方法ですが、公開済みのページ編集時にプレビューボタンを押すと、投稿メタ(カスタムフィールド)のデータの一部が削除されてしまうという悩ましい問題が起こったんです。
ええ、「更新」ボタンを押さずにページを閉じたり移動したりすると、設定していた筈の投稿メタデータが削除されてしまうということで、ブログのように一度公開したら編集しないようなページなら良いのかもしれませんが、固定ページのように公開してからも編集する可能性が比較的高いページでもそれが起こり得るのはちょっと危険な気がしました。
これを設定値が上手く保存された状態を保つ為に講じた手段をいくつか紹介しておきます。
データが消える現象について
まだちゃんと確認した訳じゃないんですが、以前にも「#20299 (Preview changes on a published post makes all post meta “live”) – WordPress Trac」というチケットがあって、というか2ヶ月前にもメッセージがあったということは、まだ続いている話なのかなとも思ったんですが、まぁワードプレスの方でもこの問題については認識されているようです。
ただ、ちょっと疑問だったのは、今回データが上手く保存されなかったのが、JSON式に切り替えた、もっと言えば、オプショナルアウトプットの連想配列形式に変換したデータのみ削除されていました。
具体的にどのような処理で保存していたかというと、
- オプショナルアウトプットに関連するINPUTタグなどのデータをJavaScriptでJSON化
- メソッドstringifyでJSONデータをテキスト化し、INPUT:hiddenでフォーム送信
- 受け取ったテキストの形でワードプレス関数「update_post_meta」で一旦保存
- ワードプレス関数「get_post_meta」を使い、保存してアンエスケープされたテキスト形式のJSONを取得してPHP関数で連想配列化
- 関数処理をして、受け取ったname属性値「[child]」などを連想配列の階層に変換
- 変換後の連想配列をforeachで展開しながら、各データをサニタイズ
- 最後に各データをサニタイズした連想配列の形でも「update_post_meta」で保存
といった流れの処理をアクションフック「save_post」で行っていました。
ところが、プレビュー時のみ、最初のテキスト形式の方は保存されていたんですが、最後の連想配列は空白の配列になってしまったんですよ。foreachループが上手く機能しなかったのか、それともテキストを取得してから階層付きの連想配列への変換までの処理でエラーが出てしまったのか、連想配列の形で保存する筈が空白の配列になっていたというわけです。
これは公開前の下書き状態でのプレビューでは起こらない現象だったので、僕も最近まで気付いていませんでした。
空白の配列が保存されていたところを見ると、ループ処理が上手く行かなかったということなんでしょうけど、正直どういう処理が行われていたのかが見えないので、見えるようにする方法があれば教えてほしいですね。
対処方法として
気になったのが、テキスト形式のデータが保存されていたように、一部のデータは上手く保存されていることです。
ええ、全てのデータが保存に失敗するわけじゃないようで、そこにちょっと活路を見出そうかと。
といっても、実際に行ったのはサニタイズ過程で、各データをまとめる前に1つずつ保存することです。
- オプショナルアウトプットに関連するINPUTタグなどのデータをJavaScriptでJSON化
- メソッドstringifyでJSONデータをテキスト化し、INPUT:hiddenでフォーム送信
- 受け取ったテキストの形でワードプレス関数「update_post_meta」で一旦保存
- ワードプレス関数「get_post_meta」を使い、保存してアンエスケープされたテキスト形式のJSONを取得して関数「json_decode」連想配列化
- 関数処理をして、受け取ったname属性の[child]などを連想配列の階層に変換
- (削除)
変換後の連想配列をforeachで展開しながら、各データをサニタイズ - 連想配列のカウントを最大値を保存して、その最大値を使ったforループで連想配列をサニタイズ
- サニタイズ過程で各データを個別に保存(キーに直接インデックスナンバーを入れると識別しやすい)
- (以前の最後の処理と同じ)最後に各データをサニタイズした連想配列の形でも「update_post_meta」で保存
保存処理はこんな感じに編集し直します。
次に、HTMLに出力する際、今までは保存した連想配列をそのまま展開して、各フォームのINPUTタグなどの設定値として使っていましたが、テキスト形式のデータを連想配列に変換する処理から始めました。あとは、消えてしまう事態も想定して、連想配列のカウントでforループを使い、各データを配列から取得する際、連想配列のインデックスがセットされていない場合は、各データ毎に保存した値を「get_post_meta」で引っ張ってくるようにしました。
ちょっと面倒でしたけど、プレビューボタンを押してもデータが残っていて引き出せるようになりましたので、取り敢えずプラグイン「WP Theme ShapeShifter Extensions」最新版では、上手く機能してくれるんじゃないかと思います。
念のためAJAXも使用
保存処理を行う関数・メソッドをAJAXで処理できるようにし、プレビュー時にトリガーされるようにしておくと便利です。
オプショナルアウトプットの場合は、JSONテキストを生成してINPUT:hiddenの値に与えるJavaScriptコードは既に書いてあったので、jQueryで「$( ‘#post-preview’ ).click( function( e ) { $.ajax( settings ) } )」を用意するだけでした。
プレビューボタンのIDは「post-preview」ですので、セレクターを間違えないようにすれば大丈夫だと思いますが、実際にAJAXによる保存処理で、プレビュー時にワードプレスによって行われる処理で消データが消えるのを絶対に防げるとは言えないんですが、気休めにはなるかもしれません。
確実に保存されるには?
ここで書くのは、PHPやJavaScriptコードによる処理ではなく、管理画面でページ編集時に行うべき処理なのですが、公開済みページの「プレビュー」ボタンを押した後、プレビューに投稿メタ(カスタムフィールド)設定が反映されていないとちょっとでも感じたら、編集画面の投稿メタのフォーム内にあるINPUTタグなどに、値が入力されている内に一度「更新」ボタンをクリックして保存することが確実な対処法です。
この場合は、入力されているデータが正しく、プラグインやテーマによって定められた方法で保存されます。
一番危険なのが「プレビュー」ボタンを押した後、「更新」ボタンを押すなどして保存せず、ページのリロードをしたり、別のページに移動してしまうことです。
何故なら通常、設定値に変更がある場合にページを遷移しようとすると、confirmにより「編集されていますけど、ページを移動しますか?」といった内容のポップアップが出てきてチェックしてくれますよね? ただ、プレビューボタンによってデータが消えても、INPUTタグ内に出力されている値に変更が無いために、ポップアップが起こらないんですよ。一旦保存してからプレビューするとなった場合だって有り得るからです。
まぁ殆どの場合は大丈夫だと思うんですが、プラグインによって追加されている場合、プラグイン「WP Theme ShapeShifter Extensions」のオプショナルアウトプットのように起こりえるので、万が一「データが消えてる?」と感じたら、念の為に保存を忘れない