Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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