WordPress で任意の権限を持つ全てのユーザーを取得したいという要件があって、以下のコードを書きました。
カテゴリー: ブログ
WordPress が発行している SQL を確認する
※下に注釈有り
WordPress のカスタマイズを行っていると、WordPress がどのような SQL を発行しているか確認したい場面があったので、次のようなスクリプトを書きました。
CakePHP 2.x で Entity オブジェクトを使えるプラグイン — FkRecordModel
昨年ぐらいから CakePHP 2 を本格的に触ることになりまして、これまでの Paper ドライバーを卒業と喜んでいたんですが、いきなりつまづいた…というか不満に思ったのが Model の構造でした。CakePHP も 2 となり、もうてっきり Rails の ActiveRecord のようなものになっているかと思いきや、find メソッドが返すものは相変わらず array だったんですね。これはもう思想なのかも?…とはいえ、Paper ドライバーレベルではなかなか思想まで理解できません。同じような不満が出ていないかと検索してみたところ、次の記事が見つかりました。
同じことを思う方はいるもので♪ 早速、上のプラグインを利用させていただいたのですが、どうも CakePHP 2 系では上手く動かないようでしたので、修正を試みたものの、挫折。せっかくだからということで、自分で作ることにしました。これでも自称フレームワークマニアですしねw
その成果物がここで紹介する FkRecordModel です。GitHub に公開中です。
次のような簡単な構成になっています。
- FkRecord エンティティオブジェクト。find メソッドなどの戻り値などになります。
- FkRecordCollection findAll などの戻り値となります。
- FkRecordModel 拡張された Model クラスです。FkRecord 生成の為に find などをオーバーライドしています。
次のような機能があります(特に高機能ではありません)。
- FkRecord::save() で保存
- FkRecord::validates() でバリデーション
- FkRecordModel::build() 新しい FkRecord のビルド
- エンティティフィールドのシンプルなアクセス。Ex. $post->title
- Validation error は FkRecord が保持し、View で参照可能
- HTML のラベルの表示などに利用できるフィールド別名のサポート(VerboseName)
- リレーションを FkRecordCollection で取得。Ex. $post->tags //hasMany のデータの取得
上述の CakeEntity プラグインとの相違点は多々とあると思いますが、とりあえず大きなところで、本プラグインは「CakePHP の通常通り配列を返す」という選択肢を設けていません。find 系メソッドは常に FkRecord または FkRecordCollection を返します。(自分はたぶん使わないからです。ごめんなさい)。また、FkRecordCollection は不要だったり邪魔だと思われる方も我慢してください(イテレータにしておくと色々と便利という信条です)。ちなみに、FkRecordCollection::sort() メソッドには不満があります(削除または改良したいです。どなたか良い案ください)。
以下、簡単な利用例です。
AppModel の定義
まずは以下のように AppModel の super クラスとして本プラグインのクラスを利用します。AppModel の他に、AppRecord も作成してください。FkRecordCollection は全てのモデルで共通に利用されます。
1 2 3 4 5 6 7 8 9 10 11 |
<?php App::uses('FkRecordModel', 'FkRecordModel.Model'); class AppModel extends FkRecordModel { // definition... } class AppRecord extends FkRecord { // definition... } |
具象モデルクラスの定義
モデルクラスでは、それぞれのモデル毎に、Model と Record を定義します。Model の定義は通常通りですが、verboseName などの指定ができます。Record にはエンティティオブジェクトで提供したい機能を実装してください。自らのフィールドへのアクセスは、$this->fieldName または $this[$alias][$fieldName] です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<?php App::uses('AppModel', 'Model'); class Post extends AppModel { /** * Define verbose name. It is used in the label name, such as, for example. */ var $verboseName = array( 'title' => 'Title', 'description' => 'Description', ); var $hasMany = array( 'tags' => array( 'className' => 'PostTag', 'dependent' => true, ), ); // Other definition... } class PostRecord extends AppRecord { function updateDateBy($format) { return date($format, strtotime($this->modified)); } } |
Controller での利用例
Controller での利用は次のような雰囲気になります。$post->save() のようにして保存します。FkRecord::save() メソッドは、バリデーションエラーがある時には、false を返します。バリデーションエラーの内容はエンティティオブジェクトが保持し、これは View の中で利用できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<?php class PostsController extends AppController { $uses = array('Post'); function index() { $posts = $this->Post->find('all'); $this->set(compact('posts')); } function show($id) { $post = $this->Post->findById($id); $this->set(compact('post')); } function create() { $post = $this->Post->build(); $this->set(compact('post')); if ($this->request->is('post')) { $post->setData($this->data); if ($post->save()) { // processing on success. } else { // processing on failed. } } } function edit($id) { $post = $this->Post->findById($id); $this->set(compact('post')); if ($this->request->is('put')) { $post->setData($this->data); if ($post->save()) { // processing on success. } else { // processing on failed. } } } } |
View での利用例(一覧表示)
FkRecordModel に定義された関連は、$post->tags のようにして FkRecordCollection/FkRecord として取得できます。なお、FkRecordCollection は empty($posts) のように空判定できません。FkRecordCollection::isEmpty() メソッドを利用してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?php foreach ($posts as $post) : ?> <article id="post-<?php echo $post->getID() ?>"> <h1><?php echo $post->title ?></h1> <p><?php echo $post->description ?></p> <p>Updated at: <?php echo $post->updateDateBy('Y/m/s H:i') ?></p> <?php if (! $post->tags->isEmpty()) : ?> <ul> <?php foreach ($post->tags as $tag) : /* loop for relations */?> <li><?php echo $tag->name ?></li> <?php endforeach; # $post->tags ?> </ul> <?php endif; # !$post->tags->isEmpty() ?> </article> <?php endforeach; # $posts ?> |
View での利用例(編集画面)
フォームを扱うには、FkRecord::bindFormHelper() メソッドで FormHelper をエンティティにバインドし、以後、$post->Form->text($fieldName) といった感じでフォームエレメントを出力できます。FkRecord::getVerboseName($fieldName) でフィールドの別名を取得できます。FkRecord::getError($fieldName) で、そのフィールドにエラーがあれば、その内容を取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?php $post->bindFormHelper($this->Form) /* Bind to FormHelper */ ?> <?php echo $post->Form->create('Post') /* Start form */ ?> ?> <label> <?php echo $post->getVerboseName('title') ?>: <?php echo $post->Form->text('title') ?> <?php echo $post->getError('title') ?> </label> <label> <?php echo $post->getVerboseName('description') ?>: <?php echo $post->Form->textarea('decription') ?> <?php echo $post->getError('description') ?> </label> <p> <?php echo $post->submit('Send') ?> </p> <?php echo $post->Form->end() ?> |
以上、簡単にですがご紹介まで。
もし奇特な方がいらっしゃいましたら、一度ご利用いただき、是非フィードバックなどを頂けたら嬉しいです! 是非! 是非!
JavaScript の超便利なメソッド bind で this を制御する
JavaScript の関数オブジェクトには、「bind」という便利なメソッドがあります。このメソッドは関数内で参照できる this を指定のオブジェクトに束縛できるものです。この関数を使うと、this に関連した良く有る問題をスマートに解決できます。
this にまつわるよくある問題
次の例は、実行後に「こんにちわ ぷんちゃん」とアラートするコードです。
1 2 3 4 5 6 7 8 9 |
var Person = function (name) { this.name = name; } Person.prototype.sayHello = function() { alert('こんにちわ ' + this.name); } var punchan = new Person('プンちゃん'); punchan.sayHello(); //「こんにちわ プンちゃん」と表示される |
では、3秒後に表示させてみましょう。よく次のように書きます。
1 2 3 4 5 6 7 8 9 10 11 12 |
var Person = function (name) { this.name = name; } Person.prototype.sayHello = function() { alert('こんにちわ ' + this.name); } var punchan = new Person('ぷんちゃん'); setTimeout(function() { punchan.sayHello(); //3秒後に「こんにちわ プンちゃん」と表示される }, 3000) |
これは期待通りに動作します。しかし、setTimeout に渡している無名関数は、単に Person.sayHello() をコールしているだけなので、次のようにも書けそうです。
1 |
setTimeout(punchan.sayHello, 3000); |
しかし、これは期待通りには動作しません。
通常、ブラウザで実行させている時には「こんにちわ undefined」と表示されます。
これは JavaScrip における一般的な動作です。オブジェクトのメソッドはオブジェクトに束縛されているものではなく、その時々のコンテキストにおいて実行されるからです。JavaScript をある程度理解しているプログラマは、このことを良くご存知のはずです。
bind を使って this を束縛する
さて、本題です。
この this を固定してしまえるメソッド「bind」が関数オブジェクトには存在します。
例を見てみます。
1 2 3 4 5 6 7 8 9 10 |
var Person = function (name) { this.name = name; } Person.prototype.sayHello = function() { alert('こんにちわ ' + this.name); } var punchan = new Person('ぷんちゃん'); setTimeout(punchan.sayHello.bind(punchan), 3000); //3秒後に「こんにちわ プンちゃん」と表示される |
今度は期待通り動作しました。
bind メソッドはこのように、関数内の this を指定のオブジェクトに束縛した新しい関数を返す機能を提供します。上の例では、bind に punchan オブジェクトを与えて、sayHello メソッド内の this が常に punchan オブジェクトとなるような新しい関数を取得し、それを setTimeout に渡すことで、期待通りの動作を実現しました。
このように、bind を使うことで、関数実行時の this を簡単に束縛できます。JavaScript でオブジェクトを利用したスクリプトを書く時にはとても便利ですね!
別のよくありそうな例
もうひとつ、良く有りそうな例を。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var Speaker = { say: function(wordsGetter) { var words = wordsGetter(); alert(words); } } function Person(nickname) { this.nickname = nickname; } Person.prototype.sayName = function() { Speaker.say(function() { return this.nickname; //この this は Person オブジェクトでは無い! }); }; var person = new Person('Gyu-ri'); person.sayName(); //undefinend |
14行目で this を参照していますが、この時の this は Person オブジェクトでは無くて、グローバルオブジェクト(ブラウザなら通常 window オブジェクト)になります(ちなみにその this が決定されているのは、3行目の呼び出し部です)。
14 行目の this の部分で、Person オブジェクトを参照したい時に、よく次のようにするでしょう。
12 13 14 15 16 17 |
Person.prototype.sayName = function() { var self = this; Speaker.say(function() { return self.nickname; }); }; |
13 行目で期待する this の値を一旦別の変数に移して、その変数を 15 行目で参照しています。これは上手く行きます。しかし、関数オブジェクトの bind を使うと、次のようにも書けます。
12 13 14 15 16 |
Person.prototype.sayName = function() { Speaker.say(function() { return this.nickname; }.bind(this)); }; |
15 行目で bind を使って、13〜15 行目に定義した無名関数オブジェクト内での this を指定しています。このケースではこの方がスマートですね!
読みやすさについてはケースバイケースだと思いますが、これは JavaScript でオブジェクトを扱う時の基本テクニックの一つです。このような bind の用法を知っておくことで、より読みやすいコードを書ける場面も増えると思います。
ちなみに、bind にはどんなオブジェクトでも渡すことができます。ここでは this を渡していますが、必要に応じて、様々なオブジェクトを、関数内の this として bind できるということを覚えておいてください。
bind による引数の部分適用
実は bind は this だけではなく関数の引数も束縛することが出来ます。
次の例を見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var mul = function(a, b) { alert(a * b); } mul(2, 3); //6とアラートされる //bindで引数を束縛してみる var mul2 = mul.bind(null, 2); mul2(4); //8とアラートされる mul2(16); //32とアラートされる //更にbindで引数を束縛してみる var mul2x5 = mul2.bind(null, 5); mul2x5(); //10とアラートされる |
4 行目の mul(2, 3) は、単純な関数呼び出しです。6とアラートされます。
7 行目で bind を利用して、引数の一つを 2 に束縛した新しい関数 mul2 を得ています。
8、9 行目の意味が分かるでしょうか? 関数 mul2 は、関数 mul の最初の引数が 2 に束縛されているものなので、常に 2 * b の演算を行ってアラートする関数になります。結果、ここではそれぞれ 2 * 4 ⇒ 8、2 * 16 ⇒ 32 というアラートが表示されます(ちなみに、前述の通り bind の第1引数には this とするオブジェクトを指定しますが、ここでは this は使っていないため null としています)。
また、11 行目からのように、引数が部分適用された関数を、更に部分適用する事もできます。
mul.bind(null, 2) で返される関数と等価なコードを書くと、次のようになります。
1 2 3 |
var mul2 = function(b) { mul(2, b); } |
使いどころが難しい機能かも知れませんが、ライブラリなどの汎用性を高める為に利用することが出来そうですし、アイデア次第、使い方次第、ということになると思います。
引数の部分適用の例1
引数の部分適用の例を挙げてみます。
1 2 3 4 5 6 7 |
post.tags.remove(tag) .done(function() { alert('タグ「' + tag.name + '」を削除しました。'); }) .fail(function() { alert('何か問題が発生して、処理に失敗しました。'); }); |
これは tags というコレクションオブジェクトから任意の tag を削除するコードです。tags.remove メソッドは jQuery.Deferred オブジェクトを返し、tag の削除に成功した時に done、失敗した時に fail にセットされたコールバック関数が呼ばれるというコードです。ここでコールバックでは alert を表示することしかしていません。これを bind を使って書き換えると、次のように書けます。
1 2 3 |
post.tags.remove(tag) .done(alert.bind(window, 'タグ「' + tag.name + '」を削除しました。')) .fail(alert.bind(window, '何か問題が発生して、処理に失敗しました。')); |
如何でしょうか? この例が見やすいかどかは人それぞれだと思いますが、このように使えるということを覚えていると、きっと何かの時に役立つかと思います。
もう一つ、もう少し使いどころがありそうな例を挙げてみます。
引数の部分適用の例2
次のコードは、1秒毎に3回 alert がコールされて「○秒後!」と表示します。もちろん、「1秒後!」「2秒後!」「3秒後!」となることを期待していますが、これは3回とも「4秒後!」と表示されてしまいます。クロージャ変数についてよくある問題ですね。
1 2 3 4 5 |
for (var i = 0; i < 3; i++) { setTimeout(function() { alert((i + 1) + '秒後!'); }, 1000 * i); } |
これは例えば次のようにして期待通りの動作をさせることが出来ます。
1 2 3 4 5 6 7 |
for (var i = 0; i < 3; i++) { setTimeout(function(second) { return function() { alert(second + '秒後!'); }; }(i + 1), 1000 * i); } |
ただ、ちょっとややこしいですね…。
そこで、bind による引数の部分適用を利用すると、次のように書くことができます。
1 2 3 4 5 |
for (var i = 0; i < 3; i++) { setTimeout(function(second) { alert(second + '秒後!'); }.bind(null, i + 1), 1000 * i); } |
これはスッキリして、またしっくり来るのではないでしょうか (*’-‘*)
以上です。
ご存知の方はご存知というお話ですが、僕はつい半年位前まで知らなかったもので共有させて頂きました。
WordPress のマルチサイトで、ネットワークブログの投稿を一覧表示する
[2013-07-12 追加] 公式プラグインディレクトリに「WP Over Network」をリリースしました。管理画面からインストールできます。とりあえずの日本語の紹介はコチラのページになります。
[2013-06-07 追記] GitHub にプラグイン化したものをアップしています。良かったらご利用ください。フォーク歓迎。https://github.com/yuka2py/wp_over_network
WordPress をネットワーク化してマルチサイトを運用する際、ネットワーク上の各ブログの記事の更新情報をホストサイトのホームページなどに一覧表示させるという要件があると思います。
@HissyNC さんの「WordPressマルチサイトネットワークから新着記事を取得するコード(修正版)」を参考にして考えてみましたが、今回はネットワークサイトの更新情報をまとめたアーカイブページを持つ必要があったため、かわりに以下のような方法を行ってみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
<?php /** * 投稿日順で、ネットワーク上の全てのブログの投稿を取得する。 * @param mixed $args * numberposts 取得する投稿数。デフォルトは 5 * offset 取得する投稿のオフセット。デフォルトは false で指定無し。指定すると、paged より優先。 * paged 取得する投稿のページ数。get_query_var( 'paged' ) の値または1のいずれか大きな方。 * @return array<stdClass> */ function get_posts_over_network( $args=null ) { global $wpdb; $args = wp_parse_args( $args, array( 'numberposts' => 5, 'offset' => false, 'paged' => max( 1, get_query_var( 'paged' ) ), ) ); extract( $args ); //ページ指定とオフセットの調整 if ( $offset === false ) { $offset = ( $paged - 1 ) * $numberposts; } //《1》ネットワーク上のブログの一覧を取得 $query = sprintf( 'SELECT * FROM %sblogs', $wpdb->prefix ); $blogs = $wpdb->get_results( $query ); //《2》各ブログの投稿データを取得するサブクエリの準備 $sub_queries = array(); foreach ( $blogs as $blog ) { $blog_prefix = ( $blog->blog_id == 1 ) ? '' : $blog->blog_id . '_'; $sub_queries[] = implode(' ', array( sprintf( 'SELECT %3$d as blog_id, %1$s%2$sposts.* FROM %1$s%2$sposts', $wpdb->prefix, $blog_prefix, $blog->blog_id ), $wpdb->prepare('WHERE post_type = %s AND post_status = %s', 'post', 'publish'), )); } //《3》クエリの組み立て $query = array(); $query[] = 'SELECT SQL_CALC_FOUND_ROWS *'; $query[] = sprintf( 'FROM (%s) as posts', implode( ' UNION ALL ', $sub_queries ) ); $query[] = 'ORDER BY post_date DESC'; $query[] = sprintf( 'LIMIT %d, %d', $offset, $numberposts ); $query = implode( ' ', $query ); //《4》問い合わせの実行 $posts = $wpdb->get_results( $query ); $foundRows = $wpdb->get_results( 'SELECT FOUND_ROWS() as count' ); $foundRows = $foundRows[0]->count; //《5》wp_query の書き換え global $wp_query; $wp_query->query_vars['posts_per_page'] = $numberposts; $wp_query->found_posts = $foundRows; $wp_query->max_num_pages = ceil( $foundRows / $numberposts ); return $posts; } |
以下に簡単に解説します。
- 《1》で wp_blogs テーブルよりブログの一覧を取得
- 《2》で《1》で取得したブログから投稿を取得するサブクエリを準備
- 《3》でクエリ全体を組み立て
- 《4》で記事データと、総件数を取得
- 《5》で wp_query の変数の一部を書き換え
大きなポイントは、《3》で各サイトの投稿テーブルを UNION ALL してしまうことと、《5》で wp_query を書き換えることです。《5》を行う事によって、wp_pagenavi などのプラグインでページナビゲーションの表示が可能になります。なかなか泥臭いことをやっていますね。 (; ^ω^)
上記の関数は、例えば次のようにして利用できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
<?php get_header(); the_post(); ?> <section id="content-primary"> <header id="page-header"> <h1><?php the_title() ?></h1> </header> <?php $posts = get_posts_over_network(); wp_pagenavi(); ?> <?php if ( ! empty ($posts) ) : ?> <section class="post-list"> <?php foreach ( $posts as $post ) : switch_to_blog( $post->blog_id ); setup_postdata( $post ) ?> <section id="post-<?php the_ID() ?>" <?php post_class() ?>> <h2>【<?php echo get_bloginfo( $post->blog_id ) ?>】</h2> <h1><a href="<?php the_permalink() ?>"><?php the_title() ?></a></h1> <?php echo get_the_excerpt() ?> </section> <?php restore_current_blog(); endforeach; ?> </section> <?php else : ?> <p>投稿がありません。</p> <?php endif; # End of empty( $posts ) ?> </section> <?php wp_reset_query(); get_sidebar(); get_footer(); |
このサンプルは固定ページを準備して、固定ページのテンプレートに直接上記のコードを書いて、ネットワークブログのアーカイブページとして表示させるイメージです。
- 15行目で投稿データを取得
- 17行目で、wp_pagenavi を呼び出してページャーを表示しています。
- 24行目でブログを切り替え、25行目で投稿データをセットアップ
- 33行目でブログをカレントブログに戻します。
これで wp_pagenavi によるページナビゲーションもちゃんと表示されます。もちろんページの移動も可能です。
§
以下は、汎用的に使えるように幾つかオプションを足して、整理したものです。ご参考まで。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
<?php class network { /** * ネットワーク上の投稿データを取得します。 * @param mixed $args * numberposts 取得する投稿数。デフォルトは 5 * offset 取得する投稿のオフセット。デフォルトは false で指定無し。指定すると、paged より優先。 * paged 取得する投稿のページ数。get_query_var( 'paged' ) の値または1のいずれか大きな方。 * post_type 取得する投稿タイプ。デフォルトは post * orderby 並び替え対象。デフォルトは post_date * order 並び替え順。デフォルトは DESC で降順 * post_status 投稿のステータス。デフォルトは publish * blog_ids 取得するブログのIDを指定。デフォルトは null で指定無し * exclude_blog_ids 除外するブログのIDを指定。デフォルトは null で指定無し * affect_wp_query wp_query を書き換えるか否か。デフォルトは false で書き換えない。wp_pagenavi など wp_query を参照するページャープラグインの利用時には true とする * @return array<stdClass> */ function get_posts( $args=null ) { global $wpdb; $args = wp_parse_args( $args, array( 'numberposts' => 5, 'offset' => false, 'paged' => max( 1, get_query_var( 'paged' ) ), 'post_type' => 'post', 'orderby' => 'post_date', 'order' => 'DESC', 'post_status' => 'publish', 'blog_ids' => null, 'exclude_blog_ids' => null, 'affect_wp_query' => false, ) ); extract( $args ); //ページ指定とオフセットの調整 if ( $offset === false ) { $offset = ( $paged - 1 ) * $numberposts; } //ブログの一覧を取得 $blogs = self::get_blogs( compact( 'blog_ids', 'exclude_blog_ids' ) ); //投稿データを取得するサブクエリの準備 $sub_queries = array(); foreach ( $blogs as $blog ) { $blog_prefix = ( $blog->blog_id == 1 ) ? '' : $blog->blog_id . '_'; $sub_queries[] = implode(' ', array( sprintf( 'SELECT %3$d as blog_id, %1$s%2$sposts.* FROM %1$s%2$sposts', $wpdb->prefix, $blog_prefix, $blog->blog_id ), $wpdb->prepare('WHERE post_type = %s AND post_status = %s', $post_type, $post_status), )); } //クエリの組み立て $query[] = 'SELECT SQL_CALC_FOUND_ROWS *'; $query[] = sprintf( 'FROM (%s) as posts', implode( ' UNION ALL ', $sub_queries ) ); $query[] = sprintf( 'ORDER BY %s %s', $orderby, $order ); $query[] = sprintf( 'LIMIT %d, %d', $offset, $numberposts ); $query = implode( ' ', $query ); //問い合わせの実行 global $wpdb; $posts = $wpdb->get_results( $query ); $foundRows = $wpdb->get_results( 'SELECT FOUND_ROWS() as count' ); $foundRows = $foundRows[0]->count; //wp_query の書き換え if ( $affect_wp_query ) { global $wp_query; $wp_query->query_vars['posts_per_page'] = $numberposts; $wp_query->found_posts = $foundRows; $wp_query->max_num_pages = ceil( $foundRows / $numberposts ); } return $posts; } /** * ブログの一覧を取得する。 * 返される各ブログの情報を持つオブジェクトは、ブログ名とその Home URL を含む。 * @param mixed $args * blog_ids 取得するブログのIDを指定。デフォルトは null で指定無し * exclude_blog_ids 除外するブログのIDを指定。デフォルトは null で指定無し * @return array<stdClass> */ function get_blogs( $args=null ) { global $wpdb; $args = wp_parse_args( $args, array( 'blog_ids' => null, 'exclude_blog_ids' => null, ) ); extract( $args ); //必要に応じて、where 句を準備 $where = array(); if ( $blog_ids ) { if ( is_array( $blog_ids ) ) { $blog_ids = array_map( 'intval', (array) $blog_ids ); $blog_ids = implode( ',', $blog_ids ); } $where[] = sprintf( 'blog_id IN (%s)', $blog_ids ); } if ( $exclude_blog_ids ) { if ( is_array( $exclude_blog_ids ) ) { $exclude_blog_ids = array_map( 'intval', (array) $exclude_blog_ids ); $exclude_blog_ids = implode( ',', $exclude_blog_ids ); } $where[] = sprintf( 'blog_id NOT IN (%s)', $exclude_blog_ids ); } //クエリの組み立て $query[] = sprintf( 'SELECT * FROM %sblogs', $wpdb->prefix ); if ( $where ) { $query[] = "WHERE " . implode(' AND ', $where); } $query[] = 'ORDER BY blog_id'; $query = implode( ' ', $query ); //問い合わせの実行 $blogs = $wpdb->get_results( $query ); //各ブログの情報を取得 foreach ( $blogs as &$blog ) { switch_to_blog( $blog->blog_id ); $blog->name = get_bloginfo('name'); $blog->home_url = get_home_url(); restore_current_blog(); } return $blogs; } /** * 投稿データをブログとともにセットアップする。 * 内部的に switch_to_blog を使っているので、呼び出した後の処理が終わったら、 * restore_current_blog() を都度コールする * @param array $post 投稿データ。$post->blog_id を保持していること。 * @return void */ function setup_postdata_and_switch_to_blog( $post ) { switch_to_blog( $post->blog_id ); $post->blog_name = get_bloginfo( 'name' ); $post->blog_home_url = get_home_url(); setup_postdata( $post ); } } |
上記は、最初のサンプルとほぼ同様に、次のようにして利用できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
<?php get_header(); the_post(); ?> <section id="content-primary"> <header id="page-header"> <h1><?php the_title() ?></h1> </header> <?php //ホストブログを除いた一覧の取得。wp_pagenavi を利用するため、affect_wp_query=true とする $posts = network::get_posts('exclude_blog_ids=1&affect_wp_query=true'); wp_pagenavi(); ?> <?php if ( ! empty ($posts) ) : ?> <section class="post-list"> <?php foreach ( $posts as $post ) : network::setup_postdata_and_switch_to_blog( $post ); ?> <section id="post-<?php the_ID() ?>" <?php post_class() ?>> <h2>【<?php echo $post->blog_name ?>】</h2> <h1><a href="<?php the_permalink() ?>"><?php the_title() ?></a></h1> <?php echo get_the_excerpt() ?> </section> <?php restore_current_blog(); endforeach; ?> </section> <?php else : ?> <p>投稿がありません。</p> <?php endif; # End of empty( $posts ) ?> </section> <?php wp_reset_query(); get_sidebar(); get_footer(); |
以上です。