開發、把玩 drupal 指引

Drupal擁有極佳的彈性,允許各種類型的應用,然而在開始動手前,總是得先瞭解一下Drupal的核心運作原理,如此以來才會得心應手。

下面蒐集了一些把玩Drupal的範例和教學,如果你覺得這裡無法滿足你的需求,還可以至drupal.org瞧瞧,找找看是否有對你有用的資訊。

Drupal API: 提供Drupal核心函式的範例和說明
http://drupaldocs.org/api

Development for Drupal:官方為開發者提供詳盡的說明文件
http://drupal.org/contributors-guide

我是初學者,想要自行開發該如何上手

大家好,我是最近才接觸CMS的新手,在試著安裝過 joomla、e107、xoops之後,最後透過友人的推薦,決定使用drupal來架設網站,目前網站己經架設完成,但是非常陽春,我還是個初學者,其實很多東西也不是了解,市面上好像也沒有drupal的書,可是我想要自己開發新的模組(或修改現成的模組)建立自己的網站,我該從何上手呢,有沒有什麼資料值得我先閱讀的,我想從基本學起,也許只是從自己寫一個功能簡單的搜尋關鍵字的模組開始,慢慢漸入佳境,可是目前對drupal架構完全沒什麼概念,光是看原始碼我就有點頭痛了,所以想自己寫一些簡單基本的功能,有沒有人可以給我一些意見要如何上手。 謝謝

Code Snippets : 計算Views生成資料的總數

Views用來「列資料」很棒,可是如果要做一些統計、計算,雖然有些模組可以用(像是Views Cul等等),但其實都不是很好的解決方案。不過Views的API非常齊全,所以只要了解Views的寫法,也是可以不需要自己寫query喔!

以下範例是算出某個Views的total rows,也就是該Views的總數。
實際應用舉例:
獎金獵人的「目前進行中比賽有XXX件」這樣的區塊。

<?php
$view = views_get_view( 'VIEWS_NAME' );
$view->get_total_rows = TRUE;
$view->execute();
$count = $view->total_rows;

print '目前進行中比賽有' . $count . 'XXX件';
?>

如果你的狀況需要設參數的話:

<?php
$view = views_get_view( 'VIEWS_NAME' );
$view->set_arguments( array( 1, 2, 3 ) ); //參數設在這
$view->get_total_rows = TRUE;
$view->execute();
$count = $view->total_rows;

print $count;
?>

如果希望最後輸出可以依照總數改變的話:

<?php
$view = views_get_view( 'VIEWS_NAME' );
$view->set_arguments( array( 1, 2, 3 ) ); //參數設在這
$view->get_total_rows = TRUE;
$view->execute();
$count = $view->total_rows;
if ($count > 0) {
$output = format_plural($count,
'只有 1 個',
'總共有 @count 個');
print $output;
}
?>

drupal_foo

drupal_foo的意思,就是drupal很多函數裡面以「drupal_」為開頭名稱的,會以drupal開頭的函數,表示很常被模組的開發者使用,也特別的重要。

幾乎所有的drupal_foo函數,都會在下面這些位置找到。
include/bootstrap.inc
include/common.inc

也建議模組開發者,可以善加利用drupal核心提供的API,並注意不要自行在模組裡新增drupal_foo的function,以免混淆。

這個單元是許多熱心的貢獻者,將許多drupal_foo函數寫上中文說明,每個drupal_foo的文件說明裡裡通常會包含:定義、描述、參數、傳回值、範例、程式碼...等等。若您也知道一些drupal_foo的用途,或是看到有錯誤的地方,歡迎直接共筆編輯,幫忙加上說明和解釋。

Drupal版本:

drupal_add_js

定義

drupal_add_js($data = NULL, $type = 'module', $scope = 'header', $defer = FALSE, $cache = TRUE)
includes/common.inc, 第 1600 行之後

描述

加入一個 JavaScript 的檔案、設定、或是程式碼到頁面中

根據不同的參數,會有不同的結果產生。一般,它會加入 JavaScript 的"檔案連結"或是"直接貼入程式碼"到網頁裡。以下列出不同的結果:

  • 加入一個檔案('core', 'module' and 'theme'): 加入一個連結到網頁中。排列的順有一定的規則, 'core' 最前面,再來是 'module' ,最後是 'theme',就算加入 的時間比較晚,還是會依這個順序排列。
    例:

    drupal_addjs('js1.js', 'module');
    drupal_addjs('js2.js', 'core');

    因為 'core' 的順序在前面,所以 js2.js 會排在 js1.js 前面,會優先被讀入。
  • 加入一段程式碼 ('inline'): 貼入程式碼,如果目的不是引用一個 *.js 的檔案,而是一段 JavaScript 程式碼,就用這個參數。例如,開一個新視窗,或是跳出訊息視窗顯示一段訊息。
  • * 加入一個設定 ('setting'): 傳入一個變數的值。這個值會被放到名為 Drupal.settings 的陣列中,讓其它部分的 JavaScript 去呼叫它。

參數

$data (選擇性) 如果用到這個參數,它的內容的和後面的 $type 參數有關:

  • 'core', 'module' 或 'theme': 一個由網站根目錄算起的相對路徑(*.js 檔案)
  • 'inline': 一段 JavaScript 程式碼
  • 'setting': 一個關連陣列,將會被轉換為 Drupal.settings 下的物件

$type (選擇性) 一個表示要放到網頁裡的 JavaScript 是哪一種類型。可以是 'core', 'module', 'theme', 'inline' 和 'setting'。你也可以自訂一個名稱,這時 $data 的內容會被視為一個 JavaScript 檔案(*.js)路徑。預設值是 'module'
排列順序: 'core' -> 'module' -> 'theme' -> 'setting' -> 'inline' -> [自訂的名稱]

