Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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 'analytics models' => [ 269 'singular' => 'analytics model', 270 'datagenerator' => 'analytics_model', 271 'required' => ['target', 'indicators', 'timesplitting', 'enabled'], 272 ], 273 'user preferences' => [ 274 'singular' => 'user preference', 275 'datagenerator' => 'user_preferences', 276 'required' => array('user', 'preference', 'value'), 277 'switchids' => array('user' => 'userid'), 278 ], 279 'contentbank contents' => [ 280 'singular' => 'contentbank content', 281 'datagenerator' => 'contentbank_content', 282 'required' => array('contextlevel', 'reference', 'contenttype', 'user', 'contentname'), 283 'switchids' => array('user' => 'userid') 284 ], 285 'badge external backpacks' => [ 286 'singular' => 'badge external backpack', 287 'datagenerator' => 'badge_external_backpack', 288 'required' => ['backpackapiurl', 'backpackweburl', 'apiversion'] 289 ], 290 'setup backpacks connected' => [ 291 'singular' => 'setup backpack connected', 292 'datagenerator' => 'setup_backpack_connected', 293 'required' => ['user', 'externalbackpack'], 294 'switchids' => ['user' => 'userid', 'externalbackpack' => 'externalbackpackid'] 295 ], 296 'last access times' => [ 297 'singular' => 'last access time', 298 'datagenerator' => 'last_access_times', 299 'required' => ['user', 'course', 'lastaccess'], 300 'switchids' => ['user' => 'userid', 'course' => 'courseid'], 301 ], 302 'notifications' => [ 303 'singular' => 'notification', 304 'datagenerator' => 'notification', 305 'required' => ['subject', 'userfrom', 'userto'], 306 'switchids' => ['userfrom' => 'userfromid', 'userto' => 'usertoid'], 307 ], 308 ]; 309 310 return $entities; 311 } 312 313 /** 314 * Get the grade item id using a name. 315 * 316 * @param string $name 317 * @return int The grade item id 318 */ 319 protected function get_gradeitem_id(string $name): int { 320 global $DB; 321 322 if (!$id = $DB->get_field('grade_items', 'id', ['itemname' => $name])) { 323 throw new Exception('The specified grade item with name "' . $name . '" could not be found.'); 324 } 325 326 return $id; 327 } 328 329 /** 330 * Remove any empty custom fields, to avoid errors when creating the course. 331 * 332 * @param array $data 333 * @return array 334 */ 335 protected function preprocess_course($data) { 336 foreach ($data as $fieldname => $value) { 337 if ($value === '' && strpos($fieldname, 'customfield_') === 0) { 338 unset($data[$fieldname]); 339 } 340 } 341 return $data; 342 } 343 344 /** 345 * If password is not set it uses the username. 346 * 347 * @param array $data 348 * @return array 349 */ 350 protected function preprocess_user($data) { 351 if (!isset($data['password'])) { 352 $data['password'] = $data['username']; 353 } 354 return $data; 355 } 356 357 /** 358 * If contextlevel and reference are specified for cohort, transform them to the contextid. 359 * 360 * @param array $data 361 * @return array 362 */ 363 protected function preprocess_cohort($data) { 364 if (isset($data['contextlevel'])) { 365 if (!isset($data['reference'])) { 366 throw new Exception('If field contextlevel is specified, field reference must also be present'); 367 } 368 $context = $this->get_context($data['contextlevel'], $data['reference']); 369 unset($data['contextlevel']); 370 unset($data['reference']); 371 $data['contextid'] = $context->id; 372 } 373 return $data; 374 } 375 376 /** 377 * Preprocesses the creation of a grade item. Converts gradetype text to a number. 378 * 379 * @param array $data 380 * @return array 381 */ 382 protected function preprocess_grade_item($data) { 383 global $CFG; 384 require_once("$CFG->libdir/grade/constants.php"); 385 386 if (isset($data['gradetype'])) { 387 $data['gradetype'] = constant("GRADE_TYPE_" . strtoupper($data['gradetype'])); 388 } 389 390 if (!empty($data['category']) && !empty($data['courseid'])) { 391 $cat = grade_category::fetch(array('fullname' => $data['category'], 'courseid' => $data['courseid'])); 392 if (!$cat) { 393 throw new Exception('Could not resolve category with name "' . $data['category'] . '"'); 394 } 395 unset($data['category']); 396 $data['categoryid'] = $cat->id; 397 } 398 399 // We need to ensure that all these attributes coming from data are not-localised floats. 400 $attrs = [ 401 'grademax', 402 'grademin', 403 'gradepass', 404 'multfactor', 405 'plusfactor', 406 'aggregationcoef', 407 'aggregationcoef2', 408 ]; 409 foreach ($attrs as $attr) { 410 if (array_key_exists($attr, $data)) { 411 $data[$attr] = unformat_float($data[$attr]); 412 } 413 } 414 415 return $data; 416 } 417 418 /** 419 * Adapter to modules generator. 420 * 421 * @throws Exception Custom exception for test writers 422 * @param array $data 423 * @return void 424 */ 425 protected function process_activity($data) { 426 global $DB, $CFG; 427 428 // The the_following_exists() method checks that the field exists. 429 $activityname = $data['activity']; 430 unset($data['activity']); 431 432 // Convert scale name into scale id (negative number indicates using scale). 433 if (isset($data['grade']) && strlen($data['grade']) && !is_number($data['grade'])) { 434 $data['grade'] = - $this->get_scale_id($data['grade']); 435 require_once("$CFG->libdir/grade/constants.php"); 436 437 if (!isset($data['gradetype'])) { 438 $data['gradetype'] = GRADE_TYPE_SCALE; 439 } 440 } 441 442 if (!array_key_exists('idnumber', $data)) { 443 $data['idnumber'] = $data['name']; 444 if (strlen($data['name']) > 100) { 445 throw new Exception( 446 "Activity '{$activityname}' cannot be used as the default idnumber. " . 447 "The idnumber has a max length of 100 chars. " . 448 "Please manually specify an idnumber." 449 ); 450 } 451 } 452 453 // We split $data in the activity $record and the course module $options. 454 $cmoptions = array(); 455 $cmcolumns = $DB->get_columns('course_modules'); 456 foreach ($cmcolumns as $key => $value) { 457 if (isset($data[$key])) { 458 $cmoptions[$key] = $data[$key]; 459 } 460 } 461 462 // Custom exception. 463 try { 464 $this->datagenerator->create_module($activityname, $data, $cmoptions); 465 } catch (coding_exception $e) { 466 throw new Exception('\'' . $activityname . '\' activity can not be added using this step,' . 467 ' use the step \'I add a "ACTIVITY_OR_RESOURCE_NAME_STRING" to section "SECTION_NUMBER"\' instead'); 468 } 469 } 470 471 /** 472 * Add a block to a page. 473 * 474 * @param array $data should mostly match the fields of the block_instances table. 475 * The block type is specified by blockname. 476 * The parentcontextid is set from contextlevel and reference. 477 * Missing values are filled in by testing_block_generator::prepare_record. 478 * $data is passed to create_block as both $record and $options. Normally 479 * the keys are different, so this is a way to let people set values in either place. 480 */ 481 protected function process_block_instance($data) { 482 483 if (empty($data['blockname'])) { 484 throw new Exception('\'blocks\' requires the field \'block\' type to be specified'); 485 } 486 487 if (empty($data['contextlevel'])) { 488 throw new Exception('\'blocks\' requires the field \'contextlevel\' to be specified'); 489 } 490 491 if (!isset($data['reference'])) { 492 throw new Exception('\'blocks\' requires the field \'reference\' to be specified'); 493 } 494 495 $context = $this->get_context($data['contextlevel'], $data['reference']); 496 $data['parentcontextid'] = $context->id; 497 498 // Pass $data as both $record and $options. I think that is unlikely to 499 // cause problems since the relevant key names are different. 500 // $options is not used in most blocks I have seen, but where it is, it is necessary. 501 $this->datagenerator->create_block($data['blockname'], $data, $data); 502 } 503 504 /** 505 * Creates language customisation. 506 * 507 * @throws Exception 508 * @throws dml_exception 509 * @param array $data 510 * @return void 511 */ 512 protected function process_customlang($data) { 513 global $CFG, $DB, $USER; 514 515 require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/customlang/locallib.php'); 516 require_once($CFG->libdir . '/adminlib.php'); 517 518 if (empty($data['component'])) { 519 throw new Exception('\'customlang\' requires the field \'component\' type to be specified'); 520 } 521 522 if (empty($data['stringid'])) { 523 throw new Exception('\'customlang\' requires the field \'stringid\' to be specified'); 524 } 525 526 if (!isset($data['value'])) { 527 throw new Exception('\'customlang\' requires the field \'value\' to be specified'); 528 } 529 530 $now = time(); 531 532 tool_customlang_utils::checkout($USER->lang); 533 534 $record = $DB->get_record_sql("SELECT s.* 535 FROM {tool_customlang} s 536 JOIN {tool_customlang_components} c ON s.componentid = c.id 537 WHERE c.name = ? AND s.lang = ? AND s.stringid = ?", 538 array($data['component'], $USER->lang, $data['stringid'])); 539 540 if (empty($data['value']) && !is_null($record->local)) { 541 $record->local = null; 542 $record->modified = 1; 543 $record->outdated = 0; 544 $record->timecustomized = null; 545 $DB->update_record('tool_customlang', $record); 546 tool_customlang_utils::checkin($USER->lang); 547 } 548 549 if (!empty($data['value']) && $data['value'] != $record->local) { 550 $record->local = $data['value']; 551 $record->modified = 1; 552 $record->outdated = 0; 553 $record->timecustomized = $now; 554 $DB->update_record('tool_customlang', $record); 555 tool_customlang_utils::checkin($USER->lang); 556 } 557 } 558 559 /** 560 * Adapter to enrol_user() data generator. 561 * 562 * @throws Exception 563 * @param array $data 564 * @return void 565 */ 566 protected function process_enrol_user($data) { 567 global $SITE; 568 569 if (empty($data['roleid'])) { 570 throw new Exception('\'course enrolments\' requires the field \'role\' to be specified'); 571 } 572 573 if (!isset($data['userid'])) { 574 throw new Exception('\'course enrolments\' requires the field \'user\' to be specified'); 575 } 576 577 if (!isset($data['courseid'])) { 578 throw new Exception('\'course enrolments\' requires the field \'course\' to be specified'); 579 } 580 581 if (!isset($data['enrol'])) { 582 $data['enrol'] = 'manual'; 583 } 584 585 if (!isset($data['timestart'])) { 586 $data['timestart'] = 0; 587 } 588 589 if (!isset($data['timeend'])) { 590 $data['timeend'] = 0; 591 } 592 593 if (!isset($data['status'])) { 594 $data['status'] = null; 595 } else { 596 $status = strtolower($data['status']); 597 switch ($status) { 598 case 'active': 599 $data['status'] = ENROL_USER_ACTIVE; 600 break; 601 case 'suspended': 602 $data['status'] = ENROL_USER_SUSPENDED; 603 break; 604 } 605 } 606 607 // If the provided course shortname is the site shortname we consider it a system role assign. 608 if ($data['courseid'] == $SITE->id) { 609 // Frontpage course assign. 610 $context = context_course::instance($data['courseid']); 611 role_assign($data['roleid'], $data['userid'], $context->id); 612 613 } else { 614 // Course assign. 615 $this->datagenerator->enrol_user($data['userid'], $data['courseid'], $data['roleid'], $data['enrol'], 616 $data['timestart'], $data['timeend'], $data['status']); 617 } 618 619 } 620 621 /** 622 * Allows/denies a capability at the specified context 623 * 624 * @throws Exception 625 * @param array $data 626 * @return void 627 */ 628 protected function process_permission_override($data) { 629 630 // Will throw an exception if it does not exist. 631 $context = $this->get_context($data['contextlevel'], $data['reference']); 632 633 switch ($data['permission']) { 634 case get_string('allow', 'role'): 635 $permission = CAP_ALLOW; 636 break; 637 case get_string('prevent', 'role'): 638 $permission = CAP_PREVENT; 639 break; 640 case get_string('prohibit', 'role'): 641 $permission = CAP_PROHIBIT; 642 break; 643 default: 644 throw new Exception('The \'' . $data['permission'] . '\' permission does not exist'); 645 break; 646 } 647 648 if (is_null(get_capability_info($data['capability']))) { 649 throw new Exception('The \'' . $data['capability'] . '\' capability does not exist'); 650 } 651 652 role_change_permission($data['roleid'], $context, $data['capability'], $permission); 653 } 654 655 /** 656 * Assigns a role to a user at system context 657 * 658 * Used by "system role assigns" can be deleted when 659 * system role assign will be deprecated in favour of 660 * "role assigns" 661 * 662 * @throws Exception 663 * @param array $data 664 * @return void 665 */ 666 protected function process_system_role_assign($data) { 667 668 if (empty($data['roleid'])) { 669 throw new Exception('\'system role assigns\' requires the field \'role\' to be specified'); 670 } 671 672 if (!isset($data['userid'])) { 673 throw new Exception('\'system role assigns\' requires the field \'user\' to be specified'); 674 } 675 676 $context = context_system::instance(); 677 678 $this->datagenerator->role_assign($data['roleid'], $data['userid'], $context->id); 679 } 680 681 /** 682 * Assigns a role to a user at the specified context 683 * 684 * @throws Exception 685 * @param array $data 686 * @return void 687 */ 688 protected function process_role_assign($data) { 689 690 if (empty($data['roleid'])) { 691 throw new Exception('\'role assigns\' requires the field \'role\' to be specified'); 692 } 693 694 if (!isset($data['userid'])) { 695 throw new Exception('\'role assigns\' requires the field \'user\' to be specified'); 696 } 697 698 if (empty($data['contextlevel'])) { 699 throw new Exception('\'role assigns\' requires the field \'contextlevel\' to be specified'); 700 } 701 702 if (!isset($data['reference'])) { 703 throw new Exception('\'role assigns\' requires the field \'reference\' to be specified'); 704 } 705 706 // Getting the context id. 707 $context = $this->get_context($data['contextlevel'], $data['reference']); 708 709 $this->datagenerator->role_assign($data['roleid'], $data['userid'], $context->id); 710 } 711 712 /** 713 * Creates a role. 714 * 715 * @param array $data 716 * @return void 717 */ 718 protected function process_role($data) { 719 720 // We require the user to fill the role shortname. 721 if (empty($data['shortname'])) { 722 throw new Exception('\'role\' requires the field \'shortname\' to be specified'); 723 } 724 725 $this->datagenerator->create_role($data); 726 } 727 728 /** 729 * Assign capabilities to a role. 730 * 731 * @param array $data 732 */ 733 protected function process_role_capability($data): void { 734 // We require the user to fill the role shortname. 735 if (empty($data['roleid'])) { 736 throw new Exception('\'role capability\' requires the field \'roleid\' to be specified'); 737 } 738 739 $roleid = $data['roleid']; 740 unset($data['roleid']); 741 742 $this->datagenerator->create_role_capability($roleid, $data, \context_system::instance()); 743 } 744 745 /** 746 * Adds members to cohorts 747 * 748 * @param array $data 749 * @return void 750 */ 751 protected function process_cohort_member($data) { 752 cohort_add_member($data['cohortid'], $data['userid']); 753 } 754 755 /** 756 * Create a question category. 757 * 758 * @param array $data the row of data from the behat script. 759 */ 760 protected function process_question_category($data) { 761 global $DB; 762 763 $context = $this->get_context($data['contextlevel'], $data['reference']); 764 765 // The way this class works, we have already looked up the given parent category 766 // name and found a matching category. However, it is possible, particularly 767 // for the 'top' category, for there to be several categories with the 768 // same name. So far one will have been picked at random, but we need 769 // the one from the right context. So, if we have the wrong category, try again. 770 // (Just fixing it here, rather than getting it right first time, is a bit 771 // of a bodge, but in general this class assumes that names are unique, 772 // and normally they are, so this was the easiest fix.) 773 if (!empty($data['parent'])) { 774 $foundparent = $DB->get_record('question_categories', ['id' => $data['parent']], '*', MUST_EXIST); 775 if ($foundparent->contextid != $context->id) { 776 $rightparentid = $DB->get_field('question_categories', 'id', 777 ['contextid' => $context->id, 'name' => $foundparent->name]); 778 if (!$rightparentid) { 779 throw new Exception('The specified question category with name "' . $foundparent->name . 780 '" does not exist in context "' . $context->get_context_name() . '"."'); 781 } 782 $data['parent'] = $rightparentid; 783 } 784 } 785 786 $data['contextid'] = $context->id; 787 $this->datagenerator->get_plugin_generator('core_question')->create_question_category($data); 788 } 789 790 /** 791 * Create a question. 792 * 793 * Creating questions relies on the question/type/.../tests/helper.php mechanism. 794 * We start with test_question_maker::get_question_form_data($data['qtype'], $data['template']) 795 * and then overlay the values from any other fields of $data that are set. 796 * 797 * There is a special case that allows you to set qtype to 'missingtype'. 798 * This creates an example of broken question, such as you might get if you 799 * install a question type, create some questions of that type, and then 800 * uninstall the question type (which is prevented through the UI but can 801 * still happen). This special lets tests verify that these questions are 802 * handled OK. 803 * 804 * @param array $data the row of data from the behat script. 805 */ 806 protected function process_question($data) { 807 global $DB; 808 809 if (array_key_exists('questiontext', $data)) { 810 $data['questiontext'] = array( 811 'text' => $data['questiontext'], 812 'format' => FORMAT_HTML, 813 ); 814 } 815 816 if (array_key_exists('generalfeedback', $data)) { 817 $data['generalfeedback'] = array( 818 'text' => $data['generalfeedback'], 819 'format' => FORMAT_HTML, 820 ); 821 } 822 823 $which = null; 824 if (!empty($data['template'])) { 825 $which = $data['template']; 826 } 827 828 $missingtypespecialcase = false; 829 if ($data['qtype'] === 'missingtype') { 830 $data['qtype'] = 'essay'; // Actual type uses here does not matter. We just need any question. 831 $missingtypespecialcase = true; 832 } 833 834 $questiondata = $this->datagenerator->get_plugin_generator('core_question') 835 ->create_question($data['qtype'], $which, $data); 836 837 if ($missingtypespecialcase) { 838 $DB->set_field('question', 'qtype', 'unknownqtype', ['id' => $questiondata->id]); 839 } 840 } 841 842 /** 843 * Adds user to contacts 844 * 845 * @param array $data 846 * @return void 847 */ 848 protected function process_message_contacts($data) { 849 \core_message\api::add_contact($data['userid'], $data['contactid']); 850 } 851 852 /** 853 * Send a new message from user to contact in a private conversation 854 * 855 * @param array $data 856 * @return void 857 */ 858 protected function process_private_messages(array $data) { 859 if (empty($data['format'])) { 860 $data['format'] = 'FORMAT_PLAIN'; 861 } 862 863 if (!$conversationid = \core_message\api::get_conversation_between_users([$data['userid'], $data['contactid']])) { 864 $conversation = \core_message\api::create_conversation( 865 \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, 866 [$data['userid'], $data['contactid']] 867 ); 868 $conversationid = $conversation->id; 869 } 870 \core_message\api::send_message_to_conversation( 871 $data['userid'], 872 $conversationid, 873 $data['message'], 874 constant($data['format']) 875 ); 876 } 877 878 /** 879 * Send a new message from user to a group conversation 880 * 881 * @param array $data 882 * @return void 883 */ 884 protected function process_group_messages(array $data) { 885 global $DB; 886 887 if (empty($data['format'])) { 888 $data['format'] = 'FORMAT_PLAIN'; 889 } 890 891 $group = $DB->get_record('groups', ['id' => $data['groupid']]); 892 $coursecontext = context_course::instance($group->courseid); 893 if (!$conversation = \core_message\api::get_conversation_by_area('core_group', 'groups', $data['groupid'], 894 $coursecontext->id)) { 895 $members = $DB->get_records_menu('groups_members', ['groupid' => $data['groupid']], '', 'userid, id'); 896 $conversation = \core_message\api::create_conversation( 897 \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, 898 array_keys($members), 899 $group->name, 900 \core_message\api::MESSAGE_CONVERSATION_ENABLED, 901 'core_group', 902 'groups', 903 $group->id, 904 $coursecontext->id); 905 } 906 \core_message\api::send_message_to_conversation( 907 $data['userid'], 908 $conversation->id, 909 $data['message'], 910 constant($data['format']) 911 ); 912 } 913 914 /** 915 * Mark a private conversation as favourite for user 916 * 917 * @param array $data 918 * @return void 919 */ 920 protected function process_favourite_conversations(array $data) { 921 if (!$conversationid = \core_message\api::get_conversation_between_users([$data['userid'], $data['contactid']])) { 922 $conversation = \core_message\api::create_conversation( 923 \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, 924 [$data['userid'], $data['contactid']] 925 ); 926 $conversationid = $conversation->id; 927 } 928 \core_message\api::set_favourite_conversation($conversationid, $data['userid']); 929 } 930 931 /** 932 * Mute an existing group conversation for user 933 * 934 * @param array $data 935 * @return void 936 */ 937 protected function process_mute_group_conversations(array $data) { 938 if (groups_is_member($data['groupid'], $data['userid'])) { 939 $context = context_course::instance($data['courseid']); 940 $conversation = \core_message\api::get_conversation_by_area( 941 'core_group', 942 'groups', 943 $data['groupid'], 944 $context->id 945 ); 946 if ($conversation) { 947 \core_message\api::mute_conversation($data['userid'], $conversation->id); 948 } 949 } 950 } 951 952 /** 953 * Mute a private conversation for user 954 * 955 * @param array $data 956 * @return void 957 */ 958 protected function process_mute_private_conversations(array $data) { 959 if (!$conversationid = \core_message\api::get_conversation_between_users([$data['userid'], $data['contactid']])) { 960 $conversation = \core_message\api::create_conversation( 961 \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, 962 [$data['userid'], $data['contactid']] 963 ); 964 $conversationid = $conversation->id; 965 } 966 \core_message\api::mute_conversation($data['userid'], $conversationid); 967 } 968 969 /** 970 * Transform indicators string into array. 971 * 972 * @param array $data 973 * @return array 974 */ 975 protected function preprocess_analytics_model($data) { 976 $data['indicators'] = explode(',', $data['indicators']); 977 return $data; 978 } 979 980 /** 981 * Creates an analytics model 982 * 983 * @param target $data 984 * @return void 985 */ 986 protected function process_analytics_model($data) { 987 \core_analytics\manager::create_declared_model($data); 988 } 989 990 /** 991 * Set a preference value for user 992 * 993 * @param array $data 994 * @return void 995 */ 996 protected function process_user_preferences(array $data) { 997 set_user_preference($data['preference'], $data['value'], $data['userid']); 998 } 999 1000 /** 1001 * Create content in the given context's content bank. 1002 * 1003 * @param array $data 1004 * @return void 1005 */ 1006 protected function process_contentbank_content(array $data) { 1007 global $CFG; 1008 1009 if (empty($data['contextlevel'])) { 1010 throw new Exception('contentbank_content requires the field contextlevel to be specified'); 1011 } 1012 1013 if (!isset($data['reference'])) { 1014 throw new Exception('contentbank_content requires the field reference to be specified'); 1015 } 1016 1017 if (empty($data['contenttype'])) { 1018 throw new Exception('contentbank_content requires the field contenttype to be specified'); 1019 } 1020 1021 $contenttypeclass = "\\".$data['contenttype']."\\contenttype"; 1022 if (class_exists($contenttypeclass)) { 1023 $context = $this->get_context($data['contextlevel'], $data['reference']); 1024 $contenttype = new $contenttypeclass($context); 1025 $record = new stdClass(); 1026 $record->usercreated = $data['userid']; 1027 $record->name = $data['contentname']; 1028 if (isset($data['visibility'])) { 1029 $record->visibility = $data['visibility']; 1030 } 1031 $content = $contenttype->create_content($record); 1032 1033 if (!empty($data['filepath'])) { 1034 $filename = basename($data['filepath']); 1035 $fs = get_file_storage(); 1036 $filerecord = array( 1037 'component' => 'contentbank', 1038 'filearea' => 'public', 1039 'contextid' => $context->id, 1040 'userid' => $data['userid'], 1041 'itemid' => $content->get_id(), 1042 'filename' => $filename, 1043 'filepath' => '/' 1044 ); 1045 $fs->create_file_from_pathname($filerecord, $CFG->dirroot . $data['filepath']); 1046 } 1047 } else { 1048 throw new Exception('The specified "' . $data['contenttype'] . '" contenttype does not exist'); 1049 } 1050 } 1051 1052 /** 1053 * Create a exetrnal backpack. 1054 * 1055 * @param array $data 1056 */ 1057 protected function process_badge_external_backpack(array $data) { 1058 global $DB; 1059 $DB->insert_record('badge_external_backpack', $data, true); 1060 } 1061 1062 /** 1063 * Setup a backpack connected for user. 1064 * 1065 * @param array $data 1066 * @throws dml_exception 1067 */ 1068 protected function process_setup_backpack_connected(array $data) { 1069 global $DB; 1070 1071 if (empty($data['userid'])) { 1072 throw new Exception('\'setup backpack connected\' requires the field \'user\' to be specified'); 1073 } 1074 if (empty($data['externalbackpackid'])) { 1075 throw new Exception('\'setup backpack connected\' requires the field \'externalbackpack\' to be specified'); 1076 } 1077 // Dummy badge_backpack_oauth2 data. 1078 $timenow = time(); 1079 $backpackoauth2 = new stdClass(); 1080 $backpackoauth2->usermodified = $data['userid']; 1081 $backpackoauth2->timecreated = $timenow; 1082 $backpackoauth2->timemodified = $timenow; 1083 $backpackoauth2->userid = $data['userid']; 1084 $backpackoauth2->issuerid = 1; 1085 $backpackoauth2->externalbackpackid = $data['externalbackpackid']; 1086 $backpackoauth2->token = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 1087 $backpackoauth2->refreshtoken = '0123456789abcdefghijk'; 1088 $backpackoauth2->expires = $timenow + 3600; 1089 $backpackoauth2->scope = 'https://purl.imsglobal.org/spec/ob/v2p1/scope/assertion.create'; 1090 $backpackoauth2->scope .= ' https://purl.imsglobal.org/spec/ob/v2p1/scope/assertion.readonly offline_access'; 1091 $DB->insert_record('badge_backpack_oauth2', $backpackoauth2); 1092 1093 // Dummy badge_backpack data. 1094 $backpack = new stdClass(); 1095 $backpack->userid = $data['userid']; 1096 $backpack->email = 'student@behat.moodle'; 1097 $backpack->backpackuid = 0; 1098 $backpack->autosync = 0; 1099 $backpack->password = ''; 1100 $backpack->externalbackpackid = $data['externalbackpackid']; 1101 $DB->insert_record('badge_backpack', $backpack); 1102 } 1103 1104 /** 1105 * Creates notifications to specific user. 1106 * 1107 * @param array $data 1108 * @return void 1109 */ 1110 protected function process_notification(array $data) { 1111 global $DB; 1112 1113 $notification = new stdClass(); 1114 $notification->useridfrom = $data['userfromid']; 1115 $notification->useridto = $data['usertoid']; 1116 $notification->subject = $data['subject']; 1117 $notification->fullmessage = $data['subject'] . ' description'; 1118 $notification->smallmessage = $data['subject'] . ' description'; 1119 $notification->fullmessagehtml = $data['subject'] . ' description'; 1120 1121 if ($data['timecreated'] !== 'null') { 1122 $notification->timecreated = $data['timecreated']; 1123 } 1124 1125 if ($data['timeread'] !== 'null') { 1126 $notification->timeread = $data['timeread']; 1127 } 1128 1129 if (!empty($data)) { 1130 $popupnotification = new stdClass(); 1131 $popupnotification->notificationid = $DB->insert_record('notifications', $notification); 1132 $DB->insert_record('message_popup_notifications', $popupnotification); 1133 } 1134 1135 } 1136 1137 /** 1138 * Creates user last access data within given courses. 1139 * 1140 * @param array $data 1141 * @return void 1142 */ 1143 protected function process_last_access_times(array $data) { 1144 global $DB; 1145 1146 if (!isset($data['userid'])) { 1147 throw new Exception('\'last acces times\' requires the field \'user\' to be specified'); 1148 } 1149 1150 if (!isset($data['courseid'])) { 1151 throw new Exception('\'last acces times\' requires the field \'course\' to be specified'); 1152 } 1153 1154 if (!isset($data['lastaccess'])) { 1155 throw new Exception('\'last acces times\' requires the field \'lastaccess\' to be specified'); 1156 } 1157 1158 $userdata = []; 1159 $userdata['old'] = $DB->get_record('user', ['id' => $data['userid']], 'firstaccess, lastaccess, lastlogin, currentlogin'); 1160 $userdata['new'] = [ 1161 'firstaccess' => $userdata['old']->firstaccess, 1162 'lastaccess' => $userdata['old']->lastaccess, 1163 'lastlogin' => $userdata['old']->lastlogin, 1164 'currentlogin' => $userdata['old']->currentlogin, 1165 ]; 1166 1167 // Check for lastaccess data for this course. 1168 $lastaccessdata = [ 1169 'userid' => $data['userid'], 1170 'courseid' => $data['courseid'], 1171 ]; 1172 1173 $lastaccessid = $DB->get_field('user_lastaccess', 'id', $lastaccessdata); 1174 1175 $dbdata = (object) $lastaccessdata; 1176 $dbdata->timeaccess = $data['lastaccess']; 1177 1178 // Set the course last access time. 1179 if ($lastaccessid) { 1180 $dbdata->id = $lastaccessid; 1181 $DB->update_record('user_lastaccess', $dbdata); 1182 } else { 1183 $DB->insert_record('user_lastaccess', $dbdata); 1184 } 1185 1186 // Store changes to other user access times as needed. 1187 1188 // Update first access if this is the user's first login, or this access is earlier than their current first access. 1189 if (empty($userdata['new']['firstaccess']) || 1190 $userdata['new']['firstaccess'] > $data['lastaccess']) { 1191 $userdata['new']['firstaccess'] = $data['lastaccess']; 1192 } 1193 1194 // Update last access if it is the user's most recent access. 1195 if (empty($userdata['new']['lastaccess']) || 1196 $userdata['new']['lastaccess'] < $data['lastaccess']) { 1197 $userdata['new']['lastaccess'] = $data['lastaccess']; 1198 } 1199 1200 // Update last and current login if it is the user's most recent access. 1201 if (empty($userdata['new']['lastlogin']) || 1202 $userdata['new']['lastlogin'] < $data['lastaccess']) { 1203 $userdata['new']['lastlogin'] = $data['lastaccess']; 1204 $userdata['new']['currentlogin'] = $data['lastaccess']; 1205 } 1206 1207 $updatedata = []; 1208 1209 if ($userdata['new']['firstaccess'] != $userdata['old']->firstaccess) { 1210 $updatedata['firstaccess'] = $userdata['new']['firstaccess']; 1211 } 1212 1213 if ($userdata['new']['lastaccess'] != $userdata['old']->lastaccess) { 1214 $updatedata['lastaccess'] = $userdata['new']['lastaccess']; 1215 } 1216 1217 if ($userdata['new']['lastlogin'] != $userdata['old']->lastlogin) { 1218 $updatedata['lastlogin'] = $userdata['new']['lastlogin']; 1219 } 1220 1221 if ($userdata['new']['currentlogin'] != $userdata['old']->currentlogin) { 1222 $updatedata['currentlogin'] = $userdata['new']['currentlogin']; 1223 } 1224 1225 // Only update user access data if there have been any changes. 1226 if (!empty($updatedata)) { 1227 $updatedata['id'] = $data['userid']; 1228 $updatedata = (object) $updatedata; 1229 $DB->update_record('user', $updatedata); 1230 } 1231 } 1232 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body