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