WordPress で階層化コンテンツ管理を補助するためのプラグインを作りました。
記念にソースを公開しておきますので、自分でプラグインを作ってみたい方は参考にしてみてください。
/* Plugin Name: Hierarchical document Plugin URI: http://www.game-create.com/ Description: WordPress で階層化コンテンツを管理するために必要な関数群を提供します。 Author: Byerkut Version: 1.0 Author URI: http://www.game-create.com/menu/profile */ /** * 階層化コンテンツを管理するために必要なメソッドを提供する */ class HierarchicalDocument { /** * デバッグ情報 * @access private * @var mixed */ var $__debug; /** * デバッグ情報を保存する * * @params mixed $debugInfo デバッグ情報 */ function debug($debugInfo) { $this->__debug = $debugInfo; } /** * デバッグ情報を取得する * * @return mixed デバッグ情報 */ function getDebug() { return $this->__debug; } /** * 指定した記事のオブジェクトを返す * * @access public * @param integer $id 取得する記事の ID * @return object 記事 */ function getPost($id = null) { if (is_null($id)) { return $this->getCurrentPost(); } global $wpdb; $sql = sprintf("SELECT %s FROM %s WHERE %s", "*", $wpdb->posts, "ID = ". $id); $post = $wpdb->get_row($sql, OBJECT); return $post; } /** * 現在のクエリが特定している記事のオブジェクトを返す * * @access public * @return object 現在の記事 */ function getCurrentPost() { global $wp_query; // 記事でもページでもない場合は戻る if (!is_single() && !is_page()) { return false; } // 記事が取得できていない場合は戻る if (!isset($wp_query->post)) { return false; } // 記事が1つに絞られていない場合は戻る if (!is_object($wp_query->post)) { return false; } return $wp_query->post; } /** * 前の記事を取得する * * @access public * @return object 前の記事 */ function getPreviousPost() { if (($post = $this->getCurrentPost()) == false) { return 0; } global $wpdb; $where = array("menu_order <". $post->menu_order, "post_parent = ". $post->post_parent, "post_status = 'publish'"); $sql = sprintf("SELECT %s FROM %s WHERE %s ORDER BY %s LIMIT 1", "*", $wpdb->posts, "(". join(") AND (", $where). ")", "menu_order DESC"); $post = $wpdb->get_row($sql, OBJECT); return $post; } /** * 次の記事を取得する * * @access public * @return object 次の記事 */ function getNextPost() { if (($post = $this->getCurrentPost()) == false) { return 0; } global $wpdb; $where = array("menu_order > ". $post->menu_order, "post_parent = ". $post->post_parent, "post_status = 'publish'"); $sql = sprintf("SELECT %s FROM %s WHERE %s ORDER BY %s LIMIT 1", "*", $wpdb->posts, "(". join(") AND (", $where). ")", "menu_order ASC"); $post = $wpdb->get_row($sql, OBJECT); return $post; } /** * 現在の記事が所属するトップ記事の ID を取得する * * @access public * @param integer $topParentID トップ記事の親 ID * @return integer トップ記事の ID */ function getTopPostID($topParentID = 0) { if (($post = $this->getCurrentPost()) == false) { return 0; } // 親がルートでない場合は再帰的に取得 if ($topParentID != $post->post_parent) { return $this->__findTopPostID($topParentID, $post->post_parent); } return $post->ID; } /** * HierarchicalDocument::getTopPostID() の内部メソッド * * 指定した ID の親記事を検索する * * @access private * @param integer $topParentID トップ記事の親 ID * @param integer $targetID 親記事の ID * @return integer トップ記事の ID */ function __findTopPostID($topParentID, $target_id) { global $wpdb; // てっぺんにたどり着いてしまったら戻る if ($target_id == 0) { return 0; } // 親ページの親 ID を求める $sql = sprintf("SELECT %s FROM %s WHERE %s", "post_parent", $wpdb->posts, "ID = ". $target_id); $post_parent = $wpdb->get_var($sql); // 親がルートでない場合は再帰的に取得 if ($topParentID != $post_parent) { return $this->__findTopPostID($topParentID, $post_parent); } return $target_id; } /** * 記事が子供の記事を持っているかの真偽値を取得する * * @access public * @param integer $id 対象の記事の ID * @return boolean 子供の記事を持っているかの真偽値 */ function hasChildren($id = null) { $post = (is_null($id)) ? $this->getCurrentPost(): $this->getPost($id); if (empty($post)) { return false; } global $wpdb; $where = array("post_parent = ". $post->ID, "post_status = 'publish'"); $sql = sprintf("SELECT %s FROM %s WHERE %s", "count(*)", $wpdb->posts, "(". join(") AND (", $where). ")"); $children = $wpdb->get_var($sql); return (0 <$children); } /** * 記事の子供となる記事のリストを取得する * * @access public * @param integer $id 対象の記事の ID * @return array 子供記事のリスト */ function getChildren($id = null) { $post = (is_null($id)) ? $this->getCurrentPost(): $this->getPost($id); if (empty($post)) { return array(); } global $wpdb; $where = array("post_parent = ". $post->ID, "post_status = 'publish'"); $sql = sprintf("SELECT %s FROM %s WHERE %s ORDER BY %s", "*", $wpdb->posts, "(". join(") AND (", $where). ")", "menu_order ASC"); $children = $wpdb->get_results($sql, OBJECT); return $children; } /** * 記事中の [child_post_list] を子供記事のリストに展開する * * the_content のフィルタ * * @access public * @param integer $id 対象の記事の ID * @return string 子供記事リストのタグ */ function getChildPostList($id = null) { // 子供がいない場合は戻る if (!$this->hasChildren($id)) { return null; } // 子供を取得する $children = $this->getChildren($id); // データを整形する $output = "<ul>\n"; foreach ($children as $child) { $format = '<li class="%s"><a href="%s">%s</a></li>'; $output .= sprintf($format. "\n", "page_item page-item-". $child->ID, get_permalink($child->ID), $child->post_title); } $output .= "</ul>\n"; return $output; } } /** * 記事中の [child_post_list] を子供記事のリストに展開する * * the_content のフィルタ * * @access public * @params string $content 記事の本文 * @return string 更新した記事の本文 */ function expandChildPostList($content) { if ($list = $GLOBALS["hrDoc"]->getChildPostList()) { $content = preg_replace('/\[child_post_list\]/', $list, $content); } return $content; } /** * 自動的に生成する HierarchicalDocument のインスタンス * @global object $hrDoc */ $GLOBALS["hrDoc"] =& new HierarchicalDocument(); // フィルタ追加 add_filter("the_content", "expandChildPostList");
重要な部分を解説していきます。
/* Plugin Name: Hierarchical document Plugin URI: http://www.game-create.com/ Description: WordPress で階層化コンテンツを管理するために必要な関数群を提供します。 Author: Byerkut Version: 1.0 Author URI: http://www.game-create.com/menu/profile */
これはプラグインを認識するために必要なコメントです。ソースの先頭に必ず書いてください。意味は何となくわかると思います。この情報が管理画面に出てきますので、最初はちょっと感動します。
/** * 階層化コンテンツを管理するために必要なメソッドを提供する */ class HierarchicalDocument { }
これは必要ではありません。クラスによってプラグインの機能を実装したかったので作りました。このようにプラグインの中には自由にクラスや関数を作ることができます。
/** * 自動的に生成する HierarchicalDocument のインスタンス * @global object $hrDoc */ $GLOBALS["hrDoc"] =& new HierarchicalDocument();
ここは少し重要です。このコードはプラグインが有効になると WordPress の初期化の段階で実行されるようになります。つまり require_once() されるのと同じイメージになります。このコードは関数やメソッドの外に書いてあるわけですので require_once() されれば自動的に実行されるというわけです。
なぜグローバル変数を作っているのかというと、このグローバル変数があるかないかによって、テンプレート内からプラグインが有効になっているかなっていないかを判断するためです。次のようにして使います。
if (isset($GLOBALS["hrDoc"])) { // プラグインが有効になっているときのみプラグインの機能を使う }
この判断は必須ではありませんが、プラグインを無効にした際にエラーに遭遇する可能性があります。また、プラグインの動作が後述のフィルタやアクションで完結する場合はこの判断は不要です(というか書く場所がありません)。
// フィルタ追加 add_filter("the_content", "expandChildPostList");
ここもキモです。このコードによって WordPress が公開するプログラムのフックポイントにユーザコードを登録します。第1引数にはフックポイントの名前、第2引数にはフックしたい関数の名前を指定します。もし、クラスのメソッドを指定したい場合は array(&$instance, “methodName”) のように配列で指定します。
フックポイントにはアクションとフィルタの2種類あります。アクションとは新規記事の保存やメールの送信など、なんらかの動作(変化)を伴う機能に対するフックポイントです。フィルタとは記事の内容表示やコメントの内容表示など、表示の機能に対するフックポイントです。
今回の例にある add_filter() はフィルタにユーザコードを登録する関数で、アクションにユーザコードを登録するためには add_action() を使います。
アクションとフィルタのフックポイントの一覧は次のページにまとめられています。英語ですが、なんとなく読めると思います。
add_action() や add_filter() によって登録された関数やメソッドが WordPress から呼ばれる際に引数を伴う場合があります。今回は the_content というフックポイントに expandChildPostList() という関数を登録していますが、この関数には $content という引数がひとつ渡されます。ここには投稿内容が入っており、この変数を編集して戻り値として返すことによって、記事の内容が表示される際に expandChildPostList() の実行結果で内容をフィルタリングすることができるようになります。 the_content とは記事が表示される際のフックポイントです。
急ぎ足でしたが WordPress のプラグインの作り方のさわりの部分は押さえることができたと思います。管理画面を作ったり、テーブルを追加したりするには、まだまだ知らなければならないことがたくさんあるのですが、時間がありましたら公開したいと思います。
Comments
[...] WordPress プラグインのサンプル http://www.game-create.com/archives/259 [...]
[...] wordpress プラグインの作り方 WordPress プラグインのサンプル | いちばんやさしいゲームの作り方 [...]
Contributions