Search moodle.org's
Developer Documentation

See Release Notes

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