1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Events tests.
19 *
20 * @package core_tag
21 * @category test
22 * @copyright 2014 Mark Nelson <markn@moodle.com>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26defined('MOODLE_INTERNAL') || die();
27
28global $CFG;
29
30
31// Used to create a wiki page to tag.
32require_once($CFG->dirroot . '/mod/wiki/locallib.php');
33
34class core_tag_events_testcase extends advanced_testcase {
35
36    /**
37     * Test set up.
38     *
39     * This is executed before running any test in this file.
40     */
41    public function setUp(): void {
42        $this->resetAfterTest();
43    }
44
45    /**
46     * Test the tag updated event.
47     */
48    public function test_tag_updated() {
49        $this->setAdminUser();
50
51        // Save the system context.
52        $systemcontext = context_system::instance();
53
54        // Create a tag we are going to update.
55        $tag = $this->getDataGenerator()->create_tag();
56
57        // Store the name before we change it.
58        $oldname = $tag->name;
59
60        // Trigger and capture the event when renaming a tag.
61        $sink = $this->redirectEvents();
62        core_tag_tag::get($tag->id, '*')->update(array('rawname' => 'newname'));
63        // Update the tag's name since we have renamed it.
64        $tag->name = 'newname';
65        $events = $sink->get_events();
66        $event = reset($events);
67
68        // Check that the event data is valid.
69        $this->assertInstanceOf('\core\event\tag_updated', $event);
70        $this->assertEquals($systemcontext, $event->get_context());
71        $expected = array(SITEID, 'tag', 'update', 'index.php?id=' . $tag->id, $oldname . '->'. $tag->name);
72        $this->assertEventLegacyLogData($expected, $event);
73
74        // Trigger and capture the event when setting the type of a tag.
75        $sink = $this->redirectEvents();
76        core_tag_tag::get($tag->id, '*')->update(array('isstandard' => 1));
77        $events = $sink->get_events();
78        $event = reset($events);
79
80        // Check that the event data is valid.
81        $this->assertInstanceOf('\core\event\tag_updated', $event);
82        $this->assertEquals($systemcontext, $event->get_context());
83        $expected = array(0, 'tag', 'update', 'index.php?id=' . $tag->id, $tag->name);
84        $this->assertEventLegacyLogData($expected, $event);
85
86        // Trigger and capture the event for setting the description of a tag.
87        $sink = $this->redirectEvents();
88        core_tag_tag::get($tag->id, '*')->update(
89                array('description' => 'description', 'descriptionformat' => FORMAT_MOODLE));
90        $events = $sink->get_events();
91        $event = reset($events);
92
93        // Check that the event data is valid.
94        $this->assertInstanceOf('\core\event\tag_updated', $event);
95        $this->assertEquals($systemcontext, $event->get_context());
96        $expected = array(0, 'tag', 'update', 'index.php?id=' . $tag->id, $tag->name);
97        $this->assertEventLegacyLogData($expected, $event);
98    }
99
100    /**
101     * Test the tag added event.
102     */
103    public function test_tag_added() {
104        global $DB;
105
106        // Create a course to tag.
107        $course = $this->getDataGenerator()->create_course();
108
109        // Trigger and capture the event for tagging a course.
110        $sink = $this->redirectEvents();
111        core_tag_tag::set_item_tags('core', 'course', $course->id, context_course::instance($course->id), array('A tag'));
112        $events = $sink->get_events();
113        $event = $events[1];
114
115        // Check that the tag was added to the course and that the event data is valid.
116        $this->assertEquals(1, $DB->count_records('tag_instance', array('component' => 'core')));
117        $this->assertInstanceOf('\core\event\tag_added', $event);
118        $this->assertEquals(context_course::instance($course->id), $event->get_context());
119        $expected = array($course->id, 'coursetags', 'add', 'tag/search.php?query=A+tag', 'Course tagged');
120        $this->assertEventLegacyLogData($expected, $event);
121
122        // Create a question to tag.
123        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
124        $cat = $questiongenerator->create_question_category();
125        $question = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
126
127        // Trigger and capture the event for tagging a question.
128        $this->assertEquals(1, $DB->count_records('tag_instance'));
129        $sink = $this->redirectEvents();
130        core_tag_tag::set_item_tags('core_question', 'question', $question->id,
131            context::instance_by_id($cat->contextid), array('A tag'));
132        $events = $sink->get_events();
133        $event = reset($events);
134
135        // Check that the tag was added to the question and the event data is valid.
136        $this->assertEquals(1, $DB->count_records('tag_instance', array('component' => 'core')));
137        $this->assertInstanceOf('\core\event\tag_added', $event);
138        $this->assertEquals(context_system::instance(), $event->get_context());
139        $expected = null;
140        $this->assertEventLegacyLogData($expected, $event);
141    }
142
143    /**
144     * Test the tag removed event.
145     */
146    public function test_tag_removed() {
147        global $DB;
148
149        $this->setAdminUser();
150
151        // Create a course to tag.
152        $course = $this->getDataGenerator()->create_course();
153
154        // Create a wiki page to tag.
155        $wikigenerator = $this->getDataGenerator()->get_plugin_generator('mod_wiki');
156        $wiki = $wikigenerator->create_instance(array('course' => $course->id));
157        $subwikiid = wiki_add_subwiki($wiki->id, 0);
158        $wikipageid = wiki_create_page($subwikiid, 'Title', FORMAT_HTML, '2');
159
160        // Create the tag.
161        $tag = $this->getDataGenerator()->create_tag();
162
163        // Assign a tag to a course.
164        core_tag_tag::add_item_tag('core', 'course', $course->id, context_course::instance($course->id), $tag->rawname);
165
166        // Trigger and capture the event for untagging a course.
167        $sink = $this->redirectEvents();
168        core_tag_tag::remove_item_tag('core', 'course', $course->id, $tag->rawname);
169        $events = $sink->get_events();
170        $event = reset($events);
171
172        // Check that the tag was removed from the course and the event data is valid.
173        $this->assertEquals(0, $DB->count_records('tag_instance'));
174        $this->assertInstanceOf('\core\event\tag_removed', $event);
175        $this->assertEquals(context_course::instance($course->id), $event->get_context());
176
177        // Create the tag.
178        $tag = $this->getDataGenerator()->create_tag();
179
180        // Assign a tag to a wiki this time.
181        core_tag_tag::add_item_tag('mod_wiki', 'wiki_pages', $wikipageid, context_module::instance($wiki->cmid), $tag->rawname);
182
183        // Trigger and capture the event for deleting this tag instance.
184        $sink = $this->redirectEvents();
185        core_tag_tag::remove_item_tag('mod_wiki', 'wiki_pages', $wikipageid, $tag->rawname);
186        $events = $sink->get_events();
187        $event = reset($events);
188
189        // Check that tag was removed from the wiki page and the event data is valid.
190        $this->assertEquals(0, $DB->count_records('tag_instance'));
191        $this->assertInstanceOf('\core\event\tag_removed', $event);
192        $this->assertEquals(context_module::instance($wiki->cmid), $event->get_context());
193
194        // Create a tag again - the other would have been deleted since there were no more instances associated with it.
195        $tag = $this->getDataGenerator()->create_tag();
196
197        // Assign a tag to the wiki again.
198        core_tag_tag::add_item_tag('mod_wiki', 'wiki_pages', $wikipageid, context_module::instance($wiki->cmid), $tag->rawname);
199
200        // Now we want to delete this tag, and because there is only one tag instance
201        // associated with it, it should get deleted as well.
202        $sink = $this->redirectEvents();
203        core_tag_tag::delete_tags($tag->id);
204        $events = $sink->get_events();
205        $event = reset($events);
206
207        // Check that tag was removed from the wiki page and the event data is valid.
208        $this->assertEquals(0, $DB->count_records('tag_instance'));
209        $this->assertInstanceOf('\core\event\tag_removed', $event);
210        $this->assertEquals(context_module::instance($wiki->cmid), $event->get_context());
211
212        // Create a tag again - the other would have been deleted since there were no more instances associated with it.
213        $tag = $this->getDataGenerator()->create_tag();
214
215        // Assign a tag to the wiki again.
216        core_tag_tag::add_item_tag('mod_wiki', 'wiki_pages', $wikipageid, context_module::instance($wiki->cmid), $tag->rawname);
217
218        // Delete all tag instances for this wiki instance.
219        $sink = $this->redirectEvents();
220        core_tag_tag::delete_instances('mod_wiki', 'wiki_pages', context_module::instance($wiki->cmid)->id);
221        $events = $sink->get_events();
222        $event = reset($events);
223
224        // Check that tag was removed from the wiki page and the event data is valid.
225        $this->assertEquals(0, $DB->count_records('tag_instance'));
226        $this->assertInstanceOf('\core\event\tag_removed', $event);
227        $this->assertEquals(context_module::instance($wiki->cmid), $event->get_context());
228
229        // Create another wiki.
230        $wiki2 = $wikigenerator->create_instance(array('course' => $course->id));
231        $subwikiid2 = wiki_add_subwiki($wiki2->id, 0);
232        $wikipageid2 = wiki_create_page($subwikiid2, 'Title', FORMAT_HTML, '2');
233
234        // Assign a tag to both wiki pages.
235        core_tag_tag::add_item_tag('mod_wiki', 'wiki_pages', $wikipageid, context_module::instance($wiki->cmid), $tag->rawname);
236        core_tag_tag::add_item_tag('mod_wiki', 'wiki_pages', $wikipageid2, context_module::instance($wiki2->cmid), $tag->rawname);
237
238        // Now remove all tag_instances associated with all wikis.
239        $sink = $this->redirectEvents();
240        core_tag_tag::delete_instances('mod_wiki');
241        $events = $sink->get_events();
242
243        // There will be two events - one for each wiki instance removed.
244        $this->assertCount(2, $events);
245        $contexts = [context_module::instance($wiki->cmid), context_module::instance($wiki2->cmid)];
246        $this->assertNotEquals($events[0]->contextid, $events[1]->contextid);
247
248        // Check that the tags were removed from the wiki pages.
249        $this->assertEquals(0, $DB->count_records('tag_instance'));
250
251        // Check the first event data is valid.
252        $this->assertInstanceOf('\core\event\tag_removed', $events[0]);
253        $this->assertContains($events[0]->get_context(), $contexts);
254
255        // Check that the second event data is valid.
256        $this->assertInstanceOf('\core\event\tag_removed', $events[1]);
257        $this->assertContains($events[1]->get_context(), $contexts);
258    }
259
260    /**
261     * Test the tag flagged event.
262     */
263    public function test_tag_flagged() {
264        global $DB;
265
266        $this->setAdminUser();
267
268        // Create tags we are going to flag.
269        $tag = $this->getDataGenerator()->create_tag();
270        $tag2 = $this->getDataGenerator()->create_tag();
271        $tags = array($tag, $tag2);
272
273        // Trigger and capture the event for setting the flag of a tag.
274        $sink = $this->redirectEvents();
275        core_tag_tag::get($tag->id, '*')->flag();
276        $events = $sink->get_events();
277        $event = reset($events);
278
279        // Check that the flag was updated.
280        $tag = $DB->get_record('tag', array('id' => $tag->id));
281        $this->assertEquals(1, $tag->flag);
282
283        // Check that the event data is valid.
284        $this->assertInstanceOf('\core\event\tag_flagged', $event);
285        $this->assertEquals(context_system::instance(), $event->get_context());
286        $expected = array(SITEID, 'tag', 'flag', 'index.php?id=' . $tag->id, $tag->id, '', '2');
287        $this->assertEventLegacyLogData($expected, $event);
288
289        // Unset the flag for both (though by default tag2 should have been created with 0 already).
290        foreach ($tags as $t) {
291            core_tag_tag::get($t->id, '*')->reset_flag();
292        }
293
294        // Trigger and capture the event for setting the flag for multiple tags.
295        $sink = $this->redirectEvents();
296        foreach ($tags as $t) {
297            core_tag_tag::get($t->id, '*')->flag();
298        }
299        $events = $sink->get_events();
300
301        // Check that the flags were updated.
302        $tag = $DB->get_record('tag', array('id' => $tag->id));
303        $this->assertEquals(1, $tag->flag);
304        $tag2 = $DB->get_record('tag', array('id' => $tag2->id));
305        $this->assertEquals(1, $tag2->flag);
306
307        // Confirm the events.
308        $event = $events[0];
309        $this->assertInstanceOf('\core\event\tag_flagged', $event);
310        $this->assertEquals(context_system::instance(), $event->get_context());
311        $expected = array(SITEID, 'tag', 'flag', 'index.php?id=' . $tag->id, $tag->id, '', '2');
312        $this->assertEventLegacyLogData($expected, $event);
313
314        $event = $events[1];
315        $this->assertInstanceOf('\core\event\tag_flagged', $event);
316        $this->assertEquals(context_system::instance(), $event->get_context());
317        $expected = array(SITEID, 'tag', 'flag', 'index.php?id=' . $tag2->id, $tag2->id, '', '2');
318        $this->assertEventLegacyLogData($expected, $event);
319    }
320
321    /**
322     * Test the tag unflagged event.
323     */
324    public function test_tag_unflagged() {
325        global $DB;
326
327        $this->setAdminUser();
328
329        // Create tags we are going to unflag.
330        $tag = $this->getDataGenerator()->create_tag();
331        $tag2 = $this->getDataGenerator()->create_tag();
332        $tags = array($tag, $tag2);
333
334        // Flag it.
335        core_tag_tag::get($tag->id, '*')->flag();
336
337        // Trigger and capture the event for unsetting the flag of a tag.
338        $sink = $this->redirectEvents();
339        core_tag_tag::get($tag->id, '*')->reset_flag();
340        $events = $sink->get_events();
341        $event = reset($events);
342
343        // Check that the flag was updated.
344        $tag = $DB->get_record('tag', array('id' => $tag->id));
345        $this->assertEquals(0, $tag->flag);
346
347        // Check that the event data is valid.
348        $this->assertInstanceOf('\core\event\tag_unflagged', $event);
349        $this->assertEquals(context_system::instance(), $event->get_context());
350
351        // Set the flag back for both.
352        foreach ($tags as $t) {
353            core_tag_tag::get($t->id, '*')->flag();
354        }
355
356        // Trigger and capture the event for unsetting the flag for multiple tags.
357        $sink = $this->redirectEvents();
358        foreach ($tags as $t) {
359            core_tag_tag::get($t->id, '*')->reset_flag();
360        }
361        $events = $sink->get_events();
362
363        // Check that the flags were updated.
364        $tag = $DB->get_record('tag', array('id' => $tag->id));
365        $this->assertEquals(0, $tag->flag);
366        $tag2 = $DB->get_record('tag', array('id' => $tag2->id));
367        $this->assertEquals(0, $tag2->flag);
368
369        // Confirm the events.
370        $event = $events[0];
371        $this->assertInstanceOf('\core\event\tag_unflagged', $event);
372        $this->assertEquals(context_system::instance(), $event->get_context());
373
374        $event = $events[1];
375        $this->assertInstanceOf('\core\event\tag_unflagged', $event);
376        $this->assertEquals(context_system::instance(), $event->get_context());
377    }
378
379    /**
380     * Test the tag deleted event
381     */
382    public function test_tag_deleted() {
383        global $DB;
384
385        $this->setAdminUser();
386
387        // Create a course and a user.
388        $course = $this->getDataGenerator()->create_course();
389        $user = $this->getDataGenerator()->create_user();
390
391        // Create tag we are going to delete.
392        $tag = $this->getDataGenerator()->create_tag();
393
394        // Trigger and capture the event for deleting a tag.
395        $sink = $this->redirectEvents();
396        core_tag_tag::delete_tags($tag->id);
397        $events = $sink->get_events();
398        $event = reset($events);
399
400        // Check that the tag was deleted and the event data is valid.
401        $this->assertEquals(0, $DB->count_records('tag'));
402        $this->assertInstanceOf('\core\event\tag_deleted', $event);
403        $this->assertEquals(context_system::instance(), $event->get_context());
404
405        // Create two tags we are going to delete to ensure passing multiple tags work.
406        $tag = $this->getDataGenerator()->create_tag();
407        $tag2 = $this->getDataGenerator()->create_tag();
408
409        // Trigger and capture the events for deleting multiple tags.
410        $sink = $this->redirectEvents();
411        core_tag_tag::delete_tags(array($tag->id, $tag2->id));
412        $events = $sink->get_events();
413
414        // Check that the tags were deleted and the events data is valid.
415        $this->assertEquals(0, $DB->count_records('tag'));
416        foreach ($events as $event) {
417            $this->assertInstanceOf('\core\event\tag_deleted', $event);
418            $this->assertEquals(context_system::instance(), $event->get_context());
419        }
420
421        // Add a tag instance to a course.
422        core_tag_tag::add_item_tag('core', 'course', $course->id, context_course::instance($course->id), 'cat', $user->id);
423
424        // Trigger and capture the event for deleting a personal tag for a user for a course.
425        $sink = $this->redirectEvents();
426        core_tag_tag::remove_item_tag('core', 'course', $course->id, 'cat', $user->id);
427        $events = $sink->get_events();
428        $event = $events[1];
429
430        // Check that the tag was deleted and the event data is valid.
431        $this->assertEquals(0, $DB->count_records('tag'));
432        $this->assertInstanceOf('\core\event\tag_deleted', $event);
433        $this->assertEquals(context_system::instance(), $event->get_context());
434
435        // Add the tag instance to the course again as it was deleted.
436        core_tag_tag::add_item_tag('core', 'course', $course->id, context_course::instance($course->id), 'dog', $user->id);
437
438        // Trigger and capture the event for deleting all tags in a course.
439        $sink = $this->redirectEvents();
440        core_tag_tag::remove_all_item_tags('core', 'course', $course->id);
441        $events = $sink->get_events();
442        $event = $events[1];
443
444        // Check that the tag was deleted and the event data is valid.
445        $this->assertEquals(0, $DB->count_records('tag'));
446        $this->assertInstanceOf('\core\event\tag_deleted', $event);
447        $this->assertEquals(context_system::instance(), $event->get_context());
448
449        // Add multiple tag instances now and check that it still works.
450        core_tag_tag::set_item_tags('core', 'course', $course->id, context_course::instance($course->id),
451            array('fish', 'hamster'), $user->id);
452
453        // Trigger and capture the event for deleting all tags in a course.
454        $sink = $this->redirectEvents();
455        core_tag_tag::remove_all_item_tags('core', 'course', $course->id);
456        $events = $sink->get_events();
457        $events = array($events[1], $events[3]);
458
459        // Check that the tags were deleted and the events data is valid.
460        $this->assertEquals(0, $DB->count_records('tag'));
461        foreach ($events as $event) {
462            $this->assertInstanceOf('\core\event\tag_deleted', $event);
463            $this->assertEquals(context_system::instance(), $event->get_context());
464        }
465    }
466
467    /**
468     * Test the tag created event.
469     */
470    public function test_tag_created() {
471        global $DB;
472
473        // Trigger and capture the event for creating a tag.
474        $sink = $this->redirectEvents();
475        core_tag_tag::create_if_missing(core_tag_area::get_collection('core', 'course'),
476                array('A really awesome tag!'));
477        $events = $sink->get_events();
478        $event = reset($events);
479
480        // Check that the tag was created and the event data is valid.
481        $this->assertEquals(1, $DB->count_records('tag'));
482        $this->assertInstanceOf('\core\event\tag_created', $event);
483        $this->assertEquals(context_system::instance(), $event->get_context());
484    }
485}
486