Rev 11 | AutorÃa | Comparar con el anterior | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>.namespace core_tag;use core_tag_area;use core_tag_collection;use core_tag_tag;use core_tag;/*** Tag related unit tests.** @package core_tag* @category test* @copyright 2014 Mark Nelson <markn@moodle.com>* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later* @covers \core_tag_tag*/final class taglib_test extends \advanced_testcase {/*** Test set up.** This is executed before running any test in this file.*/public function setUp(): void {parent::setUp();$this->resetAfterTest();}/*** Test the core_tag_tag::add_item_tag() and core_tag_tag::remove_item_tag() functions.*/public function test_add_remove_item_tag(): void {global $DB;// Create a course to tag.$course = $this->getDataGenerator()->create_course();// Create the tag and tag instance we are going to delete.core_tag_tag::add_item_tag('core', 'course', $course->id, \context_course::instance($course->id), 'A random tag');$this->assertEquals(1, $DB->count_records('tag'));$this->assertEquals(1, $DB->count_records('tag_instance'));// Call the tag_set_delete function.core_tag_tag::remove_item_tag('core', 'course', $course->id, 'A random tag');// Now check that there are no tags or tag instances.$this->assertEquals(0, $DB->count_records('tag'));$this->assertEquals(0, $DB->count_records('tag_instance'));}/*** Test add_item_tag function correctly calculates the ordering for a new tag.*/public function test_add_tag_ordering_calculation(): void {global $DB;$user1 = $this->getDataGenerator()->create_user();$course1 = $this->getDataGenerator()->create_course();$course2 = $this->getDataGenerator()->create_course();$book1 = $this->getDataGenerator()->create_module('book', array('course' => $course1->id));$now = time();$chapter1id = $DB->insert_record('book_chapters', (object) ['bookid' => $book1->id,'hidden' => 0,'timecreated' => $now,'timemodified' => $now,'importsrc' => '','content' => '','contentformat' => FORMAT_HTML,]);// Create a tag (ordering should start at 1).$ti1 = core_tag_tag::add_item_tag('core', 'course', $course1->id,\context_course::instance($course1->id), 'A random tag for course 1');$this->assertEquals(1, $DB->get_field('tag_instance', 'ordering', ['id' => $ti1]));// Create another tag with a common component, itemtype and itemid (should increase the ordering by 1).$ti2 = core_tag_tag::add_item_tag('core', 'course', $course1->id,\context_course::instance($course1->id), 'Another random tag for course 1');$this->assertEquals(2, $DB->get_field('tag_instance', 'ordering', ['id' => $ti2]));// Create a new tag with the same component and itemtype, but different itemid (should start counting from 1 again).$ti3 = core_tag_tag::add_item_tag('core', 'course', $course2->id,\context_course::instance($course2->id), 'A random tag for course 2');$this->assertEquals(1, $DB->get_field('tag_instance', 'ordering', ['id' => $ti3]));// Create a new tag with a different itemtype (should start counting from 1 again).$ti4 = core_tag_tag::add_item_tag('core', 'user', $user1->id,\context_user::instance($user1->id), 'A random tag for user 1');$this->assertEquals(1, $DB->get_field('tag_instance', 'ordering', ['id' => $ti4]));// Create a new tag with a different component (should start counting from 1 again).$ti5 = core_tag_tag::add_item_tag('mod_book', 'book_chapters', $chapter1id,\context_module::instance($book1->cmid), 'A random tag for a book chapter');$this->assertEquals(1, $DB->get_field('tag_instance', 'ordering', ['id' => $ti5]));}/*** Test the tag cleanup function used by the cron.*/public function test_tag_cleanup(): void {global $DB;$task = new \core\task\tag_cron_task();// Create some users.$users = array();for ($i = 0; $i < 10; $i++) {$users[] = $this->getDataGenerator()->create_user();}// Create a course to tag.$course = $this->getDataGenerator()->create_course();$context = \context_course::instance($course->id);// Test clean up instances with tags that no longer exist.$tags = array();$tagnames = array();for ($i = 0; $i < 10; $i++) {$tags[] = $tag = $this->getDataGenerator()->create_tag(array('userid' => $users[0]->id));$tagnames[] = $tag->rawname;}// Create instances with the tags.core_tag_tag::set_item_tags('core', 'course', $course->id, $context, $tagnames);// We should now have ten tag instances.$coursetaginstances = $DB->count_records('tag_instance', array('itemtype' => 'course'));$this->assertEquals(10, $coursetaginstances);// Delete four tags// Manual delete of tags is done as the function will remove the instances as well.$DB->delete_records('tag', array('id' => $tags[6]->id));$DB->delete_records('tag', array('id' => $tags[7]->id));$DB->delete_records('tag', array('id' => $tags[8]->id));$DB->delete_records('tag', array('id' => $tags[9]->id));// Clean up the tags.$task->cleanup();// Check that we now only have six tag_instance records left.$coursetaginstances = $DB->count_records('tag_instance', array('itemtype' => 'course'));$this->assertEquals(6, $coursetaginstances);// Test clean up with users that have been deleted.// Create a tag for this course.foreach ($users as $user) {$context = \context_user::instance($user->id);core_tag_tag::set_item_tags('core', 'user', $user->id, $context, array($tags[0]->rawname));}$usertags = $DB->count_records('tag_instance', array('itemtype' => 'user'));$this->assertCount($usertags, $users);// Remove three students.// Using the proper function to delete the user will also remove the tags.$DB->update_record('user', array('id' => $users[4]->id, 'deleted' => 1));$DB->update_record('user', array('id' => $users[5]->id, 'deleted' => 1));$DB->update_record('user', array('id' => $users[6]->id, 'deleted' => 1));// Clean up the tags.$task->cleanup();$usertags = $DB->count_records('tag_instance', array('itemtype' => 'user'));$usercount = $DB->count_records('user', array('deleted' => 0));// Remove admin and guest from the count.$this->assertEquals($usertags, ($usercount - 2));// Test clean up where a course has been removed.// Delete the course. This also needs to be this way otherwise the tags are removed by using the proper function.$DB->delete_records('course', array('id' => $course->id));$task->cleanup();$coursetags = $DB->count_records('tag_instance', array('itemtype' => 'course'));$this->assertEquals(0, $coursetags);// Test clean up where a post has been removed.// Create default post.$post = new \stdClass();$post->userid = $users[1]->id;$post->content = 'test post content text';$post->id = $DB->insert_record('post', $post);$context = \context_system::instance();core_tag_tag::set_item_tags('core', 'post', $post->id, $context, array($tags[0]->rawname));// Add another one with a fake post id to be removed.core_tag_tag::set_item_tags('core', 'post', 15, $context, array($tags[0]->rawname));// Check that there are two tag instances.$posttags = $DB->count_records('tag_instance', array('itemtype' => 'post'));$this->assertEquals(2, $posttags);// Clean up the tags.$task->cleanup();// We should only have one entry left now.$posttags = $DB->count_records('tag_instance', array('itemtype' => 'post'));$this->assertEquals(1, $posttags);}/*** Test deleting a group of tag instances.*/public function test_tag_bulk_delete_instances(): void {global $DB;$task = new \core\task\tag_cron_task();// Setup.$user = $this->getDataGenerator()->create_user();$course = $this->getDataGenerator()->create_course();$context = \context_course::instance($course->id);// Create some tag instances.for ($i = 0; $i < 10; $i++) {$tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id));core_tag_tag::add_item_tag('core', 'course', $course->id, $context, $tag->rawname);}// Get tag instances. tag name and rawname are required for the event fired in this function.$sql = "SELECT ti.*, t.name, t.rawnameFROM {tag_instance} tiJOIN {tag} t ON t.id = ti.tagid";$taginstances = $DB->get_records_sql($sql);$this->assertCount(10, $taginstances);// Run the function.$task->bulk_delete_instances($taginstances);// Make sure they are gone.$instancecount = $DB->count_records('tag_instance');$this->assertEquals(0, $instancecount);}/*** Test that setting a list of tags for "tag" item type throws exception if userid specified*/public function test_set_item_tags_with_invalid_userid(): void {$user = $this->getDataGenerator()->create_user();$this->expectException(\coding_exception::class);$this->expectExceptionMessage('Related tags can not have tag instance userid');core_tag_tag::set_item_tags('core', 'tag', 1, \context_system::instance(), ['all', 'night', 'long'], $user->id);}/*** Prepares environment for testing tag correlations* @return core_tag_tag[] list of used tags*/protected function prepare_correlated() {global $DB;$user = $this->getDataGenerator()->create_user();$this->setUser($user);$user1 = $this->getDataGenerator()->create_user();$user2 = $this->getDataGenerator()->create_user();$user3 = $this->getDataGenerator()->create_user();$user4 = $this->getDataGenerator()->create_user();$user5 = $this->getDataGenerator()->create_user();$user6 = $this->getDataGenerator()->create_user();// Several records have both 'cat' and 'cats' tags attached to them.// This will make those tags automatically correlated.// Same with 'dog', 'dogs' and 'puppy.core_tag_tag::set_item_tags('core', 'user', $user1->id, \context_user::instance($user1->id), array('cat', 'cats'));core_tag_tag::set_item_tags('core', 'user', $user2->id, \context_user::instance($user2->id), array('cat', 'cats', 'kitten'));core_tag_tag::set_item_tags('core', 'user', $user3->id, \context_user::instance($user3->id), array('cat', 'cats'));core_tag_tag::set_item_tags('core', 'user', $user4->id, \context_user::instance($user4->id), array('dog', 'dogs', 'puppy'));core_tag_tag::set_item_tags('core', 'user', $user5->id, \context_user::instance($user5->id), array('dog', 'dogs', 'puppy'));core_tag_tag::set_item_tags('core', 'user', $user6->id, \context_user::instance($user6->id), array('dog', 'dogs', 'puppy'));$tags = core_tag_tag::get_by_name_bulk(core_tag_collection::get_default(),array('cat', 'cats', 'dog', 'dogs', 'kitten', 'puppy'), '*');// Add manual relation between tags 'cat' and 'kitten'.core_tag_tag::get($tags['cat']->id)->set_related_tags(array('kitten'));return $tags;}/*** Test for function compute_correlations() that is part of tag cron*/public function test_correlations(): void {global $DB;$task = new \core\task\tag_cron_task();$tags = array_map(function ($t) {return $t->id;}, $this->prepare_correlated());$task->compute_correlations();$this->assertEquals($tags['cats'],$DB->get_field_select('tag_correlation', 'correlatedtags','tagid = ?', array($tags['cat'])));$this->assertEquals($tags['cat'],$DB->get_field_select('tag_correlation', 'correlatedtags','tagid = ?', array($tags['cats'])));$this->assertEquals($tags['dogs'] . ',' . $tags['puppy'],$DB->get_field_select('tag_correlation', 'correlatedtags','tagid = ?', array($tags['dog'])));$this->assertEquals($tags['dog'] . ',' . $tags['puppy'],$DB->get_field_select('tag_correlation', 'correlatedtags','tagid = ?', array($tags['dogs'])));$this->assertEquals($tags['dog'] . ',' . $tags['dogs'],$DB->get_field_select('tag_correlation', 'correlatedtags','tagid = ?', array($tags['puppy'])));// Make sure get_correlated_tags() returns 'cats' as the only correlated tag to the 'cat'.$correlatedtags = array_values(core_tag_tag::get($tags['cat'])->get_correlated_tags(true));$this->assertCount(3, $correlatedtags); // This will return all existing instances but they all point to the same tag.$this->assertEquals('cats', $correlatedtags[0]->rawname);$this->assertEquals('cats', $correlatedtags[1]->rawname);$this->assertEquals('cats', $correlatedtags[2]->rawname);$correlatedtags = array_values(core_tag_tag::get($tags['cat'])->get_correlated_tags());$this->assertCount(1, $correlatedtags); // Duplicates are filtered out here.$this->assertEquals('cats', $correlatedtags[0]->rawname);// Make sure get_correlated_tags() returns 'dogs' and 'puppy' as the correlated tags to the 'dog'.$correlatedtags = core_tag_tag::get($tags['dog'])->get_correlated_tags(true);$this->assertCount(6, $correlatedtags); // 2 tags times 3 instances.$correlatedtags = array_values(core_tag_tag::get($tags['dog'])->get_correlated_tags());$this->assertCount(2, $correlatedtags);$this->assertEquals('dogs', $correlatedtags[0]->rawname);$this->assertEquals('puppy', $correlatedtags[1]->rawname);// Function get_related_tags() will return both related and correlated tags.$relatedtags = array_values(core_tag_tag::get($tags['cat'])->get_related_tags());$this->assertCount(2, $relatedtags);$this->assertEquals('kitten', $relatedtags[0]->rawname);$this->assertEquals('cats', $relatedtags[1]->rawname);// Also test get_correlated_tags().$correlatedtags = array_values(core_tag_tag::get($tags['cat'])->get_correlated_tags(true));$this->assertCount(3, $correlatedtags); // This will return all existing instances but they all point to the same tag.$this->assertEquals('cats', $correlatedtags[0]->rawname);$this->assertEquals('cats', $correlatedtags[1]->rawname);$this->assertEquals('cats', $correlatedtags[2]->rawname);$correlatedtags = array_values(core_tag_tag::get($tags['cat'])->get_correlated_tags());$this->assertCount(1, $correlatedtags); // Duplicates are filtered out here.$this->assertEquals('cats', $correlatedtags[0]->rawname);$correlatedtags = array_values(core_tag_tag::get($tags['dog'])->get_correlated_tags(true));$this->assertCount(6, $correlatedtags); // 2 tags times 3 instances.$correlatedtags = array_values(core_tag_tag::get($tags['dog'])->get_correlated_tags());$this->assertCount(2, $correlatedtags);$this->assertEquals('dogs', $correlatedtags[0]->rawname);$this->assertEquals('puppy', $correlatedtags[1]->rawname);$relatedtags = array_values(core_tag_tag::get($tags['cat'])->get_related_tags());$this->assertCount(2, $relatedtags);$this->assertEquals('kitten', $relatedtags[0]->rawname);$this->assertEquals('cats', $relatedtags[1]->rawname);// End of testing deprecated methods.// If we then manually set 'cat' and 'cats' as related, get_related_tags() will filter out duplicates.core_tag_tag::get($tags['cat'])->set_related_tags(array('kitten', 'cats'));$relatedtags = array_values(core_tag_tag::get($tags['cat'])->get_related_tags());$this->assertCount(2, $relatedtags);$this->assertEquals('kitten', $relatedtags[0]->rawname);$this->assertEquals('cats', $relatedtags[1]->rawname);// Make sure core_tag_tag::get_item_tags(), core_tag_tag::get_correlated_tags() return the same set of fields.$relatedtags = core_tag_tag::get_item_tags('core', 'tag', $tags['cat']);$relatedtag = reset($relatedtags);$correlatedtags = core_tag_tag::get($tags['cat'])->get_correlated_tags();$correlatedtag = reset($correlatedtags);$this->assertEquals(array_keys((array)$relatedtag->to_object()), array_keys((array)$correlatedtag->to_object()));$relatedtags = core_tag_tag::get_item_tags(null, 'tag', $tags['cat']);$relatedtag = reset($relatedtags);$correlatedtags = core_tag_tag::get($tags['cat'])->get_correlated_tags();$correlatedtag = reset($correlatedtags);$this->assertEquals(array_keys((array)$relatedtag), array_keys((array)$correlatedtag));}/*** Test for function cleanup() that is part of tag cron*/public function test_cleanup(): void {global $DB;$task = new \core\task\tag_cron_task();$user = $this->getDataGenerator()->create_user();$defaultcoll = core_tag_collection::get_default();// Setting tags will create non-standard tags 'cat', 'dog' and 'fish'.core_tag_tag::set_item_tags('core', 'user', $user->id, \context_user::instance($user->id), array('cat', 'dog', 'fish'));$this->assertTrue($DB->record_exists('tag', array('name' => 'cat')));$this->assertTrue($DB->record_exists('tag', array('name' => 'dog')));$this->assertTrue($DB->record_exists('tag', array('name' => 'fish')));// Make tag 'dog' standard.$dogtag = core_tag_tag::get_by_name($defaultcoll, 'dog', '*');$fishtag = core_tag_tag::get_by_name($defaultcoll, 'fish');$dogtag->update(array('isstandard' => 1));// Manually remove the instances pointing on tags 'dog' and 'fish'.$DB->execute('DELETE FROM {tag_instance} WHERE tagid in (?,?)', array($dogtag->id, $fishtag->id));$task->cleanup();// Tag 'cat' is still present because it's used. Tag 'dog' is present because it's standard.// Tag 'fish' was removed because it is not standard and it is no longer used by anybody.$this->assertTrue($DB->record_exists('tag', array('name' => 'cat')));$this->assertTrue($DB->record_exists('tag', array('name' => 'dog')));$this->assertFalse($DB->record_exists('tag', array('name' => 'fish')));// Delete user without using API function.$DB->update_record('user', array('id' => $user->id, 'deleted' => 1));$task->cleanup();// Tag 'cat' was now deleted too.$this->assertFalse($DB->record_exists('tag', array('name' => 'cat')));// Assign tag to non-existing record. Make sure tag was created in the DB.core_tag_tag::set_item_tags('core', 'course', 1231231, \context_system::instance(), array('bird'));$this->assertTrue($DB->record_exists('tag', array('name' => 'bird')));$task->cleanup();// Tag 'bird' was now deleted because the related record does not exist in the DB.$this->assertFalse($DB->record_exists('tag', array('name' => 'bird')));// Now we have a tag instance pointing on 'sometag' tag.$user = $this->getDataGenerator()->create_user();core_tag_tag::set_item_tags('core', 'user', $user->id, \context_user::instance($user->id), array('sometag'));$sometag = core_tag_tag::get_by_name($defaultcoll, 'sometag');$this->assertTrue($DB->record_exists('tag_instance', array('tagid' => $sometag->id)));// Some hacker removes the tag without using API.$DB->delete_records('tag', array('id' => $sometag->id));$task->cleanup();// The tag instances were also removed.$this->assertFalse($DB->record_exists('tag_instance', array('tagid' => $sometag->id)));}public function test_guess_tag(): void {global $DB;$user = $this->getDataGenerator()->create_user();$this->setUser($user);$tag1 = $this->getDataGenerator()->create_tag(array('name' => 'Cat'));$tc = core_tag_collection::create((object)array('name' => 'tagcoll'));$tag2 = $this->getDataGenerator()->create_tag(array('name' => 'Cat', 'tagcollid' => $tc->id));$this->assertEquals(2, count($DB->get_records('tag')));$this->assertEquals(2, count(core_tag_tag::guess_by_name('Cat')));$this->assertEquals(core_tag_collection::get_default(), core_tag_tag::get_by_name(0, 'Cat')->tagcollid);}public function test_instances(): void {global $DB;$user = $this->getDataGenerator()->create_user();$this->setUser($user);// Create a course to tag.$course = $this->getDataGenerator()->create_course();$context = \context_course::instance($course->id);$initialtagscount = $DB->count_records('tag');core_tag_tag::set_item_tags('core', 'course', $course->id, $context, array('Tag 1', 'Tag 2'));$tags = core_tag_tag::get_item_tags('core', 'course', $course->id);$tagssimple = array_values($tags);$this->assertEquals(2, count($tags));$this->assertEquals('Tag 1', $tagssimple[0]->rawname);$this->assertEquals('Tag 2', $tagssimple[1]->rawname);$this->assertEquals($initialtagscount + 2, $DB->count_records('tag'));core_tag_tag::set_item_tags('core', 'course', $course->id, $context, array('Tag 3', 'Tag 2', 'Tag 1'));$tags = core_tag_tag::get_item_tags('core', 'course', $course->id);$tagssimple = array_values($tags);$this->assertEquals(3, count($tags));$this->assertEquals('Tag 3', $tagssimple[0]->rawname);$this->assertEquals('Tag 2', $tagssimple[1]->rawname);$this->assertEquals('Tag 1', $tagssimple[2]->rawname);$this->assertEquals($initialtagscount + 3, $DB->count_records('tag'));core_tag_tag::set_item_tags('core', 'course', $course->id, $context, array('Tag 3'));$tags = core_tag_tag::get_item_tags('core', 'course', $course->id);$tagssimple = array_values($tags);$this->assertEquals(1, count($tags));$this->assertEquals('Tag 3', $tagssimple[0]->rawname);// Make sure the unused tags were removed from tag table.$this->assertEquals($initialtagscount + 1, $DB->count_records('tag'));}public function test_related_tags(): void {global $DB;$user = $this->getDataGenerator()->create_user();$this->setUser($user);$tagcollid = core_tag_collection::get_default();$tag = $this->getDataGenerator()->create_tag(array('$tagcollid' => $tagcollid, 'rawname' => 'My tag'));$tag = core_tag_tag::get($tag->id, '*');$tag->set_related_tags(array('Synonym 1', 'Synonym 2'));$relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $tag->id));$this->assertEquals(2, count($relatedtags));$this->assertEquals('Synonym 1', $relatedtags[0]->rawname);$this->assertEquals('Synonym 2', $relatedtags[1]->rawname);$t1 = core_tag_tag::get_by_name($tagcollid, 'Synonym 1', '*');$relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $t1->id));$this->assertEquals(1, count($relatedtags));$this->assertEquals('My tag', $relatedtags[0]->rawname);$t2 = core_tag_tag::get_by_name($tagcollid, 'Synonym 2', '*');$relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $t2->id));$this->assertEquals(1, count($relatedtags));$this->assertEquals('My tag', $relatedtags[0]->rawname);$tag->set_related_tags(array('Synonym 3', 'Synonym 2', 'Synonym 1'));$relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $tag->id));$this->assertEquals(3, count($relatedtags));$this->assertEquals('Synonym 1', $relatedtags[0]->rawname);$this->assertEquals('Synonym 2', $relatedtags[1]->rawname);$this->assertEquals('Synonym 3', $relatedtags[2]->rawname);$t3 = core_tag_tag::get_by_name($tagcollid, 'Synonym 3', '*');$relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $t3->id));$this->assertEquals(1, count($relatedtags));$this->assertEquals('My tag', $relatedtags[0]->rawname);$tag->set_related_tags(array('Synonym 3', 'Synonym 2'));$relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $tag->id));$this->assertEquals(2, count($relatedtags));$this->assertEquals('Synonym 2', $relatedtags[0]->rawname);$this->assertEquals('Synonym 3', $relatedtags[1]->rawname);// Assert "Synonym 1" no longer links but is still present (will be removed by cron).$relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $t1->id));$this->assertEquals(0, count($relatedtags));}/*** Very basic test for create/move/update/delete actions, without any itemtype movements.*/public function test_tag_coll_basic(): void {global $DB;// Make sure there is one and only one tag coll that is marked as default.$tagcolls = core_tag_collection::get_collections();$this->assertEquals(1, count($DB->get_records('tag_coll', array('isdefault' => 1))));$defaulttagcoll = core_tag_collection::get_default();// Create a new tag coll to store user tags and something else.$data = (object)array('name' => 'new tag coll');$tagcollid1 = core_tag_collection::create($data)->id;$tagcolls = core_tag_collection::get_collections();$this->assertEquals('new tag coll', $tagcolls[$tagcollid1]->name);// Create a new tag coll to store post tags.$data = (object)array('name' => 'posts');$tagcollid2 = core_tag_collection::create($data)->id;$tagcolls = core_tag_collection::get_collections();$this->assertEquals('posts', $tagcolls[$tagcollid2]->name);$this->assertEquals($tagcolls[$tagcollid1]->sortorder + 1,$tagcolls[$tagcollid2]->sortorder);// Illegal tag colls sortorder changing.$this->assertFalse(core_tag_collection::change_sortorder($tagcolls[$defaulttagcoll], 1));$this->assertFalse(core_tag_collection::change_sortorder($tagcolls[$defaulttagcoll], -1));$this->assertFalse(core_tag_collection::change_sortorder($tagcolls[$tagcollid2], 1));// Move the very last tag coll one position up.$this->assertTrue(core_tag_collection::change_sortorder($tagcolls[$tagcollid2], -1));$tagcolls = core_tag_collection::get_collections();$this->assertEquals($tagcolls[$tagcollid2]->sortorder + 1,$tagcolls[$tagcollid1]->sortorder);// Move the second last tag coll one position down.$this->assertTrue(core_tag_collection::change_sortorder($tagcolls[$tagcollid2], 1));$tagcolls = core_tag_collection::get_collections();$this->assertEquals($tagcolls[$tagcollid1]->sortorder + 1,$tagcolls[$tagcollid2]->sortorder);// Edit tag coll.$this->assertTrue(core_tag_collection::update($tagcolls[$tagcollid2],(object)array('name' => 'posts2')));$tagcolls = core_tag_collection::get_collections();$this->assertEquals('posts2', $tagcolls[$tagcollid2]->name);// Delete tag coll.$count = $DB->count_records('tag_coll');$this->assertFalse(core_tag_collection::delete($tagcolls[$defaulttagcoll]));$this->assertTrue(core_tag_collection::delete($tagcolls[$tagcollid1]));$this->assertEquals($count - 1, $DB->count_records('tag_coll'));}/*** Prepares environment for test_move_tags_* tests*/protected function prepare_move_tags() {global $CFG;require_once($CFG->dirroot.'/blog/locallib.php');$this->setUser($this->getDataGenerator()->create_user());$collid1 = core_tag_collection::get_default();$collid2 = core_tag_collection::create(array('name' => 'newcoll'))->id;$user1 = $this->getDataGenerator()->create_user();$user2 = $this->getDataGenerator()->create_user();$blogpost = new \blog_entry(null, array('subject' => 'test'), null);$states = \blog_entry::get_applicable_publish_states();$blogpost->publishstate = reset($states);$blogpost->add();core_tag_tag::set_item_tags('core', 'user', $user1->id, \context_user::instance($user1->id),array('Tag1', 'Tag2'));core_tag_tag::set_item_tags('core', 'user', $user2->id, \context_user::instance($user2->id),array('Tag2', 'Tag3'));$this->getDataGenerator()->create_tag(array('rawname' => 'Tag4','tagcollid' => $collid1, 'isstandard' => 1));$this->getDataGenerator()->create_tag(array('rawname' => 'Tag5','tagcollid' => $collid2, 'isstandard' => 1));return array($collid1, $collid2, $user1, $user2, $blogpost);}public function test_move_tags_simple(): void {global $DB;list($collid1, $collid2, $user1, $user2, $blogpost) = $this->prepare_move_tags();// Move 'user' area from collection 1 to collection 2, make sure tags were moved completely.$tagarea = $DB->get_record('tag_area', array('itemtype' => 'user', 'component' => 'core'));core_tag_area::update($tagarea, array('tagcollid' => $collid2));$tagsaftermove = $DB->get_records('tag');foreach ($tagsaftermove as $tag) {// Confirm that the time modified has not been unset.$this->assertNotEmpty($tag->timemodified);}$this->assertEquals(array('Tag4'),$DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid1)));$this->assertEquals(array('Tag1', 'Tag2', 'Tag3', 'Tag5'),$DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid2)));$this->assertEquals(array('Tag1', 'Tag2'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user1->id)));$this->assertEquals(array('Tag2', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user2->id)));}public function test_move_tags_split_tag(): void {global $DB;list($collid1, $collid2, $user1, $user2, $blogpost) = $this->prepare_move_tags();core_tag_tag::set_item_tags('core', 'post', $blogpost->id, \context_system::instance(),array('Tag1', 'Tag3'));// Move 'user' area from collection 1 to collection 2, make sure tag Tag2 was moved and tags Tag1 and Tag3 were duplicated.$tagareauser = $DB->get_record('tag_area', array('itemtype' => 'user', 'component' => 'core'));core_tag_area::update($tagareauser, array('tagcollid' => $collid2));$tagsaftermove = $DB->get_records('tag');foreach ($tagsaftermove as $tag) {// Confirm that the time modified has not been unset.$this->assertNotEmpty($tag->timemodified);}$this->assertEquals(array('Tag1', 'Tag3', 'Tag4'),$DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid1)));$this->assertEquals(array('Tag1', 'Tag2', 'Tag3', 'Tag5'),$DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid2)));$this->assertEquals(array('Tag1', 'Tag2'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user1->id)));$this->assertEquals(array('Tag2', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user2->id)));$this->assertEquals(array('Tag1', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'post', $blogpost->id)));}public function test_move_tags_merge_tag(): void {global $DB;list($collid1, $collid2, $user1, $user2, $blogpost) = $this->prepare_move_tags();// Set collection for 'post' tag area to be collection 2 and add some tags there.$tagareablog = $DB->get_record('tag_area', array('itemtype' => 'post', 'component' => 'core'));core_tag_area::update($tagareablog, array('tagcollid' => $collid2));core_tag_tag::set_item_tags('core', 'post', $blogpost->id, \context_system::instance(),array('TAG1', 'Tag3'));// Move 'user' area from collection 1 to collection 2,// make sure tag Tag2 was moved and tags Tag1 and Tag3 were merged into existing.$tagareauser = $DB->get_record('tag_area', array('itemtype' => 'user', 'component' => 'core'));core_tag_area::update($tagareauser, array('tagcollid' => $collid2));$tagsaftermove = $DB->get_records('tag');foreach ($tagsaftermove as $tag) {// Confirm that the time modified has not been unset.$this->assertNotEmpty($tag->timemodified);}$this->assertEquals(array('Tag4'),$DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid1)));$this->assertEquals(array('TAG1', 'Tag2', 'Tag3', 'Tag5'),$DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid2)));$this->assertEquals(array('TAG1', 'Tag2'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user1->id)));$this->assertEquals(array('Tag2', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user2->id)));$this->assertEquals(array('TAG1', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'post', $blogpost->id)));}public function test_move_tags_with_related(): void {global $DB;list($collid1, $collid2, $user1, $user2, $blogpost) = $this->prepare_move_tags();// Set Tag1 to be related to Tag2 and Tag4 (in collection 1).core_tag_tag::get_by_name($collid1, 'Tag1')->set_related_tags(array('Tag2', 'Tag4'));// Set collection for 'post' tag area to be collection 2 and add some tags there.$tagareablog = $DB->get_record('tag_area', array('itemtype' => 'post', 'component' => 'core'));core_tag_area::update($tagareablog, array('tagcollid' => $collid2));core_tag_tag::set_item_tags('core', 'post', $blogpost->id, \context_system::instance(),array('TAG1', 'Tag3'));// Move 'user' area from collection 1 to collection 2, make sure tags were moved completely.$tagarea = $DB->get_record('tag_area', array('itemtype' => 'user', 'component' => 'core'));core_tag_area::update($tagarea, array('tagcollid' => $collid2));$tagsaftermove = $DB->get_records('tag');foreach ($tagsaftermove as $tag) {// Confirm that the time modified has not been unset.$this->assertNotEmpty($tag->timemodified);}$this->assertEquals(array('Tag1', 'Tag2', 'Tag4'),$DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid1)));$this->assertEquals(array('TAG1', 'Tag2', 'Tag3', 'Tag4', 'Tag5'),$DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid2)));$this->assertEquals(array('TAG1', 'Tag2'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user1->id)));$this->assertEquals(array('Tag2', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user2->id)));$tag11 = core_tag_tag::get_by_name($collid1, 'Tag1');$related11 = core_tag_tag::get($tag11->id)->get_manual_related_tags();$related11 = array_map('core_tag_tag::make_display_name', $related11);sort($related11); // Order of related tags may be random.$this->assertEquals('Tag2, Tag4', join(', ', $related11));$tag21 = core_tag_tag::get_by_name($collid2, 'TAG1');$related21 = core_tag_tag::get($tag21->id)->get_manual_related_tags();$related21 = array_map('core_tag_tag::make_display_name', $related21);sort($related21); // Order of related tags may be random.$this->assertEquals('Tag2, Tag4', join(', ', $related21));}public function test_move_tags_corrupted(): void {global $DB;list($collid1, $collid2, $user1, $user2, $blogpost) = $this->prepare_move_tags();$collid3 = core_tag_collection::create(array('name' => 'weirdcoll'))->id;// We already have Tag1 in coll1, now let's create it in coll3.$extratag1 = $this->getDataGenerator()->create_tag(array('rawname' => 'Tag1','tagcollid' => $collid3, 'isstandard' => 1));// Artificially add 'Tag1' from coll3 to user2.$DB->insert_record('tag_instance', array('tagid' => $extratag1->id, 'itemtype' => 'user','component' => 'core', 'itemid' => $user2->id, 'ordering' => 3));// Now we have corrupted data: both users are tagged with 'Tag1', however these are two tags in different collections.$user1tags = array_values(core_tag_tag::get_item_tags('core', 'user', $user1->id));$user2tags = array_values(core_tag_tag::get_item_tags('core', 'user', $user2->id));$this->assertEquals('Tag1', $user1tags[0]->rawname);$this->assertEquals('Tag1', $user2tags[2]->rawname);$this->assertNotEquals($user1tags[0]->tagcollid, $user2tags[2]->tagcollid);// Move user interests tag area into coll2.$tagarea = $DB->get_record('tag_area', array('itemtype' => 'user', 'component' => 'core'));core_tag_area::update($tagarea, array('tagcollid' => $collid2));$tagsaftermove = $DB->get_records('tag');foreach ($tagsaftermove as $tag) {// Confirm that the time modified has not been unset.$this->assertNotEmpty($tag->timemodified);}// Now all tags are correctly moved to the new collection and both tags 'Tag1' were merged.$user1tags = array_values(core_tag_tag::get_item_tags('core', 'user', $user1->id));$user2tags = array_values(core_tag_tag::get_item_tags('core', 'user', $user2->id));$this->assertEquals('Tag1', $user1tags[0]->rawname);$this->assertEquals('Tag1', $user2tags[2]->rawname);$this->assertEquals($collid2, $user1tags[0]->tagcollid);$this->assertEquals($collid2, $user2tags[2]->tagcollid);}/*** Test functions core_tag_tag::create_if_missing() and core_tag_tag::get_by_name_bulk().*/public function test_create_get(): void {$tagset = array('Cat', ' Dog ', '<Mouse', '<>', 'mouse', 'Dog');$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagset);$this->assertEquals(array('cat', 'dog', 'mouse'), array_keys($tags));$this->assertEquals('Dog', $tags['dog']->rawname);$this->assertEquals('mouse', $tags['mouse']->rawname); // Case of the last tag wins.$tags2 = core_tag_tag::create_if_missing($collid, array('CAT', 'Elephant'));$this->assertEquals(array('cat', 'elephant'), array_keys($tags2));$this->assertEquals('Cat', $tags2['cat']->rawname);$this->assertEquals('Elephant', $tags2['elephant']->rawname);$this->assertEquals($tags['cat']->id, $tags2['cat']->id); // Tag 'cat' already existed and was not created again.$tags3 = core_tag_tag::get_by_name_bulk($collid, $tagset);$this->assertEquals(array('cat', 'dog', 'mouse'), array_keys($tags3));$this->assertEquals('Dog', $tags3['dog']->rawname);$this->assertEquals('mouse', $tags3['mouse']->rawname);}/*** Testing function core_tag_tag::combine_tags()*/public function test_combine_tags(): void {$initialtags = array(array('Cat', 'Dog'),array('Dog', 'Cat'),array('Cats', 'Hippo'),array('Hippo', 'Cats'),array('Cat', 'Mouse', 'Kitten'),array('Cats', 'Mouse', 'Kitten'),array('Kitten', 'Mouse', 'Cat'),array('Kitten', 'Mouse', 'Cats'),array('Cats', 'Mouse', 'Kitten'),array('Mouse', 'Hippo'));$finaltags = array(array('Cat', 'Dog'),array('Dog', 'Cat'),array('Cat', 'Hippo'),array('Hippo', 'Cat'),array('Cat', 'Mouse'),array('Cat', 'Mouse'),array('Mouse', 'Cat'),array('Mouse', 'Cat'),array('Cat', 'Mouse'),array('Mouse', 'Hippo'));$collid = core_tag_collection::get_default();$context = \context_system::instance();foreach ($initialtags as $id => $taglist) {core_tag_tag::set_item_tags('core', 'course', $id + 10, $context, $initialtags[$id]);}core_tag_tag::get_by_name($collid, 'Cats', '*')->update(array('isstandard' => 1));// Combine tags 'Cats' and 'Kitten' into 'Cat'.$cat = core_tag_tag::get_by_name($collid, 'Cat', '*');$cats = core_tag_tag::get_by_name($collid, 'Cats', '*');$kitten = core_tag_tag::get_by_name($collid, 'Kitten', '*');$cat->combine_tags(array($cats, $kitten));foreach ($finaltags as $id => $taglist) {$this->assertEquals($taglist,array_values(core_tag_tag::get_item_tags_array('core', 'course', $id + 10)),'Original array ('.join(', ', $initialtags[$id]).')');}// Ensure combined tags are deleted and 'Cat' is now official (because 'Cats' was official).$this->assertEmpty(core_tag_tag::get_by_name($collid, 'Cats'));$this->assertEmpty(core_tag_tag::get_by_name($collid, 'Kitten'));$cattag = core_tag_tag::get_by_name($collid, 'Cat', '*');$this->assertEquals(1, $cattag->isstandard);}/*** Testing function core_tag_tag::combine_tags() when related tags are present.*/public function test_combine_tags_with_related(): void {$collid = core_tag_collection::get_default();$context = \context_system::instance();core_tag_tag::set_item_tags('core', 'course', 10, $context, array('Cat', 'Cats', 'Dog'));core_tag_tag::get_by_name($collid, 'Cat', '*')->set_related_tags(array('Kitty'));core_tag_tag::get_by_name($collid, 'Cats', '*')->set_related_tags(array('Cat', 'Kitten', 'Kitty'));// Combine tags 'Cats' into 'Cat'.$cat = core_tag_tag::get_by_name($collid, 'Cat', '*');$cats = core_tag_tag::get_by_name($collid, 'Cats', '*');$cat->combine_tags(array($cats));// Ensure 'Cat' is now related to 'Kitten' and 'Kitty' (order of related tags may be random).$relatedtags = array_map(function($t) {return $t->rawname;}, $cat->get_manual_related_tags());sort($relatedtags);$this->assertEquals(array('Kitten', 'Kitty'), array_values($relatedtags));}/*** Testing function core_tag_tag::combine_tags() when correlated tags are present.*/public function test_combine_tags_with_correlated(): void {$task = new \core\task\tag_cron_task();$tags = $this->prepare_correlated();$task->compute_correlations();// Now 'cat' is correlated with 'cats'.// Also 'dog', 'dogs' and 'puppy' are correlated.// There is a manual relation between 'cat' and 'kitten'.// See function test_correlations() for assertions.// Combine tags 'dog' and 'kitten' into 'cat' and make sure that cat is now correlated with dogs and puppy.$tags['cat']->combine_tags(array($tags['dog'], $tags['kitten']));$correlatedtags = $this->get_correlated_tags_names($tags['cat']);$this->assertEquals(['cats', 'dogs', 'puppy'], $correlatedtags);$correlatedtags = $this->get_correlated_tags_names($tags['dogs']);$this->assertEquals(['cat', 'puppy'], $correlatedtags);$correlatedtags = $this->get_correlated_tags_names($tags['puppy']);$this->assertEquals(['cat', 'dogs'], $correlatedtags);// Add tag that does not have any correlations.$user7 = $this->getDataGenerator()->create_user();core_tag_tag::set_item_tags('core', 'user', $user7->id, \context_user::instance($user7->id), array('hippo'));$tags['hippo'] = core_tag_tag::get_by_name(core_tag_collection::get_default(), 'hippo', '*');// Combine tag 'cat' into 'hippo'. Now 'hippo' should have the same correlations 'cat' used to have and also// tags 'dogs' and 'puppy' should have 'hippo' in correlations.$tags['hippo']->combine_tags(array($tags['cat']));$correlatedtags = $this->get_correlated_tags_names($tags['hippo']);$this->assertEquals(['cats', 'dogs', 'puppy'], $correlatedtags);$correlatedtags = $this->get_correlated_tags_names($tags['dogs']);$this->assertEquals(['hippo', 'puppy'], $correlatedtags);$correlatedtags = $this->get_correlated_tags_names($tags['puppy']);$this->assertEquals(['dogs', 'hippo'], $correlatedtags);}/*** get_tags_by_area_in_contexts should return an empty array if there* are no tag instances for the area in the given context.*/public function test_get_tags_by_area_in_contexts_empty(): void {$tagnames = ['foo'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user = $this->getDataGenerator()->create_user();$context = \context_user::instance($user->id);$component = 'core';$itemtype = 'user';$result = core_tag_tag::get_tags_by_area_in_contexts($component, $itemtype, [$context]);$this->assertEmpty($result);}/*** get_tags_by_area_in_contexts should return an array of tags that* have instances in the given context even when there is only a single* instance.*/public function test_get_tags_by_area_in_contexts_single_tag_one_context(): void {$tagnames = ['foo'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user = $this->getDataGenerator()->create_user();$context = \context_user::instance($user->id);$component = 'core';$itemtype = 'user';core_tag_tag::set_item_tags($component, $itemtype, $user->id, $context, $tagnames);$result = core_tag_tag::get_tags_by_area_in_contexts($component, $itemtype, [$context]);$expected = array_map(function($t) {return $t->id;}, $tags);$actual = array_map(function($t) {return $t->id;}, $result);sort($expected);sort($actual);$this->assertEquals($expected, $actual);}/*** get_tags_by_area_in_contexts should return all tags in an array* that have tag instances in for the area in the given context and* should ignore all tags that don't have an instance.*/public function test_get_tags_by_area_in_contexts_multiple_tags_one_context(): void {$tagnames = ['foo', 'bar', 'baz'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user = $this->getDataGenerator()->create_user();$context = \context_user::instance($user->id);$component = 'core';$itemtype = 'user';core_tag_tag::set_item_tags($component, $itemtype, $user->id, $context, array_slice($tagnames, 0, 2));$result = core_tag_tag::get_tags_by_area_in_contexts($component, $itemtype, [$context]);$expected = ['foo', 'bar'];$actual = array_map(function($t) {return $t->name;}, $result);sort($expected);sort($actual);$this->assertEquals($expected, $actual);}/*** get_tags_by_area_in_contexts should return the unique set of* tags for a area in the given contexts. Multiple tag instances of* the same tag don't result in duplicates in the result set.** Tags with tag instances in the same area with in difference contexts* should be ignored.*/public function test_get_tags_by_area_in_contexts_multiple_tags_multiple_contexts(): void {$tagnames = ['foo', 'bar', 'baz', 'bop', 'bam', 'bip'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user1 = $this->getDataGenerator()->create_user();$user2 = $this->getDataGenerator()->create_user();$user3 = $this->getDataGenerator()->create_user();$context1 = \context_user::instance($user1->id);$context2 = \context_user::instance($user2->id);$context3 = \context_user::instance($user3->id);$component = 'core';$itemtype = 'user';// User 1 tags: 'foo', 'bar'.core_tag_tag::set_item_tags($component, $itemtype, $user1->id, $context1, array_slice($tagnames, 0, 2));// User 2 tags: 'bar', 'baz'.core_tag_tag::set_item_tags($component, $itemtype, $user2->id, $context2, array_slice($tagnames, 1, 2));// User 3 tags: 'bop', 'bam'.core_tag_tag::set_item_tags($component, $itemtype, $user3->id, $context3, array_slice($tagnames, 3, 2));$result = core_tag_tag::get_tags_by_area_in_contexts($component, $itemtype, [$context1, $context2]);// Both User 1 and 2 have tagged using 'bar' but we don't// expect duplicate tags in the result since they are the same// tag.//// User 3 has tagged 'bop' and 'bam' but we aren't searching in// that context so they shouldn't be in the results.$expected = ['foo', 'bar', 'baz'];$actual = array_map(function($t) {return $t->name;}, $result);sort($expected);sort($actual);$this->assertEquals($expected, $actual);}/*** get_items_tags should return an empty array if the tag area is disabled.*/public function test_get_items_tags_disabled_component(): void {global $CFG;$user1 = $this->getDataGenerator()->create_user();$context1 = \context_user::instance($user1->id);$component = 'core';$itemtype = 'user';$itemids = [$user1->id];// User 1 tags: 'foo', 'bar'.core_tag_tag::set_item_tags($component, $itemtype, $user1->id, $context1, ['foo']);// This mimics disabling tags for a component.$CFG->usetags = false;$result = core_tag_tag::get_items_tags($component, $itemtype, $itemids);$this->assertEmpty($result);}/*** get_items_tags should return an empty array if the tag item ids list* is empty.*/public function test_get_items_tags_empty_itemids(): void {$user1 = $this->getDataGenerator()->create_user();$context1 = \context_user::instance($user1->id);$component = 'core';$itemtype = 'user';// User 1 tags: 'foo', 'bar'.core_tag_tag::set_item_tags($component, $itemtype, $user1->id, $context1, ['foo']);$result = core_tag_tag::get_items_tags($component, $itemtype, []);$this->assertEmpty($result);}/*** get_items_tags should return an array indexed by the item ids with empty* arrays as the values when the component or itemtype is unknown.*/public function test_get_items_tags_unknown_component_itemtype(): void {$itemids = [1, 2, 3];$result = core_tag_tag::get_items_tags('someunknowncomponent', 'user', $itemids);foreach ($itemids as $itemid) {// Unknown component should return an array indexed by the item ids// with empty arrays as the values.$this->assertEmpty($result[$itemid]);}$result = core_tag_tag::get_items_tags('core', 'someunknownitemtype', $itemids);foreach ($itemids as $itemid) {// Unknown item type should return an array indexed by the item ids// with empty arrays as the values.$this->assertEmpty($result[$itemid]);}}/*** get_items_tags should return an array indexed by the item ids with empty* arrays as the values for any item ids that don't have tag instances.** Data setup:* Users: 1, 2, 3* Tags: user 1 = ['foo', 'bar']* user 2 = ['baz', 'bop']* user 3 = []** Expected result:* [* 1 => [* 1 => 'foo',* 2 => 'bar'* ],* 2 => [* 3 => 'baz',* 4 => 'bop'* ],* 3 => []* ]*/public function test_get_items_tags_missing_itemids(): void {$user1 = $this->getDataGenerator()->create_user();$user2 = $this->getDataGenerator()->create_user();$user3 = $this->getDataGenerator()->create_user();$context1 = \context_user::instance($user1->id);$context2 = \context_user::instance($user2->id);$component = 'core';$itemtype = 'user';$itemids = [$user1->id, $user2->id, $user3->id];$expecteduser1tagnames = ['foo', 'bar'];$expecteduser2tagnames = ['baz', 'bop'];$expecteduser3tagnames = [];// User 1 tags: 'foo', 'bar'.core_tag_tag::set_item_tags($component, $itemtype, $user1->id, $context1, $expecteduser1tagnames);// User 2 tags: 'bar', 'baz'.core_tag_tag::set_item_tags($component, $itemtype, $user2->id, $context2, $expecteduser2tagnames);$result = core_tag_tag::get_items_tags($component, $itemtype, $itemids);$actualuser1tagnames = array_map(function($taginstance) {return $taginstance->name;}, $result[$user1->id]);$actualuser2tagnames = array_map(function($taginstance) {return $taginstance->name;}, $result[$user2->id]);$actualuser3tagnames = $result[$user3->id];sort($expecteduser1tagnames);sort($expecteduser2tagnames);sort($actualuser1tagnames);sort($actualuser2tagnames);$this->assertEquals($expecteduser1tagnames, $actualuser1tagnames);$this->assertEquals($expecteduser2tagnames, $actualuser2tagnames);$this->assertEquals($expecteduser3tagnames, $actualuser3tagnames);}/*** set_item_tags should remove any tags that aren't in the given list and should* add any instances that are missing.*/public function test_set_item_tags_no_multiple_context_add_remove_instances(): void {$tagnames = ['foo', 'bar', 'baz', 'bop'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user1 = $this->getDataGenerator()->create_user();$context = \context_user::instance($user1->id);$component = 'core';$itemtype = 'user';$itemid = 1;$tagareas = core_tag_area::get_areas();$tagarea = $tagareas[$itemtype][$component];$newtagnames = ['bar', 'baz', 'bop'];// Make sure the tag area doesn't allow multiple contexts.core_tag_area::update($tagarea, ['multiplecontexts' => false]);// Create tag instances in separate contexts.$this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context);$this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context);core_tag_tag::set_item_tags($component, $itemtype, $itemid, $context, $newtagnames);$result = core_tag_tag::get_item_tags($component, $itemtype, $itemid);$actualtagnames = array_map(function($record) {return $record->name;}, $result);sort($newtagnames);sort($actualtagnames);// The list of tags should match the $newtagnames which means 'foo'// should have been removed while 'baz' and 'bop' were added. 'bar'// should remain as it was in the new list of tags.$this->assertEquals($newtagnames, $actualtagnames);}/*** set_item_tags should set all of the tag instance context ids to the given* context if the tag area for the items doesn't allow multiple contexts for* the tag instances.*/public function test_set_item_tags_no_multiple_context_updates_context_of_instances(): void {$tagnames = ['foo', 'bar'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user1 = $this->getDataGenerator()->create_user();$user2 = $this->getDataGenerator()->create_user();$context1 = \context_user::instance($user1->id);$context2 = \context_user::instance($user2->id);$component = 'core';$itemtype = 'user';$itemid = 1;$tagareas = core_tag_area::get_areas();$tagarea = $tagareas[$itemtype][$component];// Make sure the tag area doesn't allow multiple contexts.core_tag_area::update($tagarea, ['multiplecontexts' => false]);// Create tag instances in separate contexts.$this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);$this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context2);core_tag_tag::set_item_tags($component, $itemtype, $itemid, $context1, $tagnames);$result = core_tag_tag::get_item_tags($component, $itemtype, $itemid);$this->assertCount(count($tagnames), $result);foreach ($result as $tag) {// The core user tag area doesn't allow multiple contexts for tag instances// so set_item_tags should have set all of the tag instance context ids// to match $context1.$this->assertEquals($context1->id, $tag->taginstancecontextid);}}/*** set_item_tags should delete all of the tag instances that don't match* the new set of tags, regardless of the context that the tag instance* is in.*/public function test_set_item_tags_no_multiple_contex_deletes_old_instancest(): void {$tagnames = ['foo', 'bar', 'baz', 'bop'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user1 = $this->getDataGenerator()->create_user();$user2 = $this->getDataGenerator()->create_user();$context1 = \context_user::instance($user1->id);$context2 = \context_user::instance($user2->id);$component = 'core';$itemtype = 'user';$itemid = 1;$expectedtagnames = ['foo', 'baz'];$tagareas = core_tag_area::get_areas();$tagarea = $tagareas[$itemtype][$component];// Make sure the tag area doesn't allow multiple contexts.core_tag_area::update($tagarea, ['multiplecontexts' => false]);// Create tag instances in separate contexts.$this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);$this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context1);$this->add_tag_instance($tags['baz'], $component, $itemtype, $itemid, $context2);$this->add_tag_instance($tags['bop'], $component, $itemtype, $itemid, $context2);core_tag_tag::set_item_tags($component, $itemtype, $itemid, $context1, $expectedtagnames);$result = core_tag_tag::get_item_tags($component, $itemtype, $itemid);$actualtagnames = array_map(function($record) {return $record->name;}, $result);sort($expectedtagnames);sort($actualtagnames);// The list of tags should match the $expectedtagnames.$this->assertEquals($expectedtagnames, $actualtagnames);foreach ($result as $tag) {// The core user tag area doesn't allow multiple contexts for tag instances// so set_item_tags should have set all of the tag instance context ids// to match $context1.$this->assertEquals($context1->id, $tag->taginstancecontextid);}}/*** set_item_tags should not change tag instances in a different context to the one* it's opertating on if the tag area allows instances from multiple contexts.*/public function test_set_item_tags_allow_multiple_context_doesnt_update_context(): void {global $DB;$tagnames = ['foo', 'bar', 'bop'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user1 = $this->getDataGenerator()->create_user();$user2 = $this->getDataGenerator()->create_user();$context1 = \context_user::instance($user1->id);$context2 = \context_user::instance($user2->id);$component = 'core';$itemtype = 'user';$itemid = 1;$tagareas = core_tag_area::get_areas();$tagarea = $tagareas[$itemtype][$component];// Make sure the tag area allows multiple contexts.core_tag_area::update($tagarea, ['multiplecontexts' => true]);// Create tag instances in separate contexts.$this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);$this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context2);// Set the list of tags for $context1. This includes a tag that already exists// in that context and a new tag. There is another tag, 'bar', that exists in a// different context ($context2) that should be ignored.core_tag_tag::set_item_tags($component, $itemtype, $itemid, $context1, ['foo', 'bop']);$result = core_tag_tag::get_item_tags($component, $itemtype, $itemid);$actualtagnames = array_map(function($record) {return $record->name;}, $result);sort($tagnames);sort($actualtagnames);// The list of tags should match the $tagnames.$this->assertEquals($tagnames, $actualtagnames);foreach ($result as $tag) {if ($tag->name == 'bar') {// The tag instance for 'bar' should have been left untouched// because it was in a different context.$this->assertEquals($context2->id, $tag->taginstancecontextid);} else {$this->assertEquals($context1->id, $tag->taginstancecontextid);}}}/*** set_item_tags should delete all of the tag instances that don't match* the new set of tags only in the same context if the tag area allows* multiple contexts.*/public function test_set_item_tags_allow_multiple_context_deletes_instances_in_same_context(): void {$tagnames = ['foo', 'bar', 'baz', 'bop'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user1 = $this->getDataGenerator()->create_user();$user2 = $this->getDataGenerator()->create_user();$context1 = \context_user::instance($user1->id);$context2 = \context_user::instance($user2->id);$component = 'core';$itemtype = 'user';$itemid = 1;$expectedtagnames = ['foo', 'bar', 'bop'];$tagareas = core_tag_area::get_areas();$tagarea = $tagareas[$itemtype][$component];// Make sure the tag area allows multiple contexts.core_tag_area::update($tagarea, ['multiplecontexts' => true]);// Create tag instances in separate contexts.$this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);$this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context1);$this->add_tag_instance($tags['baz'], $component, $itemtype, $itemid, $context1);$this->add_tag_instance($tags['bop'], $component, $itemtype, $itemid, $context2);core_tag_tag::set_item_tags($component, $itemtype, $itemid, $context1, ['foo', 'bar']);$result = core_tag_tag::get_item_tags($component, $itemtype, $itemid);$actualtagnames = array_map(function($record) {return $record->name;}, $result);sort($expectedtagnames);sort($actualtagnames);// The list of tags should match the $expectedtagnames, which includes the// tag 'bop' because it was in a different context to the one being set// even though it wasn't in the new set of tags.$this->assertEquals($expectedtagnames, $actualtagnames);}/*** set_item_tags should allow multiple instances of the same tag in different* contexts if the tag area allows multiple contexts.*/public function test_set_item_tags_allow_multiple_context_same_tag_multiple_contexts(): void {$tagnames = ['foo'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user1 = $this->getDataGenerator()->create_user();$user2 = $this->getDataGenerator()->create_user();$context1 = \context_user::instance($user1->id);$context2 = \context_user::instance($user2->id);$component = 'core';$itemtype = 'user';$itemid = 1;$expectedtagnames = ['foo', 'bar', 'bop'];$tagareas = core_tag_area::get_areas();$tagarea = $tagareas[$itemtype][$component];// Make sure the tag area allows multiple contexts.core_tag_area::update($tagarea, ['multiplecontexts' => true]);// Create first instance of 'foo' in $context1.$this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);core_tag_tag::set_item_tags($component, $itemtype, $itemid, $context2, ['foo']);$result = core_tag_tag::get_item_tags($component, $itemtype, $itemid);$tagsbycontext = array_reduce($result, function($carry, $tag) {$contextid = $tag->taginstancecontextid;if (isset($carry[$contextid])) {$carry[$contextid][] = $tag;} else {$carry[$contextid] = [$tag];}return $carry;}, []);// The result should be two tag instances of 'foo' in each of the// two contexts, $context1 and $context2.$this->assertCount(1, $tagsbycontext[$context1->id]);$this->assertCount(1, $tagsbycontext[$context2->id]);$this->assertEquals('foo', $tagsbycontext[$context1->id][0]->name);$this->assertEquals('foo', $tagsbycontext[$context2->id][0]->name);}/*** delete_instances_as_record with an empty set of instances should do nothing.*/public function test_delete_instances_as_record_empty_set(): void {$user = $this->getDataGenerator()->create_user();$context = \context_user::instance($user->id);$component = 'core';$itemtype = 'user';$itemid = 1;core_tag_tag::set_item_tags($component, $itemtype, $itemid, $context, ['foo']);// This shouldn't error.core_tag_tag::delete_instances_as_record([]);$tags = core_tag_tag::get_item_tags($component, $itemtype, $itemid);// We should still have one tag.$this->assertCount(1, $tags);}/*** delete_instances_as_record with an instance that doesn't exist should do* nothing.*/public function test_delete_instances_as_record_missing_set(): void {$tagnames = ['foo'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user = $this->getDataGenerator()->create_user();$context = \context_user::instance($user->id);$component = 'core';$itemtype = 'user';$itemid = 1;$taginstance = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context);$taginstance->id++;// Delete an instance that doesn't exist should do nothing.core_tag_tag::delete_instances_as_record([$taginstance]);$tags = core_tag_tag::get_item_tags($component, $itemtype, $itemid);// We should still have one tag.$this->assertCount(1, $tags);}/*** delete_instances_as_record with a list of all tag instances should* leave no tags left.*/public function test_delete_instances_as_record_whole_set(): void {$tagnames = ['foo'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user = $this->getDataGenerator()->create_user();$context = \context_user::instance($user->id);$component = 'core';$itemtype = 'user';$itemid = 1;$taginstance = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context);core_tag_tag::delete_instances_as_record([$taginstance]);$tags = core_tag_tag::get_item_tags($component, $itemtype, $itemid);// There should be no tags left.$this->assertEmpty($tags);}/*** delete_instances_as_record with a list of only some tag instances should* delete only the given tag instances and leave other tag instances.*/public function test_delete_instances_as_record_partial_set(): void {$tagnames = ['foo', 'bar'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user = $this->getDataGenerator()->create_user();$context = \context_user::instance($user->id);$component = 'core';$itemtype = 'user';$itemid = 1;$taginstance = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context);$this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context);core_tag_tag::delete_instances_as_record([$taginstance]);$tags = core_tag_tag::get_item_tags($component, $itemtype, $itemid);// We should be left with a single tag, 'bar'.$this->assertCount(1, $tags);$tag = array_shift($tags);$this->assertEquals('bar', $tag->name);}/*** delete_instances_by_id with an empty set of ids should do nothing.*/public function test_delete_instances_by_id_empty_set(): void {$user = $this->getDataGenerator()->create_user();$context = \context_user::instance($user->id);$component = 'core';$itemtype = 'user';$itemid = 1;core_tag_tag::set_item_tags($component, $itemtype, $itemid, $context, ['foo']);// This shouldn't error.core_tag_tag::delete_instances_by_id([]);$tags = core_tag_tag::get_item_tags($component, $itemtype, $itemid);// We should still have one tag.$this->assertCount(1, $tags);}/*** delete_instances_by_id with an id that doesn't exist should do* nothing.*/public function test_delete_instances_by_id_missing_set(): void {$tagnames = ['foo'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user = $this->getDataGenerator()->create_user();$context = \context_user::instance($user->id);$component = 'core';$itemtype = 'user';$itemid = 1;$taginstance = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context);// Delete an instance that doesn't exist should do nothing.core_tag_tag::delete_instances_by_id([$taginstance->id + 1]);$tags = core_tag_tag::get_item_tags($component, $itemtype, $itemid);// We should still have one tag.$this->assertCount(1, $tags);}/*** delete_instances_by_id with a list of all tag instance ids should* leave no tags left.*/public function test_delete_instances_by_id_whole_set(): void {$tagnames = ['foo'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user = $this->getDataGenerator()->create_user();$context = \context_user::instance($user->id);$component = 'core';$itemtype = 'user';$itemid = 1;$taginstance = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context);core_tag_tag::delete_instances_by_id([$taginstance->id]);$tags = core_tag_tag::get_item_tags($component, $itemtype, $itemid);// There should be no tags left.$this->assertEmpty($tags);}/*** delete_instances_by_id with a list of only some tag instance ids should* delete only the given tag instance ids and leave other tag instances.*/public function test_delete_instances_by_id_partial_set(): void {$tagnames = ['foo', 'bar'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user = $this->getDataGenerator()->create_user();$context = \context_user::instance($user->id);$component = 'core';$itemtype = 'user';$itemid = 1;$taginstance = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context);$this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context);core_tag_tag::delete_instances_by_id([$taginstance->id]);$tags = core_tag_tag::get_item_tags($component, $itemtype, $itemid);// We should be left with a single tag, 'bar'.$this->assertCount(1, $tags);$tag = array_shift($tags);$this->assertEquals('bar', $tag->name);}/*** delete_instances should delete all tag instances for a component if given* only the component as a parameter.*/public function test_delete_instances_with_component(): void {global $DB;$tagnames = ['foo', 'bar'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user = $this->getDataGenerator()->create_user();$context = \context_user::instance($user->id);$component = 'core';$itemtype1 = 'user';$itemtype2 = 'course';$itemid = 1;// Add 2 tag instances in the same $component but with different item types.$this->add_tag_instance($tags['foo'], $component, $itemtype1, $itemid, $context);$this->add_tag_instance($tags['bar'], $component, $itemtype2, $itemid, $context);// Delete all tag instances for the component.core_tag_tag::delete_instances($component);$taginstances = $DB->get_records_sql('SELECT * FROM {tag_instance} WHERE component = ?', [$component]);// Both tag instances from the $component should have been deleted even though// they are in different item types.$this->assertEmpty($taginstances);}/*** delete_instances should delete all tag instances for a component if given* only the component as a parameter.*/public function test_delete_instances_with_component_and_itemtype(): void {global $DB;$tagnames = ['foo', 'bar'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user = $this->getDataGenerator()->create_user();$context = \context_user::instance($user->id);$component = 'core';$itemtype1 = 'user';$itemtype2 = 'course';$itemid = 1;// Add 2 tag instances in the same $component but with different item types.$this->add_tag_instance($tags['foo'], $component, $itemtype1, $itemid, $context);$this->add_tag_instance($tags['bar'], $component, $itemtype2, $itemid, $context);// Delete all tag instances for the component and itemtype.core_tag_tag::delete_instances($component, $itemtype1);$taginstances = $DB->get_records_sql('SELECT * FROM {tag_instance} WHERE component = ?', [$component]);// Only the tag instances for $itemtype1 should have been deleted. We// should still be left with the instance for 'bar'.$this->assertCount(1, $taginstances);$taginstance = array_shift($taginstances);$this->assertEquals($itemtype2, $taginstance->itemtype);$this->assertEquals($tags['bar']->id, $taginstance->tagid);}/*** delete_instances should delete all tag instances for a component in a context* if given both the component and context id as parameters.*/public function test_delete_instances_with_component_and_context(): void {global $DB;$tagnames = ['foo', 'bar', 'baz'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user1 = $this->getDataGenerator()->create_user();$user2 = $this->getDataGenerator()->create_user();$context1 = \context_user::instance($user1->id);$context2 = \context_user::instance($user2->id);$component = 'core';$itemtype1 = 'user';$itemtype2 = 'course';$itemid = 1;// Add 3 tag instances in the same $component but with different contexts.$this->add_tag_instance($tags['foo'], $component, $itemtype1, $itemid, $context1);$this->add_tag_instance($tags['bar'], $component, $itemtype2, $itemid, $context1);$this->add_tag_instance($tags['baz'], $component, $itemtype2, $itemid, $context2);// Delete all tag instances for the component and context.core_tag_tag::delete_instances($component, null, $context1->id);$taginstances = $DB->get_records_sql('SELECT * FROM {tag_instance} WHERE component = ?', [$component]);// Only the tag instances for $context1 should have been deleted. We// should still be left with the instance for 'baz'.$this->assertCount(1, $taginstances);$taginstance = array_shift($taginstances);$this->assertEquals($context2->id, $taginstance->contextid);$this->assertEquals($tags['baz']->id, $taginstance->tagid);}/*** delete_instances should delete all tag instances for a component, item type* and context if given the component, itemtype, and context id as parameters.*/public function test_delete_instances_with_component_and_itemtype_and_context(): void {global $DB;$tagnames = ['foo', 'bar', 'baz'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user1 = $this->getDataGenerator()->create_user();$user2 = $this->getDataGenerator()->create_user();$context1 = \context_user::instance($user1->id);$context2 = \context_user::instance($user2->id);$component = 'core';$itemtype1 = 'user';$itemtype2 = 'course';$itemid = 1;// Add 3 tag instances in the same $component but with different contexts.$this->add_tag_instance($tags['foo'], $component, $itemtype1, $itemid, $context1);$this->add_tag_instance($tags['bar'], $component, $itemtype2, $itemid, $context1);$this->add_tag_instance($tags['baz'], $component, $itemtype2, $itemid, $context2);// Delete all tag instances for the component and context.core_tag_tag::delete_instances($component, $itemtype2, $context1->id);$taginstances = $DB->get_records_sql('SELECT * FROM {tag_instance} WHERE component = ?', [$component]);// Only the tag instances for $itemtype2 in $context1 should have been// deleted. We should still be left with the instance for 'foo' and 'baz'.$this->assertCount(2, $taginstances);$fooinstances = array_filter($taginstances, function($instance) use ($tags) {return $instance->tagid == $tags['foo']->id;});$fooinstance = array_shift($fooinstances);$bazinstances = array_filter($taginstances, function($instance) use ($tags) {return $instance->tagid == $tags['baz']->id;});$bazinstance = array_shift($bazinstances);$this->assertNotEmpty($fooinstance);$this->assertNotEmpty($bazinstance);$this->assertEquals($context1->id, $fooinstance->contextid);$this->assertEquals($context2->id, $bazinstance->contextid);}/*** change_instances_context should not change any existing instance contexts* if not given any instance ids.*/public function test_change_instances_context_empty_set(): void {global $DB;$tagnames = ['foo'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user1 = $this->getDataGenerator()->create_user();$user2 = $this->getDataGenerator()->create_user();$context1 = \context_user::instance($user1->id);$context2 = \context_user::instance($user2->id);$component = 'core';$itemtype = 'user';$itemid = 1;$this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);core_tag_tag::change_instances_context([], $context2);$taginstances = $DB->get_records_sql('SELECT * FROM {tag_instance}');// The existing tag instance should not have changed.$this->assertCount(1, $taginstances);$taginstance = array_shift($taginstances);$this->assertEquals($context1->id, $taginstance->contextid);}/*** change_instances_context should only change the context of the given ids.*/public function test_change_instances_context_partial_set(): void {global $DB;$tagnames = ['foo', 'bar'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user1 = $this->getDataGenerator()->create_user();$user2 = $this->getDataGenerator()->create_user();$context1 = \context_user::instance($user1->id);$context2 = \context_user::instance($user2->id);$component = 'core';$itemtype = 'user';$itemid = 1;$fooinstance = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);$barinstance = $this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context1);core_tag_tag::change_instances_context([$fooinstance->id], $context2);// Reload the record.$fooinstance = $DB->get_record('tag_instance', ['id' => $fooinstance->id]);$barinstance = $DB->get_record('tag_instance', ['id' => $barinstance->id]);// Tag 'foo' context should be updated.$this->assertEquals($context2->id, $fooinstance->contextid);// Tag 'bar' context should not be changed.$this->assertEquals($context1->id, $barinstance->contextid);}/*** change_instances_context should change multiple items from multiple contexts.*/public function test_change_instances_context_multiple_contexts(): void {global $DB;$tagnames = ['foo', 'bar'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user1 = $this->getDataGenerator()->create_user();$user2 = $this->getDataGenerator()->create_user();$user3 = $this->getDataGenerator()->create_user();$context1 = \context_user::instance($user1->id);$context2 = \context_user::instance($user2->id);$context3 = \context_user::instance($user3->id);$component = 'core';$itemtype = 'user';$itemid = 1;// Two instances in different contexts.$fooinstance = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);$barinstance = $this->add_tag_instance($tags['bar'], $component, $itemtype, $itemid, $context2);core_tag_tag::change_instances_context([$fooinstance->id, $barinstance->id], $context3);// Reload the record.$fooinstance = $DB->get_record('tag_instance', ['id' => $fooinstance->id]);$barinstance = $DB->get_record('tag_instance', ['id' => $barinstance->id]);// Tag 'foo' context should be updated.$this->assertEquals($context3->id, $fooinstance->contextid);// Tag 'bar' context should be updated.$this->assertEquals($context3->id, $barinstance->contextid);// There shouldn't be any tag instances left in $context1.$context1records = $DB->get_records('tag_instance', ['contextid' => $context1->id]);$this->assertEmpty($context1records);// There shouldn't be any tag instances left in $context2.$context2records = $DB->get_records('tag_instance', ['contextid' => $context2->id]);$this->assertEmpty($context2records);}/*** change_instances_context moving an instance from one context into a context* that already has an instance of that tag should throw an exception.*/public function test_change_instances_context_conflicting_instances(): void {global $DB;$tagnames = ['foo'];$collid = core_tag_collection::get_default();$tags = core_tag_tag::create_if_missing($collid, $tagnames);$user1 = $this->getDataGenerator()->create_user();$user2 = $this->getDataGenerator()->create_user();$context1 = \context_user::instance($user1->id);$context2 = \context_user::instance($user2->id);$component = 'core';$itemtype = 'user';$itemid = 1;// Two instances of 'foo' in different contexts.$fooinstance1 = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context1);$fooinstance2 = $this->add_tag_instance($tags['foo'], $component, $itemtype, $itemid, $context2);// There is already an instance of 'foo' in $context2 so the code// should throw an exception when we try to move another instance there.$this->expectException('Exception');core_tag_tag::change_instances_context([$fooinstance1->id], $context2);}/*** Tests user pagination works correctly for filtered users.*/public function test_user_get_tagged_users(): void {global $DB;// Create some users.$users = [];for ($i = 0; $i < 11; $i++) {$users[] = $this->getDataGenerator()->create_user();}// Create the tag.$tagcollid = core_tag_collection::get_default();$tag = $this->getDataGenerator()->create_tag(['tagcollid' => $tagcollid, 'rawname' => 'bike']);// Add the tag to the users.for ($i = 0; $i < count($users); $i++) {core_tag_tag::add_item_tag('core', 'user', $users[$i]->id,\context_user::instance($users[$i]->id), 'bike');}// The logged-in user.$this->setUser($users[0]);// Get the tagged users.$tag = core_tag_tag::get($tag->id, '*');$taggedusers = user_get_tagged_users($tag);// Ensure it has content.$this->assertEquals(1, $taggedusers->hascontent);// Ensure it should have 1 user and the "more" link is hidden (null).$this->assertEquals(1, $this->count_html_elements($taggedusers->content, 'li'));$this->assertNull($taggedusers->nextpageurl);// Test which users are visible to the logged-in user based on the course.// Create a course to tag.$course = $this->getDataGenerator()->create_course();$studentrole = $DB->get_record('role', ['shortname' => 'student']);for ($i = 0; $i < count($users); $i++) {// Enrol only some users (0, 2, 4, 6, 8, 10).if ($i % 2 === 0) {$this->getDataGenerator()->enrol_user($users[$i]->id, $course->id, $studentrole->id);}}// First page should have 5 users and the "more" link is visible (not null).$taggedusers = user_get_tagged_users(tag: $tag,page: 0,);$this->assertNotNull($taggedusers->nextpageurl);$this->assertEquals(5, $this->count_html_elements($taggedusers->content, 'li'));// Second page should have 1 user and the "more" link is hidden (null).$taggedusers = user_get_tagged_users(tag: $tag,page: 1,);$this->assertNull($taggedusers->nextpageurl);$this->assertEquals(1, $this->count_html_elements($taggedusers->content, 'li'));}/*** Counts the number of specified HTML elements in a given HTML string.** @param string $html The HTML string to be parsed.* @param string $tagname The name of the HTML tag to count (e.g., 'li', 'div').* @return int The number of elements with the specified tag name found in the HTML.*/private function count_html_elements(string $html, string $tagname): int {// Load the HTML into DOMDocument.$dom = new \DOMDocument();libxml_use_internal_errors(true); // Suppress warnings for invalid HTML.$dom->loadHTML($html);libxml_clear_errors();// Find all elements with the specified tag name.$elements = $dom->getElementsByTagName($tagname);// Count the number of elements.return $elements->length;}/*** Help method to return sorted array of names of correlated tags to use for assertions* @param core_tag $tag* @return string*/protected function get_correlated_tags_names($tag) {$rv = array_map(function($t) {return $t->rawname;}, $tag->get_correlated_tags());sort($rv);return array_values($rv);}/*** Add a tag instance.** @param core_tag_tag $tag* @param string $component* @param string $itemtype* @param int $itemid* @param \context $context* @return \stdClass*/protected function add_tag_instance(core_tag_tag $tag, $component, $itemtype, $itemid, $context) {global $DB;$record = (array) $tag->to_object();$record['tagid'] = $record['id'];$record['component'] = $component;$record['itemtype'] = $itemtype;$record['itemid'] = $itemid;$record['contextid'] = $context->id;$record['tiuserid'] = 0;$record['ordering'] = 0;$record['timecreated'] = time();$record['id'] = $DB->insert_record('tag_instance', $record);return (object) $record;}/*** Checks the contents of the a tagcloud** @param array $tags* @param \core_tag\output\tagcloud $tagcloud*/protected function assert_tag_cloud_contains_tags(array $tags, \core_tag\output\tagcloud $tagcloud) {global $PAGE;$renderer = $PAGE->get_renderer('core', 'tag');$result = $tagcloud->export_for_template($renderer);$result = json_decode(json_encode($result), true);$this->assertEqualsCanonicalizing($tags, array_values(array_column($result['tags'], 'name')));}public function test_get_tag_cloud(): void {global $DB, $PAGE;$this->resetAfterTest();// Create a course and a user with tags.$this->getDataGenerator()->create_course(['tags' => 'cats,animals']);$this->getDataGenerator()->create_user(['interests' => 'dogs,animals']);// Default tag cloud contains all three tags.$tagcloud = core_tag_collection::get_tag_cloud(0);$this->assert_tag_cloud_contains_tags(['animals', 'cats', 'dogs'], $tagcloud);// Create two new tag collections, move course tags to C1 and user tags to C2.$c1 = core_tag_collection::create((object)['name' => 'C1', 'searchable' => 1]);$c2 = core_tag_collection::create((object)['name' => 'C2', 'searchable' => 1]);$tagareacourse = $DB->get_record('tag_area', ['component' => 'core', 'itemtype' => 'course'], '*', MUST_EXIST);core_tag_area::update($tagareacourse, ['tagcollid' => $c1->id]);$tagareauser = $DB->get_record('tag_area', ['component' => 'core', 'itemtype' => 'user'], '*', MUST_EXIST);core_tag_area::update($tagareauser, ['tagcollid' => $c2->id]);// Tag cloud still has all tags and you can also search by a collection. Tag 'animals' now has two different view links.$this->assert_tag_cloud_contains_tags(['animals', 'animals', 'cats', 'dogs'], core_tag_collection::get_tag_cloud(0));$this->assert_tag_cloud_contains_tags(['animals', 'cats'], core_tag_collection::get_tag_cloud($c1->id));$this->assert_tag_cloud_contains_tags(['animals', 'dogs'], core_tag_collection::get_tag_cloud($c2->id));// Make user interest tag area not searchable.core_tag_collection::update($c2, ['searchable' => 0]);// Check that the user interest tags do not appear in the tagclouds.$this->assert_tag_cloud_contains_tags(['animals', 'cats'], core_tag_collection::get_tag_cloud(0));$this->assert_tag_cloud_contains_tags(['animals', 'cats'], core_tag_collection::get_tag_cloud($c1->id));$this->assert_tag_cloud_contains_tags([], core_tag_collection::get_tag_cloud($c2->id));}}