$scope (選擇性) JavaScript 程式要放置的位置,預設可以是 'header'(開頭) and 'footer'(節尾). 如果你所用的版型有定義其它的位置,也可以使用它們放到這個參數中。

$defer (選擇性) 如果為 TRUE,在 標籤中會加入 defer 屬性,預設為 FALSE。這個參數在 $type 是 'setting' 沒有作用。

$cache (選擇性) 如果設定為 FALSE, JavaScript 檔案會在每一次呼叫頁面的時候重新讀取,預設值是 TRUE,這個參數只對 $type 表示引入的是一個 JavaScript 檔案有用

值回值

如果第一個參數為 NULL,就會傳回和 $scope 同樣參數 JavaScript 陣列回來

範例

1. 引入檔案

drupal_add_js('test1.js', 'module');
drupal_add_js('test2.js', 'theme');
drupal_add_js('test3.js', 'core');

結果網頁原始檔的開頭部分可以找到類似下面的程式碼


由於 core 的較優先,所以會被排在前面先被讀入。

2. 加入程式碼 & 加入設定

drupal_add_js('alert("var1 = "+Drupal.settings.var1);', 'inline');
drupal_add_js(array('var1' => 123, 'var2' => 456), 'setting');

執行的時候,會跳出一個訊息視窗,顯示「var1 = 123」。

原始碼

<?php
function drupal_add_js($data = NULL, $type = 'module', $scope = 'header', $defer = FALSE, $cache = TRUE) {
if (!is_null($data)) {
_drupal_add_js('misc/jquery.js', 'core', 'header', FALSE, $cache);
_drupal_add_js('misc/drupal.js', 'core', 'header', FALSE, $cache);
}
return _drupal_add_js($data, $type, $scope, $defer, $cache);
}
?>

Drupal版本:

drupal_get_path

定義

drupal_get_path($type, $name)
includes/common.inc

描述

可用此函數取得模組、版型或版型引擎(theme engine)的路徑

參數

$type: 要尋找的種類 (如: theme, theme_engine, module)
$name: 尋找東西的名字

傳回值

要找東西的路徑(網站相對路徑,不是伺服器的系統路徑)

範例

當你開發的模組裡面有圖片,或是由其它需要引入的 JavaScript 的時候,那就很有用了。因為你不能確定你的模組會被放在哪裡。
有可能在 modules/ 下,也可能在 sites/all/modules/ 下面。這時候就需要用它來幫你找出模組的路徑,好引入它資料夾裡面的檔案。
如:

$my_module_path = druapl_get_path('module', 'my_module');
$my_theme_path = druapl_get_path('theme', 'my_theme');
$phptemplate_engine_path = druapl_get_path('theme_engine', 'phptemplate');

附帶一提的,如果使用了「簡潔網址」,就是網址不出現 ?q=xxx 的模式。連結使用相對路徑的時候,可能會出現找不到檔案的情形。(瀏覽器被網址列的網址搞混了)
這個時候建議搭配 base_path() 這個函數,寫一個相對於網頁伺服器的"絕對路徑"。
如:

$my_module_path = base_path() . drupal_get_path("module", "my_module");
// 如果網站是在 drupal 資料夾下, base_path() 會傳回 "/drupal"

路徑用 "/" 開頭,就表示由網頁伺服器的"根"開始算相對路徑。
如: "/drupal/my_module/test_page/"

不過如果使用 drupal_add_js() 函數,因為它本身就會加上網站本身的路徑,所以就不需要加上 base_path()了。

程式碼

<?php
function drupal_get_path($type, $name) {
return dirname(drupal_get_filename($type, $name));
}
?>

Drupal版本:

drupal_set_message

定義

drupal_set_message($message = NULL, $type = 'status')
includes/bootstrap.inc

描述

定義一組訊息,以反映剛執行命令的狀態

參數

$message: 要顯示的訊息
$type: 類別,可以是

  • 'status'
  • 'error'

傳回值

所有已經定義的訊息

範例

例如要告訴使用者”你己經成功註冊了”:
<?php
drupal_set_message(t('Created a new user account. No e-mail has been sent.'));
?>

一個綠色的方框就會出現在下一個用戶的可視頁面內

開發者不用操心顯示部份

又例如要顯示一個錯誤:
<?php
drupal_set_message(t('Invalid password.') , 'error' );
?>

後面的一個參數如果為'error'
一個錯誤的紅色框就會出現

註:第一個參數可以為html
註:t() 函數指可翻譯的

程式碼

<?php
function drupal_set_message($message = NULL, $type = 'status') {
if ($message) {
if (!isset($_SESSION['messages'])) {
$_SESSION['messages'] = array();
}

if (!isset($_SESSION['messages'][$type])) {
$_SESSION['messages'][$type] = array();
}

$_SESSION['messages'][$type][] = $message;
}

//如果發生資料庫連接錯誤,則訊息不會被記錄
return isset($_SESSION['messages']) ? $_SESSION['messages'] : NULL;
}
?>

附加檔案大小
Image icon drupal_set_message_1.JPG6.59 KB

Drupal版本:

drupal_set_title

定義

drupal_set_title($title = NULL)
includes/path.inc, 第 187 行之後

描述

設定頁面的標題文字(不是網站標題)

參數

$title 選擇性參數,如果為 NULL 則不會改變概有的 標題內容。

傳回值

沒有傳回值,但會改變當下頁面的標題。

範例


drupal_set_title("新標題");

當同一個頁面要有動態標題內容時…
如︰大家都看到都是同樣"我的帳號"連結,但是開啟後的頁面標題變成自己的帳號名稱。
就需要例用 drupal_set_title 去修改頁面的標題,不然預設標題的文字和連結名稱是一樣的。

原始碼

<?php
function drupal_set_title($title = NULL) {
static $stored_title;

if (isset($title)) {
$stored_title = $title;
}
return $stored_title;
}
?>

Drupal版本:

module_foo

