Proyectos de Subversion Moodle

Rev

| 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
 * Class for providing quiz settings, to make setting up quiz form manageable.
19
 *
20
 * To make sure there are no inconsistencies between data sets, run tests in tests/phpunit/settings_provider_test.php.
21
 *
22
 * @package    quizaccess_seb
23
 * @author     Luca Bösch <luca.boesch@bfh.ch>
24
 * @author     Andrew Madden <andrewmadden@catalyst-au.net>
25
 * @author     Dmitrii Metelkin <dmitriim@catalyst-au.net>
26
 * @copyright  2019 Catalyst IT
27
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28
 */
29
 
30
namespace quizaccess_seb;
31
 
32
use context_module;
33
use context_user;
34
use lang_string;
35
use stdClass;
36
use stored_file;
37
 
38
defined('MOODLE_INTERNAL') || die();
39
 
40
/**
41
 * Helper class for providing quiz settings, to make setting up quiz form manageable.
42
 *
43
 * @copyright  2020 Catalyst IT
44
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
45
 */
46
class settings_provider {
47
 
48
    /**
49
     * No SEB should be used.
50
     */
51
    const USE_SEB_NO = 0;
52
 
53
    /**
54
     * Use SEB and configure it manually.
55
     */
56
    const USE_SEB_CONFIG_MANUALLY = 1;
57
 
58
    /**
59
     * Use SEB config from pre configured template.
60
     */
61
    const USE_SEB_TEMPLATE = 2;
62
 
63
    /**
64
     * Use SEB config from uploaded config file.
65
     */
66
    const USE_SEB_UPLOAD_CONFIG = 3;
67
 
68
    /**
69
     * Use client config. Not SEB config is required.
70
     */
71
    const USE_SEB_CLIENT_CONFIG = 4;
72
 
73
    /**
74
     * Insert form element.
75
     *
76
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
77
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
78
     * @param \HTML_QuickForm_element $element Element to insert.
79
     * @param string $before Insert element before.
80
     */
81
    protected static function insert_element(\mod_quiz_mod_form $quizform,
82
                                             \MoodleQuickForm $mform, \HTML_QuickForm_element $element, $before = 'security') {
83
        $mform->insertElementBefore($element, $before);
84
    }
85
 
86
    /**
87
     * Remove element from the form.
88
     *
89
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
90
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
91
     * @param string $elementname Element name.
92
     */
93
    protected static function remove_element(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform, string  $elementname) {
94
        if ($mform->elementExists($elementname)) {
95
            $mform->removeElement($elementname);
96
            $mform->setDefault($elementname, null);
97
        }
98
    }
99
 
100
    /**
101
     * Add help button to the element.
102
     *
103
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
104
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
105
     * @param string $elementname Element name.
106
     */
107
    protected static function add_help_button(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform, string $elementname) {
108
        if ($mform->elementExists($elementname)) {
109
            $mform->addHelpButton($elementname, $elementname, 'quizaccess_seb');
110
        }
111
    }
112
 
113
    /**
114
     * Set default value for the element.
115
     *
116
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
117
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
118
     * @param string $elementname Element name.
119
     * @param mixed $value Default value.
120
     */
121
    protected static function set_default(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform, string  $elementname, $value) {
122
        $mform->setDefault($elementname, $value);
123
    }
124
 
125
    /**
126
     * Set element type.
127
     *
128
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
129
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
130
     * @param string $elementname Element name.
131
     * @param string $type Type of the form element.
132
     */
133
    protected static function set_type(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform, string $elementname, string $type) {
134
        $mform->setType($elementname, $type);
135
    }
136
 
137
    /**
138
     * Freeze form element.
139
     *
140
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
141
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
142
     * @param string $elementname Element name.
143
     */
144
    protected static function freeze_element(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform, string $elementname) {
145
        if ($mform->elementExists($elementname)) {
146
            $mform->freeze($elementname);
147
        }
148
    }
149
 
150
    /**
151
     * Add SEB header element to  the form.
152
     *
153
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
154
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
155
     */
156
    protected static function add_seb_header_element(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
157
        global  $OUTPUT;
158
 
159
        $element = $mform->createElement('header', 'seb', get_string('seb', 'quizaccess_seb'));
160
        self::insert_element($quizform, $mform, $element);
161
 
162
        // Display notification about locked settings.
163
        if (self::is_seb_settings_locked($quizform->get_instance())) {
164
            $notify = new \core\output\notification(
165
                get_string('settingsfrozen', 'quizaccess_seb'),
166
                \core\output\notification::NOTIFY_WARNING
167
            );
168
 
169
            $notifyelement = $mform->createElement('html', $OUTPUT->render($notify));
170
            self::insert_element($quizform, $mform, $notifyelement);
171
        }
172
 
173
        if (self::is_conflicting_permissions($quizform->get_context())) {
174
            $notify = new \core\output\notification(
175
                get_string('conflictingsettings', 'quizaccess_seb'),
176
                \core\output\notification::NOTIFY_WARNING
177
            );
178
 
179
            $notifyelement = $mform->createElement('html', $OUTPUT->render($notify));
180
            self::insert_element($quizform, $mform, $notifyelement);
181
        }
182
    }
183
 
184
    /**
185
     * Add SEB usage element with all available options.
186
     *
187
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
188
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
189
     */
190
    protected static function add_seb_usage_options(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
191
        $element = $mform->createElement(
192
            'select',
193
            'seb_requiresafeexambrowser',
194
            get_string('seb_requiresafeexambrowser', 'quizaccess_seb'),
195
            self::get_requiresafeexambrowser_options($quizform->get_context())
196
        );
197
 
198
        self::insert_element($quizform, $mform, $element);
199
        self::set_type($quizform, $mform, 'seb_requiresafeexambrowser', PARAM_INT);
200
        self::set_default($quizform, $mform, 'seb_requiresafeexambrowser', self::USE_SEB_NO);
201
        self::add_help_button($quizform, $mform, 'seb_requiresafeexambrowser');
202
 
203
        if (self::is_conflicting_permissions($quizform->get_context())) {
204
            self::freeze_element($quizform, $mform, 'seb_requiresafeexambrowser');
205
        }
206
    }
207
 
208
    /**
209
     * Add Templates element.
210
     *
211
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
212
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
213
     */
214
    protected static function add_seb_templates(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
215
        if (self::can_use_seb_template($quizform->get_context()) || self::is_conflicting_permissions($quizform->get_context())) {
216
            $element = $mform->createElement(
217
                'select',
218
                'seb_templateid',
219
                get_string('seb_templateid', 'quizaccess_seb'),
220
                self::get_template_options()
221
            );
222
        } else {
223
            $element = $mform->createElement('hidden', 'seb_templateid');
224
        }
225
 
226
        self::insert_element($quizform, $mform, $element);
227
        self::set_type($quizform, $mform, 'seb_templateid', PARAM_INT);
228
        self::set_default($quizform, $mform, 'seb_templateid', 0);
229
        self::add_help_button($quizform, $mform, 'seb_templateid');
230
 
231
        // In case if the user can't use templates, but the quiz is configured to use them,
232
        // we'd like to display template, but freeze it.
233
        if (self::is_conflicting_permissions($quizform->get_context())) {
234
            self::freeze_element($quizform, $mform, 'seb_templateid');
235
        }
236
    }
237
 
238
    /**
239
     * Add upload config file element.
240
     *
241
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
242
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
243
     */
244
    protected static function add_seb_config_file(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
245
        $itemid = 0;
246
 
247
        $draftitemid = 0;
248
        file_prepare_draft_area(
249
            $draftitemid,
250
            $quizform->get_context()->id,
251
            'quizaccess_seb',
252
            'filemanager_sebconfigfile',
253
            $itemid
254
        );
255
 
256
        if (self::can_upload_seb_file($quizform->get_context())) {
257
            $element = $mform->createElement(
258
                'filemanager',
259
                'filemanager_sebconfigfile',
260
                get_string('filemanager_sebconfigfile', 'quizaccess_seb'),
261
                null,
262
                self::get_filemanager_options()
263
            );
264
        } else {
265
            $element = $mform->createElement('hidden', 'filemanager_sebconfigfile');
266
        }
267
 
268
        self::insert_element($quizform, $mform, $element);
269
        self::set_type($quizform, $mform, 'filemanager_sebconfigfile', PARAM_RAW);
270
        self::set_default($quizform, $mform, 'filemanager_sebconfigfile', $draftitemid);
271
        self::add_help_button($quizform, $mform, 'filemanager_sebconfigfile');
272
    }
273
 
274
    /**
275
     * Add Show Safe Exam Browser download button.
276
     *
277
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
278
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
279
     */
280
    protected static function add_seb_show_download_link(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
281
        if (self::can_change_seb_showsebdownloadlink($quizform->get_context())) {
282
            $element = $mform->createElement('selectyesno',
283
                'seb_showsebdownloadlink',
284
                get_string('seb_showsebdownloadlink', 'quizaccess_seb')
285
            );
286
            self::insert_element($quizform, $mform, $element);
287
            self::set_type($quizform, $mform, 'seb_showsebdownloadlink', PARAM_BOOL);
288
            self::set_default($quizform, $mform, 'seb_showsebdownloadlink', 1);
289
            self::add_help_button($quizform, $mform, 'seb_showsebdownloadlink');
290
        }
291
    }
292
 
293
    /**
294
     * Add Allowed Browser Exam Keys setting.
295
     *
296
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
297
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
298
     */
299
    protected static function add_seb_allowedbrowserexamkeys(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
300
        if (self::can_change_seb_allowedbrowserexamkeys($quizform->get_context())) {
301
            $element = $mform->createElement('textarea',
302
                'seb_allowedbrowserexamkeys',
303
                get_string('seb_allowedbrowserexamkeys', 'quizaccess_seb')
304
            );
305
            self::insert_element($quizform, $mform, $element);
306
            self::set_type($quizform, $mform, 'seb_allowedbrowserexamkeys', PARAM_RAW);
307
            self::set_default($quizform, $mform, 'seb_allowedbrowserexamkeys', '');
308
            self::add_help_button($quizform, $mform, 'seb_allowedbrowserexamkeys');
309
        }
310
    }
311
 
312
    /**
313
     * Add SEB config elements.
314
     *
315
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
316
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
317
     */
318
    protected static function add_seb_config_elements(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
319
        $defaults = self::get_seb_config_element_defaults();
320
        $types = self::get_seb_config_element_types();
321
 
322
        foreach (self::get_seb_config_elements() as $name => $type) {
323
            if (!self::can_manage_seb_config_setting($name, $quizform->get_context())) {
324
                $type = 'hidden';
325
            }
326
 
327
            $element = $mform->createElement($type, $name, get_string($name, 'quizaccess_seb'));
328
            self::insert_element($quizform, $mform, $element);
329
            unset($element); // We need to make sure each &element only references the current element in loop.
330
 
331
            self::add_help_button($quizform, $mform, $name);
332
 
333
            if (isset($defaults[$name])) {
334
                self::set_default($quizform, $mform, $name, $defaults[$name]);
335
            }
336
 
337
            if (isset($types[$name])) {
338
                self::set_type($quizform, $mform, $name, $types[$name]);
339
            }
340
        }
341
    }
342
 
343
    /**
344
     * Add setting fields.
345
     *
346
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
347
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
348
     */
349
    public static function add_seb_settings_fields(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
350
        if (self::can_configure_seb($quizform->get_context())) {
351
            self::add_seb_header_element($quizform, $mform);
352
            self::add_seb_usage_options($quizform, $mform);
353
            self::add_seb_templates($quizform, $mform);
354
            self::add_seb_config_file($quizform, $mform);
355
            self::add_seb_show_download_link($quizform, $mform);
356
            self::add_seb_config_elements($quizform, $mform);
357
            self::add_seb_allowedbrowserexamkeys($quizform, $mform);
358
            self::hide_seb_elements($quizform, $mform);
359
            self::lock_seb_elements($quizform, $mform);
360
        }
361
    }
362
 
363
    /**
364
     * Hide SEB elements if required.
365
     *
366
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
367
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
368
     */
369
    protected static function hide_seb_elements(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
370
        foreach (self::get_quiz_hideifs() as $elname => $rules) {
371
            if ($mform->elementExists($elname)) {
372
                foreach ($rules as $hideif) {
373
                    $mform->hideIf(
374
                        $hideif->get_element(),
375
                        $hideif->get_dependantname(),
376
                        $hideif->get_condition(),
377
                        $hideif->get_dependantvalue()
378
                    );
379
                }
380
            }
381
        }
382
    }
383
 
384
    /**
385
     * Lock SEB elements if required.
386
     *
387
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
388
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
389
     */
390
    protected static function lock_seb_elements(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
391
        if (self::is_seb_settings_locked($quizform->get_instance()) || self::is_conflicting_permissions($quizform->get_context())) {
392
            // Freeze common quiz settings.
393
            self::freeze_element($quizform, $mform, 'seb_requiresafeexambrowser');
394
            self::freeze_element($quizform, $mform, 'seb_templateid');
395
            self::freeze_element($quizform, $mform, 'seb_showsebdownloadlink');
396
            self::freeze_element($quizform, $mform, 'seb_allowedbrowserexamkeys');
397
 
398
            $quizsettings = seb_quiz_settings::get_by_quiz_id((int) $quizform->get_instance());
399
 
400
            // If the file has been uploaded, then replace it with the link to download the file.
401
            if (!empty($quizsettings) && $quizsettings->get('requiresafeexambrowser') == self::USE_SEB_UPLOAD_CONFIG) {
402
                self::remove_element($quizform, $mform, 'filemanager_sebconfigfile');
403
                if ($link = self::get_uploaded_seb_file_download_link($quizform, $mform)) {
404
                    $element = $mform->createElement(
405
                        'static',
406
                        'filemanager_sebconfigfile',
407
                        get_string('filemanager_sebconfigfile', 'quizaccess_seb'),
408
                        $link
409
                    );
410
                    self::insert_element($quizform, $mform, $element, 'seb_showsebdownloadlink');
411
                }
412
            }
413
 
414
            // Remove template ID if not using template for this quiz.
415
            if (empty($quizsettings) || $quizsettings->get('requiresafeexambrowser') != self::USE_SEB_TEMPLATE) {
416
                $mform->removeElement('seb_templateid');
417
            }
418
 
419
            // Freeze all SEB specific settings.
420
            foreach (self::get_seb_config_elements() as $element => $type) {
421
                self::freeze_element($quizform, $mform, $element);
422
            }
423
        }
424
    }
425
 
426
    /**
427
     * Return uploaded SEB config file link.
428
     *
429
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
430
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
431
     * @return string
432
     */
433
    protected static function get_uploaded_seb_file_download_link(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform): string {
434
        $link = '';
435
        $file = self::get_module_context_sebconfig_file($quizform->get_coursemodule()->id);
436
 
437
        if ($file) {
438
            $url = \moodle_url::make_pluginfile_url(
439
                $file->get_contextid(),
440
                $file->get_component(),
441
                $file->get_filearea(),
442
                $file->get_itemid(),
443
                $file->get_filepath(),
444
                $file->get_filename(),
445
                true
446
            );
447
            $link = \html_writer::link($url, get_string('downloadsebconfig', 'quizaccess_seb'));
448
        }
449
 
450
        return $link;
451
    }
452
 
453
    /**
454
     * Get the type of element for each of the form elements in quiz settings.
455
     *
456
     * Contains all setting elements. Array key is name of 'form element'/'database column (excluding prefix)'.
457
     *
458
     * @return array All quiz form elements to be added and their types.
459
     */
460
    public static function get_seb_config_elements(): array {
461
        return [
462
            'seb_linkquitseb' => 'text',
463
            'seb_userconfirmquit' => 'selectyesno',
464
            'seb_allowuserquitseb' => 'selectyesno',
465
            'seb_quitpassword' => 'passwordunmask',
466
            'seb_allowreloadinexam' => 'selectyesno',
467
            'seb_showsebtaskbar' => 'selectyesno',
468
            'seb_showreloadbutton' => 'selectyesno',
469
            'seb_showtime' => 'selectyesno',
470
            'seb_showkeyboardlayout' => 'selectyesno',
471
            'seb_showwificontrol' => 'selectyesno',
472
            'seb_enableaudiocontrol' => 'selectyesno',
473
            'seb_muteonstartup' => 'selectyesno',
474
            'seb_allowspellchecking' => 'selectyesno',
475
            'seb_activateurlfiltering' => 'selectyesno',
476
            'seb_filterembeddedcontent' => 'selectyesno',
477
            'seb_expressionsallowed' => 'textarea',
478
            'seb_regexallowed' => 'textarea',
479
            'seb_expressionsblocked' => 'textarea',
480
            'seb_regexblocked' => 'textarea',
481
        ];
482
    }
483
 
484
 
485
    /**
486
     * Get the types of the quiz settings elements.
487
     * @return array List of types for the setting elements.
488
     */
489
    public static function get_seb_config_element_types(): array {
490
        return [
491
            'seb_linkquitseb' => PARAM_RAW,
492
            'seb_userconfirmquit' => PARAM_BOOL,
493
            'seb_allowuserquitseb' => PARAM_BOOL,
494
            'seb_quitpassword' => PARAM_RAW,
495
            'seb_allowreloadinexam' => PARAM_BOOL,
496
            'seb_showsebtaskbar' => PARAM_BOOL,
497
            'seb_showreloadbutton' => PARAM_BOOL,
498
            'seb_showtime' => PARAM_BOOL,
499
            'seb_showkeyboardlayout' => PARAM_BOOL,
500
            'seb_showwificontrol' => PARAM_BOOL,
501
            'seb_enableaudiocontrol' => PARAM_BOOL,
502
            'seb_muteonstartup' => PARAM_BOOL,
503
            'seb_allowspellchecking' => PARAM_BOOL,
504
            'seb_activateurlfiltering' => PARAM_BOOL,
505
            'seb_filterembeddedcontent' => PARAM_BOOL,
506
            'seb_expressionsallowed' => PARAM_RAW,
507
            'seb_regexallowed' => PARAM_RAW,
508
            'seb_expressionsblocked' => PARAM_RAW,
509
            'seb_regexblocked' => PARAM_RAW,
510
        ];
511
    }
512
 
513
    /**
514
     * Check that we have conflicting permissions.
515
     *
516
     * In Some point we can have settings save by the person who use specific
517
     * type of SEB usage (e.g. use templates). But then another person who can't
518
     * use template (but still can update other settings) edit the same quiz. This is
519
     * conflict of permissions and we'd like to build the settings form having this in
520
     * mind.
521
     *
522
     * @param \context $context Context used with capability checking.
523
     *
524
     * @return bool
525
     */
526
    public static function is_conflicting_permissions(\context $context) {
527
        if ($context instanceof \context_course) {
528
            return false;
529
        }
530
 
531
        $settings = seb_quiz_settings::get_record(['cmid' => (int) $context->instanceid]);
532
 
533
        if (empty($settings)) {
534
            return false;
535
        }
536
 
537
        if (!self::can_use_seb_template($context) &&
538
            $settings->get('requiresafeexambrowser') == self::USE_SEB_TEMPLATE) {
539
            return true;
540
        }
541
 
542
        if (!self::can_upload_seb_file($context) &&
543
            $settings->get('requiresafeexambrowser') == self::USE_SEB_UPLOAD_CONFIG) {
544
            return true;
545
        }
546
 
547
        if (!self::can_configure_manually($context) &&
548
            $settings->get('requiresafeexambrowser') == self::USE_SEB_CONFIG_MANUALLY) {
549
            return true;
550
        }
551
 
552
        return false;
553
    }
554
 
555
    /**
556
     * Returns a list of all options of SEB usage.
557
     *
558
     * @param \context $context Context used with capability checking selection options.
559
     * @return array
560
     */
561
    public static function get_requiresafeexambrowser_options(\context $context): array {
562
        $options[self::USE_SEB_NO] = get_string('no');
563
 
564
        if (self::can_configure_manually($context) || self::is_conflicting_permissions($context)) {
565
            $options[self::USE_SEB_CONFIG_MANUALLY] = get_string('seb_use_manually', 'quizaccess_seb');
566
        }
567
 
568
        if (self::can_use_seb_template($context) || self::is_conflicting_permissions($context)) {
569
            if (!empty(self::get_template_options())) {
570
                $options[self::USE_SEB_TEMPLATE] = get_string('seb_use_template', 'quizaccess_seb');
571
            }
572
        }
573
 
574
        if (self::can_upload_seb_file($context) || self::is_conflicting_permissions($context)) {
575
            $options[self::USE_SEB_UPLOAD_CONFIG] = get_string('seb_use_upload', 'quizaccess_seb');
576
        }
577
 
578
        $options[self::USE_SEB_CLIENT_CONFIG] = get_string('seb_use_client', 'quizaccess_seb');
579
 
580
        return $options;
581
    }
582
 
583
    /**
584
     * Returns a list of templates.
585
     * @return array
586
     */
587
    protected static function get_template_options(): array {
588
        $templates = [];
589
        $records = template::get_records(['enabled' => 1], 'name');
590
        if ($records) {
591
            foreach ($records as $record) {
592
                $templates[$record->get('id')] = $record->get('name');
593
            }
594
        }
595
 
596
        return $templates;
597
    }
598
 
599
    /**
600
     * Returns a list of options for the file manager element.
601
     * @return array
602
     */
603
    public static function get_filemanager_options(): array {
604
        return [
605
            'subdirs' => 0,
606
            'maxfiles' => 1,
607
            'accepted_types' => ['.seb']
608
        ];
609
    }
610
 
611
    /**
612
     * Get the default values of the quiz settings.
613
     *
614
     * Array key is name of 'form element'/'database column (excluding prefix)'.
615
     *
616
     * @return array List of settings and their defaults.
617
     */
618
    public static function get_seb_config_element_defaults(): array {
619
        return [
620
            'seb_linkquitseb' => '',
621
            'seb_userconfirmquit' => 1,
622
            'seb_allowuserquitseb' => 1,
623
            'seb_quitpassword' => '',
624
            'seb_allowreloadinexam' => 1,
625
            'seb_showsebtaskbar' => 1,
626
            'seb_showreloadbutton' => 1,
627
            'seb_showtime' => 1,
628
            'seb_showkeyboardlayout' => 1,
629
            'seb_showwificontrol' => 0,
630
            'seb_enableaudiocontrol' => 0,
631
            'seb_muteonstartup' => 0,
632
            'seb_allowspellchecking' => 0,
633
            'seb_activateurlfiltering' => 0,
634
            'seb_filterembeddedcontent' => 0,
635
            'seb_expressionsallowed' => '',
636
            'seb_regexallowed' => '',
637
            'seb_expressionsblocked' => '',
638
            'seb_regexblocked' => '',
639
        ];
640
    }
641
 
642
    /**
643
     * Validate that if a file has been uploaded by current user, that it is a valid PLIST XML file.
644
     * This function is only called if requiresafeexambrowser == settings_provider::USE_SEB_UPLOAD_CONFIG.
645
     *
646
     * @param string $itemid Item ID of file in user draft file area.
647
     * @return void|lang_string
648
     */
649
    public static function validate_draftarea_configfile($itemid) {
650
        // When saving the settings, this value will be null.
651
        if (is_null($itemid)) {
652
            return;
653
        }
654
        // If there is a config file uploaded, make sure it is a PList XML file.
655
        $file = self::get_current_user_draft_file($itemid);
656
 
657
        // If we require an SEB config uploaded, and the file exists, parse it.
658
        if ($file) {
659
            if (!helper::is_valid_seb_config($file->get_content())) {
660
                return new lang_string('fileparsefailed', 'quizaccess_seb');
661
            }
662
        }
663
 
664
        // If we require an SEB config uploaded, and the file does not exist, error.
665
        if (!$file) {
666
            return new lang_string('filenotpresent', 'quizaccess_seb');
667
        }
668
    }
669
 
670
    /**
671
     * Try and get a file in the user draft filearea by itemid.
672
     *
673
     * @param string $itemid Item ID of the file.
674
     * @return stored_file|null Returns null if no file is found.
675
     */
676
    public static function get_current_user_draft_file(string $itemid): ?stored_file {
677
        global $USER;
678
        $context = context_user::instance($USER->id);
679
        $fs = get_file_storage();
680
        if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $itemid, 'id DESC', false)) {
681
            return null;
682
        }
