オープン・ソース・ソフトウェアによる協調 Web サイトの設計、開発、デプロイメント第 5 回: Drupal 入門

この連載では、IBM® Internet Technology Group が無料で入手可能な一連のソフトウェアを使って、閉鎖的コミュニティーのWeb サイト を設計、開発、およびデプロイメントします。これまでの記事で説明した手順に従っていれば、汎用Drupal システムがインストール済みのはずです。今回は、このシステムにコンテンツを追加し、スタイルを変更します。この記事では、Web開発で用いる Drupal プログラミング・モデルを紹介し、さまざまなタイプのコンテンツ、モジュールを使用した新しい機能の開発、これらのモジュールを使用可能にするフックのインプリメンテーション、さらにサイトのURL 設計について説明します。

Alister Lewis-Bowen, Senior Software Engineer, IBM

Alister's photoAlister Lewis-Bowen は、IBM Internet Technology Group のシニア・ソフトウェア・エンジニアです。1993年から IBM 社員としてインターネットおよび Web テクノロジーに取り組んでいます。彼は IBM 後援のスポーツ・イベントの Web サイトに従事するためにアメリカ合衆国に移り、のちに ibm.com のシニア・ウェブマスターとなりました。現在は、セマンティック Web プロトタイプの作成を支援しています。



Stephen Evanchik, Student, Clarkson University

Stephen Evanchik は、IBM Internet Technology Group のソフトウェア・エンジニアです。多くのオープン・ソース・ソフトウェア・プロジェクトに貢献した経歴を持ち、なかでも Linux カーネルの IBM TrackPoint は彼の注目に値する功績となっています。現在は、新しいセマンティック Web テクノロジーに取り組んでいます。



Louis Weitzman, Senior Software Engineer, IBM

Louie's photoLouis Weitzman は、IBM Internet Technology Group のシニア・ソフトウェア・エンジニアです。30年間、設計とコンピューティングが交わる部分で働いています。彼は ibm.com で使用する XML、フラグメント・ベースのコンテンツ管理システムの開発を支援し、現在は新しいプロジェクトへの設計プロセスの統合に従事しています。



2006年 8月 29日

はじめに

この記事では、Drupal コンテンツ管理システムの概要を説明します。共通ビルディング・ブロックを取り上げ、Drupal手法に共通のいくつかの前提について検討します。この記事と以降の連載を読めば、中心となるコンセプトと基本的なDrupal の用語を理解できるようになります。

Drupal コンテンツ管理システムは、そのコンテンツをデータベースに保持します。データベース内では、これらのコンテンツがノードやその他の高位オブジェクト (ユーザー、コメントなど) として保管されます。事前定義されているノード・タイプには、ストーリー、ブログ、投票など、さまざまなタイプがあります。

Drupal が組み立てるページには、1 つ以上の情報がノード、ブロックまたはその他の項目という形式で含まれます。それぞれのページは通常、コンテンツの中央コラムを中心に編成され、左右にサイドバー、そしてヘッダーとフッターがあります。コンテンツの中央コラムを除き、その他すべてのエリアはオプションです。

中央コラムはメインとなるサイト・コンテンツを表示するために使用され、オプション・エリアは追加コンテンツ用に使用されます。Drupalはブロックを使用して、オプション・エリアに細かい情報を載せます。オプション・エリアには通常、ナビゲーション・リンク(もっとも人気のあるノードなど) やその他の要約されたコンテンツが保持されます。すべてのコンテンツと同様に、ブロックにはユーザーのロールによってカスタマイズされた情報を表示できます。

Drupal のもっとも重要な特徴の 1 つは、カスタム・ノード・モジュールを作成して使用可能なノードのタイプ (アプリケーション固有のコンテンツなど)を拡張できることです。モジュールは Drupal の拡張機能で、事前定義されたインターフェースの1 つ以上のフックをインプリメントします。これらのフックによって、ユーザーのアクセス権を定義し、データベースを操作し、そしてコンテンツを編集するためのインターフェースを提供します。

この連載では次回、オンライン・コミュニティーのアナウンスメントを保管して表示するためのカスタム・モジュールを作成します。

Web サイトのナビゲーションを制御するメニュー・システムは、インターフェースを使って完全にカスタマイズできます。一方、menuフック関数は URL の編成方法、URL の変換方法、そして特定の URL からコールされる関数を制御します。menuフック関数とは言っても、実際にはメニューに関するものではないため、Drupalを使い始めたばかりの人はこのフックの名前に惑わされることがよくあります。ですから、Drupalの「メニュー」を扱う際には混乱しないように注意してください。

ページをテンプレートに基づいてテーマ設定するシステムによって、コンテンツを表示から切り離すことができます。ほとんどのコンテンツは、テンプレート(tpl) ファイル一式とテーマ関数を定義することで、簡単に構成したり、スタイルを設定することができます。

ノードはカテゴリーまたはタクソノミーに分けることができます。フォーラムは、タクソノミー内の階層コンテンツの一例です。

すべてのコンテンツへのアクセスは、Web サイトでのアクセスとコンテンツの編集を制御するアクセス権システムによって行われます。

この記事では、この連載のサンプル・オンライン・コミュニティー・サイトに必要な機能をインプリメントした方法を紹介しています。つまり、ここに記載する情報は、Drupalサイトを正常に、または機能的に開発するための絶対的な開発ガイドラインではありません。そのように解釈する代わりに、この記事を独自のサイトを開発する際の比較基準として使ってください。


ノード

Drupal での重要なコンセプトは、すべてのコンテンツをノードとして保管するということです。ノードはシステムの基本ビルディング・ブロックで、これを基礎としてDrupal に保管されたコンテンツを拡張します。開発者は新しいノード・モジュールを作成することによって、それぞれのサイトのニーズに特有の追加フィールドをデータベースに定義し、保管できます。ノードはタイプによって分類されます。各ノード・タイプの操作とレンダリング方法は、そのユース・ケースに基づいて変えることができます。標準ノード・タイプには、以下のものが含まれます。

