Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * The xapi_handler for xAPI statements.
  19   *
  20   * @package    mod_h5pactivity
  21   * @since      Moodle 3.9
  22   * @copyright  2020 Ferran Recio <ferran@moodle.com>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  namespace mod_h5pactivity\xapi;
  27  
  28  use mod_h5pactivity\local\attempt;
  29  use mod_h5pactivity\local\manager;
  30  use mod_h5pactivity\event\statement_received;
  31  use core_xapi\local\statement;
  32  use core_xapi\handler as handler_base;
  33  use core\event\base as event_base;
  34  use context_module;
  35  
  36  defined('MOODLE_INTERNAL') || die();
  37  
  38  global $CFG;
  39  require_once($CFG->dirroot.'/mod/h5pactivity/lib.php');
  40  
  41  /**
  42   * Class xapi_handler for H5P statements.
  43   *
  44   * @package mod_h5pactivity
  45   * @since      Moodle 3.9
  46   * @copyright  2020 Ferran Recio <ferran@moodle.com>
  47   */
  48  class handler extends handler_base {
  49  
  50      /**
  51       * Convert a statement object into a Moodle xAPI Event.
  52       *
  53       * If a statement is accepted by the xAPI webservice the component must provide
  54       * an event to handle that statement, otherwise the statement will be rejected.
  55       *
  56       * @param statement $statement
  57       * @return core\event\base|null a Moodle event to trigger
  58       */
  59      public function statement_to_event(statement $statement): ?event_base {
  60  
  61          // Only process statements with results.
  62          $xapiresult = $statement->get_result();
  63          if (empty($xapiresult)) {
  64              return null;
  65          }
  66  
  67          // Statements can contain any verb, for security reasons each
  68          // plugin needs to filter it's own specific verbs. For now the only verbs the H5P
  69          // plugin keeps track on are "answered" and "completed" because they are realted to grading.
  70          // In the future this list can be increased to track more user interactions.
  71          $validvalues = [
  72                  'http://adlnet.gov/expapi/verbs/answered',
  73                  'http://adlnet.gov/expapi/verbs/completed',
  74              ];
  75          $xapiverbid = $statement->get_verb_id();
  76          if (!in_array($xapiverbid, $validvalues)) {
  77              return null;
  78          }
  79  
  80          // Validate object.
  81          $xapiobject = $statement->get_activity_id();
  82  
  83          // H5P add some extra params to ID to define subcontents.
  84          $parts = explode('?', $xapiobject, 2);
  85          $contextid = array_shift($parts);
  86          $subcontent = str_replace('subContentId=', '', array_shift($parts));
  87          if (empty($contextid) || !is_numeric($contextid)) {
  88              return null;
  89          }
  90          $context = \context::instance_by_id($contextid);
  91          if (!$context instanceof \context_module) {
  92              return null;
  93          }
  94  
  95          // As the activity does not accept group statement, the code can assume that the
  96          // statement user is valid (otherwise the xAPI library will reject the statement).
  97          $user = $statement->get_user();
  98          if (!has_capability('mod/h5pactivity:view', $context, $user)) {
  99              return null;
 100          }
 101  
 102          $cm = get_coursemodule_from_id('h5pactivity', $context->instanceid, 0, false);
 103          if (!$cm) {
 104              return null;
 105          }
 106  
 107          $manager = manager::create_from_coursemodule($cm);
 108  
 109          if (!$manager->is_tracking_enabled($user)) {
 110              return null;
 111          }
 112  
 113          // For now, attempts are only processed on a single batch starting with the final "completed"
 114          // and "answered" statements (this could change in the future). This initial statement have no
 115          // subcontent defined as they are the main finishing statement. For this reason, this statement
 116          // indicates a new attempt creation. This way, simpler H5P activies like multichoice can generate
 117          // an attempt each time the user answers while complex like question-set could group all questions
 118          // in a single attempt (using subcontents).
 119          if (empty($subcontent)) {
 120              $attempt = attempt::new_attempt($user, $cm);
 121          } else {
 122              $attempt = attempt::last_attempt($user, $cm);
 123          }
 124          if (!$attempt) {
 125              return null;
 126          }
 127          $result = $attempt->save_statement($statement, $subcontent);
 128          if (!$result) {
 129              return null;
 130          }
 131  
 132          // Update activity if necessary.
 133          if ($attempt->get_scoreupdated()) {
 134              $grader = $manager->get_grader();
 135              $grader->update_grades($user->id);
 136          }
 137  
 138          // Convert into a Moodle event.
 139          $minstatement = $statement->minify();
 140          $params = [
 141              'other' => $minstatement,
 142              'context' => $context,
 143              'objectid' => $cm->instance,
 144              'userid' => $user->id,
 145          ];
 146          return statement_received::create($params);
 147      }
 148  }