 | レベル: 中級 Alister Lewis-Bowen (alister.lewisbowen@gmail.com), Senior Software Engineer, FIT Team, IBM China Development Lab Stephen Evanchik (evanchsa@clarkson.edu), Student, Clarkson University Louis Weitzman (louis.weitzman@gmail.com ), Senior Software Engineer, FIT Team, IBM China Development Lab
2006年 9月 12日 この連載では、IBM® Internet Technology Group が無料で入手可能な一連のソフトウェアを使って、閉鎖的コミュニティーの Web サイトを設計、開発、およびデプロイメントします。第 5 回では、Drupal のコンセプトと、Web サイトの開発に用いるプログラミング標準をいくつか紹介しました。この記事では引き続きサンプル・プロセスを検討し、アナウンスメント用カスタム Drupal モジュールの作成について説明します。3 人の著者がインプリメンテーションについて説明し、サンプル・コードを記載して独自のカスタム・モジュールの作成方法を示します。この記事を参考に、独自のカスタム・モジュールを作成してみてください。
はじめに
この記事では、Web サイトにアナウンスメントを提供する非常にシンプルなカスタム・モジュールを作成する方法について学びます。ここに記載する情報は絶対的な開発ガイドラインとしてではなく、独自のカスタム・モジュールを作成するための手掛かりとして使ってください。ほとんどの場合はトピックを簡単に説明するだけにとどめ、詳細説明については Drupal の資料を参照してください。
この連載に登場する架空の会社、IBC (nternational Business Council) では、関連アナウンスメントを表示する必要があります。これらのアナウンスメントは自動的に公開され、特定の時間が過ぎると Web サイトから削除されます。アナウンスメントには、以下の新しいデータ・フィールドが必要です。
- 要約。これはデフォルトの簡単な記述 (ティーザー) とは異なり、アナウンスメントの作成者が管理します (またこれによって、いくつかの Drupal 機能を説明できます)。
- 公開日。アナウンスメントが最初に Web サイトに掲載される日です。
- 有効期限。アナウンスメントが Web サイトから削除される日です。
- アナウンスメントの公開日前または有効期限が過ぎた後でも、管理スタッフがアナウンスメントを表示できるかどうかを示すフィールド。
図 1 に示すように、現在のアナウンスメントはホーム・ページのメイン・コンテンツ・エリアに表示されます。すべてのページでは、サイドバーに最近のアナウンスメントが記載されます。現行ユーザーが作成したアナウンスメントは、色付きの背景で強調表示されます。
図 1. IBC のホーム・ページ
準備作業
モジュールの作成を開始する前に、この新規モジュールを定義するコードをすべて含めるファイルを作成します。次に、データベースを変更して、このモジュールの関連情報をサポートするようにします。
ファイルの作成
まず、ibc_site の下に modules/announcement ディレクトリーを作成します。このディレクトリーには、announcement.module ファイルを作成します。Eclipse 環境では図 2 のようになります。通常、モジュールの名前と、そのモジュールが作成する新しいノード・タイプの名前は同じです。この記事で説明する特定のアナウンスメント・コードはすべて、このファイルに含まれることになります。
 |