ページ (Pages)
コンテンツを表示するためのシンプルなノード。PHP を使用して、コンテンツを動的に更新できます。コンテンツのどの部分でも、PHPを組み込むことによって動的にすることができます。
ブログ・エントリー (Blog entry)
オンライン・ジャーナルやウェブログを保持するノード。
フォーラム (Forum)
ノードとそれぞれのコメントのセット。これらのノードは、タクソノミー (taxonomy:分類方法) を割り当てることによってグループ化されます。
ストーリー (Story)
一定の日付が過ぎると期限切れになる汎用ページ。通常のページと同様ですが、スタイルを変えることができます。
コメント (Comment)
特殊なタイプのコンテンツで、他のノードで定義されたコンテンツに関するコメントをユーザーが追加できます。コメントはノードのタイプではないため、データベースの別個のテーブルに保管されます。

データベースは、ノードに関する基本的な情報一式を記録します。この情報には、タイトル、簡単な記述(または要約)、および本文が含まれます。作成者、作成時間、ステータスに関する情報も記録されます。通常、アプリケーションに必要な情報を追加するには、新しいノード・タイプを作成し、そのノード・タイプに対して明確に定義されたデータベース・テーブルに書き込む必要があります。

Drupal は、ノード・コンテンツに加えられた変更に対応するためのリビジョン・システムをインプリメントします。モジュール開発者はこのシステムを使用して、ノード・リビジョン専用に割り振られたテーブル内のそれぞれのノード更新に対する新規データベース・レコードを切り取ることができます。詳細は、Drupal revisions overhaul に記載されています。

タクソノミー

Drupal タクソノミー・システムによって、ノードを分類し、表示された Web ページ上でノード・コンテンツを整理できます。このカテゴリー化を使って、Webサイトのナビゲーションを変更することも可能です。

カテゴリーはタグ (用語) で定義され、用語のセットはボキャブラリーにグループ化することができます。Drupalでは用語によってノード・コンテンツを自動的に分類したり、あるいは関連ボキャブラリーを使用して手動で分類することもできます。また、自由にタグを付けることもできるため、ユーザーがノード・コンテンツに独自の用語を定義できます。

モジュール開発者は、タクソノミー・モジュールに用意されたさまざまな組織化機能を使って、カテゴリー化用語で分類されたノードを利用することができます。このモジュールには、開発者がノード・コンテンツの分類に基づいてページにナビゲーションを追加できるようにする機能もあります。

コメント

コメント・システムは、Drupal のもう一つの特徴です。ノードは、適切なアクセス権を持つユーザー・グループがスレッド化したコメントの添付ファイルを受け付けるように構成することが可能です。これによって、ユーザーはWeb ページに表示された特定のコンテンツに関するコメントを投稿できるようになります。通常、投稿はフォーラムのトピックまたはウェブログのエントリーに表示されます。


ブロック

ブロックとは必要なものを完備した小さな情報単位で、通常はページのナビゲーション・エリアや端の部分に表示されますが、ページ上の任意の場所に配置することもできます。ブロックは、他のノードの表示に組み込まれた情報を提供する小さな表示枠です。コンテンツの表示に使用する基本ブロックは、モジュールが提供します。管理者は既存のブロックに基づいて新しいブロックを作成したり、PHPコードをクエリーに直接書き込んでブロック内のコンテンツをレンダリングすることができます。

図 1 の構成ページでは、以下のことを行えます。

  • 登録されたブロックを有効または無効にする。
  • 重みを割り当て (-10 から 10 で、数値が低いほどグループの上位に表示される)、情報の表示を並び替える。
  • ブロックを左側または右側のサイドバー、ヘッダー、フッター、またはコンテンツ領域に配置する。
図 1. サンプル Web サイトに定義された各ブロックの指定
図 1. サンプル Web サイトに定義された各ブロックの指定

モジュール

モジュールは、Drupal を拡張するための主要なメカニズムです。モジュールは明確なインターフェースをインプリメントするので、新しいモジュールとシステムとの相互作用が可能になります。Drupalでは、このインターフェース内の関数をフックと呼びます。Drupal のフックは、3つのカテゴリーにグループ化されます。この 3 つのカテゴリーのモジュール内での用途は以下のとおりです。

認証 (Authentication)
ユーザー認証メカニズムを追加します。
コア (Core)
コア Drupal コードに応答し、相互作用します。
ノード (Node)
新しいノード・タイプをシステムに提供します。

通常、Drupal を何らかの方法で拡張する必要がある場合には新しいモジュールを作成します。モジュールを使用するのは一般的に、データベース内の標準ノード・テーブルにアプリケーションが必要とする情報が含まれていない場合です。ノード・フックをインプリメントするモジュールは、拡張データベース・スキーマを利用して簡単に必要な情報を保管できます。ここで理解しておかなければならないのは、モジュールはDrupal のすべてのフックをインプリメントする必要はないということです。インプリメントする必要があるのは、拡張機能をインプリメントするために必要なフックだけです。

モジュールを作成したら、そのモジュールを有効にして、定義されている各種のロールにアクセス権を割り当てます。図 2 に、モジュールを有効にする (administer (管理セクション) > modules (モジュール)) 管理者画面の一部を示します。この画面では、announcement (アナウンスメント) モジュールと comment (コメント) モジュールが有効になっています。この管理者インターフェースではチェック・ボックスを使って、カスタムのannouncement モジュールやシステム提供の comment モジュールなどの個別モジュールを有効または無効にします。

図 2. モジュールの有効化
図 2. モジュールの有効化

フック