看名字就知道這些函數是用來處理和模組有關的事情。
如果今天開發的模組需要和其它的模組互動,或是"相依"在其它的模組之上,或是要觸發其它模組的 hook 。
那麼這個部分的函數就要花時間去研究。

Drupal版本:

module_exists

定義

module_exists($module)
includes/module.inc

描述

判斷某個模組是否存在(並已啟用)

參數

$module: 模組的名字(不要帶上 .module 的副檔名).

傳回值

如果該名稱的模組已安裝且正被啟用,則傳回 TRUE 。

程式碼

<?php
function module_exists($module) {
$list = module_list();
return array_key_exists($module, $list);
}
?>

theme user login block form

之前在 怎樣styling user login block ?討論內 回應的內容上有一些錯誤
回應的內容已經沒辦法編輯
所以修改後另外PO一篇

在版型資料夾下的 template.php 內加上


function theme_user_login_block($form){
//自訂登入畫面 block
return _phptemplate_callback('login_block_form', array('form' => $form));
}

然後在版型資料夾下增加一個 login_block_form.tpl.php 的檔案作為樣板檔
直接在樣板檔裡排成我想要的樣子

帳號

<?php print drupal_render($form['name']) ?>

密碼

<?php print drupal_render($form['pass']) ?>

<?php print drupal_render($form['submit']) ?>
<?php print drupal_render($form['form_id']) ?>

drupal 的表單必須把 $form['form_id'] & $form['form_token'] 必須 print 出來 表單才有作用
不過 user_login_block 似乎是例外 表單結構內原本就沒有 $form['form_token']
所以這邊只 render $form['form_id']

另外 搭配 imagebutton 還可以把原本的submit按鈕改成圖片


<?php
$form['submit']['#type'] = 'imagebutton';
$form['submit']['#image'] = 'xxx/xxx.jpg';
print drupal_render($form['submit']);
?>

註冊新帳號 & 忘記密碼的連結 也都是定義在$form裡面
在樣板檔裡 print_r($form) 看一下就知道了

然後再加入css (這邊只是簡單示意一下 詳細的 css 請自己再作設定)

要直接加在樣板檔的檔案內
或版型的 style.css 檔案內都可以
如果是只有一個樣板檔用到的 style
個人是喜歡直接放在樣板檔裡

在樣板檔裡就像一般的網頁製作一樣
隨你怎麼排列
只要知道 element 的名字
想在哪 render 都可以 也不用照 $form 裡的順序
可以改出單用 css 或 hook_form_alter 沒辦法達成的排版

雖然在 $form['name']['title'] 可以把標題改成空字串 而不顯示標題
但是不建議這樣做
因為表單的錯誤訊息會沒有欄位的名稱
例如 請填寫密碼欄位 的錯誤訊息會變成 請填寫 欄位
如果表單的欄位有5個欄位沒填 就會變成

請填寫 欄位
請填寫 欄位
請填寫 欄位
請填寫 欄位
請填寫 欄位

根本沒辦法辨識是哪個欄位未輸入
所以 #title 還是留著比較好
不想顯示標題的時候 建議還是用 css 把 label 隱藏掉

user_foo

user 也是模組之一,管理使用者的登入登出,或是權限的認證。
總之想作好一個有管理使用者的模組,這個部分的函數就必須有所了解。
這些函數不需要特別去引用,直接呼叫函數的名稱就可以了。

Drupal版本:

user_access

定義

user_access($string, $account = NULL)
modules/user/user.module, 第 351 行開始

描述

判斷使用者是否具有某種權限。
所有的權限判斷,都應該要例用這個函數。一來是讓所有的程式有一致性,而且可以保證SuperUser能擁有所有的權限。

參數

$string: 要判定的權限名字,如"administer nodes"
$account (預設): 一個 User 物件。在要判斷的使用者並不是「目前登入的那個人」時使用。

傳回值

如果判定是「有這個權限」,則傳回布林值 TRUE。

範例

檢查目前使用者是否有「存取管理頁面(administer comments)」的權限

$has_permission = user_access("administer comments"); // 第二個參數省略

原始碼

<?php
function user_access($string, $account = NULL) {
global $user;
static $perm = array();

if (is_null($account)) {
$account = $user;
}

// 超級使用者 SuperUser 擁有所有的權限
if ($account->uid == 1) {
return TRUE;
}

// 為了滅少到資料庫作查詢連線數,會把作查過的資料放入靜態變數中
if (!isset($perm[$account->uid])) {
$result = db_query("SELECT DISTINCT(p.perm) FROM {role} r INNER JOIN {permission} p ON p.rid = r.rid WHERE r.rid IN (%s)", implode(',', array_keys($account->roles)));

$perm[$account->uid] = '';
while ($row = db_fetch_object($result)) {
$perm[$account->uid] .= "$row->perm, ";
}
}

if (isset($perm[$account->uid])) {
return strpos($perm[$account->uid], "$string, ") !== FALSE;
}

return FALSE;
}
?>

Drupal版本:

user_load

定義

user_load($array = array())
modules/user/user.module

描述

取得一個 user 物件

參數

$array 一個關連式陣列,讓這個函數可以找得到你想找的使用者資料,可能是帳號名稱或是e-mail位址。

傳回值

如果找到的話,傳回一個"完整"的 user 物件,否則傳回 FALSE

範例

其實Drupal 的登入就是靠它來檢查,輸入帳號密碼,來作搜尋有沒有符合的條件。

$account = user_load(array('name' => '納格髓', 'pass' => 'very secret', 'status' => 1))

name: 帳號
pass: 密碼
status:狀態 (1 代表啟用中)

如果有找到,就表示帳號密碼正確,$account 就會傳回一個 user 物件。再下兩行

global $user;
$user = $account; // 把找到的 user 物件"存"起來

這樣就算是完成登入了。很簡單吧!!

附帶一提,登出的語法也很簡單。

global $user;
$user = drupal_anonymous_user();

