Differences Between: [Versions 311 and 402] [Versions 311 and 403]
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 = 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 = 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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body