依頼で作ったので例によってメモ程度に。サイト内の特定の部分に表示するためのフラグとしてカテゴリーを使っていることがあると思いますが、ただのフラグなので、wp_list_categories() や the_category() には出てきてほしくない、と言うときのフィルターのかけかたです。
でも、本来こう言う「表示したくないカテゴリー」はカスタムタクソノミーでやった方がいいですね。
依頼で作ったので例によってメモ程度に。サイト内の特定の部分に表示するためのフラグとしてカテゴリーを使っていることがあると思いますが、ただのフラグなので、wp_list_categories() や the_category() には出てきてほしくない、と言うときのフィルターのかけかたです。
でも、本来こう言う「表示したくないカテゴリー」はカスタムタクソノミーでやった方がいいですね。
拙作のWordPressプラグイン、Really Simple CSV Importerにはフィルターフックが用意してあり、活用すればCSVファイルから読み込んだデータをインポート前に様々に加工することが可能です。このフィルターフックを活用して、2000件くらいのブログ記事にあとからカスタムフィールドやカスタムタクソノミーのデータを追加するという仕事があり、その下調べとして色々試してみたので、ブログでもご紹介します。
今回は一例としてAdvanced Custom Fields(ACF)プラグインのRepeater Fieldアドオン(有償)で作ったフィールドにインポートする方法を取り上げます。フィルターフックの詳細はプラグインのOther Notesのページをご参照ください。
今回用意したCSVデータはこんなデータです。
post_status | post_title | textfield | select | text_1 | text_2 | text_3 | num_1 | num_2 | rnum_3 |
publish | ACFインポートテスト | インポートテキスト | red | 繰り返し1 | 繰り返し2 | 繰り返し3 | 100 | 101 | 102 |
publish | ACFインポートテスト2 | red,blue | あ | い | 250 |
このうち、”textfield” と “select” の値をそれぞれACFのフィールドにインポートしたいと思います。そして、”select” の値はACFのセレクトボックスにインポートしたいと思います。この場合、カンマ区切りのままではだめで、配列に変換する必要があります。
“text_1″ ”text_2″ ”text_3″ “num_1″ ”num_2″ ”num_3″ のそれぞれは、Repeater Field(繰り返しフィールド)にインポートしたいと思います。この場合、6つのフィールドに入っているデータを1つの配列にまとめてあげる必要があります。
ACFの設定画面
繰り返しフィールドを使用しています
実際の投稿画面はこんな風になります
それでは、実際のカスタマイズ例をご紹介しましょう。下記のコードをphpファイルに保存し、wp-content/plugins/ ディレクトリーにアップロードし、プラグインとして有効化します。
<?php /* Plugin Name: RS CSV Importer Customizer Version: 0.1 */ function rsci_meta_filter( $meta, $post, $is_update ) { echo '<pre>'; print_r($meta); echo '</pre>'; $meta_array = array(); $repeater_array = array(); foreach ($meta as $key => $value) { // カスタムフィールド名が "textfield" だった時 if ($key == 'textfield') { // ACF用のフィールドキーに変換 $meta_array['field_52528d5b8ad30'] = $value; // カスタムフィールド名が "select" だった時 } elseif ($key == 'select') { // カンマで分割して配列として登録 $meta_array['field_52528dc88ad31'] = preg_split("/,+/", $value); // 繰り返しフィールド用のデータを配列に入れていく処理 } elseif ($key == 'text_1') { $repeater_array[0]['repeater_text'] = $value; } elseif ($key == 'text_2') { $repeater_array[1]['repeater_text'] = $value; } elseif ($key == 'text_3') { $repeater_array[2]['repeater_text'] = $value; } elseif ($key == 'num_1') { $repeater_array[0]['repeater_number'] = $value; } elseif ($key == 'num_2') { $repeater_array[1]['repeater_number'] = $value; } elseif ($key == 'num_3') { $repeater_array[2]['repeater_number'] = $value; // ACF以外のメタデータはそのまま通す } else { $meta_array[$key] = $value; } } // 繰り返しフィールドの配列を戻す $meta_array['field_52528dea8ad32'] = $repeater_array; echo '<pre>'; print_r($meta_array); echo '</pre>'; return $meta_array; } add_filter( 'really_simple_csv_importer_save_meta', 'rsci_meta_filter', 10, 3 );
では、このプラグインが何をしているかの解説です。カスタムフィールドのデータは配列として、”really_simple_csv_importer_save_meta”フィルターを通ります。
Array ( [textfield] => インポートテキスト [select] => red [text_1] => 繰り返し1 [text_2] => 繰り返し2 [text_3] => 繰り返し3 [num_1] => 100 [num_2] => 101 [num_3] => 102 )
このままではACFのデータとして取り込まれませんので、”textfield”など仮のフィールドキーをACF用のフィールドキーに置き換えてあげる必要があります。
// カスタムフィールド名が "textfield" だった時 if ($key == 'textfield') { // ACF用のフィールドキーに変換 $meta_array['field_52528d5b8ad30'] = $value; }
また、複数データをカンマ区切りでCSVファイルの中に入れているときなどは、カンマで分割して配列に変更してあげる必要があります。
if ($key == 'select') { // カンマで分割して配列として登録 $meta_array['field_52528dc88ad31'] = preg_split("/,+/", $value); }
繰り返しフィールド用のデータは少し複雑なことをしていて、複数の値を配列として再構成しています。
Array ( [0] => Array ( [repeater_text] => 繰り返し1 [repeater_number] => 100 ) [1] => Array ( [repeater_text] => 繰り返し2 [repeater_number] => 101 ) [2] => Array ( [repeater_text] => 繰り返し3 [repeater_number] => 102 ) )
再構成した繰り返しフィールド用のデータも、メタデータの配列に戻しています。
$meta_array['field_52528dea8ad32'] = $repeater_array;
最終的にはこういう配列になるように加工しています。そんなに難しくないと思います。
Array ( [field_52528d5b8ad30] => インポートテキスト [field_52528dc88ad31] => Array ( [0] => red ) [field_52528dea8ad32] => Array ( [0] => Array ( [repeater_text] => 繰り返し1 [repeater_number] => 100 ) [1] => Array ( [repeater_text] => 繰り返し2 [repeater_number] => 101 ) [2] => Array ( [repeater_text] => 繰り返し3 [repeater_number] => 102 ) ) )
加工がうまく行っていれば、無事ACFのフィールドにデータが取り込まれます。
一応、日本語情報が見つからなかったのでメモ。3.7から管理画面の「更新」ページから手動で実行しなくても、勝手にバージョンアップしてくれる自動バックグラウンドアップデート機能が実装されました。アップデートがチェックされるのは、コアファイル、テーマ、プラグイン、翻訳の4つです。テーマ、プラグイン、コアのメジャーバージョンは自動アップデートがデフォルトでオフになっていますので、コアのマイナーバージョンと翻訳が今のところ自動アップデートの対象です。
自動アップデートはwp-config.phpに以下追記で停止できます。
define( 'AUTOMATIC_UPDATER_DISABLED', true );
ただし、セキュリティを高めるために特別な理由がない限り止めない方がよいです。何となく怖いから止めておくというのはナンセンスな態度です。WordPress.orgからダウンロードしたテーマやプラグインを使っているのであれば問題ないはずです。この機会に常に最新バージョンを使う習慣をつけてみてはいかがでしょうか。
コアファイルの自動バックグラウンドアップデートは現在マイナーバージョンのみに適用されるようです。「3.7.0」としたら「7」がメジャーバージョン、「0」がマイナーバージョンですね。
設定でメジャーバージョンにも自動アップデートを適用することができるようです。
// コアのアップデートを停止 define( 'WP_AUTO_UPDATE_CORE', false ); // マイナーバージョンのみ自動アップデートを適用 define( 'WP_AUTO_UPDATE_CORE', 'minor' ); // メジャーバージョンも自動アップデートを適用 define( 'WP_AUTO_UPDATE_CORE', true );
プラグインとテーマも自動アップデートの対象にしたい場合、または翻訳の更新だけ自動アップデートの対象から外したい場合は、これはwp-config.phpには書けないのですが、テーマのfunctions.phpかプラグインか何かで下記を記入します。
プラグインの自動更新を有効化するには以下を記入します。
add_filter( 'auto_update_plugin', '__return_true' );
テーマの自動更新を有効化するには以下を記入します。
add_filter( 'auto_update_theme', '__return_true' );
翻訳の自動更新を無効化するには以下を記入します。
add_filter( 'auto_update_translation', '__return_false' );
自動バックグラウンドアップデートが成功もしくは失敗したらメール通知が飛びます。「auto_core_update_send_email」フィルターで通知するかどうか、「auto_core_update_email」フィルターで通知の宛先などを変更できます。
注意点として、下記の設定ですでに一切のファイルの変更を止めている場合も、自動バックグラウンドアップデートが無効になります。
define( 'DISALLOW_FILE_MODS', true );
この設定がtrueの方は、これまで通り新しいバージョンがでたら、ステージングでテストした上で、手動でアップデートを適用という流れになりそうです。
似たような名前の設定で(実際混同していたのですが)管理画面からのテーマやプラグインの編集機能を無効にする設定があります。自動バックグラウンドアップデートで問題が起こらないようにするためには、テーマやプラグインに独自のカスタマイズを加えない方がよいので、下記の設定はtrueにしておくことをおすすめします。
define( 'DISALLOW_FILE_EDIT', true );
WordPressのセキュリティ上の問題として、管理画面に入られてしまうと、知らないうちにテーマのPHPファイルを何らかの攻撃スクリプトに書き換えられると言うことがありますので、セキュリティ上の理由としてもファイルの編集機能はオフにしておいた方がよいでしょう。
参考リンク
No related posts.
Really Simple CSV Importerプラグインでは、フィルターフックが仕込んであります。フックを使えば、CSVデータの解析とインポート処理の間に任意の処理を挟み込むことができます。この処理をプラグインとしてサイトにインストールしておくことで、プラグインを直接書き換えなくても、お使いのサイトに合ったオリジナルのCSVインポートプラグインを作ることができます。
一例として、インポート画面に投稿、カスタムフィールド、カスタムタクソノミーのデータをダンプするデバッグ用のアドオンプラグインの例を掲載しておきます。
これを応用すれば、特定のフィールドを無視したり、CSVデータにない情報を追加したりといったカスタマイズができます。アドオンとしてプラグイン本体から切り離すことで、プラグインのバグ修正アップデートをいち早く導入していただけます。
各フィルターの詳細はプラグインページの「Other Notes」をご覧ください。
余談ですがWordPressプラグインはこんな感じでシングルトンで書くのがマイブームです。
Trust Formを設置したフォームに、下記のようにGETパラメーターで値を渡す想定で、フォームの特定のフィールドにその値を入れる方法です。
<?php $link = home_url('/form') .'?event_name=' .esc_attr(get_the_title()); ?> <a href="<?php echo $link; ?>">イベント参加申し込みフォーム</a>
Trust Formはtrust-form-tpl-{$id}.phpという形式でフォームのテンプレートをテーマディレクトリーに設置できます。そのテンプレート内で、$_POSTに値を放り込みます。
はい無理矢理ですね。なんか、こういう用途のAPIを用意してもらえると使い勝手がいいと思いますよ、確認さん!ω・`)チラッ
ユーザーにフォームから何かを投稿させる際、一部の情報をブラウザにCookieで保存して、次の入力からは保存された情報を読み込んで入力の手間を省くということがあると思いますが、確認さんのWordPressプラグインTrust Formでそれをやる実例です。
まず、Cookieに保存する処理。
Cookieの保存はWordPressがヘッダーを送信するまえに行なわないといけないので、ヘッダー送信前の適当なアクションフックにひっかけます。ポイントとしては、この時点ではグローバル変数 $trust_form がセットされていないので、
$trust_form = new Trust_Form_Front(3);
という感じで、フォームIDを指定してTrust_Form_Frontクラスのインスタンスを生成しています。あとはまあ、よしなにCookieに保存してください。シリアライズしているのは、複数のフィールドをCookieに保存するためで、ひとつだけなら特に不要です。
Cookieからの情報の読み出しは、Trust Formのテンプレートから可能です。
$_POSTにつっこんでいるだけですね。Trust Formのフィールドに初期値を与えるでやっているのと同じです。
あとは、テンプレートのどこかで、「この情報を保存する」チェックを入れておいてください。
<label><input type="checkbox" name="tr_default_3" value="1" />IDを保存する</label>
Trust Form、なかなか便利に使えますね!
WordPressの有名なお問い合わせフォーム用プラグインContact Form 7は基本的にメールを送信するだけですが、Flamingoプラグインを併用することで、メールの送信内容をデータベース保存しておくことができます。Flamingoプラグインを使わずに、例えばメールの送信内容をカスタムフィールドに登録して行くという場合の処理を考えてみました。
まず、FlamingoはどうやってContact Form 7の情報を取得しているのでしょうか。その秘密はContact Form 7に同梱されている modules/flamingo.php にありました。下記に引用します。
add_action( 'wpcf7_before_send_mail', 'wpcf7_flamingo_before_send_mail' ); function wpcf7_flamingo_before_send_mail( $contactform ) { if ( ! ( class_exists( 'Flamingo_Contact' ) && class_exists( 'Flamingo_Inbound_Message' ) ) ) return; if ( empty( $contactform->posted_data ) || ! empty( $contactform->skip_mail ) ) return; $fields_senseless = $contactform->form_scan_shortcode( array( 'type' => array( 'captchar', 'quiz', 'acceptance' ) ) ); $exclude_names = array(); foreach ( $fields_senseless as $tag ) $exclude_names[] = $tag['name']; $posted_data = $contactform->posted_data; foreach ( $posted_data as $key => $value ) { if ( '_' == substr( $key, 0, 1 ) || in_array( $key, $exclude_names ) ) unset( $posted_data[$key] ); } $email = isset( $posted_data['your-email'] ) ? trim( $posted_data['your-email'] ) : ''; $name = isset( $posted_data['your-name'] ) ? trim( $posted_data['your-name'] ) : ''; $subject = isset( $posted_data['your-subject'] ) ? trim( $posted_data['your-subject'] ) : ''; $meta = array(); $special_mail_tags = array( 'remote_ip', 'user_agent', 'url', 'date', 'time', 'post_id', 'post_name', 'post_title', 'post_url', 'post_author', 'post_author_email' ); foreach ( $special_mail_tags as $smt ) $meta[$smt] = apply_filters( 'wpcf7_special_mail_tags', '', '_' . $smt, false ); $akismet = isset( $contactform->akismet ) ? (array) $contactform->akismet : null; Flamingo_Contact::add( array( 'email' => $email, 'name' => $name ) ); Flamingo_Inbound_Message::add( array( 'channel' => 'contact-form-7', 'subject' => $subject, 'from' => trim( sprintf( '%s <%s>', $name, $email ) ), 'from_name' => $name, 'from_email' => $email, 'fields' => $posted_data, 'meta' => $meta, 'akismet' => $akismet ) ); }
このコードを見ると分かる通り、wpcf7_before_send_mailというアクションフックで、メールの送信前に処理を追加することができるようです。Contact Form 7のプラグイン内で、Flamingoプラグインが有効化されている場合はFlamingoに情報を追加するという処理が書かれています。なので、FlamingoがCF7の情報を取得しているというよりは、CF7からFlamingoに情報を送っていると言うことですね。
wpcf7_before_send_mailアクションはdo_action_ref_arrayで呼ばれていて、引数の$contactformは参照で渡されているため、Contact Form 7のオブジェクトそのものが渡されてきています。そのため、このフックでContact Form 7の挙動を変更する(たとえば、メールを送信しないとか)ことや、送信前にフォームで入力された値を取得することが可能です。
このフックを使って、Contact Form 7で送信されたデータを、フォームが埋め込まれた投稿のカスタムフィールドに追加して行くという処理の実例がこちらです。
/** * customize contact form 7 */ function my_wpcf7_before_send_mail( $wpcf7 ) { // get post id $post_id = apply_filters( 'wpcf7_special_mail_tags', '', '_post_id', false ); // get posted data if ( isset($wpcf7->posted_data) && is_array($wpcf7->posted_data) ) { foreach ( $wpcf7->posted_data as $key => $value ) { if ($value != '') add_post_meta($post_id, $key, $value); } } } add_action( 'wpcf7_before_send_mail', 'my_wpcf7_before_send_mail' );
投稿IDを取得するのにフィルターを使っていますが、テーマに埋め込むと動かないのでご注意ください。Contact Form 7、さすが柔軟な設計になっていますね!
時々WordPressコミュニティの人に「WordPressとconcrete5の使い分けをどうしてるのか?」と聞かれるのですが、内心では「WordPressのタクソノミーはマジ便利なのでタクソノミーを使う場合はWordPress、WordPressのカスタムフィールドは不便なのでconcrete5の方がいい」と思ってるのですが、細かい話になるのでそこまでは言わないのですが、実例をひとつ。
映画データベースのサイトを作る際に、カスタムタクソノミーに「Actors」を追加し、俳優の名前をタームとして登録していくプランを考えてみましょう。ジェイソン・ステイサムの検索結果がこちら。スナッチとトランスポーターが登録されています。
続いてブラッド・ピットの検索結果です。ファイトクラブとスナッチが表示されます。
さて、ジェイソン・ステイサムとブラッド・ピットを両方検索パラメーターに付けるとどうなるか。
ジェイソン・ステイサムとブラッド・ピットのどちらかが出演している映画が表示されます。ここまで、インストール直後の状態にカスタムタクソノミーを追加しただけです。一切カスタマイズしていません。これでOKな仕様なら、WordPressで作るのが圧倒的にラクチンです。
ところで、ジェイソン・ステイサムとブラッド・ピットの両方が出演している映画を表示したい場合はちょっと工夫が必要です。「parse_tax_query」アクションフックを使いましょう。
function mod_tax_query( $the_query ) { // $the_query は WP_Query のインスタンス if ($the_query->is_main_query()) { // メインクエリー中の WP_Tax_Query を取得 $_tax_query = $the_query->tax_query; $_queries = array(); if ($_tax_query instanceof WP_Tax_Query) { foreach ($_tax_query->queries as $query) { // 目的のタクソノミーだった場合に if ($query['taxonomy'] == 'actors') { // オペレーターを変更 $query['operator'] = 'AND'; } $_queries[] = $query; } // メインクエリーに改変した WP_Tax_Query を戻す $_tax_query->queries = $_queries; $the_query->tax_query = $_tax_query; } unset($_queries); unset($_tax_query); } } add_action( 'parse_tax_query', 'mod_tax_query' );
Taxonomy Queryの ‘operator’ パラメーターの初期値は ‘IN’ なので、これを ‘AND’ に変更しています。
Codex: WP_Query # Taxonomy Parameters
この通り。タクソノミーを使う用事が予見されるとWordPressを使うのが楽です。
上記のように、WordPressは複数のタームを受け取る際にカンマ区切り、または “+” で区切る仕様になっていますが、実際のところチェックボックスでインターフェースを作ると ?actors[]=brad-pitt&actors[]=jason-statham のようなURLになると思います。この場合actorsパラメーターの値は配列になりますが、なんとWordPressは配列を受け取るとエラーになるので、、、parse_requestフックで+区切りなどに変更してあげないといけません。
function mod_parse_request( $wp ) { $actors = $_REQUEST['actors']; if(isset($actors) && is_array($actors)) { $actors = implode('+', $actors); $wp->set_query_var('actors',$actors); } } add_action('parse_request', 'mod_parse_request');
特別サービスや!持って行き!
<form action="<?php echo home_url(); ?>" method="get"> <ul> <?php $terms = get_terms(array('actors')); foreach($terms as $term){ $checked = (is_array($_REQUEST['actors'])) ? checked(in_array($term->slug, $_REQUEST['actors']), true, false) : ''; echo sprintf( '<li><label><input type="checkbox" name="%s[]" value="%s" %s/>%s</label></li>', $term->taxonomy, $term->slug, $checked, $term->name ); } ?> </ul> <input type="submit" value="検索" /> </form>
モバイルファーストなレスポンシブウェブデザインを実現するフロントエンド・フレームワークのFoundationがメジャーアップデートし、バージョン5がリリースされました!当ブログでも何度かFoundationをご紹介してきましたが、今回はかなり意欲的な内容だと思います。 そもそも、今年Foundation 4がリリースされたばかりでもう変わるのか!とすでにゲンナリしてる方は、そう面倒くさがらないでください。デザイン的にはFoundation 4と変わっていません。Foundation 5はデザインのアップデートではなく、パフォーマンスのアップデート、表示速度の改善が大きなポイントになっていると言うことです。
表示サイズによって自動的に異なるコンテンツを配信する技術です。このレスポンシブ配信技術により、大きな画面では大きな画像、小さな画面では小さな画像を配信することが可能になります。さらに、concrete5だとリサイズは自動でできますからね〜。Foundationベースでサイトを作れば、1つの大きな画像をアップするだけで各デバイスごとに最適なサイズの画像を表示することができる。これは近く検証してみたいと思います。
これまでもソリューションとしてはZurbから提供されていましたが、正式にFoundationに採用になりました。オフキャンバスレイアウトとは、画面の外にコンテンツを隠しておき、タップで画面の中にスライドしてくるという、主にモバイル用のナビゲーションで使用される技術です。スライドアニメーションは100%CSSで書かれており、Javascriptで動作させるときのようにスマートフォンで見ても重たくなりません。
その他、ZeptoとjQueryの併用を廃止し、jQuery2に統一されたり、アニメーションのハードウェア・アクセラレーションを使用している関係で、IE8のサポートは完全に廃止されました。
Foundation 5のもうひとつの大きなトピックは、開発ツールの改善です。目新しいのは
foundation new
という新しいコマンドラインツールです。このコマンドを打つだけで、すぐにFoundationベースの開発を開始することができます!コマンドの実行にはBowerが必要です。foundation new フォルダ名 で、すぐにそのフォルダ内にbower.jsonとconfig.rbが作られ、Foundationに必要なコンポーネントがダウンロードされます。
さらに、Libsassを採用することでビルドも高速化したそうです。
cliツールのリニューアルに伴い、gem名がzurb-foundationからfoundationに変わったので注意が必要かもしれません。
Chrome, Firefox, IE9+, iOS, Android2, Android4, Windows Phone 7+, Surface
基本的には、Javascriptのみ変更が必要です。まず、Zepto.jsが廃止されましたので、もし使っていたらjQueryに変更する必要があります。また、初期化パラメーターが変更になっています。詳しくは公式サイトをご覧ください。
アドベントカレンダーには参加してないけど今日も今日とてWordPressネタの更新です。今年に入ってから17記事目ですよ。concrete5 Japan Inc.なんてやってるのにおかしいですね。
それはさておき月末の追い込み時に調べる時間がないのにまとまった資料がなくて困った件の覚え書き。30分で終わらせようと思ったのに1時間以上かかってしまった…。WordPressのAjaxはやり方が多すぎてとりあえずこうしとけというのがまとまってないように思います。どうせ山ほどあるWordPress本にも書いてないだろうしな。これでいいのかよく分からないので、間違ってたらツッコミお願いします。
んで、どうも wp_ajax_{action_name}
または wp_ajax_nopriv_{action_name}
というアクションフックで処理するのがよさそうである。noprivの方はログアウト中、noprivがついてない方はログイン中のみ動作する。{action_name}
の部分は $_REQUEST['action']
と対応している。Nonceのチェックは check_ajax_referer() 関数で可能で、$_REQUEST['_ajax_nonce']
と対応している。
と言うわけで。以下が作例です。「送信」ボタンをクリックすると現在見ている投稿IDがWordPressにAjaxで送信されて、送った投稿IDの値がただ返ってきて、コンソールにそのまま表示するだけというシンプルな例です。実際には、ここからオリジナルのいいねボタンを作ったり評価ボタンを作ったり好きにすればいいさ!
フロント側の作例
<?php $post_id = get_the_ID(); // Ajaxは基本admin-ajax.phpを経由することになっている。 $admin_url = admin_url( 'admin-ajax.php', is_ssl() ? 'https' : 'http' ); // CSRF防止のためにNonceを生成する。 $ajax_nonce = wp_create_nonce('my_great_action'); // フォームではactionと_ajax_nonceを送る必要がある。 // でもnonceは無くても動いちゃう(強制すればいいのに) ?> <form id="my_great_action_form" method="post" action="<?php echo esc_js( esc_url_raw($admin_url) ); ?>"> <input type="hidden" name="action" value="my_great_action" /> <input type="hidden" name="_ajax_nonce" value="<?php echo $ajax_nonce;?>" /> <input type="hidden" name="post_id" value="<?php echo esc_attr($post_id) ?>" /> <input type="submit" value="送信" /> </form> <script type="text/javascript"> jQuery(document).ready(function($){ $('#my_great_action_form').submit(function(event){ event.preventDefault(); $.ajax({ url: $(this).attr("action"), type: $(this).attr("method"), data: $(this).serialize() }).done(function(response){ console.log(response); }); }); }); </script>
サーバー側の作例
<?php class My_WP_Ajax { // singleton instance private static $instance; public static function instance() { if ( isset( self::$instance ) ) return self::$instance; self::$instance = new My_WP_Ajax; self::$instance->run_init(); return self::$instance; } private function __construct() { /** Do nothing **/ } protected function run_init() { // wp_ajax_{action_name} というアクションフックが自動で作られる add_action( 'wp_ajax_my_great_action', array( $this, 'my_great_action' ) ); } public function my_great_action() { // Nonceのチェック。 check_ajax_referer( $action_name ) check_ajax_referer( 'my_great_action' ); // チェックしたら処理する $post_id = (int) $_REQUEST['post_id']; echo $post_id; die(); } } My_WP_Ajax::instance();
さて、次はconcrete5のAjaxについて調べておくか。
WordPressのアーカイブに<meta name="robots" content="noindex">
タグをつけるべきかどうか?という話題がありまして、こちらの記事に巻き込まれています。
【改訂版】重複コンテンツ問題まとめ~ややTogetter風~|ウェブシュフ
ざっくり言うと「アーカイブが重複コンテンツ(Duplicate Content)とみなされるとGoogleの順位が落ちるから、アーカイブはあらかじめ検索インデックスに含まれないようにすべき」という話かと思うのですが、全くナンセンスだと思います。ここからはおでさんの記事と同じようなことを書いてますが
サイトに重複するコンテンツが存在しても、偽装や検索エンジンの結果を操作する意図がうかがえない限り、そのサイトに対する処置の理由にはなりません。
- 重複するコンテンツ – ウェブマスターツールヘルプ
ということですので。さらに
Google は、固有の情報を持つページをインデックスに登録して表示するよう努めています。たとえば、サイトの各記事に「通常」バージョンと「印刷用」バージョンがあり、両方とも noindex メタ タグでブロックされていない場合、フィルタリングによってどちらか 1 つが登録されます。
- 重複するコンテンツ – ウェブマスターツールヘルプ
コンテンツが重複してて noindex が入ってなかったとしても、よろしくやっとくよ。ということですね。
ごくまれなケースとして、Google でのランキングの操作やユーザーへの偽装を意図した重複コンテンツが表示される可能性が認識された場合も、Google では関係するサイトのインデックス登録とランキングに対して適切な調整を行います。その場合、該当するサイトはランキングが低下するか、Google インデックスから完全に削除されて検索結果に表示されなくなる可能性があります。
- 重複するコンテンツ – ウェブマスターツールヘルプ
通常のサイトではあり得ないごくまれなケース(In the rare cases)ではあるが、偽装を意図して重複コンテンツを作っている人がいますので、そういう悪い子は検索結果から削除するよ。ということですね。
重複するコンテンツに関して事前に対処し、ユーザーに意図したとおりのコンテンツが表示されるようにするための手順を以下に示します。
- 重複するコンテンツ – ウェブマスターツールヘルプ
Googleは重複コンテンツがあってもどれを検索結果に出すべきかちゃんと判断するけど、自動なので自分の意図通りじゃないものが検索結果に出る可能性がある。そう言う場合は事前にGoogleに知らせる方法があるよ。ということですね。
これ以上なにか疑問があるかしら、というくらい明確な文章ですが、似たような質問に動画でも答えています。
「robots.txtを使って自分で重複コンテンツ対策することは推奨しない。全てクロールしたうえで、サイトの構造を解析して、どのURLを検索結果に表示するか判断したいからね。」
結論としてはGoogle神の祟りを畏れてnoindexを入れなくても全然問題ないし、でも検索結果に不本意なURLが出ていたら対策することもできるよ。と言うことだと思います。Twitterで @web_shufu さんに教えてもらったんですが(Thanks!)、Webmaster Centralのフォーラムでも同様の回答がありました。
- If you have a lot of tag-pages and feel that they aren’t providing a great user-experience, you could use a “noindex” meta tag on them. That would allow us to crawl through them, while avoiding them being shown in the search results.
- Webmaster Central Forum
もしたくさんのタグページがあって、それらが良いユーザーエクスペリエンスを提供していないと感じる場合は、noindexメタタグを使用してもやむを得ないだろう。それらが検索結果に表示されなくはなるが、我々は依然クロールすることはできるからね。
ということですね。”you could use” なのでだいぶ弱い表現です。ユーザーエクスペリエンスを損なうと感じるならば、noindex使うのも仕方ないけど、robots.txtで拒否するのはやめてね。って感じですね。
Googleの姿勢として全体的に、ユーザーが検索エンジンに何を求めているのか、オマエラより俺らの方が分かってるんだから任せておけ、というニュアンスを感じます。それはそれで過去の歴史上そうでもなかっただろという気がしなくもないですが。
ここまでの情報を集めて、ふとこの記事を思い出しました。
【WordPressカテゴリーページのSEO】meta name=”robots”をindexにする前にやるべき8つのこと|ウェブシュフ
低品質なアーカイブはnoindexした方がいいんじゃないの、という記事ですね。確かに、今回この記事に”noindex”というタグ(metaタグじゃなくて、WordPressのタグ機能のことですよ)をつけますが、このブログに”noindex”タグがついた記事は今のところこの1件ですので、”noindex”タグのアーカイブはユーザーが見ても何の意味も無いページだと思います。一方、WordPressという検索語でユーザーが当ブログにアクセスした時、どのURLが最もWordPressというキーワードに合致しているかと言うとWordPressタグのアーカイブだと思います。個々の記事がもっとも役に立つWordPress記事かどうかなんて判断できませんので、アーカイブから見てもらうのが一番いいと思います。
つまり、タグアーカイブもぼくはインデックスしてほしいし、でも件数が少ないタグアーカイブはそうでもないな。ということです。で、作ってみました。記事数が2件以下、つまりまだ1件しか記事がないタグアーカイブにnoindexを出力するプラグインです。すでに有効化済みです。
サイトのコンテンツや構造によっては件数を調整したいかもしれませんので、そのときは $number 変数を調整してください。
<?php /* Plugin Name: No-indexing newborn tag archives */ function my_archives_noindexing() { global $wp_query; $number = 2; // Print noindex tag if the tag archive has posts less than this number if (is_tag() && (int) $wp_query->found_posts < $number) { echo '<!-- This tag archive does not have much content. -->'; echo '<meta name="robots" content="noindex,follow">'; } } add_action( 'wp_head', 'my_archives_noindexing' );
最後の判断基準は「ユーザーエクスペリエンス」これを忘れないようにしたいものです。
こちらからは以上です。
This is a modified version of a tutorial “Cross-Browser Grayscale image example using CSS3 + JS“.
This original version worked well, but only for actual img objects. If you’d like to apply mouse over grayscale effect for background images, try modify functions.js like this:
if (getInternetExplorerVersion() >= 10){ $('.example ul').imagesLoaded(function(){ $('.example ul li').each(function(){ // get background-image style value var backgroundImage = $(this).css('background-image'); // get background-image src var backgroundImageSrc = backgroundImage.replace(/^url\(["']?/, '').replace(/["']?\)$/, ''); // get grayscale data url var grayScaleImageSrc = grayscaleIE10(backgroundImageSrc); // set color image src to data attribute $(this).data('image-src', backgroundImageSrc); // set grayscale image src to data attribute $(this).data('grayscale-src', grayScaleImageSrc); // change grayscale to default this.style.backgroundImage = "url(" + grayScaleImageSrc + ")"; }).hover(function(){ // change to the color image var src = $(this).data('image-src'); this.style.backgroundImage = "url(" + src + ")"; }, function(){ // change back to the grayscale image var src = $(this).data('grayscale-src'); this.style.backgroundImage = "url(" + src + ")"; }); }); function grayscaleIE10(src){ var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); var imgObj = new Image(); imgObj.src = src; canvas.width = 300; // from your stylesheet canvas.height = 200; // from your stylesheet ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(imgObj, 0, 0); var imgPixels = ctx.getImageData(0, 0, canvas.width, canvas.height); for(var y = 0; y < imgPixels.height; y++){ for(var x = 0; x < imgPixels.width; x++){ var i = (y * 4) * imgPixels.width + x * 4; var avg = (imgPixels.data[i] + imgPixels.data[i + 1] + imgPixels.data[i + 2]) / 3; imgPixels.data[i] = avg; imgPixels.data[i + 1] = avg; imgPixels.data[i + 2] = avg; } } ctx.putImageData(imgPixels, 0, 0, 0, 0, imgPixels.width, imgPixels.height); return canvas.toDataURL(); }; };
マウスオーバーするとグレイスケールの画像がカラーになる、という効果を実装するサンプル。リンク先のチュートリアルの通りにすればimgタグを使っている場合はうまくいくのですが、背景画像の場合、このIE10以降対応の部分が動きません。なのでちょいと変更してみたサンプルです。
Thanks to original blog post and wonderful tutorial!
My plugin, Really Simple CSV Importer has filter hooks. If you use these, you will be able to customize your data from csv file before importing. This blog post is an example of using filters of the importer, import data to a Repeater Field of Advanced Custom Fields (ACF) plugin. If you would like to know the detail of filter hooks, please see the Other Notes page.
Here is an example csv data.
post_status | post_title | textfield | select | text_1 | text_2 | text_3 | num_1 | num_2 | num_3 |
publish | ACF Import test | Text example | red,blue | Repeat 1 | Repeat 2 | Repeat 3 | 100 | 101 | 102 |
To import to ACF Select field or ACF Repeater field, you should create array data. Usually, you can interpret array data as comma divided text or multiple columns.
Then, this is an example of filtering data. Save below code as php file, upload to wp-content/plugins/ directory, and activate it.
<?php /* Plugin Name: RS CSV Importer Customizer Version: 0.1 */ function rsci_meta_filter( $meta, $post, $is_update ) { // Show the raw post data from csv echo '<pre>'; print_r($meta); echo '</pre>'; // Create containers $meta_array = array(); $repeater_array = array(); foreach ($meta as $key => $value) { // If custom filed name is "textfield" if ($key == 'textfield') { // Convert field name to ACF Field key $meta_array['field_52528d5b8ad30'] = $value; // If custom field name is "select" } elseif ($key == 'select') { // To import to the "Select" field, split the comma divided data to array $meta_array['field_52528dc88ad31'] = preg_split("/,+/", $value); // Create array data to import to the Repeater Field } elseif ($key == 'text_1') { $repeater_array[0]['repeater_text'] = $value; } elseif ($key == 'text_2') { $repeater_array[1]['repeater_text'] = $value; } elseif ($key == 'text_3') { $repeater_array[2]['repeater_text'] = $value; } elseif ($key == 'num_1') { $repeater_array[0]['repeater_number'] = $value; } elseif ($key == 'num_2') { $repeater_array[1]['repeater_number'] = $value; } elseif ($key == 'num_3') { $repeater_array[2]['repeater_number'] = $value; // Pass through normal (not ACF) custom field data } else { $meta_array[$key] = $value; } } // Insert Repeater data $meta_array['field_52528dea8ad32'] = $repeater_array; // Show the converted data echo '<pre>'; print_r($meta_array); echo '</pre>'; return $meta_array; } add_filter( 'really_simple_csv_importer_save_meta', 'rsci_meta_filter', 10, 3 );
Finally, you will get data as below:
Array ( [field_52528d5b8ad30] => Text example [field_52528dc88ad31] => Array ( [0] => red [1] => blue ) [field_52528dea8ad32] => Array ( [0] => Array ( [repeater_text] => Repeat 1 [repeater_number] => 100 ) [1] => Array ( [repeater_text] => Repeat 2 [repeater_number] => 101 ) [2] => Array ( [repeater_text] => Repeat 3 [repeater_number] => 102 ) ) )
If you have a question, feel free to post Support forum. Thanks!
WordPressで子テーマを作る際、Codexには親テーマのスタイルシートを @import で読み込めばいいよともう何年も書いてあるんですが、@importを使うのは表示速度の問題などでやっぱり抵抗がある。
Codexに記載されている子テーマのstyle.cssの作成例:
/* Theme Name: Twenty Fourteen Child Theme URI: http://example.com/twenty-fourteen-child/ Description: Twenty Fourteen Child Theme Author: John Doe Author URI: http://example.com Template: twentyfourteen Version: 1.0.0 Tags: light, dark, two-columns, right-sidebar, responsive-layout, accessibility-ready Text Domain: twenty-fourteen-child */ @import url("../twentyfourteen/style.css"); /* =Theme customization starts here -------------------------------------------------------------- */
そこで、このブログではこんな風にしてみました。
/* Theme Name: notnil creation weblog Theme URI: http://notnil-creative.com/ Version: 1.0 Author: Takuro Hishikawa (@HissyNC) Template: match */
<?php function load_parent_theme_styles() { wp_enqueue_style( 'parent-style', get_template_directory_uri() . '/style.css' ); } add_action( 'wp_enqueue_scripts', 'load_parent_theme_styles' );
そんだけ。
大成功のうちに終了した WordCamp Kansai 2014 の2日目の会場で、Audrey Capital の Ninja Wrangler として WordPress コアの開発に携わっている Samuel Sidler さんをつかまえて、4.0以降の WordPress の注目機能についてお話しを聞くことができました。
concrete5 も次期バージョンでは TinyMCE を捨て Redactor エディタが採用されますが、WordPress でも TinyMCE はイマイチだと思っている方は多いのではないでしょうか。WordPress でも新しいエディタの開発プロジェクトが動いています。Sam さんの話ではまだリリース時期は未定だが、開発は GitHub 上で行なわれているということです。このようにプラグインとして先に実装して検証を重ねてから新機能をコアに取り込むというのがいまの開発スタイルになっているそう。今のバージョンの WordPress でもインストールして試すことができます。
https://github.com/avryl/editor-experiments
インストールするとまず目につくのがボタンの少なさです。取り消し/進むボタン、テキストのペーストボタン、スタイルのクリアボタンの4つ。エディタ内で改行すると「Add a content block(コンテンツブロックを追加)」というリンクが表示されます。
Add a content block をクリックすると、このようにブロック一覧が表示されます。なんか、ちょっと concrete5 っぽいネ
たとえば、Map を選択すると Google Map が挿入されます(なぜ OSM じゃないのか)。
ブロックの横には編集と削除のボタンが表示されます。編集ボタンをクリックすると、ブロックの編集ウィンドウが開きます。これは、Map ブロックの編集ウィンドウ。
これらのブロックは、どのように本文に挿入されているかと言うと、ショートコードになっています。
この新しいエディタはまだ未実装の機能も多いのですが、とにかく分かりにくかったショートコードの編集が劇的に楽になると思われます。ただし、このエディタに対応した編集ウィンドウを Javascript で実装しないといけないでしょうから、ショートコード系のプラグイン作者は大変になるかもしれません。
使いにくくかった XML-RPC に変わる、モダンな JSON スタイルの REST API の開発も進んでいるそうです。Publishing がキーワードの WordPress にとって、このような Web UI を介さない API の存在は必要不可欠と言えるのではないかと思いますが、ようやくリリース時期の目処も立ったようで、バージョン4.1のリリースサイクルに乗ることが決まったそうです。
こちらもプラグインとしてリリースされていますので、どなたでも試せます。
https://github.com/WP-API/WP-API
検索や投稿がどのように行なえるのか、認証がどうなるのかあたりが気になる点かな。ちょっと入れてチラ見しただけですが、こりゃ便利そうですね。Ajax 経由でのAPIが整備されることで、フロントエンド編集もこのAPIに統合されるのではないかと勝手に予想。
Samさんは、このAPIでJavascriptだけのテーマが作れるという可能性に言及されていました。よく分かってないんですが、ステートフルJavascriptってやつですかネ。この本、買ったっきり積んであるからちょっと勉強しよう…。
ステートフルJavaScript ―MVCアーキテクチャに基づくWebアプリケーションの状態管理
Really Simple CSV Importer をこのたび Custom Field Suite に対応させたんですが、実際のところまだまだ他にもカスタムフィールドのインターフェースを作るプラグインが乱立していて、もう収拾がつかない段階になっているんですよ。で、なんとかなる予定はあるのか質問したところ、あるみたいですちゃんと。
こちらも例によってすでにプラグインとしてインストールが可能になっています。
https://github.com/wordpress-metadata/metadata-ui-api
テーマのfunctions.phpにこーんな感じでPHPを記述すると
function example_init() { register_post_type( 'pm_solution', array( 'label' => __( 'Solutions', 'pm-sherpa' ), 'public' => true, 'rewrite' => true, 'form' => 'after-title' ) ); register_post_field( 'website', 'pm_solution', array( 'type' => 'url', 'label' => __( 'Website', 'pm-sherpa' ), 'html_placeholder' => 'http://www.example.com', 'html_size' => 50 ) ); register_post_field( 'tagline', 'pm_solution', array( 'label' => __( 'Tagline', 'pm-sherpa' ), 'html_size' => 50 ) ); register_post_field( 'blurb', 'pm_solution', array( 'type' => 'textarea', 'label' => __( 'Blurb', 'pm-sherpa' ), 'html_size' => 160 ) ); } add_action( 'init', 'example_init' );
画面上でこのように表示されます。
数多あるカスタムフィールド系のプラグインの基本機能ですね。
さらに、テキスト/テキストエリア/日付などのフィールドタイプの実装、繰り返しフィールドの実装などが予定されているそうですが、見たところ全然作りかけですね。コアに取り込まれるのはだいぶ先になりそうな気がします。興味のある方はぜひプルリクエストを送って参加してみるといいかもしれない。
データベースを見たところ、保存方法はこれまでのカスタムフィールドと同じ。なので、カスタムフィールドの検索の遅さや、リビジョンにカスタムフィールドが含まれないと言う問題については特にこのプラグインで改善されることはないようだ。これはちょっと残念。インデックスの貼り方などのパフォーマンスの改善は別で考えると言うことか。また、カスタムフィールドのデータの取得部分のテンプレートタグなども特に追加するとは書かれていないので、カスタムフィールド系のプラグインの隆盛は今後もしばらく続きそうな予感。ちょっと、ここは早急に何とかするべき部分だと思うのだが…。
最近concrete5ばかりやっているワタクシですが、WordPressも好きなんですよね。なので、今後の進化には期待せざるを得ません。ただ、編集UIに関してはずっと直感的な編集体験を追求してきたconcrete5の方がやはり先を行っているなと思いました。企業向けのエンタープライズなCMSの導入が業務として多い自分としては、concrete5に今後も取り組むことは変わらないなと思います。
とはいえ、個人的にWordPressに期待しているのはデバイスやブラウザの制約を超えた Content Publishing のスタンダードとなることなので、その意味で最も注目しているREST APIのリリースの目処が立ったことは大いに喜ばしいことだと思っています。concrete5は、さらにブラウザ依存になっていっていますが、ブラウザに依存することは、ブラウザ上でのリッチな編集体験を追求しようとしていけば避けられない運命なのではないかと。リッチな編集体験自体は悪いことではありません。ただし、インターネットにブラウザからアクセスするという前提条件がそこにはあります。その常識が無くなった未来に置いても、WordPressは残り続けてほしいと思っているのです。
まとめ:割と大変(今のところ)。
WordPress JSON REST APIとは、WordPressの外部からWordPressのデータベースにアクセスできるAPIのことです。まだWordPressに組み込まれた機能ではありませんが、すでに注目を集めている新機能のひとつです。バージョン4.1のマイルストーンに組み込まれていますので、近い将来にコアに組み込まれると思われます。他の新機能と同様プラグインで開発が進められています。
このAPIを使うと、WordPressとやりとりできるリソースはURIで表され、JSON形式のデータでやりとりすることができます。RESTの概念を説明しだすと長くなるのですが、要するにかんたんにシステムとデータのやり取りをするための人気のある規格と覚えておいていただければと思います。実際に、これまでもXML-RPC APIでデータのやりとりは可能であったので、JSON REST APIはそれより簡便で汎用的であることから注目されているのだと思います。
参考URL
JSON REST APIでアクセスできるリソースは公式サイトに一覧されています。投稿の取得/作成/編集/削除、カスタムフィールドの取得/作成/編集/削除、添付ファイルの取得/作成、ユーザーの取得/作成/編集/削除、タクソノミーの取得など、外観やプラグインに関する部分以外はの範囲を管理画面にアクセスせずにコントロールが可能になります。WordPressのデータに外部からアクセスできると、例えば次のような活用方法が考えられます。
この辺のカスタマイズが欲しい方は弊社までお問い合わせください
WordPress REST APIを介すると外部から管理画面にアクセスせずに様々な情報をやりとりできるわけですが、当然ですが誰でもこのAPIが使えると困ります。なので、WordPressのログイン画面を使わずに、特定の外部のアプリケーションからWordPressにアクセスすることを認証する仕組みが必要になります。OAuthは非常に広範に使われているAPI認証のための規格で、Twitter、Facebook、GitHubなど様々なサービスのAPIの認証に使われています。WordPress REST APIはOAuth1.0a(Three Legged)を採用しています。なぜこれから出てくるAPIで1.0なのかは謎ですが…まだ2.0は時期尚早ということなのでしょうか(※Facebookのコメントで、2.0は暗号化必須だからではないか、とのこと)。
参考URL
OAuthの実装は複雑なので、自力で実装するのはおすすめしません。汎用的な規格なので、世の中には色々なOAuth用のライブラリがあります。今回はconcrete5からWordPress REST APIへのアクセスを目指していますので、concrete5も使用しているZend Framework1を使用することにします。余談ですが、Symfonyで使えるOAuthライブラリは結構あるので、Symfonyを採用するconcrete5.7で使えるようにいずれはSymfonyで使えるライブラリを使って書き直そうと思っています。
必要なのはJSON REST APIプラグインとOAuth Serverプラグインの2つです。この2つは将来的にコアに統合される予定みたいです。現在開発中の機能なので、今のところはGitHubから最新を落としてくるのがおすすめです。
OAuthでログインするには、まずログインされる側、ここではWordPressの方で専用のキーを発行する必要があります。キーの発行はUIでも行なえるようになるみたいですが開発中で、今のところコマンドラインでしか行なえません。
$ wp oauth1 add ID: 4 Key: sDc51JgH2mFu Secret: LnUdIsyhPFnURkatekRIAUfYV7nmP4iF3AVxkS5PRHPXxgOW
コマンドライン(cli)の使い方まで解説すると長くなるので、こちらのブログをみていただければざっと分かると思います。
第32回WordBench大阪に参加して〜WP-CLIの情報だよ〜 – Kimiya Kitaniの徒然なるブログ
発行したコンシューマーキーは、適当に画面を作って保存しておけば良いですね。
発行されたConsumer KeyとConsumer Secretをもとに、Request Tokenを取得します。ログインしたい側からWordPressへのリクエストになります。ここでは、Zend_OAuthを使っています。
Introduction to OAuth – Zend_Oauth – Zend Framework
// 設定 $config = array( 'requestScheme' => Zend_Oauth::REQUEST_SCHEME_HEADER, 'callbackUrl' => $callback_url, 'requestTokenUrl' => $request_token_url, 'userAuthorizationUrl' => $authorization_url, 'accessTokenUrl' => $access_token_url, 'consumerKey' => $consumer_key, 'consumerSecret' => $consumer_secret ); // コンシューマーの生成 $consumer = new Zend_Oauth_Consumer($config); // リクエストトークンを取得 $token = $consumer->getRequestToken(); // トークンをセッションに一時保管 $_SESSION['REQUEST_TOKEN'] = serialize($token);
$callback_url
はWordPress側での認証が終わったら戻ってくるURLの指定です。
$request_token_url
と$authorization_url
、$access_token_url
の3つはハードコードせず、あらかじめWordPress JSON APIから取得しておく必要があります(これらの情報の取得に認証は要りません)。2つのプラグインがインストールされた状態で/wp-json
にアクセスすると、WordPressサイトの基本情報が表示されますが、この中の authentication
ノード内に3つのURLが表示されています。
$consumer_key
と$consumer_secret
はあらかじめ発行しておいたものです。
このあとのステップでWordPress側の画面に遷移して承認するステップがあるのですが、その際画面を離れます。取得したリクエストトークンは、一時的に保管しておく必要があります。ここではセッションを使用していますが、データベースでも構いません。
次に、取得したリクエストトークンをもとにWordPressの認証画面にリダイレクトさせます。このとき、システムに戻ってくるURLをGETパラメーターで付与しておく必要があります。今後WordPress側でコンシューマーキーの管理画面ができた際に、コールバックURLも指定できるようになる可能性はありますが、とりあえず現状では指定が必要です。
$consumer->redirect(array( 'oauth_callback' => $callback_url ));
もうひとつポイントなのですが、このコールバックURLはWordPress側のフィルターで拒否されてしまいます(めんどくせえ)。WordPress側に適当なプラグインを作って、戻ってくるURLのホストを許可しておくとよいでしょう。
<?php /* Plugin Name: Hacks */ add_filter('http_request_host_is_external',function($allow, $host, $url) { if ($host = 'c5631.example.com') $allow = true; return $allow; },10,3);
こんな感じで適当に認証ボタンを作りました。クリックするとWordPressに飛んで認証を求められます。ここで認証したユーザー権限がREST APIで取得できるデータの権限に該当しますので、adminで認証すると全データが操作可能になります。
認証ボタンをクリックすると認証済みのトークンがコールバックURLに渡されてきます。この認証済みのトークンを使ってアクセストークンを取得します。アクセストークンを一度取得すると、毎回WordPressの認証画面を踏む必要なく、APIへのアクセスを行なうことができます。このステップも詳細は省略。ライブラリにお任せ。
// 設定 $config = array( 'requestScheme' => Zend_Oauth::REQUEST_SCHEME_HEADER, 'callbackUrl' => $callback_url, 'requestTokenUrl' => $request_token_url, 'userAuthorizationUrl' => $authorization_url, 'accessTokenUrl' => $access_token_url, 'consumerKey' => $consumer_key, 'consumerSecret' => $consumer_secret ); // コンシューマーの生成 $consumer = new Zend_Oauth_Consumer($config); // アクセストークンを取得 $token = $consumer->getAccessToken($_GET, unserialize($_SESSION['REQUEST_TOKEN'])); // Zend_Oauth_Token_AccessオブジェクトからTokenとSecretを分けて取り出しておきます $oauth_token = $token->getToken(); $oauth_token_secret = $token->getTokenSecret(); // 適当に保存します $co->save('WP_REST_API_OAUTH_TOKEN', $oauth_token); $co->save('WP_REST_API_OAUTH_TOKEN_SECRET', $oauth_token_secret); // 一時保管していたリクエストトークンを破棄します $_SESSION['REQUEST_TOKEN'] = null;
以上で認証が完了しましたので、晴れてWordPress REST APIに接続できます。長かった…。
// アクセストークンとコンシューマーキーの両方が必要です $token = new Zend_Oauth_Token_Access; $token->setParams(array( Zend_Oauth_Token_Access::TOKEN_PARAM_KEY => $oauth_token, Zend_Oauth_Token_Access::TOKEN_SECRET_PARAM_KEY => $oauth_token_secret )); $client = $token->getHttpClient(array( 'consumerKey' => $consumer_key, 'consumerSecret' => $consumer_secret )); // ユーザーを取得してテストしてみます(認証が必要)。 $client->setUri($wp_rest_api_url.'/users'); $client->setMethod(Zend_Http_Client::GET); $response = $client->request(); // データはJSON形式で送られてきますので、デコードしてデータが取り出せれば成功! $data = Zend_Json::decode($response->getBody());
ただ、ここでもちょっとしたトラップが発動。CGIモードで動いている場合らしいのですが、PHPにAuthenticateヘッダのデータが渡らないことがある模様。そのため、WordPress側の.htaccessとindex.phpにちょっと細工して回避。
How can I access request headers that don’t appear in $_SERVER?
.htaccess
# BEGIN WordPress <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index\.php$ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] </IfModule> # END WordPress
index.php
<?php // ここから追加 if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; } // ここまで追加 /** * Front to the WordPress application. This file doesn't do anything, but loads * wp-blog-header.php which does and tells WordPress to load the theme. * * @package WordPress */ /** * Tells WordPress to load the WordPress theme and output it. * * @var bool */ define('WP_USE_THEMES', true); /** Loads the WordPress Environment and Template */ require( dirname( __FILE__ ) . '/wp-blog-header.php' );
index.phpの方は、面倒くさがらずにプラグインにしようかと思いましたがやっぱり面倒になったのでとりあえずこれで。
認証が必要なユーザーデータが取得できましたので、次はWordPressにリモートで何か記事を投稿してみます。ブログにも書きますが、先に 9/28 のコンフジ! in 沼津で実演も行ないますので、よろしくお願いいたします。
WordPressのプラグイン、Really Simple CSV Importer のバージョン1.0をリリースしました。ということで、今さらですが、プラグイン説明欄の日本語訳を載せておきます。ネット上には、何やら怪しげな解説記事がちらほら出回っているようですので…。
バージョン1.0では、アクションフックを追加し、便利なヘルパークラスも新作しました。当初考えていた機能はこれで出そろいましたので、あとはメンテナンスやら多言語対応やらをのんびりやっていこうかと思います。次に作成するWordPressプラグインとしては、concrete5のインポートファイル形式へのエクスポートプラグインを予定しています。
なお、Really Simple CSV Importerには、torounitさん作のReally Simple CSV Importer Media Plusという派生プラグインが存在しており、こちらを使っている方も多いと思います。Media Plusの方は、カスタムフィールドの値が画像だったら自動でメディアにアップしてIDに置換すると言うものですが、URLのままカスタムフィールドに入れておきたい場合もあると思いますので、当プラグインには該当機能をマージしていませんでした。アクションフックの追加によって、同様のことが可能になりました。下の方でその例を解説しています。
CSVインポータープラグインのもうひとつの選択肢。シンプルで強力、ギークに最適。
CSVファイルのサンプルを /wp-content/plugins/really-simple-csv-importer/sample
ディレクトリから入手できます。
ID
or post_id
: (数値) 投稿ID。post_author
: (ログイン名 または ID) 投稿者のユーザー名またはユーザーID数値。post_date
: (文字列) 公開日の時間指定。post_content
: (文字列) 投稿の本文。post_title
: (文字列) 投稿のタイトル。post_excerpt
: (文字列) 投稿の抜粋。post_status
: (‘draft’ または ‘publish’または ‘pending’または ‘future’または ‘private’またはカスタムステータス) 投稿ステータス。’draft’がデフォルトです。Codexの投稿ステータスページpost_name
: (文字列) 投稿のスラッグ。post_parent
: (数値) 投稿の親ID。階層構造を持つ投稿タイプの場合に使います。menu_order
: (数値) 並び順。post_type
: (‘post’ or ‘page’ or any other post type name) (必須) 投稿タイプスラッグ。ラベルではありません。post_thumbnail
: (文字列) 投稿サムネイル(アイキャッチ画像)のURI、またはパス。post_category
: (文字列、カンマ区切り) 投稿カテゴリーをスラッグで指定post_tags
: (文字列、カンマ区切り) 投稿タグを名前で指定tax_{taxonomy}
: (文字列、カンマ区切り) 接頭辞 tax_
で始まるフィールドは、カスタムタクソノミーとして使われます。タクソノミーはすでに登録済みである必要があります。入力値はタームの名前、またはスラッグです。{custom_field_key}
: (文字列) その他のカラムのラベルはすべてカスタムフィールドとして扱われます。cfs_{field_name}
: (文字列) Custom Field Suiteプラグインで設定されたカスタムフィールドにインポートしたい場合は、接頭辞 cfs_
を追加してください。注意:CSVファイル内のセルは「値の維持」を意味し、「削除」ではありません。
注意:ページのページテンプレートを指定したい場合は、カスタムフィールドのキーで _wp_page_template
を指定してください。
注意:投稿ステータスで ‘future’(予約済み)を指定する場合は、post_data を指定することでWordPressにいつ投稿を公開すべきかを指示する必要があります。
Advanced Custom Fieldプラグインのキーが存在する場合は、インポーターは標準の add_post_meta 関数ではなく、update_field 関数を使ってインポートを試みます。
Advanced Custom Fieldキーの取得方法
スターをよろしく! GitHub
カバーバナーデザイン: @luchino__
Really Simple CSV Importerに組み込まれているフィルターフックを使うことで、配列データをカスタムフィールドに登録できます。フィルターフックを使うには、Really Simple CSV Importerをカスタマイズするための別のプラグインを作成します。以下は、そんなプラグインの作例です。
このプラグインを有効にすると、下記のようなCSVファイルをインポートする際、フィルターフックからのカスタマイズ無しには、lat と lng はそれぞれ同名のカスタムフィールドに別々に格納されますが、フィルターフックによるデータ加工により、コード中で指定された field_54899effa7dbe というAdvaced Custom FieldのGoogle Mapフィールドに配列で保存されます。
Really Simple CSV Importerに組み込まれているアクションフックを使うと、インポートした直後に投稿データを操作することができます。下記の作例では、インポート時にはいったん画像のURLをカスタムフィールドに保存しています。
このCSVをそのままインポートすると、image というカスタムフィールドに画像のURLが保存されます。アクションフックを使って、カスタムフィールドに保存されている画像のURLを読み込み、インターネットから取得してメディアにアップロードし、アップロードしたファイルを投稿に添付し、カスタムフィールドに保存されているURLを添付ファイルIDに変換、という処理を行なっています。
Really Simple CSV Importerプラグインに同梱されている RSCSV_Import_Post_Helper クラスは、単独でも便利に使っていただけると思います。
<?php // 必要ファイルのインクルード require_once ABSPATH . 'wp-content/plugins/really-simple-csv-importer/class-rscsv_import_post_helper.php'; // 投稿データの設定 $data = array( 'post_title' => '投稿タイトル', 'post_content' => '本文', 'post_type' => 'post', 'post_status' => 'publish' ); // データベースに追加 $h = RSCSV_Import_Post_Helper::add($data); // エラーの確認 if ($h->isError()) { // エラーメッセージの表示 echo implode(' ', $h->getError()->get_error_messages()); } else { // カスタムフィールドの追加 $meta = array( 'custom_field_key' => 'Custom Field Value' ); $h->setMeta($meta); // タグの追加 $tags = 'Hello, World'; $h->setPostTags($tags); // タクソノミーを指定してタームの追加 $terms = array( 'Lorem', 'Ipsum'); $h->setObjectTerms( 'category', $terms ); // アイキャッチ画像をアップロード $image_uri = 'http://example.com/example.png'; $h->addThumbnail( $image_uri ); echo 'インポート完了。'; }
<?php // 必要ファイルのインクルード require_once ABSPATH . 'wp-content/plugins/really-simple-csv-importer/class-rscsv_import_post_helper.php'; // 投稿データの設定 $data = array( 'post_title' => '投稿タイトル更新', 'post_content' => '本文更新', 'post_type' => 'post', 'post_status' => 'draft' ); // 投稿IDを指定して内容を更新 $h = RSCSV_Import_Post_Helper::getByID(123); $h->update($data); // エラーの確認 if ($h->isError()) { // エラーメッセージの表示 echo implode(' ', $h->getError()->get_error_messages()); } else { // 以下投稿の追加時と同じ $meta = array( 'custom_field_key' => 'Custom Field Value' ); $h->setMeta($meta); $tags = 'Hello, World'; $h->setPostTags($tags); $terms = array( 'Lorem', 'Ipsum'); $h->setObjectTerms( 'category', $terms ); $image_uri = 'http://example.com/example.png'; $h->addThumbnail( $image_uri ); echo '更新完了。'; }
Googleが、ウェブサイトがモバイルフレンドリーかどうかを、4月21日からモバイル検索でのランキング指標に使用する、と発表しました。
検索結果をもっとモバイル フレンドリーに | Google ウェブマスター向け公式ブログ
ウェブサイトがモバイルフレンドリーかどうかをモバイル検索結果で表示する変更はすでに行なわれていましたが、ランキングに影響するとあってにわかに騒がしくなってきたようです。
Googleは以前から、ウェブサイトをモバイルフレンドリーにする方法としてレスポンシブウェブデザイン(RWD)を推奨しています。Googleが公開しているモバイルSEOガイドでも、「Google では、デザインパターンとしてレスポンシブデザインを推奨しています」と明記されています。この記事では、GoogleがなぜRWDを推奨するのかを改めてまとめてみたいと思います。
さて、この発表後すぐにWebmaster Central office-hours hangoutでも、この件が取り上げられました。
冒頭のスライドで、モバイルユーザーがモバイルフレンドリーではない検索結果に不満を持っているという調査結果が示され、モバイルフレンドリーな検索結果の必要性について紹介されています。また、その後でRWDの利点も紹介されています。RWDが他の手法に比べて優れているのは、モバイルユーザーに見せるページとそれ以外のユーザーに見せるページの「URLが同じ」「HTMLが同じ」という2点です。
それでは、この2点がなぜ重要なのでしょうか。その理由は Google Developers サイトのモバイルSEOのページにて箇条書きで示されていますが、さらにその理由を分かりやすく「ユーザーの利便性」「運用コストの節約」「Googleの都合」の3つに分類して整理してみました。
URLが2つ以上あると、どちらをシェアしたらいいのか分かりにくいですよね。ただしこれが当てはまるのは、PCサイトとモバイルサイトを違うURLにする場合で、HTMLは別でも良いことになります。
PCサイトとモバイルサイトでURLが違う場合、ユーザーの端末に応じてリダイレクトが発生するため、その分RWDより読み込み時間が余計にかかります。また、PCサイトとモバイルサイトでHTMLが違う場合も、動的に出し分けるために余計に処理時間がかかります。
Googleがページの表示速度をランキングの指標に使用すると発表したことも記憶に新しいと思いますが、削れる処理時間は削ってしまいたいところです。
もし積極的にモバイルサイトのコンテンツを別に管理する理由がなければ、モバイル用のサイトがメインサイトと別にあることで、更新作業が単に二度手間と言うことになってしまいます。これはPCページとモバイルページを同時に生成できるCMSを導入すれば多少は改善するかもしれませんね。
モバイルユーザーがPCサイトにアクセスした場合は、モバイルサイト内の該当のページにリダイレクトする必要があります。もちろん、その逆もあります。PCサイトとモバイルサイトを分ける場合、このリダイレクトを適切にメンテナンスする必要性が発生します。
その他に起こりやすいミスについては、モバイルサイトのよくあるミスを回避するページにまとめられています。
PCからのアクセスなのか、モバイルからのアクセスなのかを判断する方法としては、ユーザーエージェントの解析に頼ることになります。ユーザーエージェントとは、ウェブページにアクセスする際にブラウザから送信される文字列で、どんな端末のどんなブラウザでアクセスしているかを自己申告するものです。
このユーザーエージェント文字列は、「私はスマホです」のような単純なものではありません。たとえば、多くのPHPアプリケーションで使用されているMobile Detectライブラリのソースコードを見ても、膨大なパターンが羅列されており、一見してメンテナンスの大変さが分かります。世の中にはもっと簡便に、PCとモバイルを見分けられるかのようなサンプルコードがたくさん落ちていますが、実際には正しくモバイルデバイスを見分けるコードのメンテナンスは大変です。単純なサンプルコードで実装している場合は、おそらく一部のモバイルデバイスにモバイル用のコンテンツが配信されていないことでしょう。
同じページでデバイスごとに複数のパターンがあると、ページのインデックスがやりにくい、ということのようです。完全にGoogleの都合ではありますが、Googleでも分からないことはユーザーにとっても分かりにくいと思います。特に、モバイルサイトに切り替えた場合に、モバイルサイトのトップページにリダイレクトするようなミスを犯している場合は、ユーザーは即離脱してしまうでしょう。
同じコンテンツなのにページが複数あると、Googlebotがクロールする手間がそれだけ増える、ということのようです。これもGoogleの都合ではありますが、作成したページがいち早くインデックスされるために、Googleの手間を減らしてあげるというのも必要なことなのかもしれません。
Googleは、img 要素の srcset 属性や、picture タグと言ったレスポンシブ画像や、SVGフォントのなどのテクニックも、すでに十分な後方互換性があり、推奨すべき手法として紹介しています。
マークアップ内の画像 | Web Fundamentals | Google Developers
アイコンに SVG を使用する | Web Fundamentals | Google Developers
このように、Googleが旗を振ることでRWDはいよいよ普及の段階に入ったのだなぁという印象です。2015年時点で、技術的にRWDを避ける理由はもはや無いと思います。
一方で、何が何でもRWDを採用しないといけない、という意味ではないことも強調しておきましょう。冒頭で紹介した動画でも言われていますが、今回の変更はモバイル検索結果でのランキングにのみ影響しますので、もしあなたのビジネスにおいてモバイルからの検索流入が不要と言うことであれば、モバイル対応も不要と言う結論になります。また、上記のRWDを採用すべき理由があったとしても、それを上回るだけのモバイルに最適化したユーザーインターフェースを別途用意する理由があることは否定しません。しかし、そのような理由がある場合は、ウェブサイトを2つ用意するよりは、モバイルアプリを検討すべきなのかなと思います。その際は、同時に発表されたApp Indexingが効果的でしょう。
この記事ではGoogleがソースの情報しか紹介していませんが、私が普段利用しているCMS「concrete5」も、レスポンシブなグリッドレイアウトを作成する機能が追加されたり、レスポンシブ画像に対応したりと、よりRWDへの親和性を高めていますので、Googleだけが言っているというわけではない世界的なRWDへの動きがあることを感じています。これらの機能を使ってモバイルフレンドリーなサイトを作るのが楽しみです!このまとめがRWDを提案する際の一助になれば幸いです。
No related posts.
WordPress4.2でまたもやWP_Query周りの仕様変更がありましたが、なぜかCodexに反映されないので先回りで情報共有です。4.1以前でも、下記のように meta_query
パラメーターに配列を渡すことで、複数のカスタムフィールドのキーで検索を行なうことができました。
下記のコードは、商品データベースの検索をイメージしたサンプルです。price
というキーのカスタムフィールドに入った価格情報とrelease
というキーのカスタムフィールドに入った発売日を組み合わせて、1000円以上、かつ2013年以降に発売された商品のみ取得しています(実際の構築においては、おそらく投稿タイプの指定も必要でしょうが、本稿の趣旨とは関係がないため省いています)。
<?php $args = array( 'meta_query' => array( 'relation' => 'AND', array( 'key' => 'price', 'value' => '1000', 'type' => 'numeric', 'compare' => '>' ), array( 'key' => 'release', 'value' => '2013-01-01', 'type' => 'date', 'compare' => '>' ) ) ); $query = new WP_Query( $args );
このコードで出力されるSQLは下記のようになります。wp_postmeta
テーブルを2回結合しており、2回目に結合した方は mt1
というエイリアスが付けられているのが分かります。
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts INNER JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id ) INNER JOIN wp_postmeta AS mt1 ON ( wp_posts.ID = mt1.post_id ) WHERE 1=1 AND ( ( wp_postmeta.meta_key = ‘price’ AND CAST(wp_postmeta.meta_value AS SIGNED) > ‘1000’ ) AND ( mt1.meta_key = ‘release’ AND CAST(mt1.meta_value AS DATE) > ‘2013-01-01′ ) ) AND wp_posts.post_type = ‘post’ AND (wp_posts.post_status = ‘publish’ OR wp_posts.post_status = ‘future’ OR wp_posts.post_status = ‘draft’ OR wp_posts.post_status = ‘pending’ OR wp_posts.post_status = ‘private’) GROUP BY wp_posts.ID ORDER BY wp_posts.post_date DESC LIMIT 0, 10
このような検索を行なう場合、まずブログ記事ということは無いでしょうから、ソートも同時に行ないたいことと思います。その場合は、orderby
パラメーターに meta_value
と指定するのが4.1までのやりかたでした。
<?php $args = array( 'meta_query' => array( 'relation' => 'AND', array( 'key' => 'price', 'value' => '1000', 'type' => 'numeric', 'compare' => '>' ), array( 'key' => 'release', 'value' => '2013-01-01', 'type' => 'date', 'compare' => '>' ) ), 'orderby' => 'meta_value' ); $query = new WP_Query( $args );
出力されるSQLは下記のようになります。ORDER BY句が変化したことが分かります。
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts INNER JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id ) INNER JOIN wp_postmeta AS mt1 ON ( wp_posts.ID = mt1.post_id ) WHERE 1=1 AND ( ( wp_postmeta.meta_key = ‘price’ AND CAST(wp_postmeta.meta_value AS SIGNED) > ‘1000’ ) AND ( mt1.meta_key = ‘release’ AND CAST(mt1.meta_value AS DATE) > ‘2013-01-01′ ) ) AND wp_posts.post_type = ‘post’ AND (wp_posts.post_status = ‘publish’ OR wp_posts.post_status = ‘future’ OR wp_posts.post_status = ‘draft’ OR wp_posts.post_status = ‘pending’ OR wp_posts.post_status = ‘private’) GROUP BY wp_posts.ID ORDER BY CAST(wp_postmeta.meta_value AS SIGNED) DESC LIMIT 0, 10
ここまで見てきて、じゃあソートキーを複合指定したい場合はどうするんだ、と思われると思いますが、その手段が4.1まではなかったんですね。4.2からは、meta_query
に渡す配列にキーを指定することで、ソートキーを明示することが可能になりました。
<?php $args = array( 'meta_query' => array( 'relation' => 'AND', 'meta_price' => array( 'key' => 'price', 'value' => '1000', 'type' => 'numeric', 'compare' => '>' ), 'meta_release' => array( 'key' => 'release', 'value' => '2013-01-01', 'type' => 'date', 'compare' => '>' ) ), 'orderby' => 'meta_release meta_price date' ); $query = new WP_Query( $args );
この記述で、リリース日の降順でまずソートし、同じリリース日であった場合は価格でソートし、さらに同じ価格であったら投稿日でソートするという複合指定が可能になります。出力されるSQLは下記のようになります。ORDER BY句に変化があることが分かると思います。また、配列で指定したキーは実際のSQLのエイリアスで使われることは無いと言うことにもご注目ください。てっきりエイリアス指定されるもんだと思っていました。
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts INNER JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id ) INNER JOIN wp_postmeta AS mt1 ON ( wp_posts.ID = mt1.post_id ) WHERE 1=1 AND ( ( wp_postmeta.meta_key = ‘price’ AND CAST(wp_postmeta.meta_value AS SIGNED) > ‘1000’ ) AND ( mt1.meta_key = ‘release’ AND CAST(mt1.meta_value AS DATE) > ‘2013-01-01′ ) ) AND wp_posts.post_type = ‘post’ AND (wp_posts.post_status = ‘publish’ OR wp_posts.post_status = ‘future’ OR wp_posts.post_status = ‘draft’ OR wp_posts.post_status = ‘pending’ OR wp_posts.post_status = ‘private’) GROUP BY wp_posts.ID ORDER BY CAST(mt1.meta_value AS DATE) DESC, CAST(wp_postmeta.meta_value AS SIGNED) DESC, wp_posts.post_date DESC LIMIT 0, 10
orderby
パラメーターを配列で指定すれば、昇順と降順を組み合わせることもできます。
<?php $args = array( 'meta_query' => array( 'relation' => 'AND', 'meta_price' => array( 'key' => 'price', 'value' => '1000', 'type' => 'numeric', 'compare' => '>' ), 'meta_release' => array( 'key' => 'release', 'value' => '2013-01-01', 'type' => 'date', 'compare' => '>' ) ), 'orderby' => array( 'meta_price' => 'asc', 'meta_release' => 'desc', 'date' => 'desc' ) ); $query = new WP_Query( $args );
この記述で生成されるSQLは下記のようになります。
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts INNER JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id ) INNER JOIN wp_postmeta AS mt1 ON ( wp_posts.ID = mt1.post_id ) WHERE 1=1 AND ( ( wp_postmeta.meta_key = ‘price’ AND CAST(wp_postmeta.meta_value AS SIGNED) > ‘1000’ ) AND ( mt1.meta_key = ‘release’ AND CAST(mt1.meta_value AS DATE) > ‘2013-01-01′ ) ) AND wp_posts.post_type = ‘post’ AND (wp_posts.post_status = ‘publish’ OR wp_posts.post_status = ‘future’ OR wp_posts.post_status = ‘draft’ OR wp_posts.post_status = ‘pending’ OR wp_posts.post_status = ‘private’) GROUP BY wp_posts.ID ORDER BY CAST(wp_postmeta.meta_value AS SIGNED) ASC, CAST(mt1.meta_value AS DATE) DESC, wp_posts.post_date DESC LIMIT 0, 10
WordPressのフォーラムでも、何度かソートの複合指定をしたい場合は、フィルターフックでSQLを直接いじくるしか方法がないという回答をしてきましたが、これでようやくフィルターフックの出番はほとんど無くなりそうです。