Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace core_completion;
  18  
  19  use core_completion_external;
  20  use externallib_advanced_testcase;
  21  
  22  defined('MOODLE_INTERNAL') || die();
  23  
  24  global $CFG;
  25  
  26  require_once($CFG->dirroot . '/webservice/tests/helpers.php');
  27  
  28  /**
  29   * External completion functions unit tests
  30   *
  31   * @package    core_completion
  32   * @category   external
  33   * @copyright  2015 Juan Leyva <juan@moodle.com>
  34   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   * @since      Moodle 2.9
  36   */
  37  class externallib_test extends externallib_advanced_testcase {
  38  
  39      /**
  40       * Test update_activity_completion_status_manually
  41       */
  42      public function test_update_activity_completion_status_manually() {
  43          global $DB, $CFG;
  44  
  45          $this->resetAfterTest(true);
  46  
  47          $CFG->enablecompletion = true;
  48          $user = $this->getDataGenerator()->create_user();
  49          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
  50          $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id),
  51                                                               array('completion' => 1));
  52          $cm = get_coursemodule_from_id('data', $data->cmid);
  53  
  54          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
  55          $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
  56  
  57          $this->setUser($user);
  58  
  59          $result = core_completion_external::update_activity_completion_status_manually($data->cmid, true);
  60          // We need to execute the return values cleaning process to simulate the web service server.
  61          $result = \external_api::clean_returnvalue(
  62              core_completion_external::update_activity_completion_status_manually_returns(), $result);
  63  
  64          // Check in DB.
  65          $this->assertEquals(1, $DB->get_field('course_modules_completion', 'completionstate',
  66                              array('coursemoduleid' => $data->cmid)));
  67  
  68          // Check using the API.
  69          $completion = new \completion_info($course);
  70          $completiondata = $completion->get_data($cm);
  71          $this->assertEquals(1, $completiondata->completionstate);
  72          $this->assertTrue($result['status']);
  73  
  74          $result = core_completion_external::update_activity_completion_status_manually($data->cmid, false);
  75          // We need to execute the return values cleaning process to simulate the web service server.
  76          $result = \external_api::clean_returnvalue(
  77              core_completion_external::update_activity_completion_status_manually_returns(), $result);
  78  
  79          $this->assertEquals(0, $DB->get_field('course_modules_completion', 'completionstate',
  80                              array('coursemoduleid' => $data->cmid)));
  81          $completiondata = $completion->get_data($cm);
  82          $this->assertEquals(0, $completiondata->completionstate);
  83          $this->assertTrue($result['status']);
  84      }
  85  
  86      /**
  87       * Test update_activity_completion_status
  88       */
  89      public function test_get_activities_completion_status() {
  90          global $DB, $CFG, $PAGE;
  91  
  92          $this->resetAfterTest(true);
  93  
  94          $CFG->enablecompletion = true;
  95          $student = $this->getDataGenerator()->create_user();
  96          $teacher = $this->getDataGenerator()->create_user();
  97  
  98          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1,
  99                                                                      'groupmode' => SEPARATEGROUPS,
 100                                                                      'groupmodeforce' => 1));
 101          \availability_completion\condition::wipe_static_cache();
 102  
 103          $data = $this->getDataGenerator()->create_module('data',
 104              ['course' => $course->id],
 105              ['completion' => COMPLETION_TRACKING_MANUAL],
 106          );
 107          $forum = $this->getDataGenerator()->create_module('forum',
 108              ['course' => $course->id],
 109              ['completion' => COMPLETION_TRACKING_MANUAL],
 110          );
 111          $forumautocompletion = $this->getDataGenerator()->create_module('forum',
 112              ['course' => $course->id],
 113              ['showdescription' => true, 'completionview' => 1, 'completion' => COMPLETION_TRACKING_AUTOMATIC],
 114          );
 115          $availability = '{"op":"&","c":[{"type":"completion","cm":' . $forum->cmid .',"e":1}],"showc":[true]}';
 116          $assign = $this->getDataGenerator()->create_module('assign',
 117              ['course' => $course->id],
 118              ['availability' => $availability],
 119          );
 120          $assignautocompletion = $this->getDataGenerator()->create_module('assign',
 121              ['course' => $course->id], [
 122                  'showdescription' => true,
 123                  'completionview' => 1,
 124                  'completion' => COMPLETION_TRACKING_AUTOMATIC,
 125                  'completiongradeitemnumber' => 1,
 126                  'completionpassgrade' => 1,
 127              ],
 128          );
 129          $page = $this->getDataGenerator()->create_module('page',  array('course' => $course->id),
 130                                                              array('completion' => 1, 'visible' => 0));
 131  
 132          $cmdata = get_coursemodule_from_id('data', $data->cmid);
 133          $cmforum = get_coursemodule_from_id('forum', $forum->cmid);
 134          $cmforumautocompletion = get_coursemodule_from_id('forum', $forumautocompletion->cmid);
 135  
 136          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 137          $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
 138          $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
 139          $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
 140  
 141          $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
 142          $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
 143  
 144          // Teacher and student in different groups initially.
 145          groups_add_member($group1->id, $student->id);
 146          groups_add_member($group2->id, $teacher->id);
 147  
 148          $this->setUser($student);
 149          // Forum complete.
 150          $completion = new \completion_info($course);
 151          $completion->update_state($cmforum, COMPLETION_COMPLETE);
 152  
 153          $result = core_completion_external::get_activities_completion_status($course->id, $student->id);
 154          // We need to execute the return values cleaning process to simulate the web service server.
 155          $result = \external_api::clean_returnvalue(
 156              core_completion_external::get_activities_completion_status_returns(), $result);
 157  
 158          // We added 6 activities, but only 4 with completion enabled and one of those is hidden.
 159          $numberofactivities = 6;
 160          $numberofhidden = 1;
 161          $numberofcompletions = $numberofactivities - $numberofhidden;
 162          $numberofstatusstudent = 4;
 163  
 164          $this->assertCount($numberofstatusstudent, $result['statuses']);
 165  
 166          $activitiesfound = 0;
 167          foreach ($result['statuses'] as $status) {
 168              if ($status['cmid'] == $forum->cmid and $status['modname'] == 'forum' and $status['instance'] == $forum->id) {
 169                  $activitiesfound++;
 170                  $this->assertEquals(COMPLETION_COMPLETE, $status['state']);
 171                  $this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
 172                  $this->assertTrue($status['valueused']);
 173                  $this->assertTrue($status['hascompletion']);
 174                  $this->assertFalse($status['isautomatic']);
 175                  $this->assertTrue($status['istrackeduser']);
 176                  $this->assertTrue($status['uservisible']);
 177                  $details = $status['details'];
 178                  $this->assertCount(0, $details);
 179              } else if ($status['cmid'] == $forumautocompletion->cmid) {
 180                  $activitiesfound++;
 181                  $this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
 182                  $this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $status['tracking']);
 183                  $this->assertFalse($status['valueused']);
 184                  $this->assertTrue($status['hascompletion']);
 185                  $this->assertTrue($status['isautomatic']);
 186                  $this->assertTrue($status['istrackeduser']);
 187                  $this->assertTrue($status['uservisible']);
 188                  $details = $status['details'];
 189                  $this->assertCount(1, $details);
 190                  $this->assertEquals('completionview', $details[0]['rulename']);
 191                  $this->assertEquals(0, $details[0]['rulevalue']['status']);
 192  
 193              } else if ($status['cmid'] == $assignautocompletion->cmid) {
 194                  $activitiesfound++;
 195                  $this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
 196                  $this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $status['tracking']);
 197                  $this->assertFalse($status['valueused']);
 198                  $this->assertTrue($status['hascompletion']);
 199                  $this->assertTrue($status['isautomatic']);
 200                  $this->assertTrue($status['istrackeduser']);
 201                  $this->assertTrue($status['uservisible']);
 202                  $details = $status['details'];
 203                  $this->assertCount(3, $details);
 204                  $expecteddetails = [
 205                      'completionview',
 206                      'completionusegrade',
 207                      'completionpassgrade',
 208                  ];
 209                  foreach ($expecteddetails as $index => $name) {
 210                      $this->assertEquals($name, $details[$index]['rulename']);
 211                      $this->assertEquals(0, $details[$index]['rulevalue']['status']);
 212                  }
 213              } else if ($status['cmid'] == $data->cmid and $status['modname'] == 'data' and $status['instance'] == $data->id) {
 214                  $activitiesfound++;
 215                  $this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
 216                  $this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
 217                  $this->assertFalse($status['valueused']);
 218                  $this->assertFalse($status['valueused']);
 219                  $this->assertTrue($status['hascompletion']);
 220                  $this->assertFalse($status['isautomatic']);
 221                  $this->assertTrue($status['istrackeduser']);
 222                  $this->assertTrue($status['uservisible']);
 223                  $details = $status['details'];
 224                  $this->assertCount(0, $details);
 225              }
 226          }
 227          $this->assertEquals(4, $activitiesfound);
 228  
 229          // Teacher should see students status, they are in different groups but the teacher can access all groups.
 230          $this->setUser($teacher);
 231          $result = core_completion_external::get_activities_completion_status($course->id, $student->id);
 232          // We need to execute the return values cleaning process to simulate the web service server.
 233          $result = \external_api::clean_returnvalue(
 234              core_completion_external::get_activities_completion_status_returns(), $result);
 235  
 236          $this->assertCount($numberofcompletions, $result['statuses']);
 237  
 238          // Override status by teacher.
 239          $completion->update_state($cmforum, COMPLETION_INCOMPLETE, $student->id, true);
 240  
 241          $result = core_completion_external::get_activities_completion_status($course->id, $student->id);
 242          // We need to execute the return values cleaning process to simulate the web service server.
 243          $result = \external_api::clean_returnvalue(
 244              core_completion_external::get_activities_completion_status_returns(), $result);
 245  
 246          // Check forum has been overriden by the teacher.
 247          foreach ($result['statuses'] as $status) {
 248              if ($status['cmid'] == $forum->cmid) {
 249                  $this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
 250                  $this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
 251                  $this->assertEquals($teacher->id, $status['overrideby']);
 252                  break;
 253              }
 254          }
 255  
 256          // Teacher should see his own completion status.
 257  
 258          // Forum complete for teacher.
 259          $completion = new \completion_info($course);
 260          $completion->update_state($cmforum, COMPLETION_COMPLETE);
 261  
 262          $result = core_completion_external::get_activities_completion_status($course->id, $teacher->id);
 263          // We need to execute the return values cleaning process to simulate the web service server.
 264          $result = \external_api::clean_returnvalue(
 265              core_completion_external::get_activities_completion_status_returns(), $result);
 266  
 267          $this->assertCount($numberofcompletions, $result['statuses']);
 268  
 269          $activitiesfound = 0;
 270          foreach ($result['statuses'] as $status) {
 271              if ($status['cmid'] == $forum->cmid and $status['modname'] == 'forum' and $status['instance'] == $forum->id) {
 272                  $activitiesfound++;
 273                  $this->assertEquals(COMPLETION_COMPLETE, $status['state']);
 274                  $this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
 275              } else if (in_array($status['cmid'], [$forumautocompletion->cmid, $assignautocompletion->cmid])) {
 276                  $activitiesfound++;
 277                  $this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
 278                  $this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $status['tracking']);
 279              } else {
 280                  $activitiesfound++;
 281                  $this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
 282                  $this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
 283              }
 284          }
 285          $this->assertEquals(5, $activitiesfound);
 286  
 287          // Change teacher role capabilities (disable access all groups).
 288          $context = \context_course::instance($course->id);
 289          assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $teacherrole->id, $context);
 290          accesslib_clear_all_caches_for_unit_testing();
 291  
 292          try {
 293              $result = core_completion_external::get_activities_completion_status($course->id, $student->id);
 294              $this->fail('Exception expected due to groups permissions.');
 295          } catch (\moodle_exception $e) {
 296              $this->assertEquals('accessdenied', $e->errorcode);
 297          }
 298  
 299          // Now add the teacher in the same group.
 300          groups_add_member($group1->id, $teacher->id);
 301          $result = core_completion_external::get_activities_completion_status($course->id, $student->id);
 302          // We need to execute the return values cleaning process to simulate the web service server.
 303          $result = \external_api::clean_returnvalue(
 304              core_completion_external::get_activities_completion_status_returns(), $result);
 305          $this->assertCount($numberofcompletions, $result['statuses']);
 306      }
 307  
 308      /**
 309       * Test override_activity_completion_status
 310       */
 311      public function test_override_activity_completion_status() {
 312          global $DB, $CFG;
 313          $this->resetAfterTest(true);
 314  
 315          // Create course with teacher and student enrolled.
 316          $CFG->enablecompletion = true;
 317          $course  = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
 318          $student = $this->getDataGenerator()->create_user();
 319          $teacher = $this->getDataGenerator()->create_user();
 320          $studentrole = $DB->get_record('role', ['shortname' => 'student']);
 321          $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
 322          $teacherrole = $DB->get_record('role', ['shortname' => 'teacher']);
 323          $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
 324  
 325          // Create 2 activities, one with manual completion (data), one with automatic completion triggered by viewing it (forum).
 326          $data    = $this->getDataGenerator()->create_module('data', ['course' => $course->id], ['completion' => 1]);
 327          $forum   = $this->getDataGenerator()->create_module('forum',  ['course' => $course->id],
 328                                                              ['completion' => 2, 'completionview' => 1]);
 329          $cmdata = get_coursemodule_from_id('data', $data->cmid);
 330          $cmforum = get_coursemodule_from_id('forum', $forum->cmid);
 331  
 332          // Manually complete the data activity as the student.
 333          $this->setUser($student);
 334          $completion = new \completion_info($course);
 335          $completion->update_state($cmdata, COMPLETION_COMPLETE);
 336  
 337          // Test overriding the status of the manual-completion-activity 'incomplete'.
 338          $this->setUser($teacher);
 339          $result = core_completion_external::override_activity_completion_status($student->id, $data->cmid, COMPLETION_INCOMPLETE);
 340          $result = \external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
 341          $this->assertEquals($result['state'], COMPLETION_INCOMPLETE);
 342          $completiondata = $completion->get_data($cmdata, false, $student->id);
 343          $this->assertEquals(COMPLETION_INCOMPLETE, $completiondata->completionstate);
 344  
 345          // Test overriding the status of the manual-completion-activity back to 'complete'.
 346          $result = core_completion_external::override_activity_completion_status($student->id, $data->cmid, COMPLETION_COMPLETE);
 347          $result = \external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
 348          $this->assertEquals($result['state'], COMPLETION_COMPLETE);
 349          $completiondata = $completion->get_data($cmdata, false, $student->id);
 350          $this->assertEquals(COMPLETION_COMPLETE, $completiondata->completionstate);
 351  
 352          // Test overriding the status of the auto-completion-activity to 'complete'.
 353          $result = core_completion_external::override_activity_completion_status($student->id, $forum->cmid, COMPLETION_COMPLETE);
 354          $result = \external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
 355          $this->assertEquals($result['state'], COMPLETION_COMPLETE);
 356          $completionforum = $completion->get_data($cmforum, false, $student->id);
 357          $this->assertEquals(COMPLETION_COMPLETE, $completionforum->completionstate);
 358  
 359          // Test overriding the status of the auto-completion-activity to 'incomplete'.
 360          $result = core_completion_external::override_activity_completion_status($student->id, $forum->cmid, COMPLETION_INCOMPLETE);
 361          $result = \external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
 362          $this->assertEquals($result['state'], COMPLETION_INCOMPLETE);
 363          $completionforum = $completion->get_data($cmforum, false, $student->id);
 364          $this->assertEquals(COMPLETION_INCOMPLETE, $completionforum->completionstate);
 365  
 366          // Test overriding the status of the auto-completion-activity to an invalid state.
 367          $this->expectException('moodle_exception');
 368          core_completion_external::override_activity_completion_status($student->id, $forum->cmid, 3);
 369      }
 370  
 371      /**
 372       * Test overriding the activity completion status as a user without the capability to do so.
 373       */
 374      public function test_override_status_user_without_capability() {
 375          global $DB, $CFG;
 376          $this->resetAfterTest(true);
 377  
 378          // Create course with teacher and student enrolled.
 379          $CFG->enablecompletion = true;
 380          $course  = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
 381          $student = $this->getDataGenerator()->create_user();
 382          $teacher = $this->getDataGenerator()->create_user();
 383          $studentrole = $DB->get_record('role', ['shortname' => 'student']);
 384          $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
 385          $teacherrole = $DB->get_record('role', ['shortname' => 'teacher']);
 386          $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
 387          $coursecontext = \context_course::instance($course->id);
 388  
 389          // Create an activity with automatic completion (a forum).
 390          $forum   = $this->getDataGenerator()->create_module('forum',  ['course' => $course->id],
 391              ['completion' => 2, 'completionview' => 1]);
 392  
 393          // Test overriding the status of the activity for a user without the capability.
 394          $this->setUser($teacher);
 395          assign_capability('moodle/course:overridecompletion', CAP_PREVENT, $teacherrole->id, $coursecontext);
 396          $this->expectException('required_capability_exception');
 397          core_completion_external::override_activity_completion_status($student->id, $forum->cmid, COMPLETION_COMPLETE);
 398      }
 399  
 400      /**
 401       * Test get_course_completion_status
 402       */
 403      public function test_get_course_completion_status() {
 404          global $DB, $CFG, $COMPLETION_CRITERIA_TYPES;
 405          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_self.php');
 406          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_date.php');
 407          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_unenrol.php');
 408          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_activity.php');
 409          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_duration.php');
 410          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_grade.php');
 411          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_role.php');
 412          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_course.php');
 413  
 414          $this->resetAfterTest(true);
 415  
 416          $CFG->enablecompletion = true;
 417          $student = $this->getDataGenerator()->create_user();
 418          $teacher = $this->getDataGenerator()->create_user();
 419  
 420          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1,
 421                                                                      'groupmode' => SEPARATEGROUPS,
 422                                                                      'groupmodeforce' => 1));
 423  
 424          $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id),
 425                                                               array('completion' => 1));
 426          $forum = $this->getDataGenerator()->create_module('forum',  array('course' => $course->id),
 427                                                               array('completion' => 1));
 428          $assign = $this->getDataGenerator()->create_module('assign',  array('course' => $course->id));
 429  
 430          $cmdata = get_coursemodule_from_id('data', $data->cmid);
 431          $cmforum = get_coursemodule_from_id('forum', $forum->cmid);
 432  
 433          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 434          $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
 435          $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
 436          $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
 437  
 438          $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
 439          $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
 440          // Teacher and student in different groups initially.
 441          groups_add_member($group1->id, $student->id);
 442          groups_add_member($group2->id, $teacher->id);
 443  
 444          // Set completion rules.
 445          $completion = new \completion_info($course);
 446  
 447          // Loop through each criteria type and run its update_config() method.
 448  
 449          $criteriadata = new \stdClass();
 450          $criteriadata->id = $course->id;
 451          $criteriadata->criteria_activity = array();
 452          // Some activities.
 453          $criteriadata->criteria_activity[$cmdata->id] = 1;
 454          $criteriadata->criteria_activity[$cmforum->id] = 1;
 455  
 456          // In a week criteria date value.
 457          $criteriadata->criteria_date_value = time() + WEEKSECS;
 458  
 459          // Self completion.
 460          $criteriadata->criteria_self = 1;
 461  
 462          foreach ($COMPLETION_CRITERIA_TYPES as $type) {
 463              $class = 'completion_criteria_'.$type;
 464              $criterion = new $class();
 465              $criterion->update_config($criteriadata);
 466          }
 467  
 468          // Handle overall aggregation.
 469          $aggdata = array(
 470              'course'        => $course->id,
 471              'criteriatype'  => null
 472          );
 473          $aggregation = new \completion_aggregation($aggdata);
 474          $aggregation->setMethod(COMPLETION_AGGREGATION_ALL);
 475          $aggregation->save();
 476  
 477          $aggdata['criteriatype'] = COMPLETION_CRITERIA_TYPE_ACTIVITY;
 478          $aggregation = new \completion_aggregation($aggdata);
 479          $aggregation->setMethod(COMPLETION_AGGREGATION_ALL);
 480          $aggregation->save();
 481  
 482          $this->setUser($student);
 483  
 484          $result = core_completion_external::get_course_completion_status($course->id, $student->id);
 485          // We need to execute the return values cleaning process to simulate the web service server.
 486          $studentresult = \external_api::clean_returnvalue(
 487              core_completion_external::get_course_completion_status_returns(), $result);
 488  
 489          // 3 different criteria.
 490          $this->assertCount(3, $studentresult['completionstatus']['completions']);
 491  
 492          $this->assertEquals(COMPLETION_AGGREGATION_ALL, $studentresult['completionstatus']['aggregation']);
 493          $this->assertFalse($studentresult['completionstatus']['completed']);
 494  
 495          $this->assertEquals('No', $studentresult['completionstatus']['completions'][0]['status']);
 496          $this->assertEquals('No', $studentresult['completionstatus']['completions'][1]['status']);
 497          $this->assertEquals('No', $studentresult['completionstatus']['completions'][2]['status']);
 498  
 499          // Teacher should see students status, they are in different groups but the teacher can access all groups.
 500          $this->setUser($teacher);
 501          $result = core_completion_external::get_course_completion_status($course->id, $student->id);
 502          // We need to execute the return values cleaning process to simulate the web service server.
 503          $teacherresult = \external_api::clean_returnvalue(
 504              core_completion_external::get_course_completion_status_returns(), $result);
 505  
 506          $this->assertEquals($studentresult, $teacherresult);
 507  
 508          // Change teacher role capabilities (disable access al goups).
 509          $context = \context_course::instance($course->id);
 510          assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $teacherrole->id, $context);
 511          accesslib_clear_all_caches_for_unit_testing();
 512  
 513          try {
 514              $result = core_completion_external::get_course_completion_status($course->id, $student->id);
 515              $this->fail('Exception expected due to groups permissions.');
 516          } catch (\moodle_exception $e) {
 517              $this->assertEquals('accessdenied', $e->errorcode);
 518          }
 519  
 520          // Now add the teacher in the same group.
 521          groups_add_member($group1->id, $teacher->id);
 522          $result = core_completion_external::get_course_completion_status($course->id, $student->id);
 523          // We need to execute the return values cleaning process to simulate the web service server.
 524          $teacherresult = \external_api::clean_returnvalue(
 525              core_completion_external::get_course_completion_status_returns(), $result);
 526  
 527          $this->assertEquals($studentresult, $teacherresult);
 528  
 529      }
 530  
 531      /**
 532       * Test mark_course_self_completed
 533       */
 534      public function test_mark_course_self_completed() {
 535          global $DB, $CFG;
 536          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_self.php');
 537  
 538          $this->resetAfterTest(true);
 539  
 540          $CFG->enablecompletion = true;
 541          $student = $this->getDataGenerator()->create_user();
 542          $teacher = $this->getDataGenerator()->create_user();
 543  
 544          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 545  
 546          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 547          $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
 548  
 549          // Set completion rules.
 550          $completion = new \completion_info($course);
 551  
 552          $criteriadata = new \stdClass();
 553          $criteriadata->id = $course->id;
 554          $criteriadata->criteria_activity = array();
 555  
 556          // Self completion.
 557          $criteriadata->criteria_self = COMPLETION_CRITERIA_TYPE_SELF;
 558          $class = 'completion_criteria_self';
 559          $criterion = new $class();
 560          $criterion->update_config($criteriadata);
 561  
 562          // Handle overall aggregation.
 563          $aggdata = array(
 564              'course'        => $course->id,
 565              'criteriatype'  => null
 566          );
 567          $aggregation = new \completion_aggregation($aggdata);
 568          $aggregation->setMethod(COMPLETION_AGGREGATION_ALL);
 569          $aggregation->save();
 570  
 571          $this->setUser($student);
 572  
 573          $result = core_completion_external::mark_course_self_completed($course->id);
 574          // We need to execute the return values cleaning process to simulate the web service server.
 575          $result = \external_api::clean_returnvalue(
 576              core_completion_external::mark_course_self_completed_returns(), $result);
 577  
 578          // We expect a valid result.
 579          $this->assertEquals(true, $result['status']);
 580  
 581          $result = core_completion_external::get_course_completion_status($course->id, $student->id);
 582          // We need to execute the return values cleaning process to simulate the web service server.
 583          $result = \external_api::clean_returnvalue(
 584              core_completion_external::get_course_completion_status_returns(), $result);
 585  
 586          // Course must be completed.
 587          $this->assertEquals(COMPLETION_COMPLETE, $result['completionstatus']['completions'][0]['complete']);
 588  
 589          try {
 590              $result = core_completion_external::mark_course_self_completed($course->id);
 591              $this->fail('Exception expected due course already self completed.');
 592          } catch (\moodle_exception $e) {
 593              $this->assertEquals('useralreadymarkedcomplete', $e->errorcode);
 594          }
 595  
 596      }
 597  
 598  }