程式碼

<?php
function user_load($array = array()) {
// Dynamically compose a SQL query:
$query = array();
$params = array();

foreach ($array as $key => $value) {
if ($key == 'uid' || $key == 'status') {
$query[] = "$key = %d";
$params[] = $value;
}
else if ($key == 'pass') {
$query[] = "pass = '%s'";
$params[] = md5($value);
}
else {
$query[]= "LOWER($key) = LOWER('%s')";
$params[] = $value;
}
}
$result = db_query('SELECT * FROM {users} u WHERE '. implode(' AND ', $query), $params);

if (db_num_rows($result)) {
$user = db_fetch_object($result);
$user = drupal_unpack($user);

$user->roles = array();
if ($user->uid) {
$user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
}
else {
$user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
}
$result = db_query('SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = %d', $user->uid);
while ($role = db_fetch_object($result)) {
$user->roles[$role->rid] = $role->name;
}
user_module_invoke('load', $array, $user);
}
else {
$user = FALSE;
}

return $user;
}
?>

Drupal版本:

user_roles

定義

user_roles($membersonly = 0, $permission = 0)
modules/user/user.module, 第 1762 行開始

描述

傳回符合條件的「群組(身分)」陣列
(在 Drupal 裡,群組(身分)被叫作"role")

參數

$membersonly: 如果設為 TRUE ,則傳回的時候會排除「訪客群組(身分)」
$permission: 一個字串,如果加到它,則只傳回包含這個權限的群組(身分)回來

傳回值

一個關連陣列,鍵是該群組在資料庫的ID,值是它的名字。

範例

列出現在所有的群組: (假設我事前建立一個叫作 power user 的群組)

print_r(user_roles());

傳回值:

Array
(
[1] => anonymous user
[2] => authenticated user
[3] => power user
)

程式碼

<?php
function user_roles($membersonly = 0, $permission = 0) {
$roles = array();

if ($permission) {
$result = db_query("SELECT r.* FROM {role} r INNER JOIN {permission} p ON r.rid = p.rid WHERE p.perm LIKE '%%%s%%' ORDER BY r.name", $permission);
}
else {
$result = db_query('SELECT * FROM {role} ORDER BY name');
}
while ($role = db_fetch_object($result)) {
if (!$membersonly || ($membersonly && $role->rid != DRUPAL_ANONYMOUS_RID)) {
$roles[$role->rid] = $role->name;
}
}
return $roles;
}
?>

Drupal版本:

user_save

定義

user_save($account, $array = array(), $category = 'account')
modules/user/user.module, 第 106 行開始

描述

更新一個使用者帳號內容,或是建立一個新的帳號

參數

$account 一個 user 物件,如果 $user->uid(帳號 ID)為空,則建立新的帳號,如果有東西的話,就更新這個 uid 的帳號資料
$array 一個帶有帳號資訊的陣列。例如: array('name' => 'My name'); 值為 NULL 的話,就表示把這個欄位清空。
(註: 在更新中,沒有列在這個陣列裡的欄位會保持不變)
$category (選擇性參數) 用來作註記的參數。在這個函數中並沒有用到,主要是用來當參數傳給相關的 hook 函數。

範例

第一個參數要求的是一個 user 物件,但是其實只用到裡面的 uid 屬性。
所以要新增一個帳號的時候,並不用費心去建立一個"空的 user 物件",給它一個 '' (空字串)就好了。
如:

$roles = array(
'3' => '角色名稱',
'4' => '角色名稱',
'5' => '角色名稱',
);
user_save('', array("name" => '納格髓', "pass" => 'unknow', "status" => 1, 'roles' => $roles , 'profile_tel' => '0800 123456'));;

name: 帳號名稱
pass: 密碼 (不需要作加密動作)
status: 狀態(1 表示啟用中)
roles: 權限,也許叫它"群組"比較適合,這裡要放array。(例子中,是加入編號3, 4, 5 的群組)
profile_tel: 這個是自訂的使用者欄位,名字叫作"tel"

註:
在 Drupal 的群組(roles)中,1 表示訪客,2 表示已註冊帳號。這兩個資訊在記錄的時候會"自動跳過"。因為有沒有帳號就已經足以作為這兩者的區分。所以就算你在roles放入 1 或 2 也不會被記入資料庫中。
(原來文章的例子是直接打 array(3, 4, 5) 來加入編號 3, 4, 5的角色,這種寫法是錯誤的)

原始碼

<?php
function user_save($account, $array = array(), $category = 'account') {
// Dynamically compose a SQL query:
$user_fields = user_fields();
if ($account->uid) {
user_module_invoke('update', $array, $account, $category);

$data = unserialize(db_result(db_query('SELECT data FROM {users} WHERE uid = %d', $account->uid)));
foreach ($array as $key => $value) {
if ($key == 'pass' && !empty($value)) {
$query .= "$key = '%s', ";
$v[] = md5($value);
}
else if ((substr($key, 0, 4) !== 'auth') && ($key != 'pass')) {
if (in_array($key, $user_fields)) {
// Save standard fields
$query .= "$key = '%s', ";
$v[] = $value;
}
else if ($key != 'roles') {
// Roles is a special case: it used below.
if ($value === NULL) {
unset($data[$key]);
}
else {
$data[$key] = $value;
}
}
}
}
$query .= "data = '%s' ";
$v[] = serialize($data);

db_query("UPDATE {users} SET $query WHERE uid = %d", array_merge($v, array($account->uid)));

// Reload user roles if provided
if (is_array($array['roles'])) {
db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid);

foreach (array_keys($array['roles']) as $rid) {
if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $account->uid, $rid);
}
}
}

// Delete a blocked user's sessions to kick them if they are online.
if (isset($array['status']) && $array['status'] == 0) {
sess_destroy_uid($account->uid);
}

