Merge branch 'MDL-58720-master' of git://github.com/jleyva/moodle
authorJun Pataleta <jun@moodle.com>
Mon, 1 May 2017 02:47:52 +0000 (10:47 +0800)
committerJun Pataleta <jun@moodle.com>
Mon, 1 May 2017 02:47:52 +0000 (10:47 +0800)
1  2 
course/externallib.php
course/tests/externallib_test.php
course/upgrade.txt

diff --combined course/externallib.php
@@@ -166,7 -166,13 +166,13 @@@ class core_course_external extends exte
              $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;
                  }
  
                                  $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;
                          }
  
                          //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';
+                         // Return contents only if the user can access to the module.
+                         if ($cm->uservisible) {
+                             $baseurl = 'webservice/pluginfile.php';
  
-                         //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();
+                             // 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();
+                                 }
                              }
                          }
  
                      '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(
                                      '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'),
                  'showgrades' => new external_value(PARAM_INT, '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
                  'newsitems' => new external_value(PARAM_INT, 'Number of recent items appearing on the course page', VALUE_OPTIONAL),
                  'startdate' => new external_value(PARAM_INT, 'Timestamp when the course start', VALUE_OPTIONAL),
 +                'enddate' => new external_value(PARAM_INT, 'Timestamp when the course end', VALUE_OPTIONAL),
                  'maxbytes' => new external_value(PARAM_INT, 'Largest size of file that can be uploaded into', VALUE_OPTIONAL),
                  'showreports' => new external_value(PARAM_INT, 'Are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
                  'visible' => new external_value(PARAM_INT, '1: available to student, 0:not available', VALUE_OPTIONAL),
                  continue;
              }
              // Return information for any user that can access the course.
 -            $coursefields = array('format', 'showgrades', 'newsitems', 'startdate', 'maxbytes', 'showreports', 'visible',
 +            $coursefields = array('format', 'showgrades', 'newsitems', 'startdate', 'enddate', 'maxbytes', 'showreports', 'visible',
                  'groupmode', 'groupmodeforce', 'defaultgroupingid', 'enablecompletion', 'completionnotify', 'lang', 'theme',
                  'marker');
  
@@@ -770,7 -770,7 +770,7 @@@ class core_course_externallib_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),
          $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);
  
          $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);
          // 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,
              }
          }
          $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)));
          // 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']);
      }
  
      /**
          // 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"]);
      }
          // 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"]);
      }
          // 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"]);
          $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
          $this->assertCount(3, $result['courses']);
          // Expect to receive all the fields.
 -        $this->assertCount(36, $result['courses'][0]);
 -        $this->assertCount(36, $result['courses'][1]);
 -        $this->assertCount(36, $result['courses'][2]);
 +        $this->assertCount(37, $result['courses'][0]);
 +        $this->assertCount(37, $result['courses'][1]);
 +        $this->assertCount(37, $result['courses'][2]);
  
          $result = core_course_external::get_courses_by_field('id', $course1->id);
          $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
          $this->assertCount(1, $result['courses']);
          $this->assertEquals($course1->id, $result['courses'][0]['id']);
          // Expect to receive all the fields.
 -        $this->assertCount(36, $result['courses'][0]);
 +        $this->assertCount(37, $result['courses'][0]);
  
          $result = core_course_external::get_courses_by_field('id', $course2->id);
          $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
          $result = core_course_external::get_courses_by_field();
          $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
          $this->assertCount(2, $result['courses']);
 -        $this->assertCount(29, $result['courses'][0]);
 -        $this->assertCount(29, $result['courses'][1]);
 +        $this->assertCount(30, $result['courses'][0]);
 +        $this->assertCount(30, $result['courses'][1]);
  
          $result = core_course_external::get_courses_by_field('id', $course1->id);
          $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
          $this->assertCount(1, $result['courses']);
          $this->assertEquals($course1->id, $result['courses'][0]['id']);
          // Expect to receive all the files that a student can see.
 -        $this->assertCount(29, $result['courses'][0]);
 +        $this->assertCount(30, $result['courses'][0]);
  
          // Check default filters.
          $filters = $result['courses'][0]['filters'];
          $result = core_course_external::get_courses_by_field();
          $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
          $this->assertCount(2, $result['courses']);
 -        $this->assertCount(29, $result['courses'][0]);  // Site course.
 +        $this->assertCount(30, $result['courses'][0]);  // Site course.
          $this->assertCount(13, $result['courses'][1]);  // Only public information, not enrolled.
  
          $result = core_course_external::get_courses_by_field('id', $course1->id);
diff --combined course/upgrade.txt
@@@ -4,12 -4,14 +4,15 @@@ information provided here is intended e
  === 3.3 ===
  
   * External function core_course_external::get_courses_by_field now returns the course filters list and status.
 + * External function core_course_external::get_courses_by_field now returns the end date of the course.
   * External function core_course_external::get_course_contents now return the following additional file fields:
     - mimetype (the file mime type)
     - 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 ===