他システムと連携可能なMoodleプラグインの開発

カバー

はじめに

この記事では、他システムと連携可能なMoodleプラグインの開発方法を紹介します。

私の所属するチームでは、大学が運営する学修支援システム(LMS)と動画配信システムを連携させ、動画配信システムに蓄積された動画のURLをLMSに自動で公開する連携システムを開発しました。この連携システムが今後サポートするLMSの一つにMoodleがあります。Moodleは、多くの大学で採用されているオープンソースのプラットフォームでプラグインによる機能拡張が容易です。

プラグインを開発するためには、Moodleの仕様やプラグインの実装ルールに関する前提知識が必要です。しかし、プラグイン開発についてのウェブ上の情報は少なく、日本語のドキュメントが散在しているため、なかなか学習が進まないという課題があります。特に、Moodleを外部から操作可能な手段(WebAPI)に関する情報が不足しているため、開発が難航することがあります。

このような背景を踏まえて、プラグイン開発に役立つ基本知識について紹介します。

今回の紹介で用いる構成

当社連携システムを例に、他システムと連携可能なMoodleプラグインを開発する基礎的な方法を説明します。当社連携システムの目的は「連携システムが自動的に動画配信システムの動画URLをMoodle上に公開すること」「Moodleから動画配信システム上の動画をシームレスに再生できるようにすること」です。この目的達成には、以下の2点が必要です。

  1. 連携システムからMoodleを操作できること
  2. Moodleから動画配信サーバと個別に認証し再生できること

alt text

今回は「Moodleのコースから動画配信システムの動画を再生可能にする」「連携システムからMoodleにWebAPIで"活動名称"、"動画URL"が登録できる」プラグインを作成します。なお、連携システムや動画配信システムの詳細については、今回の主旨ではないため扱いません。

Moodleに関する前提知識

Moodleのプラグイン開発を行うためには、Moodleのアーキテクチャについて理解しておく必要があります。このセクションでは、プラグイン開発に必要なMoodleの重要な概念について説明します。 なお、この記事ではMoodleの知識の他、PHP、HTML、リレーショナルデータベースの知識も必要ですが、これらについては扱いません。

アクセスコントロール

Moodleでは、ログインしたユーザに対してさまざまなアクセスコントロールを設定できます。アクセスコントロールには、「ユーザ」「コンテクスト」「ロール」「パーミッションとケイパビリティ」という重要な概念があります。

ユーザ

Moodleに登録された、Moodleを利用する個人の情報を指します。ユーザそのものには、直接的なアクセスコントロール設定機能はありません。

コンテクスト

公式ドキュメントでは「ロールを割り当てることができるスペース」と定義されています。例えば、Moodleのサイト自体が一つのコンテクストであり、その内部にカテゴリやコース、さらにはサブカテゴリといった他のコンテクストが存在します。 Moodleには以下の6つのコンテクストタイプがあります。

  • システム
  • ユーザ
  • カテゴリ
  • コース
  • 活動
  • ブロック

これらのコンテクストにユーザとロールを割り当てることで、ユーザは当該コンテクストを参照する際に、適切な権限を持つことができます。

ロール

特定のコンテクスト内で特定のユーザに割り当てることのできるパーミッション群です。ロールには「標準ロール」と「カスタムロール」が存在し、カスタムロールは標準ロールをベースにするか、もしくは何もない状態から独自のロールを作成できます。標準ロールは以下の9つが存在します。

  • サイト管理者
  • マネージャ
  • コース作成者
  • 教師
  • 非編集教師
  • 学生
  • ゲスト
  • 認証されたユーザ
  • フロントページロールの認証済みユーザ

標準の設定では、ログイン直後のロールは「認証されたユーザ」が全ユーザ共通で割り当てられます。

パーミッションとケイパビリティ

パーミッションとは、いくつものケイパビリティに対し必要に応じて実行可否を設定し、利用者の操作権限を制限するものです。ケイパビリティとは、サイト、ユーザ、コース、モジュールなどに対して実行できる操作を定義します。 各ケイパビリティの設定には「設定なし」「許可」「抑制」「禁止」の4つが選択できます。 基本的には、初期設定では各ケイパビリティは「設定なし」に設定されており、必要に応じてロールに許可を割り当てます。セキュリティの観点から、割り当てるケイパビリティの許可は必要最小限に留めるべきです。

プラグインの実装

Moodleの機能を拡張するプラグインにはさまざまな種類があります。コース上で特定の活動を行うための「活動モジュール」の利用頻度が一番高いため、詳解します。

Moodleの実行環境の準備

自作プラグインを作成する前に、動作確認用のMoodleを用意しておきます。今回は、ダウンロードしてすぐ利用ができるWindows用Moodleパッケージをダウンロードします。

活動モジュールは/path/to/moodle/mod/(モジュール名)フォルダに配置されるため、他モジュールのフォルダの内容を参考にすると理解しやすくなります。

バージョン

この記事で扱うMoodleのバージョン4.5.0を使用します。2024年10月時点での最新の安定版であり、バージョン4.5はLTS(Long Term Support)版としてサポート期間が長く設定されています。そのため、これから開発するにあたり最適なバージョンです。

活動モジュールの実装

今回は「testmod」という活動モジュールを作成します。活動モジュールを作成するには、testmod/フォルダを作成し、そのフォルダ配下に以下の必須ファイルを作成します。

ファイル内容
db/access.phpプラグインがどのような機能を実現するかを定義します。
db/install.xmlプラグインがどのようなデータを保持するかを定義します。プラグインインストール時に呼び出されます。
lang/en/testmod.php英語の言語ファイルを配置します。作成は必須となります。
lang/ja/testmod.php多言語対応のため、日本語で定義した言語ファイルを配置します。
pix/icon.gifMoodle上で表示されるアイコンファイルです。16x16のGif形式で作成します。pix/フォルダにはアイコン以外の画像ファイルも配置することができます。
lib.php自モジュールが特定のコースで生成・更新・削除される場合に呼び出される関数を定義します。
mod_form.phpコースにモジュールを追加/編集する際、ブラウザで描画する画面の生成ルールを定義します。
index.php特定のコースに存在する自モジュールをリストアップする際使用されます。
view.phpコースがページのレイアウトや活動モジュールを表示する際には、本スクリプトを使って表示用のリンクを作成します。
version.php自作プラグインのバージョン、必要とするMoodleのバージョンなどの属性を定義します。

上記ファイルを実装していくには、MoodleのAPIを確認しながら進める必要があります。MoodleのAPIについてはAPI Guidesを確認してください。

活動モジュールを外部から操作できるようにするためには、外部サービスを定義します。外部サービスには、利用可能範囲、利用可能な外部関数、利用可能なユーザを定義します。外部サービスで用いる外部関数はモジュール毎に実装し提供します。今回はサンプルとして「testmod」に以下の外部関数を用意します。

  • create_testmod
    • 活動「testmod」を作成する。入力にcourseid:intname:texturl:textを受け付ける。
    • 指定したコースに「テストモジュール」セクションを作成し、コースモジュールと活動「testmod」を作成する。
    • 成功したらTrueを返す
ファイル内容
db/services.phpプラグインがどのような外部関数を提供するかを定義します。
classes/external/create_testmod.php外部関数の挙動を実装するクラスファイルです。外部関数1つを1ファイルで作成します。

他にも以下のファイルを標準で用意するのですが、基本動作に影響はないため今回の解説では扱いません。

ファイル内容
backup/プラグインのバックアップを行うための機能を実装するフォルダ
db/events.phpプラグイン内でイベントをフックしプラグイン固有の処理をさせるためのファイル
db/upgrade.phpプラグインアップグレード時のデータベース更新手続きを実装したファイル
mobile.phpモバイルからのアクセスに対応するファイル

db/access.php

本ファイルには、プラグインのアクセスルールの構成および初期設定値を定義します。定義したルールはケイパビリティの1つとして一覧に反映されます。このファイルでは少なくとも「mod/testmod:addinstance(活動モジュールを生成する)」「mod/testmod:view(活動モジュールを表示する)」を実装する必要があります。

riskbitmaskには、生じる可能性のあるセキュリティリスクを設定します。リスク設定によっては使用できるロールに制限がかかります。デフォルト設定では、ゲストロールだとリスクのない機能のみを許容し、学生ロールだとRISK_SPAMである機能を許容し、教師ロールにはRISK_PERSONAL, RISK_XSSである機能を許容し、管理者にはすべてのリスクを許容します。

archetypesclonepermissionsfromには、自作プラグインを初回インストールする際、既存のロールに対して自作プラグインへのパーミッションをどう割り当てるか設定します。clonepermissionsfromを使用すると、記述したケイパビリティを持つ全ロールに対して当該ケイパビリティを割り当てることができ、カスタムロールにも適用することもできます。

db/access.phpのサンプルコード
<?php
// ケイパビリティを定義;ロール編集時のケイパビリティ一覧に出現する
$capabilities = [
// testmodを新規登録する
'mod/testmod:addinstance' => [
'riskbitmask' => RISK_SPAM, // 許可した時のリスク、RISK_SPAM:サイトに表示可能なコンテンツを追加
'captype' => 'write', // 「read」または「write」を指定
'contextlevel' => CONTEXT_COURSE, // 活動モジュールが最も関連するコンテキストレベル
// 初回インストール時に当該ケイパビリティを割り当てるロールを記載する。標準ロールを記載する。
'archetypes' => [
'editingteacher' => CAP_ALLOW, // 教師
'manager' => CAP_ALLOW // マネージャ
],
// 初回インストール時に指定のケイパビリティを持つ全ロールに当該ケイパビリティを割り当てる。
// 以下は、「コースに活動モジュールを追加/削除/更新を行う」を持つ全ロールに当該ケイパビリティを割り当てる。
'clonepermissionsfrom' => 'moodle/course:manageactivities'
],
// testmodを表示する
'mod/testmod:view' => [
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => [ // このケイパビリティは全ての標準ロールで許容される
'guest' => CAP_ALLOW,
'student' => CAP_ALLOW,
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
],
],
];

db/install.xml

XML形式で初期データベースを作成するためのテーブル構造を作成します。ファイル内に記載すべきXMLの構成はmoodle/lib/xmldb/xmldb.xsdで定義されています。

<FIELDS>の以下のパラメータは事実上必須のため必ず定義してください:idcoursenametimemodifiedintrointroformat

db/install.xmlのサンプルコード
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/testmod/db" VERSION="2024103000" COMMENT="XMLDB file for Moodle mod_testmod"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="testmod" COMMENT="test module">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="intro" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="introformat" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<!-- 活動モジュールで使用するデータを定義する -->
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="url" TYPE="char" LENGTH="512" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="course" TYPE="foreign" FIELDS="course" REFTABLE="course" REFFIELDS="id"/>
</KEYS>
</TABLE>
</TABLES>
<!--
<TABLES>
活動モジュールでサブテーブルを使用するときは追加で定義する
</TABLES>
-->
</XMLDB>

lang/[言語コード]/testmod.php

当該活動モジュールで使用される文言を [言語コード](ISO 639にて定義)で指定された言語に置き換えるためのファイルです。少なくとも[言語コード]がen(英語)を定義する必要があります。また、活動プラグインの場合は必ずmodulenameを定義する必要があります。

lang/ja/testmod.phpのサンプルコード
<?php
$string['invalidurl'] = '不正なURLフォーマット.';
$string['modulename'] = 'テストモジュール';
$string['modulenameplural'] = 'テストモジュール';
$string['namealreadyexists'] = '指定の名称は既に存在している.';
$string['pluginadministration'] = 'テストモジュール管理';
$string['pluginname'] = 'testmod';
$string['testmod:addinstance'] = 'testmodをコースに新規登録する';
$string['testmod:view'] = 'testmodを表示する';
$string['url'] = 'コンテンツURL';

Moodleの標準に従うとプレースホルダ名でアルファベット順に並べる必要があります。

lib.php

特定のコースで当該活動モジュールが生成・更新・削除される際に呼び出される関数を定義します。このファイルでは、以下のメソッドを必ず実装します。

  • [modulename]_add_instance($instancedata, $mform = null)
  • [modulename]_update_instance($instancedata, $mform)
  • [modulename]_delete_instance($id)

これらの関数は設定フォームでユーザに入力された情報を受け取り、Moodle内部のデータベースに追加、更新、削除を行います。$instancedataには、mod_form.phpにて受け付けた入力を含むインスタンスが渡されます。

[modulename]_add_instance 他 サンプルコード
<?php
function testmod_add_instance($instancedata, $mform = null): int {
global $DB; // Moodleデータベースにアクセスするためのオブジェクト
// 以下の値はフォームで入力されないため自ら更新する
$instancedata->timecreated = time();
$instancedata->timemodified = $instancedata->timecreated;
// mod_form.phpで入力させた値をすべて含む$instancedataをMoodleデータベースにそのまま登録する
$instancedata->id = $DB->insert_record('testmod', $instancedata);
// ※その他依存したレコードを追加
return $instancedata->id;
}
function testmod_update_instance($instancedata, $mform): bool {
global $DB;
// 以下の値はフォームで入力されないため自ら更新する
$instancedata->timemodified = time();
// mod_form.phpで入力させた値で更新
$instancedata->id = $instancedata->instance;
$DB->update_record('testmod', $instancedata);
return true;
}
function testmod_delete_instance($id): bool {
global $DB;
if ( !$testmod = $DB->get_record('testmod', ['id' => $id]) ) {
return false;
}
// ※その他依存したレコードを削除
return $DB->delete_records('testmod', array('id' => $testmod->id));
}

また、モジュールの状態をシステムで確認できるようにするため、以下のメソッドも実装します。

  • function [modulename]_supports(string $feature)

このメソッドは、プラグインがサポートしている機能を確認するタイミングでMoodleシステムから呼び出されます。当該モジュールが指定された機能($feature)をサポートしている場合はtrueを返し、サポートしていなければfalseを返し、認識していない機能を指定されたらnullを返すように実装します。

$featureにFEATURE_MOD_PURPOSEを指定された場合、アクティビティの目的MOD_PURPOSE_XXXXXXを返します。

[modulename]_supportsのサンプルコード
function testmod_supports(string $feature) {
switch ($feature) {
case FEATURE_GROUPS:
case FEATURE_GROUPINGS:
case FEATURE_MOD_INTRO:
case FEATURE_COMPLETION_TRACKS_VIEWS:
case FEATURE_GRADE_HAS_GRADE:
case FEATURE_GRADE_OUTCOMES:
case FEATURE_BACKUP_MOODLE2:
case FEATURE_SHOW_DESCRIPTION:
return false;
case FEATURE_MOD_PURPOSE:
return MOD_PURPOSE_CONTENT;
default:
return null;
}
}

Moodleのコアは全てのプラグインのlib.phpをロードするため、本ファイルは極力小さくすることが強く推奨されています。内部で使用するクラスを実装する場合、classes/フォルダに配置されることが推奨されています。 中にはlocallib.phpにサブ関数を実装しているプラグインもありますが、これはレガシーな実装でありこのファイルへの実装はもはや推奨されていません。

mod_form.php

