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
 * 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) {
1441 ariadna 215
        $context = $quizform->get_context();
216
        if (self::can_use_seb_template($context) || self::is_conflicting_permissions($context)) {
1 efrain 217
            $element = $mform->createElement(
218
                'select',
219
                'seb_templateid',
220
                get_string('seb_templateid', 'quizaccess_seb'),
1441 ariadna 221
                self::get_template_options($context->instanceid)
1 efrain 222
            );
223
        } else {
224
            $element = $mform->createElement('hidden', 'seb_templateid');
225
        }
226
 
227
        self::insert_element($quizform, $mform, $element);
228
        self::set_type($quizform, $mform, 'seb_templateid', PARAM_INT);
229
        self::set_default($quizform, $mform, 'seb_templateid', 0);
230
        self::add_help_button($quizform, $mform, 'seb_templateid');
231
 
232
        // In case if the user can't use templates, but the quiz is configured to use them,
233
        // we'd like to display template, but freeze it.
1441 ariadna 234
        if (self::is_conflicting_permissions($context)) {
1 efrain 235
            self::freeze_element($quizform, $mform, 'seb_templateid');
236
        }
237
    }
238
 
239
    /**
240
     * Add upload config file element.
241
     *
242
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
243
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
244
     */
245
    protected static function add_seb_config_file(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
246
        $itemid = 0;
247
 
248
        $draftitemid = 0;
249
        file_prepare_draft_area(
250
            $draftitemid,
251
            $quizform->get_context()->id,
252
            'quizaccess_seb',
253
            'filemanager_sebconfigfile',
254
            $itemid
255
        );
256
 
257
        if (self::can_upload_seb_file($quizform->get_context())) {
258
            $element = $mform->createElement(
259
                'filemanager',
260
                'filemanager_sebconfigfile',
261
                get_string('filemanager_sebconfigfile', 'quizaccess_seb'),
262
                null,
263
                self::get_filemanager_options()
264
            );
265
        } else {
266
            $element = $mform->createElement('hidden', 'filemanager_sebconfigfile');
267
        }
268
 
269
        self::insert_element($quizform, $mform, $element);
270
        self::set_type($quizform, $mform, 'filemanager_sebconfigfile', PARAM_RAW);
271
        self::set_default($quizform, $mform, 'filemanager_sebconfigfile', $draftitemid);
272
        self::add_help_button($quizform, $mform, 'filemanager_sebconfigfile');
273
    }
274
 
275
    /**
276
     * Add Show Safe Exam Browser download button.
277
     *
278
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
279
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
280
     */
281
    protected static function add_seb_show_download_link(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
282
        if (self::can_change_seb_showsebdownloadlink($quizform->get_context())) {
283
            $element = $mform->createElement('selectyesno',
284
                'seb_showsebdownloadlink',
285
                get_string('seb_showsebdownloadlink', 'quizaccess_seb')
286
            );
287
            self::insert_element($quizform, $mform, $element);
288
            self::set_type($quizform, $mform, 'seb_showsebdownloadlink', PARAM_BOOL);
289
            self::set_default($quizform, $mform, 'seb_showsebdownloadlink', 1);
290
            self::add_help_button($quizform, $mform, 'seb_showsebdownloadlink');
291
        }
292
    }
293
 
294
    /**
295
     * Add Allowed Browser Exam Keys setting.
296
     *
297
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
298
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
299
     */
300
    protected static function add_seb_allowedbrowserexamkeys(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
301
        if (self::can_change_seb_allowedbrowserexamkeys($quizform->get_context())) {
302
            $element = $mform->createElement('textarea',
303
                'seb_allowedbrowserexamkeys',
304
                get_string('seb_allowedbrowserexamkeys', 'quizaccess_seb')
305
            );
306
            self::insert_element($quizform, $mform, $element);
307
            self::set_type($quizform, $mform, 'seb_allowedbrowserexamkeys', PARAM_RAW);
308
            self::set_default($quizform, $mform, 'seb_allowedbrowserexamkeys', '');
309
            self::add_help_button($quizform, $mform, 'seb_allowedbrowserexamkeys');
310
        }
311
    }
312
 
313
    /**
314
     * Add SEB config elements.
315
     *
316
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
317
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
318
     */
319
    protected static function add_seb_config_elements(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
320
        $defaults = self::get_seb_config_element_defaults();
321
        $types = self::get_seb_config_element_types();
322
 
323
        foreach (self::get_seb_config_elements() as $name => $type) {
324
            if (!self::can_manage_seb_config_setting($name, $quizform->get_context())) {
325
                $type = 'hidden';
326
            }
327
 
328
            $element = $mform->createElement($type, $name, get_string($name, 'quizaccess_seb'));
329
            self::insert_element($quizform, $mform, $element);
330
            unset($element); // We need to make sure each &element only references the current element in loop.
331
 
332
            self::add_help_button($quizform, $mform, $name);
333
 
334
            if (isset($defaults[$name])) {
335
                self::set_default($quizform, $mform, $name, $defaults[$name]);
336
            }
337
 
338
            if (isset($types[$name])) {
339
                self::set_type($quizform, $mform, $name, $types[$name]);
340
            }
341
        }
342
    }
