カテゴリー: 研究

  • 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]

  • 「IT+教育最前線 2015セミナー大阪」で講演しました

    Office365 Education の本質:ソフトウェア、システム運用、サポート体制の観点から
    Office365 Education の本質:ソフトウェア、システム運用、サポート体制の観点から

    3年前にも登壇したセミナーに再度呼んでいただきました。これまでの3年半のマイクロソフトのオンラインサービスの運用を総括しました。つまり、CLE16の続編ということになり、スラドで「論文の体をなしていない」とか「雑文だ」とか批判されたことを反省し(いやそもそもCLEは研究会なんで論文ではなく、論文と言っているのはスラドなのですが)、もう少し体系的、つまりオンラインサービスは次の三要素からなっているという考えからまとめようとしたものです(講演資料)。

    • ソフトウェア
    • システム運用
    • サポート体制

    いずれの観点からも「否」と言いたいのではなく、ソフトウェアにはバグがつきものです。システム運用のミスは信頼を失う原因になります。しかし、サポート対応でこれらのマイナス面はいかようにでもなります(生身の人間ですから)。でも、サポートとのやり取りのメールが、意味不明で冗長な日本語の繰り返しだったらどうでしょう。最悪ですよね。たとえばこんなのです。

    再発防止につきましては、短期的な対策の実施および、長期的な取り組みをそれぞれ実施している状況となります。具体的な内容や時期についてはお伝えする事ができずに申し訳ございませんが、引き続き改善に努めさせていただく所存でございます。

    これ、何も言っていないのと同じですよね…。ユーザとのインタラクションがすべての印象を決めてしまうということを認識していないのでしょうか?

  • 「有線/無線LANによるシングルサインオンと学認連携」セミナーで講演しました

    「Office365 Educationの真実…」がスラドに取り上げられたという話をしているところ
    「Office365 Educationの真実…」がスラドに取り上げられたという話をしているところ

    アルカテル・ルーセント・エンタープライズの和田様から「「有線/無線LANによるシングルサインオンと学認連携」セミナー」登壇依頼をいただきました(講演資料)。最近はOffice365の話をしてくれと言われてばっかりなのですが、今回は認証VLANの話も含め(いやそっちがメイン)ることになりました。思えば2006年に前任地の群馬大学で統一の認証基盤の構築をはじめたとき、精神的には誰も味方がいない状態からのスタートでした。センターじたい人心バラバラ、もちろん部局ごとにメンタリティがまったく違うところに、単科大学出身でそれまで研究所という閉じた環境にいた、怪しい関西人の若造がいきなりぶち上げたのですから…。

    こういう図で偉い先生方に認証統合の重要性について説明しました(遠い目
    こういう図で偉い先生方に認証統合の重要性について説明しました(遠い目

    群馬大学では様々な出会いがあり、結果的には上司にもCo-Workerにも恵まれ、認証基盤の構築とセットで「キラーアプリになるサービス」を次々に打ちプロモーションするという方針を貫くことができたと思います。
    私的には最大のキラーアプリはMACアドレス認証VLANでした。これと光直収,FTTDを組み合わせた学内LANは分かりやすくシンプルでインパクトがあったと自負しています。

    速い、故障なし、部局ルータ排除という「やりたいこと」が詰まった学内LANです
    速い、故障なし、部局ルータ排除という「やりたいこと」が詰まった学内LANです

     

  • CLE16で京都大学におけるMicrosoftクラウドシステムの運用について発表しました

    熊本で行われた、情報処理学会CLE研究会で「Office365 Educationの真実:カイゼンの裏にあるもの」と題して発表してきました。なぜこのネタでCLEなのかというツッコミを受けそうですが、単に熊本に行ったことがなかったからです。また、3月末で運用を離れたので、一旦Wrap upしようという意図もあります。

    プレゼンはUSBブートのChronium OS (つまりPowerPoint Free, Windows Free)で行いました。理由は予稿をご覧いただければ分かる通り、3年余りの運用を通して、Microsoftのソフトウェア、システム運用、サポート体制に強い不信感を持たざるを得なかったからです。

    もちろん離れてしまったので言いたい放題というのもありますが、日本の場合MicrosoftとSIerのフィルタリング(しかも、MicrosoftはSIerに上から目線なのです)でこのような話は日の目を見ていないのだと思います。少々当惑しているのですが、この話はスラドで取り上げられてしまい、それなりに賛否両論が渦巻いたようです(これこそ私の意図であり、我々は思考停止してはいけないのです)。

  • IOT28での質問: ネットブート端末起動のバラツキは?

    3月に小名浜で開催されたIOT28で下記の発表を行いました。内容は、一斉ネットブートの際のトラフィックを計測して、改善のための材料とするというものです。十分練れていなかったので様々なご指導を頂戴しました。

    ネットブートのキャッシュの有効性に着目した教育用端末トラフィックの評価
    上田 浩(京都大学), 外村 孝一郎(京都大学), 石井 良和(京都大学), 植木 徹(京都大学)

    その中で、

    すべての端末が一斉に起動したとしても、すべての端末の起動終了が同期するはずはないが実際はどうなっているのか?

    という質問を頂戴しました。たしかにそうです。原稿を執筆するときにこのことに気付いていなければいけなかったのですが…。

    データの計測と可視化をお願いした @daisuke_k さんが、起動終了時刻が分かるグラフ(rc_update_boot_pvs_stacked_finline)を描いてくれました。

    • 端末のイメージのアップデートをした直後に起動したときの PVS のトラフィック
    • 縦軸に平行な黒線が、11 * n + 1 (n=0,1,2,3,4,5,6) 台目の起動が 終わった時間
    • いちばん右の黒線がすべての端末の起動が終わった時間
    起動完了などの時間と トラフィックの関係
    起動完了などの時間とトラフィックの関係

    やはり早い者勝ちで帯域を次々に使うという傾向は変わっていないと考えられます。つまり、一斉に起動したとしてもネットワークを含むネットブートシステム系がボトルネックになり、一斉に起動が完了することがないという事例となりましたが、早い者勝ちとなる事象がPVSログイン以前のDHCPにあるのか、それともPVSにあるのかについては今後の課題とします。

  • 学術情報メディアセンターセミナー「IoT時代の認証とセキュリティ」開催しました

    4/28(火)に、学術情報メディアセンターセミナー「IoT時代の認証とセキュリティ」開催しました。認証システムの運用は私がこれまで情熱を傾けてきたもので、今回は生体認証とパスワード認証を含む実運用の認証方式についてご講演いただきました。いずれも興味深いもので、聴衆を含めた議論は多いに盛り上がりました。

    DSC_0003
    名古屋市立大学 渡邊裕司先生「モバイル端末における行動的特徴に基づく生体認証」
    大阪大学 江原 康生先生「総合大学における統合認証基盤システムの構築と課題」
    大阪大学 江原 康生先生「総合大学における統合認証基盤システムの構築と課題」

    セミナーのタイトルがミーハーになってしまったのは私の不徳の致すところです。今回は「認証/認証システムって奥が深いな〜」と共感いただければセミナーは成功したと言えるでしょう。

  • 「Office365への移行と認証連携事例の評価」が優秀論文賞を受賞しました

    クラウドシステムの移行とShibboleth認証への対応を同時にえいやで行い、四苦八苦した経験をまとめた論文「Office365 への移行と認証連携事例の評価」が大学ICT推進協議会 2013年度年次大会にて優秀論文賞を受賞しました。昨年度はOffice365にふり回されていた感が強いのですが、このように評価いただき、うれしいやら困ったやらです…。これは決して私ひとりの賞ではなく、センターで共に仕事をさせていただいている皆様のものです。今後どうなるか分かりませんが、シンプルで分かりやすい仕事に努めて行くという指向は変わりません。どうもありがとうございました。

    ICTDSC_0011

  • IOTシンポジウム 2013

    去年の話になるが、ユニアデックスの高橋 @v_takahashi  さんの招待講演をはじめ、たいへんためになったので忘れないためにメモ。

    • ENIAC以前は研究者とは当時のコンピューターをマネージする、つまり女性たちをうまく扱うスキルが要求されたが、ENIACの登場で、どれだけ時間をぶん取れるかに変わった。
    • Virtual = 本来の意味は「実質上の!」
    • 現在「仮想化」と言われていることの本質は集中化である。
    • ロードバランサではなくクラウドでスケールアウトするのが良い。
    • 仮想化によりインフラエンジニアとサーバエンジニアの垣根がなくなってきた。

    他にもGedowFatherさんの資料の紹介などが鮮烈に記憶に残った。個人的には日々学認連携Moodleの負荷を何とかしたいと思っているので、クラウドでスケールアウトはぜひ取り組んでみたいと感じた。その前に政治的、事務的なハードルがあったりするのだが…。

  • 臨時学術情報メディアセンターセミナー「米国におけるクラウドフォレンジックスに関する研究開発の現状」を開催しました

    臨時学術情報メディアセンターセミナーを開催し、Metropolitan State University の Jigang Liu 先生に、米国におけるクラウドフォレンジックスに関する研究開発の現状についてご講演いただきました。英語でのセミナーとあって参加者の大多数は留学生でした。先生の講演の後、濃い議論が行われました。Liu 先生ありがとうございました。

  • Moodleで手書きレポートを提出 (pdf2submission + Moodle 2.5 on RHEL6)

    喜多研Moodlepdf2submission をインストールし、動作確認に成功したので備忘録を兼ねて。全て「タダ」でできました。

    これは何?

    紙で提出された手書きレポートをMoodleで提出したことにできるソリューションです。

    1. Moodleで「課題」を作成し、pdf2submission ブロックでQRコードを含むレポートのカバーシートを印刷
    2. 学生がカバーシートを含んだレポートを提出
    3. レポートをスキャンしPDF化
    4. PDFをFTPでMoodleサーバにアップロード
    5. MoodleがQRコードの情報をもとに自動でPDFを分割し対応する課題、学生の領域にアップロード

    必要なもの

    • Moodle(笑)
    • PDF出力可能なドキュメントスキャナ(Canon DR-150で動作確認)

    Moodleの設定

    Moodle 2.5+ (Build: 20130524) を RHEL6 で運用。

    バックアップ

    MySQLのダンプを保存。ソースコード, moodledata ディレクトリはコピーしておく。

    convert, ghostscript のインストール

    [code]yum install ImageMagick ImageMagick-devel ghostscript[/code]

    rpmforge リポジトリの追加と pdftk のインストール

    [code]sudo rpm -ivh rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm
    yum install pdftk[/code]

    EPELリポジトリの追加と zbarimg のインストール

    [code]sudo rpm -ivh epel-release-6-8.noarch.rpm
    yum install zbar[/code]

    pdf2submission のインストール

    block_pdf2submission_moodle23_2012091400.zip を blocks/ に解凍し、「通知」をクリック。必要なテーブルが自動で生成される。pdfscan.php を編集。
    [code][uep@kvm420 pdf2submission]$ diff pdfscan.php.dist pdfscan.php
    14,17c14,17
    < $zbarimg_command= "/usr/local/bin/zbarimg ";
    < $convert_command= "convert";
    < $gs_command= "gs";
    < $pdftk_command= "pdftk";

    > $zbarimg_command= "/usr/bin/zbarimg";
    > $convert_command= "/usr/bin/convert";
    > $gs_command= "/usr/bin/gs";
    > $pdftk_command= "/usr/bin/pdftk";
    [/code]

    pdfアップロード先ディレクトリの作成

    デフォルトに従い /home/pdf2moodle/pdfs を作成する。当然のことながら、このディレクトリが apache から読めなければ何も始まらない。
    [code]
    adduser pdf2moodle
    mkdir -p /home/pdf2moodle/pdfs
    chmod 755 /home/pdf2moodle
    chgrp apache /home/pdf2moodle/pdfs
    chmod g+w /home/pdf2moodle/pdfs
    [/code]

    動作確認

    /home/pdf2moodle/pdfsにQRコードを含むPDFをアップロードすると、その中にさらにディレクトリが掘られ、pdftkでページ分割がなされているか確認する。分割されていなければ、apache のログを確認する。分割されていれば、そこからMoodleの課題にアップロードするところまでの障害は無いと思われる。

    謝辞

    本ブロックを開発された喜多 敏博教授ならびに Moodle.org 各位に厚く御礼申し上げます。