MDL-58720 course: Return restricted sections and modules in get_contents
authorJuan Leyva <juanleyvadelgado@gmail.com>
Tue, 25 Apr 2017 20:25:50 +0000 (22:25 +0200)
committerJuan Leyva <juanleyvadelgado@gmail.com>
Thu, 27 Apr 2017 08:53:17 +0000 (10:53 +0200)
core_course_get_contents was only checking for modules visible by the
user (via uservisible).
It was not checking for modules that are not visible for the user but
should be displayed in the course because they have access restrictions
configured.

course/externallib.php
course/tests/externallib_test.php
course/upgrade.txt

index 27733fc..9a71e99 100644 (file)
@@ -166,7 +166,13 @@ class core_course_external extends external_api {
             $modinfosections = $modinfo->get_sections();
             foreach ($sections as $key => $section) {
 
-                if (!$section->uservisible) {
+                // Show the section if the user is permitted to access it, OR if it's not available
+                // but there is some available info text which explains the reason & should display.
+                $showsection = $section->uservisible ||
+                    ($section->visible && !$section->available &&
+                    !empty($section->availableinfo));
+
+                if (!$showsection) {
                     continue;
                 }
 
@@ -203,15 +209,21 @@ class core_course_external extends external_api {
                                 $context->id, 'course', 'section', $section->id, $options);
                 $sectionvalues['section'] = $section->section;
                 $sectionvalues['hiddenbynumsections'] = $section->section > $coursenumsections ? 1 : 0;
+                $sectionvalues['uservisible'] = $section->uservisible;
+                if (!empty($section->availableinfo)) {
+                    $sectionvalues['availabilityinfo'] = \core_availability\info::format_info($section->availableinfo, $course);
+                }
+
                 $sectioncontents = array();
 
-                //for each module of the section
-                if (empty($filters['excludemodules']) and !empty($modinfosections[$section->section])) {
+                // For each module of the section (if it is visible).
+                if ($section->uservisible and empty($filters['excludemodules']) and !empty($modinfosections[$section->section])) {
                     foreach ($modinfosections[$section->section] as $cmid) {
                         $cm = $modinfo->cms[$cmid];
 
-                        // stop here if the module is not visible to the user
-                        if (!$cm->uservisible) {
+                        // Stop here if the module is not visible to the user on the course main page:
+                        // The user can't access the module and the user can't view the module on the course page.
+                        if (!$cm->uservisible && !$cm->is_visible_on_course_page()) {
                             continue;
                         }
 
@@ -271,24 +283,29 @@ class core_course_external extends external_api {
                         //user that can view hidden module should know about the visibility
                         $module['visible'] = $cm->visible;
                         $module['visibleoncoursepage'] = $cm->visibleoncoursepage;
+                        $module['uservisible'] = $cm->uservisible;
+                        if (!empty($cm->availableinfo)) {
+                            $module['availabilityinfo'] = \core_availability\info::format_info($cm->availableinfo, $course);
+                        }
 
                         // Availability date (also send to user who can see hidden module).
                         if ($CFG->enableavailability && ($canviewhidden || $canupdatecourse)) {
                             $module['availability'] = $cm->availability;
                         }
 
-                        $baseurl = 'webservice/pluginfile.php';
-
-                        //call $modulename_export_contents
-                        //(each module callback take care about checking the capabilities)
+                        // Return contents only if the user can access to the module.
+                        if ($cm->uservisible) {
+                            $baseurl = 'webservice/pluginfile.php';
 
-                        require_once($CFG->dirroot . '/mod/' . $cm->modname . '/lib.php');
-                        $getcontentfunction = $cm->modname.'_export_contents';
-                        if (function_exists($getcontentfunction)) {
-                            if (empty($filters['excludecontents']) and $contents = $getcontentfunction($cm, $baseurl)) {
-                                $module['contents'] = $contents;
-                            } else {
-                                $module['contents'] = array();
+                            // Call $modulename_export_contents (each module callback take care about checking the capabilities).
+                            require_once($CFG->dirroot . '/mod/' . $cm->modname . '/lib.php');
+                            $getcontentfunction = $cm->modname.'_export_contents';
+                            if (function_exists($getcontentfunction)) {
+                                if (empty($filters['excludecontents']) and $contents = $getcontentfunction($cm, $baseurl)) {
+                                    $module['contents'] = $contents;
+                                } else {
+                                    $module['contents'] = array();
+                                }
                             }
                         }
 
@@ -334,6 +351,8 @@ class core_course_external extends external_api {
                     'section' => new external_value(PARAM_INT, 'Section number inside the course', VALUE_OPTIONAL),
                     'hiddenbynumsections' => new external_value(PARAM_INT, 'Whether is a section hidden in the course format',
                                                                 VALUE_OPTIONAL),
+                    'uservisible' => new external_value(PARAM_BOOL, 'Is the section visible for the user?', VALUE_OPTIONAL),
+                    'availabilityinfo' => new external_value(PARAM_RAW, 'Availability information.', VALUE_OPTIONAL),
                     'modules' => new external_multiple_structure(
                             new external_single_structure(
                                 array(
@@ -343,6 +362,10 @@ class core_course_external extends external_api {
                                     'instance' => new external_value(PARAM_INT, 'instance id', VALUE_OPTIONAL),
                                     'description' => new external_value(PARAM_RAW, 'activity description', VALUE_OPTIONAL),
                                     'visible' => new external_value(PARAM_INT, 'is the module visible', VALUE_OPTIONAL),
+                                    'uservisible' => new external_value(PARAM_BOOL, 'Is the module visible for the user?',
+                                        VALUE_OPTIONAL),
+                                    'availabilityinfo' => new external_value(PARAM_RAW, 'Availability information.',
+                                        VALUE_OPTIONAL),
                                     'visibleoncoursepage' => new external_value(PARAM_INT, 'is the module visible on course page',
                                         VALUE_OPTIONAL),
                                     'modicon' => new external_value(PARAM_URL, 'activity icon url'),
index 054f2ce..dd88d9a 100644 (file)
@@ -770,7 +770,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
      */
     private function prepare_get_course_contents_test() {
         global $DB;
-        $course  = self::getDataGenerator()->create_course(['numsections' => 2]);
+        $course  = self::getDataGenerator()->create_course(['numsections' => 3]);
         $forumdescription = 'This is the forum description';
         $forum = $this->getDataGenerator()->create_module('forum',
             array('course' => $course->id, 'intro' => $forumdescription),
@@ -785,9 +785,19 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
         $label = $this->getDataGenerator()->create_module('label', array('course' => $course->id,
             'intro' => $labeldescription));
         $labelcm = get_coursemodule_from_instance('label', $label->id);
-        $url = $this->getDataGenerator()->create_module('url', array('course' => $course->id,
-            'name' => 'URL: % & $ ../', 'section' => 2));
+        // Module with availability restrictions not met.
+        $url = $this->getDataGenerator()->create_module('url',
+            array('course' => $course->id, 'name' => 'URL: % & $ ../', 'section' => 2),
+            array('availability' => '{"op":"&","c":[{"type":"date","d":">=","t":2502892800}],"showc":[true]}'));
         $urlcm = get_coursemodule_from_instance('url', $url->id);
+        // Module for the last section.
+        $this->getDataGenerator()->create_module('url',
+            array('course' => $course->id, 'name' => 'URL for last section', 'section' => 3));
+        // Module for section 1 with availability restrictions met.
+        $yesterday = time() - DAYSECS;
+        $this->getDataGenerator()->create_module('url',
+            array('course' => $course->id, 'name' => 'URL restrictions met', 'section' => 1),
+            array('availability' => '{"op":"&","c":[{"type":"date","d":">=","t":'. $yesterday .'}],"showc":[true]}'));
 
         // Set the required capabilities by the external function.
         $context = context_course::instance($course->id);
@@ -797,6 +807,11 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
 
         $conditions = array('course' => $course->id, 'section' => 2);
         $DB->set_field('course_sections', 'summary', 'Text with iframe <iframe src="https://moodle.org"></iframe>', $conditions);
+
+        // Add date availability condition not met for last section.
+        $availability = '{"op":"&","c":[{"type":"date","d":">=","t":2502892800}],"showc":[true]}';
+        $DB->set_field('course_sections', 'availability', $availability,
+                array('course' => $course->id, 'section' => 3));
         rebuild_course_cache($course->id, true);
 
         return array($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm);
@@ -814,13 +829,9 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
         // We need to execute the return values cleaning process to simulate the web service server.
         $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
 
-        // Check that forum and label descriptions are correctly returned.
-        $firstsection = array_shift($sections);
-        $lastsection = array_pop($sections);
-
         $modinfo = get_fast_modinfo($course);
         $testexecuted = 0;
-        foreach ($firstsection['modules'] as $module) {
+        foreach ($sections[0]['modules'] as $module) {
             if ($module['id'] == $forumcm->id and $module['modname'] == 'forum') {
                 $cm = $modinfo->cms[$forumcm->id];
                 $formattedtext = format_text($cm->content, FORMAT_HTML,
@@ -838,15 +849,24 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
             }
         }
         $this->assertEquals(2, $testexecuted);
-        $this->assertEquals(0, $firstsection['section']);
+        $this->assertEquals(0, $sections[0]['section']);
 
         // Check that the only return section has the 5 created modules.
-        $this->assertCount(4, $firstsection['modules']);
-        $this->assertCount(1, $lastsection['modules']);
-        $this->assertEquals(2, $lastsection['section']);
-        $this->assertContains('<iframe', $lastsection['summary']);
-        $this->assertContains('</iframe>', $lastsection['summary']);
-
+        $this->assertCount(4, $sections[0]['modules']);
+        $this->assertCount(1, $sections[1]['modules']);
+        $this->assertCount(1, $sections[2]['modules']);
+        $this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions.
+        $this->assertNotEmpty($sections[3]['availabilityinfo']);
+        $this->assertEquals(1, $sections[1]['section']);
+        $this->assertEquals(2, $sections[2]['section']);
+        $this->assertEquals(3, $sections[3]['section']);
+        $this->assertContains('<iframe', $sections[2]['summary']);
+        $this->assertContains('</iframe>', $sections[2]['summary']);
+        // The module with the availability restriction met is returning contents.
+        $this->assertNotEmpty($sections[1]['modules'][0]['contents']);
+        // The module with the availability restriction not met is not returning contents.
+        $this->assertArrayNotHasKey('contents', $sections[2]['modules'][0]);
+        $this->assertNotEmpty($sections[2]['modules'][0]['availabilityinfo']);
         try {
             $sections = core_course_external::get_course_contents($course->id,
                                                                     array(array("name" => "invalid", "value" => 1)));
@@ -871,11 +891,8 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
         // We need to execute the return values cleaning process to simulate the web service server.
         $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
 
-        $firstsection = array_shift($sections);
-        $lastsection = array_pop($sections);
-
-        $this->assertEmpty($firstsection['modules']);
-        $this->assertEmpty($lastsection['modules']);
+        $this->assertEmpty($sections[0]['modules']);
+        $this->assertEmpty($sections[1]['modules']);
     }
 
     /**
@@ -934,7 +951,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
         // We need to execute the return values cleaning process to simulate the web service server.
         $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
 
-        $this->assertCount(3, $sections);
+        $this->assertCount(4, $sections);
         $this->assertCount(1, $sections[0]['modules']);
         $this->assertEquals($forumcm->id, $sections[0]['modules'][0]["id"]);
     }
@@ -976,7 +993,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
         // We need to execute the return values cleaning process to simulate the web service server.
         $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
 
-        $this->assertCount(3, $sections);
+        $this->assertCount(4, $sections);
         $this->assertCount(1, $sections[0]['modules']);
         $this->assertEquals($forumcm->id, $sections[0]['modules'][0]["id"]);
     }
@@ -998,7 +1015,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
         // We need to execute the return values cleaning process to simulate the web service server.
         $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
 
-        $this->assertCount(3, $sections);
+        $this->assertCount(4, $sections);
         $this->assertCount(1, $sections[0]['modules']);
         $this->assertEquals("page", $sections[0]['modules'][0]["modname"]);
         $this->assertEquals($pagecm->instance, $sections[0]['modules'][0]["instance"]);
index 1c53aa7..eb2b56e 100644 (file)
@@ -9,6 +9,9 @@ information provided here is intended especially for developers.
    - isexternalfile (if is a file reference to a external repository)
    - repositorytype (the repository name in case is a external file)
    Those fields are VALUE_OPTIONAL for backwards compatibility.
+ * External function core_course_external::get_course_contents now return the following fields for section and modules:
+   - uservisible (whether the section or module is visible by the user)
+   - availabilityinfo (availability information if the course or module has any access restriction set
 
 === 3.2 ===