683
        return reset($files);
684
    }
685
 
686
    /**
687
     * Get the file that is stored in the course module file area.
688
     *
689
     * @param string $cmid The course module id which is used as an itemid reference.
690
     * @return stored_file|null Returns null if no file is found.
691
     */
692
    public static function get_module_context_sebconfig_file(string $cmid): ?stored_file {
693
        $fs = new \file_storage();
694
        $context = context_module::instance($cmid);
695
 
696
        if (!$files = $fs->get_area_files($context->id, 'quizaccess_seb', 'filemanager_sebconfigfile', 0,
697
            'id DESC', false)) {
698
            return null;
699
        }
700
 
701
        return reset($files);
702
    }
703
 
704
    /**
705
     * Saves filemanager_sebconfigfile files to the moodle storage backend.
706
     *
707
     * @param string $draftitemid The id of the draft area to use.
708
     * @param string $cmid The cmid of for the quiz.
709
     * @return bool Always true
710
     */
711
    public static function save_filemanager_sebconfigfile_draftarea(string $draftitemid, string $cmid): bool {
712
        if ($draftitemid) {
713
            $context = context_module::instance($cmid);
714
            file_save_draft_area_files($draftitemid, $context->id, 'quizaccess_seb', 'filemanager_sebconfigfile',
715
                0, []);
716
        }
717
 
718
        return true;
719
    }
