作者:Tim Hunt
譯者:Li Shijian(李詩(shī)劍)
Moodle是一款為教育系統(tǒng)設(shè)計(jì)的Web應(yīng)用。我會(huì)對(duì)Moodle各個(gè)部分如何運(yùn)作做一個(gè)綜述,同時(shí)我將專注于介紹幾個(gè)我認(rèn)為特別有趣的設(shè)計(jì):
Moddle?提供了一個(gè)師生之間的在線教學(xué)平臺(tái)。一個(gè)Moodle站點(diǎn)被劃分為不同的課程。特定的用戶可以以不同的身份參與到一門課程中去,比如學(xué)生或者老師。每一門課程都由一系列的資源和活動(dòng)組成。一個(gè)資源可以是一份PDF文檔,Moodle的一個(gè)HTML頁(yè)面,或者干脆是一個(gè)指向網(wǎng)絡(luò)上其他位置的鏈接。一個(gè)活動(dòng)可能是一個(gè)論壇,一次測(cè)驗(yàn),或者是一個(gè)Wiki。在一門課程中,這些資源和活動(dòng)以某種方式被組織起來(lái)。例如,它們可以按照邏輯上的話題,或者是日程上特定的周目被分配到一起。
圖13.2: 大學(xué)系統(tǒng)典型架構(gòu)
Moodle專注于為所有參與到教學(xué)中的人提供一個(gè)在線平臺(tái),而不是為某一個(gè)教育組織特別設(shè)計(jì)的某個(gè)系統(tǒng)。Moodle僅僅為非主要功能提供了最基本的實(shí)現(xiàn),所以它可以單獨(dú)的作為一個(gè)應(yīng)用,或者與其它系統(tǒng)進(jìn)行集成。Moodle扮演的角色被正式地稱為虛擬教學(xué)環(huán)境(VLE),或者是教學(xué)/課程管理系統(tǒng)(LMS,CMS,甚至LCMS)。
Moodle是一個(gè)用PHP編寫的開源免費(fèi)軟件(GPL)。它可以在絕大多數(shù)的Web服務(wù)器和平臺(tái)上運(yùn)行。它需要一個(gè)數(shù)據(jù)庫(kù),目前支持MySQL,PostgreSQL,MS SQL Server以及Oracle。
Moodle項(xiàng)目由Martin Dougiamas在1999年開創(chuàng),當(dāng)時(shí)他正在澳大利亞的科廷大學(xué)工作。1.0版本于2002年發(fā)布,當(dāng)時(shí)使用的語(yǔ)言和數(shù)據(jù)庫(kù)版本是PHP 4.2和MySQL 3.23。那時(shí)的版本從一開始就限制了Moodle可能采用的框架。然而從那以后,整個(gè)軟件發(fā)生了翻天覆地的變化。現(xiàn)在發(fā)布的版本是Moodle 2.2.x系列。
Moodle安裝由三部分組成:
/var/www/moodle
?或者?~/htdocs/moodle
?的目錄里。Web服務(wù)器應(yīng)該對(duì)這個(gè)目錄具有寫權(quán)限。moodledata
?目錄。這個(gè)目錄用于存儲(chǔ)用戶上傳的文件以及系統(tǒng)生成的文件,同樣Web服務(wù)器需要有對(duì)這個(gè)目錄的寫權(quán)限。出于安全考慮,這個(gè)目錄應(yīng)該設(shè)置于Web根目錄之外。以上三部分可以完全部署在一臺(tái)服務(wù)器上?;蛘?,采用負(fù)載均衡的設(shè)置,在每臺(tái)Web服務(wù)器上都部署代碼,但是僅僅共用一個(gè)數(shù)據(jù)庫(kù)和一個(gè)很有可能在其他服務(wù)器上的moodledata
目錄。
當(dāng)Moodle安裝完畢后,這三個(gè)部分的配置信息被存儲(chǔ)在moodle
根目錄下的config.php
文件中。
Moodle是一個(gè)Web應(yīng)用,所以用戶通過瀏覽器來(lái)與之交互。從Moodle自己的視角來(lái)看,這就意味著它要響應(yīng)HTTP請(qǐng)求。Moodle的一個(gè)重要設(shè)計(jì)考量就是URL的名字空間,以及URL如何被調(diào)度到不同的腳本上。
Moodle在這里采用PHP標(biāo)準(zhǔn)方法。瀏覽一個(gè)課程的主頁(yè)時(shí),URL可能像?.../course/view.php?id=123
,這里123
就是這門課程在數(shù)據(jù)庫(kù)中的唯一標(biāo)識(shí)。瀏覽一個(gè)論壇討論時(shí),URL可能是.../mod/forum/discuss.php?id=456789
。也就是說(shuō),這些特定的腳本,course/view.php
?或者mod/forum/discuss.php
?會(huì)來(lái)處理這些請(qǐng)求。
這對(duì)于開發(fā)者來(lái)說(shuō)是非常簡(jiǎn)單的。想要理解Moodle是怎么處理一個(gè)特定的請(qǐng)求,你只需要觀察URL,從閱讀那份php文件的代碼開始。但是從用戶的角度來(lái)看這是十分丑陋的,因?yàn)檫@些URL是永久不變的。比方說(shuō)一個(gè)課程改了名字,或者一個(gè)管理員把一個(gè)討論轉(zhuǎn)移到另一個(gè)論壇中,這些URL都不會(huì)變。(這對(duì)于URL來(lái)說(shuō)是一個(gè)非常好的性質(zhì),正如Tim Berners-Lee在他的文章Cool URIs don't change中提到的)
另一種可以采用的方法是建立一個(gè)唯一入口?.../index.php/[其他使請(qǐng)求唯一確定的信息]
。這個(gè)單獨(dú)的index.php
腳本會(huì)通過某種方式將請(qǐng)求進(jìn)行調(diào)度。這個(gè)方法添加了一個(gè)大多數(shù)軟件開發(fā)者都喜歡用的間接層。缺少了這個(gè)間接層并不會(huì)影響到Moodle的使用。
和許多其它成功的開源項(xiàng)目一樣,Moodle由許多和系統(tǒng)內(nèi)核協(xié)同工作的插件構(gòu)建起來(lái)。這是一個(gè)絕妙的主意,因?yàn)樗梢允褂脩舭凑账麄兌ㄖ频姆椒▉?lái)增強(qiáng)Moodle的功能。一個(gè)開源系統(tǒng)的重要優(yōu)勢(shì)在于,你可以根據(jù)自己的特定需求來(lái)更改它。然而,為代碼增加高可定制性的同時(shí),會(huì)在系統(tǒng)升級(jí)的時(shí)候引入大麻煩,即使我們已經(jīng)采用了很好的版本控制系統(tǒng)。Moodle的插件通過定義好的API與內(nèi)核交互,所以在自包含的插件中,可以允許盡可能多的用戶定制與新特性被開發(fā)出來(lái)。這也方便了用戶根據(jù)需求定制自己的Moodle,分享這些定制內(nèi)容,同時(shí)也便于對(duì)Moodle系統(tǒng)內(nèi)核進(jìn)行升級(jí)。
有許多不同的方法可以將一個(gè)系統(tǒng)構(gòu)建成插件化的。Moodle具有一個(gè)相對(duì)龐大的內(nèi)核,并且插件是強(qiáng)類型的。我所說(shuō)的相對(duì)龐大的內(nèi)核,指的是內(nèi)核提供了大量的功能。這違反了那類,由一個(gè)小型的插件啟動(dòng)器進(jìn)行引導(dǎo),其余部分都是插件的架構(gòu)設(shè)計(jì)。
當(dāng)我提及插件是強(qiáng)類型的時(shí)候,我指的是根據(jù)你想要實(shí)現(xiàn)的具體功能,你可能需要寫完全不同的插件,實(shí)現(xiàn)不同的API。比如,一個(gè)新的活動(dòng)模塊插件會(huì)與一個(gè)新的認(rèn)證插件,或者是提問插件截然不同。根據(jù)最后統(tǒng)計(jì),現(xiàn)在我們一共有35種不同的插件(這里有一個(gè)Moodle插件類型完全列表)。這違背了那類,所有插件通過使用最基本的API,通過注冊(cè)它們感興趣的鉤子和事件與內(nèi)核進(jìn)行交互的架構(gòu)設(shè)計(jì)。
通常來(lái)說(shuō),Moodle現(xiàn)在有嘗試把更多的功能移到插件中以減小內(nèi)核的趨勢(shì)??墒沁@并沒有帶來(lái)巨大的成功,因?yàn)楫?dāng)前一個(gè)逐漸增長(zhǎng)的特性集趨于去擴(kuò)展內(nèi)核。另一個(gè)趨勢(shì)是盡可能將不同種類的插件進(jìn)行規(guī)范化。這樣在許多公共功能上,比如安裝和升級(jí),所有類型的插件都能夠按照統(tǒng)一的方式運(yùn)行。
一個(gè)Moodle中的插件其實(shí)就是一個(gè)包含許多文件的目錄。每一個(gè)插件都有一個(gè)類型和名字,這兩個(gè)構(gòu)成了這個(gè)插件的"Frankenstyle"組件名稱。("Frankenstyle"這個(gè)單詞出自于開發(fā)者Jabber頻道的一次討論,人人都愛它,所以這個(gè)單詞就被固定下來(lái)了)插件的類型和名字決定了這個(gè)插件目錄的路徑。插件類型給定一個(gè)前綴,目錄名稱就是這個(gè)插件的名字。這里有一些例子:
| 插件類型 | 插件名稱 | Frankenstyle | 目錄 |
| mod (Activity module) | forum
| mod_forum
| mod/forum
|
| mod (Activity module) | quiz
| mod_quiz
| mod/quiz
|
| block (Side-block) | navigation
| block_navigation
| blocks/navigation
|
| qtype (Question type) | shortanswer
| qtype_shortanswer
| question/type/shortanswer
|
| quiz (Quiz report) | statistics
| quiz_statistics
| mod/quiz/report/statistics
|
最后的一個(gè)例子表明了每一個(gè)活動(dòng)模塊被允許聲明子插件類型。只有活動(dòng)模塊才能做到這個(gè),出于兩點(diǎn)原因。首先如果所有的插件都可以聲明子插件類型,這或許會(huì)帶來(lái)嚴(yán)重的性能問題。另外活動(dòng)模塊是Moodle中最重要的教育活動(dòng),也是插件中最重要的類型,所以它們應(yīng)該具有特殊的權(quán)限。
我會(huì)以一個(gè)具體的插件實(shí)例來(lái)解釋Moodle架構(gòu)中的大量細(xì)節(jié)。作為慣例,我選擇實(shí)現(xiàn)一個(gè)顯示"Hello world"的插件。
這個(gè)插件實(shí)際上并不適合任何一種Moodle標(biāo)準(zhǔn)插件。它只是一個(gè)簡(jiǎn)單的腳本,和其他任何東西都沒有聯(lián)系,所以我選擇把它制作成一個(gè)'local'類型的插件。這是一個(gè)catch-all的插件類型,專門處理一些雜亂的功能,所以在這里再適合不過了。我給我的插件命名為greet
,所以它的Frankenstyle的名字是local_greet
,路徑為local/greet
。(插件代碼下載)
每一個(gè)插件都必需包含一個(gè)叫做version.php
的文件,這個(gè)文件定義了關(guān)于這個(gè)插件本身的元數(shù)據(jù)。Moodle的插件安裝系統(tǒng)會(huì)使用它來(lái)對(duì)插件進(jìn)行安裝和升級(jí)。例如local/greet/version.php
包含代碼:
<?php
$plugin->component = 'local_greet';
$plugin->version = 2011102900;
$plugin->requires = 2011102700;
$plugin->maturity = MATURITY_STABLE;
因?yàn)榭梢詮穆窂缴巷@然地推導(dǎo)出插件的名字,所以乍看之下代碼里面包含組件名(component name)略顯多余。而實(shí)際上,安裝器需要通過組件名來(lái)驗(yàn)證插件是否安裝在正確的位置上。版本(Version)字段定義了這個(gè)插件的版本,成熟度(Maturity)是諸如ALPHA,BETA,RC(發(fā)布候選版, release candidate), 或者STABLE這樣的標(biāo)簽。Requires字段標(biāo)識(shí)著能和這個(gè)版本兼容的Moodle最低版本號(hào)。必要的話,你也要記錄下這個(gè)插件依賴的其他插件。
這里是這個(gè)簡(jiǎn)單插件的主要腳本(存儲(chǔ)在local/greet/index.php
):
<?php
require_once(dirname(__FILE__) . '/../../config.php'); // 1
require_login(); // 2
$context = context_system::instance(); // 3
require_capability('local/greet:begreeted', $context); // 4
$name = optional_param('name', '', PARAM_TEXT); // 5
if (!$name) {
$name = fullname($USER); // 6
}
add_to_log(SITEID, 'local_greet', 'begreeted',
'local/greet/index.php?name=' . urlencode($name)); // 7
$PAGE->set_context($context); // 8
$PAGE->set_url(new moodle_url('/local/greet/index.php'),
array('name' => $name)); // 9
$PAGE->set_title(get_string('welcome', 'local_greet')); // 10
echo $OUTPUT->header(); // 11
echo $OUTPUT->box(get_string('greet', 'local_greet',
format_string($name))); // 12
echo $OUTPUT->footer(); // 13
require_once(dirname(__FILE__) . '/../../config.php'); // 1
這單獨(dú)的一行是大多數(shù)工作都要首先完成的。我之前說(shuō)過,config.php
包含著Moodle如何連接數(shù)據(jù)庫(kù)以及找到metadata
目錄的細(xì)節(jié)。然后,它以一行require_once('lib/setup.php')
結(jié)束。這樣:
require_once
加載所有Moodle標(biāo)準(zhǔn)庫(kù);require_login();
這行使得Moodle利用管理員配置過的任何認(rèn)證插件來(lái)判斷,當(dāng)前訪問用戶是否已經(jīng)登錄。
一個(gè)與Moodle整合性更好的插件會(huì)在這里傳遞更多的參數(shù),比如這個(gè)頁(yè)面屬于哪個(gè)課程或者活動(dòng)。然后調(diào)用的require_login
仍然會(huì)檢查是否當(dāng)前用戶是否參加了這門課程或者活動(dòng)。如果是,用戶就可以訪問這門課程,或者觀看這個(gè)活動(dòng);如果不是,那么適當(dāng)?shù)腻e(cuò)誤信息會(huì)被顯示出來(lái)。
接下來(lái)的兩行代碼顯示出如何檢查用戶是否有做某件事的權(quán)限。正如你所見,從開發(fā)者的角度來(lái)說(shuō),這些API都十分的簡(jiǎn)單。但是,實(shí)際上在這下面是一個(gè)非常復(fù)雜的接入系統(tǒng)。這會(huì)給管理員很大的伸縮性,以控制什么人可以做什么。
$context = context_system::instance(); // 3
在Moodle中,同一個(gè)人可能在不同的地方擁有不同的權(quán)限。比如說(shuō)一個(gè)用戶可能在某個(gè)課程上做一名老師,也可能是另一門課程的一位學(xué)生。這些地方被稱作為上下文(context)。上下文在Moodle中構(gòu)筑了一個(gè)特別像文件系統(tǒng)中目錄結(jié)構(gòu)那樣的多層結(jié)構(gòu)。
在系統(tǒng)的上下文中,有許多的上下文信息被構(gòu)造出來(lái),它們負(fù)責(zé)維護(hù)那些為了組織課程而被創(chuàng)建的不同分類(Category)。這些上下文可以是嵌套的,比如在一個(gè)分類里面包含有其他更多的分類。分類上下文同時(shí)也包含著課程上下文。最后,每一個(gè)課程中的活動(dòng)也會(huì)擁有一個(gè)自己的Moodle上下文。
更多建議: