在這個(gè)文章系列中,在 IBM? Internet Technology Group 團(tuán)隊(duì)的帶領(lǐng)下使用一套可免費(fèi)獲得的軟件為虛構(gòu)的 International Business Council(IBC)公司設(shè)計(jì)、開發(fā)和部署一個(gè)外部網(wǎng) Web 站點(diǎn)。在本文中,學(xué)習(xí)如何定義外部網(wǎng)來滿足客戶需求,并研究創(chuàng)建外部網(wǎng) Web 站點(diǎn)的實(shí)現(xiàn)技術(shù)。
簡介
在這個(gè) 文章系列 中,團(tuán)隊(duì)為虛構(gòu)的 International Business Council(IBC)公司創(chuàng)建一個(gè)外部網(wǎng) Web 站點(diǎn)。這個(gè) Web 站點(diǎn)必須有文檔存儲(chǔ)、討論組、專門的工作組、會(huì)議日程安排、日程議題描述。
在本系列的 第 2 部分 中,研究了設(shè)計(jì)階段,這個(gè)階段幫助我們查明了 IBC 的業(yè)務(wù)目標(biāo)和用戶目標(biāo)。通過分析,我們獲得了實(shí)現(xiàn) IBC Web 站點(diǎn)的使用策略的一套需求,這樣就定義了一個(gè)環(huán)境,IBC 成員可以在這個(gè)環(huán)境中查看機(jī)密資料并進(jìn)行協(xié)作。這些需求包括:
- 每個(gè)成員必須通過身份驗(yàn)證,之后才能查看信息或進(jìn)行交互。
- 如果一個(gè)成員是非活躍的(也就是說,他或她在一段預(yù)定義的時(shí)間段內(nèi)沒有與 Web 站點(diǎn)進(jìn)行交互),成員的身份驗(yàn)證就失效了。
- 如果一個(gè)成員沒有經(jīng)過身份驗(yàn)證,那么除了與重新身份驗(yàn)證相關(guān)的信息之外,不顯示任何信息。
- 在第一次成功地通過身份驗(yàn)證時(shí),每個(gè)成員都需要同意依從使用條款和條件。
- 每個(gè)成員需要定期檢查是否依從使用條款和條件。
|
外部網(wǎng)。一個(gè)使用互聯(lián)網(wǎng)協(xié)議和網(wǎng)絡(luò)連接的私有網(wǎng)絡(luò),它還可能使用公共電信系統(tǒng),在供應(yīng)商、生產(chǎn)商、合作伙伴、客戶或其他業(yè)務(wù)單位之間安全地共享公司的信息或操作。 |
|
這些需求就組成了外部網(wǎng)的定義。在本文中,學(xué)習(xí)如何創(chuàng)建登錄頁面、會(huì)話過期并向用戶提供在繼續(xù)查看 Web 站點(diǎn)的其他內(nèi)容之前需要了解的依從信息。您將使用在 第 7 部分 中創(chuàng)建的主題模板,改編 Automated Logout 模塊,實(shí)現(xiàn)帶警告的計(jì)時(shí)客戶端注銷,并開發(fā)一個(gè)新模塊來實(shí)現(xiàn)依從確認(rèn)系統(tǒng)。
我們希望我們構(gòu)建的外部網(wǎng)函數(shù)可能令您感興趣,也希望我們修改現(xiàn)有模塊和創(chuàng)建新模塊的方法對(duì)您有幫助。這里提供的信息不應(yīng)該解釋為嚴(yán)格的開發(fā)規(guī)則,而是應(yīng)該作為構(gòu)建自己的解決方案時(shí)的起點(diǎn)?,F(xiàn)在,首先構(gòu)建登錄頁面。
登錄外部網(wǎng)
因?yàn)槲覀円獎(jiǎng)?chuàng)建一個(gè) “互聯(lián)網(wǎng)內(nèi)的互聯(lián)網(wǎng)”,所以需要確保這個(gè)私有網(wǎng)絡(luò)之外的任何用戶都看不到這個(gè)網(wǎng)絡(luò)中的任何信息。當(dāng)我們查看 IBC Web 站點(diǎn)時(shí),應(yīng)該只看到登錄頁面 —— 假設(shè)沒有通過 cookie 或現(xiàn)代瀏覽器常常附帶的身份驗(yàn)證存儲(chǔ)工具進(jìn)行自動(dòng)的身份驗(yàn)證。(cookie 和密碼存儲(chǔ)實(shí)用程序超出了本文的范圍。)圖 1 顯示 IBC 登錄頁面。
圖 1. IBC Web 站點(diǎn)的登錄頁面
在 第 7 部分 中,學(xué)習(xí)了如何根據(jù) Drupal 用戶是否通過了身份驗(yàn)證選擇替代的模板文件。這涉及在 page.tpl.php 文件中非常接近開頭的地方使用一些簡單的 PHP 代碼。當(dāng)用戶還未通過身份驗(yàn)證時(shí),這段代碼就會(huì)顯示登錄頁面,如 清單 1 所示。
清單 1. 來自 page.tpl.php 的代碼片段
<?php
global $user;
if (!$user->uid) {
include('login.tpl.php');
return;
}
?>
|
只有在用戶已經(jīng)通過身份驗(yàn)證的情況下,uid 屬性才會(huì)出現(xiàn)在 $user 對(duì)象中,所以這種方法可以可靠地通知 Drupal 主題系統(tǒng)使用另一個(gè)模板文件。在這個(gè)示例中,我們將登錄頁面模板命名為 login.tpl.php。
如 圖 1 所示,登錄頁面的外觀與以前文章中顯示的任何 IBC Web 頁面布局都很不一樣。除了 IBC 標(biāo)志之外,只顯示與身份驗(yàn)證相關(guān)的信息。login.tpl.php 模板文件幾乎是純粹的 XHTML。我們可以建立自己的登錄表單,從而確保輸入表單元素使用正確的值來觸發(fā) Drupal 身份驗(yàn)證系統(tǒng)。但是,我們選擇讓 Drupal 替我們創(chuàng)建表單,并將這個(gè)表單嵌入我們的 XHTML 結(jié)構(gòu)中。
清單 2 中的代碼演示如何使用 module_invoke 函數(shù)安全地調(diào)用 user.module 中的 user_login 函數(shù),并顯示我們需要的主題化登錄表單?,F(xiàn)在,在一個(gè)由主題控制的單獨(dú)頁面中有了一個(gè) Drupal 生成的登錄表單,這個(gè)頁面只在用戶還未通過身份驗(yàn)證的情況下顯示。
清單 2. 嵌入 Drupal 生成的登錄表單
<div id="login_form">
<h2>Please log in</h2>
<?php
print module_invoke('user','login');
if ($messages != "") { print $messages; }
?>
</div>
|
對(duì)無活動(dòng)時(shí)間進(jìn)行計(jì)時(shí)并進(jìn)行注銷
對(duì)于外部網(wǎng) Web 站點(diǎn)來說,其部分信息或所有信息常常是敏感的,只應(yīng)該讓通過身份驗(yàn)證的用戶看到。用戶可能離開已經(jīng)通過身份驗(yàn)證的瀏覽器,從而讓別人有機(jī)會(huì)接觸敏感信息;為了減少這種可能性,需要使用一種機(jī)制在一段指定的時(shí)間之后自動(dòng)地注銷用戶。
最初,我們實(shí)現(xiàn)了一種解決方案,它在屬于 Drupal 核心的 session.inc 文件中添加代碼,這使我們能夠在 HTTP 請(qǐng)求周期的早期添加鉤子,從而在預(yù)定義的無活動(dòng)時(shí)間段之后影響用戶會(huì)話。我們的解決方案是有效的,但不是推薦的解決方案。在正常的 開發(fā)周期 之外修改 Drupal 核心會(huì)導(dǎo)致很大的麻煩。在升級(jí)到新的 Drupal 版本時(shí),這個(gè)問題尤其嚴(yán)重。
我們了解到,已經(jīng)有一個(gè)補(bǔ)丁被提交給 Drupal 項(xiàng)目,它支持在 HTTPD 請(qǐng)求周期 的啟動(dòng)階段裝載不同的 session.inc 文件。
與任何問題一樣,實(shí)現(xiàn)自動(dòng)注銷也有許多方式。使用 PHP 會(huì)話超時(shí)設(shè)置無法提供足夠的控制能力,所以實(shí)現(xiàn)自動(dòng)注銷有兩種主要方式:服務(wù)器端和客戶端。
- 服務(wù)器端
- 將判斷用戶多長時(shí)間沒有活動(dòng)并對(duì)他們進(jìn)行注銷的所有邏輯都放在服務(wù)器上。這樣做的好處主要是增加了安全性。通過將代碼放在服務(wù)器端,減少了通過跨站點(diǎn)腳本、SQL 注入等手段進(jìn)行惡意活動(dòng)的風(fēng)險(xiǎn)。缺點(diǎn)是只能針對(duì)每個(gè)請(qǐng)求檢查計(jì)時(shí)。我們還考慮了通過一個(gè)每分鐘的觸發(fā)器使用 cron 鉤子來檢查計(jì)時(shí)。
- 客戶端
- 使用客戶端解決方案意味著使用 JavaScript? 這樣的技術(shù)將所有邏輯都放在客戶端。當(dāng)然,如果解決方案編寫得不好的話,就容易遭到跨站點(diǎn)腳本攻擊。這么做的好處是邏輯可以實(shí)時(shí)檢查無活動(dòng)的時(shí)間長度。還可以在快發(fā)生自動(dòng)注銷時(shí)提供警告。
一種組合式的方法是,由服務(wù)器端邏輯向客戶端發(fā)出不可見的消息,在客戶端使用 Ajax 解決方案接收這些消息來創(chuàng)建警告。
修改 Automated Logout 模塊
這個(gè)模塊為開發(fā)我們自己的解決方案提供了一個(gè)良好的起點(diǎn);我們首先在 Drupal 環(huán)境中安裝這個(gè)模塊?;谇懊娼o出的所有考慮因素,我們希望使用服務(wù)器端或客戶端解決方案來為 IBC 提供靈活性。我們注意到了 Automated Logout 模塊。這個(gè)模塊提供了針對(duì)每個(gè)請(qǐng)求對(duì)用戶的無活動(dòng)時(shí)間進(jìn)行計(jì)時(shí)的邏輯,如果無活動(dòng)時(shí)間到達(dá)了預(yù)定義的時(shí)間,就注銷用戶(也就是,刪除會(huì)話信息并將用戶重定向到首頁)。我們首先在 Drupal 環(huán)境中安裝這個(gè)模塊。
我們?cè)?Automated Logout 模塊中添加了幾個(gè)特性,包括:
- 設(shè)置啟用客戶端腳本所需的所有數(shù)據(jù)庫字段和變量。
- 配置客戶端腳本來執(zhí)行自動(dòng)注銷邏輯。
- 將服務(wù)器端變量傳遞給客戶端腳本。
- 插入 JavaScript 對(duì)用戶無活動(dòng)時(shí)間進(jìn)行計(jì)時(shí),在自動(dòng)注銷之前的特定時(shí)間向用戶發(fā)出警告,并讓服務(wù)器注銷用戶的會(huì)話。
- 如果用戶被自動(dòng)注銷了,當(dāng)他們重新登錄時(shí),將他們重定向到 Web 站點(diǎn)中當(dāng)前的位置。
現(xiàn)在,我們來創(chuàng)建支持自動(dòng)注銷的數(shù)據(jù)庫表和變量。
實(shí)現(xiàn)數(shù)據(jù)庫修改和新變量
根據(jù)我們的需求,我們需要許多額外的變量來支持用 autologout.module 實(shí)現(xiàn)這個(gè)新功能。這些變量包括:
- 表示是否使用客戶端腳本的標(biāo)志
- 要顯示警告的時(shí)間
- 警告所用的文本
- 要將用戶返回到的當(dāng)前 URL(也就是自動(dòng)注銷之前的位置)
可以考慮使用 Drupal 目標(biāo)變量??梢栽诒韱沃械囊粋€(gè)隱藏輸入元素中定義它,然后由 drupal_goto 函數(shù)獲取它,從而在提交表單之后提供重定向。我們認(rèn)為可以使用這種方法將用戶重定向到原來的位置,但是更簡單的方法是將 URL 存儲(chǔ)在 autologout 中,這樣就能夠在模塊請(qǐng)求周期中的任何時(shí)刻或未來的請(qǐng)求中獲取它。
第 9 部分 解釋了模塊安裝文件的函數(shù)以及如何應(yīng)用更新。清單 3 演示如何更新現(xiàn)有的 autologout 表,用一個(gè)更新函數(shù)在其中存儲(chǔ) URL。
清單 3. 在 autologout.install 文件中更新 autologout 表模式
function autologout_update_1() {
global $db_type;
switch ($db_type) {
case 'mysql':
case 'mysqli':
db_query('ALTER TABLE {autologout} ADD COLUMN url VARCHAR(255) NOT NULL ');
break;
case 'pgsql':
break;
}
}
|
如果已經(jīng)安裝了這個(gè)模塊,Web 站點(diǎn)管理員可以運(yùn)行 update.php 腳本來更新 autologout 表。如果這個(gè)模塊是新安裝的,Drupal 將運(yùn)行更新函數(shù),從而確保 autologout 表的模式是最新的。
可以在安裝文件中使用 variable_set 函數(shù)在變量表中創(chuàng)建新變量。但是,為了與 autologout.module 保持一致,我們將這些變量添加到在文件頂部定義的 autologout_default_settings 類對(duì)象中。添加的內(nèi)容見 清單 4。注意,$alert_text 變量包含以 % 開頭的文本。后面將使用它們替換警告文本中的值。
清單 4. 在 autologout.module 文件中添加的變量和默認(rèn)值
class autologout_default_settings {
...
var $clientsidetrigger = FALSE; // Initially disabled
var $alert_time = 160; // default 2 minutes
var $alert_text = 'ALERT! %user_name, you have been inactive for some time. '.
'You will be automatically logged out in %time_remaining seconds '.
'unless you interact with our web site.';
...
}
|
添加新的模塊設(shè)置
既然已經(jīng)有了客戶端腳本所需的變量,就需要讓這些變量出現(xiàn)在模塊設(shè)置頁面上(admin/settings/autologout )。通過使用 清單 5 所示的代碼,可以添加一個(gè)啟用客戶端邏輯的復(fù)選框,以及用來修改警告時(shí)間和警告文本值的輸入元素。
清單 5. 在自動(dòng)注銷設(shè)置頁面中修改新變量的表單元素
function autologout_settings() {
...
$form['autologout']['clientsidetrigger'] = array(
'#type' => 'checkbox',
'#title' => t('Enable client side timeout'),
'#default_value' => _autologout_local_settings('clientsidetrigger'),
'#description' => t('Check this to allow javascript to trigger the '.
'auto logout instead of relying on the HTTP '.
'request mechanism. Enabling this disables any '.
'use of the browser refresh delta')
);
$form['autologout']['alert_time'] = array(
'#type' => 'textfield',
'#title' => t('Alert time value in seconds'),
'#default_value' => _autologout_local_settings('alert_time'),
'#size' => 10,
'#maxlength' => 12,
'#description' => t('The length of time, in seconds, before auto logout '.
'when an alert is shown warning the user. This time '.
'needs to be smaller than the timeout setting if an '.
'alert is displayed. This settings works only when '.
'the client side trigger is enabled.')
);
$form['autologout']['alert_text'] = array(
'#type' => 'textarea',
'#title' => t('Alert text'),
'#default_value' => _autologout_local_settings('alert_text'),
'#cols' => 60,
'#rows' => 3,
'#description' => t('The text to be used in the text alert dialog that '.
'warns the user of a pending auto logout. You can '.
'use the variables %timeout, %alert_time and %user_name.')
);
...
}
|
這三個(gè)新的表單元素是用 Drupal Forms API 構(gòu)建的,它們使用現(xiàn)有的 _autologout_local_settings 函數(shù)來獲得用于填充表單元素的值。當(dāng) Drupal 處理設(shè)置表單時(shí),如果有公用父數(shù)組名的話,就使用這個(gè)名稱作為 variables 表中記錄的名稱字段;子名稱和值將依次放進(jìn)值字段。
例如,$form['autologout']['alert_text'] 這個(gè)元素包含父名稱 autologout 。在提交表單之后,Drupal 將鍵 alert_text 和與這個(gè)表單元素相關(guān)聯(lián)的值串在一起,并使用 autologout 作為名稱字段,串在一起的鍵/值對(duì)作為值字段,從而在變量表中設(shè)置一個(gè)記錄。_autologout_local_settings 函數(shù)會(huì)把來自變量表的鍵/值對(duì)分解開,從而提供作為參數(shù)傳遞的設(shè)置鍵的值。
在稍后描述的 Compliance 模塊中,將使用另一種方法組織 variable 表中存儲(chǔ)的設(shè)置。
將服務(wù)器端變量傳遞給客戶端腳本
現(xiàn)在需要將服務(wù)器端變量傳遞給客戶端 JavaScript。一種方法是在模塊中以變量的形式創(chuàng)建 JavaScript,將變量替換進(jìn)去,并將修改后的代碼提供給 Drupal,從而包含在 Web 頁面中。
另一種更常用的方法是在 Web 頁面中創(chuàng)建新的命名容器,比如帶 id 屬性值的 DIV 元素,這樣 JavaScript 就能夠在運(yùn)行時(shí)找到它們。主張純語義標(biāo)記的人可能不喜歡這種方式,而且如果具有這些額外標(biāo)記的頁面沒有應(yīng)用樣式,就會(huì)顯示出不希望的內(nèi)容。在這里,我們將演示如何在閉包變量中創(chuàng)建隱藏的表單元素,在其中包含 JavaScript 所需的變量。盡管這仍然要添加非語義標(biāo)記,但是在關(guān)閉樣式時(shí),內(nèi)容至少會(huì)隱藏起來。
現(xiàn)有的 autologout.module 使用 footer 鉤子來包含邏輯,這些邏輯檢查用戶是否在指定的時(shí)間段內(nèi)沒有活動(dòng),如果出現(xiàn)這種情況,就關(guān)閉會(huì)話并將用戶重定向到首頁。清單 6 顯示對(duì)這個(gè)現(xiàn)有函數(shù)的修改。
清單 6. 對(duì) autologout.module 文件的 footer 鉤子的修改
function autologout_footer() {
...
$footer = '';
...
if (_autologout_local_settings('enabled')) {
if (_autologout_local_settings('clientsidetrigger')) {
$alert_text = t(_autologout_local_settings('alert_text'), array(
'%timeout' => (int)_autologout_local_settings('timeout'),
'%time_remaining' => (int)_autologout_local_settings('alert_time'),
'%user_name' => check_plain($user->name)
));
$form = array();
$form['clientsidetrigger']['timeout'] = array(
'#type' => 'hidden',
'#value' => (int)_autologout_local_settings('timeout');
);
$form['clientsidetrigger']['alert_time'] = array(
'#type' => 'hidden',
'#value' => (int)_autologout_local_settings('alert_time')
);
$form['clientsidetrigger']['alert_text'] = array(
'#type' => 'hidden',
'#value' => check_plain($alert_text)
);
$footer = drupal_get_form('autologout_clientside_trigger_js', $form);
drupal_add_js(drupal_get_path('module', 'autologout') . '/clientsidetrigger.js');
}
else {
...
// existing logic for server side timing and logout
...
}
}
return $footer;
}
|
為了保留現(xiàn)有函數(shù),但是允許選擇客戶端方法,我們將這些邏輯包圍在條件語句中,使用 $clientsidetrigger 變量決定要運(yùn)行的邏輯。
我們來看一下啟用客戶端函數(shù)的邏輯。首先,使用 t 函數(shù)構(gòu)造 $alert_text 。這樣就可以將警告文本中的 %string 替換為它們代表的值。接下來,使用 Drupal Forms API 構(gòu)建隱藏的表單元素,這些元素用來包含客戶端邏輯要檢查的參數(shù)。drupal_get_form 函數(shù)生成表單的 XHTML,這些 XHTML 將由 phptemplate 引擎放在閉包區(qū)域變量中。為了讓這些內(nèi)容出現(xiàn)在 Web 頁面中,在 page.tpl.php 模板中定義的 XHTML 結(jié)構(gòu)前面使用 清單 7 中的代碼。
清單 7. 在 page.tpl.php 文件中定義的 Web 頁面中包含閉包區(qū)域 XHTML
使用 drupal_add_js 函數(shù)將提供客戶端邏輯的 JavaScript 提供給 Drupal,以便包含在 Web 頁面中。 drupal_get_path 函數(shù)幫助識(shí)別 autologout.module 中放置新的 JavaScript 文件的路徑。
客戶端腳本
autologout.module 附帶一個(gè)用 JavaScript 實(shí)現(xiàn)的簡單的倒數(shù)計(jì)時(shí)器。這可以顯示在一個(gè)塊中,讓用戶可以看到會(huì)話什么時(shí)候?qū)?huì)超時(shí)。盡管可以將警告和注銷邏輯添加在其中,但是我們決定將修改分隔開以簡化實(shí)現(xiàn)。
客戶端 JavaScript 的功能是創(chuàng)建一個(gè)倒數(shù)計(jì)時(shí)器,它從定義的超時(shí)值倒數(shù)到零,到達(dá)零時(shí)用戶被重定向到對(duì)他們進(jìn)行注銷的 URL。在某個(gè)預(yù)定義的時(shí)間,向用戶顯示一個(gè)警告,指出如果他們?cè)俨慌c Web 站點(diǎn)進(jìn)行交互,就會(huì)被注銷。
這一修改的客戶端 JavaScript 包含在 clientsidetrigger.js 文件中,這個(gè)文件在與 autologout.module 相同的目錄中。正如在前一節(jié)中看到的,使用 drupal_add_js 函數(shù)將它提供給 Drupal。這個(gè)腳本中所做的第一件事是,告訴 Drupal 在裝載 Web 頁面時(shí)運(yùn)行它,如 清單 8 所示。
清單 8. 確保這個(gè) JavaScript 腳本在裝載 Web 頁面時(shí)運(yùn)行
if (isJsEnabled()) {
addLoadEvent(autologoutClientsideTriggerStart);
}
|
Drupal 4.7 提供了一些有用的 JavaScript 實(shí)用程序。isJsEnabled 函數(shù)測試所需的 JavaScript 方法(比如 getElementsByTagName 和 getElementById )的可用性。如果沒有這些方法,Drupal 環(huán)境提供的其他 JavaScript 函數(shù)可能是無效的。addLoadEvent 函數(shù)將一個(gè) JavaScript 函數(shù)添加到 window.onload 事件上,這確保它在裝載 Web 頁面時(shí)運(yùn)行。
在 清單 8 中可以看到,autologoutClientsideTriggerStart 函數(shù)被添加到了 window.onload 事件上。
autologoutClientsideTriggerStart 對(duì)倒數(shù)計(jì)時(shí)邏輯進(jìn)行初始化,如 清單 9 所示。
清單 9. 對(duì)客戶端倒數(shù)計(jì)時(shí)器進(jìn)行初始化
var AUTOLOGOUT_CLIENTSIDE_TRIGGER_ENABLED = 0;
var AUTOLOGOUT_CLIENTSIDE_TRIGGER_TIMEOUT_ID;
function autologoutClientsideTriggerStart() {
var countdown = parseInt(document.getElementById('edit-timeout').value);
var alertTime = parseInt(document.getElementById('edit-alert_time').value);
AUTOLOGOUT_CLIENTSIDE_TRIGGER_ENABLED = 1;
autologoutClientsideTriggerCountdown(countdown,alertTime);
}
|
這里定義兩個(gè)全局變量,它們用來處理 setTimeout 函數(shù)創(chuàng)建的循環(huán)??梢钥吹饺绾螌⒈韱卧刂械闹底鳛?countdown 和 alertTime 變量傳遞給 JavaScript。我們?cè)O(shè)置全局循環(huán)變量,并調(diào)用 autologoutClientsideTriggerCountdown 函數(shù)來啟動(dòng)倒數(shù)計(jì)時(shí)器。
倒數(shù)計(jì)時(shí)器僅僅是一個(gè)在 JavaScript 應(yīng)用程序中執(zhí)行計(jì)時(shí)操作的簡單的 setTimout 結(jié)構(gòu)。清單 10 顯示創(chuàng)建計(jì)時(shí)器邏輯的代碼。
清單 10. 客戶端倒數(shù)計(jì)時(shí)器
function autologoutClientsideTriggerCountdown(countdown,alertTime) {
if (countdown == 0) {
clearTimeout(AUTOLOGOUT_CLIENTSIDE_TRIGGER_TIMEOUT_ID);
AUTOLOGOUT_CLIENTSIDE_TRIGGER_ENABLED = 0;
window.location.href = window.location.protocol + '//' +
window.location.hostname + ':' +
window.location.port +
'/autologout/logout';
}
if (countdown == alertTime) {
autologoutClientsideTriggerAlert();
}
if (AUTOLOGOUT_CLIENTSIDE_TRIGGER_ENABLED) {
countdown --;
AUTOLOGOUT_CLIENTSIDE_TRIGGER_TIMEOUT_ID =
setTimeout('autologoutClientsideTriggerCountdown(' + countdown + ',
' + alertTime + ')',
1000);
}
}
|
autologoutClientsideTriggerCountdown 函數(shù)檢查 $countdown 變量。如果它與 $alertTime 相同,那么使用 autologoutClientsideTriggerAlert 函數(shù)向用戶顯示警告。如果 $countdown 變量是零,那么將用戶重定向到 URL /autologout/logout 。
創(chuàng)建警告的邏輯放在一個(gè)單獨(dú)的函數(shù) autologoutClientsideTriggerAlert() 中。這意味著在構(gòu)建警告時(shí)可以從輸入字段元素一次裝載警告文本。清單 11 顯示了這個(gè)函數(shù)。
清單 11. 創(chuàng)建警告的邏輯
function autologoutClientsideTriggerAlert() {
alert(document.getElementById('edit-alert_text').value);
}
|
這是一個(gè)非常簡單的警告,但是可以在 page.tpl.php 文件或使用 drupal_add_js 函數(shù)提供給 Drupal 的新 JavaScript 文件中用自己的函數(shù)覆蓋這個(gè) JavaScript 函數(shù),從而創(chuàng)建更復(fù)雜的警告機(jī)制。
在服務(wù)器端控制注銷
因?yàn)榭蛻舳诉壿嬚{(diào)用 URL /autologout/logout ,我們需要?jiǎng)?chuàng)建一個(gè)新的菜單項(xiàng)。現(xiàn)有的 autologout.module 需要一個(gè)新的菜單鉤子來實(shí)現(xiàn)這個(gè)菜單項(xiàng),如 清單 12 所示。
清單 12. 在 autologout.module 中注冊(cè)注銷 URL
function autologout_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array('path' => 'autologout/logout',
'title' => t('Autologut'),
'access' => TRUE,
'type' => MENU_CALLBACK,
'callback' => '_autologout_logout');
}
return $items;
}
|
現(xiàn)在,這個(gè) URL 將調(diào)用 _autologout_logout 函數(shù)。現(xiàn)有的 autologout.module 在上面描述的 footer 鉤子中執(zhí)行注銷邏輯。我們將這些代碼放進(jìn)一個(gè)單獨(dú)的函數(shù),并從 footer 鉤子中原來的位置調(diào)用它,從而重用這些代碼。這個(gè)函數(shù)稱為 _autologout_logout ;部分清單見 清單 13。
將用戶返回到正確的位置
為了方便用戶,在用戶重新通過身份驗(yàn)證時(shí),應(yīng)該將用戶返回自動(dòng)注銷時(shí)所在的 Web 頁面。這要使用數(shù)據(jù)庫中的 URL 列??梢詫?duì) autologout.module 進(jìn)行幾處修改來創(chuàng)建這個(gè)特性:
- 確保對(duì)
autologout 表的所有 INSERT 操作都包含新的 URL 列。
- 在
_autologout_logout 函數(shù)中注銷用戶之前,將 URL 存儲(chǔ)在 autologout 表中。
- 當(dāng)用戶重新登錄之后,將用戶重定向到
autologout 表中存儲(chǔ)的 URL。
- 如果用戶進(jìn)行手工注銷,就清空
autologout 表中存儲(chǔ)的 URL。
autologout.module 中目前只有一個(gè) INSERT 操作,它位于 user 鉤子中執(zhí)行更新操作的邏輯中。將這個(gè)操作改為將 URL 字段設(shè)置為空字符串。
為了在用戶自動(dòng)注銷時(shí)存儲(chǔ) URL,我們將 清單 13 中的代碼添加到 _autologout_logout 函數(shù)中,放在將用戶重定向到首頁的 drupal_goto 調(diào)用前面。
清單 13. 在 _autologout_logout() 函數(shù)中添加存儲(chǔ) URL 的代碼
function _autologout_logout() {
...
global $user;
$r = db_query("SELECT * FROM {autologout} WHERE uid = %d",
$user->uid);
if (db_num_rows($r) > 0) {
$r = db_query("UPDATE {autologout} SET url = '%s' WHERE uid = %d",
check_url($_SERVER["HTTP_REFERER"]), $user->uid);
}
else {
$r = db_query("INSERT INTO {autologout} SET uid = %d, url = '%s'",
$user->uid, check_url($_SERVER["HTTP_REFERER"]));
}
if (!$r) {
watchdog('user', 'Unable to insert current url before auto logout',
WATCHDOG_ERROR);
}
...
}
|
這是非常標(biāo)準(zhǔn)的代碼,可以確保以用戶 ID 作為鍵將 URL 存儲(chǔ)在 autologout 表中。為了進(jìn)行審計(jì),如果遇到問題,就發(fā)出一條警告消息。
為了確保在用戶重新登錄之后將用戶重定向到這個(gè) URL,可以在現(xiàn)有的 user 鉤子中使用登錄操作。autologout.module 在現(xiàn)有的 user 鉤子使用一個(gè)分支語句來識(shí)別觸發(fā)函數(shù)調(diào)用的操作。清單 14 顯示用來獲得 URL 并將用戶重定向到這個(gè) URL 的分支語句。
清單 14. 在 autologin.module 中在登錄之后對(duì)用戶進(jìn)行重定向
function autologout_user($op, &$edit, &$account, $category = NULL) {
...
case 'login':
$q = db_query("SELECT url FROM {autologout} WHERE uid = %d",
$account->uid);
if ($r = db_fetch_object($q)) {
if (isset($r->url) and $r->url != '') {
drupal_goto($r->url);
}
}
break;
...
|
如果成功地從 autologout 表中獲得了與用戶 ID 對(duì)應(yīng)的 URL,drupal_goto 將用戶重定向到這個(gè) URL。
當(dāng)然,如果用戶從 Web 站點(diǎn)手工注銷,那么就需要將為這個(gè)用戶存儲(chǔ)的所有 URL 重新設(shè)置為空字符串。為此,可以在現(xiàn)有的 user 鉤子中使用 logout 操作。清單 15 顯示 user 鉤子中執(zhí)行此操作的代碼。
清單 15. 清除存儲(chǔ)的 URL
...
case 'logout':
$r = db_query("SELECT * FROM {autologout} WHERE uid = %d",
$account->uid);
if (db_num_rows($r) > 0) {
$r = db_query("UPDATE {autologout} SET url = '' WHERE uid = %d",
$account->uid);
if (!$r) {
watchdog('user', 'Unable to reset URL before logout',
WATCHDOG_ERROR);
}
}
break;
...
|
對(duì) autologout.module 的這些修改啟用了客戶端超時(shí)控制和警告,以及在自動(dòng)注銷之前存儲(chǔ)用戶的當(dāng)前 URL 的特性。
在下一節(jié)中,學(xué)習(xí)如何創(chuàng)建一個(gè)模塊來向用戶顯示依從信息,并創(chuàng)建檢查這些條款和條件的周期。
依從機(jī)制
IBC 為 Web 站點(diǎn)維護(hù)一個(gè)使用策略,其中包含一組條款和條件,IBC 成員必須確認(rèn)并同意這些條款和條件,之后才能與 Web 站點(diǎn)進(jìn)行交互。還需要進(jìn)行定期檢查,以確保用戶依從這些要求。
在創(chuàng)建任何新模塊之前,一定要檢查是否有現(xiàn)有的模塊能夠提供所需的功能,或者作為開發(fā)新模塊的起點(diǎn)。我們考慮了 Legal 模塊,它的功能與新模塊有些重疊。Legal 模塊在賬號(hào)注冊(cè)期間向讀者提供 Terms and Conditions 頁面,要求讀者同意。但是,我們客戶的過程更嚴(yán)格。在 IBC 管理團(tuán)隊(duì)創(chuàng)建成員賬號(hào)之后,新成員在第一次登錄期間需要查看并同意依從頁面,然后才能看到其他內(nèi)容。無論如何,Legal 模塊是一個(gè)良好的起點(diǎn)。
在用戶登錄之后,compliance 模塊提供一個(gè)依從信息頁面,如 圖 2 所示。
圖 2. 在登錄之后顯示的依從頁面,要求用戶確認(rèn)這些條款和條件
這個(gè)頁面要求用戶確認(rèn)這些信息,并在他們同意之前限制他們與 Web 站點(diǎn)的交互。它記錄哪些用戶已經(jīng)同意了依從信息,并創(chuàng)建 圖 3 所示的依從信息視圖。這個(gè)模塊還提供一個(gè)檢查周期,定期確保 Web 站點(diǎn)用戶確認(rèn)了依從信息。第 6 部分 中解釋了基本模塊的構(gòu)造過程,所以我們不再討論模塊的總體結(jié)構(gòu),而是集中于這個(gè) compliance 模塊特有的實(shí)現(xiàn)細(xì)節(jié)。
圖 3. 管理員使用的依從審計(jì)頁面
安裝文件
包含 compliance 模塊數(shù)據(jù)的表的模式包含以下內(nèi)容:
uid |
以用戶 ID 作為鍵 |
accept_date |
用戶最近一次接受依從信息的日期 |
expiration_date |
用戶需要重新確認(rèn)依從信息的日期(如果啟用了檢查周期) |
url |
臨時(shí)存儲(chǔ)用戶當(dāng)前的 URL,用來將用戶重定向回原來的位置 |
expiration_date 列其實(shí)是多余的,因?yàn)榭梢詫z查時(shí)間偏移量與 accept_date 時(shí)間相加來決定這個(gè)時(shí)間。但是,使用這個(gè)列比較方便。
清單 16 給出在 mysql/mysqli 數(shù)據(jù)庫中創(chuàng)建存儲(chǔ)依從信息的表的代碼。
清單 16. 在 compliance.install 中創(chuàng)建 users_compliance 表
function compliance_install() {
global $db_type;
$created = FALSE;
switch ($db_type) {
case 'mysql':
case 'mysqli':
$q = db_query("
CREATE TABLE IF NOT EXISTS users_compliance (
uid integer unsigned NOT NULL default '0',
accept_date integer NOT NULL default '0',
expiration_date integer NOT NULL default '0',
url varchar(255) NOT NULL default '',
PRIMARY KEY (uid)
);
");
if($q) {
$created = TRUE;
}
break;
case 'pgsql':
break;
}
_compliance_install_init();
if ($created) {
drupal_set_message(t('Compliance module installation was successful.'));
}
else {
drupal_set_message(t('Installation for the Compliance module was unsuccessful.'));
}
}
|
在創(chuàng)建這個(gè)表之后,調(diào)用 _compliance_install_init() 函數(shù)。這會(huì)用默認(rèn)值對(duì) compliance 模塊使用的變量進(jìn)行初始化,見 清單 17。
清單 17. 在 compliance.install 文件中設(shè)置 compliance 模塊默認(rèn)值
function _compliance_install_init() {
variable_set('compliance_page_node', 0);
variable_set('compliance_page_tile', t('Terms and conditions'));
variable_set('compliance_review_enabled', 0);
variable_set('compliance_review_cycle_time', 365);
variable_set('compliance_message',t('Please read the following and answer the '.
'question at the bottom of the page. Thank you.'));
variable_set('compliance_question',t('Do you agree to these term and conditions?'));
variable_set('compliance_positive_answer',t('Agree'));
variable_set('compliance_negative_answer',t('Disagree'));
variable_set('compliance_disagree_url', '/logout');
variable_set('compliance_reset', 0);
}
|
variable_set 函數(shù)將這些變量及其值存儲(chǔ)在變量表中?,F(xiàn)在,當(dāng)在 admin/modules 顯示的模塊列表中啟用這個(gè)模塊時(shí),將創(chuàng)建這個(gè)數(shù)據(jù)庫表并將模塊的默認(rèn)值安裝進(jìn) Drupal 系統(tǒng)。如果具有適當(dāng)?shù)脑L問權(quán)限,就可以使用依從設(shè)置(admin/settings/compliance)編輯這些變量。
這些變量包括:
compliance_page_node |
用來描述依從信息的頁面的節(jié)點(diǎn) ID。 |
compliance_page_title |
當(dāng)顯示用戶確認(rèn)表單時(shí),依從信息的標(biāo)題。 |
compliance_review_enabled |
用來啟用或禁用檢查機(jī)制的標(biāo)志。 |
compliance_review_cycle_time |
要求用戶再次確認(rèn)依從信息之前的時(shí)間(以天為單位)。 |
compliance_message |
一個(gè)可選的消息,它放在用戶確認(rèn)表單中依從信息的頂部。 |
compliance_question |
要求用戶確認(rèn)表單中的依從信息的問題。 |
compliance_positive_answer |
確認(rèn)按鈕上使用的文本,用戶按這個(gè)按鈕就表示接受依從信息。 |
compliance_negative_answer |
拒絕按鈕上使用的文本,用戶按這個(gè)按鈕就表示拒絕依從信息。 |
compliance_disagree_url |
如果用戶拒絕依從信息,就將他們重定向到這個(gè) URL。如果用戶接受依從信息,就將他們重定向到原來的 Web 頁面。 |
訪問權(quán)限
注意, variable_set 函數(shù)也可以將傳遞給它的數(shù)組串在一起。這樣就可以為所有依從設(shè)置使用一個(gè)記錄,而不是為每個(gè)變量建立一個(gè)記錄。在獲取設(shè)置時(shí),可以使用 drupal_unpack 函數(shù)分解這些值。
我們假設(shè)任何通過身份驗(yàn)證的用戶都可以看到一個(gè)顯示依從信息的表單,這個(gè)表單要求用戶確認(rèn)此信息。所需的訪問限制僅僅是只允許管理員配置 compliance 模塊并查看依從審計(jì)頁面。審計(jì)頁面顯示哪些用戶已經(jīng)確認(rèn)了依從信息。清單 18 給出實(shí)現(xiàn)這些訪問權(quán)限的 perm 和 access 鉤子。
清單 18. 在 compliance.module 中配置訪問權(quán)限
/***
* Implementation of hook_perm
*/
function compliance_perm() {
return array('administer compliance','audit compliance');
}
/**
* Implementation of hook_access().
*/
function compliance_access($op, $node) {
if ($op == 'view') {
return user_access('access content');
}
else if ($op == 'create' || $op == 'update' || $op == 'delete') {
if(user_access('administer compliance')) {
return true;
}
else {
return false;
}
}
else {
return false;
}
}
|
URL 定義
包含依從信息的頁面是作為基本頁面節(jié)點(diǎn)創(chuàng)建的(/node/add/page )。對(duì)于 IBC Web 站點(diǎn),我們使用 Drupal 4.7 附帶的 Path 模塊為依從信息的頁面節(jié)點(diǎn)設(shè)置別名 URL /tncs (“terms and conditions” 的縮寫)。在頁面底部的鏈接中使用這個(gè)別名,讓用戶隨時(shí)都可以查看 Web 站點(diǎn)的依從信息。
依從頁面(/compliance )是 tncs 頁面節(jié)點(diǎn)加上一些額外內(nèi)容,讓用戶可以對(duì)依從信息進(jìn)行響應(yīng)。我們?cè)?清單 19 所示的菜單鉤子中定義這個(gè) URL。
這個(gè)菜單鉤子中注冊(cè)了另一個(gè) URL(/compliance/audit )。這個(gè)頁面顯示哪些用戶已經(jīng)確認(rèn)了依從信息,如 圖 3 所示。
注意,用來為依從審計(jì)頁面提供樣式的默認(rèn)層疊樣式表(CSS)文件是通過 theme_add_style 函數(shù)提供給 Drupal 系統(tǒng)的。
清單 19. 在 compliance.module 的菜單鉤子中注冊(cè) URL
function compliance_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array('path' => 'compliance',
'title' => variable_get('compliance_page_title',
t('Terms and conditions')),
'access' => user_access('access content'),
'type' => MENU_CALLBACK,
'callback' => 'compliance_display');
$items[] = array('path' => 'compliance/audit',
'title' => t("Compliance audit"),
'access' => user_access('audit compliance'),
'type' => MENU_CALLBACK,
'callback' => 'compliance_audit');
theme_add_style(drupal_get_path('module', 'compliance') .
'/compliance.css');
}
return $items;
}
|
設(shè)置
正如 第 6 部分 所述,settings 鉤子提供一個(gè)表單,用來交互式地管理模塊的變量。這個(gè)鉤子使用 Drupal Forms API 構(gòu)建表單布局。compliance 模塊的 settings 鉤子中比較有意思的部分見 清單 20。
表單部件之一創(chuàng)建一個(gè) SELECT 表單元素,其中將頁面節(jié)點(diǎn)以 OPTION 元素的形式列出。然后,可以選擇其中之一作為依從信息頁面。如果沒有找到頁面節(jié)點(diǎn),就提示用戶添加一個(gè)節(jié)點(diǎn)。
注意,每個(gè)元素的 $form 數(shù)組名與 variable_get 函數(shù)中的變量名匹配。當(dāng) Drupal 處理用 settings 構(gòu)建的表單時(shí),它使用這個(gè)數(shù)組名作為變量來存儲(chǔ)相關(guān)聯(lián)的表單元素值。所以,在 $form['autologout']['alert_time'] 中定義的表單元素的值將在變量表中作為 alert_time 存儲(chǔ)。這種方式與 autologout_settings 函數(shù)中存儲(chǔ)變量的方式稍有不同。
清單 20. 為 compliance.module 中的 settings 鉤子中的選擇部件建立頁面節(jié)點(diǎn)選項(xiàng)
function compliance_settings() {
...
$q = db_query('SELECT n.nid, r.title FROM node n INNER JOIN
node_revisions r USING (vid) WHERE n.type="page"');
while ($r = db_fetch_object($q)) {
$options[$r->nid] = t($r->title);
$selected = $r->nid;
}
if (count($options) == 0) {
drupal_set_message(t('Please %link that will display the compliance terms '.
'and conditions and then come back to this page to complete '.
'the compliance settings.',
array('%link' => l(t('add a page'), 'node/add/page/'))));
return;
}
...
$form['compliance_page']['compliance_page_node'] = array(
'#type' => 'select',
'#title' => t('Page'),
'#default_value' => variable_get('compliance_page_node', $selected),
'#options' => $options,
'#description' => t('This is the page displayed to the user describing '.
'the compliance terms and conditions. You can create '.
'a %link if no existing pages are appropriate to use.',
array('%link' => l(t('new page'), 'node/add/page/')))
);
$form['compliance_page']['compliance_page_title'] = array(
'#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => variable_get('compliance_page_title',
t('Terms and conditions')),
'#description' => t('This is the text of the title that will '.
'override the one that comes with the node chosen above.')
);
$form['compliance_ack'] = array(
'#type' => 'fieldset',
'#title' => t('Acknowledgment'),
'#weight' => -14,
'#description' => t('So that we can confirm that the user has read '.
'and/or agrees to this compliance content, a question '.
'is presented to ask the user to acknowledge this '.
'compliance page before moving on. If a positive answer '.
'is given, the user is redirected to the original page '.
'they would have gone to. If a negative answer is given, '.
'then the user is redirected to the URL defined below.')
);
$form['compliance_ack']['compliance_message'] = array(
'#type' => 'textfield',
'#title' => t('Message'),
'#default_value' => variable_get('compliance_message',
t('Please read the following and answer '.
'the question at the bottom of the page. '
'Thank you.')),
'#description' => t('This is the text of a message that will appear at '.
'the top of the compliance content intended to '.
'instruct the user to read the page and answer '.
'the question defined below.')
);
...
}
|
依從頁面
如 清單 19 所示,向 Drupal 菜單系統(tǒng)注冊(cè)了 URL /compliance ,從而觸發(fā) compliance_display 函數(shù)。清單 21 給出這個(gè)函數(shù)。我們希望使用創(chuàng)建的頁面節(jié)點(diǎn)顯示依從信息,但是要求從 compliance 模塊控制它,以便使用 view 鉤子這樣的函數(shù)添加內(nèi)容,比如確認(rèn)表單。
清單 21. 在 compliance.module 中控制依從頁面的顯示
function compliance_display() {
$nid = variable_get('compliance_page_node', 0);
if ($nid == 0 || !is_numeric($nid)) {
drupal_not_found();
}
$node = node_load($nid);
if ($node->nid) {
$title = check_plain(variable_get('compliance_page_title', t('Terms and conditions')));
drupal_set_title($title);
$node->title = $title;
$node->type = 'compliance';
return node_show($node, 'view');
}
}
|
這個(gè)函數(shù)的前半部分通過 node_load 函數(shù)使用節(jié)點(diǎn) ID 獲得頁面節(jié)點(diǎn)的節(jié)點(diǎn)對(duì)象。后半部分使用存儲(chǔ)的標(biāo)題文本覆蓋頁面和節(jié)點(diǎn)標(biāo)題,將節(jié)點(diǎn)類型從 page 改為 compliance ,然后使用 node_show 函數(shù)要求 Drupal 顯示這個(gè)節(jié)點(diǎn)。
改變節(jié)點(diǎn)類型使 compliance 模塊能夠控制頁面節(jié)點(diǎn)的內(nèi)容。清單 22 演示如何使用 view 鉤子添加內(nèi)容。
清單 22. 在 compliance.module 中向現(xiàn)有的依從頁面添加內(nèi)容
function compliance_view(&$node) {
$form = array();
$form['compliance_ack_question'] = array(
'#type' => 'markup',
'#value' => '<p>'.variable_get('compliance_question',
t('Do you agree to these term and conditions?')).'</p>'
);
$form['compliance_ack_disagree'] = array(
'#type' => 'submit',
'#value' => variable_get('compliance_negative_answer', t('Disagree'))
);
$form['compliance_ack_agree'] = array(
'#type' => 'submit',
'#value' => variable_get('compliance_positive_answer', t('Agree'))
);
$node->body .= drupal_get_form('compliance_ack', $form);
drupal_set_message(variable_get('compliance_message',
t('Please read the following and answer the '.
'question at the bottom of the page. '.
'Thank you.')));
return $node;
}
|
這個(gè)表單提示用戶確認(rèn)依從信息,它是用 Form API 構(gòu)建的。使用 drupal_get_form 函數(shù)將這個(gè)表單主題化成 XHTML。這個(gè)函數(shù)使用表單 ID (compliance_ack )設(shè)置處理表單檢驗(yàn)和提交的回調(diào)函數(shù)(見下一節(jié)中的描述)。然后將這個(gè) XHTML 表單追加到現(xiàn)有的頁面(目前是依從頁面)節(jié)點(diǎn)內(nèi)容中。然后使用 drupal_set_message 函數(shù)將存儲(chǔ)的消息添加到 Drupal 消息隊(duì)列中。
處理確認(rèn)表單
使用 drupal_get_form 函數(shù)和表單 ID (compliance_ack )讓 Drupal 尋找與這個(gè)表單 ID 相關(guān)聯(lián)的提交和檢驗(yàn)鉤子函數(shù)。對(duì)于這個(gè)表單 ID ,鉤子函數(shù)分別稱為 compliance_ack_submit 和 compliance_ack_validate 。compliance 模塊只使用 submit 鉤子,如 清單 23 所示。
清單 23. 在 compliance.module 中處理確認(rèn)表單
function compliance_ack_submit($form_id, $form_values) {
switch($_POST["op"]) {
case variable_get('compliance_positive_answer', t('Agree')):
$url = _compliance_get_destination();
_compliance_set_accept();
drupal_goto($url);
break;
case variable_get('compliance_negative_answer', t('Disagree')):
drupal_goto(variable_get('compliance_disagree_url', '/logout'));
break;
}
}
|
使用一個(gè)分支語句測試在提交表單之后在 $_POST 數(shù)組中傳遞回來的 $op 變量。如果用戶給出了肯定的響應(yīng),就使用 清單 24 所示的 _compliance_get_destination 函數(shù)獲得原來請(qǐng)求的 URL(見下一節(jié)中的解釋)。通過 清單 25 所示的 _compliance_set_accept 函數(shù),使用接受日期和過期日期更新 users_compliance 表中這個(gè)用戶的記錄。用戶被重定向到原來請(qǐng)求的位置。如果用戶給出了否定的響應(yīng),用戶就被重定向到存儲(chǔ)的指定位置。
清單 24 所示的 _compliance_get_destination 函數(shù)僅僅獲取以前為這個(gè)用戶存儲(chǔ)的 URL 信息。如果沒有找到 URL,就返回空字符串。將空字符串傳遞給 drupal_goto 函數(shù),就會(huì)將用戶重定向到首頁。
清單 24. 在 compliance.module 中獲取存儲(chǔ)的 URL
function _compliance_get_destination($uid = NULL) {
global $user;
if ($uid == NULL) {
$uid = $user->uid;
}
$url = '';
$q = db_query('SELECT url FROM {users_compliance} WHERE uid = %d', $uid);
if (db_num_rows($q) > 0) {
$r = db_fetch_object($q);
$url = $r->url;
}
return $url;
}
|
_compliance_set_accept 函數(shù)使用接受時(shí)間和過期時(shí)間在 users_compliance 表中更新或創(chuàng)建記錄。過期時(shí)間只在啟用了檢查周期的情況下使用。
清單 25. 在 compliance.module 中存儲(chǔ)接受依從信息的情況
function _compliance_set_accept($uid = NULL) {
global $user;
if ($uid == NULL) {
$uid = $user->uid;
}
$now = time();
$later = $now + (variable_get('compliance_review_cycle_time',365) * 60 * 60 * 24 );
db_query("DELETE FROM {users_compliance} WHERE uid = %d", $user->uid);
db_query("INSERT INTO {users_compliance} SET uid = %d, accept_date = %d, ".
"expiration_date = %d, url=''", $user->uid, $now, $later);
watchdog('user', 'Compliance has been acknowledged', WATCHDOG_NOTICE);
}
|
觸發(fā)依從頁面的顯示
依從頁面應(yīng)該在用戶剛剛通過身份驗(yàn)證(或者說登錄進(jìn) Web 站點(diǎn))時(shí)顯示。compliance 模塊要求用戶確認(rèn)依從信息,然后才能做其他事情。通過在 user 鉤子中使用 login 操作(見 清單 26)就可以實(shí)現(xiàn)這個(gè)需求。
清單 26. 在 compliance.module 中在登錄之后觸發(fā)依從頁面的顯示
function compliance_user($op, &$edit, &$account, $category = NULL) {
if ($account->uid < 2) {
return; // UID 0 or UID 1 not applicable
}
switch ($op) {
case 'login':
if (_compliance_expired()) {
_compliance_set_destination();
drupal_goto('compliance');
}
break;
}
}
|
因?yàn)?Drupal 系統(tǒng)的第一個(gè)用戶是特殊的根用戶,我們要確保不對(duì)他應(yīng)用依從過程。通過使用一個(gè)分支語句測試操作變量 $op 中的 login 值,檢查用戶是否對(duì)依從信息給出了肯定的響應(yīng),并使用 清單 27 所示的 _compliance_expired 函數(shù)檢查用戶是否需要再次查看并確認(rèn)依從信息。
如果用戶需要查看依從頁面,就使用 _compliance_set_destination 函數(shù)存儲(chǔ)他們的當(dāng)前 URL,這樣的話,在他們確認(rèn)依從信息之后,就可以將他們重定向回這個(gè)位置。最后,將用戶重定向到依從頁面。
_compliance_expired 函數(shù)見 清單 27。從 users_compliance 表獲取用戶記錄中的過期日期字段。如果沒有找到記錄或者過期日期字段為零,用戶就需要查看依從頁面。如果啟用了依從檢查周期,而且過期日期值是過去的某一時(shí)間,用戶也需要查看依從頁面。
清單 27. 在 compliance.module 中檢查是否應(yīng)該顯示依從頁面
function _compliance_expired($uid = NULL) {
global $user;
if ($uid == NULL) {
$uid = $user->uid;
}
$q = db_query('SELECT expiration_date FROM {users_compliance} '.
'WHERE uid = %d', $user->uid);
if (db_num_rows($q) > 0) {
$r = db_fetch_object($q);
if (((time() > (int)$r->expiration_date)) &&
(variable_get('compliance_review_enabled', 0) == 1)) {
// expired – need to ack again
return true;
}
elseif ((int)$r-> expiration_date == 0) {
// record exists but no previous ack
return true;
}
}
else {
// no previous ack
return true;
}
return false;
}
|
_compliance_set_destination 函數(shù)(見 清單 28)在 users_compliance 表中創(chuàng)建或更新用戶的記錄,確保將 URL 字段設(shè)置為當(dāng)前 URL。
清單 28. 在 compliance.module 中將用戶目標(biāo) URL 存儲(chǔ)在 user_compliance 表中
function _compliance_set_destination($uid = NULL) {
global $user;
if ($uid == NULL) {
$uid = $user->uid;
}
$q = db_query('SELECT * FROM {users_compliance} WHERE uid = %d', $uid);
$url = check_url(url($_GET["q"]));
if (db_num_rows($q) > 0) {
$q = db_query('UPDATE {users_compliance} SET url = "%s" WHERE '.
'uid = %d', $url ,$uid);
}
else {
$q = db_query('INSERT INTO {users_compliance} '.
'(uid, accept_date, expiration_date, url) '.
'VALUES (%d, %d, %d, "%s")', $uid, 0, 0, $url);
}
return $q;
}
|
用 $_GET["q"] 值調(diào)用 url 函數(shù)來獲得 URL,然后將 URL 傳遞給 check_url 函數(shù)。調(diào)用 check_url 的目的是防止跨站點(diǎn)腳本攻擊導(dǎo)致的惡意使用。
依從審計(jì)頁面
使用 URL /compliance/audit 顯示審計(jì)頁面,如 圖 3 所示。這個(gè) URL 導(dǎo)致調(diào)用 清單 29 所示的 compliance_audit 函數(shù)。這個(gè)頁面提供 user_compliance 表的主題化視圖。在默認(rèn)情況下,主題化輸出突出顯示還沒有確認(rèn)依從信息的用戶,也就是需要再次查看依從信息的用戶。
清單 29. 在 compliance.module 中創(chuàng)建審計(jì)視圖
function compliance_audit() {
$q = db_query('SELECT u.uid,u.name,uc.accept_date,uc.expiration_date '.
'FROM users_compliance uc RIGHT OUTER JOIN users u USING '.
'(uid) ORDER BY u.name ASC');
$acknowledments = array();
while ($r = db_fetch_object($q)) {
$acknowledments[] = $r;
}
return theme('compliance_audit', $acknowledments);
}
|
將 users_compliance 表中的數(shù)據(jù)逐行提取到一個(gè)數(shù)組中,然后傳遞給主題函數(shù)來創(chuàng)建依從審計(jì)頁面體中使用的 XHTML 內(nèi)容。theme 函數(shù)鉤子 compliance_audit 也傳遞給這個(gè)函數(shù)。通過使用 第 7 部分 中描述的主題函數(shù)選擇過程,Drupal 尋找一個(gè)適當(dāng)?shù)闹黝}函數(shù)來創(chuàng)建 XHTML。
compliance.module 文件中提供了審計(jì)頁面的默認(rèn)主題函數(shù) theme_compliance_audit ,見 清單 30。
清單 30. compliance.module 中審計(jì)頁面的默認(rèn)主題函數(shù)
function theme_compliance_audit($ack) {
$header = array(t('User name'), t('Accept date'), t('Expiration date'),t('Status'));
$rows = array();
foreach($ack as $row) {
if ($row->uid < 2) {
continue;
}
$class = 'compliance_okay';
$status = 'Okay';
$accept = ' - ';
$expiration = ' - ';
if ((time() > $row->expiration_date) &&
variable_get('compliance_review_enabled',0) == 1) {
$class = 'compliance_expired';
$status = 'Expired';
}
if (!isset($row->expiration_date)) {
$class = 'compliance_unaccounted';
$status = 'Unaccounted';
}
$name = l($row->name,'user/'.$row->uid);
if (isset($row->accept_date)) {
$accept = format_date($row->accept_date,'custom','l, F j, Y');
}
if (isset($row->expiration_date)) {
$expiration = format_date($row->expiration_date,'custom','l, F j, Y');
}
$rows[] = array('data'=>
array($name,$accept,$expiration,$status),'class'=>$class);
}
$output = '<div id="compliance-audit">';
$output .= '<h2>Compliance audit</h2>';
if (variable_get('compliance_review_enabled',0) == 1) {
$output .= '<p>A review cycle of ' .
format_plural(variable_get('compliance_review_cycle_time',365),
'a day','%count days') . ' has been enabled.</p>';
}
$output .= theme('table', $header, $rows);
$output .= '</div>';
return $output;
}
|
在這里,將來自數(shù)據(jù)庫的每行數(shù)據(jù)添加到 $rows 數(shù)組中,然后將它主題化成一個(gè)表格。使用 l 函數(shù)創(chuàng)建鏈接。注意,在 rows 數(shù)組中添加了類,用來幫助識(shí)別用戶依從的不同狀態(tài)。這些樣式在 compliance.css 文件中定義,在前面描述的 menu 鉤子中添加進(jìn)系統(tǒng)。
當(dāng)然,這只是默認(rèn)主題函數(shù),可以用另一個(gè)主題函數(shù)覆蓋它,從而創(chuàng)建更精細(xì)的審計(jì)頁面。
結(jié)束語
在本文中,了解了 IBC Web 站點(diǎn)的需求以及這些需求如何影響外部網(wǎng)站點(diǎn)。為了實(shí)現(xiàn)這些需求,學(xué)習(xí)了如何創(chuàng)建登錄頁面來防止未通過身份驗(yàn)證的用戶訪問 Web 站點(diǎn)中的內(nèi)容,使用 Automated Logout 模塊啟用客戶端注銷計(jì)時(shí)器和警告,創(chuàng)建一個(gè)新模塊來提供 Web 站點(diǎn)使用策略所需的依從機(jī)制。
在本系列的下一篇文章中,將了解 Drupal 的分類法系統(tǒng),學(xué)習(xí)如何使用它對(duì)內(nèi)容進(jìn)行組織和導(dǎo)航。
致謝
作者希望感謝 Moshe Weitzman、Károly Négyesi(也稱為 “chx”)、Boris Mann 和 Jeff Robbins 提供的意見和建議。
參考資料
學(xué)習(xí)
獲得產(chǎn)品和技術(shù)
- 使用 IBM 試用軟件 構(gòu)建您的下一個(gè)開發(fā)項(xiàng)目,這些軟件可以從 developerWorks 直接下載。
討論
|
|