720
 
721
    /**
722
     * Cleanup function to delete the saved config when it has not been specified.
723
     * This will be called when settings_provider::USE_SEB_UPLOAD_CONFIG is not true.
724
     *
725
     * @param string $cmid The cmid of for the quiz.
726
     * @return bool Always true or exception if error occurred
727
     */
728
    public static function delete_uploaded_config_file(string $cmid): bool {
729
        $file = self::get_module_context_sebconfig_file($cmid);
730
 
731
        if (!empty($file)) {
732
            return $file->delete();
733
        }
734
 
735
        return false;
736
    }
737
 
738
    /**
739
     * Check if the current user can configure SEB.
740
     *
741
     * @param \context $context Context to check access in.
742
     * @return bool
743
     */
744
    public static function can_configure_seb(\context $context): bool {
745
        return has_capability('quizaccess/seb:manage_seb_requiresafeexambrowser', $context);
746
    }
747
 
748
    /**
749
     * Check if the current user can use preconfigured templates.
750
     *
751
     * @param \context $context Context to check access in.
752
     * @return bool
753
     */
754
    public static function can_use_seb_template(\context $context): bool {
755
        return has_capability('quizaccess/seb:manage_seb_templateid', $context);
756
    }
757
 
758
    /**
759
     * Check if the current user can upload own SEB config file.
760
     *
761
     * @param \context $context Context to check access in.
762
     * @return bool
763
     */