コースに活動モジュールを追加するとき、もしくは活動モジュールを更新するときに実行されます。活動モジュールの初期設定値を入力するためのフォームを定義します。

mod_form.phpのサンプルコード
<?php
require_once($CFG->dirroot.'/course/moodleform_mod.php');
require_once($CFG->dirroot.'/mod/testmod/lib.php');
class mod_testmod_mod_form extends moodleform_mod {
// 入力フォームを作成する
function definition() {
global $CFG, $DB, $OUTPUT;
// ここに入力フォームを実装
$mform = $this->_form;
$mform->addElement('header', 'general', get_string('general', 'form'));
// 活動名称
$mform->addElement('text', 'name', get_string('name'), ['size' => '64']);
$mform->setType('name', PARAM_TEXT);
$mform->addRule('name', null, 'required', null, 'client');
// 動画URL
$mform->addElement('text', 'url', get_string('url', 'testmod'), ['size' => '64']);
$mform->setType('url', PARAM_TEXT);
$mform->addRule('url', null, 'required', null, 'client');
// 標準コースモジュール要素を追加
$this->standard_coursemodule_elements();
// アクションボタン追加
$this->add_action_buttons();
}
// 入力データチェックを行う
function validation($data, $files) {
$errors = parent::validation($data, $files);
// URLバリデーション
if (!empty($data['url']) && !filter_var($data['url'], FILTER_VALIDATE_URL)) {
$errors['url'] = get_string('invalidurl', 'testmod');
}
// 同一名称チェック
global $DB;
if ($DB->record_exists('testmod', ['name' => $data['name']])) {
$errors['name'] = get_string('namealreadyexists', 'testmod');
}
return $errors;
}
}

index.php

特定のコースで現在のユーザがアクセスできる活動モジュールのすべてのインスタンスを一覧表示するための実装を行います。

index.phpのサンプルコード
<?php
require_once('../../config.php');
$id = required_param('id', PARAM_INT); // URLからコースIDを取得
// ユーザがこのコースにアクセスする権限があるか確認する
if( !$course = $DB->get_record('course', ['id'=> $id], '*', MUST_EXIST) ){
throw new \moodle_exception('Invalid course ID: ' . $id);
}
require_course_login($course);
$PAGE->set_pagelayout('incourse');
// HTMLヘッダの表示
$strmodules = get_string("modulenameplural", "testmod");
$PAGE->navbar->add($strmodules);
$PAGE->set_title("$course->shortname: $strmodules");
$PAGE->set_heading($course->fullname);
echo $OUTPUT->header();
echo $OUTPUT->heading($strmodules, 2);
$modinfo = get_fast_modinfo($course);
foreach ($modinfo->get_instances_of('testmod') as $instanceid => $cm) {
// モジュールの情報を表示する例
echo $OUTPUT->box(format_string($cm->name));
}
echo $OUTPUT->footer();

view.php

活動モジュールを表示するためのページを生成するための実装を行います。コースの一覧にて活動モジュールをクリックすると、本ファイルのリンクにidが付与されたものが自動生成されます。idはコースモジュールID で、活動モジュールインスタンスを取得するために使用します。

view.phpのサンプルコード
<?php
// view.phpの必須処理
require_once('../../config.php');
global $DB, $PAGE, $USER, $OUTPUT;
$cmid = required_param('id', PARAM_INT);
list ($course, $cm) = get_course_and_cm_from_cmid($cmid, 'testmod');
require_login($course, true, $cm);
$testmod = $DB->get_record('testmod', ['id'=> $cm->instance], '*', MUST_EXIST);
if (!$testmod) {
throw new moodle_exception('Unable to find the specified test module.');
}
// ページ設定:ヘッダー
$PAGE->set_url('/mod/testmod/view.php', ['id' => $cmid]);
$PAGE->set_title("$course->shortname: " . format_string($testmod->name));
$PAGE->set_heading($course->fullname);
echo $OUTPUT->header();
// ページ設定:メイン
echo html_writer::tag('a', "クリックして再生", ['href' => $testmod->url]);
// ページ設定:フッター
echo $OUTPUT->footer();

