Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * 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 26 defined('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 */ 36 class 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 77 Lorem 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. 78 Temporibus 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. 79 Vivamus 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. 80 Integer 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. 81 In 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. 82 EOD; 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->gradecategorycounter = 0; 91 $this->gradeitemcounter = 0; 92 $this->gradeoutcomecounter = 0; 93 $this->usercounter = 0; 94 $this->categorycount = 0; 95 $this->cohortcount = 0; 96 $this->coursecount = 0; 97 $this->scalecount = 0; 98 $this->groupcount = 0; 99 $this->groupingcount = 0; 100 $this->rolecount = 0; 101 $this->tagcount = 0; 102 103 foreach ($this->generators as $generator) { 104 $generator->reset(); 105 } 106 } 107 108 /** 109 * Return generator for given plugin or component. 110 * @param string $component the component name, e.g. 'mod_forum' or 'core_question'. 111 * @return component_generator_base or rather an instance of the appropriate subclass. 112 */ 113 public function get_plugin_generator($component) { 114 // Note: This global is included so that generator have access to it. 115 // CFG is widely used in require statements. 116 global $CFG; 117 list($type, $plugin) = core_component::normalize_component($component); 118 $cleancomponent = $type . '_' . $plugin; 119 if ($cleancomponent != $component) { 120 debugging("Please specify the component you want a generator for as " . 121 "{$cleancomponent}, not {$component}.", DEBUG_DEVELOPER); 122 $component = $cleancomponent; 123 } 124 125 if (isset($this->generators[$component])) { 126 return $this->generators[$component]; 127 } 128 129 $dir = core_component::get_component_directory($component); 130 $lib = $dir . '/tests/generator/lib.php'; 131 if (!$dir || !is_readable($lib)) { 132 $this->generators[$component] = $this->get_default_plugin_generator($component); 133 134 return $this->generators[$component]; 135 } 136 137 include_once($lib); 138 $classname = $component . '_generator'; 139 140 if (class_exists($classname)) { 141 $this->generators[$component] = new $classname($this); 142 } else { 143 $this->generators[$component] = $this->get_default_plugin_generator($component, $classname); 144 } 145 146 return $this->generators[$component]; 147 } 148 149 /** 150 * Create a test user 151 * @param array|stdClass $record 152 * @param array $options 153 * @return stdClass user record 154 */ 155 public function create_user($record=null, array $options=null) { 156 global $DB, $CFG; 157 require_once($CFG->dirroot.'/user/lib.php'); 158 159 $this->usercounter++; 160 $i = $this->usercounter; 161 162 $record = (array)$record; 163 164 if (!isset($record['auth'])) { 165 $record['auth'] = 'manual'; 166 } 167 168 if (!isset($record['firstname']) and !isset($record['lastname'])) { 169 $country = rand(0, 5); 170 $firstname = rand(0, 4); 171 $lastname = rand(0, 4); 172 $female = rand(0, 1); 173 $record['firstname'] = $this->firstnames[($country*10) + $firstname + ($female*5)]; 174 $record['lastname'] = $this->lastnames[($country*10) + $lastname + ($female*5)]; 175 176 } else if (!isset($record['firstname'])) { 177 $record['firstname'] = 'Firstname'.$i; 178 179 } else if (!isset($record['lastname'])) { 180 $record['lastname'] = 'Lastname'.$i; 181 } 182 183 if (!isset($record['firstnamephonetic'])) { 184 $firstnamephonetic = rand(0, 59); 185 $record['firstnamephonetic'] = $this->firstnames[$firstnamephonetic]; 186 } 187 188 if (!isset($record['lastnamephonetic'])) { 189 $lastnamephonetic = rand(0, 59); 190 $record['lastnamephonetic'] = $this->lastnames[$lastnamephonetic]; 191 } 192 193 if (!isset($record['middlename'])) { 194 $middlename = rand(0, 59); 195 $record['middlename'] = $this->firstnames[$middlename]; 196 } 197 198 if (!isset($record['alternatename'])) { 199 $alternatename = rand(0, 59); 200 $record['alternatename'] = $this->firstnames[$alternatename]; 201 } 202 203 if (!isset($record['idnumber'])) { 204 $record['idnumber'] = ''; 205 } 206 207 if (!isset($record['mnethostid'])) { 208 $record['mnethostid'] = $CFG->mnet_localhost_id; 209 } 210 211 if (!isset($record['username'])) { 212 $record['username'] = 'username'.$i; 213 $j = 2; 214 while ($DB->record_exists('user', array('username'=>$record['username'], 'mnethostid'=>$record['mnethostid']))) { 215 $record['username'] = 'username'.$i.'_'.$j; 216 $j++; 217 } 218 } 219 220 if (isset($record['password'])) { 221 $record['password'] = hash_internal_user_password($record['password']); 222 } 223 224 if (!isset($record['email'])) { 225 $record['email'] = $record['username'].'@example.com'; 226 } 227 228 if (!isset($record['confirmed'])) { 229 $record['confirmed'] = 1; 230 } 231 232 if (!isset($record['lastip'])) { 233 $record['lastip'] = '0.0.0.0'; 234 } 235 236 $tobedeleted = !empty($record['deleted']); 237 unset($record['deleted']); 238 239 $userid = user_create_user($record, false, false); 240 241 if ($extrafields = array_intersect_key($record, ['password' => 1, 'timecreated' => 1])) { 242 $DB->update_record('user', ['id' => $userid] + $extrafields); 243 } 244 245 if (!$tobedeleted) { 246 // All new not deleted users must have a favourite self-conversation. 247 $selfconversation = \core_message\api::create_conversation( 248 \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, 249 [$userid] 250 ); 251 \core_message\api::set_favourite_conversation($selfconversation->id, $userid); 252 253 // Save custom profile fields data. 254 $hasprofilefields = array_filter($record, function($key){ 255 return strpos($key, 'profile_field_') === 0; 256 }, ARRAY_FILTER_USE_KEY); 257 if ($hasprofilefields) { 258 require_once($CFG->dirroot.'/user/profile/lib.php'); 259 $usernew = (object)(['id' => $userid] + $record); 260 profile_save_data($usernew); 261 } 262 } 263 264 $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST); 265 266 if (!$tobedeleted && isset($record['interests'])) { 267 require_once($CFG->dirroot . '/user/editlib.php'); 268 if (!is_array($record['interests'])) { 269 $record['interests'] = preg_split('/\s*,\s*/', trim($record['interests']), -1, PREG_SPLIT_NO_EMPTY); 270 } 271 useredit_update_interests($user, $record['interests']); 272 } 273 274 \core\event\user_created::create_from_userid($userid)->trigger(); 275 276 if ($tobedeleted) { 277 delete_user($user); 278 $user = $DB->get_record('user', array('id' => $userid)); 279 } 280 return $user; 281 } 282 283 /** 284 * Create a test course category 285 * @param array|stdClass $record 286 * @param array $options 287 * @return core_course_category course category record 288 */ 289 public function create_category($record=null, array $options=null) { 290 $this->categorycount++; 291 $i = $this->categorycount; 292 293 $record = (array)$record; 294 295 if (!isset($record['name'])) { 296 $record['name'] = 'Course category '.$i; 297 } 298 299 if (!isset($record['description'])) { 300 $record['description'] = "Test course category $i\n$this->loremipsum"; 301 } 302 303 if (!isset($record['idnumber'])) { 304 $record['idnumber'] = ''; 305 } 306 307 return core_course_category::create($record); 308 } 309 310 /** 311 * Create test cohort. 312 * @param array|stdClass $record 313 * @param array $options 314 * @return stdClass cohort record 315 */ 316 public function create_cohort($record=null, array $options=null) { 317 global $DB, $CFG; 318 require_once("$CFG->dirroot/cohort/lib.php"); 319 320 $this->cohortcount++; 321 $i = $this->cohortcount; 322 323 $record = (array)$record; 324 325 if (!isset($record['contextid'])) { 326 $record['contextid'] = context_system::instance()->id; 327 } 328 329 if (!isset($record['name'])) { 330 $record['name'] = 'Cohort '.$i; 331 } 332 333 if (!isset($record['idnumber'])) { 334 $record['idnumber'] = ''; 335 } 336 337 if (!isset($record['description'])) { 338 $record['description'] = "Description for '{$record['name']}' \n$this->loremipsum"; 339 } 340 341 if (!isset($record['descriptionformat'])) { 342 $record['descriptionformat'] = FORMAT_MOODLE; 343 } 344 345 if (!isset($record['visible'])) { 346 $record['visible'] = 1; 347 } 348 349 if (!isset($record['component'])) { 350 $record['component'] = ''; 351 } 352 353 $id = cohort_add_cohort((object)$record); 354 355 return $DB->get_record('cohort', array('id'=>$id), '*', MUST_EXIST); 356 } 357 358 /** 359 * Create a test course 360 * @param array|stdClass $record 361 * @param array $options with keys: 362 * 'createsections'=>bool precreate all sections 363 * @return stdClass course record 364 */ 365 public function create_course($record=null, array $options=null) { 366 global $DB, $CFG; 367 require_once("$CFG->dirroot/course/lib.php"); 368 369 $this->coursecount++; 370 $i = $this->coursecount; 371 372 $record = (array)$record; 373 374 if (!isset($record['fullname'])) { 375 $record['fullname'] = 'Test course '.$i; 376 } 377 378 if (!isset($record['shortname'])) { 379 $record['shortname'] = 'tc_'.$i; 380 } 381 382 if (!isset($record['idnumber'])) { 383 $record['idnumber'] = ''; 384 } 385 386 if (!isset($record['format'])) { 387 $record['format'] = 'topics'; 388 } 389 390 if (!isset($record['newsitems'])) { 391 $record['newsitems'] = 0; 392 } 393 394 if (!isset($record['numsections'])) { 395 $record['numsections'] = 5; 396 } 397 398 if (!isset($record['summary'])) { 399 $record['summary'] = "Test course $i\n$this->loremipsum"; 400 } 401 402 if (!isset($record['summaryformat'])) { 403 $record['summaryformat'] = FORMAT_MOODLE; 404 } 405 406 if (!isset($record['category'])) { 407 $record['category'] = $DB->get_field_select('course_categories', "MIN(id)", "parent=0"); 408 } 409 410 if (!isset($record['startdate'])) { 411 $record['startdate'] = usergetmidnight(time()); 412 } 413 414 if (isset($record['tags']) && !is_array($record['tags'])) { 415 $record['tags'] = preg_split('/\s*,\s*/', trim($record['tags']), -1, PREG_SPLIT_NO_EMPTY); 416 } 417 418 if (!empty($options['createsections']) && empty($record['numsections'])) { 419 // Since Moodle 3.3 function create_course() automatically creates sections if numsections is specified. 420 // For BC if 'createsections' is given but 'numsections' is not, assume the default value from config. 421 $record['numsections'] = get_config('moodlecourse', 'numsections'); 422 } 423 424 if (!empty($record['customfields'])) { 425 foreach ($record['customfields'] as $field) { 426 $record['customfield_'.$field['shortname']] = $field['value']; 427 } 428 } 429 430 $course = create_course((object)$record); 431 context_course::instance($course->id); 432 433 return $course; 434 } 435 436 /** 437 * Create course section if does not exist yet 438 * @param array|stdClass $record must contain 'course' and 'section' attributes 439 * @param array|null $options 440 * @return section_info 441 * @throws coding_exception 442 */ 443 public function create_course_section($record = null, array $options = null) { 444 global $DB; 445 446 $record = (array)$record; 447 448 if (empty($record['course'])) { 449 throw new coding_exception('course must be present in testing_data_generator::create_course_section() $record'); 450 } 451 452 if (!isset($record['section'])) { 453 throw new coding_exception('section must be present in testing_data_generator::create_course_section() $record'); 454 } 455 456 course_create_sections_if_missing($record['course'], $record['section']); 457 return get_fast_modinfo($record['course'])->get_section_info($record['section']); 458 } 459 460 /** 461 * Create a test block. 462 * 463 * The $record passed in becomes the basis for the new row added to the 464 * block_instances table. You only need to supply the values of interest. 465 * Any missing values have sensible defaults filled in, and ->blockname will be set based on $blockname. 466 * 467 * The $options array provides additional data, not directly related to what 468 * will be inserted in the block_instance table, which may affect the block 469 * that is created. The meanings of any data passed here depends on the particular 470 * type of block being created. 471 * 472 * @param string $blockname the type of block to create. E.g. 'html'. 473 * @param array|stdClass $record forms the basis for the entry to be inserted in the block_instances table. 474 * @param array $options further, block-specific options to control how the block is created. 475 * @return stdClass new block_instance record. 476 */ 477 public function create_block($blockname, $record=null, array $options=array()) { 478 $generator = $this->get_plugin_generator('block_'.$blockname); 479 return $generator->create_instance($record, $options); 480 } 481 482 /** 483 * Create a test activity module. 484 * 485 * The $record should contain the same data that you would call from 486 * ->get_data() when the mod_[type]_mod_form is submitted, except that you 487 * only need to supply values of interest. The only required value is 488 * 'course'. Any missing values will have a sensible default supplied. 489 * 490 * The $options array provides additional data, not directly related to what 491 * would come back from the module edit settings form, which may affect the activity 492 * that is created. The meanings of any data passed here depends on the particular 493 * type of activity being created. 494 * 495 * @param string $modulename the type of activity to create. E.g. 'forum' or 'quiz'. 496 * @param array|stdClass $record data, as if from the module edit settings form. 497 * @param array $options additional data that may affect how the module is created. 498 * @return stdClass activity record new new record that was just inserted in the table 499 * like 'forum' or 'quiz', with a ->cmid field added. 500 */ 501 public function create_module($modulename, $record=null, array $options=null) { 502 $generator = $this->get_plugin_generator('mod_'.$modulename); 503 return $generator->create_instance($record, $options); 504 } 505 506 /** 507 * Create a test group for the specified course 508 * 509 * $record should be either an array or a stdClass containing infomation about the group to create. 510 * At the very least it needs to contain courseid. 511 * Default values are added for name, description, and descriptionformat if they are not present. 512 * 513 * This function calls groups_create_group() to create the group within the database. 514 * @see groups_create_group 515 * @param array|stdClass $record 516 * @return stdClass group record 517 */ 518 public function create_group($record) { 519 global $DB, $CFG; 520 521 require_once($CFG->dirroot . '/group/lib.php'); 522 523 $this->groupcount++; 524 $i = str_pad($this->groupcount, 4, '0', STR_PAD_LEFT); 525 526 $record = (array)$record; 527 528 if (empty($record['courseid'])) { 529 throw new coding_exception('courseid must be present in testing_data_generator::create_group() $record'); 530 } 531 532 if (!isset($record['name'])) { 533 $record['name'] = 'group-' . $i; 534 } 535 536 if (!isset($record['description'])) { 537 $record['description'] = "Test Group $i\n{$this->loremipsum}"; 538 } 539 540 if (!isset($record['descriptionformat'])) { 541 $record['descriptionformat'] = FORMAT_MOODLE; 542 } 543 544 if (!isset($record['visibility'])) { 545 $record['visibility'] = GROUPS_VISIBILITY_ALL; 546 } 547 548 if (!isset($record['participation'])) { 549 $record['participation'] = true; 550 } 551 552 $id = groups_create_group((object)$record); 553 554 // Allow tests to set group pictures. 555 if (!empty($record['picturepath'])) { 556 require_once($CFG->dirroot . '/lib/gdlib.php'); 557 $grouppicture = process_new_icon(\context_course::instance($record['courseid']), 'group', 'icon', $id, 558 $record['picturepath']); 559 560 $DB->set_field('groups', 'picture', $grouppicture, ['id' => $id]); 561 562 // Invalidate the group data as we've updated the group record. 563 cache_helper::invalidate_by_definition('core', 'groupdata', array(), [$record['courseid']]); 564 } 565 566 return $DB->get_record('groups', array('id'=>$id)); 567 } 568 569 /** 570 * Create a test group member 571 * @param array|stdClass $record 572 * @throws coding_exception 573 * @return boolean 574 */ 575 public function create_group_member($record) { 576 global $DB, $CFG; 577 578 require_once($CFG->dirroot . '/group/lib.php'); 579 580 $record = (array)$record; 581 582 if (empty($record['userid'])) { 583 throw new coding_exception('user must be present in testing_util::create_group_member() $record'); 584 } 585 586 if (!isset($record['groupid'])) { 587 throw new coding_exception('group must be present in testing_util::create_group_member() $record'); 588 } 589 590 if (!isset($record['component'])) { 591 $record['component'] = null; 592 } 593 if (!isset($record['itemid'])) { 594 $record['itemid'] = 0; 595 } 596 597 return groups_add_member($record['groupid'], $record['userid'], $record['component'], $record['itemid']); 598 } 599 600 /** 601 * Create a test grouping for the specified course 602 * 603 * $record should be either an array or a stdClass containing infomation about the grouping to create. 604 * At the very least it needs to contain courseid. 605 * Default values are added for name, description, and descriptionformat if they are not present. 606 * 607 * This function calls groups_create_grouping() to create the grouping within the database. 608 * @see groups_create_grouping 609 * @param array|stdClass $record 610 * @return stdClass grouping record 611 */ 612 public function create_grouping($record) { 613 global $DB, $CFG; 614 615 require_once($CFG->dirroot . '/group/lib.php'); 616 617 $this->groupingcount++; 618 $i = $this->groupingcount; 619 620 $record = (array)$record; 621 622 if (empty($record['courseid'])) { 623 throw new coding_exception('courseid must be present in testing_data_generator::create_grouping() $record'); 624 } 625 626 if (!isset($record['name'])) { 627 $record['name'] = 'grouping-' . $i; 628 } 629 630 if (!isset($record['description'])) { 631 $record['description'] = "Test Grouping $i\n{$this->loremipsum}"; 632 } 633 634 if (!isset($record['descriptionformat'])) { 635 $record['descriptionformat'] = FORMAT_MOODLE; 636 } 637 638 $id = groups_create_grouping((object)$record); 639 640 return $DB->get_record('groupings', array('id'=>$id)); 641 } 642 643 /** 644 * Create a test grouping group 645 * @param array|stdClass $record 646 * @throws coding_exception 647 * @return boolean 648 */ 649 public function create_grouping_group($record) { 650 global $DB, $CFG; 651 652 require_once($CFG->dirroot . '/group/lib.php'); 653 654 $record = (array)$record; 655 656 if (empty($record['groupingid'])) { 657 throw new coding_exception('grouping must be present in testing::create_grouping_group() $record'); 658 } 659 660 if (!isset($record['groupid'])) { 661 throw new coding_exception('group must be present in testing_util::create_grouping_group() $record'); 662 } 663 664 return groups_assign_grouping($record['groupingid'], $record['groupid']); 665 } 666 667 /** 668 * Create an instance of a repository. 669 * 670 * @param string type of repository to create an instance for. 671 * @param array|stdClass $record data to use to up set the instance. 672 * @param array $options options 673 * @return stdClass repository instance record 674 * @since Moodle 2.5.1 675 */ 676 public function create_repository($type, $record=null, array $options = null) { 677 $generator = $this->get_plugin_generator('repository_'.$type); 678 return $generator->create_instance($record, $options); 679 } 680 681 /** 682 * Create an instance of a repository. 683 * 684 * @param string type of repository to create an instance for. 685 * @param array|stdClass $record data to use to up set the instance. 686 * @param array $options options 687 * @return repository_type object 688 * @since Moodle 2.5.1 689 */ 690 public function create_repository_type($type, $record=null, array $options = null) { 691 $generator = $this->get_plugin_generator('repository_'.$type); 692 return $generator->create_type($record, $options); 693 } 694 695 696 /** 697 * Create a test scale 698 * @param array|stdClass $record 699 * @param array $options 700 * @return stdClass block instance record 701 */ 702 public function create_scale($record=null, array $options=null) { 703 global $DB; 704 705 $this->scalecount++; 706 $i = $this->scalecount; 707 708 $record = (array)$record; 709 710 if (!isset($record['name'])) { 711 $record['name'] = 'Test scale '.$i; 712 } 713 714 if (!isset($record['scale'])) { 715 $record['scale'] = 'A,B,C,D,F'; 716 } 717 718 if (!isset($record['courseid'])) { 719 $record['courseid'] = 0; 720 } 721 722 if (!isset($record['userid'])) { 723 $record['userid'] = 0; 724 } 725 726 if (!isset($record['description'])) { 727 $record['description'] = 'Test scale description '.$i; 728 } 729 730 if (!isset($record['descriptionformat'])) { 731 $record['descriptionformat'] = FORMAT_MOODLE; 732 } 733 734 $record['timemodified'] = time(); 735 736 if (isset($record['id'])) { 737 $DB->import_record('scale', $record); 738 $DB->get_manager()->reset_sequence('scale'); 739 $id = $record['id']; 740 } else { 741 $id = $DB->insert_record('scale', $record); 742 } 743 744 return $DB->get_record('scale', array('id'=>$id), '*', MUST_EXIST); 745 } 746 747 /** 748 * Creates a new role in the system. 749 * 750 * You can fill $record with the role 'name', 751 * 'shortname', 'description' and 'archetype'. 752 * 753 * If an archetype is specified it's capabilities, 754 * context where the role can be assigned and 755 * all other properties are copied from the archetype; 756 * if no archetype is specified it will create an 757 * empty role. 758 * 759 * @param array|stdClass $record 760 * @return int The new role id 761 */ 762 public function create_role($record=null) { 763 global $DB; 764 765 $this->rolecount++; 766 $i = $this->rolecount; 767 768 $record = (array)$record; 769 770 if (empty($record['shortname'])) { 771 $record['shortname'] = 'role-' . $i; 772 } 773 774 if (empty($record['name'])) { 775 $record['name'] = 'Test role ' . $i; 776 } 777 778 if (empty($record['description'])) { 779 $record['description'] = 'Test role ' . $i . ' description'; 780 } 781 782 if (empty($record['archetype'])) { 783 $record['archetype'] = ''; 784 } else { 785 $archetypes = get_role_archetypes(); 786 if (empty($archetypes[$record['archetype']])) { 787 throw new coding_exception('\'role\' requires the field \'archetype\' to specify a ' . 788 'valid archetype shortname (editingteacher, student...)'); 789 } 790 } 791 792 // Creates the role. 793 if (!$newroleid = create_role($record['name'], $record['shortname'], $record['description'], $record['archetype'])) { 794 throw new coding_exception('There was an error creating \'' . $record['shortname'] . '\' role'); 795 } 796 797 // If no archetype was specified we allow it to be added to all contexts, 798 // otherwise we allow it in the archetype contexts. 799 if (!$record['archetype']) { 800 $contextlevels = []; 801 $usefallback = true; 802 foreach (context_helper::get_all_levels() as $level => $title) { 803 if (array_key_exists($title, $record)) { 804 $usefallback = false; 805 if (!empty($record[$title])) { 806 $contextlevels[] = $level; 807 } 808 } 809 } 810 811 if ($usefallback) { 812 $contextlevels = array_keys(context_helper::get_all_levels()); 813 } 814 } else { 815 // Copying from the archetype default rol. 816 $archetyperoleid = $DB->get_field( 817 'role', 818 'id', 819 array('shortname' => $record['archetype'], 'archetype' => $record['archetype']) 820 ); 821 $contextlevels = get_role_contextlevels($archetyperoleid); 822 } 823 set_role_contextlevels($newroleid, $contextlevels); 824 825 if ($record['archetype']) { 826 // We copy all the roles the archetype can assign, override, switch to and view. 827 if ($record['archetype']) { 828 $types = array('assign', 'override', 'switch', 'view'); 829 foreach ($types as $type) { 830 $rolestocopy = get_default_role_archetype_allows($type, $record['archetype']); 831 foreach ($rolestocopy as $tocopy) { 832 $functionname = "core_role_set_{$type}_allowed"; 833 $functionname($newroleid, $tocopy); 834 } 835 } 836 } 837 838 // Copying the archetype capabilities. 839 $sourcerole = $DB->get_record('role', array('id' => $archetyperoleid)); 840 role_cap_duplicate($sourcerole, $newroleid); 841 } 842 843 $allcapabilities = get_all_capabilities(); 844 $foundcapabilities = array_intersect(array_keys($allcapabilities), array_keys($record)); 845 $systemcontext = \context_system::instance(); 846 847 $allpermissions = [ 848 'inherit' => CAP_INHERIT, 849 'allow' => CAP_ALLOW, 850 'prevent' => CAP_PREVENT, 851 'prohibit' => CAP_PROHIBIT, 852 ]; 853 854 foreach ($foundcapabilities as $capability) { 855 $permission = $record[$capability]; 856 if (!array_key_exists($permission, $allpermissions)) { 857 throw new \coding_exception("Unknown capability permissions '{$permission}'"); 858 } 859 assign_capability( 860 $capability, 861 $allpermissions[$permission], 862 $newroleid, 863 $systemcontext->id, 864 true 865 ); 866 } 867 868 return $newroleid; 869 } 870 871 /** 872 * Set role capabilities for the specified role. 873 * 874 * @param int $roleid The Role to set capabilities for 875 * @param array $rolecapabilities The list of capability =>permission to set for this role 876 * @param null|context $context The context to apply this capability to 877 */ 878 public function create_role_capability(int $roleid, array $rolecapabilities, context $context = null): void { 879 // Map the capabilities into human-readable names. 880 $allpermissions = [ 881 'inherit' => CAP_INHERIT, 882 'allow' => CAP_ALLOW, 883 'prevent' => CAP_PREVENT, 884 'prohibit' => CAP_PROHIBIT, 885 ]; 886 887 // Fetch all capabilities to check that they exist. 888 $allcapabilities = get_all_capabilities(); 889 foreach ($rolecapabilities as $capability => $permission) { 890 if ($permission === '') { 891 // Allow items to be skipped. 892 continue; 893 } 894 895 if (!array_key_exists($capability, $allcapabilities)) { 896 throw new \coding_exception("Unknown capability '{$capability}'"); 897 } 898 899 if (!array_key_exists($permission, $allpermissions)) { 900 throw new \coding_exception("Unknown capability permissions '{$permission}'"); 901 } 902 903 assign_capability( 904 $capability, 905 $allpermissions[$permission], 906 $roleid, 907 $context->id, 908 true 909 ); 910 } 911 } 912 913 /** 914 * Create a tag. 915 * 916 * @param array|stdClass $record 917 * @return stdClass the tag record 918 */ 919 public function create_tag($record = null) { 920 global $DB, $USER; 921 922 $this->tagcount++; 923 $i = $this->tagcount; 924 925 $record = (array) $record; 926 927 if (!isset($record['userid'])) { 928 $record['userid'] = $USER->id; 929 } 930 931 if (!isset($record['rawname'])) { 932 if (isset($record['name'])) { 933 $record['rawname'] = $record['name']; 934 } else { 935 $record['rawname'] = 'Tag name ' . $i; 936 } 937 } 938 939 // Attribute 'name' should be a lowercase version of 'rawname', if not set. 940 if (!isset($record['name'])) { 941 $record['name'] = core_text::strtolower($record['rawname']); 942 } else { 943 $record['name'] = core_text::strtolower($record['name']); 944 } 945 946 if (!isset($record['tagcollid'])) { 947 $record['tagcollid'] = core_tag_collection::get_default(); 948 } 949 950 if (!isset($record['description'])) { 951 $record['description'] = 'Tag description'; 952 } 953 954 if (!isset($record['descriptionformat'])) { 955 $record['descriptionformat'] = FORMAT_MOODLE; 956 } 957 958 if (!isset($record['flag'])) { 959 $record['flag'] = 0; 960 } 961 962 if (!isset($record['timemodified'])) { 963 $record['timemodified'] = time(); 964 } 965 966 $id = $DB->insert_record('tag', $record); 967 968 return $DB->get_record('tag', array('id' => $id), '*', MUST_EXIST); 969 } 970 971 /** 972 * Helper method which combines $defaults with the values specified in $record. 973 * If $record is an object, it is converted to an array. 974 * Then, for each key that is in $defaults, but not in $record, the value 975 * from $defaults is copied. 976 * @param array $defaults the default value for each field with 977 * @param array|stdClass $record 978 * @return array updated $record. 979 */ 980 public function combine_defaults_and_record(array $defaults, $record) { 981 $record = (array) $record; 982 983 foreach ($defaults as $key => $defaults) { 984 if (!array_key_exists($key, $record)) { 985 $record[$key] = $defaults; 986 } 987 } 988 return $record; 989 } 990 991 /** 992 * Simplified enrolment of user to course using default options. 993 * 994 * It is strongly recommended to use only this method for 'manual' and 'self' plugins only!!! 995 * 996 * @param int $userid 997 * @param int $courseid 998 * @param int|string $roleidorshortname optional role id or role shortname, use only with manual plugin 999 * @param string $enrol name of enrol plugin, 1000 * there must be exactly one instance in course, 1001 * it must support enrol_user() method. 1002 * @param int $timestart (optional) 0 means unknown 1003 * @param int $timeend (optional) 0 means forever 1004 * @param int $status (optional) default to ENROL_USER_ACTIVE for new enrolments 1005 * @return bool success 1006 */ 1007 public function enrol_user($userid, $courseid, $roleidorshortname = null, $enrol = 'manual', 1008 $timestart = 0, $timeend = 0, $status = null) { 1009 global $DB; 1010 1011 // If role is specified by shortname, convert it into an id. 1012 if (!is_numeric($roleidorshortname) && is_string($roleidorshortname)) { 1013 $roleid = $DB->get_field('role', 'id', array('shortname' => $roleidorshortname), MUST_EXIST); 1014 } else { 1015 $roleid = $roleidorshortname; 1016 } 1017 1018 if (!$plugin = enrol_get_plugin($enrol)) { 1019 return false; 1020 } 1021 1022 $instances = $DB->get_records('enrol', array('courseid'=>$courseid, 'enrol'=>$enrol)); 1023 if (count($instances) != 1) { 1024 return false; 1025 } 1026 $instance = reset($instances); 1027 1028 if (is_null($roleid) and $instance->roleid) { 1029 $roleid = $instance->roleid; 1030 } 1031 1032 $plugin->enrol_user($instance, $userid, $roleid, $timestart, $timeend, $status); 1033 return true; 1034 } 1035 1036 /** 1037 * Assigns the specified role to a user in the context. 1038 * 1039 * @param int|string $role either an int role id or a string role shortname. 1040 * @param int $userid 1041 * @param int|context $contextid Defaults to the system context 1042 * @return int new/existing id of the assignment 1043 */ 1044 public function role_assign($role, $userid, $contextid = false) { 1045 global $DB; 1046 1047 // Default to the system context. 1048 if (!$contextid) { 1049 $context = context_system::instance(); 1050 $contextid = $context->id; 1051 } 1052 1053 if (empty($role)) { 1054 throw new coding_exception('roleid must be present in testing_data_generator::role_assign() arguments'); 1055 } 1056 if (!is_number($role)) { 1057 $role = $DB->get_field('role', 'id', ['shortname' => $role], MUST_EXIST); 1058 } 1059 1060 if (empty($userid)) { 1061 throw new coding_exception('userid must be present in testing_data_generator::role_assign() arguments'); 1062 } 1063 1064 return role_assign($role, $userid, $contextid); 1065 } 1066 1067 /** 1068 * Create a grade_category. 1069 * 1070 * @param array|stdClass $record 1071 * @return stdClass the grade category record 1072 */ 1073 public function create_grade_category($record = null) { 1074 global $CFG; 1075 1076 $this->gradecategorycounter++; 1077 1078 $record = (array)$record; 1079 1080 if (empty($record['courseid'])) { 1081 throw new coding_exception('courseid must be present in testing::create_grade_category() $record'); 1082 } 1083 1084 if (!isset($record['fullname'])) { 1085 $record['fullname'] = 'Grade category ' . $this->gradecategorycounter; 1086 } 1087 1088 // For gradelib classes. 1089 require_once($CFG->libdir . '/gradelib.php'); 1090 // Create new grading category in this course. 1091 $gradecategory = new grade_category(array('courseid' => $record['courseid']), false); 1092 $gradecategory->apply_default_settings(); 1093 grade_category::set_properties($gradecategory, $record); 1094 $gradecategory->apply_forced_settings(); 1095 $gradecategory->insert(); 1096 1097 // This creates a default grade item for the category 1098 $gradeitem = $gradecategory->load_grade_item(); 1099 1100 $gradecategory->update_from_db(); 1101 return $gradecategory->get_record_data(); 1102 } 1103 1104 /** 1105 * Create a grade_grade. 1106 * 1107 * @param array $record 1108 * @return grade_grade the grade record 1109 */ 1110 public function create_grade_grade(?array $record = null): grade_grade { 1111 global $DB, $USER; 1112 1113 $item = $DB->get_record('grade_items', ['id' => $record['itemid']]); 1114 $userid = $record['userid'] ?? $USER->id; 1115 1116 unset($record['itemid']); 1117 unset($record['userid']); 1118 1119 if ($item->itemtype === 'mod') { 1120 $cm = get_coursemodule_from_instance($item->itemmodule, $item->iteminstance); 1121 $module = new $item->itemmodule(context_module::instance($cm->id), $cm, false); 1122 $record['attemptnumber'] = $record['attemptnumber'] ?? 0; 1123 1124 $module->save_grade($userid, (object) $record); 1125 1126 $grade = grade_grade::fetch(['userid' => $userid, 'itemid' => $item->id]); 1127 } else { 1128 $grade = grade_grade::fetch(['userid' => $userid, 'itemid' => $item->id]); 1129 $record['rawgrade'] = $record['rawgrade'] ?? $record['grade'] ?? null; 1130 $record['finalgrade'] = $record['finalgrade'] ?? $record['grade'] ?? null; 1131 1132 unset($record['grade']); 1133 1134 if ($grade) { 1135 $fields = $grade->required_fields + array_keys($grade->optional_fields); 1136 1137 foreach ($fields as $field) { 1138 $grade->{$field} = $record[$field] ?? $grade->{$field}; 1139 } 1140 1141 $grade->update(); 1142 } else { 1143 $record['userid'] = $userid; 1144 $record['itemid'] = $item->id; 1145 1146 $grade = new grade_grade($record, false); 1147 1148 $grade->insert(); 1149 } 1150 } 1151 1152 return $grade; 1153 } 1154 1155 /** 1156 * Create a grade_item. 1157 * 1158 * @param array|stdClass $record 1159 * @return stdClass the grade item record 1160 */ 1161 public function create_grade_item($record = null) { 1162 global $CFG; 1163 require_once("$CFG->libdir/gradelib.php"); 1164 1165 $this->gradeitemcounter++; 1166 1167 if (!isset($record['itemtype'])) { 1168 $record['itemtype'] = 'manual'; 1169 } 1170 1171 if (!isset($record['itemname'])) { 1172 $record['itemname'] = 'Grade item ' . $this->gradeitemcounter; 1173 } 1174 1175 if (isset($record['outcomeid'])) { 1176 $outcome = new grade_outcome(array('id' => $record['outcomeid'])); 1177 $record['scaleid'] = $outcome->scaleid; 1178 } 1179 if (isset($record['scaleid'])) { 1180 $record['gradetype'] = GRADE_TYPE_SCALE; 1181 } else if (!isset($record['gradetype'])) { 1182 $record['gradetype'] = GRADE_TYPE_VALUE; 1183 } 1184 1185 // Create new grade item in this course. 1186 $gradeitem = new grade_item($record, false); 1187 $gradeitem->insert(); 1188 1189 $gradeitem->update_from_db(); 1190 return $gradeitem->get_record_data(); 1191 } 1192 1193 /** 1194 * Create a grade_outcome. 1195 * 1196 * @param array|stdClass $record 1197 * @return stdClass the grade outcome record 1198 */ 1199 public function create_grade_outcome($record = null) { 1200 global $CFG; 1201 1202 $this->gradeoutcomecounter++; 1203 $i = $this->gradeoutcomecounter; 1204 1205 if (!isset($record['fullname'])) { 1206 $record['fullname'] = 'Grade outcome ' . $i; 1207 } 1208 1209 // For gradelib classes. 1210 require_once($CFG->libdir . '/gradelib.php'); 1211 // Create new grading outcome in this course. 1212 $gradeoutcome = new grade_outcome($record, false); 1213 $gradeoutcome->insert(); 1214 1215 $gradeoutcome->update_from_db(); 1216 return $gradeoutcome->get_record_data(); 1217 } 1218 1219 /** 1220 * Helper function used to create an LTI tool. 1221 * 1222 * @param stdClass $data 1223 * @return stdClass the tool 1224 */ 1225 public function create_lti_tool($data = array()) { 1226 global $DB; 1227 1228 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 1229 $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); 1230 1231 // Create a course if no course id was specified. 1232 if (empty($data->courseid)) { 1233 $course = $this->create_course(); 1234 $data->courseid = $course->id; 1235 } else { 1236 $course = get_course($data->courseid); 1237 } 1238 1239 if (!empty($data->cmid)) { 1240 $data->contextid = context_module::instance($data->cmid)->id; 1241 } else { 1242 $data->contextid = context_course::instance($data->courseid)->id; 1243 } 1244 1245 // Set it to enabled if no status was specified. 1246 if (!isset($data->status)) { 1247 $data->status = ENROL_INSTANCE_ENABLED; 1248 } 1249 1250 // Default to legacy lti version. 1251 if (empty($data->ltiversion) || !in_array($data->ltiversion, ['LTI-1p0/LTI-2p0', 'LTI-1p3'])) { 1252 $data->ltiversion = 'LTI-1p0/LTI-2p0'; 1253 } 1254 1255 // Add some extra necessary fields to the data. 1256 $data->name = $data->name ?? 'Test LTI'; 1257 $data->roleinstructor = $teacherrole->id; 1258 $data->rolelearner = $studentrole->id; 1259 1260 // Get the enrol LTI plugin. 1261 $enrolplugin = enrol_get_plugin('lti'); 1262 $instanceid = $enrolplugin->add_instance($course, (array) $data); 1263 1264 // Get the tool associated with this instance. 1265 return $DB->get_record('enrol_lti_tools', array('enrolid' => $instanceid)); 1266 } 1267 1268 /** 1269 * Helper function used to create an event. 1270 * 1271 * @param array $data 1272 * @return stdClass 1273 */ 1274 public function create_event($data = []) { 1275 global $CFG; 1276 1277 require_once($CFG->dirroot . '/calendar/lib.php'); 1278 $record = new \stdClass(); 1279 $record->name = 'event name'; 1280 $record->repeat = 0; 1281 $record->repeats = 0; 1282 $record->timestart = time(); 1283 $record->timeduration = 0; 1284 $record->timesort = 0; 1285 $record->eventtype = 'user'; 1286 $record->courseid = 0; 1287 $record->categoryid = 0; 1288 1289 foreach ($data as $key => $value) { 1290 $record->$key = $value; 1291 } 1292 1293 switch ($record->eventtype) { 1294 case 'user': 1295 unset($record->categoryid); 1296 unset($record->courseid); 1297 unset($record->groupid); 1298 break; 1299 case 'group': 1300 unset($record->categoryid); 1301 break; 1302 case 'course': 1303 unset($record->categoryid); 1304 unset($record->groupid); 1305 break; 1306 case 'category': 1307 unset($record->courseid); 1308 unset($record->groupid); 1309 break; 1310 case 'site': 1311 unset($record->categoryid); 1312 unset($record->courseid); 1313 unset($record->groupid); 1314 break; 1315 } 1316 1317 $event = new calendar_event($record); 1318 $event->create($record); 1319 1320 return $event->properties(); 1321 } 1322 1323 /** 1324 * Create a new course custom field category with the given name. 1325 * 1326 * @param array $data Array with data['name'] of category 1327 * @return \core_customfield\category_controller The created category 1328 */ 1329 public function create_custom_field_category($data) : \core_customfield\category_controller { 1330 return $this->get_plugin_generator('core_customfield')->create_category($data); 1331 } 1332 1333 /** 1334 * Create a new custom field 1335 * 1336 * @param array $data Array with 'name', 'shortname' and 'type' of the field 1337 * @return \core_customfield\field_controller The created field 1338 */ 1339 public function create_custom_field($data) : \core_customfield\field_controller { 1340 global $DB; 1341 if (empty($data['categoryid']) && !empty($data['category'])) { 1342 $data['categoryid'] = $DB->get_field('customfield_category', 'id', ['name' => $data['category']]); 1343 unset($data['category']); 1344 } 1345 return $this->get_plugin_generator('core_customfield')->create_field($data); 1346 } 1347 1348 /** 1349 * Create a new category for custom profile fields. 1350 * 1351 * @param array $data Array with 'name' and optionally 'sortorder' 1352 * @return \stdClass New category object 1353 */ 1354 public function create_custom_profile_field_category(array $data): \stdClass { 1355 global $DB; 1356 1357 // Pick next sortorder if not defined. 1358 if (!array_key_exists('sortorder', $data)) { 1359 $data['sortorder'] = (int)$DB->get_field_sql('SELECT MAX(sortorder) FROM {user_info_category}') + 1; 1360 } 1361 1362 $category = (object)[ 1363 'name' => $data['name'], 1364 'sortorder' => $data['sortorder'] 1365 ]; 1366 $category->id = $DB->insert_record('user_info_category', $category); 1367 1368 return $category; 1369 } 1370 1371 /** 1372 * Creates a new custom profile field. 1373 * 1374 * Optional fields are: 1375 * 1376 * categoryid (or use 'category' to specify by name). If you don't specify 1377 * either, it will add the field to a 'Testing' category, which will be created for you if 1378 * necessary. 1379 * 1380 * sortorder (if you don't specify this, it will pick the next one in the category). 1381 * 1382 * all the other database fields (if you don't specify this, it will pick sensible defaults 1383 * based on the data type). 1384 * 1385 * @param array $data Array with 'datatype', 'shortname', and 'name' 1386 * @return \stdClass Database object from the user_info_field table 1387 */ 1388 public function create_custom_profile_field(array $data): \stdClass { 1389 global $DB, $CFG; 1390 require_once($CFG->dirroot . '/user/profile/lib.php'); 1391 1392 // Set up category if necessary. 1393 if (!array_key_exists('categoryid', $data)) { 1394 if (array_key_exists('category', $data)) { 1395 $data['categoryid'] = $DB->get_field('user_info_category', 'id', 1396 ['name' => $data['category']], MUST_EXIST); 1397 } else { 1398 // Make up a 'Testing' category or use existing. 1399 $data['categoryid'] = $DB->get_field('user_info_category', 'id', ['name' => 'Testing']); 1400 if (!$data['categoryid']) { 1401 $created = $this->create_custom_profile_field_category(['name' => 'Testing']); 1402 $data['categoryid'] = $created->id; 1403 } 1404 } 1405 } 1406 1407 // Pick sort order if necessary. 1408 if (!array_key_exists('sortorder', $data)) { 1409 $data['sortorder'] = (int)$DB->get_field_sql( 1410 'SELECT MAX(sortorder) FROM {user_info_field} WHERE categoryid = ?', 1411 [$data['categoryid']]) + 1; 1412 } 1413 1414 if ($data['datatype'] === 'menu' && isset($data['param1'])) { 1415 // Convert new lines to the proper character. 1416 $data['param1'] = str_replace('\n', "\n", $data['param1']); 1417 } 1418 1419 // Defaults for other values. 1420 $defaults = [ 1421 'description' => '', 1422 'descriptionformat' => 0, 1423 'required' => 0, 1424 'locked' => 0, 1425 'visible' => PROFILE_VISIBLE_ALL, 1426 'forceunique' => 0, 1427 'signup' => 0, 1428 'defaultdata' => '', 1429 'defaultdataformat' => 0, 1430 'param1' => '', 1431 'param2' => '', 1432 'param3' => '', 1433 'param4' => '', 1434 'param5' => '' 1435 ]; 1436 1437 // Type-specific defaults for other values. 1438 $typedefaults = [ 1439 'text' => [ 1440 'param1' => 30, 1441 'param2' => 2048 1442 ], 1443 'menu' => [ 1444 'param1' => "Yes\nNo", 1445 'defaultdata' => 'No' 1446 ], 1447 'datetime' => [ 1448 'param1' => '2010', 1449 'param2' => '2015', 1450 'param3' => 1 1451 ], 1452 'checkbox' => [ 1453 'defaultdata' => 0 1454 ] 1455 ]; 1456 foreach ($typedefaults[$data['datatype']] ?? [] as $field => $value) { 1457 $defaults[$field] = $value; 1458 } 1459 1460 foreach ($defaults as $field => $value) { 1461 if (!array_key_exists($field, $data)) { 1462 $data[$field] = $value; 1463 } 1464 } 1465 1466 $data['id'] = $DB->insert_record('user_info_field', $data); 1467 return (object)$data; 1468 } 1469 1470 /** 1471 * Create a new user, and enrol them in the specified course as the supplied role. 1472 * 1473 * @param \stdClass $course The course to enrol in 1474 * @param string $role The role to give within the course 1475 * @param \stdClass|array $userparams User parameters 1476 * @return \stdClass The created user 1477 */ 1478 public function create_and_enrol($course, $role = 'student', $userparams = null, $enrol = 'manual', 1479 $timestart = 0, $timeend = 0, $status = null) { 1480 global $DB; 1481 1482 $user = $this->create_user($userparams); 1483 $roleid = $DB->get_field('role', 'id', ['shortname' => $role ]); 1484 1485 $this->enrol_user($user->id, $course->id, $roleid, $enrol, $timestart, $timeend, $status); 1486 1487 return $user; 1488 } 1489 1490 /** 1491 * Create a new last access record for a given user in a course. 1492 * 1493 * @param \stdClass $user The user 1494 * @param \stdClass $course The course the user accessed 1495 * @param int $timestamp The timestamp for when the user last accessed the course 1496 * @return \stdClass The user_lastaccess record 1497 */ 1498 public function create_user_course_lastaccess(\stdClass $user, \stdClass $course, int $timestamp): \stdClass { 1499 global $DB; 1500 1501 $record = [ 1502 'userid' => $user->id, 1503 'courseid' => $course->id, 1504 'timeaccess' => $timestamp, 1505 ]; 1506 1507 $recordid = $DB->insert_record('user_lastaccess', $record); 1508 1509 return $DB->get_record('user_lastaccess', ['id' => $recordid], '*', MUST_EXIST); 1510 } 1511 1512 /** 1513 * Gets a default generator for a given component. 1514 * 1515 * @param string $component The component name, e.g. 'mod_forum' or 'core_question'. 1516 * @param string $classname The name of the class missing from the generators file. 1517 * @return component_generator_base The generator. 1518 */ 1519 protected function get_default_plugin_generator(string $component, ?string $classname = null) { 1520 [$type, $plugin] = core_component::normalize_component($component); 1521 1522 switch ($type) { 1523 case 'block': 1524 return new default_block_generator($this, $plugin); 1525 } 1526 1527 if (is_null($classname)) { 1528 throw new coding_exception("Component {$component} does not support " . 1529 "generators yet. Missing tests/generator/lib.php."); 1530 } 1531 1532 throw new coding_exception("Component {$component} does not support " . 1533 "data generators yet. Class {$classname} not found."); 1534 } 1535 1536 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body