764
    public static function can_upload_seb_file(\context $context): bool {
765
        return has_capability('quizaccess/seb:manage_filemanager_sebconfigfile', $context);
766
    }
767
 
768
    /**
769
     * Check if the current user can change Show Safe Exam Browser download button setting.
770
     *
771
     * @param \context $context Context to check access in.
772
     * @return bool
773
     */
774
    public static function can_change_seb_showsebdownloadlink(\context $context): bool {
775
        return has_capability('quizaccess/seb:manage_seb_showsebdownloadlink', $context);
776
    }
777
 
778
    /**
779
     * Check if the current user can change Allowed Browser Exam Keys setting.
780
     *
781
     * @param \context $context Context to check access in.
782
     * @return bool
783
     */
784
    public static function can_change_seb_allowedbrowserexamkeys(\context $context): bool {
785
        return has_capability('quizaccess/seb:manage_seb_allowedbrowserexamkeys', $context);
786
    }
787
 
788
    /**
789
     * Check if the current user can config SEB manually.
790
     *
791
     * @param \context $context Context to check access in.
792
     * @return bool
793
     */
794
    public static function can_configure_manually(\context $context): bool {
795
        foreach (self::get_seb_config_elements() as $name => $type) {
796
            if (self::can_manage_seb_config_setting($name, $context)) {
797
                return true;
798
            }
799
        }
800
 
801
        return false;
802
    }