// If the password changed, delete all open sessions and recreate
// the current one.
if (isset($array['pass'])) {
sess_destroy_uid($account->uid);
sess_regenerate();
}

// Refresh user object
$user = user_load(array('uid' => $account->uid));
user_module_invoke('after_update', $array, $user, $category);
}
else {
$array['uid'] = db_next_id('{users}_uid');

if (!isset($array['created'])) { // Allow 'created' to be set by hook_auth
$array['created'] = time();
}

// Note, we wait with saving the data column to prevent module-handled
// fields from being saved there. We cannot invoke hook_user('insert') here
// because we don't have a fully initialized user object yet.
foreach ($array as $key => $value) {
switch ($key) {
case 'pass':
$fields[] = $key;
$values[] = md5($value);
$s[] = "'%s'";
break;
case 'uid': case 'mode': case 'sort':
case 'threshold': case 'created': case 'access':
case 'login': case 'status':
$fields[] = $key;
$values[] = $value;
$s[] = "%d";
break;
default:
if (substr($key, 0, 4) !== 'auth' && in_array($key, $user_fields)) {
$fields[] = $key;
$values[] = $value;
$s[] = "'%s'";
}
break;
}
}
db_query('INSERT INTO {users} ('. implode(', ', $fields) .') VALUES ('. implode(', ', $s) .')', $values);

// Build the initial user object.
$user = user_load(array('uid' => $array['uid']));

user_module_invoke('insert', $array, $user, $category);

// Build and save the serialized data field now
$data = array();
foreach ($array as $key => $value) {
if ((substr($key, 0, 4) !== 'auth') && ($key != 'roles') && (!in_array($key, $user_fields)) && ($value !== NULL)) {
$data[$key] = $value;
}
}
db_query("UPDATE {users} SET data = '%s' WHERE uid = %d", serialize($data), $user->uid);

// Save user roles (delete just to be safe).
if (is_array($array['roles'])) {
db_query('DELETE FROM {users_roles} WHERE uid = %d', $array['uid']);
foreach (array_keys($array['roles']) as $rid) {
if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $array['uid'], $rid);
}
}
}

// Build the finished user object.
$user = user_load(array('uid' => $array['uid']));
}

// Save distributed authentication mappings
$authmaps = array();
foreach ($array as $key => $value) {
if (substr($key, 0, 4) == 'auth') {
$authmaps[$key] = $value;
}
}
if (sizeof($authmaps) > 0) {
user_set_authmaps($user, $authmaps);
}

return $user;
}
?>

Drupal版本:

常用的全域變數

在寫模組的同時,發現有些資訊是記在全域變數裡面,在遍尋的函數找不到之後才發現。
所以把一些常用的全域變數整理到這裡,提供給人查詢。
所有用變數使用之前,必須要先「global $變數;」才能使用...

$user

使用者物件
既使沒有人登入,也會有一個"訪客"況態的物件。要知道這個人有那些資訊,就必須要使用這個物件。

$base_url

網站根目錄網址
當使用「clean_url(簡絜網址)」的時候,有時候會需要用到絕對路徑來作網頁連結。
不確定網址的開頭是什麼的時候,像是不確定自己的模組會被裝到哪個站裡面,可以用它來查詢網站根目錄的網址。
如果我用把drupal裝在"drupal_test"的目錄下,在本機測試時,這個變數可能會是「http://localhost/drupal_test」。

$base_path

網站根目錄網址(相對路徑)
當使用「clean_url(簡絜網址)」的時候,有時候會需要用到絕對路徑來作網頁連結。
不確定網址的開頭是什麼的時候,像是不確定自己的模組會被裝到哪個站裡面,可以用它來查詢網站根目錄的網址。
如果我用把drupal裝在"drupal_test"的目錄下,在本機測試時,這個變數會是「drupal_test」。

慢慢增加中....

把玩Drupal模組: Hook System運作簡介

模組系統是drupal很重要的運作方式,drupal依靠著少少的核心程式,便能讓模組能做到任何事情。

drupal只有21個檔案在include裡頭,每次必會loading進來,其他的全都放在modules。
也就是說,除了那幾隻檔案以外,全部的東西都把他當成module在寫。諸如CMS最基本的功能,文章管理、評論、討論區、分類...等的功能,全部都寫在module裡,include裡頭所提供的是各種api,檔案處理函式、資料庫存取、表單生成...等等,這樣的分層,module便可以專心的開發各種功能。

當然,這樣的架構不夠令人注目。有許多web app架構,對於模組 (module)、插件(plug-in)...等的運作,通常是讓他們各自為政,自己幹自己的事情。多是用核心提供的object和function,加上module自己額外的code,達到module要做到的額外功能。但是drupal的核心運作卻不是如此。

drupal處理使用的程式為modules/user.module。如果今天想要在看使用者資料的同時,也想看看所有使用者過去發表文章的list,那該怎麼寫呢?

直接一點,更改user.modue,在顯示時,順便去文章資料庫抓相關的資料?然而這樣卻不是一個好方式,今天任何想要對使用者增加新功能的時候,都得trace一次user.module的code,看懂他在幹啥,然後把新的code安插在合適的地方... 最後可能增加user.module的複雜度,增加維護那支module的難度,共同開發時,更是一個危險的方式。

第二種方式,重寫一個新的瀏覽頁面,重新寫一個SELECT的語句,讓SELECT的時候除了使用者資訊,也把文章資料一起抓出來,然後顯示到不同的頁面。但是這樣很浪費,明明跟user.module重複的功能達到一半以上,那是不是之後要新增功能,都得重寫一次呢?

上面兩種方式在drupal中也都可以達成,然而熟悉Drupal的人卻不會如此。Drupal的開發者很聰明,他的模組系統(module system)考慮到了模組再利用這一點,每個模組都視為可以再利用的資源,只要寫module的人想寫,透過模組系統便可以跟所有的module交互作用。

