Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 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 or string for the module purpose.
  45   */
  46  function h5pactivity_supports(string $feature) {
  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          case FEATURE_MOD_PURPOSE:
  67              return MOD_PURPOSE_CONTENT;
  68          default:
  69              return null;
  70      }
  71  }
  72  
  73  /**
  74   * Saves a new instance of the mod_h5pactivity into the database.
  75   *
  76   * Given an object containing all the necessary data, (defined by the form
  77   * in mod_form.php) this function will create a new instance and return the id
  78   * number of the instance.
  79   *
  80   * @param stdClass $data An object from the form.
  81   * @param mod_h5pactivity_mod_form $mform The form.
  82   * @return int The id of the newly inserted record.
  83   */
  84  function h5pactivity_add_instance(stdClass $data, mod_h5pactivity_mod_form $mform = null): int {
  85      global $DB;
  86  
  87      $data->timecreated = time();
  88      $data->timemodified = $data->timecreated;
  89      $cmid = $data->coursemodule;
  90  
  91      $data->id = $DB->insert_record('h5pactivity', $data);
  92  
  93      // We need to use context now, so we need to make sure all needed info is already in db.
  94      $DB->set_field('course_modules', 'instance', $data->id, ['id' => $cmid]);
  95      h5pactivity_set_mainfile($data);
  96  
  97      // Extra fields required in grade related functions.
  98      $data->cmid = $data->coursemodule;
  99      h5pactivity_grade_item_update($data);
 100      return $data->id;
 101  }
 102  
 103  /**
 104   * Updates an instance of the mod_h5pactivity in the database.
 105   *
 106   * Given an object containing all the necessary data (defined in mod_form.php),
 107   * this function will update an existing instance with new data.
 108   *
 109   * @param stdClass $data An object from the form in mod_form.php.
 110   * @param mod_h5pactivity_mod_form $mform The form.
 111   * @return bool True if successful, false otherwise.
 112   */
 113  function h5pactivity_update_instance(stdClass $data, mod_h5pactivity_mod_form $mform = null): bool {
 114      global $DB;
 115  
 116      $data->timemodified = time();
 117      $data->id = $data->instance;
 118  
 119      h5pactivity_set_mainfile($data);
 120  
 121      // Update gradings if grading method or tracking are modified.
 122      $data->cmid = $data->coursemodule;
 123      $moduleinstance = $DB->get_record('h5pactivity', ['id' => $data->id]);
 124      if (($moduleinstance->grademethod != $data->grademethod)
 125              || $data->enabletracking != $moduleinstance->enabletracking) {
 126          h5pactivity_update_grades($data);
 127      } else {
 128          h5pactivity_grade_item_update($data);
 129      }
 130  
 131      return $DB->update_record('h5pactivity', $data);
 132  }
 133  
 134  /**
 135   * Removes an instance of the mod_h5pactivity from the database.
 136   *
 137   * @param int $id Id of the module instance.
 138   * @return bool True if successful, false on failure.
 139   */
 140  function h5pactivity_delete_instance(int $id): bool {
 141      global $DB;
 142  
 143      $activity = $DB->get_record('h5pactivity', ['id' => $id]);
 144      if (!$activity) {
 145          return false;
 146      }
 147  
 148      // Remove activity record, and all associated attempt data.
 149      $attemptids = $DB->get_fieldset_select('h5pactivity_attempts', 'id', 'h5pactivityid = ?', [$id]);
 150      if ($attemptids) {
 151          $DB->delete_records_list('h5pactivity_attempts_results', 'attemptid', $attemptids);
 152          $DB->delete_records_list('h5pactivity_attempts', 'id', $attemptids);
 153      }
 154  
 155      $DB->delete_records('h5pactivity', ['id' => $id]);
 156  
 157      h5pactivity_grade_item_delete($activity);
 158  
 159      return true;
 160  }
 161  
 162  /**
 163   * Checks if scale is being used by any instance of mod_h5pactivity.
 164   *
 165   * This is used to find out if scale used anywhere.
 166   *
 167   * @param int $scaleid ID of the scale.
 168   * @return bool True if the scale is used by any mod_h5pactivity instance.
 169   */
 170  function h5pactivity_scale_used_anywhere(int $scaleid): bool {
 171      global $DB;
 172  
 173      if ($scaleid and $DB->record_exists('h5pactivity', ['grade' => -$scaleid])) {
 174          return true;
 175      } else {
 176          return false;
 177      }
 178  }
 179  
 180  /**
 181   * Creates or updates grade item for the given mod_h5pactivity instance.
 182   *
 183   * Needed by {@link grade_update_mod_grades()}.
 184   *
 185   * @param stdClass $moduleinstance Instance object with extra cmidnumber and modname property.
 186   * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
 187   * @return int int 0 if ok, error code otherwise
 188   */
 189  function h5pactivity_grade_item_update(stdClass $moduleinstance, $grades = null): int {
 190      $idnumber = $moduleinstance->idnumber ?? '';
 191      $grader = new grader($moduleinstance, $idnumber);
 192      return $grader->grade_item_update($grades);
 193  }
 194  
 195  /**
 196   * Delete grade item for given mod_h5pactivity instance.
 197   *
 198   * @param stdClass $moduleinstance Instance object.
 199   * @return int Returns GRADE_UPDATE_OK, GRADE_UPDATE_FAILED, GRADE_UPDATE_MULTIPLE or GRADE_UPDATE_ITEM_LOCKED
 200   */
 201  function h5pactivity_grade_item_delete(stdClass $moduleinstance): ?int {
 202      $idnumber = $moduleinstance->idnumber ?? '';
 203      $grader = new grader($moduleinstance, $idnumber);
 204      return $grader->grade_item_delete();
 205  }
 206  
 207  /**
 208   * Update mod_h5pactivity grades in the gradebook.
 209   *
 210   * Needed by {@link grade_update_mod_grades()}.
 211   *
 212   * @param stdClass $moduleinstance Instance object with extra cmidnumber and modname property.
 213   * @param int $userid Update grade of specific user only, 0 means all participants.
 214   */
 215  function h5pactivity_update_grades(stdClass $moduleinstance, int $userid = 0): void {
 216      $idnumber = $moduleinstance->idnumber ?? '';
 217      $grader = new grader($moduleinstance, $idnumber);
 218      $grader->update_grades($userid);
 219  }
 220  
 221  /**
 222   * Rescale all grades for this activity and push the new grades to the gradebook.
 223   *
 224   * @param stdClass $course Course db record
 225   * @param stdClass $cm Course module db record
 226   * @param float $oldmin
 227   * @param float $oldmax
 228   * @param float $newmin
 229   * @param float $newmax
 230   * @return bool true if reescale is successful
 231   */
 232  function h5pactivity_rescale_activity_grades(stdClass $course, stdClass $cm, float $oldmin,
 233          float $oldmax, float $newmin, float $newmax): bool {
 234  
 235      $manager = manager::create_from_coursemodule($cm);
 236      $grader = $manager->get_grader();
 237      $grader->update_grades();
 238      return true;
 239  }
 240  
 241  /**
 242   * Implementation of the function for printing the form elements that control
 243   * whether the course reset functionality affects the H5P activity.
 244   *
 245   * @param object $mform form passed by reference
 246   */
 247  function h5pactivity_reset_course_form_definition(&$mform): void {
 248      $mform->addElement('header', 'h5pactivityheader', get_string('modulenameplural', 'mod_h5pactivity'));
 249      $mform->addElement('advcheckbox', 'reset_h5pactivity', get_string('deleteallattempts', 'mod_h5pactivity'));
 250  }
 251  
 252  /**
 253   * Course reset form defaults.
 254   *
 255   * @param stdClass $course the course object
 256   * @return array
 257   */
 258  function h5pactivity_reset_course_form_defaults(stdClass $course): array {
 259      return ['reset_h5pactivity' => 1];
 260  }
 261  
 262  
 263  /**
 264   * This function is used by the reset_course_userdata function in moodlelib.
 265   *
 266   * This function will remove all H5P attempts in the database
 267   * and clean up any related data.
 268   *
 269   * @param stdClass $data the data submitted from the reset course.
 270   * @return array of reseting status
 271   */
 272  function h5pactivity_reset_userdata(stdClass $data): array {
 273      global $DB;
 274      $componentstr = get_string('modulenameplural', 'mod_h5pactivity');
 275      $status = [];
 276      if (!empty($data->reset_h5pactivity)) {
 277          $params = ['courseid' => $data->courseid];
 278          $sql = "SELECT a.id FROM {h5pactivity} a WHERE a.course=:courseid";
 279          if ($activities = $DB->get_records_sql($sql, $params)) {
 280              foreach ($activities as $activity) {
 281                  $cm = get_coursemodule_from_instance('h5pactivity',
 282                                                       $activity->id,
 283                                                       $data->courseid,
 284                                                       false,
 285                                                       MUST_EXIST);
 286                  mod_h5pactivity\local\attempt::delete_all_attempts ($cm);
 287              }
 288          }
 289          // Remove all grades from gradebook.
 290          if (empty($data->reset_gradebook_grades)) {
 291              h5pactivity_reset_gradebook($data->courseid, 'reset');
 292          }
 293          $status[] = [
 294              'component' => $componentstr,
 295              'item' => get_string('deleteallattempts', 'mod_h5pactivity'),
 296              'error' => false,
 297          ];
 298      }
 299      return $status;
 300  }
 301  
 302  /**
 303   * Removes all grades from gradebook
 304   *
 305   * @param int $courseid Coude ID
 306   * @param string $type optional type (default '')
 307   */
 308  function h5pactivity_reset_gradebook(int $courseid, string $type=''): void {
 309      global $DB;
 310  
 311      $sql = "SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
 312                FROM {h5pactivity} a, {course_modules} cm, {modules} m
 313               WHERE m.name='h5pactivity' AND m.id=cm.module AND cm.instance=a.id AND a.course=?";
 314  
 315      if ($activities = $DB->get_records_sql($sql, [$courseid])) {
 316          foreach ($activities as $activity) {
 317              h5pactivity_grade_item_update($activity, 'reset');
 318          }
 319      }
 320  }
 321  
 322  /**
 323   * Return a list of page types
 324   *
 325   * @param string $pagetype current page type
 326   * @param stdClass|null $parentcontext Block's parent context
 327   * @param stdClass $currentcontext Current context of block
 328   * @return array array of page types and it's names
 329   */
 330  function h5pactivity_page_type_list(string $pagetype, ?stdClass $parentcontext, stdClass $currentcontext): array {
 331      $modulepagetype = [
 332          'mod-h5pactivity-*' => get_string('page-mod-h5pactivity-x', 'h5pactivity'),
 333      ];
 334      return $modulepagetype;
 335  }
 336  
 337  /**
 338   * Check if the module has any update that affects the current user since a given time.
 339   *
 340   * @param  cm_info $cm course module data
 341   * @param  int $from the time to check updates from
 342   * @param  array $filter  if we need to check only specific updates
 343   * @return stdClass an object with the different type of areas indicating if they were updated or not
 344   */
 345  function h5pactivity_check_updates_since(cm_info $cm, int $from, array $filter = []): stdClass {
 346      global $DB, $USER;
 347  
 348      $updates = course_check_module_updates_since($cm, $from, ['package'], $filter);
 349  
 350      $updates->tracks = (object) ['updated' => false];
 351      $select = 'h5pactivityid = ? AND userid = ? AND timemodified > ?';
 352      $params = [$cm->instance, $USER->id, $from];
 353      $tracks = $DB->get_records_select('h5pactivity_attempts', $select, $params, '', 'id');
 354      if (!empty($tracks)) {
 355          $updates->tracks->updated = true;
 356          $updates->tracks->itemids = array_keys($tracks);
 357      }
 358  
 359      // Now, teachers should see other students updates.
 360      if (has_capability('mod/h5pactivity:reviewattempts', $cm->context)) {
 361          $select = 'h5pactivityid = ? AND timemodified > ?';
 362          $params = [$cm->instance, $from];
 363  
 364          if (groups_get_activity_groupmode($cm) == SEPARATEGROUPS) {
 365              $groupusers = array_keys(groups_get_activity_shared_group_members($cm));
 366              if (empty($groupusers)) {
 367                  return $updates;
 368              }
 369              list($insql, $inparams) = $DB->get_in_or_equal($groupusers);
 370              $select .= ' AND userid ' . $insql;
 371              $params = array_merge($params, $inparams);
 372          }
 373  
 374          $updates->usertracks = (object) ['updated' => false];
 375          $tracks = $DB->get_records_select('h5pactivity_attempts', $select, $params, '', 'id');
 376          if (!empty($tracks)) {
 377              $updates->usertracks->updated = true;
 378              $updates->usertracks->itemids = array_keys($tracks);
 379          }
 380      }
 381      return $updates;
 382  }
 383  
 384  /**
 385   * Returns the lists of all browsable file areas within the given module context.
 386   *
 387   * The file area 'intro' for the activity introduction field is added automatically
 388   * by {@link file_browser::get_file_info_context_module()}.
 389   *
 390   * @param stdClass $course course object
 391   * @param stdClass $cm course module object
 392   * @param stdClass $context context object
 393   * @return string[] array of pair file area => human file area name
 394   */
 395  function h5pactivity_get_file_areas(stdClass $course, stdClass $cm, stdClass $context): array {
 396      $areas = [];
 397      $areas['package'] = get_string('areapackage', 'mod_h5pactivity');
 398      return $areas;
 399  }
 400  
 401  /**
 402   * File browsing support for data module.
 403   *
 404   * @param file_browser $browser
 405   * @param array $areas
 406   * @param stdClass $course
 407   * @param stdClass $cm
 408   * @param context $context
 409   * @param string $filearea
 410   * @param int|null $itemid
 411   * @param string|null $filepath
 412   * @param string|null $filename
 413   * @return file_info_stored|null file_info_stored instance or null if not found
 414   */
 415  function h5pactivity_get_file_info(file_browser $browser, array $areas, stdClass $course,
 416              stdClass $cm, context $context, string $filearea, ?int $itemid = null,
 417              ?string $filepath = null, ?string $filename = null): ?file_info_stored {
 418      global $CFG;
 419  
 420      if (!has_capability('moodle/course:managefiles', $context)) {
 421          return null;
 422      }
 423  
 424      $fs = get_file_storage();
 425  
 426      if ($filearea === 'package') {
 427          $filepath = is_null($filepath) ? '/' : $filepath;
 428          $filename = is_null($filename) ? '.' : $filename;
 429  
 430          $urlbase = $CFG->wwwroot.'/pluginfile.php';
 431          if (!$storedfile = $fs->get_file($context->id, 'mod_h5pactivity', 'package', 0, $filepath, $filename)) {
 432              if ($filepath === '/' and $filename === '.') {
 433                  $storedfile = new virtual_root_file($context->id, 'mod_h5pactivity', 'package', 0);
 434              } else {
 435                  // Not found.
 436                  return null;
 437              }
 438          }
 439          return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, false, false);
 440      }
 441      return null;
 442  }
 443  
 444  /**
 445   * Serves the files from the mod_h5pactivity file areas.
 446   *
 447   * @param mixed $course course or id of the course
 448   * @param mixed $cm course module or id of the course module
 449   * @param context $context
 450   * @param string $filearea
 451   * @param array $args
 452   * @param bool $forcedownload
 453   * @param array $options additional options affecting the file serving
 454   * @return bool false if file not found, does not return if found - just send the file
 455   */
 456  function h5pactivity_pluginfile($course, $cm, context $context,
 457              string $filearea, array $args, bool $forcedownload, array $options = []): bool {
 458      if ($context->contextlevel != CONTEXT_MODULE) {
 459          return false;
 460      }
 461  
 462      require_login($course, true, $cm);
 463  
 464      $fullpath = '';
 465  
 466      if ($filearea === 'package') {
 467          $revision = (int)array_shift($args); // Prevents caching problems - ignored here.
 468          $relativepath = implode('/', $args);
 469          $fullpath = "/$context->id/mod_h5pactivity/package/0/$relativepath";
 470      }
 471      if (empty($fullpath)) {
 472          return false;
 473      }
 474      $fs = get_file_storage();
 475      $file = $fs->get_file_by_hash(sha1($fullpath));
 476      if (empty($file)) {
 477          return false;
 478      }
 479      send_stored_file($file, $lifetime, 0, false, $options);
 480  }
 481  
 482  /**
 483   * Saves draft files as the activity package.
 484   *
 485   * @param stdClass $data an object from the form
 486   */
 487  function h5pactivity_set_mainfile(stdClass $data): void {
 488      $fs = get_file_storage();
 489      $cmid = $data->coursemodule;
 490      $context = context_module::instance($cmid);
 491  
 492      if (!empty($data->packagefile)) {
 493          $fs = get_file_storage();
 494          $fs->delete_area_files($context->id, 'mod_h5pactivity', 'package');
 495          file_save_draft_area_files($data->packagefile, $context->id, 'mod_h5pactivity', 'package',
 496              0, ['subdirs' => 0, 'maxfiles' => 1]);
 497      }
 498  }
 499  
 500  /**
 501   * Register the ability to handle drag and drop file uploads
 502   * @return array containing details of the files / types the mod can handle
 503   */
 504  function h5pactivity_dndupload_register(): array {
 505      return [
 506          'files' => [
 507              [
 508                  'extension' => 'h5p',
 509                  'message' => get_string('dnduploadh5pactivity', 'h5pactivity')
 510              ]
 511          ]
 512      ];
 513  }
 514  
 515  /**
 516   * Handle a file that has been uploaded
 517   * @param object $uploadinfo details of the file / content that has been uploaded
 518   * @return int instance id of the newly created mod
 519   */
 520  function h5pactivity_dndupload_handle($uploadinfo): int {
 521      global $CFG;
 522  
 523      $context = context_module::instance($uploadinfo->coursemodule);
 524      file_save_draft_area_files($uploadinfo->draftitemid, $context->id, 'mod_h5pactivity', 'package', 0);
 525      $fs = get_file_storage();
 526      $files = $fs->get_area_files($context->id, 'mod_h5pactivity', 'package', 0, 'sortorder, itemid, filepath, filename', false);
 527      $file = reset($files);
 528  
 529      // Create a default h5pactivity object to pass to h5pactivity_add_instance()!
 530      $h5p = get_config('h5pactivity');
 531      $h5p->intro = '';
 532      $h5p->introformat = FORMAT_HTML;
 533      $h5p->course = $uploadinfo->course->id;
 534      $h5p->coursemodule = $uploadinfo->coursemodule;
 535      $h5p->grade = $CFG->gradepointdefault;
 536  
 537      // Add some special handling for the H5P options checkboxes.
 538      $factory = new \core_h5p\factory();
 539      $core = $factory->get_core();
 540      if (isset($uploadinfo->displayopt)) {
 541          $config = (object) $uploadinfo->displayopt;
 542      } else {
 543          $config = \core_h5p\helper::decode_display_options($core);
 544      }
 545      $h5p->displayoptions = \core_h5p\helper::get_display_options($core, $config);
 546  
 547      $h5p->cmidnumber = '';
 548      $h5p->name = $uploadinfo->displayname;
 549      $h5p->reference = $file->get_filename();
 550  
 551      return h5pactivity_add_instance($h5p, null);
 552  }
 553  
 554  /**
 555   * Print recent activity from all h5pactivities in a given course
 556   *
 557   * This is used by the recent activity block
 558   * @param mixed $course the course to print activity for
 559   * @param bool $viewfullnames boolean to determine whether to show full names or not
 560   * @param int $timestart the time the rendering started
 561   * @return bool true if activity was printed, false otherwise.
 562   */
 563  function h5pactivity_print_recent_activity($course, bool $viewfullnames, int $timestart): bool {
 564      global $CFG, $DB, $OUTPUT, $USER;
 565  
 566      $dbparams = [$timestart, $course->id, 'h5pactivity'];
 567  
 568      $userfieldsapi = \core_user\fields::for_userpic();
 569      $namefields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects;;
 570  
 571      $sql = "SELECT h5pa.id, h5pa.timemodified, cm.id as cmid, $namefields
 572                FROM {h5pactivity_attempts} h5pa
 573                JOIN {h5pactivity} h5p ON h5p.id = h5pa.h5pactivityid
 574                JOIN {course_modules} cm ON cm.instance = h5p.id
 575                JOIN {modules} md ON md.id = cm.module
 576                JOIN {user} u ON u.id = h5pa.userid
 577               WHERE h5pa.timemodified > ?
 578                 AND h5p.course = ?
 579                 AND md.name = ?
 580            ORDER BY h5pa.timemodified ASC";
 581  
 582      if (!$submissions = $DB->get_records_sql($sql, $dbparams)) {
 583          return false;
 584      }
 585  
 586      $modinfo = get_fast_modinfo($course);
 587  
 588      $recentactivity = h5pactivity_fetch_recent_activity($submissions, $course->id);
 589  
 590      if (empty($recentactivity)) {
 591          return false;
 592      }
 593  
 594      $cms = $modinfo->get_cms();
 595  
 596      echo $OUTPUT->heading(get_string('newsubmissions', 'h5pactivity') . ':', 6);
 597  
 598      foreach ($recentactivity as $submission) {
 599          $cm = $cms[$submission->cmid];
 600          $link = $CFG->wwwroot.'/mod/h5pactivity/view.php?id='.$cm->id;
 601          print_recent_activity_note($submission->timemodified,
 602              $submission,
 603              $cm->name,
 604              $link,
 605              false,
 606              $viewfullnames);
 607      }
 608  
 609      return true;
 610  }
 611  
 612  /**
 613   * Returns all h5pactivities since a given time.
 614   *
 615   * @param array $activities The activity information is returned in this array
 616   * @param int $index The current index in the activities array
 617   * @param int $timestart The earliest activity to show
 618   * @param int $courseid Limit the search to this course
 619   * @param int $cmid The course module id
 620   * @param int $userid Optional user id
 621   * @param int $groupid Optional group id
 622   * @return void
 623   */
 624  function h5pactivity_get_recent_mod_activity(array &$activities, int &$index, int $timestart, int $courseid,
 625              int $cmid, int $userid=0, int $groupid=0) {
 626      global $CFG, $DB, $USER;
 627  
 628      $course = get_course($courseid);
 629      $modinfo = get_fast_modinfo($course);
 630  
 631      $cm = $modinfo->get_cm($cmid);
 632      $params = [];
 633      if ($userid) {
 634          $userselect = 'AND u.id = :userid';
 635          $params['userid'] = $userid;
 636      } else {
 637          $userselect = '';
 638      }
 639  
 640      if ($groupid) {
 641          $groupselect = 'AND gm.groupid = :groupid';
 642          $groupjoin = 'JOIN {groups_members} gm ON gm.userid=u.id';
 643          $params['groupid'] = $groupid;
 644      } else {
 645          $groupselect = '';
 646          $groupjoin = '';
 647      }
 648  
 649      $params['cminstance'] = $cm->instance;
 650      $params['timestart'] = $timestart;
 651      $params['cmid'] = $cmid;
 652  
 653      $userfieldsapi = \core_user\fields::for_userpic();
 654      $userfields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects;
 655  
 656      $sql = "SELECT h5pa.id, h5pa.timemodified, cm.id as cmid, $userfields
 657                FROM {h5pactivity_attempts} h5pa
 658                JOIN {h5pactivity} h5p ON h5p.id = h5pa.h5pactivityid
 659                JOIN {course_modules} cm ON cm.instance = h5p.id
 660                JOIN {modules} md ON md.id = cm.module
 661                JOIN {user} u ON u.id = h5pa.userid $groupjoin
 662               WHERE h5pa.timemodified > :timestart
 663                 AND h5p.id = :cminstance $userselect $groupselect
 664                 AND cm.id = :cmid
 665            ORDER BY h5pa.timemodified ASC";
 666  
 667      if (!$submissions = $DB->get_records_sql($sql, $params)) {
 668          return;
 669      }
 670  
 671      $cmcontext = context_module::instance($cm->id);
 672      $grader = has_capability('mod/h5pactivity:reviewattempts', $cmcontext);
 673      $viewfullnames = has_capability('moodle/site:viewfullnames', $cmcontext);
 674  
 675      $recentactivity = h5pactivity_fetch_recent_activity($submissions, $courseid);
 676  
 677      if (empty($recentactivity)) {
 678          return;
 679      }
 680  
 681      if ($grader) {
 682          require_once($CFG->libdir.'/gradelib.php');
 683          $userids = [];
 684          foreach ($recentactivity as $id => $submission) {
 685              $userids[] = $submission->userid;
 686          }
 687          $grades = grade_get_grades($courseid, 'mod', 'h5pactivity', $cm->instance, $userids);
 688      }
 689  
 690      $aname = format_string($cm->name, true);
 691      foreach ($recentactivity as $submission) {
 692          $activity = new stdClass();
 693  
 694          $activity->type = 'h5pactivity';
 695          $activity->cmid = $cm->id;
 696          $activity->name = $aname;
 697          $activity->sectionnum = $cm->sectionnum;
 698          $activity->timestamp = $submission->timemodified;
 699          $activity->user = new stdClass();
 700          if ($grader) {
 701              $activity->grade = $grades->items[0]->grades[$submission->userid]->str_long_grade;
 702          }
 703  
 704          $userfields = explode(',', implode(',', \core_user\fields::get_picture_fields()));
 705          foreach ($userfields as $userfield) {
 706              if ($userfield == 'id') {
 707                  // Aliased in SQL above.
 708                  $activity->user->{$userfield} = $submission->userid;
 709              } else {
 710                  $activity->user->{$userfield} = $submission->{$userfield};
 711              }
 712          }
 713          $activity->user->fullname = fullname($submission, $viewfullnames);
 714  
 715          $activities[$index++] = $activity;
 716      }
 717  
 718      return;
 719  }
 720  
 721  /**
 722   * Print recent activity from all h5pactivities in a given course
 723   *
 724   * This is used by course/recent.php
 725   * @param stdClass $activity
 726   * @param int $courseid
 727   * @param bool $detail
 728   * @param array $modnames
 729   */
 730  function h5pactivity_print_recent_mod_activity(stdClass $activity, int $courseid, bool $detail, array $modnames) {
 731      global $OUTPUT;
 732  
 733      $modinfo = [];
 734      if ($detail) {
 735          $modinfo['modname'] = $activity->name;
 736          $modinfo['modurl'] = new moodle_url('/mod/h5pactivity/view.php', ['id' => $activity->cmid]);
 737          $modinfo['modicon'] = $OUTPUT->image_icon('monologo', $modnames[$activity->type], 'h5pactivity');
 738      }
 739  
 740      $userpicture = $OUTPUT->user_picture($activity->user);
 741  
 742      $template = ['userpicture' => $userpicture,
 743          'submissiontimestamp' => $activity->timestamp,
 744          'modinfo' => $modinfo,
 745          'userurl' => new moodle_url('/user/view.php', array('id' => $activity->user->id, 'course' => $courseid)),
 746          'fullname' => $activity->user->fullname];
 747      if (isset($activity->grade)) {
 748          $template['grade'] = get_string('grade_h5p', 'h5pactivity', $activity->grade);
 749      }
 750  
 751      echo $OUTPUT->render_from_template('mod_h5pactivity/reviewattempts', $template);
 752  }
 753  
 754  /**
 755   * Fetches recent activity for course module.
 756   *
 757   * @param array $submissions The activity submissions
 758   * @param int $courseid Limit the search to this course
 759   * @return array $recentactivity recent activity in a course.
 760   */
 761  function h5pactivity_fetch_recent_activity(array $submissions, int $courseid) : array {
 762      global $USER;
 763  
 764      $course = get_course($courseid);
 765      $modinfo = get_fast_modinfo($course);
 766  
 767      $recentactivity = [];
 768      $grader = [];
 769  
 770      $cms = $modinfo->get_cms();
 771  
 772      foreach ($submissions as $submission) {
 773          if (!array_key_exists($submission->cmid, $cms)) {
 774              continue;
 775          }
 776          $cm = $cms[$submission->cmid];
 777          if (!$cm->uservisible) {
 778              continue;
 779          }
 780  
 781          if ($USER->id == $submission->userid) {
 782              $recentactivity[$submission->userid] = $submission;
 783              continue;
 784          }
 785  
 786          $cmcontext = context_module::instance($cm->id);
 787          // The act of submitting of attempt may be considered private -
 788          // only graders will see it if specified.
 789          if (!array_key_exists($cm->id, $grader)) {
 790              $grader[$cm->id] = has_capability('mod/h5pactivity:reviewattempts', $cmcontext);
 791          }
 792          if (!$grader[$cm->id]) {
 793              continue;
 794          }
 795  
 796          $groups = [];
 797          $usersgroups = [];
 798  
 799          $groupmode = groups_get_activity_groupmode($cm, $course);
 800          $accessallgroups = has_capability('moodle/site:accessallgroups', $cmcontext);
 801  
 802          if ($groupmode == SEPARATEGROUPS && !$accessallgroups) {
 803  
 804              if (isguestuser()) {
 805                  // Shortcut - guest user does not belong into any group.
 806                  continue;
 807              }
 808  
 809              if (!isset($groups[$cm->groupingid])) {
 810                  $groups[$cm->groupingid] = $modinfo->get_groups($cm->groupingid);
 811                  if (!$groups[$cm->groupingid]) {
 812                      continue;
 813                  }
 814              }
 815  
 816              if (!isset($usersgroups[$cm->groupingid][$submission->userid])) {
 817                  $usersgroups[$cm->groupingid][$submission->userid] =
 818                      groups_get_all_groups($course->id, $submission->userid, $cm->groupingid);
 819              }
 820  
 821              if (is_array($usersgroups[$cm->groupingid][$submission->userid])) {
 822                  $usersgroupstmp = array_keys($usersgroups[$cm->groupingid][$submission->userid]);
 823                  $intersect = array_intersect($usersgroupstmp, $groups[$cm->groupingid]);
 824                  if (empty($intersect)) {
 825                      continue;
 826                  }
 827              }
 828          }
 829  
 830          $recentactivity[$submission->userid] = $submission;
 831      }
 832  
 833      return $recentactivity;
 834  }
 835  
 836  /**
 837   * Extends the settings navigation with the H5P activity settings
 838  
 839   * This function is called when the context for the page is an H5P activity. This is not called by AJAX
 840   * so it is safe to rely on the $PAGE.
 841   *
 842   * @param settings_navigation $settingsnav The settings navigation object
 843   * @param navigation_node $h5pactivitynode The node to add module settings to
 844   */
 845  function h5pactivity_extend_settings_navigation(settings_navigation $settingsnav,
 846          navigation_node $h5pactivitynode = null) {
 847      global $USER;
 848  
 849      $manager = manager::create_from_coursemodule($settingsnav->get_page()->cm);
 850  
 851      // Attempts report.
 852      if ($manager->can_view_all_attempts()) {
 853          $attemptsreporturl = new moodle_url('/mod/h5pactivity/report.php',
 854              ['a' => $settingsnav->get_page()->cm->instance]);
 855          $h5pactivitynode->add(get_string('attempts_report', 'h5pactivity'), $attemptsreporturl,
 856              settings_navigation::TYPE_SETTING, '', 'attemptsreport');
 857      } else if ($manager->can_view_own_attempts() && $manager->count_attempts($USER->id)) {
 858          $attemptsreporturl = new moodle_url('/mod/h5pactivity/report.php',
 859              ['a' => $settingsnav->get_page()->cm->instance, 'userid' => $USER->id]);
 860          $h5pactivitynode->add(get_string('attempts_report', 'h5pactivity'), $attemptsreporturl,
 861              settings_navigation::TYPE_SETTING, '', 'attemptsreport');
 862      }
 863  }