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