モジュールをプログラムで定義するには、事前定義された関数の構文に従った固有の関数を作成します。これらのフックによって、システムがページを表示したり、データベースに情報を保管するときに、該当するPHP 関数を呼び出せるようになります。それぞれのフックには一連のパラメーターと戻りの型が定義されています。フックの構文では、定義されたインターフェースの前にモジュール名を追加します。例えば、announcement タイプのノードを削除する場合は、announcement_delete(...) という形になります。このフックによって、カスタム・モジュールがデータベース内に作成した情報を削除し、指定されたノードが削除されるとそのカスタム・モジュールが除去されることになります。

同じく announcement タイプのノードを表示する例は announcement_view(...) で、これによってモジュールは現行ユーザーのアクセス権に応じてコンテンツをフィルタリングします。一方、ページ上にコンテンツを表示する例は、announcement_block(...) です。これによって、モジュールは任意の Web ページ上でブロックとして使われたテーマ設定済みコンテンツの小さなコンテナーを戻します。

ノード・アクセス、データベース操作、そしてテーマ設定を行う基本的なフックのいくつかを以下に簡単に説明します。詳細は、カスタム・アナウンスメント・モジュールについて説明する次回の記事に記載することにします。

ノード・アクセス・フック

このセクションでは、新しいノード・モジュールのために書き込むいくつかの標準的フックを紹介します。フックについての詳細は、Drupalの該当リリースの API 資料に記載されています。ここで説明するフックは、サンプル・アナウンスメント・モジュールから抜粋したものなので、モジュール開発の絶対的手法としてではなく、ガイドとして使用してください。

<module_name>_perm
perm フックは、各ユーザーに割り当てることができるアクセス権のタイプを定義します。ノード・モジュールでは、いくつかの標準タイプを定義しています。
リスト 1. ノード・モジュールの perm フックのインプリメンテーション
function node_perm() {
return array('administer nodes', 'access content', 'view revisions', 
'revert revisions');
}

ノード・モジュールは、ノードの管理、コンテンツのアクセス、リビジョンの表示と復帰を区別します。上記のストリングは、それぞれのユーザーのロールにアクセス権を割り当てるときに使用します(図 4 を参照)。可能な場合は既存のタイプを使いますが、モジュールのセマンティックが異なる場合は、いつでも固有のタイプを作成できます。例えば、アナウンスメント・モジュールで作成操作と編集操作を区別する必要がある場合、create announcement 値と edit announcement 値を配列に指定します。

Drupal 関数の user_access() を使うと、現行ユーザーの特権をこれらのストリング・タイプと照合して簡単にチェックできます。関数user_access('access content') は、現行ユーザーに「access content」特権がある場合 (つまり、ユーザーがWeb サイト上の一般コンテンツを表示できる場合)、TRUE を戻します。この関数は一般的にモジュールの menu フックで使われていますが、アクションを実行する前にユーザーのアクセス権を判断しなければならない場合は、いつでもこの関数を使ってみてください。

<module_name>_access
access フックを使って、コンテンツ上での特定の操作へのアクセスを制限できます。このフックには、アクセス権を判断できるようにノード操作(表示、作成、更新、削除など) とノードを渡します。ブール値を戻すことによって、ノードでの特定の操作を使用可能または使用不可に設定できます。例えば、現行ユーザーがノードの作成者であるかを調べ、このユーザーが現行の操作を実行できるかどうかを判断するとします。このような場合は、リスト 2 に示すコードを使います。
リスト 2. access フックのインプリメンテーション例
function <module_name>_access($op, $node) {
if ($op == 'update' ||  $op == 'delete') {
if ($user->uid == $node->uid or user_access('edit <module_name>')) {
return true;
}
else {
return false;
}
}
}

上記の access フックの例では、要求を開始したユーザーがコンテンツの所有者である場合、または該当タイプのコンテンツを編集する許可が明示的に与えられている場合には、updateまたは delete 操作が許可されます。

ノード表示フック

<module_name>_form
form フックは、このモジュールのノードの追加および編集で使われるテキスト・フィールド、チェック・ボックスなどの入力ウィジェットをモジュールに定義します。この関数の例は、該当リリースの資料に記載されています。

form インターフェースのインプリメンテーションは、Drupal 4.6 と 4.7 では異なります。

<module_name>_validate
モジュールは validate フックを使って、Web サイトに投稿する前にノード内のデータが有効であることを確認します。
<module_name>_submit
validate フックが正常に完了した後、モジュールは submit フックを使って、Web サイトに投稿する前にノード内のデータを変更します。
<module_name>_view
view フックは、モジュールが該当タイプのノードの表示を定義するための方法となります。このフックはまず、別のフックを使ってフィルターを適用することによりノードを準備し、それからテーマ設定プロセスによってHTML を生成します。
<module_name>_menu
モジュールは menu フックを使って処理対象の URL パスを定義します。戻り値は、項目の配列となります。各項目は、固有のURL を定義する関連配列です。戻り値にはさまざまなオプションがあり、ツリー形式の標準のパスの項目にしたり、特定パスをコールするための関数を登録するコールバック項目にすることもできます。

リスト 3 は、menu フック関数の例です。


リスト 3. menu フックのインプリメンテーション例
function <module_name>_menu() {
$items = array();
$items[] = array('path'     => '<module_name>', 
'title'    => t('Module Name'),
'access'   => user_access('access content'),
'type'     => MENU_SUGGESTED_ITEM,
'callback' => '<module_name>_page');
return $items;
}

上記の例が Drupal に指示している内容は、URL //<module_name> を認識し、これをデフォルト・メニュー・リストで使って「access content」アクセス権を持つロールのみがアクセス可能であることを確認し、このURL がブラウザーに表示されたときに <module_name>_page 関数を使用するというものです。