343
 
344
    /**
345
     * Add setting fields.
346
     *
347
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
348
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
349
     */
350
    public static function add_seb_settings_fields(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
351
        if (self::can_configure_seb($quizform->get_context())) {
352
            self::add_seb_header_element($quizform, $mform);
353
            self::add_seb_usage_options($quizform, $mform);
354
            self::add_seb_templates($quizform, $mform);
355
            self::add_seb_config_file($quizform, $mform);
356
            self::add_seb_show_download_link($quizform, $mform);
357
            self::add_seb_config_elements($quizform, $mform);
358
            self::add_seb_allowedbrowserexamkeys($quizform, $mform);
359
            self::hide_seb_elements($quizform, $mform);
360
            self::lock_seb_elements($quizform, $mform);
361
        }
362
    }
363
 
364
    /**
365
     * Hide SEB elements if required.
366
     *
367
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
368
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
369
     */
370
    protected static function hide_seb_elements(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
371
        foreach (self::get_quiz_hideifs() as $elname => $rules) {
372
            if ($mform->elementExists($elname)) {
373
                foreach ($rules as $hideif) {
374
                    $mform->hideIf(
375
                        $hideif->get_element(),
376
                        $hideif->get_dependantname(),
377
                        $hideif->get_condition(),
378
                        $hideif->get_dependantvalue()
379
                    );
380
                }
381
            }
382
        }
383
    }
384
 
385
    /**
386
     * Lock SEB elements if required.
387
     *
388
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
389
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
390
     */
391
    protected static function lock_seb_elements(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform) {
392
        if (self::is_seb_settings_locked($quizform->get_instance()) || self::is_conflicting_permissions($quizform->get_context())) {
393
            // Freeze common quiz settings.
394
            self::freeze_element($quizform, $mform, 'seb_requiresafeexambrowser');
395
            self::freeze_element($quizform, $mform, 'seb_templateid');
396
            self::freeze_element($quizform, $mform, 'seb_showsebdownloadlink');
397
            self::freeze_element($quizform, $mform, 'seb_allowedbrowserexamkeys');
398
 
399
            $quizsettings = seb_quiz_settings::get_by_quiz_id((int) $quizform->get_instance());
400
 
401
            // If the file has been uploaded, then replace it with the link to download the file.
402
            if (!empty($quizsettings) && $quizsettings->get('requiresafeexambrowser') == self::USE_SEB_UPLOAD_CONFIG) {
403
                self::remove_element($quizform, $mform, 'filemanager_sebconfigfile');
404
                if ($link = self::get_uploaded_seb_file_download_link($quizform, $mform)) {
405
                    $element = $mform->createElement(
406
                        'static',
407
                        'filemanager_sebconfigfile',
408
                        get_string('filemanager_sebconfigfile', 'quizaccess_seb'),
409
                        $link
410
                    );
411
                    self::insert_element($quizform, $mform, $element, 'seb_showsebdownloadlink');
412
                }
413
            }
414
 
415
            // Remove template ID if not using template for this quiz.
416
            if (empty($quizsettings) || $quizsettings->get('requiresafeexambrowser') != self::USE_SEB_TEMPLATE) {
417
                $mform->removeElement('seb_templateid');
418
            }
419
 
420
            // Freeze all SEB specific settings.
421
            foreach (self::get_seb_config_elements() as $element => $type) {
422
                self::freeze_element($quizform, $mform, $element);
423
            }
424
        }
425
    }
426
 
427
    /**
428
     * Return uploaded SEB config file link.
429
     *
430
     * @param \mod_quiz_mod_form $quizform the quiz settings form that is being built.
431
     * @param \MoodleQuickForm $mform the wrapped MoodleQuickForm.
432
     * @return string
433
     */
434
    protected static function get_uploaded_seb_file_download_link(\mod_quiz_mod_form $quizform, \MoodleQuickForm $mform): string {
435
        $link = '';
436
        $file = self::get_module_context_sebconfig_file($quizform->get_coursemodule()->id);
437
 
438
        if ($file) {
439
            $url = \moodle_url::make_pluginfile_url(
440
                $file->get_contextid(),
441
                $file->get_component(),
442
                $file->get_filearea(),
443
                $file->get_itemid(),
444
                $file->get_filepath(),
445
                $file->get_filename(),
446
                true
447
            );
448
            $link = \html_writer::link($url, get_string('downloadsebconfig', 'quizaccess_seb'));
449
        }
450
 
451
        return $link;
452
    }
