MDL-55609 testing: Add a create_and_enrol helper
[moodle.git] / lib / testing / generator / data_generator.php
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/>.
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  */
26 defined('MOODLE_INTERNAL') || die();
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;
53     /** @var array list of plugin generators */
54     protected $generators = array();
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     );
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     );
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;
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;
95         foreach ($this->generators as $generator) {
96             $generator->reset();
97         }
98     }
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         list($type, $plugin) = core_component::normalize_component($component);
107         $cleancomponent = $type . '_' . $plugin;
108         if ($cleancomponent != $component) {
109             debugging("Please specify the component you want a generator for as " .
110                     "{$cleancomponent}, not {$component}.", DEBUG_DEVELOPER);
111             $component = $cleancomponent;
112         }
114         if (isset($this->generators[$component])) {
115             return $this->generators[$component];
116         }
118         $dir = core_component::get_component_directory($component);
119         $lib = $dir . '/tests/generator/lib.php';
120         if (!$dir || !is_readable($lib)) {
121             throw new coding_exception("Component {$component} does not support " .
122                     "generators yet. Missing tests/generator/lib.php.");
123         }
125         include_once($lib);
126         $classname = $component . '_generator';
128         if (!class_exists($classname)) {
129             throw new coding_exception("Component {$component} does not support " .
130                     "data generators yet. Class {$classname} not found.");
131         }
133         $this->generators[$component] = new $classname($this);
134         return $this->generators[$component];
135     }
137     /**
138      * Create a test user
139      * @param array|stdClass $record
140      * @param array $options
141      * @return stdClass user record
142      */
143     public function create_user($record=null, array $options=null) {
144         global $DB, $CFG;
146         $this->usercounter++;
147         $i = $this->usercounter;
149         $record = (array)$record;
151         if (!isset($record['auth'])) {
152             $record['auth'] = 'manual';
153         }
155         if (!isset($record['firstname']) and !isset($record['lastname'])) {
156             $country = rand(0, 5);
157             $firstname = rand(0, 4);
158             $lastname = rand(0, 4);
159             $female = rand(0, 1);
160             $record['firstname'] = $this->firstnames[($country*10) + $firstname + ($female*5)];
161             $record['lastname'] = $this->lastnames[($country*10) + $lastname + ($female*5)];
163         } else if (!isset($record['firstname'])) {
164             $record['firstname'] = 'Firstname'.$i;
166         } else if (!isset($record['lastname'])) {
167             $record['lastname'] = 'Lastname'.$i;
168         }
170         if (!isset($record['firstnamephonetic'])) {
171             $firstnamephonetic = rand(0, 59);
172             $record['firstnamephonetic'] = $this->firstnames[$firstnamephonetic];
173         }
175         if (!isset($record['lastnamephonetic'])) {
176             $lastnamephonetic = rand(0, 59);
177             $record['lastnamephonetic'] = $this->lastnames[$lastnamephonetic];
178         }
180         if (!isset($record['middlename'])) {
181             $middlename = rand(0, 59);
182             $record['middlename'] = $this->firstnames[$middlename];
183         }
185         if (!isset($record['alternatename'])) {
186             $alternatename = rand(0, 59);
187             $record['alternatename'] = $this->firstnames[$alternatename];
188         }
190         if (!isset($record['idnumber'])) {
191             $record['idnumber'] = '';
192         }
194         if (!isset($record['mnethostid'])) {
195             $record['mnethostid'] = $CFG->mnet_localhost_id;
196         }
198         if (!isset($record['username'])) {
199             $record['username'] = 'username'.$i;
200             $j = 2;
201             while ($DB->record_exists('user', array('username'=>$record['username'], 'mnethostid'=>$record['mnethostid']))) {
202                 $record['username'] = 'username'.$i.'_'.$j;
203                 $j++;
204             }
205         }
207         if (isset($record['password'])) {
208             $record['password'] = hash_internal_user_password($record['password']);
209         } else {
210             // The auth plugin may not fully support this,
211             // but it is still better/faster than hashing random stuff.
212             $record['password'] = AUTH_PASSWORD_NOT_CACHED;
213         }
215         if (!isset($record['email'])) {
216             $record['email'] = $record['username'].'@example.com';
217         }
219         if (!isset($record['confirmed'])) {
220             $record['confirmed'] = 1;
221         }
223         if (!isset($record['lang'])) {
224             $record['lang'] = 'en';
225         }
227         if (!isset($record['maildisplay'])) {
228             $record['maildisplay'] = $CFG->defaultpreference_maildisplay;
229         }
231         if (!isset($record['mailformat'])) {
232             $record['mailformat'] = $CFG->defaultpreference_mailformat;
233         }
235         if (!isset($record['maildigest'])) {
236             $record['maildigest'] = $CFG->defaultpreference_maildigest;
237         }
239         if (!isset($record['autosubscribe'])) {
240             $record['autosubscribe'] = $CFG->defaultpreference_autosubscribe;
241         }
243         if (!isset($record['trackforums'])) {
244             $record['trackforums'] = $CFG->defaultpreference_trackforums;
245         }
247         if (!isset($record['deleted'])) {
248             $record['deleted'] = 0;
249         }
251         if (!isset($record['timecreated'])) {
252             $record['timecreated'] = time();
253         }
255         $record['timemodified'] = $record['timecreated'];
256         $record['lastip'] = '0.0.0.0';
258         if ($record['deleted']) {
259             $delname = $record['email'].'.'.time();
260             while ($DB->record_exists('user', array('username'=>$delname))) {
261                 $delname++;
262             }
263             $record['idnumber'] = '';
264             $record['email']    = md5($record['username']);
265             $record['username'] = $delname;
266             $record['picture']  = 0;
267         }
269         $userid = $DB->insert_record('user', $record);
271         if (!$record['deleted']) {
272             context_user::instance($userid);
273         }
275         $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
277         if (!$record['deleted'] && isset($record['interests'])) {
278             require_once($CFG->dirroot . '/user/editlib.php');
279             if (!is_array($record['interests'])) {
280                 $record['interests'] = preg_split('/\s*,\s*/', trim($record['interests']), -1, PREG_SPLIT_NO_EMPTY);
281             }
282             useredit_update_interests($user, $record['interests']);
283         }
285         return $user;
286     }
288     /**
289      * Create a test course category
290      * @param array|stdClass $record
291      * @param array $options
292      * @return coursecat course category record
293      */
294     public function create_category($record=null, array $options=null) {
295         global $DB, $CFG;
296         require_once("$CFG->libdir/coursecatlib.php");
298         $this->categorycount++;
299         $i = $this->categorycount;
301         $record = (array)$record;
303         if (!isset($record['name'])) {
304             $record['name'] = 'Course category '.$i;
305         }
307         if (!isset($record['description'])) {
308             $record['description'] = "Test course category $i\n$this->loremipsum";
309         }
311         if (!isset($record['idnumber'])) {
312             $record['idnumber'] = '';
313         }
315         return coursecat::create($record);
316     }
318     /**
319      * Create test cohort.
320      * @param array|stdClass $record
321      * @param array $options
322      * @return stdClass cohort record
323      */
324     public function create_cohort($record=null, array $options=null) {
325         global $DB, $CFG;
326         require_once("$CFG->dirroot/cohort/lib.php");
328         $this->cohortcount++;
329         $i = $this->cohortcount;
331         $record = (array)$record;
333         if (!isset($record['contextid'])) {
334             $record['contextid'] = context_system::instance()->id;
335         }
337         if (!isset($record['name'])) {
338             $record['name'] = 'Cohort '.$i;
339         }
341         if (!isset($record['idnumber'])) {
342             $record['idnumber'] = '';
343         }
345         if (!isset($record['description'])) {
346             $record['description'] = "Test cohort $i\n$this->loremipsum";
347         }
349         if (!isset($record['descriptionformat'])) {
350             $record['descriptionformat'] = FORMAT_MOODLE;
351         }
353         if (!isset($record['visible'])) {
354             $record['visible'] = 1;
355         }
357         if (!isset($record['component'])) {
358             $record['component'] = '';
359         }
361         $id = cohort_add_cohort((object)$record);
363         return $DB->get_record('cohort', array('id'=>$id), '*', MUST_EXIST);
364     }
366     /**
367      * Create a test course
368      * @param array|stdClass $record
369      * @param array $options with keys:
370      *      'createsections'=>bool precreate all sections
371      * @return stdClass course record
372      */
373     public function create_course($record=null, array $options=null) {
374         global $DB, $CFG;
375         require_once("$CFG->dirroot/course/lib.php");
377         $this->coursecount++;
378         $i = $this->coursecount;
380         $record = (array)$record;
382         if (!isset($record['fullname'])) {
383             $record['fullname'] = 'Test course '.$i;
384         }
386         if (!isset($record['shortname'])) {
387             $record['shortname'] = 'tc_'.$i;
388         }
390         if (!isset($record['idnumber'])) {
391             $record['idnumber'] = '';
392         }
394         if (!isset($record['format'])) {
395             $record['format'] = 'topics';
396         }
398         if (!isset($record['newsitems'])) {
399             $record['newsitems'] = 0;
400         }
402         if (!isset($record['numsections'])) {
403             $record['numsections'] = 5;
404         }
406         if (!isset($record['summary'])) {
407             $record['summary'] = "Test course $i\n$this->loremipsum";
408         }
410         if (!isset($record['summaryformat'])) {
411             $record['summaryformat'] = FORMAT_MOODLE;
412         }
414         if (!isset($record['category'])) {
415             $record['category'] = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
416         }
418         if (!isset($record['startdate'])) {
419             $record['startdate'] = usergetmidnight(time());
420         }
422         if (isset($record['tags']) && !is_array($record['tags'])) {
423             $record['tags'] = preg_split('/\s*,\s*/', trim($record['tags']), -1, PREG_SPLIT_NO_EMPTY);
424         }
426         if (!empty($options['createsections']) && empty($record['numsections'])) {
427             // Since Moodle 3.3 function create_course() automatically creates sections if numsections is specified.
428             // For BC if 'createsections' is given but 'numsections' is not, assume the default value from config.
429             $record['numsections'] = get_config('moodlecourse', 'numsections');
430         }
432         $course = create_course((object)$record);
433         context_course::instance($course->id);
435         return $course;
436     }
438     /**
439      * Create course section if does not exist yet
440      * @param array|stdClass $record must contain 'course' and 'section' attributes
441      * @param array|null $options
442      * @return stdClass
443      * @throws coding_exception
444      */
445     public function create_course_section($record = null, array $options = null) {
446         global $DB;
448         $record = (array)$record;
450         if (empty($record['course'])) {
451             throw new coding_exception('course must be present in testing_data_generator::create_course_section() $record');
452         }
454         if (!isset($record['section'])) {
455             throw new coding_exception('section must be present in testing_data_generator::create_course_section() $record');
456         }
458         course_create_sections_if_missing($record['course'], $record['section']);
459         return get_fast_modinfo($record['course'])->get_section_info($record['section']);
460     }
462     /**
463      * Create a test block.
464      *
465      * The $record passed in becomes the basis for the new row added to the
466      * block_instances table. You only need to supply the values of interest.
467      * Any missing values have sensible defaults filled in, and ->blockname will be set based on $blockname.
468      *
469      * The $options array provides additional data, not directly related to what
470      * will be inserted in the block_instance table, which may affect the block
471      * that is created. The meanings of any data passed here depends on the particular
472      * type of block being created.
473      *
474      * @param string $blockname the type of block to create. E.g. 'html'.
475      * @param array|stdClass $record forms the basis for the entry to be inserted in the block_instances table.
476      * @param array $options further, block-specific options to control how the block is created.
477      * @return stdClass new block_instance record.
478      */
479     public function create_block($blockname, $record=null, array $options=array()) {
480         $generator = $this->get_plugin_generator('block_'.$blockname);
481         return $generator->create_instance($record, $options);
482     }
484     /**
485      * Create a test activity module.
486      *
487      * The $record should contain the same data that you would call from
488      * ->get_data() when the mod_[type]_mod_form is submitted, except that you
489      * only need to supply values of interest. The only required value is
490      * 'course'. Any missing values will have a sensible default supplied.
491      *
492      * The $options array provides additional data, not directly related to what
493      * would come back from the module edit settings form, which may affect the activity
494      * that is created. The meanings of any data passed here depends on the particular
495      * type of activity being created.
496      *
497      * @param string $modulename the type of activity to create. E.g. 'forum' or 'quiz'.
498      * @param array|stdClass $record data, as if from the module edit settings form.
499      * @param array $options additional data that may affect how the module is created.
500      * @return stdClass activity record new new record that was just inserted in the table
501      *      like 'forum' or 'quiz', with a ->cmid field added.
502      */
503     public function create_module($modulename, $record=null, array $options=null) {
504         $generator = $this->get_plugin_generator('mod_'.$modulename);
505         return $generator->create_instance($record, $options);
506     }
508     /**
509      * Create a test group for the specified course
510      *
511      * $record should be either an array or a stdClass containing infomation about the group to create.
512      * At the very least it needs to contain courseid.
513      * Default values are added for name, description, and descriptionformat if they are not present.
514      *
515      * This function calls groups_create_group() to create the group within the database.
516      * @see groups_create_group
517      * @param array|stdClass $record
518      * @return stdClass group record
519      */
520     public function create_group($record) {
521         global $DB, $CFG;
523         require_once($CFG->dirroot . '/group/lib.php');
525         $this->groupcount++;
526         $i = $this->groupcount;
528         $record = (array)$record;
530         if (empty($record['courseid'])) {
531             throw new coding_exception('courseid must be present in testing_data_generator::create_group() $record');
532         }
534         if (!isset($record['name'])) {
535             $record['name'] = 'group-' . $i;
536         }
538         if (!isset($record['description'])) {
539             $record['description'] = "Test Group $i\n{$this->loremipsum}";
540         }
542         if (!isset($record['descriptionformat'])) {
543             $record['descriptionformat'] = FORMAT_MOODLE;
544         }
546         $id = groups_create_group((object)$record);
548         return $DB->get_record('groups', array('id'=>$id));
549     }
551     /**
552      * Create a test group member
553      * @param array|stdClass $record
554      * @throws coding_exception
555      * @return boolean
556      */
557     public function create_group_member($record) {
558         global $DB, $CFG;
560         require_once($CFG->dirroot . '/group/lib.php');
562         $record = (array)$record;
564         if (empty($record['userid'])) {
565             throw new coding_exception('user must be present in testing_util::create_group_member() $record');
566         }
568         if (!isset($record['groupid'])) {
569             throw new coding_exception('group must be present in testing_util::create_group_member() $record');
570         }
572         if (!isset($record['component'])) {
573             $record['component'] = null;
574         }
575         if (!isset($record['itemid'])) {
576             $record['itemid'] = 0;
577         }
579         return groups_add_member($record['groupid'], $record['userid'], $record['component'], $record['itemid']);
580     }
582     /**
583      * Create a test grouping for the specified course
584      *
585      * $record should be either an array or a stdClass containing infomation about the grouping to create.
586      * At the very least it needs to contain courseid.
587      * Default values are added for name, description, and descriptionformat if they are not present.
588      *
589      * This function calls groups_create_grouping() to create the grouping within the database.
590      * @see groups_create_grouping
591      * @param array|stdClass $record
592      * @return stdClass grouping record
593      */
594     public function create_grouping($record) {
595         global $DB, $CFG;
597         require_once($CFG->dirroot . '/group/lib.php');
599         $this->groupingcount++;
600         $i = $this->groupingcount;
602         $record = (array)$record;
604         if (empty($record['courseid'])) {
605             throw new coding_exception('courseid must be present in testing_data_generator::create_grouping() $record');
606         }
608         if (!isset($record['name'])) {
609             $record['name'] = 'grouping-' . $i;
610         }
612         if (!isset($record['description'])) {
613             $record['description'] = "Test Grouping $i\n{$this->loremipsum}";
614         }
616         if (!isset($record['descriptionformat'])) {
617             $record['descriptionformat'] = FORMAT_MOODLE;
618         }
620         $id = groups_create_grouping((object)$record);
622         return $DB->get_record('groupings', array('id'=>$id));
623     }
625     /**
626      * Create a test grouping group
627      * @param array|stdClass $record
628      * @throws coding_exception
629      * @return boolean
630      */
631     public function create_grouping_group($record) {
632         global $DB, $CFG;
634         require_once($CFG->dirroot . '/group/lib.php');
636         $record = (array)$record;
638         if (empty($record['groupingid'])) {
639             throw new coding_exception('grouping must be present in testing::create_grouping_group() $record');
640         }
642         if (!isset($record['groupid'])) {
643             throw new coding_exception('group must be present in testing_util::create_grouping_group() $record');
644         }
646         return groups_assign_grouping($record['groupingid'], $record['groupid']);
647     }
649     /**
650      * Create an instance of a repository.
651      *
652      * @param string type of repository to create an instance for.
653      * @param array|stdClass $record data to use to up set the instance.
654      * @param array $options options
655      * @return stdClass repository instance record
656      * @since Moodle 2.5.1
657      */
658     public function create_repository($type, $record=null, array $options = null) {
659         $generator = $this->get_plugin_generator('repository_'.$type);
660         return $generator->create_instance($record, $options);
661     }
663     /**
664      * Create an instance of a repository.
665      *
666      * @param string type of repository to create an instance for.
667      * @param array|stdClass $record data to use to up set the instance.
668      * @param array $options options
669      * @return repository_type object
670      * @since Moodle 2.5.1
671      */
672     public function create_repository_type($type, $record=null, array $options = null) {
673         $generator = $this->get_plugin_generator('repository_'.$type);
674         return $generator->create_type($record, $options);
675     }
678     /**
679      * Create a test scale
680      * @param array|stdClass $record
681      * @param array $options
682      * @return stdClass block instance record
683      */
684     public function create_scale($record=null, array $options=null) {
685         global $DB;
687         $this->scalecount++;
688         $i = $this->scalecount;
690         $record = (array)$record;
692         if (!isset($record['name'])) {
693             $record['name'] = 'Test scale '.$i;
694         }
696         if (!isset($record['scale'])) {
697             $record['scale'] = 'A,B,C,D,F';
698         }
700         if (!isset($record['courseid'])) {
701             $record['courseid'] = 0;
702         }
704         if (!isset($record['userid'])) {
705             $record['userid'] = 0;
706         }
708         if (!isset($record['description'])) {
709             $record['description'] = 'Test scale description '.$i;
710         }
712         if (!isset($record['descriptionformat'])) {
713             $record['descriptionformat'] = FORMAT_MOODLE;
714         }
716         $record['timemodified'] = time();
718         if (isset($record['id'])) {
719             $DB->import_record('scale', $record);
720             $DB->get_manager()->reset_sequence('scale');
721             $id = $record['id'];
722         } else {
723             $id = $DB->insert_record('scale', $record);
724         }
726         return $DB->get_record('scale', array('id'=>$id), '*', MUST_EXIST);
727     }
729     /**
730      * Creates a new role in the system.
731      *
732      * You can fill $record with the role 'name',
733      * 'shortname', 'description' and 'archetype'.
734      *
735      * If an archetype is specified it's capabilities,
736      * context where the role can be assigned and
737      * all other properties are copied from the archetype;
738      * if no archetype is specified it will create an
739      * empty role.
740      *
741      * @param array|stdClass $record
742      * @return int The new role id
743      */
744     public function create_role($record=null) {
745         global $DB;
747         $this->rolecount++;
748         $i = $this->rolecount;
750         $record = (array)$record;
752         if (empty($record['shortname'])) {
753             $record['shortname'] = 'role-' . $i;
754         }
756         if (empty($record['name'])) {
757             $record['name'] = 'Test role ' . $i;
758         }
760         if (empty($record['description'])) {
761             $record['description'] = 'Test role ' . $i . ' description';
762         }
764         if (empty($record['archetype'])) {
765             $record['archetype'] = '';
766         } else {
767             $archetypes = get_role_archetypes();
768             if (empty($archetypes[$record['archetype']])) {
769                 throw new coding_exception('\'role\' requires the field \'archetype\' to specify a ' .
770                     'valid archetype shortname (editingteacher, student...)');
771             }
772         }
774         // Creates the role.
775         if (!$newroleid = create_role($record['name'], $record['shortname'], $record['description'], $record['archetype'])) {
776             throw new coding_exception('There was an error creating \'' . $record['shortname'] . '\' role');
777         }
779         // If no archetype was specified we allow it to be added to all contexts,
780         // otherwise we allow it in the archetype contexts.
781         if (!$record['archetype']) {
782             $contextlevels = array_keys(context_helper::get_all_levels());
783         } else {
784             // Copying from the archetype default rol.
785             $archetyperoleid = $DB->get_field(
786                 'role',
787                 'id',
788                 array('shortname' => $record['archetype'], 'archetype' => $record['archetype'])
789             );
790             $contextlevels = get_role_contextlevels($archetyperoleid);
791         }
792         set_role_contextlevels($newroleid, $contextlevels);
794         if ($record['archetype']) {
796             // We copy all the roles the archetype can assign, override, switch to and view.
797             if ($record['archetype']) {
798                 $types = array('assign', 'override', 'switch', 'view');
799                 foreach ($types as $type) {
800                     $rolestocopy = get_default_role_archetype_allows($type, $record['archetype']);
801                     foreach ($rolestocopy as $tocopy) {
802                         $functionname = "core_role_set_{$type}_allowed";
803                         $functionname($newroleid, $tocopy);
804                     }
805                 }
806             }
808             // Copying the archetype capabilities.
809             $sourcerole = $DB->get_record('role', array('id' => $archetyperoleid));
810             role_cap_duplicate($sourcerole, $newroleid);
811         }
813         return $newroleid;
814     }
816     /**
817      * Create a tag.
818      *
819      * @param array|stdClass $record
820      * @return stdClass the tag record
821      */
822     public function create_tag($record = null) {
823         global $DB, $USER;
825         $this->tagcount++;
826         $i = $this->tagcount;
828         $record = (array) $record;
830         if (!isset($record['userid'])) {
831             $record['userid'] = $USER->id;
832         }
834         if (!isset($record['rawname'])) {
835             if (isset($record['name'])) {
836                 $record['rawname'] = $record['name'];
837             } else {
838                 $record['rawname'] = 'Tag name ' . $i;
839             }
840         }
842         // Attribute 'name' should be a lowercase version of 'rawname', if not set.
843         if (!isset($record['name'])) {
844             $record['name'] = core_text::strtolower($record['rawname']);
845         } else {
846             $record['name'] = core_text::strtolower($record['name']);
847         }
849         if (!isset($record['tagcollid'])) {
850             $record['tagcollid'] = core_tag_collection::get_default();
851         }
853         if (!isset($record['description'])) {
854             $record['description'] = 'Tag description';
855         }
857         if (!isset($record['descriptionformat'])) {
858             $record['descriptionformat'] = FORMAT_MOODLE;
859         }
861         if (!isset($record['flag'])) {
862             $record['flag'] = 0;
863         }
865         if (!isset($record['timemodified'])) {
866             $record['timemodified'] = time();
867         }
869         $id = $DB->insert_record('tag', $record);
871         return $DB->get_record('tag', array('id' => $id), '*', MUST_EXIST);
872     }
874     /**
875      * Helper method which combines $defaults with the values specified in $record.
876      * If $record is an object, it is converted to an array.
877      * Then, for each key that is in $defaults, but not in $record, the value
878      * from $defaults is copied.
879      * @param array $defaults the default value for each field with
880      * @param array|stdClass $record
881      * @return array updated $record.
882      */
883     public function combine_defaults_and_record(array $defaults, $record) {
884         $record = (array) $record;
886         foreach ($defaults as $key => $defaults) {
887             if (!array_key_exists($key, $record)) {
888                 $record[$key] = $defaults;
889             }
890         }
891         return $record;
892     }
894     /**
895      * Simplified enrolment of user to course using default options.
896      *
897      * It is strongly recommended to use only this method for 'manual' and 'self' plugins only!!!
898      *
899      * @param int $userid
900      * @param int $courseid
901      * @param int|string $roleidorshortname optional role id or role shortname, use only with manual plugin
902      * @param string $enrol name of enrol plugin,
903      *     there must be exactly one instance in course,
904      *     it must support enrol_user() method.
905      * @param int $timestart (optional) 0 means unknown
906      * @param int $timeend (optional) 0 means forever
907      * @param int $status (optional) default to ENROL_USER_ACTIVE for new enrolments
908      * @return bool success
909      */
910     public function enrol_user($userid, $courseid, $roleidorshortname = null, $enrol = 'manual',
911             $timestart = 0, $timeend = 0, $status = null) {
912         global $DB;
914         // If role is specified by shortname, convert it into an id.
915         if (!is_numeric($roleidorshortname) && is_string($roleidorshortname)) {
916             $roleid = $DB->get_field('role', 'id', array('shortname' => $roleidorshortname), MUST_EXIST);
917         } else {
918             $roleid = $roleidorshortname;
919         }
921         if (!$plugin = enrol_get_plugin($enrol)) {
922             return false;
923         }
925         $instances = $DB->get_records('enrol', array('courseid'=>$courseid, 'enrol'=>$enrol));
926         if (count($instances) != 1) {
927             return false;
928         }
929         $instance = reset($instances);
931         if (is_null($roleid) and $instance->roleid) {
932             $roleid = $instance->roleid;
933         }
935         $plugin->enrol_user($instance, $userid, $roleid, $timestart, $timeend, $status);
936         return true;
937     }
939     /**
940      * Assigns the specified role to a user in the context.
941      *
942      * @param int $roleid
943      * @param int $userid
944      * @param int $contextid Defaults to the system context
945      * @return int new/existing id of the assignment
946      */
947     public function role_assign($roleid, $userid, $contextid = false) {
949         // Default to the system context.
950         if (!$contextid) {
951             $context = context_system::instance();
952             $contextid = $context->id;
953         }
955         if (empty($roleid)) {
956             throw new coding_exception('roleid must be present in testing_data_generator::role_assign() arguments');
957         }
959         if (empty($userid)) {
960             throw new coding_exception('userid must be present in testing_data_generator::role_assign() arguments');
961         }
963         return role_assign($roleid, $userid, $contextid);
964     }
966     /**
967      * Create a grade_category.
968      *
969      * @param array|stdClass $record
970      * @return stdClass the grade category record
971      */
972     public function create_grade_category($record = null) {
973         global $CFG;
975         $this->gradecategorycounter++;
977         $record = (array)$record;
979         if (empty($record['courseid'])) {
980             throw new coding_exception('courseid must be present in testing::create_grade_category() $record');
981         }
983         if (!isset($record['fullname'])) {
984             $record['fullname'] = 'Grade category ' . $this->gradecategorycounter;
985         }
987         // For gradelib classes.
988         require_once($CFG->libdir . '/gradelib.php');
989         // Create new grading category in this course.
990         $gradecategory = new grade_category(array('courseid' => $record['courseid']), false);
991         $gradecategory->apply_default_settings();
992         grade_category::set_properties($gradecategory, $record);
993         $gradecategory->apply_forced_settings();
994         $gradecategory->insert();
996         // This creates a default grade item for the category
997         $gradeitem = $gradecategory->load_grade_item();
999         $gradecategory->update_from_db();
1000         return $gradecategory->get_record_data();
1001     }
1003     /**
1004      * Create a grade_item.
1005      *
1006      * @param array|stdClass $record
1007      * @return stdClass the grade item record
1008      */
1009     public function create_grade_item($record = null) {
1010         global $CFG;
1011         require_once("$CFG->libdir/gradelib.php");
1013         $this->gradeitemcounter++;
1015         if (!isset($record['itemtype'])) {
1016             $record['itemtype'] = 'manual';
1017         }
1019         if (!isset($record['itemname'])) {
1020             $record['itemname'] = 'Grade item ' . $this->gradeitemcounter;
1021         }
1023         if (isset($record['outcomeid'])) {
1024             $outcome = new grade_outcome(array('id' => $record['outcomeid']));
1025             $record['scaleid'] = $outcome->scaleid;
1026         }
1027         if (isset($record['scaleid'])) {
1028             $record['gradetype'] = GRADE_TYPE_SCALE;
1029         } else if (!isset($record['gradetype'])) {
1030             $record['gradetype'] = GRADE_TYPE_VALUE;
1031         }
1033         // Create new grade item in this course.
1034         $gradeitem = new grade_item($record, false);
1035         $gradeitem->insert();
1037         $gradeitem->update_from_db();
1038         return $gradeitem->get_record_data();
1039     }
1041     /**
1042      * Create a grade_outcome.
1043      *
1044      * @param array|stdClass $record
1045      * @return stdClass the grade outcome record
1046      */
1047     public function create_grade_outcome($record = null) {
1048         global $CFG;
1050         $this->gradeoutcomecounter++;
1051         $i = $this->gradeoutcomecounter;
1053         if (!isset($record['fullname'])) {
1054             $record['fullname'] = 'Grade outcome ' . $i;
1055         }
1057         // For gradelib classes.
1058         require_once($CFG->libdir . '/gradelib.php');
1059         // Create new grading outcome in this course.
1060         $gradeoutcome = new grade_outcome($record, false);
1061         $gradeoutcome->insert();
1063         $gradeoutcome->update_from_db();
1064         return $gradeoutcome->get_record_data();
1065     }
1067     /**
1068      * Helper function used to create an LTI tool.
1069      *
1070      * @param array $data
1071      * @return stdClass the tool
1072      */
1073     public function create_lti_tool($data = array()) {
1074         global $DB;
1076         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1077         $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1079         // Create a course if no course id was specified.
1080         if (empty($data->courseid)) {
1081             $course = $this->create_course();
1082             $data->courseid = $course->id;
1083         } else {
1084             $course = get_course($data->courseid);
1085         }
1087         if (!empty($data->cmid)) {
1088             $data->contextid = context_module::instance($data->cmid)->id;
1089         } else {
1090             $data->contextid = context_course::instance($data->courseid)->id;
1091         }
1093         // Set it to enabled if no status was specified.
1094         if (!isset($data->status)) {
1095             $data->status = ENROL_INSTANCE_ENABLED;
1096         }
1098         // Add some extra necessary fields to the data.
1099         $data->name = 'Test LTI';
1100         $data->roleinstructor = $studentrole->id;
1101         $data->rolelearner = $teacherrole->id;
1103         // Get the enrol LTI plugin.
1104         $enrolplugin = enrol_get_plugin('lti');
1105         $instanceid = $enrolplugin->add_instance($course, (array) $data);
1107         // Get the tool associated with this instance.
1108         return $DB->get_record('enrol_lti_tools', array('enrolid' => $instanceid));
1109     }
1111     /**
1112      * Helper function used to create an event.
1113      *
1114      * @param   array   $data
1115      * @return  stdClass
1116      */
1117     public function create_event($data = []) {
1118         global $CFG;
1120         require_once($CFG->dirroot . '/calendar/lib.php');
1121         $record = new \stdClass();
1122         $record->name = 'event name';
1123         $record->eventtype = 'global';
1124         $record->repeat = 0;
1125         $record->repeats = 0;
1126         $record->timestart = time();
1127         $record->timeduration = 0;
1128         $record->timesort = 0;
1129         $record->eventtype = 'user';
1130         $record->courseid = 0;
1131         $record->categoryid = 0;
1133         foreach ($data as $key => $value) {
1134             $record->$key = $value;
1135         }
1137         switch ($record->eventtype) {
1138             case 'user':
1139                 unset($record->categoryid);
1140                 unset($record->courseid);
1141                 unset($record->groupid);
1142                 break;
1143             case 'group':
1144                 unset($record->categoryid);
1145                 break;
1146             case 'course':
1147                 unset($record->categoryid);
1148                 unset($record->groupid);
1149                 break;
1150             case 'category':
1151                 unset($record->courseid);
1152                 unset($record->groupid);
1153                 break;
1154             case 'global':
1155                 unset($record->categoryid);
1156                 unset($record->courseid);
1157                 unset($record->groupid);
1158                 break;
1159         }
1161         $event = new calendar_event($record);
1162         $event->create($record);
1164         return $event->properties();
1165     }
1167     /**
1168      * Create a new user, and enrol them in the specified course as the supplied role.
1169      *
1170      * @param   \stdClass   $course The course to enrol in
1171      * @param   string      $role The role to give within the course
1172      * @param   \stdClass   $userparams User parameters
1173      * @return  \stdClass   The created user
1174      */
1175     public function create_and_enrol($course, $role = 'student', $userparams = null, $enrol = 'manual',
1176             $timestart = 0, $timeend = 0, $status = null) {
1177         global $DB;
1179         $user = $this->create_user($userparams);
1180         $roleid = $DB->get_field('role', 'id', ['shortname' => $role ]);
1182         $this->enrol_user($user->id, $course->id, $roleid, $enrol, $timestart, $timeend, $status);
1184         return $user;
1185     }