第三種方式modules/user.module為例,他即是處理包含新增、修改、刪除、註冊、登入....等所有與使用者相關的功能。在進行每個重要的功能時,user.module都會呼叫一個函式去掃所有的module,看看是否有其他的module要在user.module進行此動作時,也進行一些其他想要做的事情,這就是drupal重要的Hook System
example:
在drupal user.module裡頭可以找到如下的程式碼

function user_view($uid = 0) {
  // ... skip
  // moudle_invoke掃描所有的module
  // 看看有沒有modulename_user這個function
  // 有個話就看'view'這個功能的部份要加上什麼
  foreach (module_list() as $module) {
    if ($data = module_invoke($module, 'user', 'view', '', $account)) {
      // do something...
    }
  }
  // ... skip
}

所以,第三種方式,不用重寫,也不用改到user.module,只要自己新增module和寫一個function,便可以輕鬆讓瀏覽使用者資訊時,加上過往文章。
example:
新增自己的module,與hook system緊密運作
新增sample.module

function sample_user($type, &$edit, &$user, $category = NULL) {
  if ($type == 'view') {
    return /*過往文章,型別為一陣列*/;
  }
}

這就是drupal把眾多主要功能都寫成module的原因,讓所有模組之間都可以交互利用,或是寫給別人利用,或是利用別人的module,像積木一樣推砌成想要的功能,卻又不浪費資源。

參考資源:
詳細的用法在:
http://drupaldocs.org/api/head/function/hook_user
Module developer's guide:
http://drupal.org/node/508

不使用views,如何自訂節點清單顯示頁面

原文連結: http://www.500959.com/node/510
基本上,views和cck是大家都建議用的兩個drupal模組,因為這兩個模組太強大了,一個可以自由定制欄位,一個可以自由過濾資料用以顯示, drupal的許多其它模組都是基於這兩個的。正因其強大,所以也龐大,龐大耗費資源,相對來說,配置起來也較複雜。對於一些把drupal做為個人博客來用的朋友來說,通常不想使用這兩個模組,但drupal預設就只有一種排列文章的方式,按時間發表順序,而bloger們可能就需要有多一些的排列顯示方式。

這個時候可以用查詢資料庫再配合drupal的一些核心函數來達到簡單過濾顯示的目的。舉個例子吧,用習慣了國內cms的朋友,都喜歡在首頁上顯示一些區塊,什麼最新文章、最新推薦、最新評論、熱門文章……

咱們先創建一個page節點,標題就看你的愛好了,隨便取吧。內容呢,就隨便寫段代碼吧:
<?php
echo '嘿羅,世界';//反正一般程式測試都這個套路。
?>
然後在輸入格式裡選擇php code模式,自訂路徑呢,隨便想一個吧,這兒就定義為:index.html。提交保存,這不就一個“嘿羅世界”嗎?別著急,這只是第一步。

現在進入第二步,到“網站資訊(admin/settings/site-information)”中,拉到最後面,把默認首頁設置為咱們剛才創建的頁面路徑:index.html,好,現在打開網站,發現默認首頁就剩“嘿羅世界”了,忽悠人啊這不是。別急,接下來做第三步。

第三步,開始往裡邊添加內容了。我想添加個最新blog文章的列表。drupal區塊裡有個默認的最新blog文章,到區塊中,查看最新blog文章的區塊連結是這樣的:admin/build/block/configure/blog/0,注意最後兩層(blog/0),這很重要。這表示這個區塊是由blog.module生成的第0個區塊(從0開始計數的)。現在我們編輯那篇文章,把裡邊的“嘿羅世界”可以刪除了,放這段代碼進去:
<?php
$block = module_invoke('blog', 'block', 'view', 0);
echo $block['subject'];//顯示區塊的標題
echo $block['content'];//顯示區塊的主內容區。
?>
純粹的引用代碼讓人有些糊塗,解釋一下,module_invoke:載入模組,blog:點名要載入這個;block:區塊,view:顯示。後面的就不難理解了,要顯示這個模組的第0個區塊。就這麼簡單,要直接在文章中插入其它區塊也是這個方法。現在提交保存,到首頁看看,是不是出現了最新blog文章的列表(當然,你得先發表幾篇blog文章)。

我想要顯示其它類型,比如story的最新文章呢?因為默認沒有提供區塊,所以一般的做法呢是用views來過濾出來,但文章一開頭就說了,咱們不用views。這就進入另一個重點部分,讀取資料庫來顯示。編輯文章,在後面插入這段代碼:
<?php
echo '

最新story

';//標題隨意
$result = db_query_range("SELECT n.nid, n.title FROM {node} n WHERE n.type = 'story' ORDER BY n.created DESC", 10);
while ($test = db_fetch_object($result)) {
echo l($test->title,'node/'.$test->nid).'';
};
?>
drupal查詢資料庫語句重新定義過,但基本上和mysql手冊上的方法差不多。完整的手冊請看這兒。現在說我們上面的例子,文章的基本資訊都存儲在node這個表裡,因為我們只要顯示標題清單,所以查詢title和nid欄位,條件呢就是限制為 story類型。你可以把條件改為特定的用戶(uid),或是否推薦(promote)、置頂(sticky),或是否有評論(comment)等等方式。最後是排序,這個例子中我們按節點的創建時間倒序,也就是最新文章在前面,數目限制為10條。下面就是一個陣列迴圈了,在這裡邊可以自行排版,添加 css等。

保存,現在看看效果,最新story文章列表是不是出現了呢?而熱門內容呢,statistics模組也提供了一個按點擊排序的文章列表,我們可以直接用上面插入區塊的辦法把它放到頁面裡。現在我們頁面裡有最新文章、最新推薦、最新日誌、熱門內容、最新評論等區塊列表了,看起來像那麼回事了,接下來的事就是排版了。這個就取決於個人的審美觀了,熟悉css的就用css,不熟悉的就直接用表格套上去就行了。