453
 
454
    /**
455
     * Get the type of element for each of the form elements in quiz settings.
456
     *
457
     * Contains all setting elements. Array key is name of 'form element'/'database column (excluding prefix)'.
458
     *
459
     * @return array All quiz form elements to be added and their types.
460
     */
461
    public static function get_seb_config_elements(): array {
462
        return [
463
            'seb_linkquitseb' => 'text',
464
            'seb_userconfirmquit' => 'selectyesno',
465
            'seb_allowuserquitseb' => 'selectyesno',
466
            'seb_quitpassword' => 'passwordunmask',
467
            'seb_allowreloadinexam' => 'selectyesno',
468
            'seb_showsebtaskbar' => 'selectyesno',
469
            'seb_showreloadbutton' => 'selectyesno',
470
            'seb_showtime' => 'selectyesno',
471
            'seb_showkeyboardlayout' => 'selectyesno',
472
            'seb_showwificontrol' => 'selectyesno',
473
            'seb_enableaudiocontrol' => 'selectyesno',
474
            'seb_muteonstartup' => 'selectyesno',
1441 ariadna 475
            'seb_allowcapturecamera' => 'selectyesno',
476
            'seb_allowcapturemicrophone' => 'selectyesno',
1 efrain 477
            'seb_allowspellchecking' => 'selectyesno',
478
            'seb_activateurlfiltering' => 'selectyesno',
479
            'seb_filterembeddedcontent' => 'selectyesno',
480
            'seb_expressionsallowed' => 'textarea',
481
            'seb_regexallowed' => 'textarea',
482
            'seb_expressionsblocked' => 'textarea',
483
            'seb_regexblocked' => 'textarea',
484
        ];
485
    }
486
 
487
 
488
    /**
489
     * Get the types of the quiz settings elements.
490
     * @return array List of types for the setting elements.
491
     */
492
    public static function get_seb_config_element_types(): array {
493
        return [
494
            'seb_linkquitseb' => PARAM_RAW,
495
            'seb_userconfirmquit' => PARAM_BOOL,
496
            'seb_allowuserquitseb' => PARAM_BOOL,
497
            'seb_quitpassword' => PARAM_RAW,
498
            'seb_allowreloadinexam' => PARAM_BOOL,
499
            'seb_showsebtaskbar' => PARAM_BOOL,
500
            'seb_showreloadbutton' => PARAM_BOOL,
501
            'seb_showtime' => PARAM_BOOL,
502
            'seb_showkeyboardlayout' => PARAM_BOOL,
503
            'seb_showwificontrol' => PARAM_BOOL,
504
            'seb_enableaudiocontrol' => PARAM_BOOL,
505
            'seb_muteonstartup' => PARAM_BOOL,
1441 ariadna 506
            'seb_allowcapturecamera' => PARAM_BOOL,
507
            'seb_allowcapturemicrophone' => PARAM_BOOL,
1 efrain 508
            'seb_allowspellchecking' => PARAM_BOOL,
509
            'seb_activateurlfiltering' => PARAM_BOOL,
510
            'seb_filterembeddedcontent' => PARAM_BOOL,
511
            'seb_expressionsallowed' => PARAM_RAW,
512
            'seb_regexallowed' => PARAM_RAW,
513
            'seb_expressionsblocked' => PARAM_RAW,
514
            'seb_regexblocked' => PARAM_RAW,
515
        ];
516
    }
517
 
518
    /**
1441 ariadna 519
     * Check that we have conflicting permissions with the current SEB settings.
1 efrain 520
     *
1441 ariadna 521
     * Check if the existing settings of the quiz (if any) are conflicting with the
522
     * capabilities of the managing user.
1 efrain 523
     *
1441 ariadna 524
     * E.g. a quiz is using an SEB template and a site admin is able to select this
525
     * option while a course manager cannot. Therefore it will return true for a course
526
     * manager and return false for a site admin.
527
     *
1 efrain 528
     * @param \context $context Context used with capability checking.
529
     *
530
     * @return bool
531
     */
