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