Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | 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
namespace core_completion\form;
18
 
19
use core_grades\component_gradeitems;
20
use cm_info;
21
 
22
/**
23
 * Completion trait helper, with methods to add completion elements and validate them.
24
 *
25
 * @package    core_completion
26
 * @since      Moodle 4.3
27
 * @copyright  2023 Sara Arjona (sara@moodle.com)
28
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29
 */
30
trait form_trait {
31
 
32
    /** @var string The suffix to be added to the completion elements when creating them (for example, 'completion_assign'). */
33
    protected $suffix = '';
34
 
35
    /**
36
     * Called during validation.
37
     * Override this method to indicate, based on the data, whether a custom completion rule is selected or not.
38
     *
39
     * @param array $data Input data (not yet validated)
40
     * @return bool True if one or more rules are enabled; false if none are.
41
     */
42
    abstract protected function completion_rule_enabled($data);
43
 
44
    /**
45
     * Add completion elements to the form and return the list of element ids.
46
     *
47
     * @return array Array of string IDs of added items, empty array if none
48
     */
49
    abstract protected function add_completion_rules();
50
 
51
    /**
52
     * Get the form associated to this class, where the completion elements will be added.
53
     * This method must be overriden by the class using this trait if it doesn't include a _form property.
54
     *
55
     * @return \MoodleQuickForm
56
     * @throws \coding_exception If the class does not have a _form property.
57
     */
58
    protected function get_form(): \MoodleQuickForm {
59
        if (property_exists($this, '_form')) {
60
            return $this->_form;
61
        }
62
 
63
        throw new \coding_exception('This class does not have a _form property. Please, add it or override the get_form() method.');
64
    }
65
 
66
    /**
67
     * Set the suffix to be added to the completion elements when creating them (for example, 'completion_assign').
68
     *
69
     * @param string $suffix
70
     */
71
    public function set_suffix(string $suffix): void {
72
        $this->suffix = $suffix;
73
    }
74
 
75
    /**
76
     * Get the suffix to be added to the completion elements when creating them (for example, 'completion_assign').
77
     *
78
     * @return string The suffix
79
     */
80
    public function get_suffix(): string {
81
        return $this->suffix;
82
    }
83
 
84
    /**
85
     * Add completion elements to the form.
86
     *
87
     * @param string|null $modname The module name (for example, 'assign'). If null and form is moodleform_mod, the parameters are
88
     *                             overriden with the expected values from the form.
89
     * @param bool $supportviews True if the module supports views and false otherwise.
90
     * @param bool $supportgrades True if the module supports grades and false otherwise.
91
     * @param bool $rating True if the rating feature is enabled and false otherwise.
92
     * @param int|null $courseid Course where to add completion elements.
93
     * @throws \coding_exception If the form is not moodleform_mod and $modname is null.
94
     */
95
    protected function add_completion_elements(
96
        string $modname = null,
97
        bool $supportviews = false,
98
        bool $supportgrades = false,
99
        bool $rating = false,
100
        ?int $courseid = null
101
    ): void {
102
        global $SITE;
103
 
104
        $mform = $this->get_form();
105
        if ($modname === null) {
106
            if ($this instanceof \moodleform_mod) {
107
                // By default, all the modules can be initiatized with the same parameters.
108
                $modname = $this->_modname;
109
                $supportviews = plugin_supports('mod', $modname, FEATURE_COMPLETION_TRACKS_VIEWS, false);
110
                $supportgrades = plugin_supports('mod', $modname, FEATURE_GRADE_HAS_GRADE, false);
111
                $rating = $this->_features->rating;
112
            } else {
113
                throw new \coding_exception('You must specify the modname parameter if you are not using a moodleform_mod.');
114
            }
115
        }
116
 
117
        // Unlock button if people have completed it. The button will be removed later in definition_after_data if they haven't.
118
        // The unlock buttons don't need suffix because they are only displayed in the module settings page.
119
        $mform->addElement('submit', 'unlockcompletion', get_string('unlockcompletion', 'completion'));
120
        $mform->registerNoSubmitButton('unlockcompletion');
121
        $mform->addElement('hidden', 'completionunlocked', 0);
122
        $mform->setType('completionunlocked', PARAM_INT);
123
 
124
        $trackingdefault = COMPLETION_TRACKING_NONE;
125
 
126
        // Get the sufix to add to the completion elements name.
127
        $suffix = $this->get_suffix();
128
 
129
        $completionel = 'completion' . $suffix;
130
        $mform->addElement(
131
            'radio',
132
            $completionel,
133
            '',
134
            get_string('completion_none', 'completion'),
135
            COMPLETION_TRACKING_NONE,
136
            ['class' => 'left-indented']
137
        );
138
        $mform->addElement(
139
            'radio',
140
            $completionel,
141
            '',
142
            get_string('completion_manual', 'completion'),
143
            COMPLETION_TRACKING_MANUAL,
144
            ['class' => 'left-indented']
145
        );
146
 
147
        $allconditionsel = 'allconditions' . $suffix;
148
        $allconditions = $mform->createElement(
149
            'static',
150
            $allconditionsel,
151
            '',
152
            get_string('allconditions', 'completion'));
153
 
154
        $conditionsgroupel = 'conditionsgroup' . $suffix;
155
        $mform->addGroup([$allconditions], $conditionsgroupel, '', null, false);
156
        $mform->hideIf($conditionsgroupel, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC);
157
 
158
        $mform->setType($completionel, PARAM_INT);
159
        $mform->setDefault($completionel, COMPLETION_TRACKING_NONE);
160
 
161
        // Automatic completion once you view it.
162
        if ($supportviews) {
163
            $completionviewel = 'completionview' . $suffix;
164
            $mform->addElement(
165
                'checkbox',
166
                $completionviewel,
167
                '',
168
                get_string('completionview_desc', 'completion')
169
            );
170
            $mform->hideIf($completionviewel, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC);
171
            // Check by default if automatic completion tracking is set.
172
            if ($trackingdefault == COMPLETION_TRACKING_AUTOMATIC) {
173
                $mform->setDefault($completionviewel, 1);
174
            }
175
        }
176
 
177
        // Automatic completion according to module-specific rules.
178
        $customcompletionelements = $this->add_completion_rules();
179
        if (property_exists($this, '_customcompletionelements')) {
180
            $this->_customcompletionelements = $customcompletionelements;
181
        }
182
 
183
        if ($customcompletionelements !== null) {
184
            foreach ($customcompletionelements as $element) {
185
                $mform->hideIf($element, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC);
186
            }
187
        }
188
 
189
        // If the activity supports grading, the grade elements must be added.
190
        if ($supportgrades) {
191
            $this->add_completiongrade_elements($modname, $rating);
192
        }
193
 
194
        $autocompletionpossible = $supportviews || $supportgrades || (count($customcompletionelements) > 0);
195
 
196
        // Automatic option only appears if possible.
197
        if ($autocompletionpossible) {
198
            $automatic = $mform->createElement(
199
                'radio',
200
                $completionel,
201
                '',
202
                get_string('completion_automatic', 'completion'),
203
                COMPLETION_TRACKING_AUTOMATIC,
204
                ['class' => 'left-indented']
205
            );
206
            $mform->insertElementBefore($automatic, $conditionsgroupel);
207
        }
208
 
209
        // Completion expected at particular date? (For progress tracking).
210
        // We don't show completion expected at site level default completion.
211
        if ($courseid != $SITE->id) {
212
            $completionexpectedel = 'completionexpected' . $suffix;
213
            $mform->addElement('date_time_selector', $completionexpectedel, get_string('completionexpected', 'completion'),
214
                ['optional' => true]);
215
            $a = get_string('pluginname', $modname);
216
            $mform->addHelpButton($completionexpectedel, 'completionexpected', 'completion', '', false, $a);
217
            $mform->hideIf($completionexpectedel, $completionel, 'eq', COMPLETION_TRACKING_NONE);
218
        }
219
    }
220
 
221
    /**
222
     * Add completion grade elements to the form.
223
     *
224
     * @param string $modname The name of the module (for example, 'assign').
225
     * @param bool $rating True if the rating feature is enabled and false otherwise.
226
     */
227
    protected function add_completiongrade_elements(
228
        string $modname,
229
        bool $rating = false
230
    ): void {
231
        $mform = $this->get_form();
232
 
233
        // Get the sufix to add to the completion elements name.
234
        $suffix = $this->get_suffix();
235
 
236
        $completionel = 'completion' . $suffix;
237
        $completionelementexists = $mform->elementExists($completionel);
238
        $component = "mod_{$modname}";
239
        $itemnames = component_gradeitems::get_itemname_mapping_for_component($component);
240
 
241
        $indentation = ['parentclass' => 'ml-2'];
242
        $receiveagradeel = 'receiveagrade' . $suffix;
243
        $completionusegradeel = 'completionusegrade' . $suffix;
244
        $completionpassgradeel = 'completionpassgrade' . $suffix;
245
 
246
        if (count($itemnames) === 1) {
247
            // Only one gradeitem in this activity.
248
            // We use the completionusegrade field here.
249
            $mform->addElement(
250
                'checkbox',
251
                $completionusegradeel,
252
                '',
253
                get_string('completionusegrade_desc', 'completion')
254
            );
255
 
256
            // Complete if the user has reached any grade.
257
            $mform->addElement(
258
                'radio',
259
                $completionpassgradeel,
260
                null,
261
                get_string('completionanygrade_desc', 'completion'),
262
                0,
263
                $indentation
264
            );
265
 
266
            // Complete if the user has reached the pass grade.
267
            $mform->addElement(
268
                'radio',
269
                $completionpassgradeel,
270
                null,
271
                get_string('completionpassgrade_desc', 'completion'),
272
                1,
273
                $indentation
274
            );
275
            $mform->hideIf($completionpassgradeel, $completionusegradeel, 'notchecked');
276
 
277
            if ($completionelementexists) {
278
                $mform->hideIf($completionpassgradeel, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC);
279
                $mform->hideIf($completionusegradeel, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC);
280
            }
281
 
282
            // The disabledIf logic differs between ratings and other grade items due to different field types.
283
            if ($rating) {
284
                // If using the rating system, there is no grade unless ratings are enabled.
285
                $mform->hideIf($completionusegradeel, 'assessed', 'eq', 0);
286
                $mform->hideIf($completionusegradeel, 'assessed', 'eq', 0);
287
            } else {
288
                // All other field types use the '$gradefieldname' field's modgrade_type.
289
                $itemnumbers = array_keys($itemnames);
290
                $itemnumber = array_shift($itemnumbers);
291
                $gradefieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'grade');
292
                $mform->hideIf($completionusegradeel, "{$gradefieldname}[modgrade_type]", 'eq', 'none');
293
                $mform->hideIf($completionusegradeel, "{$gradefieldname}[modgrade_type]", 'eq', 'none');
294
            }
295
        } else if (count($itemnames) > 1) {
296
            // There are multiple grade items in this activity.
297
            // Show them all.
298
            $options = [];
299
            foreach ($itemnames as $itemnumber => $itemname) {
300
                $options[$itemnumber] = get_string("grade_{$itemname}_name", $component);
301
            }
302
 
303
            $group = [$mform->createElement(
304
                'checkbox',
305
                $completionusegradeel,
306
                null,
307
                get_string('completionusegrade_desc', 'completion')
308
            )];
309
            $completiongradeitemnumberel = 'completiongradeitemnumber' . $suffix;
310
            $group[] =& $mform->createElement(
311
                'select',
312
                $completiongradeitemnumberel,
313
                '',
314
                $options
315
            );
316
            $receiveagradegroupel = 'receiveagradegroup' . $suffix;
317
            $mform->addGroup($group, $receiveagradegroupel, '', [' '], false);
318
            if ($completionelementexists) {
319
                $mform->hideIf($completionusegradeel, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC);
320
                $mform->hideIf($receiveagradegroupel, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC);
321
            }
322
            $mform->hideIf($completiongradeitemnumberel, $completionusegradeel, 'notchecked');
323
 
324
            // Complete if the user has reached any grade.
325
            $mform->addElement(
326
                'radio',
327
                $completionpassgradeel,
328
                null,
329
                get_string('completionanygrade_desc', 'completion'),
330
                0,
331
                $indentation
332
            );
333
            // Complete if the user has reached the pass grade.
334
            $mform->addElement(
335
                'radio',
336
                $completionpassgradeel,
337
                null,
338
                get_string('completionpassgrade_desc', 'completion'),
339
                1,
340
                $indentation
341
            );
342
            $mform->hideIf($completionpassgradeel, $completionusegradeel, 'notchecked');
343
 
344
            if ($completionelementexists) {
345
                $mform->hideIf($completiongradeitemnumberel, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC);
346
                $mform->hideIf($completionpassgradeel, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC);
347
            }
348
        }
349
 
350
        $customgradingelements = $this->add_completiongrade_rules();
351
        if (property_exists($this, '_customcompletionelements')) {
352
            $this->_customcompletionelements = array_merge($this->_customcompletionelements, $customgradingelements);
353
        }
354
        if ($completionelementexists) {
355
            foreach ($customgradingelements as $customgradingelement) {
356
                $mform->hideIf($customgradingelement, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC);
357
            }
358
        }
359
    }