<module_name>_nodeapi
nodeapi フックは、システム内の他のモジュールを操作する必要がある場合に役立ちます。例えば、コメント・モジュールが有効になっている場合、comment_nodeapi() 関数をコールして、ノード・オブジェクトをそのノードに関連付けられたコメント情報に関するデータで拡張できます。nodeapiフックは、表示、データベース・アクセス、検索、検証などの他のイベントにも使用できます。

ノード・データベース・フック

データベース内の node テーブルと node_revisions テーブルには、システムに保管された情報フラグメントに関するほとんどの情報(タイトル、簡単な記述、本文、作成者、作成時刻など) が含まれます。このシステムでは、パフォーマンスやスケーラビリティーを損なうことなく、ノードの多数のリビジョンを保管することができます。新しいノード・モジュールが作成されると、通常は1 つまたは複数の新規テーブルがデータベースに追加されます。データベース・フックは、システム内での新しいノード・コンテンツの作成および変更にあわせて、モジュールがテーブル内のデータを作成、変更、および削除できるようにします。

Drupal にはデータベース抽象化レイヤーがあり、ここにはハイレベル Drupalデータベース API を MySQL や PostgreSQL 用の下位の PHP データベース・モジュールAPI にリンクする複数の関数が含まれています。データベース間の互換性は、ご使用のデータベースに対応したデータベース抽象化API をインプリメントすることによって実現します。

ただし、考慮しなければならない非常に重要なことが 1 つあります。それは、モジュールにデータベース間での真の互換性を持たせるためには、すべてのデータベース・プラットフォームで機能するように極めて慎重にSQL を作成しなければならないという点です。Drupal には、このような互換性問題に対処するためのシステムやメカニズムはまだありません。

<module_name>_load
モジュールは load フックを使って、データベース内の追加テーブルから、該当タイプのノードに関する追加情報をロードします。戻り値は、追加される追加プロパティーが含まれるオブジェクトです。Drupalはロード中のノードにこれらのプロパティーを自動的にマージします。例えば、リスト 4 に示すようなコードになります。
リスト 4. load フックのインプリメンテーション例
function <module_name>_load($node) { 
$additions = db_fetch_object(db_query('SELECT * FROM {<module_name>} WHERE nid = %s', 
$node->nid));
return $additions; 
}

$additions は、システムによってノードのロード結果に自動的にマージされます。

<module_name>_insert
insert フックは、モジュールに対してノード引数のデータベースにデータを追加するようにシグナルを送ります。これは通常、このモジュールに定義されたテーブルにノードに関するデータを挿入するための、データベース抽象化レイヤーに含まれる関数(db_query など) へのコールです。一方、ノード・データベース・テーブルに保管される標準データ(本文、作成日など) は、データベースに自動的に書き込まれます。
<module_name>_update
update フックは、モジュールがデータベース内の既存のノードに関する既存のデータを更新するための方法となります。たいていの場合は、updateフックが、モジュールのノード・リビジョンをサポートするために必要なコードをインプリメントする場所にもなります。
<module_name>_delete
モジュールは delete フックを使って、ノードがデータベースから削除されているときに追加アクションを実行します。この時点で、モジュール固有の任意のテーブルをクリーンアップできます。

URL 設計

Drupal はそのメニュー・システムを使って、Web サイト用のナビゲーションを定義します。カスタム・モジュールをビルドする際には、URLに基づいてコンテンツに対するモジュールの機能方法を指定できます。システムはページ要求を受け取ると、パスの階層構造に基づいてもっとも類似した結果を検索します。パスが登録されている場合、定義された関数をコールバックとして使って、表示するコンテンツが作成されます。パスのあらゆる部分を使って、コンテンツの表示方法を選択できます。例えば、バスが/announcement/15/edit の場合は id = 15 のノードの編集ページが表示され、パスが/announcement/15/view の場合は単にそのノードのコンテンツが表示されます。コールバックは、<module_name>_menu フックで定義されます。

Drupal には、タブ付きインターフェースの使用を定義するためのメカニズムもあります。これらのタブは、メニュー・システムに「ローカル・タスク」として指定されています。ローカル・タスクを定義する際には、そのうちの1 つをデフォルトとして指定できます。デフォルトのローカル・タスクは、ユーザーがコンテンツの一部を表示するときに最初に表示されるタスクです。この機能を使う場合は、常にデフォルトのローカル・タスクを指定することをお勧めします。

<module_name>_menu は、メニュー仕様の配列を戻します。リスト 5 のコード・スニペットは、上のコードの繰り返しですが、そのような仕様の 1つで、標準的なカスタム・モジュールに対する単純なコールバックを定義します。

リスト 5. menu フックのコード・スニペットの 1 項目
$items[] = array('path'     => '<module_name>', 
'title'    => t('Module Name'),
'access'   => user_access('access content'),
'type'     => MENU_SUGGESTED_ITEM,
'callback' => '<module_name>_page');

上記の仕様には、以下の属性が含まれています。

パス (path)
URL 要求と一致する場合、この項目を使っています。
タイトル (title)
メニュー項目のタイトルです。
アクセス (access)
この属性の値によって、現行ユーザーがこの項目で指定されているコンテンツにアクセスできるかどうかが決まります。
タイプ (type)
メニュー仕様のタイプです。
コールバック (callback)
この項目の使用中に表示されるコンテンツを作成するためにコールされる関数です。

メニュー仕様には、以下のようにさまざまなタイプがあります。

MENU_NORMAL_ITEM
メニュー項目に使われるデフォルト・タイプで、メニュー・ツリーに表示されます。
MENU_ITEM_GROUPING
アクセスするサブ・ページを単にリストにした項目のグループ化。
MENU_CALLBACK
URL へのアクセス時に表示するコンテンツを生成するために呼び出される関数を登録します。
MENU_SUGGESTED_ITEM
モジュールからの推奨項目で、管理者が有効にできます。
MENU_LOCAL_TASK
これらのページはタブとしてレンダリングされます。別のレンダリングも可能です。
MENU_DEFAULT_LOCAL_TASK
ローカル・タスクのすべてのセットは、クリック時にそのセットの親として同一のパスにリンクするデフォルト・タスクも提供していなければなりません。

