Merge branch 'MDL-64656-master' of git://github.com/jleyva/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Thu, 11 Apr 2019 18:12:13 +0000 (20:12 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Thu, 11 Apr 2019 18:12:13 +0000 (20:12 +0200)
28 files changed:
blog/classes/external/post_exporter.php
blog/tests/external_test.php
blog/upgrade.txt
course/externallib.php
course/upgrade.txt
lib/db/services.php
mod/book/lib.php
mod/book/tests/lib_test.php
mod/book/upgrade.txt
mod/data/classes/external/record_exporter.php
mod/data/tests/externallib_test.php
mod/data/upgrade.txt
mod/forum/externallib.php
mod/forum/tests/externallib_test.php
mod/forum/upgrade.txt
mod/glossary/classes/external.php
mod/glossary/tests/external_test.php
mod/glossary/upgrade.txt
mod/wiki/classes/external.php
mod/wiki/tests/externallib_test.php
mod/wiki/upgrade.txt
tag/classes/external.php
tag/classes/external/tag_area_exporter.php [new file with mode: 0644]
tag/classes/external/tag_collection_exporter.php [new file with mode: 0644]
tag/classes/external/tag_item_exporter.php [new file with mode: 0644]
tag/classes/external/util.php [new file with mode: 0644]
tag/tests/external_test.php
version.php

index 3d69299..e608c32 100644 (file)
@@ -29,6 +29,7 @@ use external_util;
 use external_files;
 use renderer_base;
 use context_system;