version.php

プラグインに関する情報を定義します。このファイルは必ず作成しなければなりません。

version.phpのサンプルコード
<?php
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'mod_testmod'; //プラグインの名称
$plugin->version = 2024103000; //プラグインのモジュールバージョン(形式: YYYYMMDDXX: 年, 月, 日, リビジョン)
$plugin->requires = 2024100700; //要求する最小Moodleバージョン
// プラグインの成熟度(安定性)を指定する
// MATURITY_ALPHA (in testing),
// MATURITY_BETA (more stable testing),
// MATURITY_RC (release candidate),
// MATURITY_STABLE (stable for production)
$plugin->maturity = MATURITY_ALPHA;

db/services.php

当該モジュールが持つ外部関数を定義します。ここで定義する外部関数の挙動はclasses/external/[function_name].phpで実装します。

db/services.phpのサンプルコード
<?php
$functions = [
'mod_testmod_create_testmod' => [
'classname' => 'mod_testmod\external\create_testmod', //クラスの名前空間
'description' => 'Create new testmod.', // 外部関数の説明
'type' => 'write', // 書き込み操作のために「write」を指定
'capabilities' => 'mod/testmod:addinstance' // 必須ケイパビリティです
],
];
$functionsは配列型で、当該モジュールで提供したい関数を列挙します。それぞれの関数のプロパティを連想配列で定義します。連想配列には`ajax`、`services`、`methodname`など他にも定義できる値がありますが、今回は取り扱いません。

classes/external/[function_name].php

外部システムから外部関数が呼び出された際に実行されるファイルです。このファイルでは以下に従ったクラスを必ず実装します。

  • external_apiを継承し、クラス名がfunction_nameであること
  • execute_parameters() を実装すること
  • execute() を実装すること
  • execute_returns() を実装すること
