Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

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  }