+use core_tag\external\tag_item_exporter;
 
 /**
  * Class for exporting a blog post (entry).
@@ -171,6 +172,12 @@ class post_exporter extends exporter {
                 'multiple' => true,
                 'optional' => true
             ),
+            'tags' => array(
+                'type' => tag_item_exporter::read_properties_definition(),
+                'description' => 'Tags.',
+                'multiple' => true,
+                'optional' => true,
+            ),
         );
     }
 
@@ -179,6 +186,12 @@ class post_exporter extends exporter {
 
         $values['summaryfiles'] = external_util::get_area_files($context->id, 'blog', 'post', $this->data->id);
         $values['attachmentfiles'] = external_util::get_area_files($context->id, 'blog', 'attachment', $this->data->id);
+        if ($this->data->module == 'blog_external') {
+            // For external blogs, the content field has the external blog id.
+            $values['tags'] = \core_tag\external\util::get_item_tags('core', 'blog_external', $this->data->content);
+        } else {
+            $values['tags'] = \core_tag\external\util::get_item_tags('core', 'post', $this->data->id);
+        }
 
         return $values;
     }
index f5822f4..5e23d5f 100644 (file)
@@ -108,6 +108,9 @@ class core_blog_external_testcase extends advanced_testcase {
         $result = core_blog\external::get_entries();
         $result = external_api::clean_returnvalue(core_blog\external::get_entries_returns(), $result);
         $this->assertCount(1, $result['entries']);
+        $this->assertCount(1, $result['entries'][0]['tags']);
+        $this->assertEquals('tag1', $result['entries'][0]['tags'][0]['rawname']);
+
         $this->assertEquals($this->postid, $result['entries'][0]['id']);
     }
 
@@ -141,6 +144,9 @@ class core_blog_external_testcase extends advanced_testcase {
         $result = core_blog\external::get_entries();
         $result = external_api::clean_returnvalue(core_blog\external::get_entries_returns(), $result);
         $this->assertCount(1, $result['entries']);
+        $this->assertCount(1, $result['entries'][0]['tags']);
+        $this->assertEquals('tag1', $result['entries'][0]['tags'][0]['rawname']);
+
         $this->assertEquals($this->postid, $result['entries'][0]['id']);
     }
 
@@ -331,6 +337,9 @@ class core_blog_external_testcase extends advanced_testcase {
         $result = external_api::clean_returnvalue(core_blog\external::get_entries_returns(), $result);
         $this->assertCount(2, $result['entries']);
         $this->assertEquals(2, $result['totalentries']);
+        $this->assertCount(0, $result['entries'][0]['tags']);
+        $this->assertCount(1, $result['entries'][1]['tags']);
+        $this->assertEquals('tag1', $result['entries'][1]['tags'][0]['rawname']);
 
         $result = core_blog\external::get_entries(array(), 0, 1);
         $result = external_api::clean_returnvalue(core_blog\external::get_entries_returns(), $result);
@@ -365,6 +374,8 @@ class core_blog_external_testcase extends advanced_testcase {
         $result = core_blog\external::get_entries(array(array('name' => 'courseid', 'value' => $this->courseid)));
         $result = external_api::clean_returnvalue(core_blog\external::get_entries_returns(), $result);
         $this->assertCount(1, $result['entries']);
+        $this->assertCount(1, $result['entries'][0]['tags']);
+        $this->assertEquals('tag1', $result['entries'][0]['tags'][0]['rawname']);
 
         // There is no entry associated with a wrong course.
         $result = core_blog\external::get_entries(array(array('name' => 'courseid', 'value' => $anothercourse->id)));
index 598a275..644ea08 100644 (file)
@@ -1,3 +1,9 @@
+This files describes API changes in /blog/* ,
+information provided here is intended especially for developers.
+
+=== 3.7 ===
+  * External function get_entries now returns an additional field "tags" returning the post tags.
+
 === 2.7 ===
 
 * blog_entry->add_associations() does not accept any params.
index 8bfa9fd..0c6456b 100644 (file)
@@ -494,6 +494,10 @@ class core_course_external extends external_api {
                                                   'userid' => new external_value(PARAM_INT, 'User who added this content to moodle'),
                                                   'author' => new external_value(PARAM_TEXT, 'Content owner'),
                                                   'license' => new external_value(PARAM_TEXT, 'Content license'),
+                                                  'tags' => new external_multiple_structure(
+                                                       \core_tag\external\tag_item_exporter::get_read_structure(), 'Tags',
+                                                            VALUE_OPTIONAL
+                                                   ),
                                               )
                                           ), VALUE_DEFAULT, array()
                                       ),
index efa1b2f..090b88a 100644 (file)
@@ -6,6 +6,8 @@ information provided here is intended especially for developers.
  * External function core_course_external::get_course_contents new returns the following additional completiondata field:
    - valueused (indicates whether the completion state affects the availability of other content)
  * External function core_course_external::get_course_contents now returns a new contentsinfo field with summary files information.
+ * External function core_course_external::get_course_contents now returns an additional field "tags" returning the content tags.
+
 
 === 3.6 ===
 
index ec4dd78..95a17be 100644 (file)
@@ -1525,6 +1525,7 @@ $functions = array(
         'description' => 'Gets tag index page for one tag and one tag area',
         'type' => 'read',
         'ajax' => true,
+        'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
     'core_tag_get_tags' => array(
         'classname' => 'core_tag_external',
@@ -1540,6 +1541,34 @@ $functions = array(
         'type' => 'write',
         'ajax' => true,
     ),
+    'core_tag_get_tagindex_per_area' => array(
+        'classname' => 'core_tag_external',
+        'methodname' => 'get_tagindex_per_area',
+        'description' => 'Gets tag index page per different areas.',
+        'type' => 'read',
+        'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
+    ),
+    'core_tag_get_tag_areas' => array(
+        'classname' => 'core_tag_external',
+        'methodname' => 'get_tag_areas',
+        'description' => 'Retrieves existing tag areas.',
+        'type' => 'read',
+        'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
+    ),
+    'core_tag_get_tag_collections' => array(
+        'classname' => 'core_tag_external',
+        'methodname' => 'get_tag_collections',
+        'description' => 'Retrieves existing tag collections.',
+        'type' => 'read',
+        'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
+    ),
+    'core_tag_get_tag_cloud' => array(
+        'classname' => 'core_tag_external',
+        'methodname' => 'get_tag_cloud',
+        'description' => 'Retrieves a tag cloud for the given collection and/or query search.',
+        'type' => 'read',
+        'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
+    ),
     'core_update_inplace_editable' => array(
         'classname' => 'core_external',
         'methodname' => 'update_inplace_editable',
index f5bd20f..0b4887f 100644 (file)
@@ -611,6 +611,7 @@ function book_export_contents($cm, $baseurl) {
         $chapterindexfile['userid']       = null;
         $chapterindexfile['author']       = null;
         $chapterindexfile['license']      = null;
+        $chapterindexfile['tags']         = \core_tag\external\util::get_item_tags('mod_book', 'book_chapters', $chapter->id);
         $contents[] = $chapterindexfile;
 
         // Chapter files (images usually).
index 51fe25f..49f740a 100644 (file)
@@ -45,7 +45,8 @@ class mod_book_lib_testcase extends advanced_testcase {
     }
 
     public function test_export_contents() {
-        global $DB;
+        global $DB, $CFG;
+        require_once($CFG->dirroot . '/course/externallib.php');
 
         $user = $this->getDataGenerator()->create_user();
         $course = $this->getDataGenerator()->create_course(array('enablecomment' => 1));
@@ -57,7 +58,10 @@ class mod_book_lib_testcase extends advanced_testcase {
         $cm = get_coursemodule_from_id('book', $book->cmid);
 
         $bookgenerator = $this->getDataGenerator()->get_plugin_generator('mod_book');
-        $chapter1 = $bookgenerator->create_chapter(array('bookid' => $book->id, "pagenum" => 1));
+        $chapter1 = $bookgenerator->create_chapter(array('bookid' => $book->id, "pagenum" => 1,
+            'tags' => array('Cats', 'Dogs')));
+        $tag = core_tag_tag::get_by_name(0, 'Cats');
+
         $chapter2 = $bookgenerator->create_chapter(array('bookid' => $book->id, "pagenum" => 2));
         $subchapter = $bookgenerator->create_chapter(array('bookid' => $book->id, "pagenum" => 3, "subchapter" => 1));
         $chapter3 = $bookgenerator->create_chapter(array('bookid' => $book->id, "pagenum" => 4, "hidden" => 1));
@@ -71,11 +75,24 @@ class mod_book_lib_testcase extends advanced_testcase {
         $this->assertEquals('structure', $contents[0]['filename']);
         $this->assertEquals('index.html', $contents[1]['filename']);
         $this->assertEquals('Chapter 1', $contents[1]['content']);
+        $this->assertCount(2, $contents[1]['tags']);
+        $this->assertEquals('Cats', $contents[1]['tags'][0]['rawname']);
+        $this->assertEquals($tag->id, $contents[1]['tags'][0]['id']);
+        $this->assertEquals('Dogs', $contents[1]['tags'][1]['rawname']);
         $this->assertEquals('index.html', $contents[2]['filename']);
         $this->assertEquals('Chapter 2', $contents[2]['content']);
         $this->assertEquals('index.html', $contents[3]['filename']);
         $this->assertEquals('Chapter 3', $contents[3]['content']);
 
+        // Now, test the function via the external API.
+        $contents = core_course_external::get_course_contents($course->id, array());
+        $contents = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $contents);
+        $this->assertEquals('book', $contents[0]['modules'][0]['modname']);
+        $this->assertEquals($cm->id, $contents[0]['modules'][0]['id']);
+        $this->assertCount(2, $contents[0]['modules'][0]['contents'][1]['tags']);
+        $this->assertEquals('Cats', $contents[0]['modules'][0]['contents'][1]['tags'][0]['rawname']);
+        $this->assertEquals('Dogs', $contents[0]['modules'][0]['contents'][1]['tags'][1]['rawname']);
+
         // Test empty book.
         $emptybook = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
         $cm = get_coursemodule_from_id('book', $emptybook->cmid);
index 53f766d..1fb9028 100644 (file)
@@ -1,5 +1,9 @@
 This files describes API changes in the book code.
 
+=== 3.7 ===
+
+* book_export_contents() callback now returns tags information for every chapter.
+
 === 3.1 ===
 
 * The following functions, previously used (exclusively) by upgrade steps are not available
index 7fecd44..428b44d 100644 (file)
@@ -27,6 +27,7 @@ defined('MOODLE_INTERNAL') || die();
 use core\external\exporter;
 use renderer_base;
 use core_user;
+use core_tag\external\tag_item_exporter;
 
 /**
  * Class for exporting record data.
@@ -102,6 +103,12 @@ class record_exporter extends exporter {
                 'multiple' => true,
                 'optional' => true,
             ),
+            'tags' => array(
+                'type' => tag_item_exporter::read_properties_definition(),
+                'description' => 'Tags.',
+                'multiple' => true,
+                'optional' => true,
+            ),
         );
     }
 
@@ -128,6 +135,9 @@ class record_exporter extends exporter {
             }
             $values['contents'] = $contents;
         }
+
+        $values['tags'] = \core_tag\external\util::get_item_tags('mod_data', 'data_records', $this->data->id);
+
         return $values;
     }
 }
index 51ad2d4..f34f032 100644 (file)
@@ -412,9 +412,9 @@ class mod_data_external_testcase extends externallib_advanced_testcase {
         }
 
         $this->setUser($this->student1);
-        $entry11 = $generator->create_entry($this->database, $fieldcontents, $this->group1->id);
+        $entry11 = $generator->create_entry($this->database, $fieldcontents, $this->group1->id, ['Cats', 'Dogs']);
         $this->setUser($this->student2);
-        $entry12 = $generator->create_entry($this->database, $fieldcontents, $this->group1->id);
+        $entry12 = $generator->create_entry($this->database, $fieldcontents, $this->group1->id, ['Cats']);
         $entry13 = $generator->create_entry($this->database, $fieldcontents, $this->group1->id);
         // Entry not in group.
         $entry14 = $generator->create_entry($this->database, $fieldcontents, 0);
@@ -447,10 +447,13 @@ class mod_data_external_testcase extends externallib_advanced_testcase {
         $this->assertCount(3, $result['entries']);
         $this->assertEquals(3, $result['totalcount']);
         $this->assertEquals($entry11, $result['entries'][0]['id']);
+        $this->assertCount(2, $result['entries'][0]['tags']);
         $this->assertEquals($this->student1->id, $result['entries'][0]['userid']);
         $this->assertEquals($this->group1->id, $result['entries'][0]['groupid']);
         $this->assertEquals($this->database->id, $result['entries'][0]['dataid']);
         $this->assertEquals($entry12, $result['entries'][1]['id']);
+        $this->assertCount(1, $result['entries'][1]['tags']);
+        $this->assertEquals('Cats', $result['entries'][1]['tags'][0]['rawname']);
         $this->assertEquals($this->student2->id, $result['entries'][1]['userid']);
         $this->assertEquals($this->group1->id, $result['entries'][1]['groupid']);
         $this->assertEquals($this->database->id, $result['entries'][1]['dataid']);
index 28f7a44..e776b4d 100644 (file)
@@ -1,6 +1,9 @@
 This files describes API changes in /mod/data - plugins,
 information provided here is intended especially for developers.
 
+=== 3.7 ===
+* External functions get_entries, get_entry and search_entries now return an additional field "tags" containing the entry tags.
+
 === 3.4 ===
 * External function mod_data_external::search_entries() now returns the maxcount field: Total count of records that the user could
     see in the database (if all the search criterias were removed).
index a53e2c4..72bc014 100644 (file)
@@ -421,6 +421,8 @@ class mod_forum_external extends external_api {
             if (!empty($messageinlinefiles)) {
                 $post->messageinlinefiles = $messageinlinefiles;
             }
+            // Post tags.
+            $post->tags = \core_tag\external\util::get_item_tags('mod_forum', 'forum_posts', $post->id);
 
             $posts[] = $post;
         }
@@ -467,6 +469,9 @@ class mod_forum_external extends external_api {
                                 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.', VALUE_OPTIONAL),
                                 'deleted' => new external_value(PARAM_BOOL, 'This post has been removed.'),
                                 'isprivatereply' => new external_value(PARAM_BOOL, 'The post is a private reply'),
+                                'tags' => new external_multiple_structure(
+                                    \core_tag\external\tag_item_exporter::get_read_structure(), 'Tags', VALUE_OPTIONAL
+                                ),
                             ), 'post'
                         )
                     ),
index 7017f17..96a4eeb 100644 (file)
@@ -260,6 +260,7 @@ class mod_forum_external_testcase extends externallib_advanced_testcase {
 
         $record->parent = $discussion1reply1->id;
         $record->userid = $user3->id;
+        $record->tags = array('Cats', 'Dogs');
         $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
 
         // Enrol the user in the  course.
@@ -311,7 +312,12 @@ class mod_forum_external_testcase extends externallib_advanced_testcase {
             'userpictureurl' => '',
             'deleted' => false,
             'isprivatereply' => false,
+            'tags' => \core_tag\external\util::get_item_tags('mod_forum', 'forum_posts', $discussion1reply2->id),
         );
+        // Cast to expected.
+        $this->assertCount(2, $expectedposts['posts'][0]['tags']);
+        $expectedposts['posts'][0]['tags'][0]['isstandard'] = (bool) $expectedposts['posts'][0]['tags'][0]['isstandard'];
+        $expectedposts['posts'][0]['tags'][1]['isstandard'] = (bool) $expectedposts['posts'][0]['tags'][1]['isstandard'];
 
         $expectedposts['posts'][] = array(
             'id' => $discussion1reply1->id,
@@ -348,6 +354,7 @@ class mod_forum_external_testcase extends externallib_advanced_testcase {
             'userpictureurl' => '',
             'deleted' => false,
             'isprivatereply' => false,
+            'tags' => array(),
         );
 
         // Test a discussion with two additional posts (total 3 posts).
index f275f74..59ad1a4 100644 (file)
@@ -9,8 +9,8 @@ information provided here is intended especially for developers.
   * The get_forum_discussion_posts web service has been deprecated in favour of get_discussion_posts.
   * The forum_count_replies function has been deprecated in favour of get_reply_count_for_post_id_in_discussion_id in
     the Post vault.
-  * External function get_forums_by_courses now returns two additional fields "duedate" and "cutoffdate" containing the due date and the cutoff date
-    for posting to the forums respectively.
+  * External function get_forums_by_courses now returns two additional fields "duedate" and "cutoffdate" containing the due date and the cutoff date for posting to the forums respectively.
+  * External function get_forum_discussion_posts now returns an additional field "tags" returning the post tags.
 
 === 3.6 ===
 
index e900e04..e60ef92 100644 (file)
@@ -101,6 +101,9 @@ class mod_glossary_external extends external_api {
             'casesensitive' => new external_value(PARAM_BOOL, 'When true, the matching is case sensitive'),
             'fullmatch' => new external_value(PARAM_BOOL, 'When true, the matching is done on full words only'),
             'approved' => new external_value(PARAM_BOOL, 'Whether the entry was approved'),
+            'tags' => new external_multiple_structure(
+                \core_tag\external\tag_item_exporter::get_read_structure(), 'Tags', VALUE_OPTIONAL
+            ),
         );
 
         if ($includecat) {
@@ -149,6 +152,8 @@ class mod_glossary_external extends external_api {
         if (!empty($definitioninlinefiles)) {
             $entry->definitioninlinefiles = $definitioninlinefiles;
         }
+
+        $entry->tags = \core_tag\external\util::get_item_tags('mod_glossary', 'glossary_entries', $entry->id);
     }
 
     /**
index 7320657..10e4e44 100644 (file)
@@ -203,7 +203,7 @@ class mod_glossary_external_testcase extends externallib_advanced_testcase {
         $ctx = context_module::instance($g1->cmid);
         $this->getDataGenerator()->enrol_user($u1->id, $c1->id);
 
-        $e1a = $gg->create_content($g1, array('approved' => 0, 'concept' => 'Bob', 'userid' => 2));
+        $e1a = $gg->create_content($g1, array('approved' => 0, 'concept' => 'Bob', 'userid' => 2, 'tags' => array('Cats', 'Dogs')));
         $e1b = $gg->create_content($g1, array('approved' => 1, 'concept' => 'Jane', 'userid' => 2));
         $e1c = $gg->create_content($g1, array('approved' => 1, 'concept' => 'Alice', 'userid' => $u1->id));
         $e1d = $gg->create_content($g1, array('approved' => 0, 'concept' => '0-day', 'userid' => $u1->id));
@@ -218,6 +218,8 @@ class mod_glossary_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals(3, $return['count']);
         $this->assertEquals($e1c->id, $return['entries'][0]['id']);
         $this->assertEquals($e1a->id, $return['entries'][1]['id']);
+        $this->assertEquals('Cats', $return['entries'][1]['tags'][0]['rawname']);
+        $this->assertEquals('Dogs', $return['entries'][1]['tags'][1]['rawname']);
         $this->assertEquals($e1b->id, $return['entries'][2]['id']);
 
         // An admin user requesting all the entries.
@@ -311,7 +313,7 @@ class mod_glossary_external_testcase extends externallib_advanced_testcase {
 
         $now = time();
         $e1a = $gg->create_content($g1, array('approved' => 1, 'concept' => 'Bob', 'userid' => $u1->id,
-            'timecreated' => 1, 'timemodified' => $now + 3600));
+            'timecreated' => 1, 'timemodified' => $now + 3600, 'tags' => array('Cats', 'Dogs')));
         $e1b = $gg->create_content($g1, array('approved' => 1, 'concept' => 'Jane', 'userid' => $u1->id,
             'timecreated' => $now + 3600, 'timemodified' => 1));
         $e1c = $gg->create_content($g1, array('approved' => 1, 'concept' => 'Alice', 'userid' => $u1->id,
@@ -328,6 +330,8 @@ class mod_glossary_external_testcase extends externallib_advanced_testcase {
         $this->assertCount(3, $return['entries']);
         $this->assertEquals(3, $return['count']);
         $this->assertEquals($e1a->id, $return['entries'][0]['id']);
+        $this->assertEquals('Cats', $return['entries'][0]['tags'][0]['rawname']);
+        $this->assertEquals('Dogs', $return['entries'][0]['tags'][1]['rawname']);
         $this->assertEquals($e1c->id, $return['entries'][1]['id']);
         $this->assertEquals($e1b->id, $return['entries'][2]['id']);
 
@@ -425,7 +429,7 @@ class mod_glossary_external_testcase extends externallib_advanced_testcase {
         $u1 = $this->getDataGenerator()->create_user();
         $ctx = context_module::instance($g1->cmid);
 
-        $e1a1 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u1->id));
+        $e1a1 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u1->id, 'tags' => array('Cats', 'Dogs')));
         $e1a2 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u1->id));
         $e1a3 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u1->id));
         $e1b1 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u1->id));
@@ -448,6 +452,8 @@ class mod_glossary_external_testcase extends externallib_advanced_testcase {
         $this->assertCount(3, $return['entries']);
         $this->assertEquals(3, $return['count']);
         $this->assertEquals($e1a1->id, $return['entries'][0]['id']);
+        $this->assertEquals('Cats', $return['entries'][0]['tags'][0]['rawname']);
+        $this->assertEquals('Dogs', $return['entries'][0]['tags'][1]['rawname']);
         $this->assertEquals($e1a2->id, $return['entries'][1]['id']);
         $this->assertEquals($e1a3->id, $return['entries'][2]['id']);
 
@@ -562,7 +568,7 @@ class mod_glossary_external_testcase extends externallib_advanced_testcase {
         $e1a2 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u1->id));
         $e1a3 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u1->id));
         $e1b1 = $gg->create_content($g1, array('approved' => 0, 'userid' => $u2->id));
-        $e1b2 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u2->id));
+        $e1b2 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u2->id, 'tags' => array('Cats', 'Dogs')));
         $e1c1 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u3->id));
         $e1d1 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u4->id));
         $e2a = $gg->create_content($g2, array('approved' => 1, 'userid' => $u1->id));
@@ -575,6 +581,8 @@ class mod_glossary_external_testcase extends externallib_advanced_testcase {
         $this->assertCount(4, $return['entries']);
         $this->assertEquals(4, $return['count']);
         $this->assertEquals($e1b2->id, $return['entries'][0]['id']);
+        $this->assertEquals('Cats', $return['entries'][0]['tags'][0]['rawname']);
+        $this->assertEquals('Dogs', $return['entries'][0]['tags'][1]['rawname']);
         $this->assertEquals($e1a1->id, $return['entries'][1]['id']);
         $this->assertEquals($e1a2->id, $return['entries'][2]['id']);
         $this->assertEquals($e1a3->id, $return['entries'][3]['id']);
@@ -780,7 +788,7 @@ class mod_glossary_external_testcase extends externallib_advanced_testcase {
 
         $e1 = $gg->create_content($g1, array('approved' => 1, 'concept' => 'House', 'timecreated' => time() + 3600));
         $e2 = $gg->create_content($g1, array('approved' => 1, 'concept' => 'Mouse', 'timemodified' => 1));
-        $e3 = $gg->create_content($g1, array('approved' => 1, 'concept' => 'Hero'));
+        $e3 = $gg->create_content($g1, array('approved' => 1, 'concept' => 'Hero', 'tags' => array('Cats', 'Dogs')));
         $e4 = $gg->create_content($g1, array('approved' => 0, 'concept' => 'Toulouse'));
         $e5 = $gg->create_content($g1, array('approved' => 1, 'definition' => 'Heroes', 'concept' => 'Abcd'));
         $e6 = $gg->create_content($g1, array('approved' => 0, 'definition' => 'When used for Heroes'));
@@ -798,6 +806,8 @@ class mod_glossary_external_testcase extends externallib_advanced_testcase {
         $this->assertCount(1, $return['entries']);
         $this->assertEquals(1, $return['count']);
         $this->assertEquals($e3->id, $return['entries'][0]['id']);
+        $this->assertEquals('Cats', $return['entries'][0]['tags'][0]['rawname']);
+        $this->assertEquals('Dogs', $return['entries'][0]['tags'][1]['rawname']);
 
         // Enabling full search.
         $query = 'hero';
@@ -893,7 +903,8 @@ class mod_glossary_external_testcase extends externallib_advanced_testcase {
 
         $this->setAdminUser();
 
-        $e1 = $gg->create_content($g1, array('userid' => $u1->id, 'approved' => 1, 'concept' => 'cat'));
+        $e1 = $gg->create_content($g1, array('userid' => $u1->id, 'approved' => 1, 'concept' => 'cat',
+            'tags' => array('Cats', 'Dogs')));
         $e2 = $gg->create_content($g1, array('userid' => $u1->id, 'approved' => 1), array('cat', 'dog'));
         $e3 = $gg->create_content($g1, array('userid' => $u1->id, 'approved' => 1), array('dog'));
         $e4 = $gg->create_content($g1, array('userid' => $u1->id, 'approved' => 0, 'concept' => 'dog'));
@@ -908,6 +919,8 @@ class mod_glossary_external_testcase extends externallib_advanced_testcase {
         $expected = array($e1->id, $e2->id);
         $actual = array($return['entries'][0]['id'], $return['entries'][1]['id']);
         $this->assertEquals($expected, $actual, '', 0.0, 10, true);
+        $this->assertEquals('Cats', $return['entries'][0]['tags'][0]['rawname']);
+        $this->assertEquals('Dogs', $return['entries'][0]['tags'][1]['rawname']);
 
         // Search alias.
         $return = mod_glossary_external::get_entries_by_term($g1->id, 'dog', 0, 20, array('includenotapproved' => false));
@@ -1063,7 +1076,7 @@ class mod_glossary_external_testcase extends externallib_advanced_testcase {
         $ctx = context_module::instance($g1->cmid);
         $this->getDataGenerator()->enrol_user($u1->id, $c1->id);
 
-        $e1 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u1->id));
+        $e1 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u1->id, 'tags' => array('Cats', 'Dogs')));
         // Add a fake inline image to the entry.
         $filename = 'shouldbeanimage.jpg';
         $filerecordinline = array(
@@ -1085,6 +1098,8 @@ class mod_glossary_external_testcase extends externallib_advanced_testcase {
         $return = mod_glossary_external::get_entry_by_id($e1->id);
         $return = external_api::clean_returnvalue(mod_glossary_external::get_entry_by_id_returns(), $return);
         $this->assertEquals($e1->id, $return['entry']['id']);
+        $this->assertEquals('Cats', $return['entry']['tags'][0]['rawname']);
+        $this->assertEquals('Dogs', $return['entry']['tags'][1]['rawname']);
         $this->assertEquals($filename, $return['entry']['definitioninlinefiles'][0]['filename']);
 
         $return = mod_glossary_external::get_entry_by_id($e2->id);
index 6b298d2..2ce1356 100644 (file)
@@ -1,6 +1,9 @@
 This files describes API changes in /mod/glossary/*,
 information provided here is intended especially for developers.
 
+=== 3.7 ===
+* External functions get_entries_by_* and get_entry now return an additional field "tags" containing the entry tags.
+
 === 3.4 ===
   * External functions returning entries now return an additional field "ratinginfo" containing the entry rating information.
 
index 7195a66..34a91c3 100644 (file)
@@ -492,7 +492,8 @@ class mod_wiki_external extends external_api {
                         'pageviews' => $page->pageviews,
                         'readonly' => $page->readonly,
                         'caneditpage' => $caneditpages,
-                        'firstpage' => $page->id == $firstpage->id
+                        'firstpage' => $page->id == $firstpage->id,
+                        'tags' => \core_tag\external\util::get_item_tags('mod_wiki', 'wiki_pages', $page->id),
                     );
 
                 // Refresh page cached content if needed.
@@ -555,6 +556,9 @@ class mod_wiki_external extends external_api {
                             'contentformat' => new external_format_value('cachedcontent', VALUE_OPTIONAL),
                             'contentsize' => new external_value(PARAM_INT, 'Size of page contents in bytes (doesn\'t include'.
                                                                             ' size of attached files).', VALUE_OPTIONAL),
+                            'tags' => new external_multiple_structure(
+                                \core_tag\external\tag_item_exporter::get_read_structure(), 'Tags', VALUE_OPTIONAL
+                            ),
                         ), 'Pages'
                     )
                 ),
@@ -623,6 +627,7 @@ class mod_wiki_external extends external_api {
         $returnedpage['groupid'] = $subwiki->groupid;
         $returnedpage['userid'] = $subwiki->userid;
         $returnedpage['title'] = $page->title;
+        $returnedpage['tags'] = \core_tag\external\util::get_item_tags('mod_wiki', 'wiki_pages', $page->id);
 
         // Refresh page cached content if needed.
         if ($page->timerendered + WIKI_REFRESH_CACHE_TIME < time()) {
@@ -668,6 +673,9 @@ class mod_wiki_external extends external_api {
                         'contentformat' => new external_format_value('cachedcontent', VALUE_OPTIONAL),
                         'caneditpage' => new external_value(PARAM_BOOL, 'True if user can edit the page.'),
                         'version' => new external_value(PARAM_INT, 'Latest version of the page.', VALUE_OPTIONAL),
+                        'tags' => new external_multiple_structure(
+                            \core_tag\external\tag_item_exporter::get_read_structure(), 'Tags', VALUE_OPTIONAL
+                        ),
                     ), 'Page'
                 ),
                 'warnings' => new external_warnings()
index 7ecf190..522cfbe 100644 (file)
@@ -69,7 +69,8 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
         $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, $this->teacherrole->id, 'manual');
 
         // Create first pages.
-        $this->firstpage = $this->getDataGenerator()->get_plugin_generator('mod_wiki')->create_first_page($this->wiki);
+        $this->firstpage = $this->getDataGenerator()->get_plugin_generator('mod_wiki')->create_first_page($this->wiki,
+            array('tags' => array('Cats', 'Dogs')));
     }
 
     /**
@@ -616,6 +617,10 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
         $expectedfirstpage['caneditpage'] = true; // No groups and students have 'mod/wiki:editpage' capability.
         $expectedfirstpage['firstpage'] = true;
         $expectedfirstpage['contentformat'] = 1;
+        $expectedfirstpage['tags'] = \core_tag\external\util::get_item_tags('mod_wiki', 'wiki_pages', $this->firstpage->id);
+        // Cast to expected.
+        $expectedfirstpage['tags'][0]['isstandard'] = (bool) $expectedfirstpage['tags'][0]['isstandard'];
+        $expectedfirstpage['tags'][1]['isstandard'] = (bool) $expectedfirstpage['tags'][1]['isstandard'];
         $expectedpages[] = $expectedfirstpage;
 
         $result = mod_wiki_external::get_subwiki_pages($this->wiki->id);
@@ -640,6 +645,7 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
         $expectednewpage['caneditpage'] = true; // No groups and students have 'mod/wiki:editpage' capability.
         $expectednewpage['firstpage'] = false;
         $expectednewpage['contentformat'] = 1;
+        $expectednewpage['tags'] = array();
         array_unshift($expectedpages, $expectednewpage); // Add page to the beginning since it orders by title by default.
 
         $result = mod_wiki_external::get_subwiki_pages($this->wiki->id);
@@ -692,6 +698,7 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
         $expectedteacherpage['caneditpage'] = true;
         $expectedteacherpage['firstpage'] = true;
         $expectedteacherpage['contentformat'] = 1;
+        $expectedteacherpage['tags'] = array();
         $expectedpages = array($expectedteacherpage);
 
         $result = mod_wiki_external::get_subwiki_pages($indwiki->id, 0, $this->teacher->id);
@@ -703,6 +710,7 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
         $expectedstudentpage['caneditpage'] = true;
         $expectedstudentpage['firstpage'] = true;
         $expectedstudentpage['contentformat'] = 1;
+        $expectedstudentpage['tags'] = array();
         $expectedpages = array($expectedstudentpage);
 
         $result = mod_wiki_external::get_subwiki_pages($indwiki->id, 0, $this->student->id);
@@ -738,6 +746,7 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
         $expectedpage['caneditpage'] = true; // User belongs to group and has 'mod/wiki:editpage' capability.
         $expectedpage['firstpage'] = true;
         $expectedpage['contentformat'] = 1;
+        $expectedpage['tags'] = array();
         $expectedpages = array($expectedpage);
 
         $result = mod_wiki_external::get_subwiki_pages($this->wikisep->id, $this->group1->id);
@@ -760,6 +769,7 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
         $expectedpage['caneditpage'] = true;
         $expectedpage['firstpage'] = true;
         $expectedpage['contentformat'] = 1;
+        $expectedpage['tags'] = array();
         $expectedpages = array($expectedpage);
 
         $result = mod_wiki_external::get_subwiki_pages($this->wikisep->id, 0);
@@ -783,6 +793,7 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
         $expectedpage['caneditpage'] = true; // User belongs to group and has 'mod/wiki:editpage' capability.
         $expectedpage['firstpage'] = true;
         $expectedpage['contentformat'] = 1;
+        $expectedpage['tags'] = array();
         $expectedpages = array($expectedpage);
 
         $result = mod_wiki_external::get_subwiki_pages($this->wikivis->id, $this->group1->id);
@@ -794,6 +805,7 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
         $expectedpage['caneditpage'] = false; // User doesn't belong to group so he can't edit the page.
         $expectedpage['firstpage'] = true;
         $expectedpage['contentformat'] = 1;
+        $expectedpage['tags'] = array();
         $expectedpages = array($expectedpage);
 
         $result = mod_wiki_external::get_subwiki_pages($this->wikivis->id, $this->group2->id);
@@ -805,6 +817,7 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
         $expectedpage['caneditpage'] = false;
         $expectedpage['firstpage'] = true;
         $expectedpage['contentformat'] = 1;
+        $expectedpage['tags'] = array();
         $expectedpages = array($expectedpage);
 
         $result = mod_wiki_external::get_subwiki_pages($this->wikivis->id, 0);
@@ -827,6 +840,7 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
         $expectedpage['caneditpage'] = true;
         $expectedpage['firstpage'] = true;
         $expectedpage['contentformat'] = 1;
+        $expectedpage['tags'] = array();
         $expectedpages = array($expectedpage);
 
         $result = mod_wiki_external::get_subwiki_pages($this->wikisepind->id, $this->group1->id, $this->student->id);
@@ -850,6 +864,7 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
         $expectedpage['caneditpage'] = false;
         $expectedpage['firstpage'] = true;
         $expectedpage['contentformat'] = 1;
+        $expectedpage['tags'] = array();
         $expectedpages = array($expectedpage);
 
         $result = mod_wiki_external::get_subwiki_pages($this->wikisepind->id, $this->group1->id, $this->student2->id);
@@ -872,6 +887,7 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
         $expectedpage['caneditpage'] = true;
         $expectedpage['firstpage'] = true;
         $expectedpage['contentformat'] = 1;
+        $expectedpage['tags'] = array();
         $expectedpages = array($expectedpage);
 
         $result = mod_wiki_external::get_subwiki_pages($this->wikivisind->id, $this->group1->id, $this->student->id);
@@ -883,6 +899,7 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
         $expectedpage['caneditpage'] = false;
         $expectedpage['firstpage'] = true;
         $expectedpage['contentformat'] = 1;
+        $expectedpage['tags'] = array();
         $expectedpages = array($expectedpage);
 
         $result = mod_wiki_external::get_subwiki_pages($this->wikivisind->id, $this->group2->id, $this->teacher->id);
@@ -894,6 +911,7 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
         $expectedpage['caneditpage'] = false;
         $expectedpage['firstpage'] = true;
         $expectedpage['contentformat'] = 1;
+        $expectedpage['tags'] = array();
         $expectedpages = array($expectedpage);
 
         $result = mod_wiki_external::get_subwiki_pages($this->wikivisind->id, 0, $this->teacher->id);
@@ -987,8 +1005,12 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
             'cachedcontent' => $this->firstpage->cachedcontent,
             'contentformat' => 1,
             'caneditpage' => true,
-            'version' => 1
+            'version' => 1,
+            'tags' => \core_tag\external\util::get_item_tags('mod_wiki', 'wiki_pages', $this->firstpage->id),
         );
+        // Cast to expected.
+        $expectedpage['tags'][0]['isstandard'] = (bool) $expectedpage['tags'][0]['isstandard'];
+        $expectedpage['tags'][1]['isstandard'] = (bool) $expectedpage['tags'][1]['isstandard'];
 
         $result = mod_wiki_external::get_page_contents($this->firstpage->id);
         $result = external_api::clean_returnvalue(mod_wiki_external::get_page_contents_returns(), $result);
@@ -1000,6 +1022,7 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
         $expectedpage['id'] = $newpage->id;
         $expectedpage['title'] = $newpage->title;
         $expectedpage['cachedcontent'] = $newpage->cachedcontent;
+        $expectedpage['tags'] = array();
 
         $result = mod_wiki_external::get_page_contents($newpage->id);
         $result = external_api::clean_returnvalue(mod_wiki_external::get_page_contents_returns(), $result);
@@ -1028,7 +1051,8 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
             'cachedcontent' => $this->fpsepg1indstu->cachedcontent,
             'contentformat' => 1,
             'caneditpage' => true,
-            'version' => 1
+            'version' => 1,
+            'tags' => array(),
         );
 
         $result = mod_wiki_external::get_page_contents($this->fpsepg1indstu->id);
index 0e4d30e..bc1bfd9 100644 (file)
@@ -1,6 +1,9 @@
 This files describes API changes in /mod/wiki/*,
 information provided here is intended especially for developers.
 
+=== 3.7 ===
+* External functions get_subwiki_pages and get_page_contents now return an additional field "tags" returning the entry tags.
+
 === 3.2 ===
 * External functions that were returning file information now return the following file fields:
   filename, filepath, mimetype, filesize, timemodified and fileurl.
index 54fa204..6214f86 100644 (file)
@@ -335,4 +335,351 @@ class core_tag_external extends external_api {
             ), 'tag index'
         );
     }
+
+
+    /**
+     * Parameters for function get_tagindex_per_area()
+     *
+     * @return external_function_parameters
+     * @since  Moodle 3.7
+     */
+    public static function get_tagindex_per_area_parameters() {
+        return new external_function_parameters(
+            array(
+                'tagindex' => new external_single_structure(array(
+                    'id' => new external_value(PARAM_INT, 'tag id', VALUE_OPTIONAL, 0),
+                    'tag' => new external_value(PARAM_TAG, 'tag name', VALUE_OPTIONAL, ''),
+                    'tc' => new external_value(PARAM_INT, 'tag collection id', VALUE_OPTIONAL, 0),
+                    'ta' => new external_value(PARAM_INT, 'tag area id', VALUE_OPTIONAL, 0),
+                    'excl' => new external_value(PARAM_BOOL, 'exlusive mode for this tag area', VALUE_OPTIONAL, 0),
+                    'from' => new external_value(PARAM_INT, 'context id where the link was displayed', VALUE_OPTIONAL, 0),
+                    'ctx' => new external_value(PARAM_INT, 'context id where to search for items', VALUE_OPTIONAL, 0),
+                    'rec' => new external_value(PARAM_INT, 'search in the context recursive', VALUE_OPTIONAL, 1),
+                    'page' => new external_value(PARAM_INT, 'page number (0-based)', VALUE_OPTIONAL, 0),
+                ), 'parameters')
+            )
+        );
+    }
+
+    /**
+     * Returns the tag index per multiple areas if requested.
+     *
+     * @param array $params Tag index required information.
+     * @throws moodle_exception
+     * @since  Moodle 3.7
+     */
+    public static function get_tagindex_per_area($params) {
+        global $CFG, $PAGE;
+        // Validate and normalize parameters.
+        $tagindex = self::validate_parameters(
+            self::get_tagindex_per_area_parameters(), array('tagindex' => $params));
+        $params = $tagindex['tagindex'] + array(    // Force defaults.
+            'id' => 0,
+            'tag' => '',
+            'tc' => 0,
+            'ta' => 0,
+            'excl' => 0,
+            'from' => 0,
+            'ctx' => 0,
+            'rec' => 1,
+            'page' => 0,
+        );
+
+        if (empty($CFG->usetags)) {
+            throw new moodle_exception('tagsaredisabled', 'tag');
+        }
+
+        if (!empty($params['tag'])) {
+            if (empty($params['tc'])) {
+                // Tag name specified but tag collection was not. Try to guess it.
+                $tags = core_tag_tag::guess_by_name($params['tag'], '*');
+                if (count($tags) > 1) {
+                    // It is in more that one collection, do not display.
+                    throw new moodle_exception('Tag is in more that one collection, please indicate one.');
+                } else if (count($tags) == 1) {
+                    $tag = reset($tags);
+                }
+            } else {
+                if (!$tag = core_tag_tag::get_by_name($params['tc'], $params['tag'], '*')) {
+                    // Not found in collection.
+                    throw new moodle_exception('notagsfound', 'tag');
+                }
+            }
+        } else if (!empty($params['id'])) {
+            $tag = core_tag_tag::get($params['id'], '*');
+        }
+
+        if (empty($tag)) {
+            throw new moodle_exception('notagsfound', 'tag');
+        }
+
+        // Login to the course / module if applicable.
+        $context = !empty($params['ctx']) ? context::instance_by_id($params['ctx']) : context_system::instance();
+        self::validate_context($context);
+
+        $tag = core_tag_tag::get_by_name($params['tc'], $tag->name, '*', MUST_EXIST);
+        $tagareas = core_tag_collection::get_areas($params['tc']);
+        $tagareaid = $params['ta'];
+
+         $exclusivemode = 0;
+        // Find all areas in this collection and their items tagged with this tag.
+        if ($tagareaid) {
+            $tagareas = array($tagareas[$tagareaid]);
+        }
+        if (!$tagareaid && count($tagareas) == 1) {
+            // Automatically set "exclusive" mode for tag collection with one tag area only.
+            $params['excl'] = 1;
+        }
+
+        $renderer = $PAGE->get_renderer('core');
+        $result = array();
+        foreach ($tagareas as $ta) {
+            $tagindex = $tag->get_tag_index($ta, $params['excl'], $params['from'], $params['ctx'], $params['rec'], $params['page']);
+            if (!empty($tagindex->hascontent)) {
+                $result[] = $tagindex->export_for_template($renderer);
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * Return structure for get_tagindex_per_area
+     *
+     * @return external_description
+     * @since  Moodle 3.7
+     */
+    public static function get_tagindex_per_area_returns() {
+        return new external_multiple_structure(
+            self::get_tagindex_returns()
+        );
+    }
+
+    /**
+     * Returns description of get_tag_areas() parameters.
+     *
+     * @return external_function_parameters
+     * @since  Moodle 3.7
+     */
+    public static function get_tag_areas_parameters() {
+        return new external_function_parameters(array());
+    }
+
+    /**
+     * Retrieves existing tag areas.
+     *
+     * @return array an array of warnings and objects containing the plugin information
+     * @throws moodle_exception
+     * @since  Moodle 3.7
+     */
+    public static function get_tag_areas() {
+        global $CFG, $PAGE;
+
+        if (empty($CFG->usetags)) {
+            throw new moodle_exception('tagsaredisabled', 'tag');
+        }
+
+        $context = context_system::instance();
+        self::validate_context($context);
+        $PAGE->set_context($context); // Needed by internal APIs.
+        $output = $PAGE->get_renderer('core');
+
+        $areas = core_tag_area::get_areas();
+        $exportedareas = array();
+        foreach ($areas as $itemtype => $component) {
+            foreach ($component as $area) {
+                // Move optional fields not part of the DB table to otherdata.
+                $locked = false;
+                if (isset($area->locked)) {
+                    $locked = $area->locked;
+                    unset($area->locked);
+                }
+                $exporter = new \core_tag\external\tag_area_exporter($area, array('locked' => $locked));
+                $exportedareas[] = $exporter->export($output);
+            }
+        }
+
+        return array(
+            'areas' => $exportedareas,
+            'warnings' => array(),
+        );
+    }
+
+    /**
+     * Returns description of get_tag_areas() result value.
+     *
+     * @return external_description
+     * @since  Moodle 3.7
+     */
+    public static function get_tag_areas_returns() {
+        return new external_single_structure(
+            array(
+                'areas' => new external_multiple_structure(
+                    \core_tag\external\tag_area_exporter::get_read_structure()
+                ),
+                'warnings' => new external_warnings(),
+            )
+        );
+    }
+
+    /**
+     * Returns description of get_tag_collections() parameters.
+     *
+     * @return external_function_parameters
+     * @since  Moodle 3.7
+     */
+    public static function get_tag_collections_parameters() {
+        return new external_function_parameters(array());
+    }
+
+    /**
+     * Retrieves existing tag collections.
+     *
+     * @return array an array of warnings and tag collections
+     * @throws moodle_exception
+     * @since  Moodle 3.7
+     */
+    public static function get_tag_collections() {
+        global $CFG, $PAGE;
+
+        if (empty($CFG->usetags)) {
+            throw new moodle_exception('tagsaredisabled', 'tag');
+        }
+
+        $context = context_system::instance();
+        self::validate_context($context);
+        $PAGE->set_context($context); // Needed by internal APIs.
+        $output = $PAGE->get_renderer('core');
+
+        $collections = core_tag_collection::get_collections();
+        $exportedcollections = array();
+        foreach ($collections as $collection) {
+            $exporter = new \core_tag\external\tag_collection_exporter($collection);
+            $exportedcollections[] = $exporter->export($output);
+        }
+
+        return array(
+            'collections' => $exportedcollections,
+            'warnings' => array(),
+        );
+    }
+
+    /**
+     * Returns description of get_tag_collections() result value.
+     *
+     * @return external_description
+     * @since  Moodle 3.7
+     */
+    public static function get_tag_collections_returns() {
+        return new external_single_structure(
+            array(
+                'collections' => new external_multiple_structure(
+                    \core_tag\external\tag_collection_exporter::get_read_structure()
+                ),
+                'warnings' => new external_warnings(),
+            )
+        );
+    }
+
+    /**
+     * Returns description of get_tag_cloud() parameters.
+     *
+     * @return external_function_parameters
+     * @since  Moodle 3.7
+     */
+    public static function get_tag_cloud_parameters() {
+        return new external_function_parameters(
+            array(
+                'tagcollid' => new external_value(PARAM_INT, 'Tag collection id.', VALUE_DEFAULT, 0),
+                'isstandard' => new external_value(PARAM_BOOL, 'Whether to return only standard tags.', VALUE_DEFAULT, false),
+                'limit' => new external_value(PARAM_INT, 'Maximum number of tags to retrieve.', VALUE_DEFAULT, 150),
+                'sort' => new external_value(PARAM_ALPHA, 'Sort order for display
+                    (id, name, rawname, count, flag, isstandard, tagcollid).', VALUE_DEFAULT, 'name'),
+                'search' => new external_value(PARAM_RAW, 'Search string.', VALUE_DEFAULT, ''),
+                'fromctx' => new external_value(PARAM_INT, 'Context id where this tag cloud is displayed.', VALUE_DEFAULT, 0),
+                'ctx' => new external_value(PARAM_INT, 'Only retrieve tag instances in this context.', VALUE_DEFAULT, 0),
+                'rec' => new external_value(PARAM_INT, 'Retrieve tag instances in the $ctx context and it\'s children.',
+                    VALUE_DEFAULT, 1),
+            )
+        );
+    }
+
+    /**
+     * Retrieves a tag cloud for display.
+     *
+     * @param int $tagcollid tag collection id
+     * @param bool $isstandard return only standard tags
+     * @param int $limit maximum number of tags to retrieve, tags are sorted by the instance count
+     *            descending here regardless of $sort parameter
+     * @param string $sort sort order for display, default 'name' - tags will be sorted after they are retrieved
+     * @param string $search search string
+     * @param int $fromctx context id where this tag cloud is displayed
+     * @param int $ctx only retrieve tag instances in this context
+     * @param int $rec retrieve tag instances in the $ctx context and it's children (default 1)
+     * @return array an array of warnings and tag cloud information and items
+     * @throws moodle_exception
+     * @since  Moodle 3.7
+     */
+    public static function get_tag_cloud($tagcollid = 0, $isstandard = false, $limit = 150, $sort = 'name',
+            $search = '', $fromctx = 0, $ctx = 0, $rec = 1) {
+        global $CFG, $PAGE;
+
+        $params = self::validate_parameters(self::get_tag_cloud_parameters(),
+            array(
+                'tagcollid' => $tagcollid,
+                'isstandard' => $isstandard,
+                'limit' => $limit,
+                'sort' => $sort,
+                'search' => $search,
+                'fromctx' => $fromctx,
+                'ctx' => $ctx,
+                'rec' => $rec,
+            )
+        );
+
+        if (empty($CFG->usetags)) {
+            throw new moodle_exception('tagsaredisabled', 'tag');
+        }
+
+        $context = context_system::instance();
+        self::validate_context($context);
+        $PAGE->set_context($context); // Needed by internal APIs.
+        $output = $PAGE->get_renderer('core');
+
+        $tagcloud = core_tag_collection::get_tag_cloud($params['tagcollid'], $params['isstandard'], $params['limit'],
+            $params['sort'], $params['search'], $params['fromctx'], $params['ctx'], $params['rec']);
+
+        $result = $tagcloud->export_for_template($output);
+        $result->warnings = array();
+
+        return (array) $result;
+    }
+
+    /**
+     * Returns description of get_tag_cloud() result value.
+     *
+     * @return external_description
+     * @since  Moodle 3.7
+     */
+    public static function get_tag_cloud_returns() {
+        return new external_single_structure(
+            array(
+                'tags' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'name' => new external_value(PARAM_TAG, 'Tag name.'),
+                            'viewurl' => new external_value(PARAM_RAW, 'URL to view the tag index.'),
+                            'flag' => new external_value(PARAM_BOOL, 'Whether the tag is flagged as inappropriate.',
+                                VALUE_OPTIONAL),
+                            'isstandard' => new external_value(PARAM_BOOL, 'Whether is a standard tag or not.', VALUE_OPTIONAL),
+                            'count' => new external_value(PARAM_INT, 'Number of tag instances.', VALUE_OPTIONAL),
+                            'size' => new external_value(PARAM_INT, 'Proportional size to display the tag.', VALUE_OPTIONAL),
+                        ), 'Tags.'
+                    )
+                ),
+                'tagscount' => new external_value(PARAM_INT, 'Number of tags returned.'),
+                'totalcount' => new external_value(PARAM_INT, 'Total count of tags.'),
+                'warnings' => new external_warnings(),
+            )
+        );
+    }
 }