注:
Drupal で現在ベスト・プラクティスとされているのは、コア・モジュール以外のすべてのモジュールを関連ドメインの sites ディレクトリーに配置することです。そのため、この記事の例では、announcement ディレクトリーを前回までの記事で作成した sites/drupal.development ディレクトリー内に配置することをお勧めします。新たに提供するモジュールをコア配布とは別にしておくと、コア Drupal コードに対する更新が簡単になります。
Drupal 4.7 では、正式な .install ファイルを使って、カスタム・モジュールに必要なデータベース・テーブルやデータを作成することもできます。この場合、announcement.sql ファイルを使う代わりに php を使って、アナウンスメント・モジュールのディレクトリーに含まれる announcement.install ファイル内で SQL 操作を実行します。詳細は、http://drupal.org/node/51220 を参照してください。
この点を指摘してくれた Boris に感謝します。この方法についての詳細は、Boris のブログを参照してください。
|
|
図 2. Eclipse でのナビゲーション・ビュー
データベースの変更
ノードで表されるすべてのデータはノード・テーブルに保管され、バージョン 4.7 では node_revisions テーブルにも保管されます。ノードを変更すると、リビジョンはタイトル、ティーザー、および本文とともに node_revisions テーブルに保管されます。既存の Drupal テーブルはサンプル・カスタム・モジュールをサポートしていないため、アナウンスメント固有の情報を保管する announcement テーブルを作成します。ティーザーを制御しやすくするため、この新しいテーブルには固有の要約を含めます。まず始めにテーブルを作成し、後から、特定のイベントでこのテーブルにアクセスする関数を作成します。
リスト 1 に示すコマンドは、ノード ID (nid) によってノード・テーブルにリンクされる announcementテーブルを作成します。このテーブルには、abstract、publish_date と expiration_date という新しい列が含まれます。このテーブルをデータベース内に作成するには、SQL コマンドラインまたは MySQL クエリー・ブラウザーを使用します。このデータベース・コマンドは、announcement ディレクトリー内の announcement.sql ファイルに保管しました (図 2)。このステップは、このモジュールのデータベース・テーブルを文書化すると同時に、必要な場合にはデータベースを簡単に更新できます。
リスト 1. 新規 announcement テーブルを作成するコマンド
CREATE TABLE announcement (
nid int(10) unsigned NOT NULL default '0',
abstract varchar(255) default '',
publish_date integer NOT NULL default '0',
expiration_date integer NOT NULL default '0',
PRIMARY KEY (nid)
);
|
モジュールの開発
Drupal のモジュール用インターフェースは、フックと呼ばれる一連の関数を使用します。このセクションでは、カスタム・モジュールをサポートするいくつかのフックの開発について説明します。以下のフックは、モジュールを立ち上げて実行するための基本的な関数のセットですが、Drupal フックの一部でしかありません。
-
hook_settings
- このモジュールの属性を作成します。これらの属性は管理者が変更できます。
-
hook_help
- インターフェースのさまざまな場所に表示される文書を指定します。
-
hook_perm
- 情報にアクセスするためのアクセス権カテゴリーを定義します。
-
hook_access
- それぞれの操作およびユーザーのアクセス権を定義します。
-
hook_menu
- アクセスを処理するときに呼び出す URL パスと関数を設定します。
-
hook_link
- サイト全体の表示に追加できるリンクを定義します。
-
hook_block
- このモジュールで表示する情報のブロックを定義します。
-
hook_form
- このノードを追加および編集するときに使用するインターフェース・ウィジェットを定義します。
-
hook_validate
- ユーザーからの入力をデータベースに保管する前に検証します。
-
hook_submit
- 検証後、ただしデータベースを更新する前にノードを変更します。
-
hook_load
- データベースから追加ノード情報をロードします。
-
hook_insert
- 追加ノード情報を新たに保管します。
-
hook_update
- ノードがすでに存在する場合、追加ノード情報を保管します。
-
hook_delete
- ノードが削除されるときに追加ノード情報を削除します。
-
hook_cron
- 管理者の定義に従って定期アクションを実行します。
-
hook_search
- このノードの情報にカスタム検索を定義します。
-
hook_nodeapi
- 他のモジュールが定義したノードに作用します。
-
hook_node_info
- モジュールのノード・タイプの名前および属性を定義します。
hook_settings
settings フックは、管理者が制御可能で、モジュールを表示するときに使用できる属性をモジュールに追加する方法となります。サンプル・アナウンスメント・モジュールでは、サイドバーに表示されるアナウンスメントの数を制限するように設定します。表示されるアナウンスメント要素を生成するのは block フックですが、サイドバーに表示されるアナウンスメント数は、管理者が管理者ユーザー・インターフェースで設定できるようにします。これらのモジュール固有の属性は、以下に示す関連 URL によってインターフェース内でアクセス可能になります。
admin/settings/<module_name> |
リスト 2 に、announcement_settings フックのインプリメンテーションを示します。
リスト 2. announcement_settings フックのインプリメンテーション
function announcement_settings() {
$form = array();
$form['announcement_block_max_list_count'] = array(
'#type' => 'textfield',
'#title' => t('Maximum number of block announcements'),
'#default_value' => variable_get('announcement_block_max_list_count', 3),
'#description' => t('The maximum number of items listed in the announcement block'),
'#required' => FALSE,
'#weight' => 0
);
return $form;
}
|
リスト 2 の announcement_settings フックは、管理者がこの属性に値を指定するときに使用するインターフェース要素を定義します。form 配列のインデックスは変数名で、例えば announcement_block_max_list_count. などのようになります。このインデックスに保管される配列のコンポーネントによって、管理者ユーザー・インターフェースの構成方法が定義されます。
これによって、表示するアナウンスメントのサイドバーを作成するときに、この値を使えるようになります。サイドバーを作成する際には、リスト 3 のコード・フラグメントを使って、settings フックに定義されたパーシスタンス変数を取得します。
リスト 3. settings フックに設定された変数の取得
$items = variable_get('announcement_block_max_list_count', 3);
|
hook_help
help フックは、管理者やユーザーがシステムを操作するときに表示される文書を配置するための場所を提供します。表示される場所は 2 つあり、それは管理者が管理/モジュール・ページでモジュールを有効または無効にするときです。図 3 に、このモジュールを有効にする管理者画面内の行を表示します。この図では、アナウンスメント・モジュールが強調表示されています。
図 3. 管理者ページ上のヘルプ文書
同様に、ユーザーが create content リンクを使って新しいアナウンスメントを追加する際には、図 4 に示すように追加するノードの説明が表示されます。
図 4. 追加可能なノード・タイプのヘルプ説明
リスト 4 に、アナウンスメント・モジュールに関する上記の 2 つの説明を生成する help フックのインプリメンテーションを示します。その他のヘルプ説明を作成することも可能です。
リスト 4. announcement_help フックのインプリメンテーション
function announcement_help($section) {
switch ($section) {
case 'admin/modules#description':
return t('Enables the creation of announcement pages ' .
'that are presented on the home page.');
case 'node/add#announcement':
return t('An Announcement. Use this page to add an announcement page.');
}
}
|
hook_perm
perm フックは、各ロールに割り当てることが可能なアクセス権を定義します。アプリケーションに関連するアクションを記述するには、任意のストリングを使用できます。以下のリスト 5 では、アナウンスメントの作成と、アナウンスメントの編集 (または削除) を区別しています。これで、hook_access 関数を呼び出して、これらのアクセス権によってコンテンツへのアクセスを制御することができます。
リスト 5. announcement_perm フックのインプリメンテーション
function announcement_perm() {
return array('create announcement', 'edit announcement');
} |
デフォルトの Drupal のロールは、匿名ユーザーと認証済みユーザーです。管理者は、パス admin/access/control のインターフェース内から追加のロールを作成できます。これらのロールが上記で定義したアクセス権と組み合わせられて、図 5 に示すアクセス権コンソールの基本となります。チェック・ボックスの行列によって、管理者はシステム内のそれぞれ異なるロールに対してアクセス権を有効にできます。この図で強調表示されているアナウンスメント・モジュールは、リスト 5 の hook_perm で定義した 2 つのストリングを表示しています。管理者のロールを持つユーザーか、オペレーションのロールを持つユーザーのみが、アナウンスメントを作成、編集することができます。
図 5. 管理者がアクセス権を割り当てられるインターフェース
hook_access
各モジュールは、その表示データへのアクセスを制限できます。モジュール作成者は access フックを使ってモジュールへのアクセスを制御します。実際に user_access 関数をコールして、現行ユーザーに特定のアクセス権があるかどうかをチェックしてください。リスト 6 の user_access 関数では、announcement_perm 関数で定義したアクセス権を参照しています。
access フック関数には、操作 (作成、表示、更新または削除など) と、その操作が対象とするノードを指定します。するとこの関数は、現行ユーザーが指定ノードに対してその操作を実行できるかどうかを示すブール値を戻します。
このインプリメンテーションでは、「create announcement」アクセス権が割り当てられたユーザーのみがアナウンスメントを作成できます。「access content」アクセス権を持つユーザー (すべての認証済みユーザー) は、アナウンスメントを表示できます。更新および削除操作は、要求を開始したユーザーがコンテンツの所有者である場合、あるいはアナウンスメントを編集する許可が明示的に与えられている場合に実行できます。
Drupal インストールの一環として作成された最初の登録ユーザー (ID = 1 のユーザー) にはルート・アクセス権があるため、システム内のあらゆるデータを編集および変更することができます。
リスト 6. announcement_access フックのインプリメンテーション
function announcement_access($op, $node) {
global $user;
if ($op == 'create') {
return user_access('create announcement');
}
else if ($op == 'view') {
return user_access('access content');
}
else if ($op == 'update' || $op == 'delete') {
if($user->uid == $node->uid || user_access('edit announcement')) {
return true;
}
else {
return false;
}
}
else {
return false;
}
|
hook_menu
Drupal の URL への応答方法が定義されるのが、menu フック関数です。この関数は、特定の URL とメニュー項目に対するコールバックを定義します。Drupal が特定ノードを参照する際に URL を構成する標準の方法では、操作の前にノード ID を追加します。このフォーマットを使ってサンプル・モジュールに指定する必要がある URL には、以下のものがあります。
/announcements
/announcements/add
/announcements/<id>/view
/announcements/<id>/edit
/announcements/<id>/delete
|
ここで <id> は、表示、編集、または削除対象のノードの ID です。リスト 7 に、announcement_menu 関数の定義を示します。
リスト 7. Announcement_menu フックのインプリメンテーション
function announcement_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array('path' => 'announcements/add',
'title' => t('Add a new Announcement'),
'access' => node_access('create', 'announcement'),
'type' => MENU_CALLBACK,
'callback arguments' => array('announcement'),
'callback' => 'node_add');
$items[] = array('path' => 'announcements',
'title' => t('Announcements'),
'access' => user_access('access content'),
'type' => MENU_CALLBACK,
'callback' => 'announcement_all');
}
else {
if(is_numeric(arg(1))) {
$node = node_load(arg(1));
$items[] = array('path' => 'announcements/' . arg(1),
'title' => t('View an Announcement'),
'access' => node_access('view', $node),
'type' => MENU_CALLBACK,
'callback' => 'node_page');
$items[] = array('path' => 'announcements/' . arg(1) . '/view',
'title' => t('View an Announcement'),
'access' => node_access('view', $node),
'type' => MENU_CALLBACK,
'callback' => 'node_page');
$items[] = array('path' => 'announcements/' . arg(1) . '/edit',
'title' => t('Edit an Announcement'),
'access' => node_access('edit', $node),
'type' => MENU_CALLBACK,
'callback' => 'node_page');
$items[] = array('path' => 'announcements/' . arg(1) . '/delete',
'access' => node_access('delete', $node),
'type' => MENU_CALLBACK,
'callback' => 'node_delete_confirm');
}
}
return $items;
}
|
上記の関数は、複数の配列で構成された 1 つの配列を戻します。それぞれの配列には、以下が含まれます。
-
path
- 対象 URL
-
title
- このメニュー項目のタイトル。このタイトルは、マウスのホバー操作で表示されます。
-
callback
- この URL がアクセスされたときに呼び出す関数
-
type
- メニュー項目のタイプ
-
access
- このメニュー項目に対するアクセス権
実動 Web サイトをより効率的に作成するには、キャッシングが使われます。上記の例では、メニュー定義を部分的にキャッシングするか、あるいはしないかを $may_cache条件を使って制御できます。ただし、キャッシングされると、開発中に行ったコードの変更を反映しません。そのため、キャッシング可能な項目には、そのパスの指定に arg(1) などの変数を含めるべきではありません。
hook_link
私たちが従うようにした基本原則の 1 つは、それぞれのアクションは、そのアクションが作用する項目の近くに配置するということです。アナウンスメントの場合には、追加、編集、削除、そしてコメントのアクション・リンクをアナウンスメント・タイトルの隣に配置しました。これらのアクションが表示されるのは、もちろん、現行ユーザーが適切なアクセス権を持っている場合のみです。アクションは、図 6 に示すように、小さな赤いフォントでテーマ設定されています。
図 6. アナウンスメント・タイトルの隣に配置されたアクション・リンク
上記を行うためのメカニズムを提供してくれるのは、link フックです。リスト 8 に、このフックのインプリメンテーションを示します。このフックでは、アクセス権に基づいて適切なリンクを作成した後、適切なマークアップを使ってリンクにテーマを設定できます。これによって、CSS がテキストを赤い小さなフォントにスタイル設定することが可能になります。コメント・モジュールはまた、「Add Comment」アクションを追加することによって、このリンクのセットに貢献します。
リスト 8 の l(t('Edit')...) というシーケンスでは、リンクの生成をサポートするために 2 つのユーティリティー関数を使用しています。まず t 関数が 'Edit' ストリングを現在のロケールに変換し、次に l 関数がこのストリングを対応する内部 Drupal リンクにフォーマット設定します。これらのリンクはテンプレート・エンジン内で theme('links', $links) をコールすることによってテーマ設定された上で、テンプレート変数$links にマップされます。
リスト 8. announcement_link フックのインプリメンテーション
function announcement_link($type, $node = NULL, $teaser = FALSE) {
global $user;
$links = array();
if($type == 'node' & $node->type == 'announcement') {
if (node_access('create', 'announcement')) {
$links[] = l(t('Add'), "node/add/announcement",
array('title' => t('Add a new announcement')));
}
if (node_access('update', $node)) {
$links[] = l(t('Edit'), "announcements/$node->nid/edit",
array('title' => t('Edit Announcement ') . $node->title));
$links[] = l(t('Delete'), "announcements/$node->nid/delete",
array('title' => t('Delete Announcement ') . $node->title));
}
}
return $links;
}
|
hook_block
図 7 に示すように、アナウンスメントはホーム・ページの中央と、すべてのページのナビゲーション・サイドバーに表示されます。サイドバーのコンテンツを使用可能にするのは、block フックです。モジュールはこのフックを使って、ページの任意の場所に表示できるコンテンツを提供します。通常、サイドバーにはコンテンツ全体の概要が表示されます。現行ユーザーを対象としたアナウンスメントの強調表示は、サイドバーとメイン・コンテンツ・エリアとで一貫していることに注目してください。図 7 に管理者用のサイドバーを示します。ログイン中に使用できるリソースを確認してください。これらのリソースは、一般ユーザーには表示されません。
図 7. block フックを使ったサイドバー内のアナウンスメント
このフックが定義されていれば、図 8 に示すように、管理者がユーザー・インターフェースでブロック情報の配置を指定することができます。右側サイドバーでは、アナウンスメントが有効に設定され、-10 の重みが指定されています。重みの数値が小さいほど、コンテンツはサイドバーの上の方に表示されます。つまり、以下の設定では、アナウンスメントが右側サイドバー内の先頭ブロック情報として表示されます。
図 8. ブロックの配置を指定するための管理者のインターフェース
リスト 9 に、アナウンスメント・モジュールの block フックのインプリメンテーションを示します。この関数では、1 つ以上のブロックを作成できます。Drupal では $delta 引数を使って、$block 配列で定義された表示対象ブロックにインデックスを付けます。この関数が最初に確認する条件は、list 操作です。その情報が、図 8 に示した管理者のインターフェースで使用されます。
2 つ目の操作は view で、これによって該当するアナウンスメントを収集し、右側サイドバー内の情報ブロックに表示します。表示するアナウンスメント数については、announcement_settings フックに定義されたアナウンスメント設定 announcement_block_max_list_count で判断します。view 操作は、announcement_block_list テンプレート・ファイル (announcement_block_list.tpl.php) を使ってアナウンスメントにテーマを設定します。(アナウンスメントのテーマ設定については、今後の記事で説明する予定です。)
リスト 9. announcement_block フックのインプリメンテーション
function announcement_block($op = 'list', $delta = 0, $edit = array()) {
global $user;
if ($op == 'list') {
$blocks[0]['info'] = t('Recently updated announcements');
return $blocks;
}
else if ($op == 'view') {
$block = array();
$output = '';
switch ($delta) {
case 0:
$now = time();
if (user_access('access content')) {
$q = 'SELECT N.uid,N.nid,N.title,A.publish_date,N.status '.
'FROM {node} N JOIN {announcement} A USING(nid) '.
"WHERE N.type='announcement' ".
'AND N.status = 1 '.
'AND A.publish_date < ' . $now . ' '.
'AND A.expiration_date > ' . $now . ' '.
'ORDER BY A.publish_date DESC ';
$items = variable_get('announcement_block_max_list_count', 3);
if ($items) { $q .= "LIMIT 0,$items"; }
$announcements = db_query($q);
$announcement_items = array();
while (db_num_rows($announcements) > 0 and $announcement =
db_fetch_object($announcements)) {
$announcement_items[] = $announcement;
}
}
$block['subject'] = t('Announcements');
$block['content'] = theme('announcement_block_list',$announcement_items);
break;
}
return $block;
}
}
|
hook_form
form フックは、ノードのコンテンツを追加または編集するためのユーザー・インターフェースを生成するためにコールされます。このフックが戻すのは複数の配列で構成された 1 つの配列で、それぞれの配列はユーザーが編集する必要のあるコンテンツの箇所を示します。図 9 は、アナウンスメント・ノードの編集に使用するインターフェースです。ここには、タイトル、公開日、有効期限、要約、および本文の入力フォームを用意しています。各フォーム要素の下には、短い説明があり、ユーザーにその情報が何で、どのように使うのかを示しています。。
図 9. アナウンスメント・ノードの編集フォーム
アナウンスメントの要約の編集用フォーム要素を生成するため、リスト 10 に示すような配列を作成します。
リスト 10. アナウンスメントの要約を編集するテキスト域の作成
$form['abstract'] = array(
'#type' => 'textarea',
'#title' => t('Abstract'),
'#default_value' => $node->abstract,
'#rows' => 3,
'#description' => t('Short summary of the full announcement'),
'#required' => TRUE,
'#weight' => 9
); |
配列のインデックス abstract は、この要素の名前です。この値には、ノード・データ構造からアクセスします ($node->abstract)。この要素の詳細には、以下が含まれます。
-
type
- ウィジェットのタイプは textarea です (使用可能な他の属性に作用します)。
-
title
- 変換関数「t」で変換される表示タイトルです。
-
default_value
- 最初に表示されるときにウィジェットで使用される値 (現在の値など) です。
-
rows
- textarea に表示する行数です。
-
description
- インターフェース内でウィジェットの下に表示するテキストです。
-
required
- この入力フィールドが必須かどうかを示します。
-
weight
- ウィジェットの順序に作用します。数値が小さいほど、インターフェース内での表示位置が上の方になります。
公開は、公開日と有効期限をグループ化する fieldset として定義されます。リスト 11 に、アナウンスメント・ノードの編集用インターフェースを作成する完全な announcement_form 関数を示します。最初の 2 つの if 節は、公開日と有効期限が指定されない場合のデフォルトを設定します。publication 配列では、#prefix および #suffix 要素を使って、日付選択機能の単純な CSS テーマ設定を有効にするための追加 HTML タグを挿入しています。$form 配列のストリング・インデックスは、後でノードから特定の値を間接参照するために使用しています。
リスト 11. 完全な announcement_form
function announcement_form(&$node) {
if ($node->expiration_date == NULL) {
$node->expiration_date = time() + (365 * 86400);
}
if ($node->publish_date == NULL) {
$node->publish_date = time();
}
$form['title'] = array('#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => $node->title,
'#description' => t('Title of the announcement'),
'#required' => TRUE,
'#weight' => 1
);
$form['publication'] = array('#type'=> 'fieldset',
'#collapsible' => FALSE,
'#title' => t('Publication dates'),
'#weight' => 5
);
$form['publication']['publish_date'] = array(
'#prefix' => '<div class="date_widget">',
'#suffix' => '</div>',
'#type' => 'date',
'#title' => t('Publication date'),
'#default_value' => _announcement_unixtime2drupaldate($node->publish_date)
);
$form['publication']['expiration_date'] = array(
'#prefix' => '<div class="date_widget">',
'#suffix' => '</div>',
'#type' => 'date',
'#title' => t('Expiration date'),
'#default_value' => _announcement_unixtime2drupaldate($node->expiration_date)
);
$form['abstract'] = array('#type' => 'textarea',
'#title' => t('Abstract'),
'#default_value' => $node->abstract,
'#rows' => 3,
'#description' => t('Short summary of the full announcement'),
'#required' => TRUE,
'#weight' => 9
);
$form['body'] = array('#type' => 'textarea',
'#title' => t('Body'),
'#default_value' => $node->body,
'#description' => t('Full content for the announcement which ' .
'is shown with the abstract on the details page'),
'#required' => TRUE,
'#weight' => 10
);
return $form;
}
|
Drupal 4.6 から 4.7 への大幅な変更の 1 つは、form フックのインプリメンテーションです。Drupal Web サイトには、以下をはじめとする関連資料が豊富に用意されています。
hook_validate
編集プロセスの終了時に、ノードが保管される前に validate フックが呼び出されます。これによって、データをデータベースに保管する前に検証することができます。アナウンスメントでは、このフックを使って開始日が終了日よりも先であることを確実にします。クライアント側のスクリプトを使った、よりインタラクティブなインプリメンテーションでは、2 つのフィールドが互いに整合していなければならないため、そのような事態は起こりません。ノードを保管する前に変更を加えたい場合には、submit フックを使ってください。
リスト 12 に、validate フックのインプリメンテーションを示します。まず日付を整数に変換して、比較できるようにします。ユーザーが対処しなければならない問題がある場合は、Drupal のエラー処理メカニズムを使用する form_set_error 関数を使用してください。リスト 12 に示された、この関数に対する最初の引数 publish_date は、form フックで使用したフォーム配列名 (リスト 11) を参照し、インターフェース内でその要素を強調表示します。
リスト 12. announcement_validate フック
function announcement_validate($node) {
if ($node) {
$publish_date =
_announcement_drupaldate2unixtime($node->publish_date);
$expiration_date =
_announcement_drupaldate2unixtime($node->expiration_date);
if ($publish_date >= $expiration_date) {
form_set_error('publish_date',
t('The publish date of an announcement must be before its expiration date.'));
}
}
}
|
hook_submit
ノードの検証段階が終わると、submit フックがコールされます。このフックを使用して、データベースが実際に更新される前にノードに変更を加えることができます。リスト 13 の submit フックは、ノードの公開日と有効期限を更新した後、現在の日付に基づいてノードのステータスを変更します。現在の日付が公開日と有効期限の範囲内であればステータスが 1 に設定され、それ以外の場合は 0 に設定されます。
リスト 13. announcement_submit フック
function announcement_submit(&$node) {
$node->publish_date =
_announcement_drupaldate2unixtime($node->publish_date);
$node->expiration_date =
_announcement_drupaldate2unixtime($node->expiration_date);
$now = time();
if ($now >= $node->publish_date &
$now < $node->expiration_date) {
$node->status = 1;
}
else {
$node->status = 0;
}
}
|
データベース・フック
Drupal はデータベースの操作中に、環境内で発生するさまざまなイベントでフック関数をトリガーします。重要なイベントには、ロード、挿入、更新、および削除などがあります。これらのフック・イベントについての詳細を参照してください。
 | |
