Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | 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
     */
1441 ariadna 146
    public function create_user($record=null, ?array $options=null) {
1 efrain 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
     */
1441 ariadna 280
    public function create_category($record=null, ?array $options=null) {
1 efrain 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
     */
1441 ariadna 307
    public function create_cohort($record=null, ?array $options=null) {
1 efrain 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
     */
1441 ariadna 357
    public function create_course($record=null, ?array $options=null) {
1 efrain 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
     */
1441 ariadna 464
    public function create_course_section($record = null, ?array $options = null) {
1 efrain 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
     */
1441 ariadna 522
    public function create_module($modulename, $record=null, ?array $options=null) {
1 efrain 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
     */
1441 ariadna 697
    public function create_repository($type, $record=null, ?array $options = null) {
1 efrain 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
     */
1441 ariadna 711
    public function create_repository_type($type, $record=null, ?array $options = null) {
1 efrain 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
     */
1441 ariadna 723
    public function create_scale($record=null, ?array $options=null) {
1 efrain 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
     */
1441 ariadna 899
    public function create_role_capability(int $roleid, array $rolecapabilities, ?context $context = null): void {
1 efrain 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
 
1441 ariadna 1004
        foreach ($defaults as $key => $default) {
1 efrain 1005
            if (!array_key_exists($key, $record)) {
1441 ariadna 1006
                $record[$key] = $default;
1007
            } else if (is_array($record[$key]) && is_array($default)) {
1008
                $record[$key] = $this->combine_defaults_and_record($default, $record[$key]);
1 efrain 1009
            }
1010
        }
1011
        return $record;
1012
    }
1013
 
1014
    /**
1015
     * Simplified enrolment of user to course using default options.
1016
     *
1017
     * It is strongly recommended to use only this method for 'manual' and 'self' plugins only!!!
1018
     *
1019
     * @param int $userid
1020
     * @param int $courseid
1021
     * @param int|string $roleidorshortname optional role id or role shortname, use only with manual plugin
1022
     * @param string $enrol name of enrol plugin,
1023
     *     there must be exactly one instance in course,
1024
     *     it must support enrol_user() method.
1025
     * @param int $timestart (optional) 0 means unknown
1026
     * @param int $timeend (optional) 0 means forever
1027
     * @param int $status (optional) default to ENROL_USER_ACTIVE for new enrolments
1028
     * @return bool success
1029
     */
1030
    public function enrol_user($userid, $courseid, $roleidorshortname = null, $enrol = 'manual',
1031
            $timestart = 0, $timeend = 0, $status = null) {
1032
        global $DB;
1033
 
1034
        // If role is specified by shortname, convert it into an id.
1035
        if (!is_numeric($roleidorshortname) && is_string($roleidorshortname)) {
1036
            $roleid = $DB->get_field('role', 'id', array('shortname' => $roleidorshortname), MUST_EXIST);
1037
        } else {
1038
            $roleid = $roleidorshortname;
1039
        }
1040
 
1041
        if (!$plugin = enrol_get_plugin($enrol)) {
1042
            return false;
1043
        }
1044
 
1045
        $instances = $DB->get_records('enrol', array('courseid'=>$courseid, 'enrol'=>$enrol));
1046
        if (count($instances) != 1) {
1047
            return false;
1048
        }
1049
        $instance = reset($instances);
1050
 
1051
        if (is_null($roleid) and $instance->roleid) {
1052
            $roleid = $instance->roleid;
1053
        }
1054
 
1055
        $plugin->enrol_user($instance, $userid, $roleid, $timestart, $timeend, $status);
1056
        return true;
1057
    }
1058
 
1059
    /**
1060
     * Assigns the specified role to a user in the context.
1061
     *
1062
     * @param int|string $role either an int role id or a string role shortname.
1063
     * @param int $userid
1064
     * @param int|context $contextid Defaults to the system context
1065
     * @return int new/existing id of the assignment
1066
     */
1067
    public function role_assign($role, $userid, $contextid = false) {
1068
        global $DB;
1069
 
1070
        // Default to the system context.
1071
        if (!$contextid) {
1072
            $context = context_system::instance();
1073
            $contextid = $context->id;
1074
        }
1075
 
1076
        if (empty($role)) {
1077
            throw new coding_exception('roleid must be present in testing_data_generator::role_assign() arguments');
1078
        }
1079
        if (!is_number($role)) {
1080
            $role = $DB->get_field('role', 'id', ['shortname' => $role], MUST_EXIST);
1081
        }
1082
 
1083
        if (empty($userid)) {
1084
            throw new coding_exception('userid must be present in testing_data_generator::role_assign() arguments');
1085
        }
1086
 
1087
        return role_assign($role, $userid, $contextid);
1088
    }
1089
 
1090
    /**
1091
     * Create a grade_category.
1092
     *
1093
     * @param array|stdClass $record
1094
     * @return stdClass the grade category record
1095
     */
1096
    public function create_grade_category($record = null) {
1097
        global $CFG;
1098
 
1099
        $this->gradecategorycounter++;
1100
 
1101
        $record = (array)$record;
1102
 
1103
        if (empty($record['courseid'])) {
1104
            throw new coding_exception('courseid must be present in testing::create_grade_category() $record');
1105
        }
1106
 
1107
        if (!isset($record['fullname'])) {
1108
            $record['fullname'] = 'Grade category ' . $this->gradecategorycounter;
1109
        }
1110
 
1111
        // For gradelib classes.
1112
        require_once($CFG->libdir . '/gradelib.php');
1113
        // Create new grading category in this course.
1114
        $gradecategory = new grade_category(array('courseid' => $record['courseid']), false);
1115
        $gradecategory->apply_default_settings();
1116
        grade_category::set_properties($gradecategory, $record);
1117
        $gradecategory->apply_forced_settings();
1118
        $gradecategory->insert();
1119
 
1120
        // This creates a default grade item for the category
1121
        $gradeitem = $gradecategory->load_grade_item();
1122
 
1123
        $gradecategory->update_from_db();
1124
        return $gradecategory->get_record_data();
1125
    }
1126
 
1127
    /**
1128
     * Create a grade_grade.
1129
     *
1130
     * @param array $record
1131
     * @return grade_grade the grade record
1132
     */
1133
    public function create_grade_grade(?array $record = null): grade_grade {
1134
        global $DB, $USER;
1135
 
1136
        $item = $DB->get_record('grade_items', ['id' => $record['itemid']]);
1137
        $userid = $record['userid'] ?? $USER->id;
1138
 
1139
        unset($record['itemid']);
1140
        unset($record['userid']);
1141
 
1142
        if ($item->itemtype === 'mod') {
1143
            $cm = get_coursemodule_from_instance($item->itemmodule, $item->iteminstance);
1144
            $module = new $item->itemmodule(context_module::instance($cm->id), $cm, false);
1145
            $record['attemptnumber'] = $record['attemptnumber'] ?? 0;
1146
 
1147
            $module->save_grade($userid, (object) $record);
1148
 
1149
            $grade = grade_grade::fetch(['userid' => $userid, 'itemid' => $item->id]);
1150
        } else {
1151
            $grade = grade_grade::fetch(['userid' => $userid, 'itemid' => $item->id]);
1152
            $record['rawgrade'] = $record['rawgrade'] ?? $record['grade'] ?? null;
1153
            $record['finalgrade'] = $record['finalgrade'] ?? $record['grade'] ?? null;
1154
 
1155
            unset($record['grade']);
1156
 
1157
            if ($grade) {
1158
                $fields = $grade->required_fields + array_keys($grade->optional_fields);
1159
 
1160
                foreach ($fields as $field) {
1161
                    $grade->{$field} = $record[$field] ?? $grade->{$field};
1162
                }
1163
 
1164
                $grade->update();
1165
            } else {
1166
                $record['userid'] = $userid;
1167
                $record['itemid'] = $item->id;
1168
 
1169
                $grade = new grade_grade($record, false);
1170
 
1171
                $grade->insert();
1172
            }
1173
        }
1174
 
1175
        return $grade;
1176
    }
1177
 
1178
    /**
1179
     * Create a grade_item.
1180
     *
1181
     * @param array|stdClass $record
1182
     * @return stdClass the grade item record
1183
     */
1184
    public function create_grade_item($record = null) {
1185
        global $CFG;
1186
        require_once("$CFG->libdir/gradelib.php");
1187
 
1188
        $this->gradeitemcounter++;
1189
 
1190
        if (!isset($record['itemtype'])) {
1191
            $record['itemtype'] = 'manual';
1192
        }
1193
 
1194
        if (!isset($record['itemname'])) {
1195
            $record['itemname'] = 'Grade item ' . $this->gradeitemcounter;
1196
        }
1197
 
1198
        if (isset($record['outcomeid'])) {
1199
            $outcome = new grade_outcome(array('id' => $record['outcomeid']));
1200
            $record['scaleid'] = $outcome->scaleid;
1201
        }
1202
        if (isset($record['scaleid'])) {
1203
            $record['gradetype'] = GRADE_TYPE_SCALE;
1204
        } else if (!isset($record['gradetype'])) {
1205
            $record['gradetype'] = GRADE_TYPE_VALUE;
1206
        }
1207
 
1208
        // Create new grade item in this course.
1209
        $gradeitem = new grade_item($record, false);
1210
        $gradeitem->insert();
1211
 
1212
        $gradeitem->update_from_db();
1213
        return $gradeitem->get_record_data();
1214
    }
1215
 
1216
    /**
1217
     * Create a grade_outcome.
1218
     *
1219
     * @param array|stdClass $record
1220
     * @return stdClass the grade outcome record
1221
     */
1222
    public function create_grade_outcome($record = null) {
1223
        global $CFG;
1224
 
1225
        $this->gradeoutcomecounter++;
1226
        $i = $this->gradeoutcomecounter;
1227
 
1228
        if (!isset($record['fullname'])) {
1229
            $record['fullname'] = 'Grade outcome ' . $i;
1230
        }
1231
 
1232
        // For gradelib classes.
1233
        require_once($CFG->libdir . '/gradelib.php');
1234
        // Create new grading outcome in this course.
1235
        $gradeoutcome = new grade_outcome($record, false);
1236
        $gradeoutcome->insert();
1237
 
1238
        $gradeoutcome->update_from_db();
1239
        return $gradeoutcome->get_record_data();
1240
    }
1241
 
1242
    /**
1243
     * Helper function used to create an LTI tool.
1244
     *
1245
     * @param stdClass $data
1246
     * @return stdClass the tool
1247
     */
1248
    public function create_lti_tool($data = array()) {
1249
        global $DB;
1250
 
1251
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1252
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1253
 
1254
        // Create a course if no course id was specified.
1255
        if (empty($data->courseid)) {
1256
            $course = $this->create_course();
1257
            $data->courseid = $course->id;
1258
        } else {
1259
            $course = get_course($data->courseid);
1260
        }
1261
 
1262
        if (!empty($data->cmid)) {
1263
            $data->contextid = context_module::instance($data->cmid)->id;
1264
        } else {
1265
            $data->contextid = context_course::instance($data->courseid)->id;
1266
        }
1267
 
1268
        // Set it to enabled if no status was specified.
1269
        if (!isset($data->status)) {
1270
            $data->status = ENROL_INSTANCE_ENABLED;
1271
        }
1272
 
1273
        // Default to legacy lti version.
1274
        if (empty($data->ltiversion) || !in_array($data->ltiversion, ['LTI-1p0/LTI-2p0', 'LTI-1p3'])) {
1275
            $data->ltiversion = 'LTI-1p0/LTI-2p0';
1276
        }
1277
 
1278
        // Add some extra necessary fields to the data.
1279
        $data->name = $data->name ?? 'Test LTI';
1280
        $data->roleinstructor = $teacherrole->id;
1281
        $data->rolelearner = $studentrole->id;
1282
 
1283
        // Get the enrol LTI plugin.
1284
        $enrolplugin = enrol_get_plugin('lti');
1285
        $instanceid = $enrolplugin->add_instance($course, (array) $data);
1286
 
1287
        // Get the tool associated with this instance.
1288
        return $DB->get_record('enrol_lti_tools', array('enrolid' => $instanceid));
1289
    }
1290
 
1291
    /**
1292
     * Helper function used to create an event.
1293
     *
1294
     * @param   array   $data
1295
     * @return  stdClass
1296
     */
1297
    public function create_event($data = []) {
1298
        global $CFG;
1299
 
1300
        require_once($CFG->dirroot . '/calendar/lib.php');
1301
        $record = new \stdClass();
1302
        $record->name = 'event name';
1303
        $record->repeat = 0;
1304
        $record->repeats = 0;
1305
        $record->timestart = time();
1306
        $record->timeduration = 0;
1307
        $record->timesort = 0;
1308
        $record->eventtype = 'user';
1309
        $record->courseid = 0;
1310
        $record->categoryid = 0;
1311
 
1312
        foreach ($data as $key => $value) {
1313
            $record->$key = $value;
1314
        }
1315
 
1316
        switch ($record->eventtype) {
1317
            case 'user':
1318
                unset($record->categoryid);
1319
                unset($record->courseid);
1320
                unset($record->groupid);
1321
                break;
1322
            case 'group':
1323
                unset($record->categoryid);
1324
                break;
1325
            case 'course':
1326
                unset($record->categoryid);
1327
                unset($record->groupid);
1328
                break;
1329
            case 'category':
1330
                unset($record->courseid);
1331
                unset($record->groupid);
1332
                break;
1333
            case 'site':
1334
                unset($record->categoryid);
1335
                unset($record->courseid);
1336
                unset($record->groupid);
1337
                break;
1338
        }
1339
 
1340
        $event = new calendar_event($record);
1341
        $event->create($record);
1342
 
1343
        return $event->properties();
1344
    }
1345
 
1346
    /**
1347
     * Create a new course custom field category with the given name.
1348
     *
1349
     * @param   array $data Array with data['name'] of category
1350
     * @return  \core_customfield\category_controller   The created category
1351
     */
1352
    public function create_custom_field_category($data): \core_customfield\category_controller {
1353
        return $this->get_plugin_generator('core_customfield')->create_category($data);
1354
    }
1355
 
1356
    /**
1357
     * Create a new custom field
1358
     *
1359
     * @param   array $data Array with 'name', 'shortname' and 'type' of the field
1360
     * @return  \core_customfield\field_controller   The created field
1361
     */
1362
    public function create_custom_field($data): \core_customfield\field_controller {
1363
        global $DB;
1364
        if (empty($data['categoryid']) && !empty($data['category'])) {
1365
            $data['categoryid'] = $DB->get_field('customfield_category', 'id', ['name' => $data['category']]);
1366
            unset($data['category']);
1367
        }
1368
        return $this->get_plugin_generator('core_customfield')->create_field($data);
1369
    }
1370
 
1371
    /**
1372
     * Create a new category for custom profile fields.
1373
     *
1374
     * @param array $data Array with 'name' and optionally 'sortorder'
1375
     * @return \stdClass New category object
1376
     */
1377
    public function create_custom_profile_field_category(array $data): \stdClass {
1378
        global $DB;
1379
 
1380
        // Pick next sortorder if not defined.
1381
        if (!array_key_exists('sortorder', $data)) {
1382
            $data['sortorder'] = (int)$DB->get_field_sql('SELECT MAX(sortorder) FROM {user_info_category}') + 1;
1383
        }
1384
 
1385
        $category = (object)[
1386
            'name' => $data['name'],
1387
            'sortorder' => $data['sortorder']
1388
        ];
1389
        $category->id = $DB->insert_record('user_info_category', $category);
1390
 
1391
        return $category;
1392
    }
1393
 
1394
    /**
1395
     * Creates a new custom profile field.
1396
     *
1397
     * Optional fields are:
1398
     *
1399
     * categoryid (or use 'category' to specify by name). If you don't specify
1400
     * either, it will add the field to a 'Testing' category, which will be created for you if
1401
     * necessary.
1402
     *
1403
     * sortorder (if you don't specify this, it will pick the next one in the category).
1404
     *
1405
     * all the other database fields (if you don't specify this, it will pick sensible defaults
1406
     * based on the data type).
1407
     *
1408
     * @param array $data Array with 'datatype', 'shortname', and 'name'
1409
     * @return \stdClass Database object from the user_info_field table
1410
     */
1411
    public function create_custom_profile_field(array $data): \stdClass {
1412
        global $DB, $CFG;
1413
        require_once($CFG->dirroot . '/user/profile/lib.php');
1414
 
1415
        // Set up category if necessary.
1416
        if (!array_key_exists('categoryid', $data)) {
1417
            if (array_key_exists('category', $data)) {
1418
                $data['categoryid'] = $DB->get_field('user_info_category', 'id',
1419
                        ['name' => $data['category']], MUST_EXIST);
1420
            } else {
1421
                // Make up a 'Testing' category or use existing.
1422
                $data['categoryid'] = $DB->get_field('user_info_category', 'id', ['name' => 'Testing']);
1423
                if (!$data['categoryid']) {
1424
                    $created = $this->create_custom_profile_field_category(['name' => 'Testing']);
1425
                    $data['categoryid'] = $created->id;
1426
                }
1427
            }
1428
        }
1429
 
1430
        // Pick sort order if necessary.
1431
        if (!array_key_exists('sortorder', $data)) {
1432
            $data['sortorder'] = (int)$DB->get_field_sql(
1433
                    'SELECT MAX(sortorder) FROM {user_info_field} WHERE categoryid = ?',
1434
                    [$data['categoryid']]) + 1;
1435
        }
1436
 
1437
        if ($data['datatype'] === 'menu' && isset($data['param1'])) {
1438
            // Convert new lines to the proper character.
1439
            $data['param1'] = str_replace('\n', "\n", $data['param1']);
1440
        }
1441
 
1442
        // Defaults for other values.
1443
        $defaults = [
1444
            'description' => '',
1445
            'descriptionformat' => 0,
1446
            'required' => 0,
1447
            'locked' => 0,
1448
            'visible' => PROFILE_VISIBLE_ALL,
1449
            'forceunique' => 0,
1450
            'signup' => 0,
1451
            'defaultdata' => '',
1452
            'defaultdataformat' => 0,
1453
            'param1' => '',
1454
            'param2' => '',
1455
            'param3' => '',
1456
            'param4' => '',
1457
            'param5' => ''
1458
        ];
1459
 
1460
        // Type-specific defaults for other values.
1461
        $typedefaults = [
1462
            'text' => [
1463
                'param1' => 30,
1464
                'param2' => 2048
1465
            ],
1466
            'menu' => [
1467
                'param1' => "Yes\nNo",
1468
                'defaultdata' => 'No'
1469
            ],
1470
            'datetime' => [
1471
                'param1' => '2010',
1472
                'param2' => '2015',
1473
                'param3' => 1
1474
            ],
1475
            'checkbox' => [
1476
                'defaultdata' => 0
1477
            ]
1478
        ];
1479
        foreach ($typedefaults[$data['datatype']] ?? [] as $field => $value) {
1480
            $defaults[$field] = $value;
1481
        }
1482
 
1483
        foreach ($defaults as $field => $value) {
1484
            if (!array_key_exists($field, $data)) {
1485
                $data[$field] = $value;
1486
            }
1487
        }
1488
 
1489
        $data['id'] = $DB->insert_record('user_info_field', $data);
1490
        return (object)$data;
1491
    }
1492
 
1493
    /**
1494
     * Create a new user, and enrol them in the specified course as the supplied role.
1495
     *
1496
     * @param   \stdClass   $course The course to enrol in
1497
     * @param   string      $role The role to give within the course
1498
     * @param   \stdClass|array   $userparams User parameters
1499
     * @return  \stdClass   The created user
1500
     */
1501
    public function create_and_enrol($course, $role = 'student', $userparams = null, $enrol = 'manual',
1502
            $timestart = 0, $timeend = 0, $status = null) {
1503
        global $DB;
1504
 
1505
        $user = $this->create_user($userparams);
1506
        $roleid = $DB->get_field('role', 'id', ['shortname' => $role ]);
1507
 
1508
        $this->enrol_user($user->id, $course->id, $roleid, $enrol, $timestart, $timeend, $status);
1509
 
1510
        return $user;
1511
    }
1512
 
1513
    /**
1514
     * Create a new last access record for a given user in a course.
1515
     *
1516
     * @param   \stdClass   $user The user
1517
     * @param   \stdClass   $course The course the user accessed
1518
     * @param   int         $timestamp The timestamp for when the user last accessed the course
1519
     * @return  \stdClass   The user_lastaccess record
1520
     */
1521
    public function create_user_course_lastaccess(\stdClass $user, \stdClass $course, int $timestamp): \stdClass {
1522
        global $DB;
1523
 
1524
        $record = [
1525
            'userid' => $user->id,
1526
            'courseid' => $course->id,
1527
            'timeaccess' => $timestamp,
1528
        ];
1529
 
1530
        $recordid = $DB->insert_record('user_lastaccess', $record);
1531
 
1532
        return $DB->get_record('user_lastaccess', ['id' => $recordid], '*', MUST_EXIST);
1533
    }
1534
 
1535
    /**
1441 ariadna 1536
     * Generate a stored_progress record and return the ID.
1537
     *
1538
     * All fields are optional, required fields will be generated if not supplied.
1539
     *
1540
     * @param ?string $idnumber The unique ID Number for this stored progress.
1541
     * @param ?int $timestart The time progress was started, defaults to now.
1542
     * @param ?int $lastupdate The time the progress was last updated.
1543
     * @param float $percent The percentage progress so far.
1544
     * @param ?string $message An error message.
1545
     * @param ?bool $haserrored Whether the process has encountered an error.
1546
     * @return stdClass The record including the inserted id.
1547
     * @throws dml_exception
1548
     */
1549
    public function create_stored_progress(
1550
        ?string $idnumber = null,
1551
        ?int $timestart = null,
1552
        ?int $lastupdate = null,
1553
        float $percent = 0.00,
1554
        ?string $message = null,
1555
        ?bool $haserrored = false,
1556
    ): stdClass {
1557
        global $DB;
1558
        $record = (object)[
1559
            'idnumber' => $idnumber ?? random_string(),
1560
            'timestart' => $timestart ?? time(),
1561
            'lastupdate' => $lastupdate,
1562
            'percentcompleted' => $percent,
1563
            'message' => $message,
1564
            'haserrored' => $haserrored,
1565
        ];
1566
        $record->id = $DB->insert_record('stored_progress', $record);
1567
        return $record;
1568
    }
1569
 
1570
    /**
1571
     * Generate a stored progress record from an array of fields.
1572
     *
1573
     * For use as a behat createable entity. Use {@see self::create_stored_progress()} if calling directly.
1574
     *
1575
     * @param array $data
1576
     * @return void
1577
     */
1578
    public function create_stored_progress_bar(array $data): void {
1579
        $this->create_stored_progress(
1580
            $data['idnumber'] ?? null,
1581
            $data['timestart'] ?? null,
1582
            $data['lastupdate'] ?? null,
1583
            $data['percent'] ?? 0.00,
1584
            $data['message'] ?? null,
1585
            $data['haserrored'] ?? false,
1586
        );
1587
    }
1588
 
1589
    /**
1 efrain 1590
     * Gets a default generator for a given component.
1591
     *
1592
     * @param string $component The component name, e.g. 'mod_forum' or 'core_question'.
1593
     * @param string $classname The name of the class missing from the generators file.
1594
     * @return component_generator_base The generator.
1595
     */
1596
    protected function get_default_plugin_generator(string $component, ?string $classname = null) {
1597
        [$type, $plugin] = core_component::normalize_component($component);
1598
 
1599
        switch ($type) {
1600
            case 'block':
1601
                return new default_block_generator($this, $plugin);
1602
        }
1603
 
1604
        if (is_null($classname)) {
1605
            throw new coding_exception("Component {$component} does not support " .
1606
                "generators yet. Missing tests/generator/lib.php.");
1607
        }
1608
 
1609
        throw new coding_exception("Component {$component} does not support " .
1610
            "data generators yet. Class {$classname} not found.");
1611
    }
1612
 
1613
}