Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

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