classes/external/[function_name].phpのサンプルコード
<?php
namespace mod_testmod\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use invalid_parameter_exception;
use moodle_exception;
defined('MOODLE_INTERNAL') || die();
class create_testmod extends external_api {
// APIの入力パラメータ定義を取得する
// リテラル値だけの定義の場合、external_value()を用いる
// 構造を持つ定義の場合、external_multiple_structure() > external_single_structure()
// > external_value() の順で各メンバーを構成する
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters (
[
'courseid' => new external_value(
PARAM_INT,
'id of course',
VALUE_REQUIRED
),
'name' => new external_value(
PARAM_TEXT,
'activity name',
VALUE_REQUIRED
),
'url' => new external_value(
PARAM_TEXT,
'content url',
VALUE_DEFAULT,
''
),
]
);
}
// APIの戻り値パラメータ定義を取得する
public static function execute($courseid, $name, $url): array {
global $DB;
// 入力データのバリデーションチェック
$params = self::validate_parameters(self::execute_parameters(), [
'courseid' => $courseid,
'name' => $name,
'url' => $url,
]);
$course_id = $params['courseid'];
// セキュリティチェック
// 外部関数はシステム全体に影響するユーザを用いるためシステムコンテクストを取得する
$context = \context_system::instance();
self::validate_context($context);
// 自らの処理で必要なケイパビリティを列挙する。自己申告なので要求するケイパビリティを記載しないと素通りしてしまうため注意
require_capability('mod/testmod:addinstance', $context);
// Moodle DBトランザクションを介し、例外が発生した時は全クエリをロールバック
$transaction = $DB->start_delegated_transaction();
// モジュール定義(testmod)を取得
$module = $DB->get_record('modules', ['name' => 'testmod']);
// コースを探す
if( !$course = $DB->get_record('course', ['id' => $course_id]) ){
throw new invalid_parameter_exception('course not found');
}
// セクションを作る
$maxsection = $DB->get_field('course_sections', 'MAX(section)', ['course' => $course_id]);
$section = new \stdClass();
$section->course = $course_id;
$section->section = $maxsection + 1; // セクションの連番を付ける
$section->name = 'テストモジュール';
$section->timemodified = time();
$section->id = $DB->insert_record('course_sections', $section);
// 活動モジュールを作る
$testmod = new \stdClass();
$testmod->course = $course_id;
$testmod->name = $params['name'];
$testmod->intro = "";
$testmod->introformat = 0;
$testmod->timecreated = time();
$testmod->timemodified = $testmod->timecreated;
$testmod->url = $params['url'];
$testmod->id = $DB->insert_record('testmod', $testmod);
// コースモジュールを作る
$course_module = new \stdClass();
$course_module->course = $course_id;
$course_module->module = $module->id;
$course_module->instance = $testmod->id;
$course_module->section = $section->id;
$course_module->added = $testmod->timecreated;
$course_module->id = $DB->insert_record('course_modules', $course_module);
// セクション更新
if( $sequence = $DB->get_field('course_sections', 'sequence', ['id' => $section->id]) ){
$section->sequence .= ',' . $course_module->id;
} else {
$section->sequence = $course_module->id;
}
$section->timemodified = time();
$DB->update_record('course_sections', $section);
// エラー時は自動的にロールバック
$transaction->allow_commit();
// コースキャッシュ更新
rebuild_course_cache($course_id, true);
return [
'status' => true,
'warnings' => [],
];
}
// APIの戻り値のパラメータ定義を取得する
public static function execute_returns() {
return new external_single_structure(
[
'status' => new external_value(PARAM_BOOL, 'status: true if success'),
'warnings' => new external_warnings()
]
);
}
}

外部サービスを有効化する

プラグイン(活動モジュール、外部サービス)の実装方法について解説しましたが、Moodleで当該活動モジュールを外部サービス経由で利用するにはサーバとしての設定が必要です。外部サービスは以下の手順で有効化します。

  1. プロトコルを有効化する
    サイト管理 > サーバ > ウェブサービス > プロトコルを管理する から、使用したいプロトコルを「有効にする」にします(今回はREST) alt text

  2. ウェブサービス認証を有効化する
    サイト管理 > プラグイン > 認証 > 認証管理 から、ウェブサービス認証を「有効にする」にします alt text

  3. 外部関数専用ユーザを作成する
    サイト管理 > ユーザ > アカウント > 新しいユーザを追加する からユーザを追加します。 ※ 設定項目「認証方法を選択する」は"ウェブサービス認証"を選択します

  4. 外部関数専用ロールを作成する
    サイト管理 > ユーザ > パーミッション > ロールを定義する から外部関数専用ユーザに割り当てるロールを作成します

    ケイパビリティ説明
    mod/testmod:addinstance活動testmodをコースに追加する
    mod/testmod:view活動testmodを参照する
  5. システムコンテクストに専用ロールと専用ユーザを割り当てる
    サイト管理 > ユーザ > パーミッション > システムロールを割り当てる から作成した専用ロールに作成したユーザを追加します

  6. 外部サービスを作成する
    サイト管理 > サーバ > ウェブサービス > 外部サービス で外部サービスを作成します。 ※「有効」には必ずチェックを入れ、「許可ユーザのみ」もセキュリティ観点上チェックを入れることをお勧めします。(今回はチェックを入れる)

  7. 外部サービスに許可ユーザを登録する

  8. 外部サービスに使用可能な関数を登録する

    関数説明
    mod_testmod_create_testmod活動testmodを作成する
  9. トークンを発行する
    サイト管理 > サーバ > ウェブサービス > トークンを管理する からトークンを作成します。 ※トークンは初回のみ表示され画面を切り替えてしまうと表示されなくなるため、必ずメモを取ります