532
    public static function is_conflicting_permissions(\context $context) {
533
        if ($context instanceof \context_course) {
534
            return false;
535
        }
536
 
537
        $settings = seb_quiz_settings::get_record(['cmid' => (int) $context->instanceid]);
538
 
539
        if (empty($settings)) {
540
            return false;
541
        }
542
 
543
        if (!self::can_use_seb_template($context) &&
1441 ariadna 544
                $settings->get('requiresafeexambrowser') == self::USE_SEB_TEMPLATE) {
1 efrain 545
            return true;
546
        }
547
 
1441 ariadna 548
        if (!self::can_use_seb_client_config($context) &&
549
                $settings->get('requiresafeexambrowser') == self::USE_SEB_CLIENT_CONFIG) {
550
            return true;
551
        }
552
 
1 efrain 553
        if (!self::can_upload_seb_file($context) &&
1441 ariadna 554
                $settings->get('requiresafeexambrowser') == self::USE_SEB_UPLOAD_CONFIG) {
1 efrain 555
            return true;
556
        }
557
 
558
        if (!self::can_configure_manually($context) &&
1441 ariadna 559
                $settings->get('requiresafeexambrowser') == self::USE_SEB_CONFIG_MANUALLY) {
1 efrain 560
            return true;
561
        }
562
 
563
        return false;
564
    }
565
 
566
    /**
567
     * Returns a list of all options of SEB usage.
568
     *
569
     * @param \context $context Context used with capability checking selection options.
570
     * @return array
571
     */
572
    public static function get_requiresafeexambrowser_options(\context $context): array {
573
        $options[self::USE_SEB_NO] = get_string('no');
574
 
575
        if (self::can_configure_manually($context) || self::is_conflicting_permissions($context)) {
576
            $options[self::USE_SEB_CONFIG_MANUALLY] = get_string('seb_use_manually', 'quizaccess_seb');
577
        }
578
 
579
        if (self::can_use_seb_template($context) || self::is_conflicting_permissions($context)) {
1441 ariadna 580
            if (!empty(self::get_template_options($context->instanceid))) {
1 efrain 581
                $options[self::USE_SEB_TEMPLATE] = get_string('seb_use_template', 'quizaccess_seb');
582
            }
583
        }
584
 
585
        if (self::can_upload_seb_file($context) || self::is_conflicting_permissions($context)) {
586
            $options[self::USE_SEB_UPLOAD_CONFIG] = get_string('seb_use_upload', 'quizaccess_seb');
587
        }
588
 
1441 ariadna 589
        if (self::can_use_seb_client_config($context) || self::is_conflicting_permissions($context)) {
590
            $options[self::USE_SEB_CLIENT_CONFIG] = get_string('seb_use_client', 'quizaccess_seb');
591
        }
1 efrain 592
 
593
        return $options;
594
    }
595
 
596
    /**
597
     * Returns a list of templates.
598
     * @return array
599
     */
1441 ariadna 600
    protected static function get_template_options($cmid): array {
1 efrain 601
        $templates = [];
1441 ariadna 602
        $templatetable = template::TABLE;
603
        $sebquizsettingstable = seb_quiz_settings::TABLE;
604
        $select = "enabled = 1
605
            OR EXISTS (
606
                SELECT 1
607
                  FROM {{$sebquizsettingstable}}
608
                 WHERE templateid = {{$templatetable}}.id
609
                   AND cmid = ?
610
            )";
611
        if ($records = template::get_records_select($select, [$cmid], 'id, name')) {
1 efrain 612
            foreach ($records as $record) {
613
                $templates[$record->get('id')] = $record->get('name');
614
            }
615
        }
616
 
617
        return $templates;
618
    }
619
 
620
    /**
621
     * Returns a list of options for the file manager element.
622
     * @return array
623
     */
624
    public static function get_filemanager_options(): array {
625
        return [
626
            'subdirs' => 0,
627
            'maxfiles' => 1,
628
            'accepted_types' => ['.seb']
629
        ];
630
    }
631
 
632
    /**
633
     * Get the default values of the quiz settings.
634
     *
635
     * Array key is name of 'form element'/'database column (excluding prefix)'.
636
     *
637
     * @return array List of settings and their defaults.
638
     */
639
    public static function get_seb_config_element_defaults(): array {
640
        return [
641
            'seb_linkquitseb' => '',
642
            'seb_userconfirmquit' => 1,
643
            'seb_allowuserquitseb' => 1,
644
            'seb_quitpassword' => '',
645
            'seb_allowreloadinexam' => 1,
646
            'seb_showsebtaskbar' => 1,
647
            'seb_showreloadbutton' => 1,
648
            'seb_showtime' => 1,
649
            'seb_showkeyboardlayout' => 1,
650
            'seb_showwificontrol' => 0,
651
            'seb_enableaudiocontrol' => 0,
1441 ariadna 652
            'seb_allowcapturecamera' => 0,
653
            'seb_allowcapturemicrophone' => 0,
1 efrain 654
            'seb_muteonstartup' => 0,
655
            'seb_allowspellchecking' => 0,
656
            'seb_activateurlfiltering' => 0,
657
            'seb_filterembeddedcontent' => 0,
658
            'seb_expressionsallowed' => '',
659
            'seb_regexallowed' => '',
660
            'seb_expressionsblocked' => '',
661
            'seb_regexblocked' => '',
662
        ];
663
    }