ユーザー

システムのもう一つの最上位オブジェクトはユーザー・オブジェクトで、これによってサイトに訪れるユーザーごとにアカウントをセットアップできます。管理者は、コンテンツに対するアクセス権ごとに、異なるロールを作成する必要もあります。ロールを作成すると、ユーザーをそのうちの1 つまたは複数に割り当てることができます。Drupal システムの構成時に最初に作成するユーザーが、システム内のすべての設定を変更する許可を持つ唯一のアカウントになることに注意してください。

管理者は、Drupal 管理インターフェースのアクセス制御セクションで定義されたさまざまなロールにユーザーを割り当てることができます。図 3 に、サンプル Web サイト用に作成したロールを示します。この例には、標準のロールの他に、管理者、カスタマー、操作、ワークグループ・リーダー用のロールが追加されています。

図 3. コンテンツに対するユーザー・アクセス・レベルを指定するロールの定義
図 3. コンテンツに対するユーザー・アクセス・レベルを指定するロールの定義

図 4 に、モジュール内のアクセス権にロールを割り当てる管理者画面を示します。管理者のインターフェースではチェック・ボックスを使って、特定のロールに対して、モジュール関連のアクションへのアクセス権を有効または無効にします。この表の上部にはロール、左端の列にはモジュールごとにグループ化されたアクセス権をリストしています。これらのアクセス権は、<module_name>_perm フックで指定されたストリングです。ユーザーをロール別に分類すると、ユーザー・クラスごとにタスクの担当を分けることができます。

図 4. アクセス権を有効または無効にする管理者インターフェース
図 4. アクセス権を有効または無効にする管理者インターフェース

Web サイトの外観をカスタマイズする方法

Drupal では、テーマ・システムを使ってコンテンツを表示から切り離します。Drupal内のさまざまなテーマ・エンジンを使って、コンテンツにテーマを設定できます。PHP を使ってテーマ全体をプログラムすることもできますが、テーマ・エンジンは開発用フレームワークを提供するため時間の節約になります。Drupalサイトには現在、PHPTemplateXTemplate、および Smarty テーマ・エンジンが用意されています。このサンプルでは、ロジック層、モジュールおよびプレゼンテーション層全体で一貫してPHP を使うことができるデフォルトの PHPTemplate エンジンを使うことにしました。

標準テーマ関数

モジュール開発者にとって重要なのは、コアとなるテーマ・コードがどのように該当するテーマ・メソッドを検索するかを理解することです。モジュールは、他のシステム・インプリメンターがそのモジュールのコンテンツをそれぞれのルック・アンド・フィールに統合できるように作成しなければなりません。Drupalでは現在、テーマ設定されたコンテンツをビルドする 3 つの PHP 関数を以下に記載した順に検索します。

  1. <theme name>_<content name>
    この関数は、現在のテーマの名前とコンテンツの名前、またはテーマの設定対象となっているノード・タイプで構成されます。現在のテーマの名前がibc で、announcement というコンテンツにテーマを設定している場合、テーマ関数の名前はibc_announcement となります。
  2. <theme engine name>_<content name>
    この関数は、現在のテーマ・エンジンの名前とコンテンツの名前で構成されます。この記事ではPHPtemplate エンジンを使っているため、announcement というコンテンツのテーマ関数の名前はphptemplate_announcement となります。
  3. theme_<content name>
    これは最後に検索される関数で、もっとも単純です。announcement というコンテンツにテーマを設定している場合、このテーマ関数の名前は単純にtheme_announcement となります。

テーマを定義するには、Drupal の theme ディレクトリーに含まれる一連のファイルを使います。Drupal にはテーマに必要なファイルが付属していますが、これらの付属ファイルは使用するテーマ・エンジンによって異なります。テーマのカスタマイズ方法はさまざまです。ここでは、データの表示を変えて、Webサイトにふさわしい xHTML 構造、スタイルおよびレイアウトにする方法を紹介します。

テンプレート・ファイル

PHPTemplate を使うと、テンプレート・ファイルと呼ばれる特定のファイルを、Drupel内の特定の関数とモジュールにマッピングすることができます。.tpl.php という拡張子が付いたテンプレート・ファイルは、関連付けられた関数またはモジュールから渡されたデータの配列を使います。このデータをWeb ページに表示するには、PHP および xHTML を使って操作します。既存のノード・タイプを対象とした汎用テンプレートで、テーマをカスタマイズできるようにDrupal が提供しているもがあります。汎用テンプレートには、page.tpl.php、node.tpl.php、およびcomment.tpl.php ファイルなどがあります。これらのファイルは、theme ディレクトリー内にあります。pageテンプレートで定義したページを使って、node テンプレートで定義されたノードの表示を含めます。

page.tpl.php
独自のテーマのカスタマイズは、おそらくこのファイルから開始することになります。これは、Drupalが表示するコンテンツのすべてのページ構造を定義するテンプレートです。このテンプレートでは、xHTMLのグローバル構造エレメント (見出しエレメントや本文エレメントなど)、スタイル・シート用のインクルード・ファイル、コンテンツ・レイアウトのセマンティックをセットアップするスケルトンDIV エレメントなどを定義できます。
node.tpl.php
このテンプレートは、ノード・データの表示方法を操作するために使われます。特定タイプのノードにテーマを設定する場合は、node.tpl.phpファイルのコピーを作成し、ファイル名を node-<type>.tpl.php に変更します(ここで、<type> はノード・タイプの名前です)。この方法を使った IBCWeb サイトの discussions セクションでは、node-forum.tpl.php という名前のテンプレートを使ってフォーラム・コンテンツのデフォルト・レイアウトを変更しています。
comment.tpl.php
このテンプレート・ファイルは、単一のコメントのレイアウトを制御します。コメントをページに追加するには、Drupalのコメント・モジュールを使います。