diff --git a/tag/classes/external/tag_area_exporter.php b/tag/classes/external/tag_area_exporter.php
new file mode 100644 (file)
index 0000000..20ec1e3
--- /dev/null
@@ -0,0 +1,116 @@
+<?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/>.
+
+/**
+ * Contains related class for displaying information of a tag area.
+ *
+ * @package   core_tag
+ * @copyright 2019 Juan Leyva <juan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_tag\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+use core\external\exporter;
+use renderer_base;
+
+/**
+ * Contains related class for displaying information of a tag area.
+ *
+ * @package   core_tag
+ * @copyright 2019 Juan Leyva <juan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tag_area_exporter extends exporter {
+
+    /**
+     * Return the list of properties.
+     *
+     * @return array
+     */
+    protected static function define_properties() {
+        return [
+            'id' => [
+                'type' => PARAM_INT,
+                'description' => 'Area id.',
+            ],
+            'component' => [
+                'type' => PARAM_COMPONENT,
+                'description' => 'Component the area is related to.',
+            ],
+            'itemtype' => [
+                'type' => PARAM_ALPHANUMEXT,
+                'description' => 'Type of item in the component.',
+            ],
+            'enabled' => [
+                'type' => PARAM_BOOL,
+                'description' => 'Whether this area is enabled.',
+                'default' => true,
+            ],
+            'tagcollid' => [
+                'type' => PARAM_INT,
+                'description' => 'The tag collection this are belongs to.',
+            ],
+            'callback' => [
+                'type' => PARAM_ALPHANUMEXT,
+                'description' => 'Component callback for processing tags.',
+                'null' => NULL_ALLOWED,
+            ],
+            'callbackfile' => [
+                'type' => PARAM_RAW,
+                'description' => 'Component callback file.',
+                'null' => NULL_ALLOWED,
+            ],
+            'showstandard' => [
+                'type' => PARAM_INT,
+                'description' => 'Return whether to display only standard, only non-standard or both tags.',
+                'default' => 0,
+            ],
+            'multiplecontexts' => [
+                'type' => PARAM_BOOL,
+                'description' => 'Whether the tag area allows tag instances to be created in multiple contexts. ',
+                'default' => false,
+            ],
+        ];
+    }
+
+    protected static function define_related() {
+        return array(
+            'locked' => 'bool?'
+        );
+    }
+
+    protected static function define_other_properties() {
+        return array(
+            'locked' => [
+                'type' => PARAM_BOOL,
+                'description' => 'Whether the area is locked.',
+                'null' => NULL_ALLOWED,
+                'default' => false,
+                'optional' => true,
+            ]
+        );
+    }
+
+    protected function get_other_values(renderer_base $output) {
+
+        $values['locked'] = $this->related['locked'] ? true : false;
+
+        return $values;
+    }
+}
diff --git a/tag/classes/external/tag_collection_exporter.php b/tag/classes/external/tag_collection_exporter.php
new file mode 100644 (file)
index 0000000..f49e70d
--- /dev/null
@@ -0,0 +1,82 @@
+<?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/>.
+
+/**
+ * Contains related class for displaying information of a tag collection.
+ *
+ * @package   core_tag
+ * @copyright 2019 Juan Leyva <juan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_tag\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+use core\external\exporter;
+
+/**
+ * Contains related class for displaying information of a tag collection.
+ *
+ * @package   core_tag
+ * @copyright 2019 Juan Leyva <juan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tag_collection_exporter extends exporter {
+
+    /**
+     * Return the list of properties.
+     *
+     * @return array
+     */
+    protected static function define_properties() {
+        return [
+            'id' => [
+                'type' => PARAM_INT,
+                'description' => 'Collection id.',
+            ],
+            'name' => [
+                'type' => PARAM_NOTAGS,
+                'description' => 'Collection name.',
+                'null' => NULL_ALLOWED,
+            ],
+            'isdefault' => [
+                'type' => PARAM_BOOL,
+                'description' => 'Whether is the default collection.',
+                'default' => false,
+            ],
+            'component' => [
+                'type' => PARAM_COMPONENT,
+                'description' => 'Component the collection is related to.',
+                'null' => NULL_ALLOWED,
+            ],
+            'sortorder' => [
+                'type' => PARAM_INT,
+                'description' => 'Collection ordering in the list.',
+            ],
+            'searchable' => [
+                'type' => PARAM_BOOL,
+                'description' => 'Whether the tag collection is searchable.',
+                'default' => true,
+            ],
+            'customurl' => [
+                'type' => PARAM_NOTAGS,
+                'description' => 'Custom URL for the tag page instead of /tag/index.php.',
+                'null' => NULL_ALLOWED,
+            ],
+        ];
+    }
+}
diff --git a/tag/classes/external/tag_item_exporter.php b/tag/classes/external/tag_item_exporter.php
new file mode 100644 (file)
index 0000000..bbbe67e
--- /dev/null
@@ -0,0 +1,92 @@
+<?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/>.
+
+/**
+ * Contains related class for displaying information of a tag item.
+ *
+ * @package   core_tag
+ * @copyright 2019 Juan Leyva <juan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_tag\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+use core\external\exporter;
+
+/**
+ * Contains related class for displaying information of a tag item.
+ *
+ * @package   core_tag
+ * @copyright 2019 Juan Leyva <juan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tag_item_exporter extends exporter {
+
+    /**
+     * Return the list of properties.
+     *
+     * @return array
+     */
+    protected static function define_properties() {
+        return [
+            'id' => [
+                'type' => PARAM_INT,
+                'description' => 'Tag id.',
+            ],
+            'name' => [
+                'type' => PARAM_TAG,
+                'description' => 'Tag name.',
+            ],
+            'rawname' => [
+                'type' => PARAM_RAW,
+                'description' => 'The raw, unnormalised name for the tag as entered by users.',
+            ],
+            'isstandard' => [
+                'type' => PARAM_BOOL,
+                'description' => 'Whether this tag is standard.',
+                'default' => false,
+            ],
+            'tagcollid' => [
+                'type' => PARAM_INT,
+                'description' => 'Tag collection id.',
+            ],
+            'taginstanceid' => [
+                'type' => PARAM_INT,
+                'description' => 'Tag instance id.',
+            ],
+            'taginstancecontextid' => [
+                'type' => PARAM_INT,
+                'description' => 'Context the tag instance belongs to.',
+            ],
+            'itemid' => [
+                'type' => PARAM_INT,
+                'description' => 'Id of the record tagged.',
+            ],
+            'ordering' => [
+                'type' => PARAM_INT,
+                'description' => 'Tag ordering.',
+            ],
+            'flag' => [
+                'type' => PARAM_INT,
+                'description' => 'Whether the tag is flagged as inappropriate.',
+                'default' => 0,
+                'null' => NULL_ALLOWED,
+            ],
+        ];
+    }
+}
diff --git a/tag/classes/external/util.php b/tag/classes/external/util.php
new file mode 100644 (file)
index 0000000..a4a1fed
--- /dev/null
@@ -0,0 +1,70 @@
+<?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/>.
+
+/**
+ * Tag external functions utility class.
+ *
+ * @package    core_tag
+ * @copyright  2019 Juan Leyva
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_tag\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/externallib.php');
+
+use core_tag\external\tag_item_exporter;
+use core_tag_tag;
+
+/**
+ * Tag external functions utility class.
+ *
+ * @package   core_tag
+ * @copyright 2019 Juan Leyva
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since     Moodle 3.7
+ */
+class util {
+
+
+    /**
+     * Get the array of core_tag_tag objects for external functions associated with an item (instances).
+     *
+     * @param string $component component responsible for tagging. For BC it can be empty but in this case the
+     *               query will be slow because DB index will not be used.
+     * @param string $itemtype type of the tagged item
+     * @param int $itemid
+     * @param int $standardonly wether to return only standard tags or any
+     * @param int $tiuserid tag instance user id, only needed for tag areas with user tagging
+     * @return array tags for external functions
+     */
+    public static function get_item_tags($component, $itemtype, $itemid, $standardonly = core_tag_tag::BOTH_STANDARD_AND_NOT,
+            $tiuserid = 0) {
+        global $PAGE;
+
+        $output = $PAGE->get_renderer('core');
+
+        $tagitems = core_tag_tag::get_item_tags($component, $itemtype, $itemid, $standardonly, $tiuserid);
+        $exportedtags = [];
+        foreach ($tagitems as $tagitem) {
+            $exporter = new tag_item_exporter($tagitem->to_object());
+            $exportedtags[] = (array) $exporter->export($output);
+        }
+        return $exportedtags;
+    }
+}
index c451d69..b6813a7 100644 (file)
@@ -181,4 +181,197 @@ class core_tag_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals('Rename me again', $res['value']);
         $this->assertEquals('Rename me again', $DB->get_field('tag', 'rawname', array('id' => $tag->id)));
     }