可以收工了,可我還想有個more,你看大多數網站的列表下面,不都有個more,點擊進去,顯示更多的內容,並且這裡邊的內容還是可以分頁的。好,現在我們來解決這個問題。還是舉例子吧,也想不到其它更好的手段了。先在首頁的清單下麵加個more連結:
<?php
echo '

最新story

';
$result = db_query_range("SELECT n.nid, n.title FROM {node} n WHERE n.type = 'story' ORDER BY n.created DESC", 10);
while ($test = db_fetch_object($result)) {
echo l($test->title,'node/'.$test->nid).'';
};
echo '更多';
?>
就這麼簡單?是的,就這麼簡單,哈哈。可怎麼也得加個判斷吧,萬一只有一篇文章,或者只有0篇文章呢,也顯示個“更多”,那多傻。那就修改一下:
<?php
echo '

最新story

';
$result = db_query_range("SELECT n.nid, n.title FROM {node} n WHERE n.type = 'story' ORDER BY n.created DESC", 10);
if (db_num_rows($result) > 0) {//加入判斷,查詢結果大於0才顯示;
while ($test = db_fetch_object($result)) {
echo l($test->title,'node/'.$test->nid).'';
};
}else {//否則就顯示
echo '沒有文章';
}

if(db_num_rows($result) > 10){//如果大於10篇,就顯示更多連結。
echo '更多';
}
?>
提交保存,有了個“更多”連結,可點選連結不是“找不到頁面”嗎?別急,下一步。創建一個page節點,標題就寫個“新聞列表”吧,輸入格式還是選取為 php code,自訂路徑呢,就要和上面的一致了:story/all。現在要填內容進來了,怎麼把這個最新的story文章清單顯示出來呢?
<?php
echo '

新聞列表

';
$result = pager_query("SELECT n.nid, n.title FROM {node} n WHERE n.type = 'story' ORDER BY n.created DESC", 15);
if (db_num_rows($result) > 0) {//加入判斷,查詢結果大於0才顯示;
while ($test = db_fetch_object($result)) {
echo l($test->title,'node/'.$test->nid).'';
};
}else {//否則就顯示
echo '沒有文章';
}
echo ''.theme('pager', NULL, 15).'';
?>
請注意這一段代碼,和上面其它的對比,有一些變化,首先是db_query_range變成了pager_query,對於有分頁需求的,都使用這個函數查詢。其次當然是下面多了翻頁函數:theme('pager', NULL, 15)。在查詢語句中我們定義了數目是15條,翻頁裡當然也是設置15條。現在保存這個節點,看看,是不是出現了列表,並且有翻頁了(當然,前提是你的 story文章得多於15條,如果不夠多,把15改成2或3試試)。

這個時候可能又會覺得一個清單就顯示個標題,未免太單調了,我還想顯示點作者啊,發表時間啊,評論數目啊,點擊數量啊等等。好吧,咱們來完成這個需求。

<?php
echo '

新聞列表

';
$result = pager_query("SELECT n.nid, n.title,n.comment, n.created,u.uid, u.name,s.totalcount FROM {node} n INNER JOIN {node_counter} s ON n.nid = s.nid INNER JOIN {users} u ON n.uid = u.uid WHERE n.type = 'story' ORDER BY n.created DESC", 15);
if (db_num_rows($result) > 0) {//加入判斷,查詢結果大於0才顯示;
while ($test = db_fetch_object($result)) {
echo '標題:'.l($test->title,'node/'.$test->nid).
'  作者:'.l($test->name,'user/'.$test->uid).
'  評論:'.$test->comment.
'  點擊:'.$test->totalcount.
'  發表時間:'.format_date($test->created).'';
};
}else {//否則就顯示
echo '沒有文章';
}
echo ''.theme('pager', NULL, 15).'';
?>

細心的你肯定發現了,這裡使用了多表查詢,因為節點的點擊量是存放在另外一個表裡的,而作者的資訊又存在users表裡。老套路,提交保存,現在看看列表,是不是多了作者、評論這些資訊。只是排版未免太難看了。那就用css自己調整吧,可我又不想使用css,而且這種清單式的顯示,使用表格更有優勢,方便又快捷。那就用列表吧。這樣改一改:

<?php
echo '

新聞列表

';

$header = array(//這裡增加了,先定義個表格頭部
array('data' => '標題'),
array('data' => '作者'),
array('data' => '評論'),
array('data' => '點擊'),
array('data' => '發表時間')
);
$tablesort = tablesort_sql($header);
$result = pager_query("SELECT n.nid, n.title,n.comment, n.created,u.uid, u.name,s.totalcount FROM {node} n INNER JOIN {node_counter} s ON n.nid = s.nid INNER JOIN {users} u ON n.uid = u.uid WHERE n.type = 'story' ORDER BY n.created DESC".$tablesort, 15);
if (db_num_rows($result) > 0) {//加入判斷,查詢結果大於0才顯示;
while ($test = db_fetch_object($result)) {
//這兒不直接列印,而是定義成一個陣列了。
$rows[] = array( 'data' =>
array(
l($test->title,'node/'.$test->nid),
l($test->name,'user/'.$test->uid),
$test->comment,
$test->totalcount,
format_date($test->created),
),
);
};
}else {//否則就顯示
echo '沒有文章';
}
echo theme('table', $header, $rows);//這兒列印出表格。
echo ''.theme('pager', NULL, 15).'';
?>

提交保存,現在看一看頁面,是不是都在一個表格裡,排版都省了,整整齊齊。這樣就差不多了吧,又有了區塊,又有了列表頁。不過,也許你突然又覺得全是標題列表有也點單調,還想看看摘要顯示是什麼效果。咱們就來把標題清單改為摘要模式,這個更簡單一點,直接使用node_view和node_load,這兩個函數,只要你告訴它節點nid,它就能載入節點的摘要或全文視圖了。省得麻煩,就直接還是用這個新聞列表頁來做試驗吧。編輯節點,放入這段代碼:
<?php
echo '