360
 
361
    /**
362
     * Add completion grading elements to the form and return the list of element ids.
363
     *
364
     * @return array Array of string IDs of added items, empty array if none
365
     */
366
    abstract public function add_completiongrade_rules(): array;
367
 
368
    /**
369
     * Perform some extra validation for completion settings.
370
     *
371
     * @param array $data Array of ["fieldname" => value] of submitted data.
372
     * @return array List of ["element_name" => "error_description"] if there are errors or an empty array if everything is OK.
373
     */
374
    protected function validate_completion(array $data): array {
375
        $errors = [];
376
 
377
        // Get the sufix to add to the completion elements name.
378
        $suffix = $this->get_suffix();
379
 
380
        $completionel = 'completion' . $suffix;
381
        // Completion: Don't let them choose automatic completion without turning on some conditions.
382
        $automaticcompletion = array_key_exists($completionel, $data) && $data[$completionel] == COMPLETION_TRACKING_AUTOMATIC;
383
        // Ignore this check when completion settings are locked, as the options are then disabled.
384
        // The unlock buttons don't need suffix because they are only displayed in the module settings page.
385
        $automaticcompletion = $automaticcompletion && !empty($data['completionunlocked']);
386
        if ($automaticcompletion) {
387
            // View to complete.
388
            $completionviewel = 'completionview' . $suffix;
389
            $rulesenabled = !empty($data[$completionviewel]);
390
 
391
            // Use grade to complete (only one grade item).
392
            $completionusegradeel = 'completionusegrade' . $suffix;
393
            $completionpassgradeel = 'completionpassgrade' . $suffix;
394
            $rulesenabled = $rulesenabled || !empty($data[$completionusegradeel]) || !empty($data[$completionpassgradeel]);
395
 
396
            // Use grade to complete (specific grade item).
397
            $completiongradeitemnumberel = 'completiongradeitemnumber' . $suffix;
398
            if (!$rulesenabled && isset($data[$completiongradeitemnumberel])) {
399
                $rulesenabled = $data[$completiongradeitemnumberel] != '';
400
            }
401
 
402
            // Module-specific completion rules.
403
            $rulesenabled = $rulesenabled || $this->completion_rule_enabled($data);
404
 
405
            if (!$rulesenabled) {
406
                // No rules are enabled. Can't set automatically completed without rules.
407
                $errors[$completionel] = get_string('badautocompletion', 'completion');
408
            }
409
        }
410
 
411
        return $errors;
412
    }