動作を確認する

自作プラグインを作成し外部サービスの設定を終えたら、実際にMoodle上で動作確認します。

  1. デバッグモードを有効にする
    サイト管理 > 開発 > デバッグ でデバッグ画面を開き、デバッグメッセージで以下を選択する

    DEVELOPER:開発者のための特別Moodleデバッグメッセージ

    またデバックメッセージを表示するにチェックを入れる

  2. Moodle本体を展開した以下のフォルダに作成したモジュールを配置する

    Terminal window
    /path/to/moodle/mod
  3. Moodleにログインし画面を更新すると配置したモジュールが検出されるため、Moodleの案内に従いインストールする

  4. Moodle画面からプラグインの動作を確認する

    • コースを作成し、新しい活動(testmod)を追加する
    • 登録した活動を更新する
    • 登録した活動を削除する

    ※画面にデバッグログが表示された場合は問題が発生しているため、デバッグログが出ないようにソースコードを修正する。

  5. 外部関数を実行して確認する
    コマンドプロンプトを起動し、以下のコマンドを実行して成功応答が返るかを確認する

    Terminal window
    curl -X POST -k "https://localhost/webservice/rest/server.php?wstoken=[トークン]&wsfunction=mod_testmod_create_testmod&courseid=[コースID]&name=[活動名]&url=[動画URL]&moodlewsrestformat=json"

パッケージ化する

動作確認ができたら、モジュールをパッケージ化します。パッケージ化とは、プラグインがMoodleの要求する形式で作られていることを検証し、問題がなければZIP形式で圧縮します。検証方法について詳細が知りたい場合はPlugin Validationをご確認ください。

パッケージ化は、プラグインを外部に公開したい場合に行いますが、自分の構築した環境でのみ使用する場合は必要ありません。

  • プラグインは単一のZIPファイルであること
  • ZIPファイルには、プラグインにちなんで名付けられたサブフォルダが1つだけ含まれること
  • サブフォルダは、プラグインタイプの最上位フォルダ(mod/等)に直接解凍できるようにレイアウトすること
  • サブフォルダにversion.phpが含まれること
  • サブフォルダに言語ファイルlang/en/[modulename].phpが含まれること

(活動モジュール特有)

  • 言語ファイルには、$string['modulename']が実装されていること
  • version.phpに$module->version$module->requiresが実装されていること
  • サブフォルダに以下が含まれていること lib.phpview.phpindex.phpdb/install.xmldb/upgrade.php
  • lib.phpには[modulename]_add_instance[modulename]_update_instanceが実装されていること

命名規則や構成が上記の通りであれば、Moodleは自動的にプラグインタイプを判別し、適切な場所へインストールします。

おわりに

この記事では、Moodleのプラグイン開発における活動モジュールと外部関数の実装を目指すにあたり、以下について解説しました。

  • Moodle前提知識:Moodleでプラグイン開発を行うにあたり、押さえておくべき項目について解説しました。
  • プラグインの実装:活動モジュールや外部関数についてどのように機能を実装するのか解説しました。
  • 外部サービスの設定:Moodleで外部サービスを有効化するための手順を詳述し、プロトコルの設定からユーザやロール設定までの流れを説明しました。
  • 動作確認とパッケージ化:作成したプラグインの動作確認、および、パッケージ化する方法について具体的な手順を説明しました。

Moodleでのプラグイン開発が初めての方でも、体系的に理解を深め、実際の開発作業に役立てることができるようになることを目指しました。今後のプラグイン開発において、この記事が今後のプラグイン開発をスムーズに行うための助けとなれば幸いです。

ご覧いただきありがとうございました。


TOP
アルファロゴ 株式会社アルファシステムズは、ITサービス事業を展開しています。このブログでは、技術的な取り組みを紹介しています。X(旧Twitter)で更新通知をしています。