Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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   * Library of interface functions and constants.
  19   *
  20   * @package     mod_h5pactivity
  21   * @copyright   2020 Ferran Recio <ferran@moodle.com>
  22   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  use mod_h5pactivity\local\manager;
  28  use mod_h5pactivity\local\grader;
  29  
  30  /**
  31   * Checks if H5P activity supports a specific feature.
  32   *
  33   * @uses FEATURE_GROUPS
  34   * @uses FEATURE_GROUPINGS
  35   * @uses FEATURE_MOD_INTRO
  36   * @uses FEATURE_SHOW_DESCRIPTION
  37   * @uses FEATURE_COMPLETION_TRACKS_VIEWS
  38   * @uses FEATURE_COMPLETION_HAS_RULES
  39   * @uses FEATURE_MODEDIT_DEFAULT_COMPLETION
  40   * @uses FEATURE_GRADE_HAS_GRADE
  41   * @uses FEATURE_GRADE_OUTCOMES
  42   * @uses FEATURE_BACKUP_MOODLE2
  43   * @param string $feature FEATURE_xx constant for requested feature
  44   * @return mixed True if module supports feature, false if not, null if doesn't know
  45   */
  46  function h5pactivity_supports(string $feature): ?bool {
  47      switch($feature) {
  48          case FEATURE_GROUPS:
  49              return true;
  50          case FEATURE_GROUPINGS:
  51              return true;
  52          case FEATURE_MOD_INTRO:
  53              return true;
  54          case FEATURE_SHOW_DESCRIPTION:
  55              return true;
  56          case FEATURE_COMPLETION_TRACKS_VIEWS:
  57              return true;
  58          case FEATURE_MODEDIT_DEFAULT_COMPLETION:
  59              return true;
  60          case FEATURE_GRADE_HAS_GRADE:
  61              return true;
  62          case FEATURE_GRADE_OUTCOMES:
  63              return true;
  64          case FEATURE_BACKUP_MOODLE2:
  65              return true;
  66          default:
  67              return null;
  68      }
  69  }
  70  
  71  /**
  72   * Saves a new instance of the mod_h5pactivity into the database.
  73   *
  74   * Given an object containing all the necessary data, (defined by the form
  75   * in mod_form.php) this function will create a new instance and return the id
  76   * number of the instance.
  77   *
  78   * @param stdClass $data An object from the form.
  79   * @param mod_h5pactivity_mod_form $mform The form.
  80   * @return int The id of the newly inserted record.
  81   */
  82  function h5pactivity_add_instance(stdClass $data, mod_h5pactivity_mod_form $mform = null): int {
  83      global $DB;
  84  
  85      $data->timecreated = time();
  86      $data->timemodified = $data->timecreated;
  87      $cmid = $data->coursemodule;
  88  
  89      $data->id = $DB->insert_record('h5pactivity', $data);
  90  
  91      // We need to use context now, so we need to make sure all needed info is already in db.
  92      $DB->set_field('course_modules', 'instance', $data->id, ['id' => $cmid]);
  93      h5pactivity_set_mainfile($data);
  94  
  95      // Extra fields required in grade related functions.
  96      $data->cmid = $data->coursemodule;
  97      h5pactivity_grade_item_update($data);
  98      return $data->id;
  99  }
 100  
 101  /**
 102   * Updates an instance of the mod_h5pactivity in the database.
 103   *
 104   * Given an object containing all the necessary data (defined in mod_form.php),
 105   * this function will update an existing instance with new data.
 106   *
 107   * @param stdClass $data An object from the form in mod_form.php.
 108   * @param mod_h5pactivity_mod_form $mform The form.
 109   * @return bool True if successful, false otherwise.
 110   */
 111  function h5pactivity_update_instance(stdClass $data, mod_h5pactivity_mod_form $mform = null): bool {
 112      global $DB;
 113  
 114      $data->timemodified = time();
 115      $data->id = $data->instance;
 116  
 117      h5pactivity_set_mainfile($data);
 118  
 119      // Update gradings if grading method or tracking are modified.
 120      $data->cmid = $data->coursemodule;
 121      $moduleinstance = $DB->get_record('h5pactivity', ['id' => $data->id]);
 122      if (($moduleinstance->grademethod != $data->grademethod)
 123              || $data->enabletracking != $moduleinstance->enabletracking) {
 124          h5pactivity_update_grades($data);
 125      } else {
 126          h5pactivity_grade_item_update($data);
 127      }
 128  
 129      return $DB->update_record('h5pactivity', $data);
 130  }
 131  
 132  /**
 133   * Removes an instance of the mod_h5pactivity from the database.
 134   *
 135   * @param int $id Id of the module instance.
 136   * @return bool True if successful, false on failure.
 137   */
 138  function h5pactivity_delete_instance(int $id): bool {
 139      global $DB;
 140  
 141      $activity = $DB->get_record('h5pactivity', ['id' => $id]);
 142      if (!$activity) {
 143          return false;
 144      }
 145  
 146      $DB->delete_records('h5pactivity', ['id' => $id]);
 147  
 148      h5pactivity_grade_item_delete($activity);
 149  
 150      return true;
 151  }
 152  
 153  /**
 154   * Checks if scale is being used by any instance of mod_h5pactivity.
 155   *
 156   * This is used to find out if scale used anywhere.
 157   *
 158   * @param int $scaleid ID of the scale.
 159   * @return bool True if the scale is used by any mod_h5pactivity instance.
 160   */
 161  function h5pactivity_scale_used_anywhere(int $scaleid): bool {
 162      global $DB;
 163  
 164      if ($scaleid and $DB->record_exists('h5pactivity', ['grade' => -$scaleid])) {
 165          return true;
 166      } else {
 167          return false;
 168      }
 169  }
 170  
 171  /**
 172   * Creates or updates grade item for the given mod_h5pactivity instance.
 173   *
 174   * Needed by {@link grade_update_mod_grades()}.
 175   *
 176   * @param stdClass $moduleinstance Instance object with extra cmidnumber and modname property.
 177   * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
 178   * @return int int 0 if ok, error code otherwise
 179   */
 180  function h5pactivity_grade_item_update(stdClass $moduleinstance, $grades = null): int {
 181      $idnumber = $moduleinstance->idnumber ?? '';
 182      $grader = new grader($moduleinstance, $idnumber);
 183      return $grader->grade_item_update($grades);
 184  }
 185  
 186  /**
 187   * Delete grade item for given mod_h5pactivity instance.
 188   *
 189   * @param stdClass $moduleinstance Instance object.
 190   * @return int Returns GRADE_UPDATE_OK, GRADE_UPDATE_FAILED, GRADE_UPDATE_MULTIPLE or GRADE_UPDATE_ITEM_LOCKED
 191   */
 192  function h5pactivity_grade_item_delete(stdClass $moduleinstance): ?int {
 193      $idnumber = $moduleinstance->idnumber ?? '';
 194      $grader = new grader($moduleinstance, $idnumber);
 195      return $grader->grade_item_delete();
 196  }
 197  
 198  /**
 199   * Update mod_h5pactivity grades in the gradebook.
 200   *
 201   * Needed by {@link grade_update_mod_grades()}.
 202   *
 203   * @param stdClass $moduleinstance Instance object with extra cmidnumber and modname property.
 204   * @param int $userid Update grade of specific user only, 0 means all participants.
 205   */
 206  function h5pactivity_update_grades(stdClass $moduleinstance, int $userid = 0): void {
 207      $idnumber = $moduleinstance->idnumber ?? '';
 208      $grader = new grader($moduleinstance, $idnumber);
 209      $grader->update_grades($userid);
 210  }
 211  
 212  /**
 213   * Rescale all grades for this activity and push the new grades to the gradebook.
 214   *
 215   * @param stdClass $course Course db record
 216   * @param stdClass $cm Course module db record
 217   * @param float $oldmin
 218   * @param float $oldmax
 219   * @param float $newmin
 220   * @param float $newmax
 221   * @return bool true if reescale is successful
 222   */
 223  function h5pactivity_rescale_activity_grades(stdClass $course, stdClass $cm, float $oldmin,
 224          float $oldmax, float $newmin, float $newmax): bool {
 225  
 226      $manager = manager::create_from_coursemodule($cm);
 227      $grader = $manager->get_grader();
 228      $grader->update_grades();
 229      return true;
 230  }
 231  
 232  /**
 233   * Implementation of the function for printing the form elements that control
 234   * whether the course reset functionality affects the H5P activity.
 235   *
 236   * @param object $mform form passed by reference
 237   */
 238  function h5pactivity_reset_course_form_definition(&$mform): void {
 239      $mform->addElement('header', 'h5pactivityheader', get_string('modulenameplural', 'mod_h5pactivity'));
 240      $mform->addElement('advcheckbox', 'reset_h5pactivity', get_string('deleteallattempts', 'mod_h5pactivity'));
 241  }
 242  
 243  /**
 244   * Course reset form defaults.
 245   *
 246   * @param stdClass $course the course object
 247   * @return array
 248   */
 249  function h5pactivity_reset_course_form_defaults(stdClass $course): array {
 250      return ['reset_h5pactivity' => 1];
 251  }
 252  
 253  
 254  /**
 255   * This function is used by the reset_course_userdata function in moodlelib.
 256   *
 257   * This function will remove all H5P attempts in the database
 258   * and clean up any related data.
 259   *
 260   * @param stdClass $data the data submitted from the reset course.
 261   * @return array of reseting status
 262   */
 263  function h5pactivity_reset_userdata(stdClass $data): array {
 264      global $CFG, $DB;
 265      $componentstr = get_string('modulenameplural', 'mod_h5pactivity');
 266      $status = [];
 267      if (!empty($data->reset_h5pactivity)) {
 268          $params = ['courseid' => $data->courseid];
 269          $sql = "SELECT a.id FROM {h5pactivity} a WHERE a.course=:courseid";
 270          if ($activities = $DB->get_records_sql($sql, $params)) {
 271              foreach ($activities as $activity) {
 272                  $cm = get_coursemodule_from_instance('h5pactivity',
 273                                                       $activity->id,
 274                                                       $data->courseid,
 275                                                       false,
 276                                                       MUST_EXIST);
 277                  mod_h5pactivity\local\attempt::delete_all_attempts ($cm);
 278              }
 279          }
 280          // Remove all grades from gradebook.
 281          if (empty($data->reset_gradebook_grades)) {
 282              h5pactivity_reset_gradebook($data->courseid, 'reset');
 283          }
 284          $status[] = [
 285              'component' => $componentstr,
 286              'item' => get_string('deleteallattempts', 'mod_h5pactivity'),
 287              'error' => false,
 288          ];
 289      }
 290      return $status;
 291  }
 292  
 293  /**
 294   * Removes all grades from gradebook
 295   *
 296   * @param int $courseid Coude ID
 297   * @param string $type optional type (default '')
 298   */
 299  function h5pactivity_reset_gradebook(int $courseid, string $type=''): void {
 300      global $DB;
 301  
 302      $sql = "SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
 303                FROM {h5pactivity} a, {course_modules} cm, {modules} m
 304               WHERE m.name='h5pactivity' AND m.id=cm.module AND cm.instance=a.id AND a.course=?";
 305  
 306      if ($activities = $DB->get_records_sql($sql, [$courseid])) {
 307          foreach ($activities as $activity) {
 308              h5pactivity_grade_item_update($activity, 'reset');
 309          }
 310      }
 311  }
 312  
 313  /**
 314   * Return a list of page types
 315   *
 316   * @param string $pagetype current page type
 317   * @param stdClass|null $parentcontext Block's parent context
 318   * @param stdClass $currentcontext Current context of block
 319   * @return array array of page types and it's names
 320   */
 321  function h5pactivity_page_type_list(string $pagetype, ?stdClass $parentcontext, stdClass $currentcontext): array {
 322      $modulepagetype = [
 323          'mod-h5pactivity-*' => get_string('page-mod-h5pactivity-x', 'h5pactivity'),
 324      ];
 325      return $modulepagetype;
 326  }
 327  
 328  /**
 329   * Check if the module has any update that affects the current user since a given time.
 330   *
 331   * @param  cm_info $cm course module data
 332   * @param  int $from the time to check updates from
 333   * @param  array $filter  if we need to check only specific updates
 334   * @return stdClass an object with the different type of areas indicating if they were updated or not
 335   */
 336  function h5pactivity_check_updates_since(cm_info $cm, int $from, array $filter = []): stdClass {
 337      global $DB, $USER;
 338  
 339      $updates = course_check_module_updates_since($cm, $from, ['package'], $filter);
 340  
 341      $updates->tracks = (object) ['updated' => false];
 342      $select = 'h5pactivityid = ? AND userid = ? AND timemodified > ?';
 343      $params = [$cm->instance, $USER->id, $from];
 344      $tracks = $DB->get_records_select('h5pactivity_attempts', $select, $params, '', 'id');
 345      if (!empty($tracks)) {
 346          $updates->tracks->updated = true;
 347          $updates->tracks->itemids = array_keys($tracks);
 348      }
 349  
 350      // Now, teachers should see other students updates.
 351      if (has_capability('mod/h5pactivity:reviewattempts', $cm->context)) {
 352          $select = 'h5pactivityid = ? AND timemodified > ?';
 353          $params = [$cm->instance, $from];
 354  
 355          if (groups_get_activity_groupmode($cm) == SEPARATEGROUPS) {
 356              $groupusers = array_keys(groups_get_activity_shared_group_members($cm));
 357              if (empty($groupusers)) {
 358                  return $updates;
 359              }
 360              list($insql, $inparams) = $DB->get_in_or_equal($groupusers);
 361              $select .= ' AND userid ' . $insql;
 362              $params = array_merge($params, $inparams);
 363          }
 364  
 365          $updates->usertracks = (object) ['updated' => false];
 366          $tracks = $DB->get_records_select('h5pactivity_attempts', $select, $params, '', 'id');
 367          if (!empty($tracks)) {
 368              $updates->usertracks->updated = true;
 369              $updates->usertracks->itemids = array_keys($tracks);
 370          }
 371      }
 372      return $updates;
 373  }
 374  
 375  /**
 376   * Returns the lists of all browsable file areas within the given module context.
 377   *
 378   * The file area 'intro' for the activity introduction field is added automatically
 379   * by {@link file_browser::get_file_info_context_module()}.
 380   *
 381   * @param stdClass $course course object
 382   * @param stdClass $cm course module object
 383   * @param stdClass $context context object
 384   * @return string[] array of pair file area => human file area name
 385   */
 386  function h5pactivity_get_file_areas(stdClass $course, stdClass $cm, stdClass $context): array {
 387      $areas = [];
 388      $areas['package'] = get_string('areapackage', 'mod_h5pactivity');
 389      return $areas;
 390  }
 391  
 392  /**
 393   * File browsing support for data module.
 394   *
 395   * @param file_browser $browser
 396   * @param array $areas
 397   * @param stdClass $course
 398   * @param stdClass $cm
 399   * @param context $context
 400   * @param string $filearea
 401   * @param int|null $itemid
 402   * @param string|null $filepath
 403   * @param string|null $filename
 404   * @return file_info_stored|null file_info_stored instance or null if not found
 405   */
 406  function h5pactivity_get_file_info(file_browser $browser, array $areas, stdClass $course,
 407              stdClass $cm, context $context, string $filearea, ?int $itemid = null,
 408              ?string $filepath = null, ?string $filename = null): ?file_info_stored {
 409      global $CFG;
 410  
 411      if (!has_capability('moodle/course:managefiles', $context)) {
 412          return null;
 413      }
 414  
 415      $fs = get_file_storage();
 416  
 417      if ($filearea === 'package') {
 418          $filepath = is_null($filepath) ? '/' : $filepath;
 419          $filename = is_null($filename) ? '.' : $filename;
 420  
 421          $urlbase = $CFG->wwwroot.'/pluginfile.php';
 422          if (!$storedfile = $fs->get_file($context->id, 'mod_h5pactivity', 'package', 0, $filepath, $filename)) {
 423              if ($filepath === '/' and $filename === '.') {
 424                  $storedfile = new virtual_root_file($context->id, 'mod_h5pactivity', 'package', 0);
 425              } else {
 426                  // Not found.
 427                  return null;
 428              }
 429          }
 430          return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, false, false);
 431      }
 432      return null;
 433  }
 434  
 435  /**
 436   * Serves the files from the mod_h5pactivity file areas.
 437   *
 438   * @param mixed $course course or id of the course
 439   * @param mixed $cm course module or id of the course module
 440   * @param context $context
 441   * @param string $filearea
 442   * @param array $args
 443   * @param bool $forcedownload
 444   * @param array $options additional options affecting the file serving
 445   * @return bool false if file not found, does not return if found - just send the file
 446   */
 447  function h5pactivity_pluginfile($course, $cm, context $context,
 448              string $filearea, array $args, bool $forcedownload, array $options = []): bool {
 449      if ($context->contextlevel != CONTEXT_MODULE) {
 450          return false;
 451      }
 452  
 453      require_login($course, true, $cm);
 454  
 455      $fullpath = '';
 456  
 457      if ($filearea === 'package') {
 458          $revision = (int)array_shift($args); // Prevents caching problems - ignored here.
 459          $relativepath = implode('/', $args);
 460          $fullpath = "/$context->id/mod_h5pactivity/package/0/$relativepath";
 461      }
 462      if (empty($fullpath)) {
 463          return false;
 464      }
 465      $fs = get_file_storage();
 466      $file = $fs->get_file_by_hash(sha1($fullpath));
 467      if (empty($file)) {
 468          return false;
 469      }
 470      send_stored_file($file, $lifetime, 0, false, $options);
 471  }
 472  
 473  /**
 474   * Saves draft files as the activity package.
 475   *
 476   * @param stdClass $data an object from the form
 477   */
 478  function h5pactivity_set_mainfile(stdClass $data): void {
 479      $fs = get_file_storage();
 480      $cmid = $data->coursemodule;
 481      $context = context_module::instance($cmid);
 482  
 483      if (!empty($data->packagefile)) {
 484          $fs = get_file_storage();
 485          $fs->delete_area_files($context->id, 'mod_h5pactivity', 'package');
 486          file_save_draft_area_files($data->packagefile, $context->id, 'mod_h5pactivity', 'package',
 487              0, ['subdirs' => 0, 'maxfiles' => 1]);
 488      }
 489  }
 490  
 491  /**
 492   * Register the ability to handle drag and drop file uploads
 493   * @return array containing details of the files / types the mod can handle
 494   */
 495  function h5pactivity_dndupload_register(): array {
 496      return [
 497          'files' => [
 498              [
 499                  'extension' => 'h5p',
 500                  'message' => get_string('dnduploadh5pactivity', 'h5pactivity')
 501              ]
 502          ]
 503      ];
 504  }
 505  
 506  /**
 507   * Handle a file that has been uploaded
 508   * @param object $uploadinfo details of the file / content that has been uploaded
 509   * @return int instance id of the newly created mod
 510   */
 511  function h5pactivity_dndupload_handle($uploadinfo): int {
 512      global $CFG;
 513  
 514      $context = context_module::instance($uploadinfo->coursemodule);
 515      file_save_draft_area_files($uploadinfo->draftitemid, $context->id, 'mod_h5pactivity', 'package', 0);
 516      $fs = get_file_storage();
 517      $files = $fs->get_area_files($context->id, 'mod_h5pactivity', 'package', 0, 'sortorder, itemid, filepath, filename', false);
 518      $file = reset($files);
 519  
 520      // Create a default h5pactivity object to pass to h5pactivity_add_instance()!
 521      $h5p = get_config('h5pactivity');
 522      $h5p->intro = '';
 523      $h5p->introformat = FORMAT_HTML;
 524      $h5p->course = $uploadinfo->course->id;
 525      $h5p->coursemodule = $uploadinfo->coursemodule;
 526      $h5p->grade = $CFG->gradepointdefault;
 527  
 528      // Add some special handling for the H5P options checkboxes.
 529      $factory = new \core_h5p\factory();
 530      $core = $factory->get_core();
 531      if (isset($uploadinfo->displayopt)) {
 532          $config = (object) $uploadinfo->displayopt;
 533      } else {
 534          $config = \core_h5p\helper::decode_display_options($core);
 535      }
 536      $h5p->displayoptions = \core_h5p\helper::get_display_options($core, $config);
 537  
 538      $h5p->cmidnumber = '';
 539      $h5p->name = $uploadinfo->displayname;
 540      $h5p->reference = $file->get_filename();
 541  
 542      return h5pactivity_add_instance($h5p, null);
 543  }