Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 * Bulk activity completion manager class
19
 *
20
 * @package     core_completion
21
 * @category    completion
22
 * @copyright   2017 Adrian Greeve
23
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
namespace core_completion;
27
 
28
use core\context;
1441 ariadna 29
use core\output\local\properties\iconsize;
30
use core_course\output\activity_icon;
1 efrain 31
use stdClass;
32
use context_course;
33
use cm_info;
34
use moodle_url;
35
 
36
/**
37
 * Bulk activity completion manager class
38
 *
39
 * @package     core_completion
40
 * @category    completion
41
 * @copyright   2017 Adrian Greeve
42
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43
 */
44
class manager {
45
 
46
    /**
47
     * @var int $courseid the course id.
48
     */
49
    protected $courseid;
50
 
51
    /**
52
     * manager constructor.
53
     * @param int $courseid the course id.
54
     */
55
    public function __construct($courseid) {
56
        $this->courseid = $courseid;
57
    }
58
 
59
    /**
60
     * Returns current course context or system level for $SITE courseid.
61
     *
62
     * @return context The course based on current courseid or system context.
63
     */
64
    protected function get_context(): context {
65
        global $SITE;
66
 
67
        if ($this->courseid && $this->courseid != $SITE->id) {
68
            return context_course::instance($this->courseid);
69
        }
70
        return \context_system::instance();
71
    }
72
 
73
    /**
74
     * Gets the data (context) to be used with the bulkactivitycompletion template.
75
     *
76
     * @return stdClass data for use with the bulkactivitycompletion template.
77
     */
78
    public function get_activities_and_headings() {
79
        global $OUTPUT;
80
        $moduleinfo = get_fast_modinfo($this->courseid);
81
        $sections = $moduleinfo->get_sections();
82
        $data = new stdClass;
83
        $data->courseid = $this->courseid;
84
        $data->sesskey = sesskey();
85
        $data->helpicon = $OUTPUT->help_icon('bulkcompletiontracking', 'core_completion');
86
        $data->sections = [];
87
        foreach ($sections as $sectionnumber => $section) {
88
            $sectioninfo = $moduleinfo->get_section_info($sectionnumber);
89
 
90
            $sectionobject = new stdClass();
91
            $sectionobject->sectionnumber = $sectionnumber;
92
            $sectionobject->name = get_section_name($this->courseid, $sectioninfo);
93
            $sectionobject->activities = $this->get_activities($section, true);
94
            $data->sections[] = $sectionobject;
95
        }
96
        return $data;
97
    }
98
 
99
    /**
100
     * Gets the data (context) to be used with the activityinstance template
101
     *
102
     * @param array $cmids list of course module ids
103
     * @param bool $withcompletiondetails include completion details
104
     * @return array
105
     */
106
    public function get_activities($cmids, $withcompletiondetails = false) {
1441 ariadna 107
        $output = \core\di::get(\core\output\renderer_helper::class)->get_core_renderer();
1 efrain 108
        $moduleinfo = get_fast_modinfo($this->courseid);
109
        $activities = [];
110
        foreach ($cmids as $cmid) {
111
            $mod = $moduleinfo->get_cm($cmid);
112
            if (!$mod->uservisible) {
113
                continue;
114
            }
1441 ariadna 115
 
116
            $icon = activity_icon::from_cm_info($mod)
117
                ->set_icon_size(iconsize::SIZE5);
118
 
1 efrain 119
            $moduleobject = new stdClass();
120
            $moduleobject->cmid = $cmid;
121
            $moduleobject->modname = $mod->get_formatted_name();
122
            $moduleobject->icon = $mod->get_icon_url()->out();
123
            $moduleobject->url = $mod->url;
1441 ariadna 124
            $moduleobject->activityicon = $icon->export_for_template($output);
1 efrain 125
            $moduleobject->canmanage = $withcompletiondetails && self::can_edit_bulk_completion($this->courseid, $mod);
126
 
127
            // Get activity completion information.
128
            if ($moduleobject->canmanage) {
129
                $moduleobject->completionstatus = $this->get_completion_detail($mod);
130
            } else {
131
                $moduleobject->completionstatus = ['icon' => null, 'string' => null];
132
            }
133
            if (self::can_edit_bulk_completion($this->courseid, $mod)) {
134
                $activities[] = $moduleobject;
135
            }
136
        }
137
        return $activities;
138
    }
139
 
140
 
141
    /**
142
     * Get completion information on the selected module or module type
143
     *
144
     * @param cm_info|stdClass $mod either instance of cm_info (with 'customcompletionrules' in customdata) or
145
     *      object with fields ->completion, ->completionview, ->completionexpected, ->completionusegrade
146
     *      and ->customdata['customcompletionrules']
147
     * @return array
148
     */
149
    private function get_completion_detail($mod) {
150
        global $OUTPUT;
151
        $strings = [];
152
        switch ($mod->completion) {
153
            case COMPLETION_TRACKING_NONE:
154
                $strings['string'] = get_string('none');
155
                break;
156
 
157
            case COMPLETION_TRACKING_MANUAL:
158
                $strings['string'] = get_string('manual', 'completion');
159
                $strings['icon'] = $OUTPUT->pix_icon('i/completion-manual-y', get_string('completion_manual', 'completion'));
160
                break;
161
 
162
            case COMPLETION_TRACKING_AUTOMATIC:
163
                $strings['string'] = get_string('withconditions', 'completion');
164
                $strings['icon'] = $OUTPUT->pix_icon('i/completion-auto-y', get_string('completion_automatic', 'completion'));
165
                break;
166
 
167
            default:
168
                $strings['string'] = get_string('none');
169
                break;
170
        }
171
 
172
        // Get the descriptions for all the active completion rules for the module.
173
        if ($ruledescriptions = $this->get_completion_active_rule_descriptions($mod)) {
174
            foreach ($ruledescriptions as $ruledescription) {
175
                $strings['string'] .= \html_writer::empty_tag('br') . $ruledescription;
176
            }
177
        }
178
        return $strings;
179
    }
180
 
181
    /**
182
     * Get the descriptions for all active conditional completion rules for the current module.
183
     *
184
     * @param cm_info|stdClass $moduledata either instance of cm_info (with 'customcompletionrules' in customdata) or
185
     *      object with fields ->completion, ->completionview, ->completionexpected, ->completionusegrade
186
     *      and ->customdata['customcompletionrules']
187
     * @return array $activeruledescriptions an array of strings describing the active completion rules.
188
     */
189
    protected function get_completion_active_rule_descriptions($moduledata) {
190
        $activeruledescriptions = [];
191
 
192
        if ($moduledata->completion == COMPLETION_TRACKING_AUTOMATIC) {
193
            // Generate the description strings for the core conditional completion rules (if set).
194
            if (!empty($moduledata->completionview)) {
195
                $activeruledescriptions[] = get_string('completionview_desc', 'completion');
196
            }
197
            if ($moduledata instanceof cm_info && !is_null($moduledata->completiongradeitemnumber) ||
198
                ($moduledata instanceof stdClass && !empty($moduledata->completionusegrade))) {
199
 
200
                $description = 'completionusegrade_desc';
201
                if (!empty($moduledata->completionpassgrade)) {
202
                    $description = 'completionpassgrade_desc';
203
                }
204
 
205
                $activeruledescriptions[] = get_string($description, 'completion');
206
            }
207
 
208
            // Now, ask the module to provide descriptions for its custom conditional completion rules.
209
            if ($customruledescriptions = component_callback($moduledata->modname,
210
                'get_completion_active_rule_descriptions', [$moduledata])) {
211
                $activeruledescriptions = array_merge($activeruledescriptions, $customruledescriptions);
212
            }
213
        }
214
 
215
        if ($moduledata->completion != COMPLETION_TRACKING_NONE) {
216
            if (!empty($moduledata->completionexpected)) {
217
                $activeruledescriptions[] = get_string('completionexpecteddesc', 'completion',
218
                    userdate($moduledata->completionexpected));
219
            }
220
        }
221
 
222
        return $activeruledescriptions;
223
    }
224
 
225
    /**
226
     * Gets the course modules for the current course.
227
     *
228
     * @param bool $includedefaults Whether the default values should be included or not.
229
     * @return stdClass $data containing the modules
230
     */
231
    public function get_activities_and_resources(bool $includedefaults = true) {
232
        global $DB, $OUTPUT, $CFG;
233
        require_once($CFG->dirroot.'/course/lib.php');
234
 
235
        // Get enabled activities and resources.
236
        $modules = $DB->get_records('modules', ['visible' => 1], 'name ASC');
237
        $data = new stdClass();
238
        $data->courseid = $this->courseid;
239
        $data->sesskey = sesskey();
240
        $data->helpicon = $OUTPUT->help_icon('bulkcompletiontracking', 'core_completion');
241
        // Add icon information.
242
        $data->modules = array_values($modules);
243
        $context = $this->get_context();
244
        $canmanage = has_capability('moodle/course:manageactivities', $context);
245
        $course = get_course($this->courseid);
246
        $availablemodules = [];
247
        foreach ($data->modules as $module) {
248
            $libfile = "$CFG->dirroot/mod/$module->name/lib.php";
249
            if (!file_exists($libfile)) {
250
                continue;
251
            }
1441 ariadna 252
            if (!plugin_supports('mod', $module->name, FEATURE_MODEDIT_DEFAULT_COMPLETION, true)) {
253
                continue;
254
            }
1 efrain 255
            $module->icon = $OUTPUT->image_url('monologo', $module->name)->out();
256
            $module->formattedname = format_string(get_string('modulename', 'mod_' . $module->name),
257
                true, ['context' => $context]);
258
            $module->canmanage = $canmanage && course_allowed_module($course, $module->name);
259
            if ($includedefaults) {
260
                $defaults = self::get_default_completion($course, $module, false);
261
                $defaults->modname = $module->name;
262
                $module->completionstatus = $this->get_completion_detail($defaults);
263
            }
264
            $availablemodules[] = $module;
265
        }
266
        // Order modules by displayed name.
267
        usort($availablemodules, function($a, $b) {
268
            return strcmp($a->formattedname, $b->formattedname);
269
        });
270
        $data->modules = $availablemodules;
271
 
272
        return $data;
273
    }
274
 
275
    /**
276
     * Checks if current user can edit activity completion
277
     *
278
     * @param int|stdClass $courseorid
279
     * @param \cm_info|null $cm if specified capability for a given coursemodule will be check,
280
     *     if not specified capability to edit at least one activity is checked.
281
     */
282
    public static function can_edit_bulk_completion($courseorid, $cm = null) {
283
        if ($cm) {
1441 ariadna 284
            if (!plugin_supports('mod', $cm->modname, FEATURE_COMPLETION, true)) {
285
                return false;
286
            }
1 efrain 287
            return $cm->uservisible && has_capability('moodle/course:manageactivities', $cm->context);
288
        }
289
        $coursecontext = context_course::instance(is_object($courseorid) ? $courseorid->id : $courseorid);
290
        if (has_capability('moodle/course:manageactivities', $coursecontext)) {
291
            return true;
292
        }
293
        $modinfo = get_fast_modinfo($courseorid);
294
        foreach ($modinfo->cms as $mod) {
295
            if ($mod->uservisible && has_capability('moodle/course:manageactivities', $mod->context)) {
296
                return true;
297
            }
298
        }
299
        return false;
300
    }
301
 
302
    /**
303
     * Returns an array with the available completion options (url => name) for the current course and user.
304
     *
305
     * @param int $courseid The course id.
306
     * @return array
307
     */
308
    public static function get_available_completion_options(int $courseid): array {
309
        $coursecontext = context_course::instance($courseid);
310
        $options = [];
311
 
312
        if (has_capability('moodle/course:update', $coursecontext)) {
313
            $completionlink = new moodle_url('/course/completion.php', ['id' => $courseid]);
314
            $options[$completionlink->out(false)] = get_string('coursecompletionsettings', 'completion');
315
        }
316
 
317
        if (has_capability('moodle/course:manageactivities', $coursecontext)) {
318
            $defaultcompletionlink = new moodle_url('/course/defaultcompletion.php', ['id' => $courseid]);
319
            $options[$defaultcompletionlink->out(false)] = get_string('defaultcompletion', 'completion');
320
        }
321
 
322
        if (self::can_edit_bulk_completion($courseid)) {
323
            $bulkcompletionlink = new moodle_url('/course/bulkcompletion.php', ['id' => $courseid]);
324
            $options[$bulkcompletionlink->out(false)] = get_string('bulkactivitycompletion', 'completion');
325
        }
326
 
327
        return $options;
328
    }
329
 
330
    /**
331
     * Applies completion from the bulk edit form to all selected modules
332
     *
333
     * @param stdClass $data data received from the core_completion_bulkedit_form
334
     * @param bool $updateinstances if we need to update the instance tables of the module (i.e. 'assign', 'forum', etc.) -
335
     *      if no module-specific completion rules were added to the form, update of the module table is not needed.
336
     */
337
    public function apply_completion($data, $updateinstances) {
338
        $updated = false;
339
        $needreset = [];
340
        $modinfo = get_fast_modinfo($this->courseid);
341
 
342
        $cmids = $data->cmid;
343
 
344
        $data = (array)$data;
345
        unset($data['id']); // This is a course id, we don't want to confuse it with cmid or instance id.
346
        unset($data['cmid']);
347
        unset($data['submitbutton']);
348
 
349
        foreach ($cmids as $cmid) {
350
            $cm = $modinfo->get_cm($cmid);
351
            if (self::can_edit_bulk_completion($this->courseid, $cm) && $this->apply_completion_cm($cm, $data, $updateinstances)) {
352
                $updated = true;
353
                if ($cm->completion != COMPLETION_TRACKING_MANUAL || $data['completion'] != COMPLETION_TRACKING_MANUAL) {
354
                    // If completion was changed we will need to reset it's state. Exception is when completion was and remains as manual.
355
                    $needreset[] = $cm->id;
356
                }
357
            }
358
            // Update completion calendar events.
359
            $completionexpected = ($data['completionexpected']) ? $data['completionexpected'] : null;
360
            \core_completion\api::update_completion_date_event($cm->id, $cm->modname, $cm->instance, $completionexpected);
361
        }
362
        if ($updated) {
363
            // Now that modules are fully updated, also update completion data if required.
364
            // This will wipe all user completion data and recalculate it.
365
            rebuild_course_cache($this->courseid, true);
366
            $modinfo = get_fast_modinfo($this->courseid);
367
            $completion = new \completion_info($modinfo->get_course());
368
            foreach ($needreset as $cmid) {
369
                $completion->reset_all_state($modinfo->get_cm($cmid));
370
            }
371
 
372
            // And notify the user of the result.
373
            \core\notification::add(get_string('activitycompletionupdated', 'core_completion'), \core\notification::SUCCESS);
374
        }
375
    }
376
 
377
    /**
378
     * Applies new completion rules to one course module
379
     *
380
     * @param \cm_info $cm
381
     * @param array $data
382
     * @param bool $updateinstance if we need to update the instance table of the module (i.e. 'assign', 'forum', etc.) -
383
     *      if no module-specific completion rules were added to the form, update of the module table is not needed.
384
     * @return bool if module was updated
385
     */
386
    protected function apply_completion_cm(\cm_info $cm, $data, $updateinstance) {
387
        global $DB;
388
 
389
        $defaults = [
390
            'completion' => COMPLETION_DISABLED, 'completionview' => COMPLETION_VIEW_NOT_REQUIRED,
391
            'completionexpected' => 0, 'completiongradeitemnumber' => null,
392
            'completionpassgrade' => 0
393
        ];
394
 
395
        $data += ['completion' => $cm->completion,
396
            'completionexpected' => $cm->completionexpected,
397
            'completionview' => $cm->completionview];
398
 
399
        if ($cm->completion == $data['completion'] && $cm->completion == COMPLETION_TRACKING_NONE) {
400
            // If old and new completion are both "none" - no changes are needed.
401
            return false;
402
        }
403
 
404
        if ($cm->completion == $data['completion'] && $cm->completion == COMPLETION_TRACKING_NONE &&
405
                $cm->completionexpected == $data['completionexpected']) {
406
            // If old and new completion are both "manual" and completion expected date is not changed - no changes are needed.
407
            return false;
408
        }
409
 
410
        if (array_key_exists('completionusegrade', $data)) {
411
            // Convert the 'use grade' checkbox into a grade-item number: 0 if checked, null if not.
412
            $data['completiongradeitemnumber'] = !empty($data['completionusegrade']) ? 0 : null;
413
            unset($data['completionusegrade']);
414
        } else {
415
            // Completion grade item number is classified in mod_edit forms as 'use grade'.
416
            $data['completionusegrade'] = is_null($cm->completiongradeitemnumber) ? 0 : 1;
417
            $data['completiongradeitemnumber'] = $cm->completiongradeitemnumber;
418
        }
419
 
420
        // Update module instance table.
421
        if ($updateinstance) {
422
            $moddata = ['id' => $cm->instance, 'timemodified' => time()] + array_diff_key($data, $defaults);
423
            $DB->update_record($cm->modname, $moddata);
424
        }
425
 
426
        // Update course modules table.
427
        $cmdata = ['id' => $cm->id, 'timemodified' => time()] + array_intersect_key($data, $defaults);
428
        $DB->update_record('course_modules', $cmdata);
429
 
430
        \core\event\course_module_updated::create_from_cm($cm, $cm->context)->trigger();
431
 
432
        // We need to reset completion data for this activity.
433
        return true;
434
    }
435
 
436
 
437
    /**
438
     * Saves default completion from edit form to all selected module types
439
     *
440
     * @param stdClass $data data received from the core_completion_bulkedit_form
441
     * @param bool $updatecustomrules if we need to update the custom rules of the module -
442
     *      if no module-specific completion rules were added to the form, update of the module table is not needed.
443
     * @param string $suffix the suffix to add to the name of the completion rules.
444
     */
445
    public function apply_default_completion($data, $updatecustomrules, string $suffix = '') {
446
        global $DB;
447
 
448
        if (!empty($suffix)) {
449
            // Fields were renamed to avoid conflicts, but they need to be stored in DB with the original name.
450
            $modules = property_exists($data, 'modules') ? $data->modules : null;
451
            if ($modules !== null) {
452
                unset($data->modules);
453
                $data = (array)$data;
454
                foreach ($data as $name => $value) {
455
                    if (str_ends_with($name, $suffix)) {
456
                        $data[substr($name, 0, strpos($name, $suffix))] = $value;
457
                        unset($data[$name]);
458
                    } else if ($name == 'customdata') {
459
                        $customrules = $value['customcompletionrules'];
460
                        foreach ($customrules as $rulename => $rulevalue) {
461
                            if (str_ends_with($rulename, $suffix)) {
462
                                $customrules[substr($rulename, 0, strpos($rulename, $suffix))] = $rulevalue;
463
                                unset($customrules[$rulename]);
464
                            }
465
                        }
466
                        $data['customdata'] = $customrules;
467
                    }
468
                }
469
                $data = (object)$data;
470
            }
471
        }
472
 
473
        $courseid = $data->id;
474
        // MDL-72375 Unset the id here, it should not be stored in customrules.
475
        unset($data->id);
476
        $coursecontext = context_course::instance($courseid);
477
        if (!$modids = $data->modids) {
478
            return;
479
        }
480
        $defaults = [
481
            'completion' => COMPLETION_DISABLED,
482
            'completionview' => COMPLETION_VIEW_NOT_REQUIRED,
483
            'completionexpected' => 0,
484
            'completionusegrade' => 0,
485
            'completionpassgrade' => 0
486
        ];
487
 
488
        $data = (array)$data;
489
        if (!array_key_exists('completionusegrade', $data)) {
490
            $data['completionusegrade'] = 0;
491
        }
492
        if (!array_key_exists('completionpassgrade', $data)) {
493
            $data['completionpassgrade'] = 0;
494
        }
495
        if ($data['completionusegrade'] == 0) {
496
            $data['completionpassgrade'] = 0;
497
        }
498
 
499
        if ($updatecustomrules) {
500
            $customdata = array_diff_key($data, $defaults);
501
            $data['customrules'] = $customdata ? json_encode($customdata) : null;
502
            $defaults['customrules'] = null;
503
        }
504
        $data = array_merge($defaults, $data);
505
 
506
        // Get names of the affected modules.
507
        list($modidssql, $params) = $DB->get_in_or_equal($modids);
508
        $params[] = 1;
509
        $modules = $DB->get_records_select_menu('modules', 'id ' . $modidssql . ' and visible = ?', $params, '', 'id, name');
510
 
511
        // Get an associative array of [module_id => course_completion_defaults_id].
512
        list($in, $params) = $DB->get_in_or_equal($modids);
513
        $params[] = $courseid;
514
        $defaultsids = $DB->get_records_select_menu('course_completion_defaults', 'module ' . $in . ' and course = ?', $params, '',
515
                                                      'module, id');
516
 
517
        foreach ($modids as $modid) {
518
            if (!array_key_exists($modid, $modules)) {
519
                continue;
520
            }
521
            if (isset($defaultsids[$modid])) {
522
                $DB->update_record('course_completion_defaults', $data + ['id' => $defaultsids[$modid]]);
523
            } else {
524
                $defaultsids[$modid] = $DB->insert_record('course_completion_defaults', $data + ['course' => $courseid,
525
                                                                                                 'module' => $modid]);
526
            }
527
            // Trigger event.
528
            \core\event\completion_defaults_updated::create([
529
                'objectid' => $defaultsids[$modid],
530
                'context' => $coursecontext,
531
                'other' => ['modulename' => $modules[$modid]],
532
            ])->trigger();
533
        }
534
 
535
        // Add notification.
536
        \core\notification::add(get_string('defaultcompletionupdated', 'completion'), \core\notification::SUCCESS);
537
    }
538
 
539
    /**
540
     * Returns default completion rules for given module type in the given course
541
     *
542
     * @param stdClass $course
543
     * @param stdClass $module
544
     * @param bool $flatten if true all module custom completion rules become properties of the same object,
545
     *   otherwise they can be found as array in ->customdata['customcompletionrules']
546
     * @param string $suffix the suffix to add to the name of the completion rules.
547
     * @return stdClass
548
     */
549
    public static function get_default_completion($course, $module, $flatten = true, string $suffix = '') {
550
        global $DB, $CFG, $SITE;
551
 
552
        $fields = 'completion, completionview, completionexpected, completionusegrade, completionpassgrade, customrules';
553
        // Check course default completion values.
554
        $params = ['course' => $course->id, 'module' => $module->id];
555
        $data = $DB->get_record('course_completion_defaults', $params, $fields);
556
        if (!$data && $course->id != $SITE->id) {
557
            // If there is no course default completion, check site level default completion values ($SITE->id).
558
            $params['course'] = $SITE->id;
559
            $data = $DB->get_record('course_completion_defaults', $params, $fields);
560
        }
561
        if ($data) {
562
            if ($data->customrules && ($customrules = @json_decode($data->customrules, true))) {
563
                // MDL-72375 This will override activity id for new mods. Skip this field, it is already exposed as courseid.
564
                unset($customrules['id']);
565
 
566
                if ($flatten) {
567
                    foreach ($customrules as $key => $value) {
568
                        $data->$key = $value;
569
                    }
570
                } else {
571
                    $data->customdata['customcompletionrules'] = $customrules;
572
                }
573
            }
574
            unset($data->customrules);
575
        } else {
576
            $data = new stdClass();
577
            $data->completion = COMPLETION_TRACKING_NONE;
578
        }
579
 
580
        // If the suffix is not empty, the completion rules need to be renamed to avoid conflicts.
581
        if (!empty($suffix)) {
582
            $data = (array)$data;
583
            foreach ($data as $name => $value) {
584
                if (str_starts_with($name, 'completion')) {
585
                    $data[$name . $suffix] = $value;
586
                    unset($data[$name]);
587
                } else if ($name == 'customdata') {
588
                    $customrules = $value['customcompletionrules'];
589
                    foreach ($customrules as $rulename => $rulevalue) {
590
                        if (str_starts_with($rulename, 'completion')) {
591
                            $customrules[$rulename . $suffix] = $rulevalue;
592
                            unset($customrules[$rulename]);
593
                        }
594
                    }
595
                    $data['customdata'] = $customrules;
596
                }
597
            }
598
            $data = (object)$data;
599
        }
600
 
601
        return $data;
602
    }
603
 
604
    /**
605
     * Return a mod_form of the given module.
606
     *
607
     * @param string $modname   Module to get the form from.
608
     * @param stdClass $course  Course object.
609
     * @param ?cm_info $cm      cm_info object to use.
610
     * @param string $suffix    The suffix to add to the name of the completion rules.
611
     * @return ?\moodleform_mod The moodleform_mod object if everything goes fine. Null otherwise.
612
     */
613
    public static function get_module_form(
614
            string $modname,
615
            stdClass $course,
616
            ?cm_info $cm = null,
617
            string $suffix = ''
618
    ): ?\moodleform_mod {
619
        global $CFG, $PAGE;
620
 
621
        $modmoodleform = "$CFG->dirroot/mod/$modname/mod_form.php";
622
        if (file_exists($modmoodleform)) {
623
            require_once($modmoodleform);
624
        } else {
625
            throw new \moodle_exception('noformdesc');
626
        }
627
 
628
        if ($cm) {
629
            [$cmrec, $context, $module, $data, $cw] = get_moduleinfo_data($cm, $course);
630
            $data->update = $modname;
631
        } else {
632
            [$module, $context, $cw, $cmrec, $data] = prepare_new_moduleinfo_data($course, $modname, 0, $suffix);
633
            $data->add = $modname;
634
        }
635
        $data->return = 0;
636
        $data->sr = 0;
637
 
638
        // Initialise the form but discard all JS requirements it adds, our form has already added them.
639
        $mformclassname = 'mod_'.$modname.'_mod_form';
640
        $PAGE->start_collecting_javascript_requirements();
641
        try {
642
            $moduleform = new $mformclassname($data, 0, $cmrec, $course);
643
            if (!$cm) {
644
                $moduleform->set_suffix('_' . $modname);
645
            }
646
        } catch (\Exception $e) {
647
            // The form class has thrown an error when instantiating.
648
            // This could happen because some conditions for the module are not met.
649
            $moduleform = null;
650
        } finally {
651
            $PAGE->end_collecting_javascript_requirements();
652
        }
653
 
654
        return $moduleform;
655
    }
656
}