新聞列表

';
$result = pager_query("SELECT n.nid FROM {node} n INNER JOIN {node_counter} s ON n.nid = s.nid INNER JOIN {users} u ON n.uid = u.uid WHERE n.type = 'story' ORDER BY n.created DESC", 15);
if (db_num_rows($result) > 0) {//加入判斷,查詢結果大於0才顯示;
while ($test = db_fetch_object($result)) {
$output .= node_view(node_load(array('nid' => $test->nid)), 1);//這兒的1或0是全文或摘要。
};
}else {//否則就顯示
echo '沒有文章';
}
echo ''.theme('pager', NULL, 15).'';
?>

注意幾個變化,首先要查詢資料庫的時候,只需要節點nid就行了。其次是在陣列迴圈時,直接用node_view和node_load來顯示出文章。這兒就用不著排版了,全部是按照你在node.tpl.php中定義的樣式來顯示。

現在終於可以結束了,好像在節點組織顯示方面,也沒其它的需求了。上面的內容我是一邊試驗一邊寫下來的,在5.x版本上,應該不會有錯誤。本來準備截圖,嫌上傳麻煩,就沒截了。如果測試過程中有什麼問題,請提出來。顯示需求不是很多,可以使用這種查詢資料庫的方式,如果有許多自訂的顯示需求,建議還是用views,畢竟它內置了緩存,而且和其它模組的互動也更好,當然,用起來也更方便。希望這篇文章對於喜歡drupal,但又不想使用views的朋友有一定的幫助。

投票/評比模組的評比

Drupal 有很多種投票/評比模組,有的簡單、有的複雜,各有巧妙不同與適用時機。該如何選擇?

之前在做 nbalive.tw 時,花滿多時間在測試擁有類似功能的不同模組,例如投票、聊天、相簿、討論區,而這篇 Lullabot - A Review of Node Review Modules 令我有相見恨晚的感覺,因為他集合了 Drupal 的 6 個投票模組,做了簡單的介紹與圖示,方便使用者做初步判定。

簡單介紹一下:
Node Vote - 簡單的1-10分數投票。
NodeReview - 可設定多種評比項目(要是能畫個五力分析圖出來就更好了)。
Simple Vote - 更簡單的五星級投票。
userreview - 類似 Amazon.com 的書評功能,投票並寫心得,心得是一個獨立的內容類型。
Vote up/down Package - 類似 digg.com,好或不好兩種選擇。
Voting - 也是五星級投票,可顯示平均票數和我的投票。

Form的美化心得

嗨!最近研究的是form的美化, 延續上篇[問題],
解決之後真開心, 分享一下我的美化的code(用CSS)
然後還是有幾個問題:
(1)想弄更漂亮, 想在input加上onFocus之類的, 不知怎麼加
(2)像我這樣把CSS放在tpl.php, 會不會不好呢?

謝謝!

先看效果, 表單

這是按鈕

下面是我的原始碼:

template.php

<?php
function phptemplate_lifestyle_node_form($form) {
global $user;
$vars = array('user' => $user, 'form' => $form);
return _phptemplate_callback('lifestyle_form', $vars);
}
?>

lifestyle_form.tpl.php

<?php
//drupal_set_message('

' . print_r($form, true) . '

');
drupal_set_title('新增寵物店家資料');
?>

謝謝你提供店家資料哦!
每次填寫資料,我們都會怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣怎樣,總之這個區塊會放一些填表單的說明。

<?php //把種類拉到最上面 ?>
<?php print drupal_render($form['taxonomy']['4']); //type ?>
<?php print drupal_render($form['taxonomy']['3']); //location ?>

<?php //不要這些 ?>
<?php drupal_render($form['taxonomy']); //分類的外框,很醜 ?>
<?php drupal_render($form['body_filter']['format']); //內文說明,不要?>

<?php //剩下的表單依照權重排列 ?>
<?php print drupal_render($form); ?>

Drupal API的Dreamweaver擴充套件

Hi!原文在這:Drupal API的Dreamweaver擴充套件

今天發現了一個不錯的東西,如果你是用Dreamweaver來coding的話,又是Drupal的使用者(不知這兩個條件都成立的人多不多),這玩意兒還滿不錯的噢!雖然幫不上大忙,不過,會在你輸入一個函數的時候,閃一個小視窗告訴你這個函數裡面要填的東西大概是什麼。

像是這樣。

這是這個extension的資訊。

名稱:Drupal API extension for Dreamweaver
網站:http://xtnd.us
下載頁面:這兒
直接下載:Drupal_API.mxp
官方說明:
*在原始碼模式時,檔案類型是Drupal相關類型(.module, .php, .tal, .info, .inc, .theme, .js等等)的話,就會出現提示。(是出現在打完函數和左邊小括號之後)
*您可在編輯>偏好設定>程式碼提示那邊停用這個功能。
*您可以在說明>Drupal API for Dreamweaver這個選單找到Drupal API網站連結。

安裝方式應該不用講了。依照這邊的說法,似乎裝了之後就會自動讓Dreamweaver可以開Drupal的檔案呢!所以我之前這篇「[Dreamweaver] 讓非php檔名也能有php色彩標示」就白搭了。

以下是官方說明原文。

Drupal API Code Hints for Dreamweaver
Dreamweaver Versions: MX(6) - CS3(9)
This extension provides code hints for Drupal API's versions: 5.x, 6.x
* Drupal Code Hints appear in Code or Split view (Ctrl+Space) when editing Drupal files (.module, .php, .tal, .info, .inc, .theme, .js, etc.)
* You can deactivate Drupal versions that you aren't using in the Edit > Preferences > Code Hints menu.
* Help is available in the Help > Drupal API for Dreamweaver menu.
Updates and information about this extension available online at http://xtnd.us