Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]
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 * Data generators for acceptance testing. 19 * 20 * @package core 21 * @category test 22 * @copyright 2012 David MonllaĆ³ 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. 27 28 defined('MOODLE_INTERNAL') || die(); 29 30 31 /** 32 * Behat data generator class for core entities. 33 * 34 * @package core 35 * @category test 36 * @copyright 2012 David MonllaĆ³ 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 */ 39 class behat_core_generator extends behat_generator_base { 40 41 protected function get_creatable_entities(): array { 42 $entities = [ 43 'users' => [ 44 'singular' => 'user', 45 'datagenerator' => 'user', 46 'required' => ['username'], 47 ], 48 'categories' => [ 49 'singular' => 'category', 50 'datagenerator' => 'category', 51 'required' => ['idnumber'], 52 'switchids' => ['category' => 'parent'], 53 ], 54 'courses' => [ 55 'singular' => 'course', 56 'datagenerator' => 'course', 57 'required' => ['shortname'], 58 'switchids' => ['category' => 'category'], 59 ], 60 'groups' => [ 61 'singular' => 'group', 62 'datagenerator' => 'group', 63 'required' => ['idnumber', 'course'], 64 'switchids' => ['course' => 'courseid'], 65 ], 66 'groupings' => [ 67 'singular' => 'grouping', 68 'datagenerator' => 'grouping', 69 'required' => ['idnumber', 'course'], 70 'switchids' => ['course' => 'courseid'], 71 ], 72 'course enrolments' => [ 73 'singular' => 'course enrolment', 74 'datagenerator' => 'enrol_user', 75 'required' => ['user', 'course', 'role'], 76 'switchids' => ['user' => 'userid', 'course' => 'courseid', 'role' => 'roleid'], 77 ], 78 'custom field categories' => [ 79 'singular' => 'custom field category', 80 'datagenerator' => 'custom_field_category', 81 'required' => ['name', 'component', 'area', 'itemid'], 82 'switchids' => [], 83 ], 84 'custom fields' => [ 85 'singular' => 'custom field', 86 'datagenerator' => 'custom_field', 87 'required' => ['name', 'category', 'type', 'shortname'], 88 'switchids' => [], 89 ], 90 'custom profile field categories' => [ 91 'singular' => 'custom profile field category', 92 'datagenerator' => 'custom_profile_field_category', 93 'required' => ['name'], 94 'switchids' => [], 95 ], 96 'custom profile fields' => [ 97 'singular' => 'custom profile field', 98 'datagenerator' => 'custom_profile_field', 99 'required' => ['datatype', 'shortname', 'name'], 100 'switchids' => [], 101 ], 102 'permission overrides' => [ 103 'singular' => 'permission override', 104 'datagenerator' => 'permission_override', 105 'required' => ['capability', 'permission', 'role', 'contextlevel', 'reference'], 106 'switchids' => ['role' => 'roleid'], 107 ], 108 'system role assigns' => [ 109 'singular' => 'system role assignment', 110 'datagenerator' => 'system_role_assign', 111 'required' => ['user', 'role'], 112 'switchids' => ['user' => 'userid', 'role' => 'roleid'], 113 ], 114 'role assigns' => [ 115 'singular' => 'role assignment', 116 'datagenerator' => 'role_assign', 117 'required' => ['user', 'role', 'contextlevel', 'reference'], 118 'switchids' => ['user' => 'userid', 'role' => 'roleid'], 119 ], 120 'activities' => [ 121 'singular' => 'activity', 122 'datagenerator' => 'activity', 123 'required' => ['activity', 'course'], 124 'switchids' => ['course' => 'course', 'gradecategory' => 'gradecat', 'grouping' => 'groupingid'], 125 ], 126 'blocks' => [ 127 'singular' => 'block', 128 'datagenerator' => 'block_instance', 129 'required' => ['blockname', 'contextlevel', 'reference'], 130 ], 131 'group members' => [ 132 'singular' => 'group member', 133 'datagenerator' => 'group_member', 134 'required' => ['user', 'group'], 135 'switchids' => ['user' => 'userid', 'group' => 'groupid'], 136 ], 137 'grouping groups' => [ 138 'singular' => 'grouping group', 139 'datagenerator' => 'grouping_group', 140 'required' => ['grouping', 'group'], 141 'switchids' => ['grouping' => 'groupingid', 'group' => 'groupid'], 142 ], 143 'cohorts' => [ 144 'singular' => 'cohort', 145 'datagenerator' => 'cohort', 146 'required' => ['idnumber'], 147 ], 148 'cohort members' => [ 149 'singular' => 'cohort member', 150 'datagenerator' => 'cohort_member', 151 'required' => ['user', 'cohort'], 152 'switchids' => ['user' => 'userid', 'cohort' => 'cohortid'], 153 ], 154 'roles' => [ 155 'singular' => 'role', 156 'datagenerator' => 'role', 157 'required' => ['shortname'], 158 ], 159 'role capabilities' => [ 160 'singular' => 'role capability', 161 'datagenerator' => 'role_capability', 162 'required' => ['role'], 163 'switchids' => ['role' => 'roleid'], 164 ], 165 'grade categories' => [ 166 'singular' => 'grade category', 167 'datagenerator' => 'grade_category', 168 'required' => ['fullname', 'course'], 169 'switchids' => ['course' => 'courseid', 'gradecategory' => 'parent'], 170 ], 171 'grade grades' => [ 172 'singular' => 'grade grade', 173 'datagenerator' => 'grade_grade', 174 'required' => ['gradeitem'], 175 'switchids' => ['user' => 'userid', 'gradeitem' => 'itemid'], 176 ], 177 'grade items' => [ 178 'singular' => 'grade item', 179 'datagenerator' => 'grade_item', 180 'required' => ['course'], 181 'switchids' => [ 182 'scale' => 'scaleid', 183 'outcome' => 'outcomeid', 184 'course' => 'courseid', 185 'gradecategory' => 'categoryid', 186 ], 187 ], 188 'grade outcomes' => [ 189 'singular' => 'grade outcome', 190 'datagenerator' => 'grade_outcome', 191 'required' => ['shortname', 'scale'], 192 'switchids' => ['course' => 'courseid', 'gradecategory' => 'categoryid', 'scale' => 'scaleid'], 193 ], 194 'scales' => [ 195 'singular' => 'scale', 196 'datagenerator' => 'scale', 197 'required' => ['name', 'scale'], 198 'switchids' => ['course' => 'courseid'], 199 ], 200 'question categories' => [ 201 'singular' => 'question category', 202 'datagenerator' => 'question_category', 203 'required' => ['name', 'contextlevel', 'reference'], 204 'switchids' => ['questioncategory' => 'parent'], 205 ], 206 'questions' => [ 207 'singular' => 'question', 208 'datagenerator' => 'question', 209 'required' => ['qtype', 'questioncategory', 'name'], 210 'switchids' => ['questioncategory' => 'category', 'user' => 'createdby'], 211 ], 212 'tags' => [ 213 'singular' => 'tag', 214 'datagenerator' => 'tag', 215 'required' => ['name'], 216 ], 217 'events' => [ 218 'singular' => 'event', 219 'datagenerator' => 'event', 220 'required' => ['name', 'eventtype'], 221 'switchids' => [ 222 'user' => 'userid', 223 'course' => 'courseid', 224 'category' => 'categoryid', 225 ], 226 ], 227 'message contacts' => [ 228 'singular' => 'message contact', 229 'datagenerator' => 'message_contacts', 230 'required' => ['user', 'contact'], 231 'switchids' => ['user' => 'userid', 'contact' => 'contactid'], 232 ], 233 'private messages' => [ 234 'singular' => 'private message', 235 'datagenerator' => 'private_messages', 236 'required' => ['user', 'contact', 'message'], 237 'switchids' => ['user' => 'userid', 'contact' => 'contactid'], 238 ], 239 'favourite conversations' => [ 240 'singular' => 'favourite conversation', 241 'datagenerator' => 'favourite_conversations', 242 'required' => ['user', 'contact'], 243 'switchids' => ['user' => 'userid', 'contact' => 'contactid'], 244 ], 245 'group messages' => [ 246 'singular' => 'group message', 247 'datagenerator' => 'group_messages', 248 'required' => ['user', 'group', 'message'], 249 'switchids' => ['user' => 'userid', 'group' => 'groupid'], 250 ], 251 'muted group conversations' => [ 252 'singular' => 'muted group conversation', 253 'datagenerator' => 'mute_group_conversations', 254 'required' => ['user', 'group', 'course'], 255 'switchids' => ['user' => 'userid', 'group' => 'groupid', 'course' => 'courseid'], 256 ], 257 'muted private conversations' => [ 258 'singular' => 'muted private conversation', 259 'datagenerator' => 'mute_private_conversations', 260 'required' => ['user', 'contact'], 261 'switchids' => ['user' => 'userid', 'contact' => 'contactid'], 262 ], 263 'language customisations' => [ 264 'singular' => 'language customisation', 265 'datagenerator' => 'customlang', 266 'required' => ['component', 'stringid', 'value'], 267 ], 268 'language packs' => [ 269 'singular' => 'language pack', 270 'datagenerator' => 'langpack', 271 'required' => ['language'], 272 ], 273 'analytics models' => [ 274 'singular' => 'analytics model', 275 'datagenerator' => 'analytics_model', 276 'required' => ['target', 'indicators', 'timesplitting', 'enabled'], 277 ], 278 'user preferences' => [ 279 'singular' => 'user preference', 280 'datagenerator' => 'user_preferences', 281 'required' => array('user', 'preference', 'value'), 282 'switchids' => array('user' => 'userid'), 283 ], 284 'contentbank contents' => [ 285 'singular' => 'contentbank content', 286 'datagenerator' => 'contentbank_content', 287 'required' => array('contextlevel', 'reference', 'contenttype', 'user', 'contentname'), 288 'switchids' => array('user' => 'userid') 289 ], 290 'user private files' => [ 291 'singular' => 'user private file', 292 'datagenerator' => 'user_private_files', 293 'required' => ['user', 'filepath', 'filename'], 294 'switchids' => ['user' => 'userid'] 295 ], 296 'badge external backpacks' => [ 297 'singular' => 'badge external backpack', 298 'datagenerator' => 'badge_external_backpack', 299 'required' => ['backpackapiurl', 'backpackweburl', 'apiversion'] 300 ], 301 'setup backpacks connected' => [ 302 'singular' => 'setup backpack connected', 303 'datagenerator' => 'setup_backpack_connected', 304 'required' => ['user', 'externalbackpack'], 305 'switchids' => ['user' => 'userid', 'externalbackpack' => 'externalbackpackid'] 306 ], 307 'last access times' => [ 308 'singular' => 'last access time', 309 'datagenerator' => 'last_access_times', 310 'required' => ['user', 'course', 'lastaccess'], 311 'switchids' => ['user' => 'userid', 'course' => 'courseid'], 312 ], 313 'notifications' => [ 314 'singular' => 'notification', 315 'datagenerator' => 'notification', 316 'required' => ['subject', 'userfrom', 'userto'], 317 'switchids' => ['userfrom' => 'userfromid', 'userto' => 'usertoid'], 318 ], 319 ]; 320 321 return $entities; 322 } 323 324 /** 325 * Get the grade item id using a name. 326 * 327 * @param string $name 328 * @return int The grade item id 329 */ 330 protected function get_gradeitem_id(string $name): int { 331 global $DB; 332 333 if (!$id = $DB->get_field('grade_items', 'id', ['itemname' => $name])) { 334 throw new Exception('The specified grade item with name "' . $name . '" could not be found.'); 335 } 336 337 return $id; 338 } 339 340 /** 341 * Remove any empty custom fields, to avoid errors when creating the course. 342 * 343 * @param array $data 344 * @return array 345 */ 346 protected function preprocess_course($data) { 347 foreach ($data as $fieldname => $value) { 348 if ($value === '' && strpos($fieldname, 'customfield_') === 0) { 349 unset($data[$fieldname]); 350 } 351 } 352 return $data; 353 } 354 355 /** 356 * If password is not set it uses the username. 357 * 358 * @param array $data 359 * @return array 360 */ 361 protected function preprocess_user($data) { 362 if (!isset($data['password'])) { 363 $data['password'] = $data['username']; 364 } 365 return $data; 366 } 367 368 /** 369 * If contextlevel and reference are specified for cohort, transform them to the contextid. 370 * 371 * @param array $data 372 * @return array 373 */ 374 protected function preprocess_cohort($data) { 375 if (isset($data['contextlevel'])) { 376 if (!isset($data['reference'])) { 377 throw new Exception('If field contextlevel is specified, field reference must also be present'); 378 } 379 $context = $this->get_context($data['contextlevel'], $data['reference']); 380 unset($data['contextlevel']); 381 unset($data['reference']); 382 $data['contextid'] = $context->id; 383 } 384 return $data; 385 } 386 387 /** 388 * Preprocesses the creation of a grade item. Converts gradetype text to a number. 389 * 390 * @param array $data 391 * @return array 392 */ 393 protected function preprocess_grade_item($data) { 394 global $CFG; 395 require_once("$CFG->libdir/grade/constants.php"); 396 397 if (isset($data['gradetype'])) { 398 $data['gradetype'] = constant("GRADE_TYPE_" . strtoupper($data['gradetype'])); 399 } 400 401 if (!empty($data['category']) && !empty($data['courseid'])) { 402 $cat = grade_category::fetch(array('fullname' => $data['category'], 'courseid' => $data['courseid'])); 403 if (!$cat) { 404 throw new Exception('Could not resolve category with name "' . $data['category'] . '"'); 405 } 406 unset($data['category']); 407 $data['categoryid'] = $cat->id; 408 } 409 410 // We need to ensure that all these attributes coming from data are not-localised floats. 411 $attrs = [ 412 'grademax', 413 'grademin', 414 'gradepass', 415 'multfactor', 416 'plusfactor', 417 'aggregationcoef', 418 'aggregationcoef2', 419 ]; 420 foreach ($attrs as $attr) { 421 if (array_key_exists($attr, $data)) { 422 $data[$attr] = unformat_float($data[$attr]); 423 } 424 } 425 426 return $data; 427 } 428 429 /** 430 * Adapter to modules generator. 431 * 432 * @throws Exception Custom exception for test writers 433 * @param array $data 434 * @return void 435 */ 436 protected function process_activity($data) { 437 global $DB, $CFG; 438 439 // The the_following_exists() method checks that the field exists. 440 $activityname = $data['activity']; 441 unset($data['activity']); 442 443 // Convert scale name into scale id (negative number indicates using scale). 444 if (isset($data['grade']) && strlen($data['grade']) && !is_number($data['grade'])) { 445 $data['grade'] = - $this->get_scale_id($data['grade']); 446 require_once("$CFG->libdir/grade/constants.php"); 447 448 if (!isset($data['gradetype'])) { 449 $data['gradetype'] = GRADE_TYPE_SCALE; 450 } 451 } 452 453 if (!array_key_exists('idnumber', $data)) { 454 $data['idnumber'] = $data['name']; 455 if (strlen($data['name']) > 100) { 456 throw new Exception( 457 "Activity '{$activityname}' cannot be used as the default idnumber. " . 458 "The idnumber has a max length of 100 chars. " . 459 "Please manually specify an idnumber." 460 ); 461 } 462 } 463 464 // We split $data in the activity $record and the course module $options. 465 $cmoptions = array(); 466 $cmcolumns = $DB->get_columns('course_modules'); 467 foreach ($cmcolumns as $key => $value) { 468 if (isset($data[$key])) { 469 $cmoptions[$key] = $data[$key]; 470 } 471 } 472 473 $this->datagenerator->create_module($activityname, $data, $cmoptions); 474 } 475 476 /** 477 * Add a block to a page. 478 * 479 * @param array $data should mostly match the fields of the block_instances table. 480 * The block type is specified by blockname. 481 * The parentcontextid is set from contextlevel and reference. 482 * Missing values are filled in by testing_block_generator::prepare_record. 483 * $data is passed to create_block as both $record and $options. Normally 484 * the keys are different, so this is a way to let people set values in either place. 485 */ 486 protected function process_block_instance($data) { 487 488 if (empty($data['blockname'])) { 489 throw new Exception('\'blocks\' requires the field \'block\' type to be specified'); 490 } 491 492 if (empty($data['contextlevel'])) { 493 throw new Exception('\'blocks\' requires the field \'contextlevel\' to be specified'); 494 } 495 496 if (!isset($data['reference'])) { 497 throw new Exception('\'blocks\' requires the field \'reference\' to be specified'); 498 } 499 500 $context = $this->get_context($data['contextlevel'], $data['reference']); 501 $data['parentcontextid'] = $context->id; 502 503 // Pass $data as both $record and $options. I think that is unlikely to 504 // cause problems since the relevant key names are different. 505 // $options is not used in most blocks I have seen, but where it is, it is necessary. 506 $this->datagenerator->create_block($data['blockname'], $data, $data); 507 } 508 509 /** 510 * Creates language customisation. 511 * 512 * @throws Exception 513 * @throws dml_exception 514 * @param array $data 515 * @return void 516 */ 517 protected function process_customlang($data) { 518 global $CFG, $DB, $USER; 519 520 require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/customlang/locallib.php'); 521 require_once($CFG->libdir . '/adminlib.php'); 522 523 if (empty($data['component'])) { 524 throw new Exception('\'customlang\' requires the field \'component\' type to be specified'); 525 } 526 527 if (empty($data['stringid'])) { 528 throw new Exception('\'customlang\' requires the field \'stringid\' to be specified'); 529 } 530 531 if (!isset($data['value'])) { 532 throw new Exception('\'customlang\' requires the field \'value\' to be specified'); 533 } 534 535 $now = time(); 536 537 tool_customlang_utils::checkout($USER->lang); 538 539 $record = $DB->get_record_sql("SELECT s.* 540 FROM {tool_customlang} s 541 JOIN {tool_customlang_components} c ON s.componentid = c.id 542 WHERE c.name = ? AND s.lang = ? AND s.stringid = ?", 543 array($data['component'], $USER->lang, $data['stringid'])); 544 545 if (empty($data['value']) && !is_null($record->local)) { 546 $record->local = null; 547 $record->modified = 1; 548 $record->outdated = 0; 549 $record->timecustomized = null; 550 $DB->update_record('tool_customlang', $record); 551 tool_customlang_utils::checkin($USER->lang); 552 } 553 554 if (!empty($data['value']) && $data['value'] != $record->local) { 555 $record->local = $data['value']; 556 $record->modified = 1; 557 $record->outdated = 0; 558 $record->timecustomized = $now; 559 $DB->update_record('tool_customlang', $record); 560 tool_customlang_utils::checkin($USER->lang); 561 } 562 } 563 /** 564 * Imports a langpack. 565 * 566 * @param array $data 567 */ 568 protected function process_langpack($data) { 569 $controller = new \tool_langimport\controller(); 570 $controller->install_languagepacks($data['language']); 571 get_string_manager()->reset_caches(); 572 } 573 574 /** 575 * Adapter to enrol_user() data generator. 576 * 577 * @throws Exception 578 * @param array $data 579 * @return void 580 */ 581 protected function process_enrol_user($data) { 582 global $SITE; 583 584 if (empty($data['roleid'])) { 585 throw new Exception('\'course enrolments\' requires the field \'role\' to be specified'); 586 } 587 588 if (!isset($data['userid'])) { 589 throw new Exception('\'course enrolments\' requires the field \'user\' to be specified'); 590 } 591 592 if (!isset($data['courseid'])) { 593 throw new Exception('\'course enrolments\' requires the field \'course\' to be specified'); 594 } 595 596 if (!isset($data['enrol'])) { 597 $data['enrol'] = 'manual'; 598 } 599 600 if (!isset($data['timestart'])) { 601 $data['timestart'] = 0; 602 } 603 604 if (!isset($data['timeend'])) { 605 $data['timeend'] = 0; 606 } 607 608 if (!isset($data['status'])) { 609 $data['status'] = null; 610 } else { 611 $status = strtolower($data['status']); 612 switch ($status) { 613 case 'active': 614 $data['status'] = ENROL_USER_ACTIVE; 615 break; 616 case 'suspended': 617 $data['status'] = ENROL_USER_SUSPENDED; 618 break; 619 } 620 } 621 622 // If the provided course shortname is the site shortname we consider it a system role assign. 623 if ($data['courseid'] == $SITE->id) { 624 // Frontpage course assign. 625 $context = context_course::instance($data['courseid']); 626 role_assign($data['roleid'], $data['userid'], $context->id); 627 628 } else { 629 // Course assign. 630 $this->datagenerator->enrol_user($data['userid'], $data['courseid'], $data['roleid'], $data['enrol'], 631 $data['timestart'], $data['timeend'], $data['status']); 632 } 633 634 } 635 636 /** 637 * Allows/denies a capability at the specified context 638 * 639 * @throws Exception 640 * @param array $data 641 * @return void 642 */ 643 protected function process_permission_override($data) { 644 645 // Will throw an exception if it does not exist. 646 $context = $this->get_context($data['contextlevel'], $data['reference']); 647 648 switch ($data['permission']) { 649 case get_string('allow', 'role'): 650 $permission = CAP_ALLOW; 651 break; 652 case get_string('prevent', 'role'): 653 $permission = CAP_PREVENT; 654 break; 655 case get_string('prohibit', 'role'): 656 $permission = CAP_PROHIBIT; 657 break; 658 default: 659 throw new Exception('The \'' . $data['permission'] . '\' permission does not exist'); 660 break; 661 } 662 663 if (is_null(get_capability_info($data['capability']))) { 664 throw new Exception('The \'' . $data['capability'] . '\' capability does not exist'); 665 } 666 667 role_change_permission($data['roleid'], $context, $data['capability'], $permission); 668 } 669 670 /** 671 * Assigns a role to a user at system context 672 * 673 * Used by "system role assigns" can be deleted when 674 * system role assign will be deprecated in favour of 675 * "role assigns" 676 * 677 * @throws Exception 678 * @param array $data 679 * @return void 680 */ 681 protected function process_system_role_assign($data) { 682 683 if (empty($data['roleid'])) { 684 throw new Exception('\'system role assigns\' requires the field \'role\' to be specified'); 685 } 686 687 if (!isset($data['userid'])) { 688 throw new Exception('\'system role assigns\' requires the field \'user\' to be specified'); 689 } 690 691 $context = context_system::instance(); 692 693 $this->datagenerator->role_assign($data['roleid'], $data['userid'], $context->id); 694 } 695 696 /** 697 * Assigns a role to a user at the specified context 698 * 699 * @throws Exception 700 * @param array $data 701 * @return void 702 */ 703 protected function process_role_assign($data) { 704 705 if (empty($data['roleid'])) { 706 throw new Exception('\'role assigns\' requires the field \'role\' to be specified'); 707 } 708 709 if (!isset($data['userid'])) { 710 throw new Exception('\'role assigns\' requires the field \'user\' to be specified'); 711 } 712 713 if (empty($data['contextlevel'])) { 714 throw new Exception('\'role assigns\' requires the field \'contextlevel\' to be specified'); 715 } 716 717 if (!isset($data['reference'])) { 718 throw new Exception('\'role assigns\' requires the field \'reference\' to be specified'); 719 } 720 721 // Getting the context id. 722 $context = $this->get_context($data['contextlevel'], $data['reference']); 723 724 $this->datagenerator->role_assign($data['roleid'], $data['userid'], $context->id); 725 } 726 727 /** 728 * Creates a role. 729 * 730 * @param array $data 731 * @return void 732 */ 733 protected function process_role($data) { 734 735 // We require the user to fill the role shortname. 736 if (empty($data['shortname'])) { 737 throw new Exception('\'role\' requires the field \'shortname\' to be specified'); 738 } 739 740 $this->datagenerator->create_role($data); 741 } 742 743 /** 744 * Assign capabilities to a role. 745 * 746 * @param array $data 747 */ 748 protected function process_role_capability($data): void { 749 // We require the user to fill the role shortname. 750 if (empty($data['roleid'])) { 751 throw new Exception('\'role capability\' requires the field \'roleid\' to be specified'); 752 } 753 754 $roleid = $data['roleid']; 755 unset($data['roleid']); 756 757 $this->datagenerator->create_role_capability($roleid, $data, \context_system::instance()); 758 } 759 760 /** 761 * Adds members to cohorts 762 * 763 * @param array $data 764 * @return void 765 */ 766 protected function process_cohort_member($data) { 767 cohort_add_member($data['cohortid'], $data['userid']); 768 } 769 770 /** 771 * Create a question category. 772 * 773 * @param array $data the row of data from the behat script. 774 */ 775 protected function process_question_category($data) { 776 global $DB; 777 778 $context = $this->get_context($data['contextlevel'], $data['reference']); 779 780 // The way this class works, we have already looked up the given parent category 781 // name and found a matching category. However, it is possible, particularly 782 // for the 'top' category, for there to be several categories with the 783 // same name. So far one will have been picked at random, but we need 784 // the one from the right context. So, if we have the wrong category, try again. 785 // (Just fixing it here, rather than getting it right first time, is a bit 786 // of a bodge, but in general this class assumes that names are unique, 787 // and normally they are, so this was the easiest fix.) 788 if (!empty($data['parent'])) { 789 $foundparent = $DB->get_record('question_categories', ['id' => $data['parent']], '*', MUST_EXIST); 790 if ($foundparent->contextid != $context->id) { 791 $rightparentid = $DB->get_field('question_categories', 'id', 792 ['contextid' => $context->id, 'name' => $foundparent->name]); 793 if (!$rightparentid) { 794 throw new Exception('The specified question category with name "' . $foundparent->name . 795 '" does not exist in context "' . $context->get_context_name() . '"."'); 796 } 797 $data['parent'] = $rightparentid; 798 } 799 } 800 801 $data['contextid'] = $context->id; 802 /** @var core_question_generator $qgenerator */ 803 $qgenerator = $this->datagenerator->get_plugin_generator('core_question'); 804 $qgenerator->create_question_category($data); 805 } 806 807 /** 808 * Create a question. 809 * 810 * Creating questions relies on the question/type/.../tests/helper.php mechanism. 811 * We start with test_question_maker::get_question_form_data($data['qtype'], $data['template']) 812 * and then overlay the values from any other fields of $data that are set. 813 * 814 * There is a special case that allows you to set qtype to 'missingtype'. 815 * This creates an example of broken question, such as you might get if you 816 * install a question type, create some questions of that type, and then 817 * uninstall the question type (which is prevented through the UI but can 818 * still happen). This special lets tests verify that these questions are 819 * handled OK. 820 * 821 * @param array $data the row of data from the behat script. 822 */ 823 protected function process_question($data) { 824 global $DB; 825 826 if (array_key_exists('questiontext', $data)) { 827 $data['questiontext'] = array( 828 'text' => $data['questiontext'], 829 'format' => FORMAT_HTML, 830 ); 831 } 832 833 if (array_key_exists('generalfeedback', $data)) { 834 $data['generalfeedback'] = array( 835 'text' => $data['generalfeedback'], 836 'format' => FORMAT_HTML, 837 ); 838 } 839 840 $which = null; 841 if (!empty($data['template'])) { 842 $which = $data['template']; 843 } 844 845 $missingtypespecialcase = false; 846 if ($data['qtype'] === 'missingtype') { 847 $data['qtype'] = 'essay'; // Actual type uses here does not matter. We just need any question. 848 $missingtypespecialcase = true; 849 } 850 851 /** @var core_question_generator $qgenerator */ 852 $qgenerator = $this->datagenerator->get_plugin_generator('core_question'); 853 $questiondata = $qgenerator 854 ->create_question($data['qtype'], $which, $data); 855 856 if ($missingtypespecialcase) { 857 $DB->set_field('question', 'qtype', 'unknownqtype', ['id' => $questiondata->id]); 858 } 859 } 860 861 /** 862 * Adds user to contacts 863 * 864 * @param array $data 865 * @return void 866 */ 867 protected function process_message_contacts($data) { 868 \core_message\api::add_contact($data['userid'], $data['contactid']); 869 } 870 871 /** 872 * Send a new message from user to contact in a private conversation 873 * 874 * @param array $data 875 * @return void 876 */ 877 protected function process_private_messages(array $data) { 878 if (empty($data['format'])) { 879 $data['format'] = 'FORMAT_PLAIN'; 880 } 881 882 if (!$conversationid = \core_message\api::get_conversation_between_users([$data['userid'], $data['contactid']])) { 883 $conversation = \core_message\api::create_conversation( 884 \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, 885 [$data['userid'], $data['contactid']] 886 ); 887 $conversationid = $conversation->id; 888 } 889 \core_message\api::send_message_to_conversation( 890 $data['userid'], 891 $conversationid, 892 $data['message'], 893 constant($data['format']) 894 ); 895 } 896 897 /** 898 * Send a new message from user to a group conversation 899 * 900 * @param array $data 901 * @return void 902 */ 903 protected function process_group_messages(array $data) { 904 global $DB; 905 906 if (empty($data['format'])) { 907 $data['format'] = 'FORMAT_PLAIN'; 908 } 909 910 $group = $DB->get_record('groups', ['id' => $data['groupid']]); 911 $coursecontext = context_course::instance($group->courseid); 912 if (!$conversation = \core_message\api::get_conversation_by_area('core_group', 'groups', $data['groupid'], 913 $coursecontext->id)) { 914 $members = $DB->get_records_menu('groups_members', ['groupid' => $data['groupid']], '', 'userid, id'); 915 $conversation = \core_message\api::create_conversation( 916 \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, 917 array_keys($members), 918 $group->name, 919 \core_message\api::MESSAGE_CONVERSATION_ENABLED, 920 'core_group', 921 'groups', 922 $group->id, 923 $coursecontext->id); 924 } 925 \core_message\api::send_message_to_conversation( 926 $data['userid'], 927 $conversation->id, 928 $data['message'], 929 constant($data['format']) 930 ); 931 } 932 933 /** 934 * Mark a private conversation as favourite for user 935 * 936 * @param array $data 937 * @return void 938 */ 939 protected function process_favourite_conversations(array $data) { 940 if (!$conversationid = \core_message\api::get_conversation_between_users([$data['userid'], $data['contactid']])) { 941 $conversation = \core_message\api::create_conversation( 942 \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, 943 [$data['userid'], $data['contactid']] 944 ); 945 $conversationid = $conversation->id; 946 } 947 \core_message\api::set_favourite_conversation($conversationid, $data['userid']); 948 } 949 950 /** 951 * Mute an existing group conversation for user 952 * 953 * @param array $data 954 * @return void 955 */ 956 protected function process_mute_group_conversations(array $data) { 957 if (groups_is_member($data['groupid'], $data['userid'])) { 958 $context = context_course::instance($data['courseid']); 959 $conversation = \core_message\api::get_conversation_by_area( 960 'core_group', 961 'groups', 962 $data['groupid'], 963 $context->id 964 ); 965 if ($conversation) { 966 \core_message\api::mute_conversation($data['userid'], $conversation->id); 967 } 968 } 969 } 970 971 /** 972 * Mute a private conversation for user 973 * 974 * @param array $data 975 * @return void 976 */ 977 protected function process_mute_private_conversations(array $data) { 978 if (!$conversationid = \core_message\api::get_conversation_between_users([$data['userid'], $data['contactid']])) { 979 $conversation = \core_message\api::create_conversation( 980 \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, 981 [$data['userid'], $data['contactid']] 982 ); 983 $conversationid = $conversation->id; 984 } 985 \core_message\api::mute_conversation($data['userid'], $conversationid); 986 } 987 988 /** 989 * Transform indicators string into array. 990 * 991 * @param array $data 992 * @return array 993 */ 994 protected function preprocess_analytics_model($data) { 995 $data['indicators'] = explode(',', $data['indicators']); 996 return $data; 997 } 998 999 /** 1000 * Creates an analytics model 1001 * 1002 * @param array $data target 1003 * @return void 1004 */ 1005 protected function process_analytics_model($data) { 1006 \core_analytics\manager::create_declared_model($data); 1007 } 1008 1009 /** 1010 * Set a preference value for user 1011 * 1012 * @param array $data 1013 * @return void 1014 */ 1015 protected function process_user_preferences(array $data) { 1016 set_user_preference($data['preference'], $data['value'], $data['userid']); 1017 } 1018 1019 /** 1020 * Create content in the given context's content bank. 1021 * 1022 * @param array $data 1023 * @return void 1024 */ 1025 protected function process_contentbank_content(array $data) { 1026 global $CFG; 1027 1028 if (empty($data['contextlevel'])) { 1029 throw new Exception('contentbank_content requires the field contextlevel to be specified'); 1030 } 1031 1032 if (!isset($data['reference'])) { 1033 throw new Exception('contentbank_content requires the field reference to be specified'); 1034 } 1035 1036 if (empty($data['contenttype'])) { 1037 throw new Exception('contentbank_content requires the field contenttype to be specified'); 1038 } 1039 1040 $contenttypeclass = "\\".$data['contenttype']."\\contenttype"; 1041 if (class_exists($contenttypeclass)) { 1042 $context = $this->get_context($data['contextlevel'], $data['reference']); 1043 $contenttype = new $contenttypeclass($context); 1044 $record = new stdClass(); 1045 $record->usercreated = $data['userid']; 1046 $record->name = $data['contentname']; 1047 if (isset($data['visibility'])) { 1048 $record->visibility = $data['visibility']; 1049 } 1050 $content = $contenttype->create_content($record); 1051 1052 if (!empty($data['filepath'])) { 1053 $filename = basename($data['filepath']); 1054 $fs = get_file_storage(); 1055 $filerecord = array( 1056 'component' => 'contentbank', 1057 'filearea' => 'public', 1058 'contextid' => $context->id, 1059 'userid' => $data['userid'], 1060 'itemid' => $content->get_id(), 1061 'filename' => $filename, 1062 'filepath' => '/' 1063 ); 1064 $fs->create_file_from_pathname($filerecord, $CFG->dirroot . $data['filepath']); 1065 } 1066 } else { 1067 throw new Exception('The specified "' . $data['contenttype'] . '" contenttype does not exist'); 1068 } 1069 } 1070 1071 /** 1072 * Create content in the given user's private files. 1073 * 1074 * @param array $data 1075 * @return void 1076 */ 1077 protected function process_user_private_files(array $data) { 1078 global $CFG; 1079 1080 $userid = $data['userid']; 1081 $fs = get_file_storage(); 1082 $filepath = "{$CFG->dirroot}/{$data['filepath']}"; 1083 1084 if (!file_exists($filepath)) { 1085 throw new coding_exception("File '{$filepath}' does not exist"); 1086 } 1087 $filerecord = [ 1088 'userid' => $userid, 1089 'contextid' => context_user::instance($userid)->id, 1090 'component' => 'user', 1091 'filearea' => 'private', 1092 'itemid' => 0, 1093 'filepath' => '/', 1094 'filename' => basename($filepath), 1095 ]; 1096 $fs->create_file_from_pathname($filerecord, $filepath); 1097 } 1098 1099 /** 1100 * Create a exetrnal backpack. 1101 * 1102 * @param array $data 1103 */ 1104 protected function process_badge_external_backpack(array $data) { 1105 global $DB; 1106 $DB->insert_record('badge_external_backpack', $data, true); 1107 } 1108 1109 /** 1110 * Setup a backpack connected for user. 1111 * 1112 * @param array $data 1113 * @throws dml_exception 1114 */ 1115 protected function process_setup_backpack_connected(array $data) { 1116 global $DB; 1117 1118 if (empty($data['userid'])) { 1119 throw new Exception('\'setup backpack connected\' requires the field \'user\' to be specified'); 1120 } 1121 if (empty($data['externalbackpackid'])) { 1122 throw new Exception('\'setup backpack connected\' requires the field \'externalbackpack\' to be specified'); 1123 } 1124 // Dummy badge_backpack_oauth2 data. 1125 $timenow = time(); 1126 $backpackoauth2 = new stdClass(); 1127 $backpackoauth2->usermodified = $data['userid']; 1128 $backpackoauth2->timecreated = $timenow; 1129 $backpackoauth2->timemodified = $timenow; 1130 $backpackoauth2->userid = $data['userid']; 1131 $backpackoauth2->issuerid = 1; 1132 $backpackoauth2->externalbackpackid = $data['externalbackpackid']; 1133 $backpackoauth2->token = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 1134 $backpackoauth2->refreshtoken = '0123456789abcdefghijk'; 1135 $backpackoauth2->expires = $timenow + 3600; 1136 $backpackoauth2->scope = 'https://purl.imsglobal.org/spec/ob/v2p1/scope/assertion.create'; 1137 $backpackoauth2->scope .= ' https://purl.imsglobal.org/spec/ob/v2p1/scope/assertion.readonly offline_access'; 1138 $DB->insert_record('badge_backpack_oauth2', $backpackoauth2); 1139 1140 // Dummy badge_backpack data. 1141 $backpack = new stdClass(); 1142 $backpack->userid = $data['userid']; 1143 $backpack->email = 'student@behat.moodle'; 1144 $backpack->backpackuid = 0; 1145 $backpack->autosync = 0; 1146 $backpack->password = ''; 1147 $backpack->externalbackpackid = $data['externalbackpackid']; 1148 $DB->insert_record('badge_backpack', $backpack); 1149 } 1150 1151 /** 1152 * Creates notifications to specific user. 1153 * 1154 * @param array $data 1155 * @return void 1156 */ 1157 protected function process_notification(array $data) { 1158 global $DB; 1159 1160 $notification = new stdClass(); 1161 $notification->useridfrom = $data['userfromid']; 1162 $notification->useridto = $data['usertoid']; 1163 $notification->subject = $data['subject']; 1164 $notification->fullmessage = $data['subject'] . ' description'; 1165 $notification->smallmessage = $data['subject'] . ' description'; 1166 $notification->fullmessagehtml = $data['subject'] . ' description'; 1167 1168 if ($data['timecreated'] !== 'null') { 1169 $notification->timecreated = $data['timecreated']; 1170 } 1171 1172 if ($data['timeread'] !== 'null') { 1173 $notification->timeread = $data['timeread']; 1174 } 1175 1176 if (!empty($data)) { 1177 $popupnotification = new stdClass(); 1178 $popupnotification->notificationid = $DB->insert_record('notifications', $notification); 1179 $DB->insert_record('message_popup_notifications', $popupnotification); 1180 } 1181 1182 } 1183 1184 /** 1185 * Creates user last access data within given courses. 1186 * 1187 * @param array $data 1188 * @return void 1189 */ 1190 protected function process_last_access_times(array $data) { 1191 global $DB; 1192 1193 if (!isset($data['userid'])) { 1194 throw new Exception('\'last acces times\' requires the field \'user\' to be specified'); 1195 } 1196 1197 if (!isset($data['courseid'])) { 1198 throw new Exception('\'last acces times\' requires the field \'course\' to be specified'); 1199 } 1200 1201 if (!isset($data['lastaccess'])) { 1202 throw new Exception('\'last acces times\' requires the field \'lastaccess\' to be specified'); 1203 } 1204 1205 $userdata = []; 1206 $userdata['old'] = $DB->get_record('user', ['id' => $data['userid']], 'firstaccess, lastaccess, lastlogin, currentlogin'); 1207 $userdata['new'] = [ 1208 'firstaccess' => $userdata['old']->firstaccess, 1209 'lastaccess' => $userdata['old']->lastaccess, 1210 'lastlogin' => $userdata['old']->lastlogin, 1211 'currentlogin' => $userdata['old']->currentlogin, 1212 ]; 1213 1214 // Check for lastaccess data for this course. 1215 $lastaccessdata = [ 1216 'userid' => $data['userid'], 1217 'courseid' => $data['courseid'], 1218 ]; 1219 1220 $lastaccessid = $DB->get_field('user_lastaccess', 'id', $lastaccessdata); 1221 1222 $dbdata = (object) $lastaccessdata; 1223 $dbdata->timeaccess = $data['lastaccess']; 1224 1225 // Set the course last access time. 1226 if ($lastaccessid) { 1227 $dbdata->id = $lastaccessid; 1228 $DB->update_record('user_lastaccess', $dbdata); 1229 } else { 1230 $DB->insert_record('user_lastaccess', $dbdata); 1231 } 1232 1233 // Store changes to other user access times as needed. 1234 1235 // Update first access if this is the user's first login, or this access is earlier than their current first access. 1236 if (empty($userdata['new']['firstaccess']) || 1237 $userdata['new']['firstaccess'] > $data['lastaccess']) { 1238 $userdata['new']['firstaccess'] = $data['lastaccess']; 1239 } 1240 1241 // Update last access if it is the user's most recent access. 1242 if (empty($userdata['new']['lastaccess']) || 1243 $userdata['new']['lastaccess'] < $data['lastaccess']) { 1244 $userdata['new']['lastaccess'] = $data['lastaccess']; 1245 } 1246 1247 // Update last and current login if it is the user's most recent access. 1248 if (empty($userdata['new']['lastlogin']) || 1249 $userdata['new']['lastlogin'] < $data['lastaccess']) { 1250 $userdata['new']['lastlogin'] = $data['lastaccess']; 1251 $userdata['new']['currentlogin'] = $data['lastaccess']; 1252 } 1253 1254 $updatedata = []; 1255 1256 if ($userdata['new']['firstaccess'] != $userdata['old']->firstaccess) { 1257 $updatedata['firstaccess'] = $userdata['new']['firstaccess']; 1258 } 1259 1260 if ($userdata['new']['lastaccess'] != $userdata['old']->lastaccess) { 1261 $updatedata['lastaccess'] = $userdata['new']['lastaccess']; 1262 } 1263 1264 if ($userdata['new']['lastlogin'] != $userdata['old']->lastlogin) { 1265 $updatedata['lastlogin'] = $userdata['new']['lastlogin']; 1266 } 1267 1268 if ($userdata['new']['currentlogin'] != $userdata['old']->currentlogin) { 1269 $updatedata['currentlogin'] = $userdata['new']['currentlogin']; 1270 } 1271 1272 // Only update user access data if there have been any changes. 1273 if (!empty($updatedata)) { 1274 $updatedata['id'] = $data['userid']; 1275 $updatedata = (object) $updatedata; 1276 $DB->update_record('user', $updatedata); 1277 } 1278 } 1279 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body