413
 
414
    /**
415
     * It should be called from the definition_after_data() to setup the completion settings in the form.
416
     *
417
     * @param cm_info|null $cm The course module associated to this form.
418
     */
419
    protected function definition_after_data_completion(?cm_info $cm = null): void {
420
        global $COURSE, $SITE;
421
        $mform = $this->get_form();
422
 
423
        $completion = new \completion_info($COURSE);
424
        // We use $SITE course for site default activity completion,
425
        // so users could set default values regardless of whether completion is enabled or not.".
426
        if ($completion->is_enabled() || $COURSE->id == $SITE->id) {
427
            $suffix = $this->get_suffix();
428
 
429
            // If anybody has completed the activity, these options will be 'locked'.
430
            // We use $SITE course for site default activity completion, so we don't need any unlock button.
431
            $completedcount = (empty($cm) || $COURSE->id == $SITE->id) ? 0 : $completion->count_user_data($cm);
432
            $freeze = false;
433
            if (!$completedcount) {
434
                // The unlock buttons don't need suffix because they are only displayed in the module settings page.
435
                if ($mform->elementExists('unlockcompletion')) {
436
                    $mform->removeElement('unlockcompletion');
437
                }
438
                // Automatically set to unlocked. Note: this is necessary in order to make it recalculate completion once
439
                // the option is changed, maybe someone has completed it now.
440
                if ($mform->elementExists('completionunlocked')) {
441
                    $mform->getElement('completionunlocked')->setValue(1);
442
                }
443
            } else {
444
                // Has the element been unlocked, either by the button being pressed in this request, or the field already
445
                // being set from a previous one?
446
                if ($mform->exportValue('unlockcompletion') || $mform->exportValue('completionunlocked')) {
447
                    // Yes, add in warning text and set the hidden variable.
448
                    $completedunlockedel = $mform->createElement(
449
                        'static',
450
                        'completedunlocked',
451
                        get_string('completedunlocked', 'completion'),
452
                        get_string('completedunlockedtext', 'completion')
453
                    );
454
                    $mform->insertElementBefore($completedunlockedel, 'unlockcompletion');
455
                    $mform->removeElement('unlockcompletion');
456
                    $mform->getElement('completionunlocked')->setValue(1);
457
                } else {
458
                    // No, add in the warning text with the count (now we know it) before the unlock button.
459
                    $completedwarningel = $mform->createElement(
460
                        'static',
461
                        'completedwarning',
462
                        get_string('completedwarning', 'completion'),
463
                        get_string('completedwarningtext', 'completion', $completedcount)
464
                    );
465
                    $mform->insertElementBefore($completedwarningel, 'unlockcompletion');
466
                    $freeze = true;
467
                }
468
            }
469
 
470
            if ($freeze) {
471
                $completionel = 'completion' . $suffix;
472
                $mform->freeze($completionel);
473
                $completionviewel = 'completionview' . $suffix;
474
                if ($mform->elementExists($completionviewel)) {
475
                    // Don't use hardFreeze or checkbox value gets lost.
476
                    $mform->freeze($completionviewel);
477
                }
478
                $completionusegradeel = 'completionusegrade' . $suffix;
479
                if ($mform->elementExists($completionusegradeel)) {
480
                    $mform->freeze($completionusegradeel);
481
                }
482
                $completionpassgradeel = 'completionpassgrade' . $suffix;
483
                if ($mform->elementExists($completionpassgradeel)) {
484
                    $mform->freeze($completionpassgradeel);
485
 
486
                    // Has the completion pass grade completion criteria been set? If it has, then we shouldn't change
487
                    // the gradepass field.
488
                    if ($mform->exportValue($completionpassgradeel)) {
489
                        $mform->freeze('gradepass');
490
                    }
491
                }
492
                $completiongradeitemnumberel = 'completiongradeitemnumber' . $suffix;
493
                if ($mform->elementExists($completiongradeitemnumberel)) {
494
                    $mform->freeze($completiongradeitemnumberel);
495
                }
496
                if (property_exists($this, '_customcompletionelements')) {
497
                    $mform->freeze($this->_customcompletionelements);
498
                }
499
            }
500
        }
501
    }
502
}