既存のテーマ関数のオーバーライド

PHPtemplate エンジンを使って、テンプレートを特定のテーマ関数にマッピングできます。テーマ関数は、Drupal のコア関数を提供するモジュール、または Drupal を拡張する独自のモジュールで使用されるWeb コンテンツをビルドする汎用メソッドを提供します。

テーマ関数の一例は、theme_links 関数です。xHTML アンカー・エレメント (リンク)の配列を指定すると、theme_links はこれらのリンクを指定の文字で区切ったストリングを戻します。これは、非常に単純なビルディング・ブロックの例です。リスト 6 では、値 links のクラス属性を持つ DIV 要素がリンクの区切りリストをラップするように出力を変更しています。

個別テーマのディレクトリー内で使うことができる、template.php という名前の特殊なファイルがあります。このファイルが存在する場合、Drupalはこれを使って、テーマ・システムのデフォルト・アクションをオーバーライドできるようにします。template.phpファイルには、リスト 6 に示す関数を作成することができます。

リスト 6. template.php ファイルの phptemplate_links 関数
function phptemplate_links($links, $delimiter = ' | ') {
if (!is_array($links)) {
return '';
}
$content = '<div class="links">';
$content .= implode($delimiter, $links);
$content .= '</div>';
return $content;
}

上記の例では、template.php ファイル内に phptemplate_links 関数を作成して、Drupalにデフォルトの theme_links 関数をオーバーライドするように指示しています。オーバーライド関数では、リンクの区切りリストを DIV 要素でラップして、結果のストリングを戻しています。

新しいモジュール内でのテーマ設定

新しいモジュールを作成して Drupal 関数を拡張する場合、そのモジュールが生成するデータの表示方法をDrupal に指示する必要があります。これを拡張可能にするには、誰がどのテーマ・システムを選択したとしても、そのテーマ・システムによって簡単にオーバーライドされるデフォルトのデータ表示をモジュール内にセットアップするのが賢い方法です。

デフォルト表示を作成する

デフォルト・テーマによる出力を生成するため、モジュール内に関数を作成しました。この関数が、渡されたデータを操作し、テーマが設定されたコンテンツをストリングで戻します。例として、モジュール内にshopping_list_items という項目リストのテーマを設定します。このテーマが設定された出力は、リスト 7 に示すように xHTML の順不同の項目リストになります。

リスト 7. 順不同項目リスト出力のテーマ設定
function theme_shopping_list_items($list = array()) {
$content = '<ul>';
foreach ($list as $list_item) {
$content .= '<li>'.$list_item.'</li>';
}
$content .= '</ul>';
return $content;
}

次に、モジュールの Web ページをビルドする部分で、Drupal テーマ関数を使ってtheme_shopping_list_items 関数をコールし、テーマが設定されたリストを戻します。実行シーケンスは、リスト 8 に示すようになります。

リスト 8. 項目リストのテーマ設定
$tools = array('hammer', 'drill', 'saw');
$content .= theme('shopping_list_items', $tools);

モジュールの出力をテーマでオーバーライドする
前述したように、テーマ関数は theme ディレクトリー内にある template.phpファイルを使ってオーバーライドできます。上記の例で、shopping_list_itemsモジュールが生成する項目リストのデフォルト表示アクションを、xHTML 順不同リストからxHTML 定義リスト (リスト 9 を参照) に変更するとします。この関数は、template.php ファイルまたはノード・モジュール・ファイル内に作成します。

リスト 9. 定義リスト出力のテーマ設定
function phptemplate_shopping_list_items($items = array()) {
$content = '<dl>';
foreach ($items as $list_item) {
$content .= '<dt>'.$list_item.'<dt>';
}
$content .= '</dl>';
return $content;
}

Drupal はこの例のテーマが PHPtemplate エンジンを使っていることを認識しているので、shopping_listモジュール内の theme_shopping_list_items 関数をこの新しい関数で自動的にオーバーライドします。その結果、ツールのxHTML 定義リストが表示されます。このステップをもう一歩進歩させ、リスト 10 に示すようにこのオーバーライド関数内にファイル名を定義して、テンプレート・ファイルを使ってこのリストの表示を定義するようにDrupal に指示することもできます。

リスト 10. テーマ設定にテンプレート・ファイルを使う関数
function phptemplate_shopping_list_items($items = array()) {
$template_file = 'shopping_list_items';
$content = _phptemplate_callback('shopping_list_items', 
array('items' => $items), $template_file); 
return $content;
}

テンプレート・ファイルには、以下のコードが含まれます。

リスト 11. 項目リストを表示するためのテンプレート・ファイルのコード・フラグメント
<dl>
<?php foreach ($items as $list_item) : ?>
<dl><?php print $list_item; ?></dl>
<?php endforeach; ?>
</dl>

ここでは、PHPtemplate エンジンのナレッジを使っています。_phptemplate_callback 関数は、変数 list_items$items配列に設定して、 phptemplate_shopping_list_items コールを shopping_list_items.tpl.php というテンプレート・ファイルに接続します。このようなコンテンツのテーマ設定方法は大量のPHP コードを xHTML から明確に区別するため、お勧めです。

コンテンツの構造とスタイル

このセクションでは、Drupal のテーマ・システムを使ってコンテンツのデフォルト表示を拡張および変更する方法を説明します。もう一つの見地は、各ノードで生成されるコンテンツの構造と、その構造に適用される全体的なスタイルを考慮することです。PHPtemplateエンジンを使うと、Web 設計者はモジュールで生成されたデータの構造化 xHTMLを保持できます。またこのオプションでは、CSS (Cascading Style Sheets) を使って、この構造の表示またはスタイルを変えることもできます。データの作成はモジュール内で行い、xHTMLの作成はテンプレート内で行うという方法を保って、データと表示の区別を維持するには、このオプションが役立ちます。

