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 generator. 19 * 20 * @package core 21 * @category test 22 * @copyright 2012 Petr Skoda {@link http://skodak.org} 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26defined('MOODLE_INTERNAL') || die(); 27 28/** 29 * Data generator class for unit tests and other tools that need to create fake test sites. 30 * 31 * @package core 32 * @category test 33 * @copyright 2012 Petr Skoda {@link http://skodak.org} 34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 */ 36class testing_data_generator { 37 /** @var int The number of grade categories created */ 38 protected $gradecategorycounter = 0; 39 /** @var int The number of grade items created */ 40 protected $gradeitemcounter = 0; 41 /** @var int The number of grade outcomes created */ 42 protected $gradeoutcomecounter = 0; 43 protected $usercounter = 0; 44 protected $categorycount = 0; 45 protected $cohortcount = 0; 46 protected $coursecount = 0; 47 protected $scalecount = 0; 48 protected $groupcount = 0; 49 protected $groupingcount = 0; 50 protected $rolecount = 0; 51 protected $tagcount = 0; 52 53 /** @var array list of plugin generators */ 54 protected $generators = array(); 55 56 /** @var array lis of common last names */ 57 public $lastnames = array( 58 'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Miller', 'Davis', 'García', 'Rodríguez', 'Wilson', 59 'Müller', 'Schmidt', 'Schneider', 'Fischer', 'Meyer', 'Weber', 'Schulz', 'Wagner', 'Becker', 'Hoffmann', 60 'Novák', 'Svoboda', 'Novotný', 'Dvořák', 'Černý', 'Procházková', 'Kučerová', 'Veselá', 'Horáková', 'Němcová', 61 'Смирнов', 'Иванов', 'Кузнецов', 'Соколов', 'Попов', 'Лебедева', 'Козлова', 'Новикова', 'Морозова', 'Петрова', 62 '王', '李', '张', '刘', '陈', '楊', '黃', '趙', '吳', '周', 63 '佐藤', '鈴木', '高橋', '田中', '渡辺', '伊藤', '山本', '中村', '小林', '斎藤', 64 ); 65 66 /** @var array lis of common first names */ 67 public $firstnames = array( 68 'Jacob', 'Ethan', 'Michael', 'Jayden', 'William', 'Isabella', 'Sophia', 'Emma', 'Olivia', 'Ava', 69 'Lukas', 'Leon', 'Luca', 'Timm', 'Paul', 'Leonie', 'Leah', 'Lena', 'Hanna', 'Laura', 70 'Jakub', 'Jan', 'Tomáš', 'Lukáš', 'Matěj', 'Tereza', 'Eliška', 'Anna', 'Adéla', 'Karolína', 71 'Даниил', 'Максим', 'Артем', 'Иван', 'Александр', 'София', 'Анастасия', 'Дарья', 'Мария', 'Полина', 72 '伟', '伟', '芳', '伟', '秀英', '秀英', '娜', '秀英', '伟', '敏', 73 '翔', '大翔', '拓海', '翔太', '颯太', '陽菜', 'さくら', '美咲', '葵', '美羽', 74 ); 75 76 public $loremipsum = <<<EOD 77Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla non arcu lacinia neque faucibus fringilla. Vivamus porttitor turpis ac leo. Integer in sapien. Nullam eget nisl. Aliquam erat volutpat. Cras elementum. Mauris suscipit, ligula sit amet pharetra semper, nibh ante cursus purus, vel sagittis velit mauris vel metus. Integer malesuada. Nullam lectus justo, vulputate eget mollis sed, tempor sed magna. Mauris elementum mauris vitae tortor. Aliquam erat volutpat. 78Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Pellentesque ipsum. Cras pede libero, dapibus nec, pretium sit amet, tempor quis. Aliquam ante. Proin in tellus sit amet nibh dignissim sagittis. Vivamus porttitor turpis ac leo. Duis bibendum, lectus ut viverra rhoncus, dolor nunc faucibus libero, eget facilisis enim ipsum id lacus. In sem justo, commodo ut, suscipit at, pharetra vitae, orci. Aliquam erat volutpat. Nulla est. 79Vivamus luctus egestas leo. Aenean fermentum risus id tortor. Mauris dictum facilisis augue. Aliquam erat volutpat. Aliquam ornare wisi eu metus. Aliquam id dolor. Duis condimentum augue id magna semper rutrum. Donec iaculis gravida nulla. Pellentesque ipsum. Etiam dictum tincidunt diam. Quisque tincidunt scelerisque libero. Etiam egestas wisi a erat. 80Integer lacinia. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris tincidunt sem sed arcu. Nullam feugiat, turpis at pulvinar vulputate, erat libero tristique tellus, nec bibendum odio risus sit amet ante. Aliquam id dolor. Maecenas sollicitudin. Et harum quidem rerum facilis est et expedita distinctio. Mauris suscipit, ligula sit amet pharetra semper, nibh ante cursus purus, vel sagittis velit mauris vel metus. Nullam dapibus fermentum ipsum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Pellentesque sapien. Duis risus. Mauris elementum mauris vitae tortor. Suspendisse nisl. Integer rutrum, orci vestibulum ullamcorper ultricies, lacus quam ultricies odio, vitae placerat pede sem sit amet enim. 81In laoreet, magna id viverra tincidunt, sem odio bibendum justo, vel imperdiet sapien wisi sed libero. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Nullam justo enim, consectetuer nec, ullamcorper ac, vestibulum in, elit. Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? Maecenas lorem. Etiam posuere lacus quis dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Nam sed tellus id magna elementum tincidunt. Suspendisse nisl. Vivamus luctus egestas leo. Nulla non arcu lacinia neque faucibus fringilla. Etiam dui sem, fermentum vitae, sagittis id, malesuada in, quam. Etiam dictum tincidunt diam. Etiam commodo dui eget wisi. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Duis ante orci, molestie vitae vehicula venenatis, tincidunt ac pede. Pellentesque sapien. 82EOD; 83 84 /** 85 * To be called from data reset code only, 86 * do not use in tests. 87 * @return void 88 */ 89 public function reset() { 90 $this->usercounter = 0; 91 $this->categorycount = 0; 92 $this->coursecount = 0; 93 $this->scalecount = 0; 94 95 foreach ($this->generators as $generator) { 96 $generator->reset(); 97 } 98 } 99 100 /** 101 * Return generator for given plugin or component. 102 * @param string $component the component name, e.g. 'mod_forum' or 'core_question'. 103 * @return component_generator_base or rather an instance of the appropriate subclass. 104 */ 105 public function get_plugin_generator($component) { 106 // Note: This global is included so that generator have access to it. 107 // CFG is widely used in require statements. 108 global $CFG; 109 list($type, $plugin) = core_component::normalize_component($component); 110 $cleancomponent = $type . '_' . $plugin; 111 if ($cleancomponent != $component) { 112 debugging("Please specify the component you want a generator for as " . 113 "{$cleancomponent}, not {$component}.", DEBUG_DEVELOPER); 114 $component = $cleancomponent; 115 } 116 117 if (isset($this->generators[$component])) { 118 return $this->generators[$component]; 119 } 120 121 $dir = core_component::get_component_directory($component); 122 $lib = $dir . '/tests/generator/lib.php'; 123 if (!$dir || !is_readable($lib)) { 124 throw new coding_exception("Component {$component} does not support " . 125 "generators yet. Missing tests/generator/lib.php."); 126 } 127 128 include_once($lib); 129 $classname = $component . '_generator'; 130 131 if (!class_exists($classname)) { 132 throw new coding_exception("Component {$component} does not support " . 133 "data generators yet. Class {$classname} not found."); 134 } 135 136 $this->generators[$component] = new $classname($this); 137 return $this->generators[$component]; 138 } 139 140 /** 141 * Create a test user 142 * @param array|stdClass $record 143 * @param array $options 144 * @return stdClass user record 145 */ 146 public function create_user($record=null, array $options=null) { 147 global $DB, $CFG; 148 require_once($CFG->dirroot.'/user/lib.php'); 149 150 $this->usercounter++; 151 $i = $this->usercounter; 152 153 $record = (array)$record; 154 155 if (!isset($record['auth'])) { 156 $record['auth'] = 'manual'; 157 } 158 159 if (!isset($record['firstname']) and !isset($record['lastname'])) { 160 $country = rand(0, 5); 161 $firstname = rand(0, 4); 162 $lastname = rand(0, 4); 163 $female = rand(0, 1); 164 $record['firstname'] = $this->firstnames[($country*10) + $firstname + ($female*5)]; 165 $record['lastname'] = $this->lastnames[($country*10) + $lastname + ($female*5)]; 166 167 } else if (!isset($record['firstname'])) { 168 $record['firstname'] = 'Firstname'.$i; 169 170 } else if (!isset($record['lastname'])) { 171 $record['lastname'] = 'Lastname'.$i; 172 } 173 174 if (!isset($record['firstnamephonetic'])) { 175 $firstnamephonetic = rand(0, 59); 176 $record['firstnamephonetic'] = $this->firstnames[$firstnamephonetic]; 177 } 178 179 if (!isset($record['lastnamephonetic'])) { 180 $lastnamephonetic = rand(0, 59); 181 $record['lastnamephonetic'] = $this->lastnames[$lastnamephonetic]; 182 } 183 184 if (!isset($record['middlename'])) { 185 $middlename = rand(0, 59); 186 $record['middlename'] = $this->firstnames[$middlename]; 187 } 188 189 if (!isset($record['alternatename'])) { 190 $alternatename = rand(0, 59); 191 $record['alternatename'] = $this->firstnames[$alternatename]; 192 } 193 194 if (!isset($record['idnumber'])) { 195 $record['idnumber'] = ''; 196 } 197 198 if (!isset($record['mnethostid'])) { 199 $record['mnethostid'] = $CFG->mnet_localhost_id; 200 } 201 202 if (!isset($record['username'])) { 203 $record['username'] = 'username'.$i; 204 $j = 2; 205 while ($DB->record_exists('user', array('username'=>$record['username'], 'mnethostid'=>$record['mnethostid']))) { 206 $record['username'] = 'username'.$i.'_'.$j; 207 $j++; 208 } 209 } 210 211 if (isset($record['password'])) { 212 $record['password'] = hash_internal_user_password($record['password']); 213 } 214 215 if (!isset($record['email'])) { 216 $record['email'] = $record['username'].'@example.com'; 217 } 218 219 if (!isset($record['confirmed'])) { 220 $record['confirmed'] = 1; 221 } 222 223 if (!isset($record['lastip'])) { 224 $record['lastip'] = '0.0.0.0'; 225 } 226 227 $tobedeleted = !empty($record['deleted']); 228 unset($record['deleted']); 229 230 $userid = user_create_user($record, false, false); 231 232 if ($extrafields = array_intersect_key($record, ['password' => 1, 'timecreated' => 1])) { 233 $DB->update_record('user', ['id' => $userid] + $extrafields); 234 } 235 236 if (!$tobedeleted) { 237 // All new not deleted users must have a favourite self-conversation. 238 $selfconversation = \core_message\api::create_conversation( 239 \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, 240 [$userid] 241 ); 242 \core_message\api::set_favourite_conversation($selfconversation->id, $userid); 243 244 // Save custom profile fields data. 245 $hasprofilefields = array_filter($record, function($key){ 246 return strpos($key, 'profile_field_') === 0; 247 }, ARRAY_FILTER_USE_KEY); 248 if ($hasprofilefields) { 249 require_once($CFG->dirroot.'/user/profile/lib.php'); 250 $usernew = (object)(['id' => $userid] + $record); 251 profile_save_data($usernew); 252 } 253 } 254 255 $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST); 256 257 if (!$tobedeleted && isset($record['interests'])) { 258 require_once($CFG->dirroot . '/user/editlib.php'); 259 if (!is_array($record['interests'])) { 260 $record['interests'] = preg_split('/\s*,\s*/', trim($record['interests']), -1, PREG_SPLIT_NO_EMPTY); 261 } 262 useredit_update_interests($user, $record['interests']); 263 } 264 265 \core\event\user_created::create_from_userid($userid)->trigger(); 266 267 if ($tobedeleted) { 268 delete_user($user); 269 $user = $DB->get_record('user', array('id' => $userid)); 270 } 271 return $user; 272 } 273 274 /** 275 * Create a test course category 276 * @param array|stdClass $record 277 * @param array $options 278 * @return core_course_category course category record 279 */ 280 public function create_category($record=null, array $options=null) { 281 $this->categorycount++; 282 $i = $this->categorycount; 283 284 $record = (array)$record; 285 286 if (!isset($record['name'])) { 287 $record['name'] = 'Course category '.$i; 288 } 289 290 if (!isset($record['description'])) { 291 $record['description'] = "Test course category $i\n$this->loremipsum"; 292 } 293 294 if (!isset($record['idnumber'])) { 295 $record['idnumber'] = ''; 296 } 297 298 return core_course_category::create($record); 299 } 300 301 /** 302 * Create test cohort. 303 * @param array|stdClass $record 304 * @param array $options 305 * @return stdClass cohort record 306 */ 307 public function create_cohort($record=null, array $options=null) { 308 global $DB, $CFG; 309 require_once("$CFG->dirroot/cohort/lib.php"); 310 311 $this->cohortcount++; 312 $i = $this->cohortcount; 313 314 $record = (array)$record; 315 316 if (!isset($record['contextid'])) { 317 $record['contextid'] = context_system::instance()->id; 318 } 319 320 if (!isset($record['name'])) { 321 $record['name'] = 'Cohort '.$i; 322 } 323 324 if (!isset($record['idnumber'])) { 325 $record['idnumber'] = ''; 326 } 327 328 if (!isset($record['description'])) { 329 $record['description'] = "Description for '{$record['name']}' \n$this->loremipsum"; 330 } 331 332 if (!isset($record['descriptionformat'])) { 333 $record['descriptionformat'] = FORMAT_MOODLE; 334 } 335 336 if (!isset($record['visible'])) { 337 $record['visible'] = 1; 338 } 339 340 if (!isset($record['component'])) { 341 $record['component'] = ''; 342 } 343 344 $id = cohort_add_cohort((object)$record); 345 346 return $DB->get_record('cohort', array('id'=>$id), '*', MUST_EXIST); 347 } 348 349 /** 350 * Create a test course 351 * @param array|stdClass $record 352 * @param array $options with keys: 353 * 'createsections'=>bool precreate all sections 354 * @return stdClass course record 355 */ 356 public function create_course($record=null, array $options=null) { 357 global $DB, $CFG; 358 require_once("$CFG->dirroot/course/lib.php"); 359 360 $this->coursecount++; 361 $i = $this->coursecount; 362 363 $record = (array)$record; 364 365 if (!isset($record['fullname'])) { 366 $record['fullname'] = 'Test course '.$i; 367 } 368 369 if (!isset($record['shortname'])) { 370 $record['shortname'] = 'tc_'.$i; 371 } 372 373 if (!isset($record['idnumber'])) { 374 $record['idnumber'] = ''; 375 } 376 377 if (!isset($record['format'])) { 378 $record['format'] = 'topics'; 379 } 380 381 if (!isset($record['newsitems'])) { 382 $record['newsitems'] = 0; 383 } 384 385 if (!isset($record['numsections'])) { 386 $record['numsections'] = 5; 387 } 388 389 if (!isset($record['summary'])) { 390 $record['summary'] = "Test course $i\n$this->loremipsum"; 391 } 392 393 if (!isset($record['summaryformat'])) { 394 $record['summaryformat'] = FORMAT_MOODLE; 395 } 396 397 if (!isset($record['category'])) { 398 $record['category'] = $DB->get_field_select('course_categories', "MIN(id)", "parent=0"); 399 } 400 401 if (!isset($record['startdate'])) { 402 $record['startdate'] = usergetmidnight(time()); 403 } 404 405 if (isset($record['tags']) && !is_array($record['tags'])) { 406 $record['tags'] = preg_split('/\s*,\s*/', trim($record['tags']), -1, PREG_SPLIT_NO_EMPTY); 407 } 408 409 if (!empty($options['createsections']) && empty($record['numsections'])) { 410 // Since Moodle 3.3 function create_course() automatically creates sections if numsections is specified. 411 // For BC if 'createsections' is given but 'numsections' is not, assume the default value from config. 412 $record['numsections'] = get_config('moodlecourse', 'numsections'); 413 } 414 415 if (!empty($record['customfields'])) { 416 foreach ($record['customfields'] as $field) { 417 $record['customfield_'.$field['shortname']] = $field['value']; 418 } 419 } 420 421 $course = create_course((object)$record); 422 context_course::instance($course->id); 423 424 return $course; 425 } 426 427 /** 428 * Create course section if does not exist yet 429 * @param array|stdClass $record must contain 'course' and 'section' attributes 430 * @param array|null $options 431 * @return stdClass 432 * @throws coding_exception 433 */ 434 public function create_course_section($record = null, array $options = null) { 435 global $DB; 436 437 $record = (array)$record; 438 439 if (empty($record['course'])) { 440 throw new coding_exception('course must be present in testing_data_generator::create_course_section() $record'); 441 } 442 443 if (!isset($record['section'])) { 444 throw new coding_exception('section must be present in testing_data_generator::create_course_section() $record'); 445 } 446 447 course_create_sections_if_missing($record['course'], $record['section']); 448 return get_fast_modinfo($record['course'])->get_section_info($record['section']); 449 } 450 451 /** 452 * Create a test block. 453 * 454 * The $record passed in becomes the basis for the new row added to the 455 * block_instances table. You only need to supply the values of interest. 456 * Any missing values have sensible defaults filled in, and ->blockname will be set based on $blockname. 457 * 458 * The $options array provides additional data, not directly related to what 459 * will be inserted in the block_instance table, which may affect the block 460 * that is created. The meanings of any data passed here depends on the particular 461 * type of block being created. 462 * 463 * @param string $blockname the type of block to create. E.g. 'html'. 464 * @param array|stdClass $record forms the basis for the entry to be inserted in the block_instances table. 465 * @param array $options further, block-specific options to control how the block is created. 466 * @return stdClass new block_instance record. 467 */ 468 public function create_block($blockname, $record=null, array $options=array()) { 469 $generator = $this->get_plugin_generator('block_'.$blockname); 470 return $generator->create_instance($record, $options); 471 } 472 473 /** 474 * Create a test activity module. 475 * 476 * The $record should contain the same data that you would call from 477 * ->get_data() when the mod_[type]_mod_form is submitted, except that you 478 * only need to supply values of interest. The only required value is 479 * 'course'. Any missing values will have a sensible default supplied. 480 * 481 * The $options array provides additional data, not directly related to what 482 * would come back from the module edit settings form, which may affect the activity 483 * that is created. The meanings of any data passed here depends on the particular 484 * type of activity being created. 485 * 486 * @param string $modulename the type of activity to create. E.g. 'forum' or 'quiz'. 487 * @param array|stdClass $record data, as if from the module edit settings form. 488 * @param array $options additional data that may affect how the module is created. 489 * @return stdClass activity record new new record that was just inserted in the table 490 * like 'forum' or 'quiz', with a ->cmid field added. 491 */ 492 public function create_module($modulename, $record=null, array $options=null) { 493 $generator = $this->get_plugin_generator('mod_'.$modulename); 494 return $generator->create_instance($record, $options); 495 } 496 497 /** 498 * Create a test group for the specified course 499 * 500 * $record should be either an array or a stdClass containing infomation about the group to create. 501 * At the very least it needs to contain courseid. 502 * Default values are added for name, description, and descriptionformat if they are not present. 503 * 504 * This function calls groups_create_group() to create the group within the database. 505 * @see groups_create_group 506 * @param array|stdClass $record 507 * @return stdClass group record 508 */ 509 public function create_group($record) { 510 global $DB, $CFG; 511 512 require_once($CFG->dirroot . '/group/lib.php'); 513 514 $this->groupcount++; 515 $i = str_pad($this->groupcount, 4, '0', STR_PAD_LEFT); 516 517 $record = (array)$record; 518 519 if (empty($record['courseid'])) { 520 throw new coding_exception('courseid must be present in testing_data_generator::create_group() $record'); 521 } 522 523 if (!isset($record['name'])) { 524 $record['name'] = 'group-' . $i; 525 } 526 527 if (!isset($record['description'])) { 528 $record['description'] = "Test Group $i\n{$this->loremipsum}"; 529 } 530 531 if (!isset($record['descriptionformat'])) { 532 $record['descriptionformat'] = FORMAT_MOODLE; 533 } 534 535 $id = groups_create_group((object)$record); 536 537 // Allow tests to set group pictures. 538 if (!empty($record['picturepath'])) { 539 require_once($CFG->dirroot . '/lib/gdlib.php'); 540 $grouppicture = process_new_icon(\context_course::instance($record['courseid']), 'group', 'icon', $id, 541 $record['picturepath']); 542 543 $DB->set_field('groups', 'picture', $grouppicture, ['id' => $id]); 544 545 // Invalidate the group data as we've updated the group record. 546 cache_helper::invalidate_by_definition('core', 'groupdata', array(), [$record['courseid']]); 547 } 548 549 return $DB->get_record('groups', array('id'=>$id)); 550 } 551 552 /** 553 * Create a test group member 554 * @param array|stdClass $record 555 * @throws coding_exception 556 * @return boolean 557 */ 558 public function create_group_member($record) { 559 global $DB, $CFG; 560 561 require_once($CFG->dirroot . '/group/lib.php'); 562 563 $record = (array)$record; 564 565 if (empty($record['userid'])) { 566 throw new coding_exception('user must be present in testing_util::create_group_member() $record'); 567 } 568 569 if (!isset($record['groupid'])) { 570 throw new coding_exception('group must be present in testing_util::create_group_member() $record'); 571 } 572 573 if (!isset($record['component'])) { 574 $record['component'] = null; 575 } 576 if (!isset($record['itemid'])) { 577 $record['itemid'] = 0; 578 } 579 580 return groups_add_member($record['groupid'], $record['userid'], $record['component'], $record['itemid']); 581 } 582 583 /** 584 * Create a test grouping for the specified course 585 * 586 * $record should be either an array or a stdClass containing infomation about the grouping to create. 587 * At the very least it needs to contain courseid. 588 * Default values are added for name, description, and descriptionformat if they are not present. 589 * 590 * This function calls groups_create_grouping() to create the grouping within the database. 591 * @see groups_create_grouping 592 * @param array|stdClass $record 593 * @return stdClass grouping record 594 */ 595 public function create_grouping($record) { 596 global $DB, $CFG; 597 598 require_once($CFG->dirroot . '/group/lib.php'); 599 600 $this->groupingcount++; 601 $i = $this->groupingcount; 602 603 $record = (array)$record; 604 605 if (empty($record['courseid'])) { 606 throw new coding_exception('courseid must be present in testing_data_generator::create_grouping() $record'); 607 } 608 609 if (!isset($record['name'])) { 610 $record['name'] = 'grouping-' . $i; 611 } 612 613 if (!isset($record['description'])) { 614 $record['description'] = "Test Grouping $i\n{$this->loremipsum}"; 615 } 616 617 if (!isset($record['descriptionformat'])) { 618 $record['descriptionformat'] = FORMAT_MOODLE; 619 } 620 621 $id = groups_create_grouping((object)$record); 622 623 return $DB->get_record('groupings', array('id'=>$id)); 624 } 625 626 /** 627 * Create a test grouping group 628 * @param array|stdClass $record 629 * @throws coding_exception 630 * @return boolean 631 */ 632 public function create_grouping_group($record) { 633 global $DB, $CFG; 634 635 require_once($CFG->dirroot . '/group/lib.php'); 636 637 $record = (array)$record; 638 639 if (empty($record['groupingid'])) { 640 throw new coding_exception('grouping must be present in testing::create_grouping_group() $record'); 641 } 642 643 if (!isset($record['groupid'])) { 644 throw new coding_exception('group must be present in testing_util::create_grouping_group() $record'); 645 } 646 647 return groups_assign_grouping($record['groupingid'], $record['groupid']); 648 } 649 650 /** 651 * Create an instance of a repository. 652 * 653 * @param string type of repository to create an instance for. 654 * @param array|stdClass $record data to use to up set the instance. 655 * @param array $options options 656 * @return stdClass repository instance record 657 * @since Moodle 2.5.1 658 */ 659 public function create_repository($type, $record=null, array $options = null) { 660 $generator = $this->get_plugin_generator('repository_'.$type); 661 return $generator->create_instance($record, $options); 662 } 663 664 /** 665 * Create an instance of a repository. 666 * 667 * @param string type of repository to create an instance for. 668 * @param array|stdClass $record data to use to up set the instance. 669 * @param array $options options 670 * @return repository_type object 671 * @since Moodle 2.5.1 672 */ 673 public function create_repository_type($type, $record=null, array $options = null) { 674 $generator = $this->get_plugin_generator('repository_'.$type); 675 return $generator->create_type($record, $options); 676 } 677 678 679 /** 680 * Create a test scale 681 * @param array|stdClass $record 682 * @param array $options 683 * @return stdClass block instance record 684 */ 685 public function create_scale($record=null, array $options=null) { 686 global $DB; 687 688 $this->scalecount++; 689 $i = $this->scalecount; 690 691 $record = (array)$record; 692 693 if (!isset($record['name'])) { 694 $record['name'] = 'Test scale '.$i; 695 } 696 697 if (!isset($record['scale'])) { 698 $record['scale'] = 'A,B,C,D,F'; 699 } 700 701 if (!isset($record['courseid'])) { 702 $record['courseid'] = 0; 703 } 704 705 if (!isset($record['userid'])) { 706 $record['userid'] = 0; 707 } 708 709 if (!isset($record['description'])) { 710 $record['description'] = 'Test scale description '.$i; 711 } 712 713 if (!isset($record['descriptionformat'])) { 714 $record['descriptionformat'] = FORMAT_MOODLE; 715 } 716 717 $record['timemodified'] = time(); 718 719 if (isset($record['id'])) { 720 $DB->import_record('scale', $record); 721 $DB->get_manager()->reset_sequence('scale'); 722 $id = $record['id']; 723 } else { 724 $id = $DB->insert_record('scale', $record); 725 } 726 727 return $DB->get_record('scale', array('id'=>$id), '*', MUST_EXIST); 728 } 729 730 /** 731 * Creates a new role in the system. 732 * 733 * You can fill $record with the role 'name', 734 * 'shortname', 'description' and 'archetype'. 735 * 736 * If an archetype is specified it's capabilities, 737 * context where the role can be assigned and 738 * all other properties are copied from the archetype; 739 * if no archetype is specified it will create an 740 * empty role. 741 * 742 * @param array|stdClass $record 743 * @return int The new role id 744 */ 745 public function create_role($record=null) { 746 global $DB; 747 748 $this->rolecount++; 749 $i = $this->rolecount; 750 751 $record = (array)$record; 752 753 if (empty($record['shortname'])) { 754 $record['shortname'] = 'role-' . $i; 755 } 756 757 if (empty($record['name'])) { 758 $record['name'] = 'Test role ' . $i; 759 } 760 761 if (empty($record['description'])) { 762 $record['description'] = 'Test role ' . $i . ' description'; 763 } 764 765 if (empty($record['archetype'])) { 766 $record['archetype'] = ''; 767 } else { 768 $archetypes = get_role_archetypes(); 769 if (empty($archetypes[$record['archetype']])) { 770 throw new coding_exception('\'role\' requires the field \'archetype\' to specify a ' . 771 'valid archetype shortname (editingteacher, student...)'); 772 } 773 } 774 775 // Creates the role. 776 if (!$newroleid = create_role($record['name'], $record['shortname'], $record['description'], $record['archetype'])) { 777 throw new coding_exception('There was an error creating \'' . $record['shortname'] . '\' role'); 778 } 779 780 // If no archetype was specified we allow it to be added to all contexts, 781 // otherwise we allow it in the archetype contexts. 782 if (!$record['archetype']) { 783 $contextlevels = array_keys(context_helper::get_all_levels()); 784 } else { 785 // Copying from the archetype default rol. 786 $archetyperoleid = $DB->get_field( 787 'role', 788 'id', 789 array('shortname' => $record['archetype'], 'archetype' => $record['archetype']) 790 ); 791 $contextlevels = get_role_contextlevels($archetyperoleid); 792 } 793 set_role_contextlevels($newroleid, $contextlevels); 794 795 if ($record['archetype']) { 796 797 // We copy all the roles the archetype can assign, override, switch to and view. 798 if ($record['archetype']) { 799 $types = array('assign', 'override', 'switch', 'view'); 800 foreach ($types as $type) { 801 $rolestocopy = get_default_role_archetype_allows($type, $record['archetype']); 802 foreach ($rolestocopy as $tocopy) { 803 $functionname = "core_role_set_{$type}_allowed"; 804 $functionname($newroleid, $tocopy); 805 } 806 } 807 } 808 809 // Copying the archetype capabilities. 810 $sourcerole = $DB->get_record('role', array('id' => $archetyperoleid)); 811 role_cap_duplicate($sourcerole, $newroleid); 812 } 813 814 return $newroleid; 815 } 816 817 /** 818 * Create a tag. 819 * 820 * @param array|stdClass $record 821 * @return stdClass the tag record 822 */ 823 public function create_tag($record = null) { 824 global $DB, $USER; 825 826 $this->tagcount++; 827 $i = $this->tagcount; 828 829 $record = (array) $record; 830 831 if (!isset($record['userid'])) { 832 $record['userid'] = $USER->id; 833 } 834 835 if (!isset($record['rawname'])) { 836 if (isset($record['name'])) { 837 $record['rawname'] = $record['name']; 838 } else { 839 $record['rawname'] = 'Tag name ' . $i; 840 } 841 } 842 843 // Attribute 'name' should be a lowercase version of 'rawname', if not set. 844 if (!isset($record['name'])) { 845 $record['name'] = core_text::strtolower($record['rawname']); 846 } else { 847 $record['name'] = core_text::strtolower($record['name']); 848 } 849 850 if (!isset($record['tagcollid'])) { 851 $record['tagcollid'] = core_tag_collection::get_default(); 852 } 853 854 if (!isset($record['description'])) { 855 $record['description'] = 'Tag description'; 856 } 857 858 if (!isset($record['descriptionformat'])) { 859 $record['descriptionformat'] = FORMAT_MOODLE; 860 } 861 862 if (!isset($record['flag'])) { 863 $record['flag'] = 0; 864 } 865 866 if (!isset($record['timemodified'])) { 867 $record['timemodified'] = time(); 868 } 869 870 $id = $DB->insert_record('tag', $record); 871 872 return $DB->get_record('tag', array('id' => $id), '*', MUST_EXIST); 873 } 874 875 /** 876 * Helper method which combines $defaults with the values specified in $record. 877 * If $record is an object, it is converted to an array. 878 * Then, for each key that is in $defaults, but not in $record, the value 879 * from $defaults is copied. 880 * @param array $defaults the default value for each field with 881 * @param array|stdClass $record 882 * @return array updated $record. 883 */ 884 public function combine_defaults_and_record(array $defaults, $record) { 885 $record = (array) $record; 886 887 foreach ($defaults as $key => $defaults) { 888 if (!array_key_exists($key, $record)) { 889 $record[$key] = $defaults; 890 } 891 } 892 return $record; 893 } 894 895 /** 896 * Simplified enrolment of user to course using default options. 897 * 898 * It is strongly recommended to use only this method for 'manual' and 'self' plugins only!!! 899 * 900 * @param int $userid 901 * @param int $courseid 902 * @param int|string $roleidorshortname optional role id or role shortname, use only with manual plugin 903 * @param string $enrol name of enrol plugin, 904 * there must be exactly one instance in course, 905 * it must support enrol_user() method. 906 * @param int $timestart (optional) 0 means unknown 907 * @param int $timeend (optional) 0 means forever 908 * @param int $status (optional) default to ENROL_USER_ACTIVE for new enrolments 909 * @return bool success 910 */ 911 public function enrol_user($userid, $courseid, $roleidorshortname = null, $enrol = 'manual', 912 $timestart = 0, $timeend = 0, $status = null) { 913 global $DB; 914 915 // If role is specified by shortname, convert it into an id. 916 if (!is_numeric($roleidorshortname) && is_string($roleidorshortname)) { 917 $roleid = $DB->get_field('role', 'id', array('shortname' => $roleidorshortname), MUST_EXIST); 918 } else { 919 $roleid = $roleidorshortname; 920 } 921 922 if (!$plugin = enrol_get_plugin($enrol)) { 923 return false; 924 } 925 926 $instances = $DB->get_records('enrol', array('courseid'=>$courseid, 'enrol'=>$enrol)); 927 if (count($instances) != 1) { 928 return false; 929 } 930 $instance = reset($instances); 931 932 if (is_null($roleid) and $instance->roleid) { 933 $roleid = $instance->roleid; 934 } 935 936 $plugin->enrol_user($instance, $userid, $roleid, $timestart, $timeend, $status); 937 return true; 938 } 939 940 /** 941 * Assigns the specified role to a user in the context. 942 * 943 * @param int $roleid 944 * @param int $userid 945 * @param int $contextid Defaults to the system context 946 * @return int new/existing id of the assignment 947 */ 948 public function role_assign($roleid, $userid, $contextid = false) { 949 950 // Default to the system context. 951 if (!$contextid) { 952 $context = context_system::instance(); 953 $contextid = $context->id; 954 } 955 956 if (empty($roleid)) { 957 throw new coding_exception('roleid must be present in testing_data_generator::role_assign() arguments'); 958 } 959 960 if (empty($userid)) { 961 throw new coding_exception('userid must be present in testing_data_generator::role_assign() arguments'); 962 } 963 964 return role_assign($roleid, $userid, $contextid); 965 } 966 967 /** 968 * Create a grade_category. 969 * 970 * @param array|stdClass $record 971 * @return stdClass the grade category record 972 */ 973 public function create_grade_category($record = null) { 974 global $CFG; 975 976 $this->gradecategorycounter++; 977 978 $record = (array)$record; 979 980 if (empty($record['courseid'])) { 981 throw new coding_exception('courseid must be present in testing::create_grade_category() $record'); 982 } 983 984 if (!isset($record['fullname'])) { 985 $record['fullname'] = 'Grade category ' . $this->gradecategorycounter; 986 } 987 988 // For gradelib classes. 989 require_once($CFG->libdir . '/gradelib.php'); 990 // Create new grading category in this course. 991 $gradecategory = new grade_category(array('courseid' => $record['courseid']), false); 992 $gradecategory->apply_default_settings(); 993 grade_category::set_properties($gradecategory, $record); 994 $gradecategory->apply_forced_settings(); 995 $gradecategory->insert(); 996 997 // This creates a default grade item for the category 998 $gradeitem = $gradecategory->load_grade_item(); 999 1000 $gradecategory->update_from_db(); 1001 return $gradecategory->get_record_data(); 1002 } 1003 1004 /** 1005 * Create a grade_item. 1006 * 1007 * @param array|stdClass $record 1008 * @return stdClass the grade item record 1009 */ 1010 public function create_grade_item($record = null) { 1011 global $CFG; 1012 require_once("$CFG->libdir/gradelib.php"); 1013 1014 $this->gradeitemcounter++; 1015 1016 if (!isset($record['itemtype'])) { 1017 $record['itemtype'] = 'manual'; 1018 } 1019 1020 if (!isset($record['itemname'])) { 1021 $record['itemname'] = 'Grade item ' . $this->gradeitemcounter; 1022 } 1023 1024 if (isset($record['outcomeid'])) { 1025 $outcome = new grade_outcome(array('id' => $record['outcomeid'])); 1026 $record['scaleid'] = $outcome->scaleid; 1027 } 1028 if (isset($record['scaleid'])) { 1029 $record['gradetype'] = GRADE_TYPE_SCALE; 1030 } else if (!isset($record['gradetype'])) { 1031 $record['gradetype'] = GRADE_TYPE_VALUE; 1032 } 1033 1034 // Create new grade item in this course. 1035 $gradeitem = new grade_item($record, false); 1036 $gradeitem->insert(); 1037 1038 $gradeitem->update_from_db(); 1039 return $gradeitem->get_record_data(); 1040 } 1041 1042 /** 1043 * Create a grade_outcome. 1044 * 1045 * @param array|stdClass $record 1046 * @return stdClass the grade outcome record 1047 */ 1048 public function create_grade_outcome($record = null) { 1049 global $CFG; 1050 1051 $this->gradeoutcomecounter++; 1052 $i = $this->gradeoutcomecounter; 1053 1054 if (!isset($record['fullname'])) { 1055 $record['fullname'] = 'Grade outcome ' . $i; 1056 } 1057 1058 // For gradelib classes. 1059 require_once($CFG->libdir . '/gradelib.php'); 1060 // Create new grading outcome in this course. 1061 $gradeoutcome = new grade_outcome($record, false); 1062 $gradeoutcome->insert(); 1063 1064 $gradeoutcome->update_from_db(); 1065 return $gradeoutcome->get_record_data(); 1066 } 1067 1068 /** 1069 * Helper function used to create an LTI tool. 1070 * 1071 * @param array $data 1072 * @return stdClass the tool 1073 */ 1074 public function create_lti_tool($data = array()) { 1075 global $DB; 1076 1077 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 1078 $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); 1079 1080 // Create a course if no course id was specified. 1081 if (empty($data->courseid)) { 1082 $course = $this->create_course(); 1083 $data->courseid = $course->id; 1084 } else { 1085 $course = get_course($data->courseid); 1086 } 1087 1088 if (!empty($data->cmid)) { 1089 $data->contextid = context_module::instance($data->cmid)->id; 1090 } else { 1091 $data->contextid = context_course::instance($data->courseid)->id; 1092 } 1093 1094 // Set it to enabled if no status was specified. 1095 if (!isset($data->status)) { 1096 $data->status = ENROL_INSTANCE_ENABLED; 1097 } 1098 1099 // Add some extra necessary fields to the data. 1100 $data->name = 'Test LTI'; 1101 $data->roleinstructor = $studentrole->id; 1102 $data->rolelearner = $teacherrole->id; 1103 1104 // Get the enrol LTI plugin. 1105 $enrolplugin = enrol_get_plugin('lti'); 1106 $instanceid = $enrolplugin->add_instance($course, (array) $data); 1107 1108 // Get the tool associated with this instance. 1109 return $DB->get_record('enrol_lti_tools', array('enrolid' => $instanceid)); 1110 } 1111 1112 /** 1113 * Helper function used to create an event. 1114 * 1115 * @param array $data 1116 * @return stdClass 1117 */ 1118 public function create_event($data = []) { 1119 global $CFG; 1120 1121 require_once($CFG->dirroot . '/calendar/lib.php'); 1122 $record = new \stdClass(); 1123 $record->name = 'event name'; 1124 $record->repeat = 0; 1125 $record->repeats = 0; 1126 $record->timestart = time(); 1127 $record->timeduration = 0; 1128 $record->timesort = 0; 1129 $record->eventtype = 'user'; 1130 $record->courseid = 0; 1131 $record->categoryid = 0; 1132 1133 foreach ($data as $key => $value) { 1134 $record->$key = $value; 1135 } 1136 1137 switch ($record->eventtype) { 1138 case 'user': 1139 unset($record->categoryid); 1140 unset($record->courseid); 1141 unset($record->groupid); 1142 break; 1143 case 'group': 1144 unset($record->categoryid); 1145 break; 1146 case 'course': 1147 unset($record->categoryid); 1148 unset($record->groupid); 1149 break; 1150 case 'category': 1151 unset($record->courseid); 1152 unset($record->groupid); 1153 break; 1154 case 'site': 1155 unset($record->categoryid); 1156 unset($record->courseid); 1157 unset($record->groupid); 1158 break; 1159 } 1160 1161 $event = new calendar_event($record); 1162 $event->create($record); 1163 1164 return $event->properties(); 1165 } 1166 1167 /** 1168 * Create a new course custom field category with the given name. 1169 * 1170 * @param array $data Array with data['name'] of category 1171 * @return \core_customfield\category_controller The created category 1172 */ 1173 public function create_custom_field_category($data) : \core_customfield\category_controller { 1174 return $this->get_plugin_generator('core_customfield')->create_category($data); 1175 } 1176 1177 /** 1178 * Create a new custom field 1179 * 1180 * @param array $data Array with 'name', 'shortname' and 'type' of the field 1181 * @return \core_customfield\field_controller The created field 1182 */ 1183 public function create_custom_field($data) : \core_customfield\field_controller { 1184 global $DB; 1185 if (empty($data['categoryid']) && !empty($data['category'])) { 1186 $data['categoryid'] = $DB->get_field('customfield_category', 'id', ['name' => $data['category']]); 1187 unset($data['category']); 1188 } 1189 return $this->get_plugin_generator('core_customfield')->create_field($data); 1190 } 1191 1192 /** 1193 * Create a new user, and enrol them in the specified course as the supplied role. 1194 * 1195 * @param \stdClass $course The course to enrol in 1196 * @param string $role The role to give within the course 1197 * @param \stdClass $userparams User parameters 1198 * @return \stdClass The created user 1199 */ 1200 public function create_and_enrol($course, $role = 'student', $userparams = null, $enrol = 'manual', 1201 $timestart = 0, $timeend = 0, $status = null) { 1202 global $DB; 1203 1204 $user = $this->create_user($userparams); 1205 $roleid = $DB->get_field('role', 'id', ['shortname' => $role ]); 1206 1207 $this->enrol_user($user->id, $course->id, $roleid, $enrol, $timestart, $timeend, $status); 1208 1209 return $user; 1210 } 1211 1212 /** 1213 * Create a new last access record for a given user in a course. 1214 * 1215 * @param \stdClass $user The user 1216 * @param \stdClass $course The course the user accessed 1217 * @param int $timestamp The timestamp for when the user last accessed the course 1218 * @return \stdClass The user_lastaccess record 1219 */ 1220 public function create_user_course_lastaccess(\stdClass $user, \stdClass $course, int $timestamp): \stdClass { 1221 global $DB; 1222 1223 $record = [ 1224 'userid' => $user->id, 1225 'courseid' => $course->id, 1226 'timeaccess' => $timestamp, 1227 ]; 1228 1229 $recordid = $DB->insert_record('user_lastaccess', $record); 1230 1231 return $DB->get_record('user_lastaccess', ['id' => $recordid], '*', MUST_EXIST); 1232 } 1233} 1234