MDL-55609 testing: Add a create_and_enrol helper
[moodle.git] / lib / testing / generator / data_generator.php
CommitLineData
7e7cfe7a
PS
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
6b219869 21 * @category test
7e7cfe7a
PS
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
c29e3e24 26defined('MOODLE_INTERNAL') || die();
7e7cfe7a
PS
27
28/**
6b219869 29 * Data generator class for unit tests and other tools that need to create fake test sites.
7e7cfe7a
PS
30 *
31 * @package core
6b219869 32 * @category test
7e7cfe7a
PS
33 * @copyright 2012 Petr Skoda {@link http://skodak.org}
34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35 */
5c3c2c81 36class testing_data_generator {
cfa91962 37 /** @var int The number of grade categories created */
15ace204 38 protected $gradecategorycounter = 0;
cfa91962
JO
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;
7e7cfe7a
PS
43 protected $usercounter = 0;
44 protected $categorycount = 0;
4729332b 45 protected $cohortcount = 0;
7e7cfe7a
PS
46 protected $coursecount = 0;
47 protected $scalecount = 0;
48 protected $groupcount = 0;
49 protected $groupingcount = 0;
702851c0 50 protected $rolecount = 0;
e0c86198 51 protected $tagcount = 0;
7e7cfe7a
PS
52
53 /** @var array list of plugin generators */
54 protected $generators = array();
55
56 /** @var array lis of common last names */
57 public $lastnames = array(
58 'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Miller', 'Davis', 'García', 'Rodríguez', 'Wilson',
59 'Müller', 'Schmidt', 'Schneider', 'Fischer', 'Meyer', 'Weber', 'Schulz', 'Wagner', 'Becker', 'Hoffmann',
60 'Novák', 'Svoboda', 'Novotný', 'Dvořák', 'Černý', 'Procházková', 'Kučerová', 'Veselá', 'Horáková', 'Němcová',
61 'Смирнов', 'Иванов', 'Кузнецов', 'Соколов', 'Попов', 'Лебедева', 'Козлова', 'Новикова', 'Морозова', 'Петрова',
62 '王', '李', '张', '刘', '陈', '楊', '黃', '趙', '吳', '周',
63 '佐藤', '鈴木', '高橋', '田中', '渡辺', '伊藤', '山本', '中村', '小林', '斎藤',
64 );
65
66 /** @var array lis of common first names */
67 public $firstnames = array(
68 'Jacob', 'Ethan', 'Michael', 'Jayden', 'William', 'Isabella', 'Sophia', 'Emma', 'Olivia', 'Ava',
69 'Lukas', 'Leon', 'Luca', 'Timm', 'Paul', 'Leonie', 'Leah', 'Lena', 'Hanna', 'Laura',
70 'Jakub', 'Jan', 'Tomáš', 'Lukáš', 'Matěj', 'Tereza', 'Eliška', 'Anna', 'Adéla', 'Karolína',
71 'Даниил', 'Максим', 'Артем', 'Иван', 'Александр', 'София', 'Анастасия', 'Дарья', 'Мария', 'Полина',
72 '伟', '伟', '芳', '伟', '秀英', '秀英', '娜', '秀英', '伟', '敏',
73 '翔', '大翔', '拓海', '翔太', '颯太', '陽菜', 'さくら', '美咲', '葵', '美羽',
74 );
75
76 public $loremipsum = <<<EOD
77Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla non arcu lacinia neque faucibus fringilla. Vivamus porttitor turpis ac leo. Integer in sapien. Nullam eget nisl. Aliquam erat volutpat. Cras elementum. Mauris suscipit, ligula sit amet pharetra semper, nibh ante cursus purus, vel sagittis velit mauris vel metus. Integer malesuada. Nullam lectus justo, vulputate eget mollis sed, tempor sed magna. Mauris elementum mauris vitae tortor. Aliquam erat volutpat.
78Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Pellentesque ipsum. Cras pede libero, dapibus nec, pretium sit amet, tempor quis. Aliquam ante. Proin in tellus sit amet nibh dignissim sagittis. Vivamus porttitor turpis ac leo. Duis bibendum, lectus ut viverra rhoncus, dolor nunc faucibus libero, eget facilisis enim ipsum id lacus. In sem justo, commodo ut, suscipit at, pharetra vitae, orci. Aliquam erat volutpat. Nulla est.
79Vivamus luctus egestas leo. Aenean fermentum risus id tortor. Mauris dictum facilisis augue. Aliquam erat volutpat. Aliquam ornare wisi eu metus. Aliquam id dolor. Duis condimentum augue id magna semper rutrum. Donec iaculis gravida nulla. Pellentesque ipsum. Etiam dictum tincidunt diam. Quisque tincidunt scelerisque libero. Etiam egestas wisi a erat.
80Integer lacinia. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris tincidunt sem sed arcu. Nullam feugiat, turpis at pulvinar vulputate, erat libero tristique tellus, nec bibendum odio risus sit amet ante. Aliquam id dolor. Maecenas sollicitudin. Et harum quidem rerum facilis est et expedita distinctio. Mauris suscipit, ligula sit amet pharetra semper, nibh ante cursus purus, vel sagittis velit mauris vel metus. Nullam dapibus fermentum ipsum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Pellentesque sapien. Duis risus. Mauris elementum mauris vitae tortor. Suspendisse nisl. Integer rutrum, orci vestibulum ullamcorper ultricies, lacus quam ultricies odio, vitae placerat pede sem sit amet enim.
81In laoreet, magna id viverra tincidunt, sem odio bibendum justo, vel imperdiet sapien wisi sed libero. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Nullam justo enim, consectetuer nec, ullamcorper ac, vestibulum in, elit. Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? Maecenas lorem. Etiam posuere lacus quis dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Nam sed tellus id magna elementum tincidunt. Suspendisse nisl. Vivamus luctus egestas leo. Nulla non arcu lacinia neque faucibus fringilla. Etiam dui sem, fermentum vitae, sagittis id, malesuada in, quam. Etiam dictum tincidunt diam. Etiam commodo dui eget wisi. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Duis ante orci, molestie vitae vehicula venenatis, tincidunt ac pede. Pellentesque sapien.
82EOD;
83
84 /**
85 * To be called from data reset code only,
86 * do not use in tests.
87 * @return void
88 */
89 public function reset() {
90 $this->usercounter = 0;
91 $this->categorycount = 0;
92 $this->coursecount = 0;
93 $this->scalecount = 0;
94
5c3c2c81 95 foreach ($this->generators as $generator) {
7e7cfe7a
PS
96 $generator->reset();
97 }
98 }
99
100 /**
ba203de1
TH
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.
7e7cfe7a
PS
104 */
105 public function get_plugin_generator($component) {
56da374e 106 list($type, $plugin) = core_component::normalize_component($component);
ba203de1
TH
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 }
7e7cfe7a 113
ba203de1
TH
114 if (isset($this->generators[$component])) {
115 return $this->generators[$component];
7e7cfe7a
PS
116 }
117
b0d1d941 118 $dir = core_component::get_component_directory($component);
ba203de1
TH
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 }
7e7cfe7a 124
ba203de1
TH
125 include_once($lib);
126 $classname = $component . '_generator';
127
128 if (!class_exists($classname)) {
129 throw new coding_exception("Component {$component} does not support " .
130 "data generators yet. Class {$classname} not found.");
7e7cfe7a
PS
131 }
132
ba203de1
TH
133 $this->generators[$component] = new $classname($this);
134 return $this->generators[$component];
7e7cfe7a
PS
135 }
136
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;
145
146 $this->usercounter++;
147 $i = $this->usercounter;
148
149 $record = (array)$record;
150
151 if (!isset($record['auth'])) {
152 $record['auth'] = 'manual';
153 }
154
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)];
162
163 } else if (!isset($record['firstname'])) {
164 $record['firstname'] = 'Firstname'.$i;
165
166 } else if (!isset($record['lastname'])) {
167 $record['lastname'] = 'Lastname'.$i;
168 }
169
a327f25e
AG
170 if (!isset($record['firstnamephonetic'])) {
171 $firstnamephonetic = rand(0, 59);
172 $record['firstnamephonetic'] = $this->firstnames[$firstnamephonetic];
173 }
174
e5035ecb 175 if (!isset($record['lastnamephonetic'])) {
a327f25e
AG
176 $lastnamephonetic = rand(0, 59);
177 $record['lastnamephonetic'] = $this->lastnames[$lastnamephonetic];
178 }
179
180 if (!isset($record['middlename'])) {
181 $middlename = rand(0, 59);
182 $record['middlename'] = $this->firstnames[$middlename];
183 }
184
185 if (!isset($record['alternatename'])) {
186 $alternatename = rand(0, 59);
187 $record['alternatename'] = $this->firstnames[$alternatename];
188 }
189
7e7cfe7a
PS
190 if (!isset($record['idnumber'])) {
191 $record['idnumber'] = '';
192 }
193
194 if (!isset($record['mnethostid'])) {
195 $record['mnethostid'] = $CFG->mnet_localhost_id;
196 }
197
198 if (!isset($record['username'])) {
fe67134e
PS
199 $record['username'] = 'username'.$i;
200 $j = 2;
7e7cfe7a 201 while ($DB->record_exists('user', array('username'=>$record['username'], 'mnethostid'=>$record['mnethostid']))) {
fe67134e
PS
202 $record['username'] = 'username'.$i.'_'.$j;
203 $j++;
7e7cfe7a
PS
204 }
205 }
206
dbf60a04
PS
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;
7e7cfe7a
PS
213 }
214
215 if (!isset($record['email'])) {
216 $record['email'] = $record['username'].'@example.com';
217 }
218
219 if (!isset($record['confirmed'])) {
220 $record['confirmed'] = 1;
221 }
222
223 if (!isset($record['lang'])) {
224 $record['lang'] = 'en';
225 }
226
227 if (!isset($record['maildisplay'])) {
9f7379e9
MG
228 $record['maildisplay'] = $CFG->defaultpreference_maildisplay;
229 }
230
231 if (!isset($record['mailformat'])) {
232 $record['mailformat'] = $CFG->defaultpreference_mailformat;
233 }
234
235 if (!isset($record['maildigest'])) {
236 $record['maildigest'] = $CFG->defaultpreference_maildigest;
237 }
238
239 if (!isset($record['autosubscribe'])) {
240 $record['autosubscribe'] = $CFG->defaultpreference_autosubscribe;
241 }
242
243 if (!isset($record['trackforums'])) {
244 $record['trackforums'] = $CFG->defaultpreference_trackforums;
7e7cfe7a
PS
245 }
246
247 if (!isset($record['deleted'])) {
248 $record['deleted'] = 0;
249 }
250
2d35b7d3
GPL
251 if (!isset($record['timecreated'])) {
252 $record['timecreated'] = time();
253 }
254
7e7cfe7a
PS
255 $record['timemodified'] = $record['timecreated'];
256 $record['lastip'] = '0.0.0.0';
257
7e7cfe7a
PS
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 }
268
269 $userid = $DB->insert_record('user', $record);
270
271 if (!$record['deleted']) {
272 context_user::instance($userid);
273 }
274
810805da
MG
275 $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
276
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 }
284
285 return $user;
7e7cfe7a
PS
286 }
287
288 /**
289 * Create a test course category
290 * @param array|stdClass $record
291 * @param array $options
b28bb7e8 292 * @return coursecat course category record
7e7cfe7a 293 */
4729332b 294 public function create_category($record=null, array $options=null) {
7e7cfe7a 295 global $DB, $CFG;
b28bb7e8 296 require_once("$CFG->libdir/coursecatlib.php");
7e7cfe7a
PS
297
298 $this->categorycount++;
299 $i = $this->categorycount;
300
301 $record = (array)$record;
302
303 if (!isset($record['name'])) {
304 $record['name'] = 'Course category '.$i;
305 }
306
7e7cfe7a
PS
307 if (!isset($record['description'])) {
308 $record['description'] = "Test course category $i\n$this->loremipsum";
309 }
310
b28bb7e8
MG
311 if (!isset($record['idnumber'])) {
312 $record['idnumber'] = '';
7e7cfe7a 313 }
7e7cfe7a 314
b28bb7e8 315 return coursecat::create($record);
7e7cfe7a
PS
316 }
317
4729332b
PS
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");
327
328 $this->cohortcount++;
329 $i = $this->cohortcount;
330
331 $record = (array)$record;
332
333 if (!isset($record['contextid'])) {
334 $record['contextid'] = context_system::instance()->id;
335 }
336
337 if (!isset($record['name'])) {
338 $record['name'] = 'Cohort '.$i;
339 }
340
341 if (!isset($record['idnumber'])) {
342 $record['idnumber'] = '';
343 }
344
345 if (!isset($record['description'])) {
346 $record['description'] = "Test cohort $i\n$this->loremipsum";
347 }
348
349 if (!isset($record['descriptionformat'])) {
350 $record['descriptionformat'] = FORMAT_MOODLE;
351 }
352
80f98467
MG
353 if (!isset($record['visible'])) {
354 $record['visible'] = 1;
355 }
356
4729332b
PS
357 if (!isset($record['component'])) {
358 $record['component'] = '';
359 }
360
361 $id = cohort_add_cohort((object)$record);
362
363 return $DB->get_record('cohort', array('id'=>$id), '*', MUST_EXIST);
364 }
365
7e7cfe7a
PS
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 */
4729332b 373 public function create_course($record=null, array $options=null) {
7e7cfe7a
PS
374 global $DB, $CFG;
375 require_once("$CFG->dirroot/course/lib.php");
376
377 $this->coursecount++;
378 $i = $this->coursecount;
379
380 $record = (array)$record;
381
382 if (!isset($record['fullname'])) {
383 $record['fullname'] = 'Test course '.$i;
384 }
385
386 if (!isset($record['shortname'])) {
387 $record['shortname'] = 'tc_'.$i;
388 }
389
390 if (!isset($record['idnumber'])) {
391 $record['idnumber'] = '';
392 }
393
394 if (!isset($record['format'])) {
395 $record['format'] = 'topics';
396 }
397
398 if (!isset($record['newsitems'])) {
399 $record['newsitems'] = 0;
400 }
401
402 if (!isset($record['numsections'])) {
403 $record['numsections'] = 5;
404 }
405
4a38e659
PS
406 if (!isset($record['summary'])) {
407 $record['summary'] = "Test course $i\n$this->loremipsum";
7e7cfe7a
PS
408 }
409
4a38e659
PS
410 if (!isset($record['summaryformat'])) {
411 $record['summaryformat'] = FORMAT_MOODLE;
7e7cfe7a
PS
412 }
413
414 if (!isset($record['category'])) {
415 $record['category'] = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
416 }
417
68c2b726
AN
418 if (!isset($record['startdate'])) {
419 $record['startdate'] = usergetmidnight(time());
420 }
421
810805da
MG
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 }
425
89b909f6
MG
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 }
431
7e7cfe7a
PS
432 $course = create_course((object)$record);
433 context_course::instance($course->id);
7e7cfe7a
PS
434
435 return $course;
436 }
437
438 /**
439 * Create course section if does not exist yet
384c3510 440 * @param array|stdClass $record must contain 'course' and 'section' attributes
7e7cfe7a
PS
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;
447
448 $record = (array)$record;
449
450 if (empty($record['course'])) {
5c3c2c81 451 throw new coding_exception('course must be present in testing_data_generator::create_course_section() $record');
7e7cfe7a
PS
452 }
453
454 if (!isset($record['section'])) {
5c3c2c81 455 throw new coding_exception('section must be present in testing_data_generator::create_course_section() $record');
7e7cfe7a
PS
456 }
457
b46be6ad
MG
458 course_create_sections_if_missing($record['course'], $record['section']);
459 return get_fast_modinfo($record['course'])->get_section_info($record['section']);
7e7cfe7a
PS
460 }
461
462 /**
eb3884e4
TH
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.
7e7cfe7a 478 */
551088fc 479 public function create_block($blockname, $record=null, array $options=array()) {
7e7cfe7a
PS
480 $generator = $this->get_plugin_generator('block_'.$blockname);
481 return $generator->create_instance($record, $options);
482 }
483
484 /**
eb3884e4
TH
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.
7e7cfe7a
PS
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 }
507
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 *
6b219869
DM
515 * This function calls groups_create_group() to create the group within the database.
516 * @see groups_create_group
7e7cfe7a
PS
517 * @param array|stdClass $record
518 * @return stdClass group record
519 */
520 public function create_group($record) {
521 global $DB, $CFG;
522
523 require_once($CFG->dirroot . '/group/lib.php');
524
525 $this->groupcount++;
526 $i = $this->groupcount;
527
528 $record = (array)$record;
529
530 if (empty($record['courseid'])) {
5c3c2c81 531 throw new coding_exception('courseid must be present in testing_data_generator::create_group() $record');
7e7cfe7a
PS
532 }
533
534 if (!isset($record['name'])) {
535 $record['name'] = 'group-' . $i;
536 }
537
538 if (!isset($record['description'])) {
539 $record['description'] = "Test Group $i\n{$this->loremipsum}";
540 }
541
542 if (!isset($record['descriptionformat'])) {
543 $record['descriptionformat'] = FORMAT_MOODLE;
544 }
545
546 $id = groups_create_group((object)$record);
547
548 return $DB->get_record('groups', array('id'=>$id));
549 }
550
87bb583c
DM
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;
559
560 require_once($CFG->dirroot . '/group/lib.php');
561
562 $record = (array)$record;
563
564 if (empty($record['userid'])) {
565 throw new coding_exception('user must be present in testing_util::create_group_member() $record');
566 }
567
568 if (!isset($record['groupid'])) {
569 throw new coding_exception('group must be present in testing_util::create_group_member() $record');
570 }
571
572 if (!isset($record['component'])) {
573 $record['component'] = null;
574 }
575 if (!isset($record['itemid'])) {
576 $record['itemid'] = 0;
577 }
578
579 return groups_add_member($record['groupid'], $record['userid'], $record['component'], $record['itemid']);
580 }
581
7e7cfe7a
PS
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 *
6b219869
DM
589 * This function calls groups_create_grouping() to create the grouping within the database.
590 * @see groups_create_grouping
7e7cfe7a
PS
591 * @param array|stdClass $record
592 * @return stdClass grouping record
593 */
594 public function create_grouping($record) {
595 global $DB, $CFG;
596
597 require_once($CFG->dirroot . '/group/lib.php');
598
599 $this->groupingcount++;
600 $i = $this->groupingcount;
601
602 $record = (array)$record;
603
604 if (empty($record['courseid'])) {
5c3c2c81 605 throw new coding_exception('courseid must be present in testing_data_generator::create_grouping() $record');
7e7cfe7a
PS
606 }
607
608 if (!isset($record['name'])) {
609 $record['name'] = 'grouping-' . $i;
610 }
611
612 if (!isset($record['description'])) {
613 $record['description'] = "Test Grouping $i\n{$this->loremipsum}";
614 }
615
616 if (!isset($record['descriptionformat'])) {
617 $record['descriptionformat'] = FORMAT_MOODLE;
618 }
619
620 $id = groups_create_grouping((object)$record);
621
622 return $DB->get_record('groupings', array('id'=>$id));
623 }
624
87bb583c
DM
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;
633
634 require_once($CFG->dirroot . '/group/lib.php');
635
636 $record = (array)$record;
637
638 if (empty($record['groupingid'])) {
639 throw new coding_exception('grouping must be present in testing::create_grouping_group() $record');
640 }
641
642 if (!isset($record['groupid'])) {
643 throw new coding_exception('group must be present in testing_util::create_grouping_group() $record');
644 }
645
646 return groups_assign_grouping($record['groupingid'], $record['groupid']);
647 }
648
0852bbae
FM
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
5bcfd504 656 * @since Moodle 2.5.1
0852bbae
FM
657 */
658 public function create_repository($type, $record=null, array $options = null) {
0852bbae
FM
659 $generator = $this->get_plugin_generator('repository_'.$type);
660 return $generator->create_instance($record, $options);
661 }
662
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
5bcfd504 670 * @since Moodle 2.5.1
0852bbae
FM
671 */
672 public function create_repository_type($type, $record=null, array $options = null) {
0852bbae
FM
673 $generator = $this->get_plugin_generator('repository_'.$type);
674 return $generator->create_type($record, $options);
675 }
676
677
7e7cfe7a
PS
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;
686
687 $this->scalecount++;
688 $i = $this->scalecount;
689
690 $record = (array)$record;
691
692 if (!isset($record['name'])) {
693 $record['name'] = 'Test scale '.$i;
694 }
695
696 if (!isset($record['scale'])) {
697 $record['scale'] = 'A,B,C,D,F';
698 }
699
700 if (!isset($record['courseid'])) {
701 $record['courseid'] = 0;
702 }
703
704 if (!isset($record['userid'])) {
705 $record['userid'] = 0;
706 }
707
708 if (!isset($record['description'])) {
709 $record['description'] = 'Test scale description '.$i;
710 }
711
712 if (!isset($record['descriptionformat'])) {
713 $record['descriptionformat'] = FORMAT_MOODLE;
714 }
715
716 $record['timemodified'] = time();
717
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 }
725
726 return $DB->get_record('scale', array('id'=>$id), '*', MUST_EXIST);
727 }
4f5789ea 728
702851c0
DM
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;
746
747 $this->rolecount++;
748 $i = $this->rolecount;
749
750 $record = (array)$record;
751
752 if (empty($record['shortname'])) {
753 $record['shortname'] = 'role-' . $i;
754 }
755
756 if (empty($record['name'])) {
757 $record['name'] = 'Test role ' . $i;
758 }
759
760 if (empty($record['description'])) {
761 $record['description'] = 'Test role ' . $i . ' description';
762 }
763
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 }
773
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 }
778
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);
793
794 if ($record['archetype']) {
795
a63cd3e2 796 // We copy all the roles the archetype can assign, override, switch to and view.
702851c0 797 if ($record['archetype']) {
a63cd3e2 798 $types = array('assign', 'override', 'switch', 'view');
702851c0
DM
799 foreach ($types as $type) {
800 $rolestocopy = get_default_role_archetype_allows($type, $record['archetype']);
801 foreach ($rolestocopy as $tocopy) {
64cd4596 802 $functionname = "core_role_set_{$type}_allowed";
702851c0
DM
803 $functionname($newroleid, $tocopy);
804 }
805 }
806 }
807
808 // Copying the archetype capabilities.
809 $sourcerole = $DB->get_record('role', array('id' => $archetyperoleid));
810 role_cap_duplicate($sourcerole, $newroleid);
811 }
812
813 return $newroleid;
814 }
815
e0c86198
MN
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;
824
825 $this->tagcount++;
826 $i = $this->tagcount;
827
828 $record = (array) $record;
829
830 if (!isset($record['userid'])) {
831 $record['userid'] = $USER->id;
832 }
833
9a7e3986
MG
834 if (!isset($record['rawname'])) {
835 if (isset($record['name'])) {
836 $record['rawname'] = $record['name'];
837 } else {
838 $record['rawname'] = 'Tag name ' . $i;
839 }
e0c86198
MN
840 }
841
9a7e3986
MG
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']);
e0c86198
MN
847 }
848
c026a28d
MG
849 if (!isset($record['tagcollid'])) {
850 $record['tagcollid'] = core_tag_collection::get_default();
851 }
852
e0c86198
MN
853 if (!isset($record['description'])) {
854 $record['description'] = 'Tag description';
855 }
856
857 if (!isset($record['descriptionformat'])) {
858 $record['descriptionformat'] = FORMAT_MOODLE;
859 }
860
861 if (!isset($record['flag'])) {
862 $record['flag'] = 0;
863 }
864
865 if (!isset($record['timemodified'])) {
866 $record['timemodified'] = time();
867 }
868
869 $id = $DB->insert_record('tag', $record);
870
871 return $DB->get_record('tag', array('id' => $id), '*', MUST_EXIST);
872 }
873
ba203de1
TH
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;
885
886 foreach ($defaults as $key => $defaults) {
887 if (!array_key_exists($key, $record)) {
888 $record[$key] = $defaults;
889 }
890 }
891 return $record;
892 }
893
4f5789ea
PS
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
92cce140 901 * @param int|string $roleidorshortname optional role id or role shortname, use only with manual plugin
4f5789ea
PS
902 * @param string $enrol name of enrol plugin,
903 * there must be exactly one instance in course,
904 * it must support enrol_user() method.
1ecb8044
RT
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
4f5789ea
PS
908 * @return bool success
909 */
92cce140 910 public function enrol_user($userid, $courseid, $roleidorshortname = null, $enrol = 'manual',
911 $timestart = 0, $timeend = 0, $status = null) {
4f5789ea
PS
912 global $DB;
913
92cce140 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 }
920
4f5789ea
PS
921 if (!$plugin = enrol_get_plugin($enrol)) {
922 return false;
923 }
924
925 $instances = $DB->get_records('enrol', array('courseid'=>$courseid, 'enrol'=>$enrol));
926 if (count($instances) != 1) {
927 return false;
928 }
929 $instance = reset($instances);
930
931 if (is_null($roleid) and $instance->roleid) {
932 $roleid = $instance->roleid;
933 }
934
e6cc5347 935 $plugin->enrol_user($instance, $userid, $roleid, $timestart, $timeend, $status);
4f5789ea
PS
936 return true;
937 }
72ddc05f
DM
938
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) {
948
949 // Default to the system context.
950 if (!$contextid) {
951 $context = context_system::instance();
952 $contextid = $context->id;
953 }
954
955 if (empty($roleid)) {
956 throw new coding_exception('roleid must be present in testing_data_generator::role_assign() arguments');
957 }
958
959 if (empty($userid)) {
960 throw new coding_exception('userid must be present in testing_data_generator::role_assign() arguments');
961 }
962
963 return role_assign($roleid, $userid, $contextid);
964 }
965
15ace204
DW
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;
974
975 $this->gradecategorycounter++;
15ace204 976
f4f2045a
MG
977 $record = (array)$record;
978
979 if (empty($record['courseid'])) {
980 throw new coding_exception('courseid must be present in testing::create_grade_category() $record');
981 }
982
15ace204 983 if (!isset($record['fullname'])) {
cfa91962 984 $record['fullname'] = 'Grade category ' . $this->gradecategorycounter;
15ace204
DW
985 }
986
987 // For gradelib classes.
988 require_once($CFG->libdir . '/gradelib.php');
989 // Create new grading category in this course.
f4f2045a 990 $gradecategory = new grade_category(array('courseid' => $record['courseid']), false);
15ace204 991 $gradecategory->apply_default_settings();
f4f2045a 992 grade_category::set_properties($gradecategory, $record);
15ace204
DW
993 $gradecategory->apply_forced_settings();
994 $gradecategory->insert();
f4f2045a 995
15ace204
DW
996 // This creates a default grade item for the category
997 $gradeitem = $gradecategory->load_grade_item();
998
15ace204
DW
999 $gradecategory->update_from_db();
1000 return $gradecategory->get_record_data();
1001 }
cfa91962
JO
1002
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");
1012
1013 $this->gradeitemcounter++;
1014
1015 if (!isset($record['itemtype'])) {
1016 $record['itemtype'] = 'manual';
1017 }
1018
1019 if (!isset($record['itemname'])) {
1020 $record['itemname'] = 'Grade item ' . $this->gradeitemcounter;
1021 }
1022
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 }
1032
1033 // Create new grade item in this course.
1034 $gradeitem = new grade_item($record, false);
1035 $gradeitem->insert();
1036
1037 $gradeitem->update_from_db();
1038 return $gradeitem->get_record_data();
1039 }
1040
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;
1049
1050 $this->gradeoutcomecounter++;
1051 $i = $this->gradeoutcomecounter;
1052
1053 if (!isset($record['fullname'])) {
1054 $record['fullname'] = 'Grade outcome ' . $i;
1055 }
1056
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();
1062
1063 $gradeoutcome->update_from_db();
1064 return $gradeoutcome->get_record_data();
1065 }
c314d6ed
JP
1066
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;
1075
1076 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1077 $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1078
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 }
1086
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 }
1092
1093 // Set it to enabled if no status was specified.
1094 if (!isset($data->status)) {
1095 $data->status = ENROL_INSTANCE_ENABLED;
1096 }
1097
1098 // Add some extra necessary fields to the data.
1099 $data->name = 'Test LTI';
1100 $data->roleinstructor = $studentrole->id;
1101 $data->rolelearner = $teacherrole->id;
1102
1103 // Get the enrol LTI plugin.
1104 $enrolplugin = enrol_get_plugin('lti');
1105 $instanceid = $enrolplugin->add_instance($course, (array) $data);
1106
1107 // Get the tool associated with this instance.
1108 return $DB->get_record('enrol_lti_tools', array('enrolid' => $instanceid));
1109 }
fb3c0fc3
AN
1110
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;
1119
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;
44ae0838 1131 $record->categoryid = 0;
fb3c0fc3
AN
1132
1133 foreach ($data as $key => $value) {
1134 $record->$key = $value;
1135 }
1136
1137 switch ($record->eventtype) {
1138 case 'user':
1139 unset($record->categoryid);
1140 unset($record->courseid);
1141 unset($record->groupid);
1142 break;
1143 case 'group':
1144 unset($record->categoryid);
1145 break;
1146 case 'course':
1147 unset($record->categoryid);
1148 unset($record->groupid);
1149 break;
1150 case 'category':
1151 unset($record->courseid);
1152 unset($record->groupid);
1153 break;
1154 case 'global':
1155 unset($record->categoryid);
1156 unset($record->courseid);
1157 unset($record->groupid);
1158 break;
1159 }
1160
1161 $event = new calendar_event($record);
1162 $event->create($record);
1163
1164 return $event->properties();
1165 }
e984917d
AN
1166
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;
1178
1179 $user = $this->create_user($userparams);
1180 $roleid = $DB->get_field('role', 'id', ['shortname' => $role ]);
1181
1182 $this->enrol_user($user->id, $course->id, $roleid, $enrol, $timestart, $timeend, $status);
1183
1184 return $user;
1185 }
7e7cfe7a 1186}