Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 400 and 402] [Versions 400 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  /**
  18   * Unit tests for WS in tags
  19   *
  20   * @package core_tag
  21   * @category test
  22   * @copyright 2015 Marina Glancy
  23   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  namespace core_tag\external;
  27  
  28  use externallib_advanced_testcase;
  29  
  30  defined('MOODLE_INTERNAL') || die();
  31  
  32  global $CFG;
  33  
  34  require_once($CFG->libdir . '/externallib.php');
  35  require_once($CFG->dirroot . '/webservice/tests/helpers.php');
  36  
  37  class external_test extends externallib_advanced_testcase {
  38      /**
  39       * Test update_categories
  40       */
  41      public function test_update_tags() {
  42          global $DB;
  43          $this->resetAfterTest();
  44          $context = \context_system::instance();
  45  
  46          $originaltag = array(
  47              'isstandard' => 0,
  48              'flag' => 1,
  49              'rawname' => 'test',
  50              'description' => 'desc'
  51          );
  52          $tag = $this->getDataGenerator()->create_tag($originaltag);
  53  
  54          $updatetag = array(
  55              'id' => $tag->id,
  56              'description' => 'Trying to change tag description',
  57              'rawname' => 'Trying to change tag name',
  58              'flag' => 0,
  59              'isstandard' => 1,
  60          );
  61          $gettag = array(
  62              'id' => $tag->id,
  63          );
  64  
  65          // User without any caps can not change anything about a tag but can request [partial] tag data.
  66          $this->setUser($this->getDataGenerator()->create_user());
  67          $result = \core_tag_external::update_tags(array($updatetag));
  68          $result = \external_api::clean_returnvalue(\core_tag_external::update_tags_returns(), $result);
  69          $this->assertEquals($tag->id, $result['warnings'][0]['item']);
  70          $this->assertEquals('nothingtoupdate', $result['warnings'][0]['warningcode']);
  71          $this->assertEquals($originaltag['rawname'], $DB->get_field('tag', 'rawname',
  72              array('id' => $tag->id)));
  73          $this->assertEquals($originaltag['description'], $DB->get_field('tag', 'description',
  74              array('id' => $tag->id)));
  75  
  76          $result = \core_tag_external::get_tags(array($gettag));
  77          $result = \external_api::clean_returnvalue(\core_tag_external::get_tags_returns(), $result);
  78          $this->assertEquals($originaltag['rawname'], $result['tags'][0]['rawname']);
  79          $this->assertEquals($originaltag['description'], $result['tags'][0]['description']);
  80          $this->assertNotEmpty($result['tags'][0]['viewurl']);
  81          $this->assertArrayNotHasKey('changetypeurl', $result['tags'][0]);
  82          $this->assertArrayNotHasKey('changeflagurl', $result['tags'][0]);
  83          $this->assertArrayNotHasKey('flag', $result['tags'][0]);
  84          $this->assertArrayNotHasKey('official', $result['tags'][0]);
  85          $this->assertArrayNotHasKey('isstandard', $result['tags'][0]);
  86  
  87          // User with editing only capability can change description but not the tag name.
  88          $roleid = $this->assignUserCapability('moodle/tag:edit', $context->id);
  89          $result = \core_tag_external::update_tags(array($updatetag));
  90          $result = \external_api::clean_returnvalue(\core_tag_external::update_tags_returns(), $result);
  91          $this->assertEmpty($result['warnings']);
  92  
  93          $result = \core_tag_external::get_tags(array($gettag));
  94          $result = \external_api::clean_returnvalue(\core_tag_external::get_tags_returns(), $result);
  95          $this->assertEquals($updatetag['id'], $result['tags'][0]['id']);
  96          $this->assertEquals($updatetag['description'], $result['tags'][0]['description']);
  97          $this->assertEquals($originaltag['rawname'], $result['tags'][0]['rawname']);
  98          $this->assertArrayNotHasKey('flag', $result['tags'][0]); // 'Flag' is not available unless 'moodle/tag:manage' cap exists.
  99          $this->assertEquals(0, $result['tags'][0]['official']);
 100          $this->assertEquals(0, $result['tags'][0]['isstandard']);
 101          $this->assertEquals($originaltag['rawname'], $DB->get_field('tag', 'rawname',
 102                  array('id' => $tag->id)));
 103          $this->assertEquals($updatetag['description'], $DB->get_field('tag', 'description',
 104                  array('id' => $tag->id)));
 105  
 106          // User with editing and manage cap can also change the tag name,
 107          // make it standard and reset flag.
 108          assign_capability('moodle/tag:manage', CAP_ALLOW, $roleid, $context->id);
 109          $this->assertTrue(has_capability('moodle/tag:manage', $context));
 110          $result = \core_tag_external::update_tags(array($updatetag));
 111          $result = \external_api::clean_returnvalue(\core_tag_external::update_tags_returns(), $result);
 112          $this->assertEmpty($result['warnings']);
 113  
 114          $result = \core_tag_external::get_tags(array($gettag));
 115          $result = \external_api::clean_returnvalue(\core_tag_external::get_tags_returns(), $result);
 116          $this->assertEquals($updatetag['id'], $result['tags'][0]['id']);
 117          $this->assertEquals($updatetag['rawname'], $result['tags'][0]['rawname']);
 118          $this->assertEquals(\core_text::strtolower($updatetag['rawname']), $result['tags'][0]['name']);
 119          $this->assertEquals($updatetag['flag'], $result['tags'][0]['flag']);
 120          $this->assertEquals($updatetag['isstandard'], $result['tags'][0]['official']);
 121          $this->assertEquals($updatetag['isstandard'], $result['tags'][0]['isstandard']);
 122          $this->assertEquals($updatetag['rawname'], $DB->get_field('tag', 'rawname',
 123                  array('id' => $tag->id)));
 124          $this->assertEquals(1, $DB->get_field('tag', 'isstandard', array('id' => $tag->id)));
 125  
 126          // Updating and getting non-existing tag.
 127          $nonexistingtag = array(
 128              'id' => 123,
 129              'description' => 'test'
 130          );
 131          $getnonexistingtag = array(
 132              'id' => 123,
 133          );
 134          $result = \core_tag_external::update_tags(array($nonexistingtag));
 135          $result = \external_api::clean_returnvalue(\core_tag_external::update_tags_returns(), $result);
 136          $this->assertEquals(123, $result['warnings'][0]['item']);
 137          $this->assertEquals('tagnotfound', $result['warnings'][0]['warningcode']);
 138  
 139          $result = \core_tag_external::get_tags(array($getnonexistingtag));
 140          $result = \external_api::clean_returnvalue(\core_tag_external::get_tags_returns(), $result);
 141          $this->assertEmpty($result['tags']);
 142          $this->assertEquals(123, $result['warnings'][0]['item']);
 143          $this->assertEquals('tagnotfound', $result['warnings'][0]['warningcode']);
 144  
 145          // Attempt to update a tag to the name that is reserved.
 146          $anothertag = $this->getDataGenerator()->create_tag(array('rawname' => 'Mytag'));
 147          $updatetag2 = array('id' => $tag->id, 'rawname' => 'MYTAG');
 148          $result = \core_tag_external::update_tags(array($updatetag2));
 149          $result = \external_api::clean_returnvalue(\core_tag_external::update_tags_returns(), $result);
 150          $this->assertEquals($tag->id, $result['warnings'][0]['item']);
 151          $this->assertEquals('namesalreadybeeingused', $result['warnings'][0]['warningcode']);
 152      }
 153  
 154      /**
 155       * Test update_inplace_editable()
 156       */
 157      public function test_update_inplace_editable() {
 158          global $CFG, $DB, $PAGE;
 159          require_once($CFG->dirroot . '/lib/external/externallib.php');
 160  
 161          $this->resetAfterTest(true);
 162          $tag = $this->getDataGenerator()->create_tag();
 163          $this->setUser($this->getDataGenerator()->create_user());
 164  
 165          // Call service for core_tag component without necessary permissions.
 166          try {
 167              \core_external::update_inplace_editable('core_tag', 'tagname', $tag->id, 'new tag name');
 168              $this->fail('Exception expected');
 169          } catch (\moodle_exception $e) {
 170              $this->assertEquals('Sorry, but you do not currently have permissions to do that (Manage all tags).',
 171                      $e->getMessage());
 172          }
 173  
 174          // Change to admin user and make sure that tag name can be updated using web service update_inplace_editable().
 175          $this->setAdminUser();
 176          $res = \core_external::update_inplace_editable('core_tag', 'tagname', $tag->id, 'New tag name');
 177          $res = \external_api::clean_returnvalue(\core_external::update_inplace_editable_returns(), $res);
 178          $this->assertEquals('New tag name', $res['value']);
 179          $this->assertEquals('New tag name', $DB->get_field('tag', 'rawname', array('id' => $tag->id)));
 180  
 181          // Call callback core_tag_inplace_editable() directly.
 182          $tmpl = component_callback('core_tag', 'inplace_editable', array('tagname', $tag->id, 'Rename me again'));
 183          $this->assertInstanceOf('core\output\inplace_editable', $tmpl);
 184          $res = $tmpl->export_for_template($PAGE->get_renderer('core'));
 185          $this->assertEquals('Rename me again', $res['value']);
 186          $this->assertEquals('Rename me again', $DB->get_field('tag', 'rawname', array('id' => $tag->id)));
 187      }
 188  
 189      /**
 190       * Test get_tagindex_per_area.
 191       */
 192      public function test_get_tagindex_per_area() {
 193          global $USER;
 194          $this->resetAfterTest(true);
 195  
 196          // Create tags for two user profiles and one course.
 197          $this->setAdminUser();
 198          $context = \context_user::instance($USER->id);
 199          \core_tag_tag::set_item_tags('core', 'user', $USER->id, $context, array('test'));
 200  
 201          $this->setUser($this->getDataGenerator()->create_user());
 202          $context = \context_user::instance($USER->id);
 203          \core_tag_tag::set_item_tags('core', 'user', $USER->id, $context, array('test'));
 204  
 205          $course = $this->getDataGenerator()->create_course();
 206          $context = \context_course::instance($course->id);
 207          \core_tag_tag::set_item_tags('core', 'course', $course->id, $context, array('test'));
 208  
 209          $tag = \core_tag_tag::get_by_name(0, 'test');
 210  
 211          // First, search by id.
 212          $result = \core_tag_external::get_tagindex_per_area(array('id' => $tag->id));
 213          $result = \external_api::clean_returnvalue(\core_tag_external::get_tagindex_per_area_returns(), $result);
 214          $this->assertCount(2, $result); // Two different areas: course and user.
 215          $this->assertEquals($tag->id, $result[0]['tagid']);
 216          $this->assertEquals('course', $result[0]['itemtype']);
 217          $this->assertEquals($tag->id, $result[1]['tagid']);
 218          $this->assertEquals('user', $result[1]['itemtype']);
 219  
 220          // Now, search by name.
 221          $result = \core_tag_external::get_tagindex_per_area(array('tag' => 'test'));
 222          $result = \external_api::clean_returnvalue(\core_tag_external::get_tagindex_per_area_returns(), $result);
 223          $this->assertCount(2, $result); // Two different areas: course and user.
 224          $this->assertEquals($tag->id, $result[0]['tagid']);
 225          $this->assertEquals('course', $result[0]['itemtype']);
 226          $this->assertEquals($tag->id, $result[1]['tagid']);
 227          $this->assertEquals('user', $result[1]['itemtype']);
 228  
 229          // Filter by tag area.
 230          $result = \core_tag_external::get_tagindex_per_area(array('tag' => 'test', 'ta' => $result[0]['ta']));
 231          $result = \external_api::clean_returnvalue(\core_tag_external::get_tagindex_per_area_returns(), $result);
 232          $this->assertCount(1, $result); // Just the given area.
 233          $this->assertEquals($tag->id, $result[0]['tagid']);
 234          $this->assertEquals('course', $result[0]['itemtype']);
 235  
 236          // Now, search by tag collection (use default).
 237          $result = \core_tag_external::get_tagindex_per_area(array('id' => $tag->id, 'tc' => 1));
 238          $result = \external_api::clean_returnvalue(\core_tag_external::get_tagindex_per_area_returns(), $result);
 239          $this->assertCount(2, $result); // Two different areas: course and user.
 240      }
 241  
 242      /**
 243       * Test get_tag_areas.
 244       */
 245      public function test_get_tag_areas() {
 246          global $DB;
 247          $this->resetAfterTest(true);
 248  
 249          $this->setAdminUser();
 250          $result = \core_tag_external::get_tag_areas();
 251          $result = \external_api::clean_returnvalue(\core_tag_external::get_tag_areas_returns(), $result);
 252          $areas = $DB->get_records('tag_area');
 253          $this->assertCount(count($areas), $result['areas']);
 254          foreach ($result['areas'] as $area) {
 255              $this->assertEquals($areas[$area['id']]->component, $area['component']);
 256              $this->assertEquals($areas[$area['id']]->itemtype, $area['itemtype']);
 257          }
 258      }
 259  
 260      /**
 261       * Test get_tag_collections.
 262       */
 263      public function test_get_tag_collections() {
 264          global $DB;
 265          $this->resetAfterTest(true);
 266  
 267          // Create new tag collection.
 268          $data = (object) array('name' => 'new tag coll');
 269          \core_tag_collection::create($data);
 270  
 271          $this->setAdminUser();
 272          $result = \core_tag_external::get_tag_collections();
 273          $result = \external_api::clean_returnvalue(\core_tag_external::get_tag_collections_returns(), $result);
 274  
 275          $collections = $DB->get_records('tag_coll');
 276          $this->assertCount(count($collections), $result['collections']);
 277          foreach ($result['collections'] as $collection) {
 278              $this->assertEquals($collections[$collection['id']]->component, $collection['component']);
 279              $this->assertEquals($collections[$collection['id']]->name, $collection['name']);
 280          }
 281      }
 282  
 283      /**
 284       * Test get_tag_cloud.
 285       */
 286      public function test_get_tag_cloud() {
 287          global $USER, $DB;
 288          $this->resetAfterTest(true);
 289  
 290          // Create tags for two user profiles, a post and one course.
 291          $this->setAdminUser();
 292          $context = \context_user::instance($USER->id);
 293          \core_tag_tag::set_item_tags('core', 'user', $USER->id, $context, array('Cats', 'Dogs'));
 294  
 295          $this->setUser($this->getDataGenerator()->create_user());
 296          $context = \context_user::instance($USER->id);
 297          \core_tag_tag::set_item_tags('core', 'user', $USER->id, $context, array('Mice'));
 298  
 299          $course = $this->getDataGenerator()->create_course();
 300          $coursecontext = \context_course::instance($course->id);
 301          \core_tag_tag::set_item_tags('core', 'course', $course->id, $coursecontext, array('Cats'));
 302  
 303          $post = new \stdClass();
 304          $post->userid = $USER->id;
 305          $post->content = 'test post content text';
 306          $post->id = $DB->insert_record('post', $post);
 307          $context = \context_system::instance();
 308          \core_tag_tag::set_item_tags('core', 'post', $post->id, $context, array('Horses', 'Cats'));
 309  
 310          // First, retrieve complete cloud.
 311          $result = \core_tag_external::get_tag_cloud();
 312          $result = \external_api::clean_returnvalue(\core_tag_external::get_tag_cloud_returns(), $result);
 313          $this->assertCount(4, $result['tags']); // Four different tags: Cats, Dogs, Mice, Horses.
 314          $this->assertEquals(4, $result['tagscount']);
 315          $this->assertEquals(4, $result['totalcount']);
 316  
 317          foreach ($result['tags'] as $tag) {
 318              if ($tag['name'] == 'Cats') {
 319                  $this->assertEquals(3, $tag['count']);
 320              } else {
 321                  $this->assertEquals(1, $tag['count']);
 322              }
 323          }
 324  
 325          // Test filter by collection, pagination and sorting.
 326          $defaultcoll = \core_tag_collection::get_default();
 327          $result = \core_tag_external::get_tag_cloud($defaultcoll, false, 2, 'count');
 328          $result = \external_api::clean_returnvalue(\core_tag_external::get_tag_cloud_returns(), $result);
 329          $this->assertCount(2, $result['tags']); // Only two tags.
 330          $this->assertEquals(2, $result['tagscount']);
 331          $this->assertEquals(4, $result['totalcount']);
 332          $this->assertEquals('Dogs', $result['tags'][0]['name']); // Lower count first.
 333  
 334          // Test search.
 335          $result = \core_tag_external::get_tag_cloud(0, false, 150, 'name', 'Mice');
 336          $result = \external_api::clean_returnvalue(\core_tag_external::get_tag_cloud_returns(), $result);
 337          $this->assertCount(1, $result['tags']); // Only the searched tags.
 338          $this->assertEquals(1, $result['tagscount']);
 339          $this->assertEquals(1, $result['totalcount']); // When searching, the total is always for the search.
 340          $this->assertEquals('Mice', $result['tags'][0]['name']);
 341  
 342          $result = \core_tag_external::get_tag_cloud(0, false, 150, 'name', 'Conejo');
 343          $result = \external_api::clean_returnvalue(\core_tag_external::get_tag_cloud_returns(), $result);
 344          $this->assertCount(0, $result['tags']); // Nothing found.
 345          $this->assertEquals(0, $result['tagscount']);
 346          $this->assertEquals(0, $result['totalcount']); // When searching, the total is always for the search.
 347  
 348          // Test standard filtering.
 349          $micetag = \core_tag_tag::get_by_name($defaultcoll, 'Mice', '*');
 350          $micetag->update(array('isstandard' => 1));
 351  
 352          $result = \core_tag_external::get_tag_cloud(0, true);
 353          $result = \external_api::clean_returnvalue(\core_tag_external::get_tag_cloud_returns(), $result);
 354          $this->assertCount(1, $result['tags']);
 355          $this->assertEquals(1, $result['tagscount']);
 356          $this->assertEquals(1, $result['totalcount']); // When searching, the total is always for the search.
 357          $this->assertEquals('Mice', $result['tags'][0]['name']);
 358  
 359          // Test course context filtering.
 360          $result = \core_tag_external::get_tag_cloud(0, false, 150, 'name', '', 0, $coursecontext->id);
 361          $result = \external_api::clean_returnvalue(\core_tag_external::get_tag_cloud_returns(), $result);
 362          $this->assertCount(1, $result['tags']);
 363          $this->assertEquals(1, $result['tagscount']);
 364          $this->assertEquals(1, $result['totalcount']); // When searching, the total is always for the search.
 365          $this->assertEquals('Cats', $result['tags'][0]['name']);
 366  
 367          // Complete system context.
 368          $result = \core_tag_external::get_tag_cloud(0, false, 150, 'name', '', 0, \context_system::instance()->id);
 369          $result = \external_api::clean_returnvalue(\core_tag_external::get_tag_cloud_returns(), $result);
 370          $this->assertCount(4, $result['tags']);
 371          $this->assertEquals(4, $result['tagscount']);
 372  
 373          // Just system context - avoid children.
 374          $result = \core_tag_external::get_tag_cloud(0, false, 150, 'name', '', 0, \context_system::instance()->id, 0);
 375          $result = \external_api::clean_returnvalue(\core_tag_external::get_tag_cloud_returns(), $result);
 376          $this->assertCount(2, $result['tags']);
 377          $this->assertEquals(2, $result['tagscount']); // Horses and Cats.
 378          $this->assertEquals('Cats', $result['tags'][0]['name']);
 379          $this->assertEquals('Horses', $result['tags'][1]['name']);
 380      }
 381  }