664
 
665
    /**
666
     * Validate that if a file has been uploaded by current user, that it is a valid PLIST XML file.
667
     * This function is only called if requiresafeexambrowser == settings_provider::USE_SEB_UPLOAD_CONFIG.
668
     *
669
     * @param string $itemid Item ID of file in user draft file area.
670
     * @return void|lang_string
671
     */
672
    public static function validate_draftarea_configfile($itemid) {
673
        // When saving the settings, this value will be null.
674
        if (is_null($itemid)) {
675
            return;
676
        }
677
        // If there is a config file uploaded, make sure it is a PList XML file.
678
        $file = self::get_current_user_draft_file($itemid);
679
 
680
        // If we require an SEB config uploaded, and the file exists, parse it.
681
        if ($file) {
682
            if (!helper::is_valid_seb_config($file->get_content())) {
683
                return new lang_string('fileparsefailed', 'quizaccess_seb');
684
            }
685
        }
686
 
687
        // If we require an SEB config uploaded, and the file does not exist, error.
688
        if (!$file) {
689
            return new lang_string('filenotpresent', 'quizaccess_seb');
690
        }
691
    }
692
 
693
    /**
694
     * Try and get a file in the user draft filearea by itemid.
695
     *
696
     * @param string $itemid Item ID of the file.
697
     * @return stored_file|null Returns null if no file is found.
698
     */
699
    public static function get_current_user_draft_file(string $itemid): ?stored_file {
700
        global $USER;
701
        $context = context_user::instance($USER->id);
702
        $fs = get_file_storage();
703
        if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $itemid, 'id DESC', false)) {
704
            return null;
705
        }
706
        return reset($files);
707
    }
708
 
709
    /**
710
     * Get the file that is stored in the course module file area.
711
     *
712
     * @param string $cmid The course module id which is used as an itemid reference.
713
     * @return stored_file|null Returns null if no file is found.
714
     */
715
    public static function get_module_context_sebconfig_file(string $cmid): ?stored_file {
716
        $fs = new \file_storage();
717
        $context = context_module::instance($cmid);
718
 
719
        if (!$files = $fs->get_area_files($context->id, 'quizaccess_seb', 'filemanager_sebconfigfile', 0,
720
            'id DESC', false)) {
721
            return null;
722
        }
723
 
724
        return reset($files);
725
    }
726
 
727
    /**
728
     * Saves filemanager_sebconfigfile files to the moodle storage backend.
729
     *
730
     * @param string $draftitemid The id of the draft area to use.
731
     * @param string $cmid The cmid of for the quiz.
732
     * @return bool Always true
733
     */
734
    public static function save_filemanager_sebconfigfile_draftarea(string $draftitemid, string $cmid): bool {
735
        if ($draftitemid) {
736
            $context = context_module::instance($cmid);
737
            file_save_draft_area_files($draftitemid, $context->id, 'quizaccess_seb', 'filemanager_sebconfigfile',
738
                0, []);
739
        }
740
 
741
        return true;
742
    }
743
 
744
    /**
745
     * Cleanup function to delete the saved config when it has not been specified.
746
     * This will be called when settings_provider::USE_SEB_UPLOAD_CONFIG is not true.
747
     *
748
     * @param string $cmid The cmid of for the quiz.
749
     * @return bool Always true or exception if error occurred
750
     */
751
    public static function delete_uploaded_config_file(string $cmid): bool {
752
        $file = self::get_module_context_sebconfig_file($cmid);
753
 
754
        if (!empty($file)) {
755
            return $file->delete();
756
        }
757
 
758
        return false;
759
    }
760
 
761
    /**
762
     * Check if the current user can configure SEB.
763
     *
764
     * @param \context $context Context to check access in.
765
     * @return bool
766
     */
767
    public static function can_configure_seb(\context $context): bool {
768
        return has_capability('quizaccess/seb:manage_seb_requiresafeexambrowser', $context);
769
    }
770
 
771
    /**
1441 ariadna 772
     * Check if the current user can select to use the SEB client configuration.
773
     *
774
     * @param \context $context Context to check access in.
775
     * @return bool
776
     */
777
    public static function can_use_seb_client_config(\context $context): bool {
778
        return has_capability('quizaccess/seb:manage_seb_usesebclientconfig', $context);
779
    }
780
 
781
    /**
1 efrain 782
     * Check if the current user can use preconfigured templates.
783
     *
784
     * @param \context $context Context to check access in.
785
     * @return bool
786
     */
787
    public static function can_use_seb_template(\context $context): bool {
788
        return has_capability('quizaccess/seb:manage_seb_templateid', $context);
789
    }
790
 