Web ページ全体の構造は、theme ディレクトリー内の page.tpl.php ファイルによって形成されます。このファイルでは、xHTMLを使って、他のノード・コンテンツが組み込まれる Web ページの基本レイアウトを定義できます。サンプルWeb サイトでは、ヘッダー、本文、サイドバー、フッターの位置は同じなので、これらの構造はこのファイルで定義します。

ナビゲーション可能な要素やセクションの見出しなどには正しい xHTML マークアップを使用するように気を配りました。そのため、スタイル設定が使用できない場合でも、サンプルWeb サイトのコンテンツは適切なフォーマットで表示されます。

Drupal テーマ・システムがこのテンプレートに提供する変数の 1 つは、トップ・ページが表示中であるかどうかをフラグで示します。サンプルではこの変数を最小限のPHP で使って、Web サイトのサブページとはわずかに異なるように xHTML が構成されています。

前のセクションで説明した方法を用いて、ノードが生成するすべての出力にはテンプレート・ファイルが使われているため、コンテンツ構造は1 つの theme ディレクトリーで制御されます。各ノード・テンプレートの出力は一貫性を持って構成され、通常の場合はDIV 要素内に含められます。使われているテンプレートは、この要素のクラス属性値が記述します。これはコンテンツのスタイル設定に役立っただけでなく、どのテンプレートが何のコンテンツを生成したかを判断するためにページ・ソースを調べる際にはデバッグの助けにもなりました。

page.tpl.php ファイルで定義されているように、画面および印刷のメディア・スタイル・シートはWeb ページのヘッダーで参照しました。スタイルのカテゴリー化と管理には、CSSスタイル設定を個別のファイルに分割し、これらのファイルをメイン画面のメディア・スタイル・シート・ファイルに組み込む方法を採りました。

Drupal Web サイトの構成とスタイル設定に用いた手法については、今後の記事で詳細を説明したいと思います。

共通の xHTML エレメントの基本スタイルは 1 つの CSS スタイル・シートに含め、レイアウトは別のCSS スタイル・シートに含めました。また、Web サイト・セクションのスタイルの変更はそれぞれ独自のファイルに含めました。テンプレート・ファイルと同様に、すべてのスタイル・シート・ファイルはtheme ディレクトリー内に配置しています。


ノードの作成シーケンス

このセクションでは、Drupal によるノードの作成方法について説明します。Drupalが特定のノードに関する情報を収集するシーケンス、コア・システムがモジュールを操作する方法、そしてこのノードがWeb ページ内に表示される前にレンダリングされる方法を理解しておくと便利です。ここでは概要を説明することを目的としているので、詳細はこのセクションでは取り上げません。図 5 に、ノードの作成シーケンスの流れを示します。

図 5. 表示操作のためのノード作成シーケンスの概要

URL 要求に含まれるパスは、/node/ (ノード・タイプ) の後にノード ID、次にノード操作が続くように構成されます。/node/15/editのパスは、Drupal にノード 15 の編集フォームを表示するように指示します。この構造の唯一の例外は、/node/addまたは /node/add/<node_type> のURL パスで、この場合、Drupal は追加操作を行うことを認識します。

このセクションでは、以下の 4 つの操作について説明します。

表示
これはデフォルト操作で、表示専用のノード・ページを作成します。
追加
この操作は、新しいノードの追加に使用するフォームを表示します。
編集
この操作は、既存のノードを編集するためのフォームを表示します。
削除
この操作は、ノードを Drupal システムから削除します。

前にも説明したように、メニュー・システムでは、指定された URL に基づいて、どの関数を呼び出すかを認識します。このノード作成シーケンスの説明は、メニュー・システムがnode.module の node_page() 関数をコールするところから始めます。

Drupal にまず必要なのは、操作を判断することです。URL でノード ID の後に操作が指定されていない場合は、デフォルトの表示操作が適用されます。

表示操作

ノードのレンダリングを開始するには、まず、このノードに関連するすべてのデータのために、コンテナーを作成する必要があります。このノード・オブジェクトには、ノードのデータベース・レコードからのデータと、主キー・フィールドにURL 内のノード ID と同じ値が設定された node_revisions テーブルからのデータが入力されます。このデータには、ノード・タイプ、タイトル、簡単な記述、本文、作成者、および作成時間が含まれます。

このノード・オブジェクトには次に、すべての拡張データが適用されます。これを行うのは、loadフックと nodeapi フックです。nodeapi フックは、モジュールが Drupal のコア操作 (ロード、表示、作成、削除) を拡張するもう一つの方法となります。

node_type はすでに既知であるため、Drupal はこれを使って、<node_type>_load() 関数が存在するかどうかを判断します。例えば、ノード・タイプが announcementだとすると、announcement_load() 関数をコールします。これによって、デフォルト・ノード情報に、この announcementの公開日と有効期限が追加される場合があります。

次に、Drupal は使用可能なすべてのモジュールで nodeapi フックを呼び出します。この関数コールに組み込まれる引数は、Drupalがノード・オブジェクトをロード中であるため、このオブジェクトに任意のモジュールから抽出したデータを挿入できることを示します。

Drupal は続いて、ノード・タイトルを保管します。このノード・タイトルは、要求のライフ・サイクル後期にWeb ページ・タイトルに組み込むために使用できます。

この時点で、Drupal はノード・オブジェクト・データのコンテンツへのレンダリングを開始します。デフォルト・アクションは、テーマ設定されたノードの本文と簡単な説明の出力を作成し、保管することです。Drupalが<node_type>_view() フックを検出した場合は、このフックはコールされ、これらのコンテンツ・フラグメントのテーマ設定をオーバーライドします。例えば、ノード・タイプがannouncement だとすると、announcement_view() 関数は、本文と簡単な記述のコンテンツの他、announcement の公開日と有効期限のテーマ設定済みコンテンツ・フラグメントを戻します。