803
 
804
    /**
805
     * Check if the current user can manage provided SEB setting.
806
     *
807
     * @param string $settingname Name of the setting.
808
     * @param \context $context Context to check access in.
809
     * @return bool
810
     */
811
    public static function can_manage_seb_config_setting(string $settingname, \context $context): bool {
812
        $capsttocheck = [];
813
 
814
        foreach (self::get_seb_settings_map() as $type => $settings) {
815
            $capsttocheck = self::build_config_capabilities_to_check($settingname, $settings);
816
            if (!empty($capsttocheck)) {
817
                break;
818
            }
819
        }
820
 
821
        foreach ($capsttocheck as $capability) {
822
            // Capability must exist.
823
            if (!$capinfo = get_capability_info($capability)) {
824
                throw new \coding_exception("Capability '{$capability}' was not found! This has to be fixed in code.");
825
            }
826
        }
827
 
828
        return has_all_capabilities($capsttocheck, $context);
829
    }
830
 
831
    /**
832
     * Helper method to build a list of capabilities to check.
833
     *
834
     * @param string $settingname Given setting name to build caps for.
835
     * @param array $settings A list of settings to go through.
836
     * @return array
837
     */
838
    protected static function build_config_capabilities_to_check(string $settingname, array $settings): array {
839
        $capsttocheck = [];
840
 
841
        foreach ($settings as $setting => $children) {
842
            if ($setting == $settingname) {
843
                $capsttocheck[$setting] = self::build_setting_capability_name($setting);
844
                break; // Found what we need exit the loop.
845
            }
846
 
847
            // Recursively check all children.
848
            $capsttocheck = self::build_config_capabilities_to_check($settingname, $children);
849
            if (!empty($capsttocheck)) {
850
                // Matching child found, add the parent capability to the list of caps to check.
851
                $capsttocheck[$setting] = self::build_setting_capability_name($setting);
852
                break; // Found what we need exit the loop.
853
            }
854
        }
855
 
856
        return $capsttocheck;
857
    }