791
    /**
792
     * Check if the current user can upload own SEB config file.
793
     *
794
     * @param \context $context Context to check access in.
795
     * @return bool
796
     */
797
    public static function can_upload_seb_file(\context $context): bool {
798
        return has_capability('quizaccess/seb:manage_filemanager_sebconfigfile', $context);
799
    }
800
 
801
    /**
802
     * Check if the current user can change Show Safe Exam Browser download button setting.
803
     *
804
     * @param \context $context Context to check access in.
805
     * @return bool
806
     */
807
    public static function can_change_seb_showsebdownloadlink(\context $context): bool {
808
        return has_capability('quizaccess/seb:manage_seb_showsebdownloadlink', $context);
809
    }
810
 
811
    /**
812
     * Check if the current user can change Allowed Browser Exam Keys setting.
813
     *
814
     * @param \context $context Context to check access in.
815
     * @return bool
816
     */
817
    public static function can_change_seb_allowedbrowserexamkeys(\context $context): bool {
818
        return has_capability('quizaccess/seb:manage_seb_allowedbrowserexamkeys', $context);
819
    }
820
 
821
    /**
822
     * Check if the current user can config SEB manually.
823
     *
824
     * @param \context $context Context to check access in.
825
     * @return bool
826
     */
827
    public static function can_configure_manually(\context $context): bool {
1441 ariadna 828
        if (!has_capability('quizaccess/seb:manage_seb_configuremanually', $context)) {
829
            return false;
830
        }
831
 
1 efrain 832
        foreach (self::get_seb_config_elements() as $name => $type) {
833
            if (self::can_manage_seb_config_setting($name, $context)) {
834
                return true;
835
            }
836
        }
837
 
838
        return false;
839
    }
840
 
841
    /**
842
     * Check if the current user can manage provided SEB setting.
843
     *
844
     * @param string $settingname Name of the setting.
845
     * @param \context $context Context to check access in.
846
     * @return bool
847
     */
848
    public static function can_manage_seb_config_setting(string $settingname, \context $context): bool {
849
        $capsttocheck = [];
850
 
851
        foreach (self::get_seb_settings_map() as $type => $settings) {
852
            $capsttocheck = self::build_config_capabilities_to_check($settingname, $settings);
853
            if (!empty($capsttocheck)) {
854
                break;
855
            }
856
        }
857
 
858
        foreach ($capsttocheck as $capability) {
859
            // Capability must exist.
860
            if (!$capinfo = get_capability_info($capability)) {
861
                throw new \coding_exception("Capability '{$capability}' was not found! This has to be fixed in code.");
862
            }
863
        }
864
 
865
        return has_all_capabilities($capsttocheck, $context);
866
    }
867
 
868
    /**
869
     * Helper method to build a list of capabilities to check.
870
     *
871
     * @param string $settingname Given setting name to build caps for.
872
     * @param array $settings A list of settings to go through.
873
     * @return array
874
     */
875
    protected static function build_config_capabilities_to_check(string $settingname, array $settings): array {
876
        $capsttocheck = [];
877
 
878
        foreach ($settings as $setting => $children) {
879
            if ($setting == $settingname) {
880
                $capsttocheck[$setting] = self::build_setting_capability_name($setting);
881
                break; // Found what we need exit the loop.
882
            }
883
 
884
            // Recursively check all children.
885
            $capsttocheck = self::build_config_capabilities_to_check($settingname, $children);
886
            if (!empty($capsttocheck)) {
887
                // Matching child found, add the parent capability to the list of caps to check.
888
                $capsttocheck[$setting] = self::build_setting_capability_name($setting);
889
                break; // Found what we need exit the loop.
890
            }
891
        }
892
 
893
        return $capsttocheck;
894
    }
895
 
896
    /**
897
     * Helper method to return a map of all settings.
898
     *
899
     * @return array
900
     */