このシーケンスのロード部分と同様に、Drupal は使用可能なすべてのモジュールでnodeapi フックを検索します。この関数コールに組み込まれる引数は、Drupal がノード・オブジェクト・データにテーマを設定中であり、任意のモジュールがテーマ設定された本文フラグメント内のコンテンツを変更または拡張できることを示します。その後、ノード・オブジェクトに保管されます。

リンクも、ノード・オブジェクトに追加できるコンテンツ・タイプです。リンクはテーマ設定されたWeb リンクをノード・コンテンツに追加するため、Drupal では、モジュールがlink フックを使ってこれらのリンクを追加することが可能になります。

Drupal は次に、このノードに対してコメントをレンダリングする必要があるかどうかをチェックします。ノード・オブジェクトに保管されたコメント・データを使って、コメントをノード・オブジェクトに保管する前に必要なコンテンツ・フラグメントにテーマを設定します。

最後に、データとテーマ設定されたコンテンツを持つノード・オブジェクトがテーマ・システムに渡され、テーマ設定されたノードとしてレンダリングされます。この時点でノードは完全に作成され、表示可能な状態になります。

追加操作

URL パスが Drupal にノードを追加するように指示している場合、ノードの作成シーケンスは表示操作の場合とはかなり異なります。まず、Drupalは URL パスにノード・タイプが含まれているかどうかを調べ、含まれている場合には、この要求を行っているユーザーに新規ノードの作成が許可されているかどうかを判断します。これらの条件が満たされると、Drupalはこのタイプのノードを追加するために必要なフォームのアセンブルを開始します。条件が満たされない場合は、使用可能なノード・タイプをリストするページが表示されます。

表示操作の場合と同様に、Drupal はフォームのレンダリングに使うことができるデータを保管するためのノード・オブジェクトを作成します。Drupalはこのノード・オブジェクトのデータ表示の一環として、<node_type>_prepare() フックが存在する場合はこのフックを呼び出します。これは、関連付けられたモジュールが、ノード・オブジェクトに組み込む必要があるデータを前処理する機会を提供します。

その他のモジュールがノード・オブジェクトを追加または変更できるようにするため、Drupalが追加フォームを作成中であることを示す引数を使って、すべての nodeapi フックがコールされます。

ノード・オブジェクト・データがある場合、Drupal はフォーム・ウィジェットを記述するデータ構造を作成します。フォームをレンダリングする前に、すべてのモジュールで<module_name>_form_alter() フックを呼び出し、フォーム・データ構造を変更できるようにします。

Drupal のフォーム処理方法については、ここでは説明しませんが、今後の記事で取り上げる予定です。

例えば、タクソノミー・モジュールは form_alter フックを使って、このノード・タイプ用に定義された分類用語を選択するためのフォームを挿入できます。

最後に、フォーム・データ構造を使って、フォームを表示するノードがレンダリングされます。

編集操作

編集操作は追加操作と似ていますが、URL パスにノード ID が指定されるという点が異なります。

編集操作を使ってノードを作成する際に、Drupal はもう一度、結果ページのレンダリングに使うデータを保管するためのノード・オブジェクトを作成します。表示操作で説明したように、<module_name>_load フックと <module_name>_nodeapi フックをコールし、モジュールがノード・オブジェクト・データを追加または変更できるようにします。指定されたノードID がデータベース内になかったり、あるいはこのノードを編集するためのアクセス権が許可されていない場合は当然、Drupalメッセージ・システムによって該当する応答が Web ページ上に表示されます。

ノード ID があり、編集へのアクセスが許可されている場合、Drupal は追加操作と同様のプロセスに従います。ただし、ノード・オブジェクトには指定されたノードに関連付けられたデータが含まれるため、フォーム・データ構造がレンダリングされるとフォーム・ウィジェット内の該当データが表示されます。

削除操作

Drupal が削除操作を処理する方法は、他の操作とは多少異なります。表示操作や編集操作と同じようにノード・オブジェクトが作成されてデータが入力されますが、このデータは、ノード・データを編集するためのノードやフォームをレンダリングするような方法では使われません。このデータは、ノードを削除するためのアクセス権をチェックするために使われます。Drupalは次に単純なフォームを作成し、このフォームがユーザーに削除操作の確認を求めるWeb ページにレンダリングされます。

このセクションの要点は、Drupal のフォームを送信するプロセスを説明することではありませんが、モジュールがこのノード・タイプに関連付けられた拡張データベース・テーブルや変数をクリーンアップするために<module_name>_delete フックと <module_name>_nodeapi フックが呼び出されることを知っておくと便利です。


まとめ

この記事では、Drupal のコンセプト、用語、そして手法を紹介しました。これらのビルディング・ブロックが、Drupalアプローチの中核となります。この記事では以下について説明しました。

  • ノード
  • ブロック
  • モジュールとフック・インターフェース
  • URL 設計とメニュー・システム
  • ユーザーとアクセス権
  • Web サイトの外観のテーマ設定
  • ノードの作成シーケンス

これは部分的な紹介にすぎませんが、これらのコンセプトは Drupal の内部処理を理解する上で重要になります。基本さえ理解できれば、環境およびWeb サイトのカスタマイズに取り掛かりやすくなります。

この連載の第 6 回では、Drupa をカスタム・モジュールで拡張し、サンプル Webサイト用のアナウンスメントを作成します。特定のコード・サンプルを含め、モジュールについての知識がますます深まるはずです。

参考文献

学ぶために

製品や技術を入手するために

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Sample IT projects, Open source
ArticleID=250131
ArticleTitle=オープン・ソース・ソフトウェアによる協調 Web サイトの設計、開発、デプロイメント第 5 回: Drupal 入門
publish-date=08292006