858
 
859
    /**
860
     * Helper method to return a map of all settings.
861
     *
862
     * @return array
863
     */
864
    public static function get_seb_settings_map(): array {
865
        return [
866
            self::USE_SEB_NO => [
867
 
868
            ],
869
            self::USE_SEB_CONFIG_MANUALLY => [
870
                'seb_showsebdownloadlink' => [],
871
                'seb_linkquitseb' => [],
872
                'seb_userconfirmquit' => [],
873
                'seb_allowuserquitseb' => [
874
                    'seb_quitpassword' => []
875
                ],
876
                'seb_allowreloadinexam' => [],
877
                'seb_showsebtaskbar' => [
878
                    'seb_showreloadbutton' => [],
879
                    'seb_showtime' => [],
880
                    'seb_showkeyboardlayout' => [],
881
                    'seb_showwificontrol' => [],
882
                ],
883
                'seb_enableaudiocontrol' => [
884
                    'seb_muteonstartup' => [],
885
                ],
886
                'seb_allowspellchecking' => [],
887
                'seb_activateurlfiltering' => [
888
                    'seb_filterembeddedcontent' => [],
889
                    'seb_expressionsallowed' => [],
890
                    'seb_regexallowed' => [],
891
                    'seb_expressionsblocked' => [],
892
                    'seb_regexblocked' => [],
893
                ],
894
            ],
895
            self::USE_SEB_TEMPLATE => [
896
                'seb_templateid' => [],
897
                'seb_showsebdownloadlink' => [],
898
                'seb_allowuserquitseb' => [
899
                    'seb_quitpassword' => [],
900
                ],
901
            ],
902
            self::USE_SEB_UPLOAD_CONFIG => [
903
                'filemanager_sebconfigfile' => [],
904
                'seb_showsebdownloadlink' => [],
905
                'seb_allowedbrowserexamkeys' => [],
906
            ],
907
            self::USE_SEB_CLIENT_CONFIG => [
908
                'seb_showsebdownloadlink' => [],
909
                'seb_allowedbrowserexamkeys' => [],
910
            ],
911
        ];
912
    }
