MDL-68451 mod_h5pactivity: Add info attempts in check_updates_since
[moodle.git] / mod / h5pactivity / lib.php
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/>.
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  */
25 defined('MOODLE_INTERNAL') || die();
27 use mod_h5pactivity\local\manager;
28 use mod_h5pactivity\local\grader;
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 }
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;
85     $data->timecreated = time();
86     $data->timemodified = $data->timecreated;
87     $cmid = $data->coursemodule;
89     $data->id = $DB->insert_record('h5pactivity', $data);
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);
95     // Extra fields required in grade related functions.
96     $data->cmid = $data->coursemodule;
97     h5pactivity_grade_item_update($data);
98     return $data->id;
99 }
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;
114     $data->timemodified = time();
115     $data->id = $data->instance;
117     h5pactivity_set_mainfile($data);
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     }
129     return $DB->update_record('h5pactivity', $data);
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;
141     $activity = $DB->get_record('h5pactivity', ['id' => $id]);
142     if (!$activity) {
143         return false;
144     }
146     $DB->delete_records('h5pactivity', ['id' => $id]);
148     h5pactivity_grade_item_delete($activity);
150     return true;
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;
164     if ($scaleid and $DB->record_exists('h5pactivity', ['grade' => -$scaleid])) {
165         return true;
166     } else {
167         return false;
168     }
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);
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();
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);
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 {
226     $manager = manager::create_from_coursemodule($cm);
227     $grader = $manager->get_grader();
228     $grader->update_grades();
229     return true;
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'));
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];
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;
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;
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=?";
306     if ($activities = $DB->get_records_sql($sql, [$courseid])) {
307         foreach ($activities as $activity) {
308             h5pactivity_grade_item_update($activity, 'reset');
309         }
310     }
313 /**
314  * Return a list of page types
315  *
316  * @param string $pagetype current page type
317  * @param stdClass $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;
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;
339     $updates = course_check_module_updates_since($cm, $from, ['package'], $filter);
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     }
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];
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         }
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;
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;
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;
411     if (!has_capability('moodle/course:managefiles', $context)) {
412         return null;
413     }
415     $fs = get_file_storage();
417     if ($filearea === 'package') {
418         $filepath = is_null($filepath) ? '/' : $filepath;
419         $filename = is_null($filename) ? '.' : $filename;
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;
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     }
453     require_login($course, true, $cm);
455     $fullpath = '';
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);
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);
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     }