Moodleの活動モジュールからJavaScriptで非同期通信を行う方法

はじめに

Moodleの活動モジュールにてボタンがクリックされた際に,Moodleサーバへリクエストを飛ばし,DBにアクセスしたかった.
活動モジュールのディレクトリ内にリクエストを受け取るファイルを設けて,そこに向かって Client JS からリクエストを投げても良かったが,セキュリティやらなんやらを考慮した結果, Moodle が用意してくれている機能を用いて上記を実現しようと思った次第.
Moodleやら活動モジュールの説明は割愛.

開発環境

require([‘core/ajax’])

Moodle では JavaScriptMoodleサーバと非同期で通信するために core/ajax (Moodleのルートディレクトリ/lib/amd/src/ajax.js) というものが用意されている.
そのため,活動モジュール開発者はリクエストパラメーターとコールバック関数だけを書けばいい.
見出しの require(['core/ajax']) という書き方は,公式のドキュメントに記載されている書き方だが, var ajax = require('core/ajax') のようにも書ける.

require(['core/ajax']) を用いる場合,

require(['core/ajax'], function(ajax) {
  let promises = ajax.call([
    methodname: 'hoge', args: {hoge: 'hogehoge', fuga: 'fugafuga'},
    methodname: 'fuga', args: {hoge: 'hogehoge', fuga: 'fugafuga'},
  ]);

  promises[0].done(function(res) {
    console.log(res);
  }).fail(function(err) {
    console.log(err);
  });

  promises[1].done(function(res) {
    console.log(res);
  }).fail(function(err) {
    console.log(err);
  });
});

と書けるため,グローバルを汚染せずに済む.
今回は,ボタンがクリックされた際に上記のような非同期通信を行いたいので, button.addEventListener('click', function(e) { ... }); の … の部分に記述する.

Moodle側のエンドポイント

core/ajax の実装を見ると, wwwroot(http://localhost:8888/moodle)/lib/ajax/service.php もしくは service-nologin.php を叩いていることがわかる.
では, ajax.call の引数に与えている methodnameargs は何に用いるのか?
これらは,Moodleが内部でごにょごにょして,最終的に methodname で指定したメソッドに対して args を与えて実行している.(厳密には少し違う)
そのため,活動モジュール開発者はサーバ側での処理を記述しておかなければならない.(当たり前っちゃ当たり前)
サーバ側で実行する処理を記述するためにいじるファイルは, Moodleのルートディレクトリ/mod/モジュール名/db/services.phpMoodleのルートディレクトリ/mod/モジュール名/externallib.php の2つ.

<?php /* Moodleのルートディレクトリ/mod/モジュール名/db/services.php */
$functions = array(
    'hoge' => array(
        'classname'   => 'mod_hoge_external',
        'methodname'  => 'methodname',
        'classpath'   => "mod/モジュール名/externallib.php",
        'description' => 'description',
        'type' => 'write',
        'loginrequired' => false,
    ),
    'fuga' => array(
        'classname'   => 'mod_fuga_external',
        'methodname'  => 'methodname',
        'classpath'   => "mod/モジュール名/externallib.php",
        'description' => 'description',
        'type' => 'write',
        'loginrequired' => false,
    ),
);

$services = array(
    'moodle_hogehoge' => array(
        'functions' => array (),
        // 'requiredcapability' => 'some/capability:specified',
        'restrictedusers' => 0,
        'enabled'=>0,
    )
);

$functions(連想配列) のキーは JavaScript 側の methodname と同じ名前にする必要がある.(JavaScript$functions['hoge']['methodname'] を指定するとエラーが返ってくるので注意)

<?php /* Moodleのルートディレクトリ/mod/モジュール名/externallib.php */
defined('MOODLE_INTERNAL') || die;

class mod_hoge_external extends external_api {
    public static function methodname_is_allowed_from_ajax() {
        return true;
    }

    public static function methodname_parameters() {
        return new external_function_parameters(
            array(
                'hoge' => new external_value(PARAM_TEXT, 'hoge'),
                'fuga' => new external_value(PARAM_TEXT, 'fuga'),
            )
        );
    }

    public static function methodname($hoge, $fuga) {
        return array(
          'hoge' => $hoge,
          'fuga' => $fuga
        );
    }

    public static function methodname_returns() {
        return new external_single_structure(
            array(
                'hoge' => new external_value(PARAM_TEXT, 'hoge'),
                'fuga' => new external_value(PARAM_TEXT, 'fuga')
            ),
            'description'
        );
    }
}
/* mod_fuga_externalクラスも同様に定義する */

今回の場合, externallib.php で最低限定義するクラスやメソッドは,

  • mod_hoge_external(クラス)
  • mod_fuga_external(クラス)
  • methodname_is_allowed_from_ajax(各クラスメソッド)
  • methodname_parameters(各クラスメソッド)
  • methodname_returns(各クラスメソッド)

となる.

活動モジュールのインストール

自作の活動モジュールは,

  1. Moodleのルートディレクトリ/mod 直下に自作した活動モジュールを配置
  2. ブラウザからMoodleへアクセス
  3. 活動モジュールのインストール
  4. コースなどに活動モジュールを配置

Moodleにインストールし,コースにモジュールを配置できる.
活動モジュールがインストールされる際, db/services.php に記述した $functionsmdl_external_functionsテーブル に, $servicesmdl_external_servicesテーブル に保存される.
ひとまず,これでMoodleサーバとの非同期通信ができるようになった.

おわりに

もっと簡単にできると思ってた分しんどかった…(´。_。`)
次は externallib.php で定義するクラスやメソッドについて少し詳しく書こうかな.