901
    public static function get_seb_settings_map(): array {
902
        return [
903
            self::USE_SEB_NO => [
904
 
905
            ],
906
            self::USE_SEB_CONFIG_MANUALLY => [
907
                'seb_showsebdownloadlink' => [],
908
                'seb_linkquitseb' => [],
909
                'seb_userconfirmquit' => [],
910
                'seb_allowuserquitseb' => [
911
                    'seb_quitpassword' => []
912
                ],
913
                'seb_allowreloadinexam' => [],
914
                'seb_showsebtaskbar' => [
915
                    'seb_showreloadbutton' => [],
916
                    'seb_showtime' => [],
917
                    'seb_showkeyboardlayout' => [],
918
                    'seb_showwificontrol' => [],
919
                ],
920
                'seb_enableaudiocontrol' => [
921
                    'seb_muteonstartup' => [],
922
                ],
1441 ariadna 923
                'seb_allowcapturecamera' => [],
924
                'seb_allowcapturemicrophone' => [],
1 efrain 925
                'seb_allowspellchecking' => [],
926
                'seb_activateurlfiltering' => [
927
                    'seb_filterembeddedcontent' => [],
928
                    'seb_expressionsallowed' => [],
929
                    'seb_regexallowed' => [],
930
                    'seb_expressionsblocked' => [],
931
                    'seb_regexblocked' => [],
932
                ],
933
            ],
934
            self::USE_SEB_TEMPLATE => [
935
                'seb_templateid' => [],
936
                'seb_showsebdownloadlink' => [],
937
                'seb_allowuserquitseb' => [
938
                    'seb_quitpassword' => [],
939
                ],
940
            ],
941
            self::USE_SEB_UPLOAD_CONFIG => [
942
                'filemanager_sebconfigfile' => [],
943
                'seb_showsebdownloadlink' => [],
944
                'seb_allowedbrowserexamkeys' => [],
945
            ],
946
            self::USE_SEB_CLIENT_CONFIG => [
947
                'seb_showsebdownloadlink' => [],
948
                'seb_allowedbrowserexamkeys' => [],
949
            ],
950
        ];
951
    }
952
 
953
    /**
954
     * Get allowed settings for provided SEB usage type.
955
     *
956
     * @param int $requiresafeexambrowser SEB usage type.
957
     * @return array
958
     */
959
    private static function get_allowed_settings(int $requiresafeexambrowser): array {
960
        $result = [];
961
        $map = self::get_seb_settings_map();
962
 
963
        if (!key_exists($requiresafeexambrowser, $map)) {
964
            return $result;
965
        }
966
 
967
        return self::build_allowed_settings($map[$requiresafeexambrowser]);
968
    }
969
 
970
    /**
971
     * Recursive method to build a list of allowed settings.
972
     *
973
     * @param array $settings A list of settings from settings map.
974
     * @return array
975
     */
976
    private static function build_allowed_settings(array $settings): array {
977
        $result = [];
978
 
979
        foreach ($settings as $name => $children) {
980
            $result[] = $name;
981
            foreach ($children as $childname => $child) {
982
                $result[] = $childname;
983
                $result = array_merge($result, self::build_allowed_settings($child));
984
            }
985
        }
986
 
987
        return $result;
988
    }
989
 
990
    /**
991
     * Get the conditions that an element should be hid in the form. Expects matching using 'eq'.
992
     *
993
     * Array key is name of 'form element'/'database column (excluding prefix)'.
994
     * Values are instances of hideif_rule class.
995
     *
996
     * @return array List of rules per element.
997
     */
998
    public static function get_quiz_hideifs(): array {
999
        $hideifs = [];
1000
 
1001
        // We are building rules based on the settings map, that means children will be dependant on parent.
1002
        // In most cases it's all pretty standard.
1003
        // However it could be some specific cases for some fields, which will be overridden later.
1004
        foreach (self::get_seb_settings_map() as $type => $settings) {
1005
            foreach ($settings as $setting => $children) {
1006
                $hideifs[$setting][] = new hideif_rule($setting, 'seb_requiresafeexambrowser', 'noteq', $type);
1007
 
1008
                foreach ($children as $childname => $child) {
1009
                    $hideifs[$childname][] = new hideif_rule($childname, 'seb_requiresafeexambrowser', 'noteq', $type);
1010
                    $hideifs[$childname][] = new hideif_rule($childname, $setting, 'eq', 0);
1011
                }
1012
            }
1013
        }
1014
 
1015
        // Specific case for "Enable quitting of SEB". It should available for Manual and Template.
1016
        $hideifs['seb_allowuserquitseb'] = [
1017
            new hideif_rule('seb_allowuserquitseb', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_NO),
1018
            new hideif_rule('seb_allowuserquitseb', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_CLIENT_CONFIG),
1019
            new hideif_rule('seb_allowuserquitseb', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_UPLOAD_CONFIG),
1020
        ];
1021
 
1022
        // Specific case for "Quit password". It should be available for Manual and Template. As it's parent.
1023
        $hideifs['seb_quitpassword'] = [
1024
            new hideif_rule('seb_quitpassword', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_NO),
1025
            new hideif_rule('seb_quitpassword', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_CLIENT_CONFIG),
1026
            new hideif_rule('seb_quitpassword', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_UPLOAD_CONFIG),
1027
            new hideif_rule('seb_quitpassword', 'seb_allowuserquitseb', 'eq', 0),
1028
        ];
1029
 
1030
        // Specific case for "Show Safe Exam Browser download button". It should be available for all cases, except No Seb.
1031
        $hideifs['seb_showsebdownloadlink'] = [
1032
            new hideif_rule('seb_showsebdownloadlink', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_NO)
1033
        ];
1034
 
1035
        // Specific case for "Allowed Browser Exam Keys". It should be available for Template and Browser config.
1036
        $hideifs['seb_allowedbrowserexamkeys'] = [
1037
            new hideif_rule('seb_allowedbrowserexamkeys', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_NO),
1038
            new hideif_rule('seb_allowedbrowserexamkeys', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_CONFIG_MANUALLY),
1039
            new hideif_rule('seb_allowedbrowserexamkeys', 'seb_requiresafeexambrowser', 'eq', self::USE_SEB_TEMPLATE),
1040
        ];
1041
 
1042
        return $hideifs;
1043
    }