913
 
914
    /**
915
     * Get allowed settings for provided SEB usage type.
916
     *
917
     * @param int $requiresafeexambrowser SEB usage type.
918
     * @return array
919
     */
920
    private static function get_allowed_settings(int $requiresafeexambrowser): array {
921
        $result = [];
922
        $map = self::get_seb_settings_map();
923
 
924
        if (!key_exists($requiresafeexambrowser, $map)) {
925
            return $result;
926
        }
927
 
928
        return self::build_allowed_settings($map[$requiresafeexambrowser]);
929
    }
930
 
931
    /**
932
     * Recursive method to build a list of allowed settings.
933
     *
934
     * @param array $settings A list of settings from settings map.
935
     * @return array
936
     */
937
    private static function build_allowed_settings(array $settings): array {
938
        $result = [];
939
 
940
        foreach ($settings as $name => $children) {
941
            $result[] = $name;
942
            foreach ($children as $childname => $child) {
943
                $result[] = $childname;
944
                $result = array_merge($result, self::build_allowed_settings($child));
945
            }
946
        }
947
 
948
        return $result;
949
    }
950
 
951
    /**
952
     * Get the conditions that an element should be hid in the form. Expects matching using 'eq'.
953
     *
954
     * Array key is name of 'form element'/'database column (excluding prefix)'.
955
     * Values are instances of hideif_rule class.
956
     *
957
     * @return array List of rules per element.
958
     */
959
    public static function get_quiz_hideifs(): array {
960
        $hideifs = [];
961
 
962
        // We are building rules based on the settings map, that means children will be dependant on parent.
963
        // In most cases it's all pretty standard.
964
        // However it could be some specific cases for some fields, which will be overridden later.
965
        foreach (self::get_seb_settings_map() as $type => $settings) {
966
            foreach ($settings as $setting => $children) {
967
                $hideifs[$setting][] = new hideif_rule($setting, 'seb_requiresafeexambrowser', 'noteq', $type);
968
 
969
                foreach ($children as $childname => $child) {
970
                    $hideifs[$childname][] = new hideif_rule($childname, 'seb_requiresafeexambrowser', 'noteq', $type);
971
                    $hideifs[$childname][] = new hideif_rule($childname, $setting, 'eq', 0);
972
                }
973
            }
974
        }
975
 
976
        // Specific case for "Enable quitting of SEB". It should available for Manual and Template.
977
        $hideifs['seb_allowuserquitseb'] = [
978
            new hideif_rule('seb_allowuserquitseb', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_NO),
979
            new hideif_rule('seb_allowuserquitseb', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_CLIENT_CONFIG),
980
            new hideif_rule('seb_allowuserquitseb', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_UPLOAD_CONFIG),
981
        ];
982
 
983
        // Specific case for "Quit password". It should be available for Manual and Template. As it's parent.
984
        $hideifs['seb_quitpassword'] = [
985
            new hideif_rule('seb_quitpassword', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_NO),
986
            new hideif_rule('seb_quitpassword', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_CLIENT_CONFIG),
987
            new hideif_rule('seb_quitpassword', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_UPLOAD_CONFIG),
988
            new hideif_rule('seb_quitpassword', 'seb_allowuserquitseb', 'eq', 0),
989
        ];
990
 
991
        // Specific case for "Show Safe Exam Browser download button". It should be available for all cases, except No Seb.
992
        $hideifs['seb_showsebdownloadlink'] = [
993
            new hideif_rule('seb_showsebdownloadlink', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_NO)
994
        ];
995
 
996
        // Specific case for "Allowed Browser Exam Keys". It should be available for Template and Browser config.
997
        $hideifs['seb_allowedbrowserexamkeys'] = [
998
            new hideif_rule('seb_allowedbrowserexamkeys', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_NO),
999
            new hideif_rule('seb_allowedbrowserexamkeys', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_CONFIG_MANUALLY),
1000
            new hideif_rule('seb_allowedbrowserexamkeys', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_TEMPLATE),
1001
        ];
1002
 
1003
        return $hideifs;
1004
    }
