カテゴリー: Moodle

Moodle

  • SCORM 2004をSCORM 1.2にダウングレード

    MoodleのSCORM 2004サポートしない問題については先の記事にまとめた通りである。この他にも、私達が開発しているeラーニング教材はMoodleで「完了」ステータスのAPI呼び出しができないという問題があり困っていた。仕方がないので以下の手順でSCORM 1.2化することにした。

    1. Moodleで「完了」ステータスが動作するパッケージのうち、複雑そうでないものをダウンロード
    2. 解凍し既存コンテンツとマージする
      • imsmanifest.xml の編集
      • JavaScriptの確認
      • 既存コンテンツで読み込んでいるJavaScript部分の一括編集(TextSS.netを使った)
    3. できたimsmanifest.xmlを「eXe」でSCORM1.2プロファイルで読み込みエラーがないことを確認

    地味な作業である、いつかはやらなければならないものだった。

  • SCORMを使うべきか、それとも???

    xAPIとかCaliparという時代だが今困っているので調べたことをまとめて(いや羅列になっているが)おく。
    SCORM とは、Shareable Content Object Reference Modelのことで、Rustici Softwareによる簡潔な説明がこちら。この会社はSCORM Cloudというサービスを運営しており、Moodleのプラグインもある。現在歴史的経緯もあり私の研究ではSCORMを使用しているが、そもそものSCORMの思想はLMSに依存するのを避けるため、コンテンツそのものに機能を持たせ、LMSとのやり取りはAPI経由で行うものである。SCORMには以下の3つの問題がある。
    1. (Moodleのみを使うのであれば)SCORMを使うべきなのか?SCORM FAQにそのものズバリトピックがある。
    2. どのように SCORM パッケージを作成すれば良いのだろうか?FlashからSCORMパッケージを作成することはできるらしいが今更である。個人的には、manifestを書くためにReload Editorを使ったことがあるがイマイチ、いやイマサン。それなりに公開されている既存SCORMパッケージを参考にする(こことかこことかここ)と良い。
    3. MoodleではSCORM 1.2までしかサポートしないという問題もある。したがって、Moodleの問題なのか、コンテンツの問題なのか、ブラウザの問題なのか切り分けが難しい。Moodleの方でAPIデバッグをONにすると役に立つかもしれない(その割にMoodleにはSCORM 1.2に起因する制限を回避する設定があるから分かりにくい)。
  • MoodleのCalendarをGoogle Calendarにエクスポートで「終日」の扱いは?

    Moodleに研究室の予定を書き込んでいるが、終日の予定は時刻をデフォルトの「00:00」のままで変更しない。特に終了時刻はわざわざ入力しない。これで「終日」になることを期待するが…。

    特に意識しなければ日付の「時刻」が「00:00」になっており、これで終日になると思ってしまう
    特に意識しなければ日付の「時刻」が「00:00」になっており、これで終日になると思ってしまう

    これをGoogle Calendarにエクスポートすると、前日の終日予定に見えてしまう。これを回避するためには、00:00以外のそれより遅い時刻に設定すればよい。

    たしかに00:00になっているが、日付の境目にイベントが発生し、表示上は…。
    たしかに00:00になっているが、日付の境目にイベントが発生し、表示上は…。
    前日の終日予定に見えてしまう
    前日の終日予定に見えてしまう
  • LMSログとマイニングツール/概念あれこれ

    前々から行いたかった、LMSに蓄積されているログを対象データとした研究について。

    行いたいことはテキストマイニング(これはこれで「重要文抽出」「キーワード抽出」「トピック抽出」「テキストの一貫性評価」「複数文書要約」「テキストクラスタリング」などの面白いトピックがある)ではない。LMSで提供しているSCORMコンテンツの改善を目的とし、データベースなど構造化されたものから、ユーザに着目した分析となるであろう。データマイニングと言えるかもしれない。たとえば、BovoらEDM2013ICEEE2013でユーザそれぞれについてのログイン回数、最終ログイン、学習時間、レッスン読了数、ダウンロード数、小テスト、課題の評定と各種平均を正規化したデータをWekaでクラスタリングしている。

    Wekaについては、恩師である渡邊裕司先生もお使いであると仰っていた、それなりに実績のあるツールのようだ(OERもある)。森幹彦先生には、orangeというビジュアライズされたワークフローを作成できるPythonベースのツールを紹介していただいた(活用している方のブログ記事)。また、これらのツールを比較した解説もある(これとかこっちとかスライドも)。

    ただし、ツールは夢のようなものではなく、それが何を行っているのか理解できない者が使うべきではない。当たり前だが、ツールを使うことが目的になってはならない。最終目的はコンテンツの改善である。ただ、ログなんぞから、ユーザの「学習」状況など分かるはずがない。しかしながら、全てをきちんと受講している「可能性」のある閲覧状態というのはあるはずである(受講していてもタレ流しかもしれないから)。

    1. SCO単位の受講率(というのか?)
    2. 各SCOの所要時間以上で受講している割合(クエリがちょっと難しいかもしれないが分かれば面白い)

    SCO単位の受講率と総合テストの成績の相関は容易に分かる。これに加えて上記2が分かれば、オンラインかつモチベーションがない、強制的にやらされる「○○○○についての学習」というシチュエーションで、ユーザの「飽き」と「作業ゲー状態」を検出することができるかもしれない。また、たとえば次のようにユーザを分類し、アダプティブなコンテンツとすることも考えられる。

    1. まじめに受講した結果成績が良い
    2. そもそもICTスキルが高く、受講せずとも成績が良い
    3. それ以外、また他の分類

    決してバズワードの研究分野のつもりはありませんから!

  • Moodleのログを解析向けに出力するカスタマイズ

    と言っていいものか分からないが、行ったことをまとめておく。大したことはやっていない。SCORM受講ログの取得に十時間以上かかってしまったし、コースログの取得はPHPのmemory_limitとのたたかいであった。やはりPHPはパフォーマンスの問題がある。Try & Error の開発がしやすいこととのトレードオフなのだろうが…。

    1. ユーザ名を特定困難、識別可能な内部IDで出力する
      [code]— course/lib.php.20151020 2015-10-20 11:26:45.000000000 +0900
      +++ course/lib.php.20151016 2015-10-16 19:34:45.000000000 +0900
      @@ -424,7 +424,7 @@
      $link = new moodle_url("/iplookup/index.php?ip=$log->ip&user=$log->userid");
      $row[] = $OUTPUT->action_link($link, $log->ip, new popup_action(‘click’, $link, ‘iplookup’, array(‘height’ => 440, ‘width’ => 700)));

      – $row[] = html_writer::link(new moodle_url("/user/view.php?id={$log->userid}&course={$log->course}"), $log->userid);
      + $row[] = html_writer::link(new moodle_url("/user/view.php?id={$log->userid}&course={$log->course}"), fullname($log, has_capability(‘moodle/site:viewfullnames’, context_course::instance($course->id))));

      $displayaction="$log->module $log->action";
      if ($brokenurl) {
      @@ -531,7 +531,7 @@
      $link = new moodle_url("/iplookup/index.php?ip=$log->ip&user=$log->userid");
      echo $OUTPUT->action_link($link, $log->ip, new popup_action(‘click’, $link, ‘iplookup’, array(‘height’ => 400, ‘width’ => 700)));
      echo "</td>\n";
      – $fullname = $log->userid;
      + $fullname = fullname($log, has_capability(‘moodle/site:viewfullnames’, context_course::instance($course->id)));
      echo "<td class=\"r$row c3\" >\n";
      echo " <a href=\"$CFG->wwwroot/user/view.php?id={$log->userid}\">$fullname</a>\n";
      echo "</td>\n";
      @@ -619,7 +619,7 @@

      $coursecontext = context_course::instance($course->id);
      $firstField = format_string($courses[$log->course], true, array(‘context’ => $coursecontext));
      – $fullname = $log->userid;
      + $fullname = fullname($log, has_capability(‘moodle/site:viewfullnames’, $coursecontext));
      $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
      $row = array($firstField, userdate($log->time, $strftimedatetime), $log->ip, $fullname, $log->module.’ ‘.$log->action.’ (‘.$actionurl.’)’, $log->info);
      $csvexporter->add_data($row);
      @@ -730,7 +730,7 @@
      $myxls->write($row, 0, format_string($courses[$log->course], true, array(‘context’ => $coursecontext)), ”);
      $myxls->write_date($row, 1, $log->time, $formatDate); // write_date() does conversion/timezone support. MDL-14934
      $myxls->write($row, 2, $log->ip, ”);
      – $fullname = $log->userid;
      + $fullname = fullname($log, has_capability(‘moodle/site:viewfullnames’, $coursecontext));
      $myxls->write($row, 3, $fullname, ”);
      $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
      $myxls->write($row, 4, $log->module.’ ‘.$log->action.’ (‘.$actionurl.’)’, ”);
      @@ -844,7 +844,7 @@
      $myxls->write_string($row, 0, format_string($courses[$log->course], true, array(‘context’ => $coursecontext)));
      $myxls->write_date($row, 1, $log->time);
      $myxls->write_string($row, 2, $log->ip);
      – $fullname = $log->userid;
      + $fullname = fullname($log, has_capability(‘moodle/site:viewfullnames’, $coursecontext));
      $myxls->write_string($row, 3, $fullname);
      $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
      $myxls->write_string($row, 4, $log->module.’ ‘.$log->action.’ (‘.$actionurl.’)’);

      — mod/quiz/report/attemptsreport.php 2015-10-16 19:03:12.000000000 +0900
      +++ mod/quiz/report/attemptsreport.php.20151016 2015-10-16 19:04:44.000000000 +0900
      @@ -153,10 +153,10 @@
      $headers[] = ”;
      }
      if (!$table->is_downloading()) {
      – $columns[] = ‘userid’;
      + $columns[] = ‘username’;
      $headers[] = get_string(‘name’);
      } else {
      – $columns[] = ‘userid’;
      + $columns[] = ‘username’;
      $headers[] = get_string(‘name’);
      }

      — mod/scorm/report/interactions/report.php.20151020 2015-10-20 11:21:52.000000000 +0900
      +++ mod/scorm/report/interactions/report.php.20151016 2015-10-16 12:33:37.000000000 +0900
      @@ -418,9 +418,9 @@
      $row[] = $OUTPUT->user_picture($user, array(‘courseid’=>$course->id));
      }
      if (!$download) {
      – $row[] = ‘<a href="’.$CFG->wwwroot.’/user/view.php?id=’.$scouser->userid.’&amp;course=’.$course->id.’">’.$scouser->userid.'</a>’;
      + $row[] = ‘<a href="’.$CFG->wwwroot.’/user/view.php?id=’.$scouser->userid.’&amp;course=’.$course->id.’">’.$scouser->username.'</a>’;
      } else {
      – $row[] = $scouser->userid;
      + $row[] = $scouser->username;
      }
      if (empty($timetracks->start)) {
      $row[] = ‘-‘;

      — mod/scorm/report/basic/report.php.20151020 2015-10-20 11:04:48.000000000 +0900
      +++ mod/scorm/report/basic/report.php.20151016 2015-10-16 11:02:03.000000000 +0900
      @@ -121,7 +121,7 @@
      $columns[]= ‘picture’;
      $headers[]= ”;
      }
      – $columns[] = ‘userid’;
      + $columns[] = ‘username’;
      $headers[] = get_string(‘name’);

      $columns[]= ‘attempt’;
      @@ -383,9 +383,9 @@
      $row[] = $OUTPUT->user_picture($user, array(‘courseid’=>$course->id));
      }
      if (!$download) {
      – $row[] = ‘<a href="’.$CFG->wwwroot.’/user/view.php?id=’.$scouser->userid.’&amp;course=’.$course->id.’">’.$scouser->userid.'</a>’;
      + $row[] = ‘<a href="’.$CFG->wwwroot.’/user/view.php?id=’.$scouser->userid.’&amp;course=’.$course->id.’">’.$scouser->username.'</a>’;
      } else {
      – $row[] = $scouser->userid;
      + $row[] = $scouser->username;
      }
      if (empty($timetracks->start)) {
      $row[] = ‘-‘;

      — mod/scorm/report/objectives/report.php.20151020 2015-10-20 11:23:52.000000000 +0900
      +++ mod/scorm/report/objectives/report.php.20151016 2015-10-16 12:34:18.000000000 +0900
      @@ -422,9 +422,9 @@
      }
      if (!$download) {
      $row[] = ‘<a href="’.$CFG->wwwroot.’/user/view.php?id=’.$scouser->userid.
      – ‘&amp;course=’.$course->id.’">’.$scouser->userid.'</a>’;
      + ‘&amp;course=’.$course->id.’">’.$scouser->username.'</a>’;
      } else {
      – $row[] = $scouser->userid;
      + $row[] = $scouser->username;
      }
      if (empty($timetracks->start)) {
      $row[] = ‘-‘;
      [/code]

    2. 時刻処理に適したunixtime型で出力する
      [code]

      — course/lib.php 2015-10-20 11:46:28.000000000 +0900
      +++ course/lib.php.20151020 2015-10-20 11:26:45.000000000 +0900
      @@ -558,7 +558,6 @@
      $header = array();
      $header[] = get_string(‘course’);
      $header[] = get_string(‘time’);
      – $header[] = get_string(‘time’);
      $header[] = get_string(‘ip_address’);
      $header[] = get_string(‘fullnameuser’);
      $header[] = get_string(‘action’);
      @@ -622,7 +621,7 @@
      $firstField = format_string($courses[$log->course], true, array(‘context’ => $coursecontext));
      $fullname = $log->userid;
      $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
      – $row = array($firstField, userdate($log->time, $strftimedatetime), $log->time, $log->ip, $fullname, $log->module.’ ‘.$log->action.’ (‘.$actionurl.’)’, $log->info);
      + $row = array($firstField, userdate($log->time, $strftimedatetime), $log->ip, $fullname, $log->module.’ ‘.$log->action.’ (‘.$actionurl.’)’, $log->info);
      $csvexporter->add_data($row);
      }
      $csvexporter->download_file();

      — mod/quiz/report/attemptsreport_table.php 2015-10-20 11:20:12.000000000 +0900
      +++ mod/quiz/report/attemptsreport_table.php.20151020 2015-10-20 11:01:19.000000000 +0900
      @@ -163,7 +163,7 @@
      */
      public function col_timestart($attempt) {
      if ($attempt->attempt) {
      – return $attempt->timestart;
      + return userdate($attempt->timestart, $this->strtimeformat);
      } else {
      return ‘-‘;
      }
      @@ -176,7 +176,7 @@
      */
      public function col_timefinish($attempt) {
      if ($attempt->attempt && $attempt->timefinish) {
      – return $attempt->timefinish;
      + return userdate($attempt->timefinish, $this->strtimeformat);
      } else {
      return ‘-‘;
      }
      @@ -189,7 +189,7 @@
      */
      public function col_duration($attempt) {
      if ($attempt->timefinish) {
      – return $attempt->timefinish – $attempt->timestart;
      + return format_time($attempt->timefinish – $attempt->timestart);
      } else {
      return ‘-‘;
      }

      — mod/scorm/report/interactions/report.php 2015-10-20 11:23:34.000000000 +0900
      +++ mod/scorm/report/interactions/report.php.20151020 2015-10-20 11:21:52.000000000 +0900
      @@ -435,14 +435,14 @@
      $row[] = $scouser->attempt;
      }
      if ($download ==’ODS’ || $download ==’Excel’ ) {
      – $row[] = $timetracks->start;
      + $row[] = userdate($timetracks->start, get_string("strftimedatetime", "langconfig"));
      } else {
      – $row[] = $timetracks->start;
      + $row[] = userdate($timetracks->start);
      }
      if ($download ==’ODS’ || $download ==’Excel’ ) {
      – $row[] = $timetracks->finish;
      + $row[] = userdate($timetracks->finish, get_string(‘strftimedatetime’, ‘langconfig’));
      } else {
      – $row[] = $timetracks->finish;
      + $row[] = userdate($timetracks->finish);
      }
      $row[] = scorm_grade_user_attempt($scorm, $scouser->userid, $scouser->attempt);
      }

      — mod/scorm/report/basic/report.php 2015-10-20 11:12:59.000000000 +0900
      +++ mod/scorm/report/basic/report.php.20151020 2015-10-20 11:04:48.000000000 +0900
      @@ -399,14 +399,14 @@
      $row[] = $scouser->attempt;
      }
      if ($download ==’ODS’ || $download ==’Excel’ ) {
      – $row[] = $timetracks->start;
      + $row[] = userdate($timetracks->start, get_string("strftimedatetime", "langconfig"));
      } else {
      – $row[] = $timetracks->start;
      + $row[] = userdate($timetracks->start);
      }
      if ($download ==’ODS’ || $download ==’Excel’ ) {
      – $row[] = $timetracks->finish;
      + $row[] = userdate($timetracks->finish, get_string(‘strftimedatetime’, ‘langconfig’));
      } else {
      – $row[] = $timetracks->finish;
      + $row[] = userdate($timetracks->finish);
      }
      $row[] = scorm_grade_user_attempt($scorm, $scouser->userid, $scouser->attempt);
      }

      — mod/scorm/report/objectives/report.php 2015-10-20 11:24:46.000000000 +0900
      +++ mod/scorm/report/objectives/report.php.20151020 2015-10-20 11:23:52.000000000 +0900
      @@ -439,14 +439,14 @@
      $row[] = $scouser->attempt;
      }
      if ($download ==’ODS’ || $download ==’Excel’ ) {
      – $row[] = $timetracks->start;
      + $row[] = userdate($timetracks->start, get_string("strftimedatetime", "langconfig"));
      } else {
      – $row[] = $timetracks->start;
      + $row[] = userdate($timetracks->start);
      }
      if ($download ==’ODS’ || $download ==’Excel’ ) {
      – $row[] = $timetracks->finish;
      + $row[] = userdate($timetracks->finish, get_string(‘strftimedatetime’, ‘langconfig’));
      } else {
      – $row[] = $timetracks->finish;
      + $row[] = userdate($timetracks->finish);
      }
      $row[] = scorm_grade_user_attempt($scorm, $scouser->userid, $scouser->attempt);
      }
      [/code]

  • Moodle + BigBluebutton で遠隔会議

    研究室メンバーで遠隔会議をしたいとき、Skype IDとかを教え合えば済むのかもしれないがちょっと違うような気がする。そこで、Moodleと親和性の高いオープンソースのWeb会議システムBigBluebuttonを試してみた。BigBluebuttonはOSをUbuntuに限定している。BigBluebuttonは様々なソフトウェア(Red5、nginx、ffmpegなどなど)の集合で、全てのディストリに対応するのは難しいのだろう。

    BigBluebuttonそのもののインストールは三重大学の三島先生の記事を参考にさせていただいた。三島先生ありがとうございます。Requirementには「物理サーバ」とあるが、今回は仮想環境にインストールした。既存のMoodle側についてはプラグインを解凍し、認証キーの設定をするだけである。

    この後はMoodleの「アクティビティ」に現れるBigBluebuttonを選択し追加すれば遠隔会議へのリンクが作成され、拍子抜けするくらい簡単。つまり、会議に参加する際にMoodleにログインしていれば追加の認証はなく、Moodleのユーザ名が自動で引き継がれる。つまり、MoodleのアカウントがあればBigBluebuttonで遠隔会議が可能となる。

    早速、テストを兼ねて本日の研究室ゼミに東北大学からの遠隔参加を試してみたが、接続そのものとWebCamの映像の確認はできたものの、PCのデバイスの関係か、マイクが使えずNGとなってしまった。次回はMacで試してみよう。

    2015/10/09追記:KUINS側で必要なポートが学外に開放されていないことが分かったので開放を申請中。
    2015/10/23追記:開放が完了し、WebRTCも問題なく使えるようになった。チェックするにはbbbサーバ以下の check/ にアクセスすれば良い。
    2015/12/01追記:問題なく利用できており、遠隔ミーティング、ゼミの遠隔参加など時間の有効活用ができるようになった。

  • RHEL6にMySQL5.6をインストール

    案外簡単にできたのでメモ。
    [code]
    sudo wget http://dev.mysql.com/get/mysql-community-release-el6-5.noarch.rpm
    sudo rpm -ivh mysql-community-release-el6-5.noarch.rpm
    sudo yum update
    [/code]
    参考:

  • Cactiで複数年のデータを比較

    CactiはRRDToolをバックエンドに持ち、MRTG(古い!)より柔軟な可視化ができる優れたツールである。これで学認連携Moodle講習サイトの負荷を可視化している。昨年は400日で良いかと思っていたが、やはり比較するには2年以上の記録を残しておく必要があることに気付き、以下のように変更した。

    RRDの大きさを、すべてのスコープに渡って800日有効に設定
    RRDの大きさを、すべてのスコープに渡って800日有効に設定

    なお、この設定についてはサバカン様の技術情報をそのまま使わせていただいています。本記事は備忘のリンク元として記述しました。

    追記 2015/09/11 上記記事はリンク切れになっているようです。同様の記述はこちらにもありました。

  • 2013年を振り返る2:Sakai

    Sakaiとは著名なオープンソースLMSである。本学はSakaiを採用した学習支援サービスを提供しているが、正直なところ、この運用には様々な問題があり、振り回されているとしか言いようがない。

    まず、採用経緯が全く不明である。仕様書もなし、スケジュールもなし、採用理由もなし。Sakaiの導入ありきで進めたため、全てが後付けになってしまっている。このような進め方で人の心が動くはずがない(重要)。

    次に、構築に様々なボトルネックが存在し、現在に至るまで今だに正式運用となるはずのシステムが動作していない(ホスト名がclspilot.iimcとなっていることから分かる)。

    加えて、Sakaiそのものの機能不足とバグがある。バグについてはソフトウェアにつきものであるから余り追求しても仕方がない。次の深刻な機能不足がある。何と数式表示ができないし(Sakai 2.9.3 ではMathMLがサポートされたのでそれなりの表示はできる)、ロールの変更ができないのである。詳しくはMoodle.orgのフォーラムを参照されたい。また、某大学で問題になったプライバシー関連のマスク機能やShibboleth認証プラグインが存在せず、それぞれに開発が必要となる。つまりお金が必要になるのである。

    このような機能不足について、先日 @nagai_www 先生とお話しする機会がありかなりクリアになった。端的に言えば、Sakaiはコミュニティで開発を分担したLMSであり(だから「Sakaiプロジェクト」と言う)、(まだなのか永久か私の知るところではないが)全体の思想というものが無い。その無さが良さでもある。つまり完成品として見てはいけないのである。Sakaiにかかわってもうすぐ2年になるのに、このことに気付くことができなかったのは痛恨の極みである。

    Moodleが良いと言うつもりはない。しかしSakaiはないだろう。Moodleの方がユーザー数とコミュティの大きさに裏打ちされた良さがあるというのが私の結論である。客観的に一つ言えることは、MoodleだけでなくSakaiを知ることができたのは良かったと思う。それにしてもSakaiの人達はなぜ他を見ようとしないのか本当に不思議でならない。自分にバウンダリをかけて良いことがあったらぜひ教えてもらいたいものである。

  • Moodle 2.6 へアップグレードのために RHEL6 に PHP5.5 をインストール

    Moodle 2.6 にアップグレードする際に PHP 5.5  をインストールした時のメモ。

    • Moodle 2.6 ではAPC ではなく Zend Opcache が推奨となった。
    • パフォーマンスを改善するためなら試してみたい

    ほぼここの通りだが、ライブラリ関係の依存性については yum whatprovides で解決した。

    Moodle 2.6 では待望の「追加氏名」機能が実装された。え?アップグレードに必要な時間ですか?バックアップと PHP5.5 のインストール含めて 30 分くらいですね。
    altername