Merge branch 'MDL-68667-master' of git://github.com/aanabit/moodle
[moodle.git] / lib / behat / classes / behat_core_generator.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Data generators for acceptance testing.
19  *
20  * @package   core
21  * @category  test
22  * @copyright 2012 David MonllaĆ³
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
28 defined('MOODLE_INTERNAL') || die();
31 /**
32  * Behat data generator class for core entities.
33  *
34  * @package   core
35  * @category  test
36  * @copyright 2012 David MonllaĆ³
37  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38  */
39 class behat_core_generator extends behat_generator_base {
41     protected function get_creatable_entities(): array {
42         return [
43             'users' => [
44                 'datagenerator' => 'user',
45                 'required' => ['username'],
46             ],
47             'categories' => [
48                 'datagenerator' => 'category',
49                 'required' => ['idnumber'],
50                 'switchids' => ['category' => 'parent'],
51             ],
52             'courses' => [
53                 'datagenerator' => 'course',
54                 'required' => ['shortname'],
55                 'switchids' => ['category' => 'category'],
56             ],
57             'groups' => [
58                 'datagenerator' => 'group',
59                 'required' => ['idnumber', 'course'],
60                 'switchids' => ['course' => 'courseid'],
61             ],
62             'groupings' => [
63                 'datagenerator' => 'grouping',
64                 'required' => ['idnumber', 'course'],
65                 'switchids' => ['course' => 'courseid'],
66             ],
67             'course enrolments' => [
68                 'datagenerator' => 'enrol_user',
69                 'required' => ['user', 'course', 'role'],
70                 'switchids' => ['user' => 'userid', 'course' => 'courseid', 'role' => 'roleid'],
71             ],
72             'custom field categories' => [
73                 'datagenerator' => 'custom_field_category',
74                 'required' => ['name', 'component', 'area', 'itemid'],
75                 'switchids' => [],
76             ],
77             'custom fields' => [
78                 'datagenerator' => 'custom_field',
79                 'required' => ['name', 'category', 'type', 'shortname'],
80                 'switchids' => [],
81             ],
82             'permission overrides' => [
83                 'datagenerator' => 'permission_override',
84                 'required' => ['capability', 'permission', 'role', 'contextlevel', 'reference'],
85                 'switchids' => ['role' => 'roleid'],
86             ],
87             'system role assigns' => [
88                 'datagenerator' => 'system_role_assign',
89                 'required' => ['user', 'role'],
90                 'switchids' => ['user' => 'userid', 'role' => 'roleid'],
91             ],
92             'role assigns' => [
93                 'datagenerator' => 'role_assign',
94                 'required' => ['user', 'role', 'contextlevel', 'reference'],
95                 'switchids' => ['user' => 'userid', 'role' => 'roleid'],
96             ],
97             'activities' => [
98                 'datagenerator' => 'activity',
99                 'required' => ['activity', 'idnumber', 'course'],
100                 'switchids' => ['course' => 'course', 'gradecategory' => 'gradecat', 'grouping' => 'groupingid'],
101             ],
102             'blocks' => [
103                 'datagenerator' => 'block_instance',
104                 'required' => ['blockname', 'contextlevel', 'reference'],
105             ],
106             'group members' => [
107                 'datagenerator' => 'group_member',
108                 'required' => ['user', 'group'],
109                 'switchids' => ['user' => 'userid', 'group' => 'groupid'],
110             ],
111             'grouping groups' => [
112                 'datagenerator' => 'grouping_group',
113                 'required' => ['grouping', 'group'],
114                 'switchids' => ['grouping' => 'groupingid', 'group' => 'groupid'],
115             ],
116             'cohorts' => [
117                 'datagenerator' => 'cohort',
118                 'required' => ['idnumber'],
119             ],
120             'cohort members' => [
121                 'datagenerator' => 'cohort_member',
122                 'required' => ['user', 'cohort'],
123                 'switchids' => ['user' => 'userid', 'cohort' => 'cohortid'],
124             ],
125             'roles' => [
126                 'datagenerator' => 'role',
127                 'required' => ['shortname'],
128             ],
129             'grade categories' => [
130                 'datagenerator' => 'grade_category',
131                 'required' => ['fullname', 'course'],
132                 'switchids' => ['course' => 'courseid', 'gradecategory' => 'parent'],
133             ],
134             'grade items' => [
135                 'datagenerator' => 'grade_item',
136                 'required' => ['course'],
137                 'switchids' => [
138                     'scale' => 'scaleid',
139                     'outcome' => 'outcomeid',
140                     'course' => 'courseid',
141                     'gradecategory' => 'categoryid',
142                 ],
143             ],
144             'grade outcomes' => [
145                 'datagenerator' => 'grade_outcome',
146                 'required' => ['shortname', 'scale'],
147                 'switchids' => ['course' => 'courseid', 'gradecategory' => 'categoryid', 'scale' => 'scaleid'],
148             ],
149             'scales' => [
150                 'datagenerator' => 'scale',
151                 'required' => ['name', 'scale'],
152                 'switchids' => ['course' => 'courseid'],
153             ],
154             'question categories' => [
155                 'datagenerator' => 'question_category',
156                 'required' => ['name', 'contextlevel', 'reference'],
157                 'switchids' => ['questioncategory' => 'parent'],
158             ],
159             'questions' => [
160                 'datagenerator' => 'question',
161                 'required' => ['qtype', 'questioncategory', 'name'],
162                 'switchids' => ['questioncategory' => 'category', 'user' => 'createdby'],
163             ],
164             'tags' => [
165                 'datagenerator' => 'tag',
166                 'required' => ['name'],
167             ],
168             'events' => [
169                 'datagenerator' => 'event',
170                 'required' => ['name', 'eventtype'],
171                 'switchids' => [
172                     'user' => 'userid',
173                     'course' => 'courseid',
174                     'category' => 'categoryid',
175                 ],
176             ],
177             'message contacts' => [
178                 'datagenerator' => 'message_contacts',
179                 'required' => ['user', 'contact'],
180                 'switchids' => ['user' => 'userid', 'contact' => 'contactid'],
181             ],
182             'private messages' => [
183                 'datagenerator' => 'private_messages',
184                 'required' => ['user', 'contact', 'message'],
185                 'switchids' => ['user' => 'userid', 'contact' => 'contactid'],
186             ],
187             'favourite conversations' => [
188                 'datagenerator' => 'favourite_conversations',
189                 'required' => ['user', 'contact'],
190                 'switchids' => ['user' => 'userid', 'contact' => 'contactid'],
191             ],
192             'group messages' => [
193                 'datagenerator' => 'group_messages',
194                 'required' => ['user', 'group', 'message'],
195                 'switchids' => ['user' => 'userid', 'group' => 'groupid'],
196             ],
197             'muted group conversations' => [
198                 'datagenerator' => 'mute_group_conversations',
199                 'required' => ['user', 'group', 'course'],
200                 'switchids' => ['user' => 'userid', 'group' => 'groupid', 'course' => 'courseid'],
201             ],
202             'muted private conversations' => [
203                 'datagenerator' => 'mute_private_conversations',
204                 'required' => ['user', 'contact'],
205                 'switchids' => ['user' => 'userid', 'contact' => 'contactid'],
206             ],
207             'language customisations' => [
208                 'datagenerator' => 'customlang',
209                 'required' => ['component', 'stringid', 'value'],
210             ],
211             'analytics model' => [
212                 'datagenerator' => 'analytics_model',
213                 'required' => ['target', 'indicators', 'timesplitting', 'enabled'],
214             ],
215             'user preferences' => [
216                 'datagenerator' => 'user_preferences',
217                 'required' => array('user', 'preference', 'value'),
218                 'switchids' => array('user' => 'userid')
219             ],
220             'contentbank content' => [
221                 'datagenerator' => 'contentbank_content',
222                 'required' => array('contextlevel', 'reference', 'contenttype', 'user', 'contentname'),
223                 'switchids' => array('user' => 'userid')
224             ],
225             'badge external backpack' => [
226                 'datagenerator' => 'badge_external_backpack',
227                 'required' => ['backpackapiurl', 'backpackweburl', 'apiversion']
228             ],
229             'setup backpack connected' => [
230                 'datagenerator' => 'setup_backpack_connected',
231                 'required' => ['user', 'externalbackpack'],
232                 'switchids' => ['user' => 'userid', 'externalbackpack' => 'externalbackpackid']
233             ]
234         ];
235     }
237     /**
238      * Remove any empty custom fields, to avoid errors when creating the course.
239      *
240      * @param array $data
241      * @return array
242      */
243     protected function preprocess_course($data) {
244         foreach ($data as $fieldname => $value) {
245             if ($value === '' && strpos($fieldname, 'customfield_') === 0) {
246                 unset($data[$fieldname]);
247             }
248         }
249         return $data;
250     }
252     /**
253      * If password is not set it uses the username.
254      *
255      * @param array $data
256      * @return array
257      */
258     protected function preprocess_user($data) {
259         if (!isset($data['password'])) {
260             $data['password'] = $data['username'];
261         }
262         return $data;
263     }
265     /**
266      * If contextlevel and reference are specified for cohort, transform them to the contextid.
267      *
268      * @param array $data
269      * @return array
270      */
271     protected function preprocess_cohort($data) {
272         if (isset($data['contextlevel'])) {
273             if (!isset($data['reference'])) {
274                 throw new Exception('If field contextlevel is specified, field reference must also be present');
275             }
276             $context = $this->get_context($data['contextlevel'], $data['reference']);
277             unset($data['contextlevel']);
278             unset($data['reference']);
279             $data['contextid'] = $context->id;
280         }
281         return $data;
282     }
284     /**
285      * Preprocesses the creation of a grade item. Converts gradetype text to a number.
286      *
287      * @param array $data
288      * @return array
289      */
290     protected function preprocess_grade_item($data) {
291         global $CFG;
292         require_once("$CFG->libdir/grade/constants.php");
294         if (isset($data['gradetype'])) {
295             $data['gradetype'] = constant("GRADE_TYPE_" . strtoupper($data['gradetype']));
296         }
298         if (!empty($data['category']) && !empty($data['courseid'])) {
299             $cat = grade_category::fetch(array('fullname' => $data['category'], 'courseid' => $data['courseid']));
300             if (!$cat) {
301                 throw new Exception('Could not resolve category with name "' . $data['category'] . '"');
302             }
303             unset($data['category']);
304             $data['categoryid'] = $cat->id;
305         }
307         return $data;
308     }
310     /**
311      * Adapter to modules generator.
312      *
313      * @throws Exception Custom exception for test writers
314      * @param array $data
315      * @return void
316      */
317     protected function process_activity($data) {
318         global $DB, $CFG;
320         // The the_following_exists() method checks that the field exists.
321         $activityname = $data['activity'];
322         unset($data['activity']);
324         // Convert scale name into scale id (negative number indicates using scale).
325         if (isset($data['grade']) && strlen($data['grade']) && !is_number($data['grade'])) {
326             $data['grade'] = - $this->get_scale_id($data['grade']);
327             require_once("$CFG->libdir/grade/constants.php");
329             if (!isset($data['gradetype'])) {
330                 $data['gradetype'] = GRADE_TYPE_SCALE;
331             }
332         }
334         // We split $data in the activity $record and the course module $options.
335         $cmoptions = array();
336         $cmcolumns = $DB->get_columns('course_modules');
337         foreach ($cmcolumns as $key => $value) {
338             if (isset($data[$key])) {
339                 $cmoptions[$key] = $data[$key];
340             }
341         }
343         // Custom exception.
344         try {
345             $this->datagenerator->create_module($activityname, $data, $cmoptions);
346         } catch (coding_exception $e) {
347             throw new Exception('\'' . $activityname . '\' activity can not be added using this step,' .
348                     ' use the step \'I add a "ACTIVITY_OR_RESOURCE_NAME_STRING" to section "SECTION_NUMBER"\' instead');
349         }
350     }
352     /**
353      * Add a block to a page.
354      *
355      * @param array $data should mostly match the fields of the block_instances table.
356      *     The block type is specified by blockname.
357      *     The parentcontextid is set from contextlevel and reference.
358      *     Missing values are filled in by testing_block_generator::prepare_record.
359      *     $data is passed to create_block as both $record and $options. Normally
360      *     the keys are different, so this is a way to let people set values in either place.
361      */
362     protected function process_block_instance($data) {
364         if (empty($data['blockname'])) {
365             throw new Exception('\'blocks\' requires the field \'block\' type to be specified');
366         }
368         if (empty($data['contextlevel'])) {
369             throw new Exception('\'blocks\' requires the field \'contextlevel\' to be specified');
370         }
372         if (!isset($data['reference'])) {
373             throw new Exception('\'blocks\' requires the field \'reference\' to be specified');
374         }
376         $context = $this->get_context($data['contextlevel'], $data['reference']);
377         $data['parentcontextid'] = $context->id;
379         // Pass $data as both $record and $options. I think that is unlikely to
380         // cause problems since the relevant key names are different.
381         // $options is not used in most blocks I have seen, but where it is, it is necessary.
382         $this->datagenerator->create_block($data['blockname'], $data, $data);
383     }
385     /**
386      * Creates language customisation.
387      *
388      * @throws Exception
389      * @throws dml_exception
390      * @param array $data
391      * @return void
392      */
393     protected function process_customlang($data) {
394         global $CFG, $DB, $USER;
396         require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/customlang/locallib.php');
397         require_once($CFG->libdir . '/adminlib.php');
399         if (empty($data['component'])) {
400             throw new Exception('\'customlang\' requires the field \'component\' type to be specified');
401         }
403         if (empty($data['stringid'])) {
404             throw new Exception('\'customlang\' requires the field \'stringid\' to be specified');
405         }
407         if (!isset($data['value'])) {
408             throw new Exception('\'customlang\' requires the field \'value\' to be specified');
409         }
411         $now = time();
413         tool_customlang_utils::checkout($USER->lang);
415         $record = $DB->get_record_sql("SELECT s.*
416                                          FROM {tool_customlang} s
417                                          JOIN {tool_customlang_components} c ON s.componentid = c.id
418                                         WHERE c.name = ? AND s.lang = ? AND s.stringid = ?",
419                 array($data['component'], $USER->lang, $data['stringid']));
421         if (empty($data['value']) && !is_null($record->local)) {
422             $record->local = null;
423             $record->modified = 1;
424             $record->outdated = 0;
425             $record->timecustomized = null;
426             $DB->update_record('tool_customlang', $record);
427             tool_customlang_utils::checkin($USER->lang);
428         }
430         if (!empty($data['value']) && $data['value'] != $record->local) {
431             $record->local = $data['value'];
432             $record->modified = 1;
433             $record->outdated = 0;
434             $record->timecustomized = $now;
435             $DB->update_record('tool_customlang', $record);
436             tool_customlang_utils::checkin($USER->lang);
437         }
438     }
440     /**
441      * Adapter to enrol_user() data generator.
442      *
443      * @throws Exception
444      * @param array $data
445      * @return void
446      */
447     protected function process_enrol_user($data) {
448         global $SITE;
450         if (empty($data['roleid'])) {
451             throw new Exception('\'course enrolments\' requires the field \'role\' to be specified');
452         }
454         if (!isset($data['userid'])) {
455             throw new Exception('\'course enrolments\' requires the field \'user\' to be specified');
456         }
458         if (!isset($data['courseid'])) {
459             throw new Exception('\'course enrolments\' requires the field \'course\' to be specified');
460         }
462         if (!isset($data['enrol'])) {
463             $data['enrol'] = 'manual';
464         }
466         if (!isset($data['timestart'])) {
467             $data['timestart'] = 0;
468         }
470         if (!isset($data['timeend'])) {
471             $data['timeend'] = 0;
472         }
474         if (!isset($data['status'])) {
475             $data['status'] = null;
476         }
478         // If the provided course shortname is the site shortname we consider it a system role assign.
479         if ($data['courseid'] == $SITE->id) {
480             // Frontpage course assign.
481             $context = context_course::instance($data['courseid']);
482             role_assign($data['roleid'], $data['userid'], $context->id);
484         } else {
485             // Course assign.
486             $this->datagenerator->enrol_user($data['userid'], $data['courseid'], $data['roleid'], $data['enrol'],
487                     $data['timestart'], $data['timeend'], $data['status']);
488         }
490     }
492     /**
493      * Allows/denies a capability at the specified context
494      *
495      * @throws Exception
496      * @param array $data
497      * @return void
498      */
499     protected function process_permission_override($data) {
501         // Will throw an exception if it does not exist.
502         $context = $this->get_context($data['contextlevel'], $data['reference']);
504         switch ($data['permission']) {
505             case get_string('allow', 'role'):
506                 $permission = CAP_ALLOW;
507                 break;
508             case get_string('prevent', 'role'):
509                 $permission = CAP_PREVENT;
510                 break;
511             case get_string('prohibit', 'role'):
512                 $permission = CAP_PROHIBIT;
513                 break;
514             default:
515                 throw new Exception('The \'' . $data['permission'] . '\' permission does not exist');
516                 break;
517         }
519         if (is_null(get_capability_info($data['capability']))) {
520             throw new Exception('The \'' . $data['capability'] . '\' capability does not exist');
521         }
523         role_change_permission($data['roleid'], $context, $data['capability'], $permission);
524     }
526     /**
527      * Assigns a role to a user at system context
528      *
529      * Used by "system role assigns" can be deleted when
530      * system role assign will be deprecated in favour of
531      * "role assigns"
532      *
533      * @throws Exception
534      * @param array $data
535      * @return void
536      */
537     protected function process_system_role_assign($data) {
539         if (empty($data['roleid'])) {
540             throw new Exception('\'system role assigns\' requires the field \'role\' to be specified');
541         }
543         if (!isset($data['userid'])) {
544             throw new Exception('\'system role assigns\' requires the field \'user\' to be specified');
545         }
547         $context = context_system::instance();
549         $this->datagenerator->role_assign($data['roleid'], $data['userid'], $context->id);
550     }
552     /**
553      * Assigns a role to a user at the specified context
554      *
555      * @throws Exception
556      * @param array $data
557      * @return void
558      */
559     protected function process_role_assign($data) {
561         if (empty($data['roleid'])) {
562             throw new Exception('\'role assigns\' requires the field \'role\' to be specified');
563         }
565         if (!isset($data['userid'])) {
566             throw new Exception('\'role assigns\' requires the field \'user\' to be specified');
567         }
569         if (empty($data['contextlevel'])) {
570             throw new Exception('\'role assigns\' requires the field \'contextlevel\' to be specified');
571         }
573         if (!isset($data['reference'])) {
574             throw new Exception('\'role assigns\' requires the field \'reference\' to be specified');
575         }
577         // Getting the context id.
578         $context = $this->get_context($data['contextlevel'], $data['reference']);
580         $this->datagenerator->role_assign($data['roleid'], $data['userid'], $context->id);
581     }
583     /**
584      * Creates a role.
585      *
586      * @param array $data
587      * @return void
588      */
589     protected function process_role($data) {
591         // We require the user to fill the role shortname.
592         if (empty($data['shortname'])) {
593             throw new Exception('\'role\' requires the field \'shortname\' to be specified');
594         }
596         $this->datagenerator->create_role($data);
597     }
599     /**
600      * Adds members to cohorts
601      *
602      * @param array $data
603      * @return void
604      */
605     protected function process_cohort_member($data) {
606         cohort_add_member($data['cohortid'], $data['userid']);
607     }
609     /**
610      * Create a question category.
611      *
612      * @param array $data the row of data from the behat script.
613      */
614     protected function process_question_category($data) {
615         global $DB;
617         $context = $this->get_context($data['contextlevel'], $data['reference']);
619         // The way this class works, we have already looked up the given parent category
620         // name and found a matching category. However, it is possible, particularly
621         // for the 'top' category, for there to be several categories with the
622         // same name. So far one will have been picked at random, but we need
623         // the one from the right context. So, if we have the wrong category, try again.
624         // (Just fixing it here, rather than getting it right first time, is a bit
625         // of a bodge, but in general this class assumes that names are unique,
626         // and normally they are, so this was the easiest fix.)
627         if (!empty($data['parent'])) {
628             $foundparent = $DB->get_record('question_categories', ['id' => $data['parent']], '*', MUST_EXIST);
629             if ($foundparent->contextid != $context->id) {
630                 $rightparentid = $DB->get_field('question_categories', 'id',
631                         ['contextid' => $context->id, 'name' => $foundparent->name]);
632                 if (!$rightparentid) {
633                     throw new Exception('The specified question category with name "' . $foundparent->name .
634                             '" does not exist in context "' . $context->get_context_name() . '"."');
635                 }
636                 $data['parent'] = $rightparentid;
637             }
638         }
640         $data['contextid'] = $context->id;
641         $this->datagenerator->get_plugin_generator('core_question')->create_question_category($data);
642     }
644     /**
645      * Create a question.
646      *
647      * Creating questions relies on the question/type/.../tests/helper.php mechanism.
648      * We start with test_question_maker::get_question_form_data($data['qtype'], $data['template'])
649      * and then overlay the values from any other fields of $data that are set.
650      *
651      * There is a special case that allows you to set qtype to 'missingtype'.
652      * This creates an example of broken question, such as you might get if you
653      * install a question type, create some questions of that type, and then
654      * uninstall the question type (which is prevented through the UI but can
655      * still happen). This special lets tests verify that these questions are
656      * handled OK.
657      *
658      * @param array $data the row of data from the behat script.
659      */
660     protected function process_question($data) {
661         global $DB;
663         if (array_key_exists('questiontext', $data)) {
664             $data['questiontext'] = array(
665                     'text'   => $data['questiontext'],
666                     'format' => FORMAT_HTML,
667             );
668         }
670         if (array_key_exists('generalfeedback', $data)) {
671             $data['generalfeedback'] = array(
672                     'text'   => $data['generalfeedback'],
673                     'format' => FORMAT_HTML,
674             );
675         }
677         $which = null;
678         if (!empty($data['template'])) {
679             $which = $data['template'];
680         }
682         $missingtypespecialcase = false;
683         if ($data['qtype'] === 'missingtype') {
684             $data['qtype'] = 'essay'; // Actual type uses here does not matter. We just need any question.
685             $missingtypespecialcase = true;
686         }
688         $questiondata = $this->datagenerator->get_plugin_generator('core_question')
689             ->create_question($data['qtype'], $which, $data);
691         if ($missingtypespecialcase) {
692             $DB->set_field('question', 'qtype', 'unknownqtype', ['id' => $questiondata->id]);
693         }
694     }
696     /**
697      * Adds user to contacts
698      *
699      * @param array $data
700      * @return void
701      */
702     protected function process_message_contacts($data) {
703         \core_message\api::add_contact($data['userid'], $data['contactid']);
704     }
706     /**
707      * Send a new message from user to contact in a private conversation
708      *
709      * @param array $data
710      * @return void
711      */
712     protected function process_private_messages(array $data) {
713         if (empty($data['format'])) {
714             $data['format'] = 'FORMAT_PLAIN';
715         }
717         if (!$conversationid = \core_message\api::get_conversation_between_users([$data['userid'], $data['contactid']])) {
718             $conversation = \core_message\api::create_conversation(
719                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
720                     [$data['userid'], $data['contactid']]
721             );
722             $conversationid = $conversation->id;
723         }
724         \core_message\api::send_message_to_conversation(
725                 $data['userid'],
726                 $conversationid,
727                 $data['message'],
728                 constant($data['format'])
729         );
730     }
732     /**
733      * Send a new message from user to a group conversation
734      *
735      * @param array $data
736      * @return void
737      */
738     protected function process_group_messages(array $data) {
739         global $DB;
741         if (empty($data['format'])) {
742             $data['format'] = 'FORMAT_PLAIN';
743         }
745         $group = $DB->get_record('groups', ['id' => $data['groupid']]);
746         $coursecontext = context_course::instance($group->courseid);
747         if (!$conversation = \core_message\api::get_conversation_by_area('core_group', 'groups', $data['groupid'],
748                 $coursecontext->id)) {
749             $members = $DB->get_records_menu('groups_members', ['groupid' => $data['groupid']], '', 'userid, id');
750             $conversation = \core_message\api::create_conversation(
751                     \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
752                     array_keys($members),
753                     $group->name,
754                     \core_message\api::MESSAGE_CONVERSATION_ENABLED,
755                     'core_group',
756                     'groups',
757                     $group->id,
758                     $coursecontext->id);
759         }
760         \core_message\api::send_message_to_conversation(
761                 $data['userid'],
762                 $conversation->id,
763                 $data['message'],
764                 constant($data['format'])
765         );
766     }
768     /**
769      * Mark a private conversation as favourite for user
770      *
771      * @param array $data
772      * @return void
773      */
774     protected function process_favourite_conversations(array $data) {
775         if (!$conversationid = \core_message\api::get_conversation_between_users([$data['userid'], $data['contactid']])) {
776             $conversation = \core_message\api::create_conversation(
777                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
778                     [$data['userid'], $data['contactid']]
779             );
780             $conversationid = $conversation->id;
781         }
782         \core_message\api::set_favourite_conversation($conversationid, $data['userid']);
783     }
785     /**
786      * Mute an existing group conversation for user
787      *
788      * @param array $data
789      * @return void
790      */
791     protected function process_mute_group_conversations(array $data) {
792         if (groups_is_member($data['groupid'], $data['userid'])) {
793             $context = context_course::instance($data['courseid']);
794             $conversation = \core_message\api::get_conversation_by_area(
795                     'core_group',
796                     'groups',
797                     $data['groupid'],
798                     $context->id
799             );
800             if ($conversation) {
801                 \core_message\api::mute_conversation($data['userid'], $conversation->id);
802             }
803         }
804     }
806     /**
807      * Mute a private conversation for user
808      *
809      * @param array $data
810      * @return void
811      */
812     protected function process_mute_private_conversations(array $data) {
813         if (!$conversationid = \core_message\api::get_conversation_between_users([$data['userid'], $data['contactid']])) {
814             $conversation = \core_message\api::create_conversation(
815                     \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
816                     [$data['userid'], $data['contactid']]
817             );
818             $conversationid = $conversation->id;
819         }
820         \core_message\api::mute_conversation($data['userid'], $conversationid);
821     }
823     /**
824      * Transform indicators string into array.
825      *
826      * @param array $data
827      * @return array
828      */
829     protected function preprocess_analytics_model($data) {
830         $data['indicators'] = explode(',', $data['indicators']);
831         return $data;
832     }
834     /**
835      * Creates an analytics model
836      *
837      * @param target $data
838      * @return void
839      */
840     protected function process_analytics_model($data) {
841         \core_analytics\manager::create_declared_model($data);
842     }
844     /**
845      * Set a preference value for user
846      *
847      * @param array $data
848      * @return void
849      */
850     protected function process_user_preferences(array $data) {
851         set_user_preference($data['preference'], $data['value'], $data['userid']);
852     }
854     /**
855      * Create content in the given context's content bank.
856      *
857      * @param array $data
858      * @return void
859      */
860     protected function process_contentbank_content(array $data) {
861         global $CFG;
863         if (empty($data['contextlevel'])) {
864             throw new Exception('contentbank_content requires the field contextlevel to be specified');
865         }
867         if (!isset($data['reference'])) {
868             throw new Exception('contentbank_content requires the field reference to be specified');
869         }
871         if (empty($data['contenttype'])) {
872             throw new Exception('contentbank_content requires the field contenttype to be specified');
873         }
875         $contenttypeclass = "\\".$data['contenttype']."\\contenttype";
876         if (class_exists($contenttypeclass)) {
877             $context = $this->get_context($data['contextlevel'], $data['reference']);
878             $contenttype = new $contenttypeclass($context);
879             $record = new stdClass();
880             $record->usercreated = $data['userid'];
881             $record->name = $data['contentname'];
882             $content = $contenttype->create_content($record);
884             if (!empty($data['filepath'])) {
885                 $filename = basename($data['filepath']);
886                 $fs = get_file_storage();
887                 $filerecord = array(
888                     'component' => 'contentbank',
889                     'filearea' => 'public',
890                     'contextid' => $context->id,
891                     'userid' => $data['userid'],
892                     'itemid' => $content->get_id(),
893                     'filename' => $filename,
894                     'filepath' => '/'
895                 );
896                 $fs->create_file_from_pathname($filerecord, $CFG->dirroot . $data['filepath']);
897             }
898         } else {
899             throw new Exception('The specified "' . $data['contenttype'] . '" contenttype does not exist');
900         }
901     }
903     /**
904      * Create a exetrnal backpack.
905      *
906      * @param array $data
907      */
908     protected function process_badge_external_backpack(array $data) {
909         global $DB;
910         $DB->insert_record('badge_external_backpack', $data, true);
911     }
913     /**
914      * Setup a backpack connected for user.
915      *
916      * @param array $data
917      * @throws dml_exception
918      */
919     protected function process_setup_backpack_connected(array $data) {
920         global $DB;
922         if (empty($data['userid'])) {
923             throw new Exception('\'setup backpack connected\' requires the field \'user\' to be specified');
924         }
925         if (empty($data['externalbackpackid'])) {
926             throw new Exception('\'setup backpack connected\' requires the field \'externalbackpack\' to be specified');
927         }
928         // Dummy badge_backpack_oauth2 data.
929         $timenow = time();
930         $backpackoauth2 = new stdClass();
931         $backpackoauth2->usermodified = $data['userid'];
932         $backpackoauth2->timecreated = $timenow;
933         $backpackoauth2->timemodified = $timenow;
934         $backpackoauth2->userid = $data['userid'];
935         $backpackoauth2->issuerid = 1;
936         $backpackoauth2->externalbackpackid = $data['externalbackpackid'];
937         $backpackoauth2->token = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
938         $backpackoauth2->refreshtoken = '0123456789abcdefghijk';
939         $backpackoauth2->expires = $timenow + 3600;
940         $backpackoauth2->scope = 'https://purl.imsglobal.org/spec/ob/v2p1/scope/assertion.create';
941         $backpackoauth2->scope .= ' https://purl.imsglobal.org/spec/ob/v2p1/scope/assertion.readonly offline_access';
942         $DB->insert_record('badge_backpack_oauth2', $backpackoauth2);
944         // Dummy badge_backpack data.
945         $backpack = new stdClass();
946         $backpack->userid = $data['userid'];
947         $backpack->email = 'student@behat.moodle';
948         $backpack->backpackuid = 0;
949         $backpack->autosync = 0;
950         $backpack->password = '';
951         $backpack->externalbackpackid = $data['externalbackpackid'];
952         $DB->insert_record('badge_backpack', $backpack);
953     }