MySQL およびデータベース抽象化レイヤーについての詳細は、次回の記事で説明します。 |
|
Drupal には、このコード全体で使用されるデータベース抽象化レイヤーがあります。データベース・フックでは db_ で始まる関数を使用して、この抽象化レイヤーにアクセスします。
hook_load
データベースから announcement タイプのノードがロードされると、自動的に load フックがコールされます。この関数によって、カスタム・モジュールがデータベースから追加コンテンツをロードし、その定義を完成することができます。関数の戻り値は、ノード・データ構造にマージされる追加コンテンツの配列です。サンプルでは、新規テーブルから要約、公開日、有効期限の 3 つのデータ項目をロードする必要があります。リスト 14 に、サンプル・アナウンスメント・ノードの追加情報をロードするためのコードを示します。
リスト 14. announcement タイプのノードをロードするための announcement_load フック
function announcement_load(&$node) {
$additions = db_fetch_object(db_query('SELECT * FROM {announcement} ' .
'WHERE nid = %d', $node->nid));
return $additions;
}
|
hook_insert
リスト 15 に示すように、アナウンスメント・ノードが Web サイトに作成されると insert フックが自動的にコールされます。このフックは、新規モジュールがノードを最初に作成する際に、追加情報をデータベースに保管する機会を提供します。このアナウンスメント・モジュールには、announcement テーブルに新しいエントリーを作成します。関数に渡されるノード・オブジェクトには、入力フォームのデータがすべて含まれます。公開日と有効期限は、月、日、年の配列として戻されます。これを変換するのは ローカル関数 _announcement_drupaldate2unixtime です。慣例により、すべてのローカル・モジュール関数には、先頭にアンダースコアー (「_」) とモジュール名が追加されます (例えば、 _announcement)。次にデータベース抽象化レイヤーを呼び出して、この新しい行を announcement テーブルに挿入します。announcement テーブルをノード・テーブルにリンクする主キーは、$node->nid です。
リスト 15. アナウンスメントをデータベースに追加する announcement_insert フックとサポート関数
function _announcement_drupaldate2unixtime($drupal_date) {
$year = $drupal_date["year"];
$month = $drupal_date["month"];
$day = $drupal_date["day"];
return mktime(0,0,0, (int)$month, (int)$day, (int)$year);
}
function announcement_insert($node) {
$publish_date = _announcement_drupaldate2unixtime($node->publish_date);
$expiration_date = _announcement_drupaldate2unixtime($node->expiration_date);
db_query("INSERT INTO {announcement} (nid, abstract, publish_date, expiration_date) ".
"VALUES (%d, '%s', '%d', '%d')",
$node->nid, $node->abstract, $publish_date, $expiration_date);
} |
hook_update
リスト 16 のupdate フックは、情報がすでにデータベース内にあり、ユーザーがその情報を編集中の場合にコールされます。これは insert フックと似ていますが、実行されるデータベース・コマンドは UPDATE です。
リスト 16. 既存のアナウンスメント・ノードを変更する announcement_update フック
function announcement_update($node) {
$publish_date = _announcement_drupaldate2unixtime($node->publish_date);
$expiration_date = _announcement_drupaldate2unixtime($node->expiration_date);
db_query("UPDATE {announcement} SET abstract='%s', publish_date = '%s', " .
"expiration_date = '%s' WHERE nid = %d",
$node->abstract, $publish_date, $expiration_date, $node->nid);
}
|
hook_delete
delete フック (リスト 17) は、ユーザーがアナウンスメント・ノードを削除するときにコールされます。このフックは、データベース内のその他のテーブルからすべての付属コンテンツを削除する機会をモジュールに与えます。以下のリストでは、nid に基づくノードの関連 announcement テーブルから、1 行削除しています。
リスト 17. アナウンスメント・ノードを削除する announcement_delete フック
function announcement_delete($node) {
db_query('DELETE FROM {announcement} WHERE nid = %d', $node->nid);
}
|
hook_cron
cron フックを使って、モジュールは定期的に実行するタスクをスケジュールします。サイト管理者が実行間隔を設定するには、http://<sitename.com>/cron.php で HTTP GET を実行する cron ジョブを使います。これによって、定義された cron フックがすべてのモジュールで呼び出されます。cron ジョブのステータスは、cron ジョブのセクションにある Administer > Settings (/admin/settings など) の管理者インターフェースに表示されます。
アナウンスメント・モジュールは、公開日と有効期限に基づいてアナウンスメントを表示するかどうかを決定します。ただし、ノード・ステータスが 0 に設定されていないと、標準ノード・メカニズム (/node/id/view) によってアナウンスメントが表示されてしまうことがあります。有効期限が過ぎたすべてのアナウンスメント・ノードでステータス・フラグを設定するには、cron フックを使います。リスト 18 に示す announcement_cron 関数は、まずデータベースに有効期限を過ぎたアナウンスメントをクエリーし、該当するノードのノード・ステータスを 0 に設定します。
リスト 18. announcement_cron フックのインプリメンテーション
function announcement_cron() {
$queryResult = db_query("UPDATE {node} AS n INNER JOIN {announcement} AS a " .
"ON n.nid = a.nid SET n.status = 0 WHERE n.type='announcement' " .
"AND n.status = 1 AND a.expiration_date < %d", time());
}
|
hook_search
search フックを使って、モジュールは検索ページを拡張し、モジュールが作成するノードでキーワード検索を実行できるようにします。これにはまず、administer > modulesページに進んで検索モジュールを有効にする必要があります。これで、administer > block ページを使用して、ヘッダー内に検索ブロックを含めることが可能になります。search フックを使用した場合、簡易検索が表示されると検索ページに別のタブがレンダリングされます。この検索フォームを使用して、カスタム・モジュールによって作成されたノード内でキーワードを検索します。
検索モジュールは cron を使って、ノード内で検出されたデータのインデックス・テーブルを作成します。これによって、Drupal でノード・コンテンツに対する全文検索ができるようになります。
このアナウンスメント・モジュールでは、検索エンジンが announcement テーブルに定義された新しい要約を選択するように設定しなければなりません。また、デフォルト検索のアクションを変更して、デフォルト・コンテンツの代わりに、要約フィールドが表示されるようにする必要があります。ただし、これを表示するための別個のタブは使用しないこととします。幸い、seach フックの代わりにこの必要を満たす方法があります。
hook_nodeapi
デフォルト検索フォームが announcement テーブルの要約フィールド内にあるキーワードを検出できるようにするため、私たちは nodeapi フック関数をインプリメントしました。この関数により、インデックスの更新中に要約フィールドを組み込むことができます。リスト 19 に、nodeapi フックのインプリメンテーションを示します。
リスト 19. announcement_nodeapi フックのインプリメンテーション
function announcement_nodeapi(&$node, $op) {
switch ($op) {
case 'update index':
if ($node->type == 'announcement') {
$text = '';
$q = db_query('SELECT a.abstract FROM node n LEFT JOIN announcement a ' .
'ON n.nid = a.nid WHERE n.nid = %d', $node->nid);
if ($r = db_fetch_object($q)) {
$text = $r->abstract;
}
return $text;
}
}
}
|
この関数内では、Drupal がデータベース内でインデックスを付ける前に追加データを収集中であることを示す update index 操作をチェックします。インデックスを付ける対象のノードが announcement タイプの場合、関連付けられた要約フィールド値が announcement テーブルから抽出されて戻されます。
これで、Drupal がこのアナウンスメントの要約にインデックスを付けることが可能になったので、今度はキーワード検索と一致した場合には、この情報がデフォルト検索ページの結果に表示されるようにする必要があります。そのためリスト 20 に示すように、phptemplate_search_item 関数を使って、検索モジュール内の theme_search_item 関数をオーバーライドしました。この関数はグローバル・テーマ変更として見なすことができるため、テーマ・ディレクトリーに含まれる template.php ファイルに配置します。
リスト 20. phptemplate_search_item 関数
function phptemplate_search_item($item, $type) {
return _phptemplate_callback('search_item',
array('node' => $item), 'search_item-' . strtolower($item['type']));
} |
上記の関数では、_phptemplate_callback 関数を使って、示されたテンプレート・ファイルに検索項目のテーマ設定を関連付けています。phptemplate エンジンが node.tpl.php および node-<node-type>.tpl.php テンプレート・ファイルを使用可能にしてノードのレンダリング方法をカスタマイズするというような方法ではなく、リスト 21 に示すように、この関数を使って search_item.tpl.php および search_item-<node-type>.tpl.php テンプレートを使えるようにしています。
これで、search.module ファイル内の theme_search_item 関数にある元のテーマ設定済み検索項目とは多少異なるバージョンの検索項目のためのデフォルト外観用テンプレートを使うことができます。この便利な手法は、あらゆるテーマ設定済みエンティティーに適用できます。
リスト 21. search_item.tpl.php テンプレート
<dt class="title search_item">
<a href="<?php print check_url($node['link']); ?>"><?php print
check_plain($node['title']) ?></a>
</dt>
<?php
$info = array();
if ($node['type']) $info[] = strtolower($node['type']);
if ($node['user']) $info[] = $node['user'];
if ($node['date']) $info[] = format_date($node['date'],'small');
if (is_array($node['extra'])) $info = array_merge($info, $node['extra']);
?>
<dd class="search_item">
<p><?php print $node['snippet']; ?></p>
<p class="search-info"><?php print implode(' - ', $info); ?></p>
</dd> |
search_item-announcement.tpl.php テンプレートを使って、announcement タイプのノードの検索項目にテーマを設定し、デフォルトのスニペットを独自に構成した要約フィールドに置き換えることができます。リスト 22 では、要約に含まれるキーワードを強調表示するために search_excerpt 関数を使いました。
リスト 22. search_item-announcement.tpl.php テンプレート
<dt class="title search_item_announcement">
<a href="<?php print check_url($node['link']); ?>">
<?php print check_plain($node['title']) ?></a>
</dt>
<?php
$info = array();
if ($node['type']) $info[] = strtolower($node['type']);
if ($node['user']) $info[] = $node['user'];
if ($node['date']) $info[] = format_date($node['date'],'small');
if (is_array($node['extra'])) $info = array_merge($info, $node['extra']);
?>
<dd class="search_item_announcement">
<p><?php print ($node['node']->abstract ? '<p>'.
search_excerpt(search_get_keys(),$node['node']->abstract) .
'</p>' : $node['snippet']); ?></p>
<p class="search-info"><?php print implode(' - ', $info); ?></p>
</dd>
|
図 10. 出力で検索用語が強調表示された検索結果ページ
hook_node_info
リスト 23 には、ノード・モジュールが複数のカスタム・ノード・タイプを定義するために使用する関数、node_info フックを示しています。このリストでは、この関数を使って、Drupal に announcement ノード・タイプを定義するように指示しています。Drupal がノード・タイプを関連付けるために必要な値は 2 つあります。その 1 つはノード・タイプの可読文字で、これはユーザー・インターフェースで使われます。もう 1 つはベース、すなわちこのノード・タイプと関連付けられる関数に使われるプレフィックスです。配列項目を追加すれば、これ以外のノード・タイプを追加することもできます。
リスト 23. モジュールのノード・タイプの名前および属性を決定するannouncement_node_info
function announcement_node_info() {
return array('announcement' => array('name' => 'Announcement',
'base' => 'announcement'));
}
|
モジュール出力のテーマ設定
モジュール・ファイルは、モジュールの情報をさまざまなコンテキストで表示するために使われる複数のテーマ関数を提供しています。ほとんどの場合、これらのコンテキストは以下のように考えると理解しやすいと思います。
- 詳細なレイアウト。ここには特定ノードに関するすべての情報が表示されます。
- 簡易表示あるいは要約表示。ここにはノードの概要を示す重要な部分だけが表示されます。
- ノード情報を小さなフォームで表示するブロック。通常は左右どちらかのサイドバーに組み込まれます。
アナウンスメント・モジュールには、上記のコンテキストごとに以下のテーマ関数をそれぞれ使用しています。
- アナウンスメント・ページでは、theme_announcement 関数を使って詳細レイアウトを作成しました。
- サイトのフロント・ページまたはホーム・ページでは、theme_announcement_compact 関数を使ってアナウンスメントのリストを作成しました。
- すべてのページの右側サイドバーに含まれるアナウンスメント・ブロックでは、theme_announcement_block_list 関数を使いました。
「第 5 回: Drupal 入門」で説明したように、テーマ関数を使用すると、選択されたテーマ・エンジンが何であろうと、アナウンスメント・モジュール出力のデフォルトの外観を作成できます。
私たちが使用しているのは phptemplate エンジンなので、このエンジンのデフォルト・テーマ関数をオーバーライドする関数は、リスト 24 に示すように、phptemplate_announcement、phptemplate_announcement_compact、および phptemplate_announcenemt_block_list となります。構造とスタイルの定義をモジュールのロジックから切り離し、_phptemplate_callback 関数を使ってテンプレート・ファイルを各コンテキストに関連付けると実用的です。
リスト 24. デフォルト・テーマ関数と、これらの関数をオーバーライドする PHP Template エンジン関数
function theme_announcement($announcement) {
// Put your default theme for the announcement detail here
return '';
}
function theme_announcement_compact($announcement) {
// Put your default theme for the announcement summary here
return '';
}
function theme_announcement_block_list($announcement_list) {
// Put your default theme for the announcement block here
return '';
}
function phptemplate_announcement($announcement) {
return _theme_phptemplate_announcement($announcement, 'announcement');
}
function phptemplate_announcement_compact($announcement) {
return _theme_phptemplate_announcement($announcement, 'announcement_compact');
}
function phptemplate_announcement_block_list($announcement_list) {
global $user;
return _phptemplate_callback('announcement_block_list',
array('announcements' => $announcement_list,
'user' => $user));
}
function _theme_phptemplate_announcement($announcement, $announcement_template) {
$expired = FALSE;
if ($announcement->expiration_date < time()) {
$expired = TRUE;
}
$variables = array(
'title' => $announcement->title,
'body' => $announcement->body,
'links' => $announcement->links ?
theme('links', $announcement->links) : '',
'abstract' => $announcement->abstract,
'published' => format_date($announcement->publish_date,'custom','j M, Y'),
'expires' => format_date($announcement->expiration_date,'custom','j M, Y'),
'expired' => $expired,
'node' => $announcement
);
return _phptemplate_callback($announcement_template, $variables);
} |
ヘルパー関数 _theme_phptemplate_announcement が使われていることに注目してください。これは、選択されたテンプレート・ファイルに渡される変数を作成するための共通の方法となります。
ここでは、カスタム・アナウンスメント・モジュールの場合の単純なテーマ設定例を説明しました。次回の記事では、ノードのスタイル設定について詳しく説明します。
まとめ
今回は、単純なカスタム・モジュールであるアナウンスメント・モジュールを Web サイトにインプリメントする方法について説明しました。このモジュールは、公開日と有効期限に基づいて Web サイトに自動的に表示され、自動的に消去されるアナウンスメントを提供します。アナウンスメントはホーム・ページのメイン・エリアと、その他のすべてのページのサイドバーに表示されます。機能的なモジュールにするために、多くのコア関数 (フック) が使われています。
この連載の以降の記事では、サンプル Web サイトのスタイル設定、そして SQL とデータベース抽象化レイヤーへのインターフェースについて、さらに詳細に説明していきます。
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | 
|  | Alister Lewis-Bowen は、IBM Internet Technology Group のシニア・ソフトウェア・エンジニアです。1993年から
IBM 社員としてインターネットおよび Web テクノロジーに取り組んでいます。彼は
IBM 後援のスポーツ・イベントの Web サイトに従事するためにアメリカ合衆国に移り、のちに
ibm.com のシニア・ウェブマスターとなりました。現在は、セマンティック Web プロトタイプの作成を支援しています。 |
 | |  | Stephen Evanchik は、IBM Internet Technology Group のソフトウェア・エンジニアです。多くのオープン・ソース・ソフトウェア・プロジェクトに貢献した経歴を持ち、なかでも
Linux カーネルの IBM TrackPoint は彼の注目に値する功績となっています。現在は、新しいセマンティック
Web テクノロジーに取り組んでいます。 |
 | 
|  | Louis Weitzman は、IBM Internet Technology Group のシニア・ソフトウェア・エンジニアです。30年間、設計とコンピューティングが交わる部分で働いています。彼は
ibm.com で使用する XML、フラグメント・ベースのコンテンツ管理システムの開発を支援し、現在は新しいプロジェクトへの設計プロセスの統合に従事しています。 |
記事の評価
|  |