1005
 
1006
    /**
1007
     * Build a capability name for the provided SEB setting.
1008
     *
1009
     * @param string $settingname Name of the setting.
1010
     * @return string
1011
     */
1012
    public static function build_setting_capability_name(string $settingname): string {
1013
        if (!key_exists($settingname, self::get_seb_config_elements())) {
1014
            throw new \coding_exception('Incorrect SEB quiz setting ' . $settingname);
1015
        }
1016
 
1017
        return 'quizaccess/seb:manage_' . $settingname;
1018
    }
1019
 
1020
    /**
1021
     * Check if settings is locked.
1022
     *
1023
     * @param int $quizid Quiz ID.
1024
     * @return bool
1025
     */
1026
    public static function is_seb_settings_locked($quizid): bool {
1027
        if (empty($quizid)) {
1028
            return false;
1029
        }
1030
 
1031
        return quiz_has_attempts($quizid);
1032
    }
1033
 
1034
    /**
1035
     * Filter a standard class by prefix.
1036
     *
1037
     * @param stdClass $settings Quiz settings object.
1038
     * @return stdClass Filtered object.
1039
     */
1040
    private static function filter_by_prefix(\stdClass $settings): stdClass {
1041
        $newsettings = new \stdClass();
1042
        foreach ($settings as $name => $setting) {
1043
            // Only add it, if not there.
1044
            if (strpos($name, "seb_") === 0) {
1045
                $newsettings->$name = $setting; // Add new key.
1046
            }
1047
        }
1048
        return $newsettings;
1049
    }
1050
 
1051
    /**
1052
     * Filter settings based on the setting map. Set value of not allowed settings to null.
1053
     *
1054
     * @param stdClass $settings Quiz settings.
1055
     * @return \stdClass
1056
     */
1057
    private static function filter_by_settings_map(stdClass $settings): stdClass {
1058
        if (!isset($settings->seb_requiresafeexambrowser)) {
1059
            return $settings;
1060
        }
1061
 
1062
        $newsettings = new \stdClass();
1063
        $newsettings->seb_requiresafeexambrowser = $settings->seb_requiresafeexambrowser;
1064
        $allowedsettings = self::get_allowed_settings((int)$newsettings->seb_requiresafeexambrowser);
1065
        unset($settings->seb_requiresafeexambrowser);
1066
 
1067
        foreach ($settings as $name => $value) {
1068
            if (!in_array($name, $allowedsettings)) {
1069
                $newsettings->$name = null;
1070
            } else {
1071
                $newsettings->$name = $value;
1072
            }
1073
        }
1074
 
1075
        return $newsettings;
1076
    }
1077
 
1078
    /**
1079
     * Filter quiz settings for this plugin only.
1080
     *
1081
     * @param stdClass $settings Quiz settings.
1082
     * @return stdClass Filtered settings.
1083
     */
1084
    public static function filter_plugin_settings(stdClass $settings): stdClass {
1085
        $settings = self::filter_by_prefix($settings);
1086
        $settings = self::filter_by_settings_map($settings);
1087
 
1088
        return self::strip_all_prefixes($settings);
1089
    }
1090
 
1091
    /**
1092
     * Strip the seb_ prefix from each setting key.
1093
     *
1094
     * @param \stdClass $settings Object containing settings.
1095
     * @return \stdClass The modified settings object.
1096
     */
1097
    private static function strip_all_prefixes(\stdClass $settings): stdClass {
1098
        $newsettings = new \stdClass();
1099
        foreach ($settings as $name => $setting) {
1100
            $newname = preg_replace("/^seb_/", "", $name);
1101
            $newsettings->$newname = $setting; // Add new key.
1102
        }
1103
        return $newsettings;
1104
    }
1105
 
1106
    /**
1107
     * Add prefix to string.
1108
     *
1109
     * @param string $name String to add prefix to.
1110
     * @return string String with prefix.
1111
     */
1112
    public static function add_prefix(string $name): string {
1113
        if (strpos($name, 'seb_') !== 0) {
1114
            $name = 'seb_' . $name;
1115
        }
1116
        return $name;
1117
    }
1118
}