MDL-51883 libraries: 3.1 final deprecation in lib/deprecatedlib.php
[moodle.git] / tag / tests / taglib_test.php
CommitLineData
cc033d48
MN
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 * Tag related unit 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
cc033d48
MN
30class core_tag_taglib_testcase extends advanced_testcase {
31
32 /**
33 * Test set up.
34 *
35 * This is executed before running any test in this file.
36 */
37 public function setUp() {
38 $this->resetAfterTest();
39 }
40
41 /**
5ab88243 42 * Test that the tag_set function throws an exception.
c026a28d 43 * This function was deprecated in 3.1
cc033d48 44 */
c026a28d 45 public function test_tag_set_get() {
5ab88243
MG
46 $this->expectException('coding_exception');
47 $this->expectExceptionMessage('tag_set() can not be used anymore. Please use ' .
48 'core_tag_tag::set_item_tags().');
49 tag_set();
cc033d48
MN
50 }
51
52 /**
5ab88243 53 * Test that tag_set_add function throws an exception.
c026a28d 54 * This function was deprecated in 3.1
cc033d48
MN
55 */
56 public function test_tag_set_add() {
5ab88243
MG
57 $this->expectException('coding_exception');
58 $this->expectExceptionMessage('tag_set_add() can not be used anymore. Please use ' .
59 'core_tag_tag::add_item_tag().');
60 tag_set_add();
cc033d48
MN
61 }
62
63 /**
5ab88243 64 * Test that tag_set_delete function returns an exception.
c026a28d 65 * This function was deprecated in 3.1
cc033d48
MN
66 */
67 public function test_tag_set_delete() {
5ab88243
MG
68 $this->expectException('coding_exception');
69 $this->expectExceptionMessage('tag_set_delete() can not be used anymore. Please use ' .
70 'core_tag_tag::remove_item_tag().');
71 tag_set_delete();
c026a28d
MG
72 }
73
74 /**
75 * Test the core_tag_tag::add_item_tag() and core_tag_tag::remove_item_tag() functions.
76 */
77 public function test_add_remove_item_tag() {
78 global $DB;
79
80 // Create a course to tag.
81 $course = $this->getDataGenerator()->create_course();
82
83 // Create the tag and tag instance we are going to delete.
84 core_tag_tag::add_item_tag('core', 'course', $course->id, context_course::instance($course->id), 'A random tag');
85
86 $this->assertEquals(1, $DB->count_records('tag'));
87 $this->assertEquals(1, $DB->count_records('tag_instance'));
88
89 // Call the tag_set_delete function.
90 core_tag_tag::remove_item_tag('core', 'course', $course->id, 'A random tag');
91
92 // Now check that there are no tags or tag instances.
93 $this->assertEquals(0, $DB->count_records('tag'));
94 $this->assertEquals(0, $DB->count_records('tag_instance'));
cc033d48
MN
95 }
96
97 /**
5ab88243 98 * Test that tag_assign function throws an exception.
c026a28d 99 * This function was deprecated in 3.1
cc033d48
MN
100 */
101 public function test_tag_assign() {
5ab88243
MG
102 $this->expectException('coding_exception');
103 $this->expectExceptionMessage('tag_assign() can not be used anymore. Please use core_tag_tag::set_item_tags() ' .
104 'or core_tag_tag::add_item_tag() instead.');
105 tag_assign();
cc033d48 106 }
3ecf043a
AG
107
108 /**
109 * Test the tag cleanup function used by the cron.
110 */
111 public function test_tag_cleanup() {
112 global $DB;
113
c026a28d
MG
114 $task = new \core\task\tag_cron_task();
115
3ecf043a
AG
116 // Create some users.
117 $users = array();
118 for ($i = 0; $i < 10; $i++) {
119 $users[] = $this->getDataGenerator()->create_user();
120 }
121
122 // Create a course to tag.
123 $course = $this->getDataGenerator()->create_course();
124 $context = context_course::instance($course->id);
125
126 // Test clean up instances with tags that no longer exist.
127 $tags = array();
c026a28d 128 $tagnames = array();
3ecf043a 129 for ($i = 0; $i < 10; $i++) {
c026a28d
MG
130 $tags[] = $tag = $this->getDataGenerator()->create_tag(array('userid' => $users[0]->id));
131 $tagnames[] = $tag->rawname;
3ecf043a
AG
132 }
133 // Create instances with the tags.
c026a28d 134 core_tag_tag::set_item_tags('core', 'course', $course->id, $context, $tagnames);
3ecf043a
AG
135 // We should now have ten tag instances.
136 $coursetaginstances = $DB->count_records('tag_instance', array('itemtype' => 'course'));
137 $this->assertEquals(10, $coursetaginstances);
c026a28d 138
3ecf043a
AG
139 // Delete four tags
140 // Manual delete of tags is done as the function will remove the instances as well.
141 $DB->delete_records('tag', array('id' => $tags[6]->id));
142 $DB->delete_records('tag', array('id' => $tags[7]->id));
143 $DB->delete_records('tag', array('id' => $tags[8]->id));
144 $DB->delete_records('tag', array('id' => $tags[9]->id));
c026a28d 145
3ecf043a 146 // Clean up the tags.
c026a28d 147 $task->cleanup();
3ecf043a
AG
148 // Check that we now only have six tag_instance records left.
149 $coursetaginstances = $DB->count_records('tag_instance', array('itemtype' => 'course'));
150 $this->assertEquals(6, $coursetaginstances);
151
152 // Test clean up with users that have been deleted.
153 // Create a tag for this course.
154 foreach ($users as $user) {
c026a28d
MG
155 $context = context_user::instance($user->id);
156 core_tag_tag::set_item_tags('core', 'user', $user->id, $context, array($tags[0]->rawname));
3ecf043a
AG
157 }
158 $usertags = $DB->count_records('tag_instance', array('itemtype' => 'user'));
159 $this->assertCount($usertags, $users);
160 // Remove three students.
161 // Using the proper function to delete the user will also remove the tags.
162 $DB->update_record('user', array('id' => $users[4]->id, 'deleted' => 1));
163 $DB->update_record('user', array('id' => $users[5]->id, 'deleted' => 1));
164 $DB->update_record('user', array('id' => $users[6]->id, 'deleted' => 1));
c026a28d 165
3ecf043a 166 // Clean up the tags.
c026a28d 167 $task->cleanup();
3ecf043a
AG
168 $usertags = $DB->count_records('tag_instance', array('itemtype' => 'user'));
169 $usercount = $DB->count_records('user', array('deleted' => 0));
170 // Remove admin and guest from the count.
171 $this->assertEquals($usertags, ($usercount - 2));
172
173 // Test clean up where a course has been removed.
174 // Delete the course. This also needs to be this way otherwise the tags are removed by using the proper function.
175 $DB->delete_records('course', array('id' => $course->id));
c026a28d 176 $task->cleanup();
3ecf043a
AG
177 $coursetags = $DB->count_records('tag_instance', array('itemtype' => 'course'));
178 $this->assertEquals(0, $coursetags);
179
180 // Test clean up where a post has been removed.
181 // Create default post.
182 $post = new stdClass();
183 $post->userid = $users[1]->id;
184 $post->content = 'test post content text';
185 $post->id = $DB->insert_record('post', $post);
c026a28d
MG
186 $context = context_system::instance();
187 core_tag_tag::set_item_tags('core', 'post', $post->id, $context, array($tags[0]->rawname));
188
3ecf043a 189 // Add another one with a fake post id to be removed.
c026a28d 190 core_tag_tag::set_item_tags('core', 'post', 15, $context, array($tags[0]->rawname));
3ecf043a
AG
191 // Check that there are two tag instances.
192 $posttags = $DB->count_records('tag_instance', array('itemtype' => 'post'));
193 $this->assertEquals(2, $posttags);
194 // Clean up the tags.
c026a28d 195 $task->cleanup();
3ecf043a
AG
196 // We should only have one entry left now.
197 $posttags = $DB->count_records('tag_instance', array('itemtype' => 'post'));
198 $this->assertEquals(1, $posttags);
199 }
200
201 /**
202 * Test deleting a group of tag instances.
203 */
204 public function test_tag_bulk_delete_instances() {
205 global $DB;
c026a28d
MG
206 $task = new \core\task\tag_cron_task();
207
3ecf043a
AG
208 // Setup.
209 $user = $this->getDataGenerator()->create_user();
210 $course = $this->getDataGenerator()->create_course();
211 $context = context_course::instance($course->id);
212
213 // Create some tag instances.
214 for ($i = 0; $i < 10; $i++) {
215 $tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id));
c026a28d 216 core_tag_tag::add_item_tag('core', 'course', $course->id, $context, $tag->rawname);
3ecf043a
AG
217 }
218 // Get tag instances. tag name and rawname are required for the event fired in this function.
219 $sql = "SELECT ti.*, t.name, t.rawname
220 FROM {tag_instance} ti
221 JOIN {tag} t ON t.id = ti.tagid";
222 $taginstances = $DB->get_records_sql($sql);
223 $this->assertCount(10, $taginstances);
224 // Run the function.
c026a28d 225 $task->bulk_delete_instances($taginstances);
3ecf043a
AG
226 // Make sure they are gone.
227 $instancecount = $DB->count_records('tag_instance');
228 $this->assertEquals(0, $instancecount);
229 }
a196f370
MG
230
231 /**
0d202789
MG
232 * Prepares environment for testing tag correlations
233 * @return core_tag_tag[] list of used tags
a196f370 234 */
0d202789 235 protected function prepare_correlated() {
a196f370 236 global $DB;
c026a28d 237
a196f370
MG
238 $user = $this->getDataGenerator()->create_user();
239 $this->setUser($user);
240
241 $user1 = $this->getDataGenerator()->create_user();
242 $user2 = $this->getDataGenerator()->create_user();
243 $user3 = $this->getDataGenerator()->create_user();
244 $user4 = $this->getDataGenerator()->create_user();
245 $user5 = $this->getDataGenerator()->create_user();
246 $user6 = $this->getDataGenerator()->create_user();
247
248 // Several records have both 'cat' and 'cats' tags attached to them.
249 // This will make those tags automatically correlated.
250 // Same with 'dog', 'dogs' and 'puppy.
c026a28d
MG
251 core_tag_tag::set_item_tags('core', 'user', $user1->id, context_user::instance($user1->id), array('cat', 'cats'));
252 core_tag_tag::set_item_tags('core', 'user', $user2->id, context_user::instance($user2->id), array('cat', 'cats', 'kitten'));
253 core_tag_tag::set_item_tags('core', 'user', $user3->id, context_user::instance($user3->id), array('cat', 'cats'));
254 core_tag_tag::set_item_tags('core', 'user', $user4->id, context_user::instance($user4->id), array('dog', 'dogs', 'puppy'));
255 core_tag_tag::set_item_tags('core', 'user', $user5->id, context_user::instance($user5->id), array('dog', 'dogs', 'puppy'));
256 core_tag_tag::set_item_tags('core', 'user', $user6->id, context_user::instance($user6->id), array('dog', 'dogs', 'puppy'));
257
258 $tags = core_tag_tag::get_by_name_bulk(core_tag_collection::get_default(),
0d202789 259 array('cat', 'cats', 'dog', 'dogs', 'kitten', 'puppy'), '*');
a196f370
MG
260
261 // Add manual relation between tags 'cat' and 'kitten'.
0d202789
MG
262 core_tag_tag::get($tags['cat']->id)->set_related_tags(array('kitten'));
263
264 return $tags;
265 }
266
267 /**
5ab88243 268 * Test for function compute_correlations() that is part of tag cron
0d202789
MG
269 */
270 public function test_correlations() {
271 global $DB;
272 $task = new \core\task\tag_cron_task();
273
274 $tags = array_map(function ($t) {
275 return $t->id;
276 }, $this->prepare_correlated());
a196f370 277
c026a28d 278 $task->compute_correlations();
a196f370
MG
279
280 $this->assertEquals($tags['cats'],
c026a28d 281 $DB->get_field_select('tag_correlation', 'correlatedtags',
a196f370
MG
282 'tagid = ?', array($tags['cat'])));
283 $this->assertEquals($tags['cat'],
c026a28d 284 $DB->get_field_select('tag_correlation', 'correlatedtags',
a196f370
MG
285 'tagid = ?', array($tags['cats'])));
286 $this->assertEquals($tags['dogs'] . ',' . $tags['puppy'],
c026a28d 287 $DB->get_field_select('tag_correlation', 'correlatedtags',
a196f370
MG
288 'tagid = ?', array($tags['dog'])));
289 $this->assertEquals($tags['dog'] . ',' . $tags['puppy'],
c026a28d 290 $DB->get_field_select('tag_correlation', 'correlatedtags',
a196f370
MG
291 'tagid = ?', array($tags['dogs'])));
292 $this->assertEquals($tags['dog'] . ',' . $tags['dogs'],
c026a28d 293 $DB->get_field_select('tag_correlation', 'correlatedtags',
a196f370
MG
294 'tagid = ?', array($tags['puppy'])));
295
c026a28d
MG
296 // Make sure get_correlated_tags() returns 'cats' as the only correlated tag to the 'cat'.
297 $correlatedtags = array_values(core_tag_tag::get($tags['cat'])->get_correlated_tags(true));
298 $this->assertCount(3, $correlatedtags); // This will return all existing instances but they all point to the same tag.
299 $this->assertEquals('cats', $correlatedtags[0]->rawname);
300 $this->assertEquals('cats', $correlatedtags[1]->rawname);
301 $this->assertEquals('cats', $correlatedtags[2]->rawname);
302
303 $correlatedtags = array_values(core_tag_tag::get($tags['cat'])->get_correlated_tags());
304 $this->assertCount(1, $correlatedtags); // Duplicates are filtered out here.
305 $this->assertEquals('cats', $correlatedtags[0]->rawname);
306
5ab88243 307 // Make sure get_correlated_tags() returns 'dogs' and 'puppy' as the correlated tags to the 'dog'.
c026a28d
MG
308 $correlatedtags = core_tag_tag::get($tags['dog'])->get_correlated_tags(true);
309 $this->assertCount(6, $correlatedtags); // 2 tags times 3 instances.
310
311 $correlatedtags = array_values(core_tag_tag::get($tags['dog'])->get_correlated_tags());
312 $this->assertCount(2, $correlatedtags);
313 $this->assertEquals('dogs', $correlatedtags[0]->rawname);
314 $this->assertEquals('puppy', $correlatedtags[1]->rawname);
315
316 // Function get_related_tags() will return both related and correlated tags.
317 $relatedtags = array_values(core_tag_tag::get($tags['cat'])->get_related_tags());
318 $this->assertCount(2, $relatedtags);
319 $this->assertEquals('kitten', $relatedtags[0]->rawname);
320 $this->assertEquals('cats', $relatedtags[1]->rawname);
321
5ab88243
MG
322 // Also test get_correlated_tags().
323 $correlatedtags = array_values(core_tag_tag::get($tags['cat'])->get_correlated_tags(true));
a196f370
MG
324 $this->assertCount(3, $correlatedtags); // This will return all existing instances but they all point to the same tag.
325 $this->assertEquals('cats', $correlatedtags[0]->rawname);
326 $this->assertEquals('cats', $correlatedtags[1]->rawname);
327 $this->assertEquals('cats', $correlatedtags[2]->rawname);
328
5ab88243 329 $correlatedtags = array_values(core_tag_tag::get($tags['cat'])->get_correlated_tags());
a196f370
MG
330 $this->assertCount(1, $correlatedtags); // Duplicates are filtered out here.
331 $this->assertEquals('cats', $correlatedtags[0]->rawname);
332
5ab88243 333 $correlatedtags = array_values(core_tag_tag::get($tags['dog'])->get_correlated_tags(true));
a196f370
MG
334 $this->assertCount(6, $correlatedtags); // 2 tags times 3 instances.
335
5ab88243 336 $correlatedtags = array_values(core_tag_tag::get($tags['dog'])->get_correlated_tags());
a196f370
MG
337 $this->assertCount(2, $correlatedtags);
338 $this->assertEquals('dogs', $correlatedtags[0]->rawname);
339 $this->assertEquals('puppy', $correlatedtags[1]->rawname);
340
5ab88243 341 $relatedtags = array_values(core_tag_tag::get($tags['cat'])->get_related_tags());
a196f370
MG
342 $this->assertCount(2, $relatedtags);
343 $this->assertEquals('kitten', $relatedtags[0]->rawname);
344 $this->assertEquals('cats', $relatedtags[1]->rawname);
c026a28d 345 // End of testing deprecated methods.
a196f370 346
c026a28d
MG
347 // If we then manually set 'cat' and 'cats' as related, get_related_tags() will filter out duplicates.
348 core_tag_tag::get($tags['cat'])->set_related_tags(array('kitten', 'cats'));
a196f370 349
c026a28d 350 $relatedtags = array_values(core_tag_tag::get($tags['cat'])->get_related_tags());
a196f370
MG
351 $this->assertCount(2, $relatedtags);
352 $this->assertEquals('kitten', $relatedtags[0]->rawname);
353 $this->assertEquals('cats', $relatedtags[1]->rawname);
354
c026a28d
MG
355 // Make sure core_tag_tag::get_item_tags(), core_tag_tag::get_correlated_tags() return the same set of fields.
356 $relatedtags = core_tag_tag::get_item_tags('core', 'tag', $tags['cat']);
357 $relatedtag = reset($relatedtags);
358 $correlatedtags = core_tag_tag::get($tags['cat'])->get_correlated_tags();
359 $correlatedtag = reset($correlatedtags);
360 $this->assertEquals(array_keys((array)$relatedtag->to_object()), array_keys((array)$correlatedtag->to_object()));
361
5ab88243 362 $relatedtags = core_tag_tag::get_item_tags(null, 'tag', $tags['cat']);
a196f370 363 $relatedtag = reset($relatedtags);
5ab88243 364 $correlatedtags = core_tag_tag::get($tags['cat'])->get_correlated_tags();
a196f370
MG
365 $correlatedtag = reset($correlatedtags);
366 $this->assertEquals(array_keys((array)$relatedtag), array_keys((array)$correlatedtag));
367 }
46f708ea
MG
368
369 /**
5ab88243 370 * Test for function cleanup() that is part of tag cron
46f708ea
MG
371 */
372 public function test_cleanup() {
373 global $DB;
c026a28d
MG
374 $task = new \core\task\tag_cron_task();
375
46f708ea 376 $user = $this->getDataGenerator()->create_user();
c026a28d 377 $defaultcoll = core_tag_collection::get_default();
46f708ea 378
e11d7380 379 // Setting tags will create non-standard tags 'cat', 'dog' and 'fish'.
c026a28d 380 core_tag_tag::set_item_tags('core', 'user', $user->id, context_user::instance($user->id), array('cat', 'dog', 'fish'));
46f708ea
MG
381
382 $this->assertTrue($DB->record_exists('tag', array('name' => 'cat')));
383 $this->assertTrue($DB->record_exists('tag', array('name' => 'dog')));
384 $this->assertTrue($DB->record_exists('tag', array('name' => 'fish')));
385
e11d7380 386 // Make tag 'dog' standard.
c026a28d
MG
387 $dogtag = core_tag_tag::get_by_name($defaultcoll, 'dog', '*');
388 $fishtag = core_tag_tag::get_by_name($defaultcoll, 'fish');
e11d7380 389 $dogtag->update(array('isstandard' => 1));
46f708ea
MG
390
391 // Manually remove the instances pointing on tags 'dog' and 'fish'.
392 $DB->execute('DELETE FROM {tag_instance} WHERE tagid in (?,?)', array($dogtag->id, $fishtag->id));
393
c026a28d 394 $task->cleanup();
46f708ea 395
e11d7380
MG
396 // Tag 'cat' is still present because it's used. Tag 'dog' is present because it's standard.
397 // Tag 'fish' was removed because it is not standard and it is no longer used by anybody.
46f708ea
MG
398 $this->assertTrue($DB->record_exists('tag', array('name' => 'cat')));
399 $this->assertTrue($DB->record_exists('tag', array('name' => 'dog')));
400 $this->assertFalse($DB->record_exists('tag', array('name' => 'fish')));
401
402 // Delete user without using API function.
403 $DB->update_record('user', array('id' => $user->id, 'deleted' => 1));
404
c026a28d 405 $task->cleanup();
46f708ea
MG
406
407 // Tag 'cat' was now deleted too.
408 $this->assertFalse($DB->record_exists('tag', array('name' => 'cat')));
409
410 // Assign tag to non-existing record. Make sure tag was created in the DB.
c026a28d 411 core_tag_tag::set_item_tags('core', 'course', 1231231, context_system::instance(), array('bird'));
46f708ea
MG
412 $this->assertTrue($DB->record_exists('tag', array('name' => 'bird')));
413
c026a28d 414 $task->cleanup();
46f708ea
MG
415
416 // Tag 'bird' was now deleted because the related record does not exist in the DB.
417 $this->assertFalse($DB->record_exists('tag', array('name' => 'bird')));
418
419 // Now we have a tag instance pointing on 'sometag' tag.
420 $user = $this->getDataGenerator()->create_user();
c026a28d
MG
421 core_tag_tag::set_item_tags('core', 'user', $user->id, context_user::instance($user->id), array('sometag'));
422 $sometag = core_tag_tag::get_by_name($defaultcoll, 'sometag');
46f708ea
MG
423
424 $this->assertTrue($DB->record_exists('tag_instance', array('tagid' => $sometag->id)));
425
426 // Some hacker removes the tag without using API.
427 $DB->delete_records('tag', array('id' => $sometag->id));
428
c026a28d 429 $task->cleanup();
46f708ea
MG
430
431 // The tag instances were also removed.
432 $this->assertFalse($DB->record_exists('tag_instance', array('tagid' => $sometag->id)));
433 }
c026a28d
MG
434
435 public function test_guess_tag() {
436 global $DB;
437 $user = $this->getDataGenerator()->create_user();
438 $this->setUser($user);
439 $tag1 = $this->getDataGenerator()->create_tag(array('name' => 'Cat'));
440 $tc = core_tag_collection::create((object)array('name' => 'tagcoll'));
441 $tag2 = $this->getDataGenerator()->create_tag(array('name' => 'Cat', 'tagcollid' => $tc->id));
442 $this->assertEquals(2, count($DB->get_records('tag')));
443 $this->assertEquals(2, count(core_tag_tag::guess_by_name('Cat')));
444 $this->assertEquals(core_tag_collection::get_default(), core_tag_tag::get_by_name(0, 'Cat')->tagcollid);
445 }
446
447 public function test_instances() {
448 global $DB;
449 $user = $this->getDataGenerator()->create_user();
450 $this->setUser($user);
451
452 // Create a course to tag.
453 $course = $this->getDataGenerator()->create_course();
454 $context = context_course::instance($course->id);
455
456 $initialtagscount = $DB->count_records('tag');
457
458 core_tag_tag::set_item_tags('core', 'course', $course->id, $context, array('Tag 1', 'Tag 2'));
459 $tags = core_tag_tag::get_item_tags('core', 'course', $course->id);
460 $tagssimple = array_values($tags);
461 $this->assertEquals(2, count($tags));
462 $this->assertEquals('Tag 1', $tagssimple[0]->rawname);
463 $this->assertEquals('Tag 2', $tagssimple[1]->rawname);
464 $this->assertEquals($initialtagscount + 2, $DB->count_records('tag'));
465
466 core_tag_tag::set_item_tags('core', 'course', $course->id, $context, array('Tag 3', 'Tag 2', 'Tag 1'));
467 $tags = core_tag_tag::get_item_tags('core', 'course', $course->id);
468 $tagssimple = array_values($tags);
469 $this->assertEquals(3, count($tags));
470 $this->assertEquals('Tag 3', $tagssimple[0]->rawname);
471 $this->assertEquals('Tag 2', $tagssimple[1]->rawname);
472 $this->assertEquals('Tag 1', $tagssimple[2]->rawname);
473 $this->assertEquals($initialtagscount + 3, $DB->count_records('tag'));
474
475 core_tag_tag::set_item_tags('core', 'course', $course->id, $context, array('Tag 3'));
476 $tags = core_tag_tag::get_item_tags('core', 'course', $course->id);
477 $tagssimple = array_values($tags);
478 $this->assertEquals(1, count($tags));
479 $this->assertEquals('Tag 3', $tagssimple[0]->rawname);
480
481 // Make sure the unused tags were removed from tag table.
482 $this->assertEquals($initialtagscount + 1, $DB->count_records('tag'));
483 }
484
485 public function test_related_tags() {
486 global $DB;
487 $user = $this->getDataGenerator()->create_user();
488 $this->setUser($user);
489 $tagcollid = core_tag_collection::get_default();
490 $tag = $this->getDataGenerator()->create_tag(array('$tagcollid' => $tagcollid, 'rawname' => 'My tag'));
491 $tag = core_tag_tag::get($tag->id, '*');
492
493 $tag->set_related_tags(array('Synonym 1', 'Synonym 2'));
494 $relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $tag->id));
495 $this->assertEquals(2, count($relatedtags));
496 $this->assertEquals('Synonym 1', $relatedtags[0]->rawname);
497 $this->assertEquals('Synonym 2', $relatedtags[1]->rawname);
498
499 $t1 = core_tag_tag::get_by_name($tagcollid, 'Synonym 1', '*');
500 $relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $t1->id));
501 $this->assertEquals(1, count($relatedtags));
502 $this->assertEquals('My tag', $relatedtags[0]->rawname);
503
504 $t2 = core_tag_tag::get_by_name($tagcollid, 'Synonym 2', '*');
505 $relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $t2->id));
506 $this->assertEquals(1, count($relatedtags));
507 $this->assertEquals('My tag', $relatedtags[0]->rawname);
508
509 $tag->set_related_tags(array('Synonym 3', 'Synonym 2', 'Synonym 1'));
510 $relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $tag->id));
511 $this->assertEquals(3, count($relatedtags));
512 $this->assertEquals('Synonym 1', $relatedtags[0]->rawname);
513 $this->assertEquals('Synonym 2', $relatedtags[1]->rawname);
514 $this->assertEquals('Synonym 3', $relatedtags[2]->rawname);
515
516 $t3 = core_tag_tag::get_by_name($tagcollid, 'Synonym 3', '*');
517 $relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $t3->id));
518 $this->assertEquals(1, count($relatedtags));
519 $this->assertEquals('My tag', $relatedtags[0]->rawname);
520
521 $tag->set_related_tags(array('Synonym 3', 'Synonym 2'));
522 $relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $tag->id));
523 $this->assertEquals(2, count($relatedtags));
524 $this->assertEquals('Synonym 2', $relatedtags[0]->rawname);
525 $this->assertEquals('Synonym 3', $relatedtags[1]->rawname);
526
527 // Assert "Synonym 1" no longer links but is still present (will be removed by cron).
528 $relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $t1->id));
529 $this->assertEquals(0, count($relatedtags));
530 }
531
532 /**
533 * Very basic test for create/move/update/delete actions, without any itemtype movements.
534 */
535 public function test_tag_coll_basic() {
536 global $DB;
537
538 // Make sure there is one and only one tag coll that is marked as default.
539 $tagcolls = core_tag_collection::get_collections();
540 $this->assertEquals(1, count($DB->get_records('tag_coll', array('isdefault' => 1))));
541 $defaulttagcoll = core_tag_collection::get_default();
542
543 // Create a new tag coll to store user tags and something else.
544 $data = (object)array('name' => 'new tag coll');
545 $tagcollid1 = core_tag_collection::create($data)->id;
546 $tagcolls = core_tag_collection::get_collections();
547 $this->assertEquals('new tag coll', $tagcolls[$tagcollid1]->name);
548
549 // Create a new tag coll to store post tags.
550 $data = (object)array('name' => 'posts');
551 $tagcollid2 = core_tag_collection::create($data)->id;
552 $tagcolls = core_tag_collection::get_collections();
553 $this->assertEquals('posts', $tagcolls[$tagcollid2]->name);
554 $this->assertEquals($tagcolls[$tagcollid1]->sortorder + 1,
555 $tagcolls[$tagcollid2]->sortorder);
556
557 // Illegal tag colls sortorder changing.
558 $this->assertFalse(core_tag_collection::change_sortorder($tagcolls[$defaulttagcoll], 1));
559 $this->assertFalse(core_tag_collection::change_sortorder($tagcolls[$defaulttagcoll], -1));
560 $this->assertFalse(core_tag_collection::change_sortorder($tagcolls[$tagcollid2], 1));
561
562 // Move the very last tag coll one position up.
563 $this->assertTrue(core_tag_collection::change_sortorder($tagcolls[$tagcollid2], -1));
564 $tagcolls = core_tag_collection::get_collections();
565 $this->assertEquals($tagcolls[$tagcollid2]->sortorder + 1,
566 $tagcolls[$tagcollid1]->sortorder);
567
568 // Move the second last tag coll one position down.
569 $this->assertTrue(core_tag_collection::change_sortorder($tagcolls[$tagcollid2], 1));
570 $tagcolls = core_tag_collection::get_collections();
571 $this->assertEquals($tagcolls[$tagcollid1]->sortorder + 1,
572 $tagcolls[$tagcollid2]->sortorder);
573
574 // Edit tag coll.
575 $this->assertTrue(core_tag_collection::update($tagcolls[$tagcollid2],
576 (object)array('name' => 'posts2')));
577 $tagcolls = core_tag_collection::get_collections();
578 $this->assertEquals('posts2', $tagcolls[$tagcollid2]->name);
579
580 // Delete tag coll.
581 $count = $DB->count_records('tag_coll');
582 $this->assertFalse(core_tag_collection::delete($tagcolls[$defaulttagcoll]));
583 $this->assertTrue(core_tag_collection::delete($tagcolls[$tagcollid1]));
584 $this->assertEquals($count - 1, $DB->count_records('tag_coll'));
585 }
586
587 /**
588 * Prepares environment for test_move_tags_* tests
589 */
590 protected function prepare_move_tags() {
591 global $CFG;
592 require_once($CFG->dirroot.'/blog/locallib.php');
593 $this->setUser($this->getDataGenerator()->create_user());
594
595 $collid1 = core_tag_collection::get_default();
596 $collid2 = core_tag_collection::create(array('name' => 'newcoll'))->id;
597 $user1 = $this->getDataGenerator()->create_user();
598 $user2 = $this->getDataGenerator()->create_user();
599 $blogpost = new blog_entry(null, array('subject' => 'test'), null);
600 $states = blog_entry::get_applicable_publish_states();
601 $blogpost->publishstate = reset($states);
602 $blogpost->add();
603
604 core_tag_tag::set_item_tags('core', 'user', $user1->id, context_user::instance($user1->id),
605 array('Tag1', 'Tag2'));
606 core_tag_tag::set_item_tags('core', 'user', $user2->id, context_user::instance($user2->id),
607 array('Tag2', 'Tag3'));
608 $this->getDataGenerator()->create_tag(array('rawname' => 'Tag4',
e11d7380 609 'tagcollid' => $collid1, 'isstandard' => 1));
c026a28d 610 $this->getDataGenerator()->create_tag(array('rawname' => 'Tag5',
e11d7380 611 'tagcollid' => $collid2, 'isstandard' => 1));
c026a28d
MG
612
613 return array($collid1, $collid2, $user1, $user2, $blogpost);
614 }
615
616 public function test_move_tags_simple() {
617 global $DB;
618 list($collid1, $collid2, $user1, $user2, $blogpost) = $this->prepare_move_tags();
619
620 // Move 'user' area from collection 1 to collection 2, make sure tags were moved completely.
621 $tagarea = $DB->get_record('tag_area', array('itemtype' => 'user', 'component' => 'core'));
622 core_tag_area::update($tagarea, array('tagcollid' => $collid2));
623
1e03d74c
MN
624 $tagsaftermove = $DB->get_records('tag');
625 foreach ($tagsaftermove as $tag) {
626 // Confirm that the time modified has not been unset.
627 $this->assertNotEmpty($tag->timemodified);
628 }
629
c026a28d
MG
630 $this->assertEquals(array('Tag4'),
631 $DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid1)));
632 $this->assertEquals(array('Tag1', 'Tag2', 'Tag3', 'Tag5'),
633 $DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid2)));
634 $this->assertEquals(array('Tag1', 'Tag2'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user1->id)));
635 $this->assertEquals(array('Tag2', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user2->id)));
636 }
637
638 public function test_move_tags_split_tag() {
639 global $DB;
640 list($collid1, $collid2, $user1, $user2, $blogpost) = $this->prepare_move_tags();
641
642 core_tag_tag::set_item_tags('core', 'post', $blogpost->id, context_system::instance(),
643 array('Tag1', 'Tag3'));
644
645 // Move 'user' area from collection 1 to collection 2, make sure tag Tag2 was moved and tags Tag1 and Tag3 were duplicated.
646 $tagareauser = $DB->get_record('tag_area', array('itemtype' => 'user', 'component' => 'core'));
647 core_tag_area::update($tagareauser, array('tagcollid' => $collid2));
648
1e03d74c
MN
649 $tagsaftermove = $DB->get_records('tag');
650 foreach ($tagsaftermove as $tag) {
651 // Confirm that the time modified has not been unset.
652 $this->assertNotEmpty($tag->timemodified);
653 }
654
c026a28d
MG
655 $this->assertEquals(array('Tag1', 'Tag3', 'Tag4'),
656 $DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid1)));
657 $this->assertEquals(array('Tag1', 'Tag2', 'Tag3', 'Tag5'),
658 $DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid2)));
659 $this->assertEquals(array('Tag1', 'Tag2'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user1->id)));
660 $this->assertEquals(array('Tag2', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user2->id)));
661 $this->assertEquals(array('Tag1', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'post', $blogpost->id)));
662 }
663
664 public function test_move_tags_merge_tag() {
665 global $DB;
666 list($collid1, $collid2, $user1, $user2, $blogpost) = $this->prepare_move_tags();
667
668 // Set collection for 'post' tag area to be collection 2 and add some tags there.
669 $tagareablog = $DB->get_record('tag_area', array('itemtype' => 'post', 'component' => 'core'));
670 core_tag_area::update($tagareablog, array('tagcollid' => $collid2));
671
672 core_tag_tag::set_item_tags('core', 'post', $blogpost->id, context_system::instance(),
673 array('TAG1', 'Tag3'));
674
675 // Move 'user' area from collection 1 to collection 2,
676 // make sure tag Tag2 was moved and tags Tag1 and Tag3 were merged into existing.
677 $tagareauser = $DB->get_record('tag_area', array('itemtype' => 'user', 'component' => 'core'));
678 core_tag_area::update($tagareauser, array('tagcollid' => $collid2));
679
1e03d74c
MN
680 $tagsaftermove = $DB->get_records('tag');
681 foreach ($tagsaftermove as $tag) {
682 // Confirm that the time modified has not been unset.
683 $this->assertNotEmpty($tag->timemodified);
684 }
685
c026a28d
MG
686 $this->assertEquals(array('Tag4'),
687 $DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid1)));
688 $this->assertEquals(array('TAG1', 'Tag2', 'Tag3', 'Tag5'),
689 $DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid2)));
690 $this->assertEquals(array('TAG1', 'Tag2'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user1->id)));
691 $this->assertEquals(array('Tag2', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user2->id)));
692 $this->assertEquals(array('TAG1', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'post', $blogpost->id)));
693 }
694
695 public function test_move_tags_with_related() {
696 global $DB;
697 list($collid1, $collid2, $user1, $user2, $blogpost) = $this->prepare_move_tags();
698
699 // Set Tag1 to be related to Tag2 and Tag4 (in collection 1).
700 core_tag_tag::get_by_name($collid1, 'Tag1')->set_related_tags(array('Tag2', 'Tag4'));
701
702 // Set collection for 'post' tag area to be collection 2 and add some tags there.
703 $tagareablog = $DB->get_record('tag_area', array('itemtype' => 'post', 'component' => 'core'));
704 core_tag_area::update($tagareablog, array('tagcollid' => $collid2));
705
706 core_tag_tag::set_item_tags('core', 'post', $blogpost->id, context_system::instance(),
707 array('TAG1', 'Tag3'));
708
709 // Move 'user' area from collection 1 to collection 2, make sure tags were moved completely.
710 $tagarea = $DB->get_record('tag_area', array('itemtype' => 'user', 'component' => 'core'));
711 core_tag_area::update($tagarea, array('tagcollid' => $collid2));
712
1e03d74c
MN
713 $tagsaftermove = $DB->get_records('tag');
714 foreach ($tagsaftermove as $tag) {
715 // Confirm that the time modified has not been unset.
716 $this->assertNotEmpty($tag->timemodified);
717 }
718
c026a28d
MG
719 $this->assertEquals(array('Tag1', 'Tag2', 'Tag4'),
720 $DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid1)));
721 $this->assertEquals(array('TAG1', 'Tag2', 'Tag3', 'Tag4', 'Tag5'),
722 $DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid2)));
723 $this->assertEquals(array('TAG1', 'Tag2'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user1->id)));
724 $this->assertEquals(array('Tag2', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user2->id)));
725
726 $tag11 = core_tag_tag::get_by_name($collid1, 'Tag1');
5ab88243 727 $related11 = core_tag_tag::get($tag11->id)->get_manual_related_tags();
c026a28d
MG
728 $related11 = array_map('core_tag_tag::make_display_name', $related11);
729 sort($related11); // Order of related tags may be random.
730 $this->assertEquals('Tag2, Tag4', join(', ', $related11));
731
732 $tag21 = core_tag_tag::get_by_name($collid2, 'TAG1');
5ab88243 733 $related21 = core_tag_tag::get($tag21->id)->get_manual_related_tags();
c026a28d
MG
734 $related21 = array_map('core_tag_tag::make_display_name', $related21);
735 sort($related21); // Order of related tags may be random.
736 $this->assertEquals('Tag2, Tag4', join(', ', $related21));
737 }
738
71da0356
MG
739 public function test_move_tags_corrupted() {
740 global $DB;
741 list($collid1, $collid2, $user1, $user2, $blogpost) = $this->prepare_move_tags();
742 $collid3 = core_tag_collection::create(array('name' => 'weirdcoll'))->id;
743
744 // We already have Tag1 in coll1, now let's create it in coll3.
745 $extratag1 = $this->getDataGenerator()->create_tag(array('rawname' => 'Tag1',
e11d7380 746 'tagcollid' => $collid3, 'isstandard' => 1));
71da0356
MG
747
748 // Artificially add 'Tag1' from coll3 to user2.
749 $DB->insert_record('tag_instance', array('tagid' => $extratag1->id, 'itemtype' => 'user',
750 'component' => 'core', 'itemid' => $user2->id, 'ordering' => 3));
751
752 // Now we have corrupted data: both users are tagged with 'Tag1', however these are two tags in different collections.
753 $user1tags = array_values(core_tag_tag::get_item_tags('core', 'user', $user1->id));
754 $user2tags = array_values(core_tag_tag::get_item_tags('core', 'user', $user2->id));
755 $this->assertEquals('Tag1', $user1tags[0]->rawname);
756 $this->assertEquals('Tag1', $user2tags[2]->rawname);
757 $this->assertNotEquals($user1tags[0]->tagcollid, $user2tags[2]->tagcollid);
758
759 // Move user interests tag area into coll2.
760 $tagarea = $DB->get_record('tag_area', array('itemtype' => 'user', 'component' => 'core'));
761 core_tag_area::update($tagarea, array('tagcollid' => $collid2));
762
1e03d74c
MN
763 $tagsaftermove = $DB->get_records('tag');
764 foreach ($tagsaftermove as $tag) {
765 // Confirm that the time modified has not been unset.
766 $this->assertNotEmpty($tag->timemodified);
767 }
768
71da0356
MG
769 // Now all tags are correctly moved to the new collection and both tags 'Tag1' were merged.
770 $user1tags = array_values(core_tag_tag::get_item_tags('core', 'user', $user1->id));
771 $user2tags = array_values(core_tag_tag::get_item_tags('core', 'user', $user2->id));
772 $this->assertEquals('Tag1', $user1tags[0]->rawname);
773 $this->assertEquals('Tag1', $user2tags[2]->rawname);
774 $this->assertEquals($collid2, $user1tags[0]->tagcollid);
775 $this->assertEquals($collid2, $user2tags[2]->tagcollid);
776 }
777
5ab88243
MG
778 /**
779 * Tests that tag_normalize function throws an exception.
780 * This function was deprecated in 3.1
781 */
c026a28d 782 public function test_normalize() {
5ab88243
MG
783 $this->expectException('coding_exception');
784 $this->expectExceptionMessage('tag_normalize() can not be used anymore. Please use ' .
785 'core_tag_tag::normalize().');
786 tag_normalize();
c026a28d
MG
787 }
788
789 /**
790 * Test functions core_tag_tag::create_if_missing() and core_tag_tag::get_by_name_bulk().
791 */
792 public function test_create_get() {
793 $tagset = array('Cat', ' Dog ', '<Mouse', '<>', 'mouse', 'Dog');
794
795 $collid = core_tag_collection::get_default();
796 $tags = core_tag_tag::create_if_missing($collid, $tagset);
797 $this->assertEquals(array('cat', 'dog', 'mouse'), array_keys($tags));
798 $this->assertEquals('Dog', $tags['dog']->rawname);
799 $this->assertEquals('mouse', $tags['mouse']->rawname); // Case of the last tag wins.
800
801 $tags2 = core_tag_tag::create_if_missing($collid, array('CAT', 'Elephant'));
802 $this->assertEquals(array('cat', 'elephant'), array_keys($tags2));
803 $this->assertEquals('Cat', $tags2['cat']->rawname);
804 $this->assertEquals('Elephant', $tags2['elephant']->rawname);
805 $this->assertEquals($tags['cat']->id, $tags2['cat']->id); // Tag 'cat' already existed and was not created again.
806
807 $tags3 = core_tag_tag::get_by_name_bulk($collid, $tagset);
808 $this->assertEquals(array('cat', 'dog', 'mouse'), array_keys($tags3));
809 $this->assertEquals('Dog', $tags3['dog']->rawname);
810 $this->assertEquals('mouse', $tags3['mouse']->rawname);
811
812 }
0d202789
MG
813
814 /**
815 * Testing function core_tag_tag::combine_tags()
816 */
817 public function test_combine_tags() {
818 $initialtags = array(
819 array('Cat', 'Dog'),
820 array('Dog', 'Cat'),
821 array('Cats', 'Hippo'),
822 array('Hippo', 'Cats'),
823 array('Cat', 'Mouse', 'Kitten'),
824 array('Cats', 'Mouse', 'Kitten'),
825 array('Kitten', 'Mouse', 'Cat'),
826 array('Kitten', 'Mouse', 'Cats'),
827 array('Cats', 'Mouse', 'Kitten'),
828 array('Mouse', 'Hippo')
829 );
830
831 $finaltags = array(
832 array('Cat', 'Dog'),
833 array('Dog', 'Cat'),
834 array('Cat', 'Hippo'),
835 array('Hippo', 'Cat'),
836 array('Cat', 'Mouse'),
837 array('Cat', 'Mouse'),
838 array('Mouse', 'Cat'),
839 array('Mouse', 'Cat'),
840 array('Cat', 'Mouse'),
841 array('Mouse', 'Hippo')
842 );
843
844 $collid = core_tag_collection::get_default();
845 $context = context_system::instance();
846 foreach ($initialtags as $id => $taglist) {
847 core_tag_tag::set_item_tags('core', 'course', $id + 10, $context, $initialtags[$id]);
848 }
849
850 core_tag_tag::get_by_name($collid, 'Cats', '*')->update(array('isstandard' => 1));
851
852 // Combine tags 'Cats' and 'Kitten' into 'Cat'.
853 $cat = core_tag_tag::get_by_name($collid, 'Cat', '*');
854 $cats = core_tag_tag::get_by_name($collid, 'Cats', '*');
855 $kitten = core_tag_tag::get_by_name($collid, 'Kitten', '*');
856 $cat->combine_tags(array($cats, $kitten));
857
858 foreach ($finaltags as $id => $taglist) {
859 $this->assertEquals($taglist,
860 array_values(core_tag_tag::get_item_tags_array('core', 'course', $id + 10)),
861 'Original array ('.join(', ', $initialtags[$id]).')');
862 }
863
864 // Ensure combined tags are deleted and 'Cat' is now official (because 'Cats' was official).
865 $this->assertEmpty(core_tag_tag::get_by_name($collid, 'Cats'));
866 $this->assertEmpty(core_tag_tag::get_by_name($collid, 'Kitten'));
867 $cattag = core_tag_tag::get_by_name($collid, 'Cat', '*');
868 $this->assertEquals(1, $cattag->isstandard);
869 }
870
871 /**
872 * Testing function core_tag_tag::combine_tags() when related tags are present.
873 */
874 public function test_combine_tags_with_related() {
875 $collid = core_tag_collection::get_default();
876 $context = context_system::instance();
877 core_tag_tag::set_item_tags('core', 'course', 10, $context, array('Cat', 'Cats', 'Dog'));
878 core_tag_tag::get_by_name($collid, 'Cat', '*')->set_related_tags(array('Kitty'));
879 core_tag_tag::get_by_name($collid, 'Cats', '*')->set_related_tags(array('Cat', 'Kitten', 'Kitty'));
880
881 // Combine tags 'Cats' into 'Cat'.
882 $cat = core_tag_tag::get_by_name($collid, 'Cat', '*');
883 $cats = core_tag_tag::get_by_name($collid, 'Cats', '*');
884 $cat->combine_tags(array($cats));
885
886 // Ensure 'Cat' is now related to 'Kitten' and 'Kitty' (order of related tags may be random).
887 $relatedtags = array_map(function($t) {return $t->rawname;}, $cat->get_manual_related_tags());
888 sort($relatedtags);
889 $this->assertEquals(array('Kitten', 'Kitty'), array_values($relatedtags));
890 }
891
892 /**
893 * Testing function core_tag_tag::combine_tags() when correlated tags are present.
894 */
895 public function test_combine_tags_with_correlated() {
896 $task = new \core\task\tag_cron_task();
897
898 $tags = $this->prepare_correlated();
899
900 $task->compute_correlations();
901 // Now 'cat' is correlated with 'cats'.
902 // Also 'dog', 'dogs' and 'puppy' are correlated.
903 // There is a manual relation between 'cat' and 'kitten'.
904 // See function test_correlations() for assertions.
905
906 // Combine tags 'dog' and 'kitten' into 'cat' and make sure that cat is now correlated with dogs and puppy.
907 $tags['cat']->combine_tags(array($tags['dog'], $tags['kitten']));
908
909 $correlatedtags = $this->get_correlated_tags_names($tags['cat']);
910 $this->assertEquals(['cats', 'dogs', 'puppy'], $correlatedtags);
911
912 $correlatedtags = $this->get_correlated_tags_names($tags['dogs']);
913 $this->assertEquals(['cat', 'puppy'], $correlatedtags);
914
915 $correlatedtags = $this->get_correlated_tags_names($tags['puppy']);
916 $this->assertEquals(['cat', 'dogs'], $correlatedtags);
917
918 // Add tag that does not have any correlations.
919 $user7 = $this->getDataGenerator()->create_user();
920 core_tag_tag::set_item_tags('core', 'user', $user7->id, context_user::instance($user7->id), array('hippo'));
921 $tags['hippo'] = core_tag_tag::get_by_name(core_tag_collection::get_default(), 'hippo', '*');
922
923 // Combine tag 'cat' into 'hippo'. Now 'hippo' should have the same correlations 'cat' used to have and also
924 // tags 'dogs' and 'puppy' should have 'hippo' in correlations.
925 $tags['hippo']->combine_tags(array($tags['cat']));
926
927 $correlatedtags = $this->get_correlated_tags_names($tags['hippo']);
928 $this->assertEquals(['cats', 'dogs', 'puppy'], $correlatedtags);
929
930 $correlatedtags = $this->get_correlated_tags_names($tags['dogs']);
931 $this->assertEquals(['hippo', 'puppy'], $correlatedtags);
932
933 $correlatedtags = $this->get_correlated_tags_names($tags['puppy']);
934 $this->assertEquals(['dogs', 'hippo'], $correlatedtags);
935 }
936
4c72d4be
RW
937 /**
938 * get_tags_by_area_in_contexts should return an empty array if there
939 * are no tag instances for the area in the given context.
940 */
941 public function test_get_tags_by_area_in_contexts_empty() {
942 $tagnames = ['foo'];
943 $collid = core_tag_collection::get_default();
944 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
945 $user = $this->getDataGenerator()->create_user();
946 $context = context_user::instance($user->id);
947 $component = 'core';
948 $itemtype = 'user';
949
950 $result = core_tag_tag::get_tags_by_area_in_contexts($component, $itemtype, [$context]);
951 $this->assertEmpty($result);
952 }
953
954 /**
955 * get_tags_by_area_in_contexts should return an array of tags that
956 * have instances in the given context even when there is only a single
957 * instance.
958 */
959 public function test_get_tags_by_area_in_contexts_single_tag_one_context() {
960 $tagnames = ['foo'];
961 $collid = core_tag_collection::get_default();
962 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
963 $user = $this->getDataGenerator()->create_user();
964 $context = context_user::instance($user->id);
965 $component = 'core';
966 $itemtype = 'user';
967 core_tag_tag::set_item_tags($component, $itemtype, $user->id, $context, $tagnames);
968
969 $result = core_tag_tag::get_tags_by_area_in_contexts($component, $itemtype, [$context]);
970 $expected = array_map(function($t) {
971 return $t->id;
972 }, $tags);
973 $actual = array_map(function($t) {
974 return $t->id;
975 }, $result);
976
977 sort($expected);
978 sort($actual);
979
980 $this->assertEquals($expected, $actual);
981 }
982
983 /**
984 * get_tags_by_area_in_contexts should return all tags in an array
985 * that have tag instances in for the area in the given context and
986 * should ignore all tags that don't have an instance.
987 */
988 public function test_get_tags_by_area_in_contexts_multiple_tags_one_context() {
989 $tagnames = ['foo', 'bar', 'baz'];
990 $collid = core_tag_collection::get_default();
991 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
992 $user = $this->getDataGenerator()->create_user();
993 $context = context_user::instance($user->id);
994 $component = 'core';
995 $itemtype = 'user';
996 core_tag_tag::set_item_tags($component, $itemtype, $user->id, $context, array_slice($tagnames, 0, 2));
997
998 $result = core_tag_tag::get_tags_by_area_in_contexts($component, $itemtype, [$context]);
999 $expected = ['foo', 'bar'];
1000 $actual = array_map(function($t) {
1001 return $t->name;
1002 }, $result);
1003
1004 sort($expected);
1005 sort($actual);
1006
1007 $this->assertEquals($expected, $actual);
1008 }
1009
1010 /**
1011 * get_tags_by_area_in_contexts should return the unique set of
1012 * tags for a area in the given contexts. Multiple tag instances of
1013 * the same tag don't result in duplicates in the result set.
1014 *
1015 * Tags with tag instances in the same area with in difference contexts
1016 * should be ignored.
1017 */
1018 public function test_get_tags_by_area_in_contexts_multiple_tags_multiple_contexts() {
1019 $tagnames = ['foo', 'bar', 'baz', 'bop', 'bam', 'bip'];
1020 $collid = core_tag_collection::get_default();
1021 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1022 $user1 = $this->getDataGenerator()->create_user();
1023 $user2 = $this->getDataGenerator()->create_user();
1024 $user3 = $this->getDataGenerator()->create_user();
1025 $context1 = context_user::instance($user1->id);
1026 $context2 = context_user::instance($user2->id);
1027 $context3 = context_user::instance($user3->id);
1028 $component = 'core';
1029 $itemtype = 'user';
1030
1031 // User 1 tags: 'foo', 'bar'.
1032 core_tag_tag::set_item_tags($component, $itemtype, $user1->id, $context1, array_slice($tagnames, 0, 2));
1033 // User 2 tags: 'bar', 'baz'.
1034 core_tag_tag::set_item_tags($component, $itemtype, $user2->id, $context2, array_slice($tagnames, 1, 2));
1035 // User 3 tags: 'bop', 'bam'.
1036 core_tag_tag::set_item_tags($component, $itemtype, $user3->id, $context3, array_slice($tagnames, 3, 2));
1037
1038 $result = core_tag_tag::get_tags_by_area_in_contexts($component, $itemtype, [$context1, $context2]);
1039 // Both User 1 and 2 have tagged using 'bar' but we don't
1040 // expect duplicate tags in the result since they are the same
1041 // tag.
1042 //
1043 // User 3 has tagged 'bop' and 'bam' but we aren't searching in
1044 // that context so they shouldn't be in the results.
1045 $expected = ['foo', 'bar', 'baz'];
1046 $actual = array_map(function($t) {
1047 return $t->name;
1048 }, $result);
1049
1050 sort($expected);
1051 sort($actual);
1052
1053 $this->assertEquals($expected, $actual);
1054 }
1055
4c3f4d2c
RW
1056 /**
1057 * get_items_tags should return an empty array if the tag area is disabled.
1058 */
1059 public function test_get_items_tags_disabled_component() {
1060 global $CFG;
1061
1062 $user1 = $this->getDataGenerator()->create_user();
1063 $context1 = context_user::instance($user1->id);
1064 $component = 'core';
1065 $itemtype = 'user';
1066 $itemids = [$user1->id];
1067
1068 // User 1 tags: 'foo', 'bar'.
1069 core_tag_tag::set_item_tags($component, $itemtype, $user1->id, $context1, ['foo']);
1070 // This mimics disabling tags for a component.
1071 $CFG->usetags = false;
1072 $result = core_tag_tag::get_items_tags($component, $itemtype, $itemids);
1073 $this->assertEmpty($result);
1074 }
1075
1076 /**
1077 * get_items_tags should return an empty array if the tag item ids list
1078 * is empty.
1079 */
1080 public function test_get_items_tags_empty_itemids() {
1081 $user1 = $this->getDataGenerator()->create_user();
1082 $context1 = context_user::instance($user1->id);
1083 $component = 'core';
1084 $itemtype = 'user';
1085
1086 // User 1 tags: 'foo', 'bar'.
1087 core_tag_tag::set_item_tags($component, $itemtype, $user1->id, $context1, ['foo']);
1088 $result = core_tag_tag::get_items_tags($component, $itemtype, []);
1089 $this->assertEmpty($result);
1090 }
1091
1092 /**
1093 * get_items_tags should return an array indexed by the item ids with empty
1094 * arrays as the values when the component or itemtype is unknown.
1095 */
1096 public function test_get_items_tags_unknown_component_itemtype() {
1097 $itemids = [1, 2, 3];
1098 $result = core_tag_tag::get_items_tags('someunknowncomponent', 'user', $itemids);
1099 foreach ($itemids as $itemid) {
1100 // Unknown component should return an array indexed by the item ids
1101 // with empty arrays as the values.
1102 $this->assertEmpty($result[$itemid]);
1103 }
1104
1105 $result = core_tag_tag::get_items_tags('core', 'someunknownitemtype', $itemids);
1106 foreach ($itemids as $itemid) {
1107 // Unknown item type should return an array indexed by the item ids
1108 // with empty arrays as the values.
1109 $this->assertEmpty($result[$itemid]);
1110 }
1111 }
1112
1113 /**
1114 * get_items_tags should return an array indexed by the item ids with empty
1115 * arrays as the values for any item ids that don't have tag instances.
1116 *
1117 * Data setup:
1118 * Users: 1, 2, 3
1119 * Tags: user 1 = ['foo', 'bar']
1120 * user 2 = ['baz', 'bop']
1121 * user 3 = []
1122 *
1123 * Expected result:
1124 * [
1125 * 1 => [
1126 * 1 => 'foo',
1127 * 2 => 'bar'
1128 * ],
1129 * 2 => [
1130 * 3 => 'baz',
1131 * 4 => 'bop'
1132 * ],
1133 * 3 => []
1134 * ]
1135 */
1136 public function test_get_items_tags_missing_itemids() {
1137 $user1 = $this->getDataGenerator()->create_user();
1138 $user2 = $this->getDataGenerator()->create_user();
1139 $user3 = $this->getDataGenerator()->create_user();
1140 $context1 = context_user::instance($user1->id);
1141 $context2 = context_user::instance($user2->id);
1142 $component = 'core';
1143 $itemtype = 'user';
1144 $itemids = [$user1->id, $user2->id, $user3->id];
1145 $expecteduser1tagnames = ['foo', 'bar'];
1146 $expecteduser2tagnames = ['baz', 'bop'];
1147 $expecteduser3tagnames = [];
1148
1149 // User 1 tags: 'foo', 'bar'.
1150 core_tag_tag::set_item_tags($component, $itemtype, $user1->id, $context1, $expecteduser1tagnames);
1151 // User 2 tags: 'bar', 'baz'.
1152 core_tag_tag::set_item_tags($component, $itemtype, $user2->id, $context2, $expecteduser2tagnames);
1153
1154 $result = core_tag_tag::get_items_tags($component, $itemtype, $itemids);
1155 $actualuser1tagnames = array_map(function($taginstance) {
1156 return $taginstance->name;
1157 }, $result[$user1->id]);
1158 $actualuser2tagnames = array_map(function($taginstance) {
1159 return $taginstance->name;
1160 }, $result[$user2->id]);
1161 $actualuser3tagnames = $result[$user3->id];
1162
1163 sort($expecteduser1tagnames);
1164 sort($expecteduser2tagnames);
1165 sort($actualuser1tagnames);
1166 sort($actualuser2tagnames);
1167
1168 $this->assertEquals($expecteduser1tagnames, $actualuser1tagnames);
1169 $this->assertEquals($expecteduser2tagnames, $actualuser2tagnames);
1170 $this->assertEquals($expecteduser3tagnames, $actualuser3tagnames);
1171 }
1172
4823de02
RW
1173 /**
1174 * set_item_tags should remove any tags that aren't in the given list and should
1175 * add any instances that are missing.
1176 */
1177 public function test_set_item_tags_no_multiple_context_add_remove_instances() {
1178 $tagnames = ['foo', 'bar', 'baz', 'bop'];
1179 $collid = core_tag_collection::get_default();
1180 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1181 $user1 = $this->getDataGenerator()->create_user();
1182 $context = context_user::instance($user1->id);
1183 $component = 'core';
1184 $itemtype = 'user';
1185 $itemid = 1;
1186 $tagareas = core_tag_area::get_areas();
1187 $tagarea = $tagareas[$itemtype][$component];
1188 $newtagnames = ['bar', 'baz', 'bop'];
1189
1190 // Make sure the tag area doesn't allow multiple contexts.
1191 core_tag_area::update($tagarea, ['multiplecontexts' => false]);
1192
1193 // Create tag instances in separate contexts.
1194 $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context);
1195 $this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context);
1196
1197 core_tag_tag::set_item_tags($component, $itemtype, $itemid, $context, $newtagnames);
1198
1199 $result = core_tag_tag::get_item_tags($component, $itemtype, $itemid);
1200 $actualtagnames = array_map(function($record) {
1201 return $record->name;
1202 }, $result);
1203
1204 sort($newtagnames);
1205 sort($actualtagnames);
1206
1207 // The list of tags should match the $newtagnames which means 'foo'
1208 // should have been removed while 'baz' and 'bop' were added. 'bar'
1209 // should remain as it was in the new list of tags.
1210 $this->assertEquals($newtagnames, $actualtagnames);
1211 }
1212
1213 /**
1214 * set_item_tags should set all of the tag instance context ids to the given
1215 * context if the tag area for the items doesn't allow multiple contexts for
1216 * the tag instances.
1217 */
1218 public function test_set_item_tags_no_multiple_context_updates_context_of_instances() {
1219 $tagnames = ['foo', 'bar'];
1220 $collid = core_tag_collection::get_default();
1221 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1222 $user1 = $this->getDataGenerator()->create_user();
1223 $user2 = $this->getDataGenerator()->create_user();
1224 $context1 = context_user::instance($user1->id);
1225 $context2 = context_user::instance($user2->id);
1226 $component = 'core';
1227 $itemtype = 'user';
1228 $itemid = 1;
1229 $tagareas = core_tag_area::get_areas();
1230 $tagarea = $tagareas[$itemtype][$component];
1231
1232 // Make sure the tag area doesn't allow multiple contexts.
1233 core_tag_area::update($tagarea, ['multiplecontexts' => false]);
1234
1235 // Create tag instances in separate contexts.
1236 $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);
1237 $this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context2);
1238
1239 core_tag_tag::set_item_tags($component, $itemtype, $itemid, $context1, $tagnames);
1240
1241 $result = core_tag_tag::get_item_tags($component, $itemtype, $itemid);
1242 $this->assertCount(count($tagnames), $result);
1243
1244 foreach ($result as $tag) {
1245 // The core user tag area doesn't allow multiple contexts for tag instances
1246 // so set_item_tags should have set all of the tag instance context ids
1247 // to match $context1.
1248 $this->assertEquals($context1->id, $tag->taginstancecontextid);
1249 }
1250 }
1251
1252 /**
1253 * set_item_tags should delete all of the tag instances that don't match
1254 * the new set of tags, regardless of the context that the tag instance
1255 * is in.
1256 */
1257 public function test_set_item_tags_no_multiple_contex_deletes_old_instancest() {
1258 $tagnames = ['foo', 'bar', 'baz', 'bop'];
1259 $collid = core_tag_collection::get_default();
1260 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1261 $user1 = $this->getDataGenerator()->create_user();
1262 $user2 = $this->getDataGenerator()->create_user();
1263 $context1 = context_user::instance($user1->id);
1264 $context2 = context_user::instance($user2->id);
1265 $component = 'core';
1266 $itemtype = 'user';
1267 $itemid = 1;
1268 $expectedtagnames = ['foo', 'baz'];
1269 $tagareas = core_tag_area::get_areas();
1270 $tagarea = $tagareas[$itemtype][$component];
1271
1272 // Make sure the tag area doesn't allow multiple contexts.
1273 core_tag_area::update($tagarea, ['multiplecontexts' => false]);
1274
1275 // Create tag instances in separate contexts.
1276 $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);
1277 $this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context1);
1278 $this->add_tag_instance($tags['baz'], $component, $itemtype, $itemid, $context2);
1279 $this->add_tag_instance($tags['bop'], $component, $itemtype, $itemid, $context2);
1280
1281 core_tag_tag::set_item_tags($component, $itemtype, $itemid, $context1, $expectedtagnames);
1282
1283 $result = core_tag_tag::get_item_tags($component, $itemtype, $itemid);
1284 $actualtagnames = array_map(function($record) {
1285 return $record->name;
1286 }, $result);
1287
1288 sort($expectedtagnames);
1289 sort($actualtagnames);
1290
1291 // The list of tags should match the $expectedtagnames.
1292 $this->assertEquals($expectedtagnames, $actualtagnames);
1293
1294 foreach ($result as $tag) {
1295 // The core user tag area doesn't allow multiple contexts for tag instances
1296 // so set_item_tags should have set all of the tag instance context ids
1297 // to match $context1.
1298 $this->assertEquals($context1->id, $tag->taginstancecontextid);
1299 }
1300 }
1301
1302 /**
1303 * set_item_tags should not change tag instances in a different context to the one
1304 * it's opertating on if the tag area allows instances from multiple contexts.
1305 */
1306 public function test_set_item_tags_allow_multiple_context_doesnt_update_context() {
1307 global $DB;
1308 $tagnames = ['foo', 'bar', 'bop'];
1309 $collid = core_tag_collection::get_default();
1310 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1311 $user1 = $this->getDataGenerator()->create_user();
1312 $user2 = $this->getDataGenerator()->create_user();
1313 $context1 = context_user::instance($user1->id);
1314 $context2 = context_user::instance($user2->id);
1315 $component = 'core';
1316 $itemtype = 'user';
1317 $itemid = 1;
1318 $tagareas = core_tag_area::get_areas();
1319 $tagarea = $tagareas[$itemtype][$component];
1320
1321 // Make sure the tag area allows multiple contexts.
1322 core_tag_area::update($tagarea, ['multiplecontexts' => true]);
1323
1324 // Create tag instances in separate contexts.
1325 $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);
1326 $this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context2);
1327
1328 // Set the list of tags for $context1. This includes a tag that already exists
1329 // in that context and a new tag. There is another tag, 'bar', that exists in a
1330 // different context ($context2) that should be ignored.
1331 core_tag_tag::set_item_tags($component, $itemtype, $itemid, $context1, ['foo', 'bop']);
1332
1333 $result = core_tag_tag::get_item_tags($component, $itemtype, $itemid);
1334 $actualtagnames = array_map(function($record) {
1335 return $record->name;
1336 }, $result);
1337
1338 sort($tagnames);
1339 sort($actualtagnames);
1340 // The list of tags should match the $tagnames.
1341 $this->assertEquals($tagnames, $actualtagnames);
1342
1343 foreach ($result as $tag) {
1344 if ($tag->name == 'bar') {
1345 // The tag instance for 'bar' should have been left untouched
1346 // because it was in a different context.
1347 $this->assertEquals($context2->id, $tag->taginstancecontextid);
1348 } else {
1349 $this->assertEquals($context1->id, $tag->taginstancecontextid);
1350 }
1351 }
1352 }
1353
1354 /**
1355 * set_item_tags should delete all of the tag instances that don't match
1356 * the new set of tags only in the same context if the tag area allows
1357 * multiple contexts.
1358 */
1359 public function test_set_item_tags_allow_multiple_context_deletes_instances_in_same_context() {
1360 $tagnames = ['foo', 'bar', 'baz', 'bop'];
1361 $collid = core_tag_collection::get_default();
1362 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1363 $user1 = $this->getDataGenerator()->create_user();
1364 $user2 = $this->getDataGenerator()->create_user();
1365 $context1 = context_user::instance($user1->id);
1366 $context2 = context_user::instance($user2->id);
1367 $component = 'core';
1368 $itemtype = 'user';
1369 $itemid = 1;
1370 $expectedtagnames = ['foo', 'bar', 'bop'];
1371 $tagareas = core_tag_area::get_areas();
1372 $tagarea = $tagareas[$itemtype][$component];
1373
1374 // Make sure the tag area allows multiple contexts.
1375 core_tag_area::update($tagarea, ['multiplecontexts' => true]);
1376
1377 // Create tag instances in separate contexts.
1378 $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);
1379 $this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context1);
1380 $this->add_tag_instance($tags['baz'], $component, $itemtype, $itemid, $context1);
1381 $this->add_tag_instance($tags['bop'], $component, $itemtype, $itemid, $context2);
1382
1383 core_tag_tag::set_item_tags($component, $itemtype, $itemid, $context1, ['foo', 'bar']);
1384
1385 $result = core_tag_tag::get_item_tags($component, $itemtype, $itemid);
1386 $actualtagnames = array_map(function($record) {
1387 return $record->name;
1388 }, $result);
1389
1390 sort($expectedtagnames);
1391 sort($actualtagnames);
1392
1393 // The list of tags should match the $expectedtagnames, which includes the
1394 // tag 'bop' because it was in a different context to the one being set
1395 // even though it wasn't in the new set of tags.
1396 $this->assertEquals($expectedtagnames, $actualtagnames);
1397 }
1398
1399 /**
1400 * set_item_tags should allow multiple instances of the same tag in different
1401 * contexts if the tag area allows multiple contexts.
1402 */
1403 public function test_set_item_tags_allow_multiple_context_same_tag_multiple_contexts() {
1404 $tagnames = ['foo'];
1405 $collid = core_tag_collection::get_default();
1406 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1407 $user1 = $this->getDataGenerator()->create_user();
1408 $user2 = $this->getDataGenerator()->create_user();
1409 $context1 = context_user::instance($user1->id);
1410 $context2 = context_user::instance($user2->id);
1411 $component = 'core';
1412 $itemtype = 'user';
1413 $itemid = 1;
1414 $expectedtagnames = ['foo', 'bar', 'bop'];
1415 $tagareas = core_tag_area::get_areas();
1416 $tagarea = $tagareas[$itemtype][$component];
1417
1418 // Make sure the tag area allows multiple contexts.
1419 core_tag_area::update($tagarea, ['multiplecontexts' => true]);
1420
1421 // Create first instance of 'foo' in $context1.
1422 $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);
1423
1424 core_tag_tag::set_item_tags($component, $itemtype, $itemid, $context2, ['foo']);
1425
1426 $result = core_tag_tag::get_item_tags($component, $itemtype, $itemid);
1427 $tagsbycontext = array_reduce($result, function($carry, $tag) {
1428 $contextid = $tag->taginstancecontextid;
1429 if (isset($carry[$contextid])) {
1430 $carry[$contextid][] = $tag;
1431 } else {
1432 $carry[$contextid] = [$tag];
1433 }
1434 return $carry;
1435 }, []);
1436
1437 // The result should be two tag instances of 'foo' in each of the
1438 // two contexts, $context1 and $context2.
1439 $this->assertCount(1, $tagsbycontext[$context1->id]);
1440 $this->assertCount(1, $tagsbycontext[$context2->id]);
1441 $this->assertEquals('foo', $tagsbycontext[$context1->id][0]->name);
1442 $this->assertEquals('foo', $tagsbycontext[$context2->id][0]->name);
1443 }
1444
f7c1c97c
RW
1445 /**
1446 * delete_instances_as_record with an empty set of instances should do nothing.
1447 */
1448 public function test_delete_instances_as_record_empty_set() {
1449 $user = $this->getDataGenerator()->create_user();
1450 $context = context_user::instance($user->id);
1451 $component = 'core';
1452 $itemtype = 'user';
1453 $itemid = 1;
1454
1455 core_tag_tag::set_item_tags($component, $itemtype, $itemid, $context, ['foo']);
1456 // This shouldn't error.
1457 core_tag_tag::delete_instances_as_record([]);
1458
1459 $tags = core_tag_tag::get_item_tags($component, $itemtype, $itemid);
1460 // We should still have one tag.
1461 $this->assertCount(1, $tags);
1462 }
1463
1464 /**
1465 * delete_instances_as_record with an instance that doesn't exist should do
1466 * nothing.
1467 */
1468 public function test_delete_instances_as_record_missing_set() {
1469 $tagnames = ['foo'];
1470 $collid = core_tag_collection::get_default();
1471 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1472 $user = $this->getDataGenerator()->create_user();
1473 $context = context_user::instance($user->id);
1474 $component = 'core';
1475 $itemtype = 'user';
1476 $itemid = 1;
1477
1478 $taginstance = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context);
1479 $taginstance->id++;
1480
1481 // Delete an instance that doesn't exist should do nothing.
1482 core_tag_tag::delete_instances_as_record([$taginstance]);
1483
1484 $tags = core_tag_tag::get_item_tags($component, $itemtype, $itemid);
1485 // We should still have one tag.
1486 $this->assertCount(1, $tags);
1487 }
1488
1489 /**
1490 * delete_instances_as_record with a list of all tag instances should
1491 * leave no tags left.
1492 */
1493 public function test_delete_instances_as_record_whole_set() {
1494 $tagnames = ['foo'];
1495 $collid = core_tag_collection::get_default();
1496 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1497 $user = $this->getDataGenerator()->create_user();
1498 $context = context_user::instance($user->id);
1499 $component = 'core';
1500 $itemtype = 'user';
1501 $itemid = 1;
1502
1503 $taginstance = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context);
1504
1505 core_tag_tag::delete_instances_as_record([$taginstance]);
1506
1507 $tags = core_tag_tag::get_item_tags($component, $itemtype, $itemid);
1508 // There should be no tags left.
1509 $this->assertEmpty($tags);
1510 }
1511
1512 /**
1513 * delete_instances_as_record with a list of only some tag instances should
1514 * delete only the given tag instances and leave other tag instances.
1515 */
1516 public function test_delete_instances_as_record_partial_set() {
1517 $tagnames = ['foo', 'bar'];
1518 $collid = core_tag_collection::get_default();
1519 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1520 $user = $this->getDataGenerator()->create_user();
1521 $context = context_user::instance($user->id);
1522 $component = 'core';
1523 $itemtype = 'user';
1524 $itemid = 1;
1525
1526 $taginstance = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context);
1527 $this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context);
1528
1529 core_tag_tag::delete_instances_as_record([$taginstance]);
1530
1531 $tags = core_tag_tag::get_item_tags($component, $itemtype, $itemid);
1532 // We should be left with a single tag, 'bar'.
1533 $this->assertCount(1, $tags);
1534 $tag = array_shift($tags);
1535 $this->assertEquals('bar', $tag->name);
1536 }
1537
1538 /**
1539 * delete_instances_by_id with an empty set of ids should do nothing.
1540 */
1541 public function test_delete_instances_by_id_empty_set() {
1542 $user = $this->getDataGenerator()->create_user();
1543 $context = context_user::instance($user->id);
1544 $component = 'core';
1545 $itemtype = 'user';
1546 $itemid = 1;
1547
1548 core_tag_tag::set_item_tags($component, $itemtype, $itemid, $context, ['foo']);
1549 // This shouldn't error.
1550 core_tag_tag::delete_instances_by_id([]);
1551
1552 $tags = core_tag_tag::get_item_tags($component, $itemtype, $itemid);
1553 // We should still have one tag.
1554 $this->assertCount(1, $tags);
1555 }
1556
1557 /**
1558 * delete_instances_by_id with an id that doesn't exist should do
1559 * nothing.
1560 */
1561 public function test_delete_instances_by_id_missing_set() {
1562 $tagnames = ['foo'];
1563 $collid = core_tag_collection::get_default();
1564 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1565 $user = $this->getDataGenerator()->create_user();
1566 $context = context_user::instance($user->id);
1567 $component = 'core';
1568 $itemtype = 'user';
1569 $itemid = 1;
1570
1571 $taginstance = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context);
1572
1573 // Delete an instance that doesn't exist should do nothing.
1574 core_tag_tag::delete_instances_by_id([$taginstance->id + 1]);
1575
1576 $tags = core_tag_tag::get_item_tags($component, $itemtype, $itemid);
1577 // We should still have one tag.
1578 $this->assertCount(1, $tags);
1579 }
1580
1581 /**
1582 * delete_instances_by_id with a list of all tag instance ids should
1583 * leave no tags left.
1584 */
1585 public function test_delete_instances_by_id_whole_set() {
1586 $tagnames = ['foo'];
1587 $collid = core_tag_collection::get_default();
1588 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1589 $user = $this->getDataGenerator()->create_user();
1590 $context = context_user::instance($user->id);
1591 $component = 'core';
1592 $itemtype = 'user';
1593 $itemid = 1;
1594
1595 $taginstance = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context);
1596
1597 core_tag_tag::delete_instances_by_id([$taginstance->id]);
1598
1599 $tags = core_tag_tag::get_item_tags($component, $itemtype, $itemid);
1600 // There should be no tags left.
1601 $this->assertEmpty($tags);
1602 }
1603
1604 /**
1605 * delete_instances_by_id with a list of only some tag instance ids should
1606 * delete only the given tag instance ids and leave other tag instances.
1607 */
1608 public function test_delete_instances_by_id_partial_set() {
1609 $tagnames = ['foo', 'bar'];
1610 $collid = core_tag_collection::get_default();
1611 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1612 $user = $this->getDataGenerator()->create_user();
1613 $context = context_user::instance($user->id);
1614 $component = 'core';
1615 $itemtype = 'user';
1616 $itemid = 1;
1617
1618 $taginstance = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context);
1619 $this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context);
1620
1621 core_tag_tag::delete_instances_by_id([$taginstance->id]);
1622
1623 $tags = core_tag_tag::get_item_tags($component, $itemtype, $itemid);
1624 // We should be left with a single tag, 'bar'.
1625 $this->assertCount(1, $tags);
1626 $tag = array_shift($tags);
1627 $this->assertEquals('bar', $tag->name);
1628 }
1629
1630 /**
1631 * delete_instances should delete all tag instances for a component if given
1632 * only the component as a parameter.
1633 */
1634 public function test_delete_instances_with_component() {
1635 global $DB;
1636
1637 $tagnames = ['foo', 'bar'];
1638 $collid = core_tag_collection::get_default();
1639 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1640 $user = $this->getDataGenerator()->create_user();
1641 $context = context_user::instance($user->id);
1642 $component = 'core';
1643 $itemtype1 = 'user';
1644 $itemtype2 = 'course';
1645 $itemid = 1;
1646
1647 // Add 2 tag instances in the same $component but with different item types.
1648 $this->add_tag_instance($tags['foo'], $component, $itemtype1, $itemid, $context);
1649 $this->add_tag_instance($tags['bar'], $component, $itemtype2, $itemid, $context);
1650
1651 // Delete all tag instances for the component.
1652 core_tag_tag::delete_instances($component);
1653
1654 $taginstances = $DB->get_records_sql('SELECT * FROM {tag_instance} WHERE component = ?', [$component]);
1655 // Both tag instances from the $component should have been deleted even though
1656 // they are in different item types.
1657 $this->assertEmpty($taginstances);
1658 }
1659
1660 /**
1661 * delete_instances should delete all tag instances for a component if given
1662 * only the component as a parameter.
1663 */
1664 public function test_delete_instances_with_component_and_itemtype() {
1665 global $DB;
1666
1667 $tagnames = ['foo', 'bar'];
1668 $collid = core_tag_collection::get_default();
1669 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1670 $user = $this->getDataGenerator()->create_user();
1671 $context = context_user::instance($user->id);
1672 $component = 'core';
1673 $itemtype1 = 'user';
1674 $itemtype2 = 'course';
1675 $itemid = 1;
1676
1677 // Add 2 tag instances in the same $component but with different item types.
1678 $this->add_tag_instance($tags['foo'], $component, $itemtype1, $itemid, $context);
1679 $this->add_tag_instance($tags['bar'], $component, $itemtype2, $itemid, $context);
1680
1681 // Delete all tag instances for the component and itemtype.
1682 core_tag_tag::delete_instances($component, $itemtype1);
1683
1684 $taginstances = $DB->get_records_sql('SELECT * FROM {tag_instance} WHERE component = ?', [$component]);
1685 // Only the tag instances for $itemtype1 should have been deleted. We
1686 // should still be left with the instance for 'bar'.
1687 $this->assertCount(1, $taginstances);
1688 $taginstance = array_shift($taginstances);
1689 $this->assertEquals($itemtype2, $taginstance->itemtype);
1690 $this->assertEquals($tags['bar']->id, $taginstance->tagid);
1691 }
1692
1693 /**
1694 * delete_instances should delete all tag instances for a component in a context
1695 * if given both the component and context id as parameters.
1696 */
1697 public function test_delete_instances_with_component_and_context() {
1698 global $DB;
1699
1700 $tagnames = ['foo', 'bar', 'baz'];
1701 $collid = core_tag_collection::get_default();
1702 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1703 $user1 = $this->getDataGenerator()->create_user();
1704 $user2 = $this->getDataGenerator()->create_user();
1705 $context1 = context_user::instance($user1->id);
1706 $context2 = context_user::instance($user2->id);
1707 $component = 'core';
1708 $itemtype1 = 'user';
1709 $itemtype2 = 'course';
1710 $itemid = 1;
1711
1712 // Add 3 tag instances in the same $component but with different contexts.
1713 $this->add_tag_instance($tags['foo'], $component, $itemtype1, $itemid, $context1);
1714 $this->add_tag_instance($tags['bar'], $component, $itemtype2, $itemid, $context1);
1715 $this->add_tag_instance($tags['baz'], $component, $itemtype2, $itemid, $context2);
1716
1717 // Delete all tag instances for the component and context.
1718 core_tag_tag::delete_instances($component, null, $context1->id);
1719
1720 $taginstances = $DB->get_records_sql('SELECT * FROM {tag_instance} WHERE component = ?', [$component]);
1721 // Only the tag instances for $context1 should have been deleted. We
1722 // should still be left with the instance for 'baz'.
1723 $this->assertCount(1, $taginstances);
1724 $taginstance = array_shift($taginstances);
1725 $this->assertEquals($context2->id, $taginstance->contextid);
1726 $this->assertEquals($tags['baz']->id, $taginstance->tagid);
1727 }
1728
1729 /**
1730 * delete_instances should delete all tag instances for a component, item type
1731 * and context if given the component, itemtype, and context id as parameters.
1732 */
1733 public function test_delete_instances_with_component_and_itemtype_and_context() {
1734 global $DB;
1735
1736 $tagnames = ['foo', 'bar', 'baz'];
1737 $collid = core_tag_collection::get_default();
1738 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1739 $user1 = $this->getDataGenerator()->create_user();
1740 $user2 = $this->getDataGenerator()->create_user();
1741 $context1 = context_user::instance($user1->id);
1742 $context2 = context_user::instance($user2->id);
1743 $component = 'core';
1744 $itemtype1 = 'user';
1745 $itemtype2 = 'course';
1746 $itemid = 1;
1747
1748 // Add 3 tag instances in the same $component but with different contexts.
1749 $this->add_tag_instance($tags['foo'], $component, $itemtype1, $itemid, $context1);
1750 $this->add_tag_instance($tags['bar'], $component, $itemtype2, $itemid, $context1);
1751 $this->add_tag_instance($tags['baz'], $component, $itemtype2, $itemid, $context2);
1752
1753 // Delete all tag instances for the component and context.
1754 core_tag_tag::delete_instances($component, $itemtype2, $context1->id);
1755
1756 $taginstances = $DB->get_records_sql('SELECT * FROM {tag_instance} WHERE component = ?', [$component]);
1757 // Only the tag instances for $itemtype2 in $context1 should have been
1758 // deleted. We should still be left with the instance for 'foo' and 'baz'.
1759 $this->assertCount(2, $taginstances);
1760 $fooinstances = array_filter($taginstances, function($instance) use ($tags) {
1761 return $instance->tagid == $tags['foo']->id;
1762 });
1763 $fooinstance = array_shift($fooinstances);
1764 $bazinstances = array_filter($taginstances, function($instance) use ($tags) {
1765 return $instance->tagid == $tags['baz']->id;
1766 });
1767 $bazinstance = array_shift($bazinstances);
1768 $this->assertNotEmpty($fooinstance);
1769 $this->assertNotEmpty($bazinstance);
1770 $this->assertEquals($context1->id, $fooinstance->contextid);
1771 $this->assertEquals($context2->id, $bazinstance->contextid);
1772 }
1773
49374833
RW
1774 /**
1775 * change_instances_context should not change any existing instance contexts
1776 * if not given any instance ids.
1777 */
1778 public function test_change_instances_context_empty_set() {
1779 global $DB;
1780
1781 $tagnames = ['foo'];
1782 $collid = core_tag_collection::get_default();
1783 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1784 $user1 = $this->getDataGenerator()->create_user();
1785 $user2 = $this->getDataGenerator()->create_user();
1786 $context1 = context_user::instance($user1->id);
1787 $context2 = context_user::instance($user2->id);
1788 $component = 'core';
1789 $itemtype = 'user';
1790 $itemid = 1;
1791
1792 $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);
1793
1794 core_tag_tag::change_instances_context([], $context2);
1795
1796 $taginstances = $DB->get_records_sql('SELECT * FROM {tag_instance}');
1797 // The existing tag instance should not have changed.
1798 $this->assertCount(1, $taginstances);
1799 $taginstance = array_shift($taginstances);
1800 $this->assertEquals($context1->id, $taginstance->contextid);
1801 }
1802
1803 /**
1804 * change_instances_context should only change the context of the given ids.
1805 */
1806 public function test_change_instances_context_partial_set() {
1807 global $DB;
1808
1809 $tagnames = ['foo', 'bar'];
1810 $collid = core_tag_collection::get_default();
1811 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1812 $user1 = $this->getDataGenerator()->create_user();
1813 $user2 = $this->getDataGenerator()->create_user();
1814 $context1 = context_user::instance($user1->id);
1815 $context2 = context_user::instance($user2->id);
1816 $component = 'core';
1817 $itemtype = 'user';
1818 $itemid = 1;
1819
1820 $fooinstance = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);
1821 $barinstance = $this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context1);
1822
1823 core_tag_tag::change_instances_context([$fooinstance->id], $context2);
1824
1825 // Reload the record.
1826 $fooinstance = $DB->get_record('tag_instance', ['id' => $fooinstance->id]);
1827 $barinstance = $DB->get_record('tag_instance', ['id' => $barinstance->id]);
1828 // Tag 'foo' context should be updated.
1829 $this->assertEquals($context2->id, $fooinstance->contextid);
1830 // Tag 'bar' context should not be changed.
1831 $this->assertEquals($context1->id, $barinstance->contextid);
1832 }
1833
1834 /**
1835 * change_instances_context should change multiple items from multiple contexts.
1836 */
1837 public function test_change_instances_context_multiple_contexts() {
1838 global $DB;
1839
1840 $tagnames = ['foo', 'bar'];
1841 $collid = core_tag_collection::get_default();
1842 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1843 $user1 = $this->getDataGenerator()->create_user();
1844 $user2 = $this->getDataGenerator()->create_user();
1845 $user3 = $this->getDataGenerator()->create_user();
1846 $context1 = context_user::instance($user1->id);
1847 $context2 = context_user::instance($user2->id);
1848 $context3 = context_user::instance($user3->id);
1849 $component = 'core';
1850 $itemtype = 'user';
1851 $itemid = 1;
1852
1853 // Two instances in different contexts.
1854 $fooinstance = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);
1855 $barinstance = $this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context2);
1856
1857 core_tag_tag::change_instances_context([$fooinstance->id, $barinstance->id], $context3);
1858
1859 // Reload the record.
1860 $fooinstance = $DB->get_record('tag_instance', ['id' => $fooinstance->id]);
1861 $barinstance = $DB->get_record('tag_instance', ['id' => $barinstance->id]);
1862 // Tag 'foo' context should be updated.
1863 $this->assertEquals($context3->id, $fooinstance->contextid);
1864 // Tag 'bar' context should be updated.
1865 $this->assertEquals($context3->id, $barinstance->contextid);
1866 // There shouldn't be any tag instances left in $context1.
1867 $context1records = $DB->get_records('tag_instance', ['contextid' => $context1->id]);
1868 $this->assertEmpty($context1records);
1869 // There shouldn't be any tag instances left in $context2.
1870 $context2records = $DB->get_records('tag_instance', ['contextid' => $context2->id]);
1871 $this->assertEmpty($context2records);
1872 }
1873
1874 /**
1875 * change_instances_context moving an instance from one context into a context
1876 * that already has an instance of that tag should throw an exception.
1877 */
1878 public function test_change_instances_context_conflicting_instances() {
1879 global $DB;
1880
1881 $tagnames = ['foo'];
1882 $collid = core_tag_collection::get_default();
1883 $tags = core_tag_tag::create_if_missing($collid, $tagnames);
1884 $user1 = $this->getDataGenerator()->create_user();
1885 $user2 = $this->getDataGenerator()->create_user();
1886 $context1 = context_user::instance($user1->id);
1887 $context2 = context_user::instance($user2->id);
1888 $component = 'core';
1889 $itemtype = 'user';
1890 $itemid = 1;
1891
1892 // Two instances of 'foo' in different contexts.
1893 $fooinstance1 = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);
1894 $fooinstance2 = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context2);
1895
1896 // There is already an instance of 'foo' in $context2 so the code
1897 // should throw an exception when we try to move another instance there.
1898 $this->expectException('Exception');
1899 core_tag_tag::change_instances_context([$fooinstance1->id], $context2);
1900 }
1901
0d202789
MG
1902 /**
1903 * Help method to return sorted array of names of correlated tags to use for assertions
1904 * @param core_tag $tag
1905 * @return string
1906 */
1907 protected function get_correlated_tags_names($tag) {
1908 $rv = array_map(function($t) {
1909 return $t->rawname;
1910 }, $tag->get_correlated_tags());
1911 sort($rv);
1912 return array_values($rv);
1913 }
4823de02
RW
1914
1915 /**
1916 * Add a tag instance.
1917 *
1918 * @param core_tag_tag $tag
1919 * @param string $component
1920 * @param string $itemtype
1921 * @param int $itemid
1922 * @param context $context
1923 * @return stdClass
1924 */
1925 protected function add_tag_instance(core_tag_tag $tag, $component, $itemtype, $itemid, $context) {
1926 global $DB;
1927 $record = (array) $tag->to_object();
1928 $record['tagid'] = $record['id'];
1929 $record['component'] = $component;
1930 $record['itemtype'] = $itemtype;
1931 $record['itemid'] = $itemid;
1932 $record['contextid'] = $context->id;
f7c1c97c
RW
1933 $record['tiuserid'] = 0;
1934 $record['ordering'] = 0;
1935 $record['timecreated'] = time();
4823de02 1936 $record['id'] = $DB->insert_record('tag_instance', $record);
f7c1c97c 1937 return (object) $record;
4823de02 1938 }
cc033d48 1939}