+
+    /**
+     * Test get_tagindex_per_area.
+     */
+    public function test_get_tagindex_per_area() {
+        global $USER;
+        $this->resetAfterTest(true);
+
+        // Create tags for two user profiles and one course.
+        $this->setAdminUser();
+        $context = context_user::instance($USER->id);
+        core_tag_tag::set_item_tags('core', 'user', $USER->id, $context, array('test'));
+
+        $this->setUser($this->getDataGenerator()->create_user());
+        $context = context_user::instance($USER->id);
+        core_tag_tag::set_item_tags('core', 'user', $USER->id, $context, array('test'));
+
+        $course = $this->getDataGenerator()->create_course();
+        $context = context_course::instance($course->id);
+        core_tag_tag::set_item_tags('core', 'course', $course->id, $context, array('test'));
+
+        $tag = core_tag_tag::get_by_name(0, 'test');
+
+        // First, search by id.
+        $result = core_tag_external::get_tagindex_per_area(array('id' => $tag->id));
+        $result = external_api::clean_returnvalue(core_tag_external::get_tagindex_per_area_returns(), $result);
+        $this->assertCount(2, $result); // Two different areas: course and user.
+        $this->assertEquals($tag->id, $result[0]['tagid']);
+        $this->assertEquals('course', $result[0]['itemtype']);
+        $this->assertEquals($tag->id, $result[1]['tagid']);
+        $this->assertEquals('user', $result[1]['itemtype']);
+
+        // Now, search by name.
+        $result = core_tag_external::get_tagindex_per_area(array('tag' => 'test'));
+        $result = external_api::clean_returnvalue(core_tag_external::get_tagindex_per_area_returns(), $result);
+        $this->assertCount(2, $result); // Two different areas: course and user.
+        $this->assertEquals($tag->id, $result[0]['tagid']);
+        $this->assertEquals('course', $result[0]['itemtype']);
+        $this->assertEquals($tag->id, $result[1]['tagid']);
+        $this->assertEquals('user', $result[1]['itemtype']);
+
+        // Filter by tag area.
+        $result = core_tag_external::get_tagindex_per_area(array('tag' => 'test', 'ta' => $result[0]['ta']));
+        $result = external_api::clean_returnvalue(core_tag_external::get_tagindex_per_area_returns(), $result);
+        $this->assertCount(1, $result); // Just the given area.
+        $this->assertEquals($tag->id, $result[0]['tagid']);
+        $this->assertEquals('course', $result[0]['itemtype']);
+
+        // Now, search by tag collection (use default).
+        $result = core_tag_external::get_tagindex_per_area(array('id' => $tag->id, 'tc' => 1));
+        $result = external_api::clean_returnvalue(core_tag_external::get_tagindex_per_area_returns(), $result);
+        $this->assertCount(2, $result); // Two different areas: course and user.
+    }
+
+    /**
+     * Test get_tag_areas.
+     */
+    public function test_get_tag_areas() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        $this->setAdminUser();
+        $result = core_tag_external::get_tag_areas();
+        $result = external_api::clean_returnvalue(core_tag_external::get_tag_areas_returns(), $result);
+        $areas = $DB->get_records('tag_area');
+        $this->assertCount(count($areas), $result['areas']);
+        foreach ($result['areas'] as $area) {
+            $this->assertEquals($areas[$area['id']]->component, $area['component']);
+            $this->assertEquals($areas[$area['id']]->itemtype, $area['itemtype']);
+        }
+    }
+
+    /**
+     * Test get_tag_collections.
+     */
+    public function test_get_tag_collections() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        // Create new tag collection.
+        $data = (object) array('name' => 'new tag coll');
+        core_tag_collection::create($data);
+
+        $this->setAdminUser();
+        $result = core_tag_external::get_tag_collections();
+        $result = external_api::clean_returnvalue(core_tag_external::get_tag_collections_returns(), $result);
+
+        $collections = $DB->get_records('tag_coll');
+        $this->assertCount(count($collections), $result['collections']);
+        foreach ($result['collections'] as $collection) {
+            $this->assertEquals($collections[$collection['id']]->component, $collection['component']);
+            $this->assertEquals($collections[$collection['id']]->name, $collection['name']);
+        }
+    }
+
+    /**
+     * Test get_tag_cloud.
+     */
+    public function test_get_tag_cloud() {
+        global $USER, $DB;
+        $this->resetAfterTest(true);
+
+        // Create tags for two user profiles, a post and one course.
+        $this->setAdminUser();
+        $context = context_user::instance($USER->id);
+        core_tag_tag::set_item_tags('core', 'user', $USER->id, $context, array('Cats', 'Dogs'));
+
+        $this->setUser($this->getDataGenerator()->create_user());
+        $context = context_user::instance($USER->id);
+        core_tag_tag::set_item_tags('core', 'user', $USER->id, $context, array('Mice'));
+
+        $course = $this->getDataGenerator()->create_course();
+        $coursecontext = context_course::instance($course->id);
+        core_tag_tag::set_item_tags('core', 'course', $course->id, $coursecontext, array('Cats'));
+
+        $post = new stdClass();
+        $post->userid = $USER->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('Horses', 'Cats'));
+
+        // First, retrieve complete cloud.
+        $result = core_tag_external::get_tag_cloud();
+        $result = external_api::clean_returnvalue(core_tag_external::get_tag_cloud_returns(), $result);
+        $this->assertCount(4, $result['tags']); // Four different tags: Cats, Dogs, Mice, Horses.
+        $this->assertEquals(4, $result['tagscount']);
+        $this->assertEquals(4, $result['totalcount']);
+
+        foreach ($result['tags'] as $tag) {
+            if ($tag['name'] == 'Cats') {
+                $this->assertEquals(3, $tag['count']);
+            } else {
+                $this->assertEquals(1, $tag['count']);
+            }
+        }
+
+        // Test filter by collection, pagination and sorting.
+        $defaultcoll = core_tag_collection::get_default();
+        $result = core_tag_external::get_tag_cloud($defaultcoll, false, 2, 'count');
+        $result = external_api::clean_returnvalue(core_tag_external::get_tag_cloud_returns(), $result);
+        $this->assertCount(2, $result['tags']); // Only two tags.
+        $this->assertEquals(2, $result['tagscount']);
+        $this->assertEquals(4, $result['totalcount']);
+        $this->assertEquals('Dogs', $result['tags'][0]['name']); // Lower count first.
+
+        // Test search.
+        $result = core_tag_external::get_tag_cloud(0, false, 150, 'name', 'Mice');
+        $result = external_api::clean_returnvalue(core_tag_external::get_tag_cloud_returns(), $result);
+        $this->assertCount(1, $result['tags']); // Only the searched tags.
+        $this->assertEquals(1, $result['tagscount']);
+        $this->assertEquals(1, $result['totalcount']); // When searching, the total is always for the search.
+        $this->assertEquals('Mice', $result['tags'][0]['name']);
+
+        $result = core_tag_external::get_tag_cloud(0, false, 150, 'name', 'Conejo');
+        $result = external_api::clean_returnvalue(core_tag_external::get_tag_cloud_returns(), $result);
+        $this->assertCount(0, $result['tags']); // Nothing found.
+        $this->assertEquals(0, $result['tagscount']);
+        $this->assertEquals(0, $result['totalcount']); // When searching, the total is always for the search.
+
+        // Test standard filtering.
+        $micetag = core_tag_tag::get_by_name($defaultcoll, 'Mice', '*');
+        $micetag->update(array('isstandard' => 1));
+
+        $result = core_tag_external::get_tag_cloud(0, true);
+        $result = external_api::clean_returnvalue(core_tag_external::get_tag_cloud_returns(), $result);
+        $this->assertCount(1, $result['tags']);
+        $this->assertEquals(1, $result['tagscount']);
+        $this->assertEquals(1, $result['totalcount']); // When searching, the total is always for the search.
+        $this->assertEquals('Mice', $result['tags'][0]['name']);
+
+        // Test course context filtering.
+        $result = core_tag_external::get_tag_cloud(0, false, 150, 'name', '', 0, $coursecontext->id);
+        $result = external_api::clean_returnvalue(core_tag_external::get_tag_cloud_returns(), $result);
+        $this->assertCount(1, $result['tags']);
+        $this->assertEquals(1, $result['tagscount']);
+        $this->assertEquals(1, $result['totalcount']); // When searching, the total is always for the search.
+        $this->assertEquals('Cats', $result['tags'][0]['name']);
+
+        // Complete system context.
+        $result = core_tag_external::get_tag_cloud(0, false, 150, 'name', '', 0, context_system::instance()->id);
+        $result = external_api::clean_returnvalue(core_tag_external::get_tag_cloud_returns(), $result);
+        $this->assertCount(4, $result['tags']);
+        $this->assertEquals(4, $result['tagscount']);
+
+        // Just system context - avoid children.
+        $result = core_tag_external::get_tag_cloud(0, false, 150, 'name', '', 0, context_system::instance()->id, 0);
+        $result = external_api::clean_returnvalue(core_tag_external::get_tag_cloud_returns(), $result);
+        $this->assertCount(2, $result['tags']);
+        $this->assertEquals(2, $result['tagscount']); // Horses and Cats.
+        $this->assertEquals('Cats', $result['tags'][0]['name']);
+        $this->assertEquals('Horses', $result['tags'][1]['name']);
+    }
 }
index 03f5cad..58f316d 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2019041000.03;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2019041000.04;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.