1044
 
1045
    /**
1046
     * Build a capability name for the provided SEB setting.
1047
     *
1048
     * @param string $settingname Name of the setting.
1049
     * @return string
1050
     */
1051
    public static function build_setting_capability_name(string $settingname): string {
1052
        if (!key_exists($settingname, self::get_seb_config_elements())) {
1053
            throw new \coding_exception('Incorrect SEB quiz setting ' . $settingname);
1054
        }
1055
 
1056
        return 'quizaccess/seb:manage_' . $settingname;
1057
    }
1058
 
1059
    /**
1060
     * Check if settings is locked.
1061
     *
1062
     * @param int $quizid Quiz ID.
1063
     * @return bool
1064
     */
1065
    public static function is_seb_settings_locked($quizid): bool {
1066
        if (empty($quizid)) {
1067
            return false;
1068
        }
1069
 
1070
        return quiz_has_attempts($quizid);
1071
    }
1072
 
1073
    /**
1074
     * Filter a standard class by prefix.
1075
     *
1076
     * @param stdClass $settings Quiz settings object.
1077
     * @return stdClass Filtered object.
1078
     */
1079
    private static function filter_by_prefix(\stdClass $settings): stdClass {
1080
        $newsettings = new \stdClass();
1081
        foreach ($settings as $name => $setting) {
1082
            // Only add it, if not there.
1083
            if (strpos($name, "seb_") === 0) {
1084
                $newsettings->$name = $setting; // Add new key.
1085
            }
1086
        }
1087
        return $newsettings;
1088
    }
1089
 
1090
    /**
1091
     * Filter settings based on the setting map. Set value of not allowed settings to null.
1092
     *
1093
     * @param stdClass $settings Quiz settings.
1094
     * @return \stdClass
1095
     */
1096
    private static function filter_by_settings_map(stdClass $settings): stdClass {
1097
        if (!isset($settings->seb_requiresafeexambrowser)) {
1098
            return $settings;
1099
        }
1100
 
1101
        $newsettings = new \stdClass();
1102
        $newsettings->seb_requiresafeexambrowser = $settings->seb_requiresafeexambrowser;
1103
        $allowedsettings = self::get_allowed_settings((int)$newsettings->seb_requiresafeexambrowser);
1104
        unset($settings->seb_requiresafeexambrowser);
1105
 
1106
        foreach ($settings as $name => $value) {
1107
            if (!in_array($name, $allowedsettings)) {
1108
                $newsettings->$name = null;
1109
            } else {
1110
                $newsettings->$name = $value;
1111
            }
1112
        }
1113
 
1114
        return $newsettings;
1115
    }
1116
 
1117
    /**
1118
     * Filter quiz settings for this plugin only.
1119
     *
1120
     * @param stdClass $settings Quiz settings.
1121
     * @return stdClass Filtered settings.
1122
     */
1123
    public static function filter_plugin_settings(stdClass $settings): stdClass {
1124
        $settings = self::filter_by_prefix($settings);
1125
        $settings = self::filter_by_settings_map($settings);
1126
 
1127
        return self::strip_all_prefixes($settings);
1128
    }
1129
 
1130
    /**
1131
     * Strip the seb_ prefix from each setting key.
1132
     *
1133
     * @param \stdClass $settings Object containing settings.
1134
     * @return \stdClass The modified settings object.
1135
     */
1136
    private static function strip_all_prefixes(\stdClass $settings): stdClass {
1137
        $newsettings = new \stdClass();
1138
        foreach ($settings as $name => $setting) {
1139
            $newname = preg_replace("/^seb_/", "", $name);
1140
            $newsettings->$newname = $setting; // Add new key.
1141
        }
1142
        return $newsettings;
1143
    }
1144
 
1145
    /**
1146
     * Add prefix to string.
1147
     *
1148
     * @param string $name String to add prefix to.
1149
     * @return string String with prefix.
1150
     */
1151
    public static function add_prefix(string $name): string {
1152
        if (strpos($name, 'seb_') !== 0) {
1153
            $name = 'seb_' . $name;
1154
        }
1155
        return $name;
1156
    }
1157
}