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 400 and 401] [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 tool_uploadcourse;
  18  
  19  use tool_uploadcourse_processor;
  20  use tool_uploadcourse_course;
  21  
  22  /**
  23   * Course test case.
  24   *
  25   * @package    tool_uploadcourse
  26   * @copyright  2013 Frédéric Massart
  27   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late
  28   */
  29  class course_test extends \advanced_testcase {
  30  
  31      public function test_proceed_without_prepare() {
  32          $this->resetAfterTest(true);
  33          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
  34          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
  35          $data = array();
  36          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
  37          $this->expectException(\coding_exception::class);
  38          $co->proceed();
  39      }
  40  
  41      public function test_proceed_when_prepare_failed() {
  42          $this->resetAfterTest(true);
  43          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
  44          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
  45          $data = array();
  46          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
  47          $this->assertFalse($co->prepare());
  48          $this->expectException(\moodle_exception::class);
  49          $co->proceed();
  50      }
  51  
  52      public function test_proceed_when_already_started() {
  53          $this->resetAfterTest(true);
  54          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
  55          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
  56          $data = array('shortname' => 'test', 'fullname' => 'New course', 'summary' => 'New', 'category' => 1);
  57          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
  58          $this->assertTrue($co->prepare());
  59          $co->proceed();
  60          $this->expectException('coding_exception');
  61          $co->proceed();
  62      }
  63  
  64      public function test_invalid_shortname() {
  65          $this->resetAfterTest(true);
  66          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
  67          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
  68          $data = array('shortname' => '<invalid>', 'fullname' => 'New course', 'summary' => 'New', 'category' => 1);
  69          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
  70          $this->assertFalse($co->prepare());
  71          $this->assertArrayHasKey('invalidshortname', $co->get_errors());
  72      }
  73  
  74      public function test_invalid_shortname_too_long() {
  75          $this->resetAfterTest();
  76  
  77          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
  78          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
  79  
  80          $upload = new tool_uploadcourse_course($mode, $updatemode, [
  81              'category' => 1,
  82              'fullname' => 'New course',
  83              'shortname' => str_repeat('X', 2000),
  84          ]);
  85  
  86          $this->assertFalse($upload->prepare());
  87          $this->assertArrayHasKey('invalidshortnametoolong', $upload->get_errors());
  88      }
  89  
  90      public function test_invalid_fullname_too_long() {
  91          $this->resetAfterTest();
  92  
  93          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
  94          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
  95  
  96          $upload = new tool_uploadcourse_course($mode, $updatemode, [
  97              'category' => 1,
  98              'fullname' => str_repeat('X', 2000),
  99          ]);
 100  
 101          $this->assertFalse($upload->prepare());
 102          $this->assertArrayHasKey('invalidfullnametoolong', $upload->get_errors());
 103      }
 104  
 105      public function test_invalid_visibility() {
 106          $this->resetAfterTest(true);
 107          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 108          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
 109          $data = array('shortname' => 'test', 'fullname' => 'New course', 'summary' => 'New', 'category' => 1, 'visible' => 2);
 110          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 111          $this->assertFalse($co->prepare());
 112          $this->assertArrayHasKey('invalidvisibilitymode', $co->get_errors());
 113      }
 114  
 115      /**
 116       * Test setting 'downloadcontent' field when the feature is globally disabled
 117       */
 118      public function test_downloadcontent_disabled(): void {
 119          $this->resetAfterTest();
 120          $this->setAdminUser();
 121  
 122          set_config('downloadcoursecontentallowed', 0);
 123  
 124          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 125          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
 126  
 127          $upload = new tool_uploadcourse_course($mode, $updatemode, [
 128              'category' => 1,
 129              'fullname' => 'Testing',
 130              'shortname' => 'T101',
 131              'downloadcontent' => DOWNLOAD_COURSE_CONTENT_ENABLED,
 132          ]);
 133  
 134          $this->assertFalse($upload->prepare());
 135          $this->assertArrayHasKey('downloadcontentnotallowed', $upload->get_errors());
 136      }
 137  
 138      /**
 139       * Test setting 'downloadcontent' field when user doesn't have required capability
 140       */
 141      public function test_downloadcontent_capability(): void {
 142          global $DB;
 143  
 144          $this->resetAfterTest();
 145  
 146          set_config('downloadcoursecontentallowed', 1);
 147  
 148          // Create category in which to create the new course.
 149          $category = $this->getDataGenerator()->create_category();
 150          $categorycontext = \context_coursecat::instance($category->id);
 151  
 152          $user = $this->getDataGenerator()->create_user();
 153          $this->setUser($user);
 154  
 155          // Assign the user as a manager of the category, disable ability to configure course content download.
 156          $roleid = $DB->get_field('role', 'id', ['shortname' => 'manager']);
 157          role_assign($roleid, $user->id, $categorycontext);
 158          role_change_permission($roleid, $categorycontext, 'moodle/course:configuredownloadcontent', CAP_PROHIBIT);
 159  
 160          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 161          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
 162  
 163          $upload = new tool_uploadcourse_course($mode, $updatemode, [
 164              'category' => $category->id,
 165              'fullname' => 'Testing',
 166              'shortname' => 'T101',
 167              'downloadcontent' => DOWNLOAD_COURSE_CONTENT_ENABLED,
 168          ]);
 169  
 170          $this->assertFalse($upload->prepare());
 171          $this->assertArrayHasKey('downloadcontentnotallowed', $upload->get_errors());
 172      }
 173  
 174      /**
 175       * Test setting 'downloadcontent' field to an invalid value
 176       */
 177      public function test_downloadcontent_invalid(): void {
 178          $this->resetAfterTest();
 179          $this->setAdminUser();
 180  
 181          set_config('downloadcoursecontentallowed', 1);
 182  
 183          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 184          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
 185  
 186          $upload = new tool_uploadcourse_course($mode, $updatemode, [
 187              'category' => 1,
 188              'fullname' => 'Testing',
 189              'shortname' => 'T101',
 190              'downloadcontent' => 42,
 191          ]);
 192  
 193          $this->assertFalse($upload->prepare());
 194          $this->assertArrayHasKey('invaliddownloadcontent', $upload->get_errors());
 195      }
 196  
 197      public function test_create() {
 198          global $DB;
 199          $this->resetAfterTest(true);
 200  
 201          // Existing course.
 202          $c1 = $this->getDataGenerator()->create_course(array('shortname' => 'c1', 'summary' => 'Yay!'));
 203          $this->assertTrue($DB->record_exists('course', array('shortname' => 'c1')));
 204  
 205          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
 206  
 207          // Try to add a new course.
 208          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 209          $data = array('shortname' => 'newcourse', 'fullname' => 'New course', 'summary' => 'New', 'category' => 1);
 210          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 211          $this->assertTrue($co->prepare());
 212          $this->assertFalse($DB->record_exists('course', array('shortname' => 'newcourse')));
 213          $co->proceed();
 214          $course = $DB->get_record('course', array('shortname' => 'newcourse'), '*', MUST_EXIST);
 215          $this->assertEquals(0, course_get_format($course)->get_course()->coursedisplay);
 216  
 217          // Try to add a new course, that already exists.
 218          $coursecount = $DB->count_records('course', array());
 219          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 220          $data = array('shortname' => 'c1', 'fullname' => 'C1FN', 'summary' => 'C1', 'category' => 1);
 221          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 222          $this->assertFalse($co->prepare());
 223          $this->assertArrayHasKey('courseexistsanduploadnotallowed', $co->get_errors());
 224          $this->assertEquals($coursecount, $DB->count_records('course', array()));
 225          $this->assertNotEquals('C1', $DB->get_field_select('course', 'summary', 'shortname = :s', array('s' => 'c1')));
 226  
 227          // Try to add new with shortname incrementation.
 228          $mode = tool_uploadcourse_processor::MODE_CREATE_ALL;
 229          $data = array('shortname' => 'c1', 'fullname' => 'C1FN', 'summary' => 'C1', 'category' => 1);
 230          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 231          $this->assertTrue($co->prepare());
 232          $co->proceed();
 233          $this->assertTrue($DB->record_exists('course', array('shortname' => 'c2')));
 234  
 235          // Add a new course with non-default course format option.
 236          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 237          $data = array('shortname' => 'c3', 'fullname' => 'C3', 'summary' => 'New c3', 'category' => 1,
 238              'format' => 'weeks', 'coursedisplay' => 1);
 239          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 240          $this->assertTrue($co->prepare());
 241          $co->proceed();
 242          $course = $DB->get_record('course', array('shortname' => 'c3'), '*', MUST_EXIST);
 243          $this->assertEquals(1, course_get_format($course)->get_course()->coursedisplay);
 244      }
 245  
 246      public function test_create_with_sections() {
 247          global $DB;
 248          $this->resetAfterTest(true);
 249          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
 250          $defaultnumsections = get_config('moodlecourse', 'numsections');
 251  
 252          // Add new course, make sure default number of sections is created.
 253          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 254          $data = array('shortname' => 'newcourse1', 'fullname' => 'New course1', 'format' => 'topics', 'category' => 1);
 255          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 256          $this->assertTrue($co->prepare());
 257          $co->proceed();
 258          $courseid = $DB->get_field('course', 'id', array('shortname' => 'newcourse1'));
 259          $this->assertNotEmpty($courseid);
 260          $this->assertEquals($defaultnumsections + 1,
 261              $DB->count_records('course_sections', ['course' => $courseid]));
 262  
 263          // Add new course specifying number of sections.
 264          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 265          $data = array('shortname' => 'newcourse2', 'fullname' => 'New course2', 'format' => 'topics', 'category' => 1,
 266              'numsections' => 15);
 267          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 268          $this->assertTrue($co->prepare());
 269          $co->proceed();
 270          $courseid = $DB->get_field('course', 'id', array('shortname' => 'newcourse2'));
 271          $this->assertNotEmpty($courseid);
 272          $this->assertEquals(15 + 1,
 273              $DB->count_records('course_sections', ['course' => $courseid]));
 274      }
 275  
 276      public function test_delete() {
 277          global $DB;
 278          $this->resetAfterTest(true);
 279  
 280          $c1 = $this->getDataGenerator()->create_course();
 281          $c2 = $this->getDataGenerator()->create_course();
 282  
 283          $this->assertTrue($DB->record_exists('course', array('shortname' => $c1->shortname)));
 284          $this->assertFalse($DB->record_exists('course', array('shortname' => 'DoesNotExist')));
 285  
 286          $mode = tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE;
 287          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 288  
 289          // Try delete when option not available.
 290          $importoptions = array('candelete' => false);
 291          $data = array('shortname' => $c1->shortname, 'delete' => 1);
 292          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
 293          $this->assertFalse($co->prepare());
 294          $this->assertArrayHasKey('coursedeletionnotallowed', $co->get_errors());
 295          $this->assertTrue($DB->record_exists('course', array('shortname' => $c1->shortname)));
 296  
 297          // Try delete when not requested.
 298          $importoptions = array('candelete' => true);
 299          $data = array('shortname' => $c1->shortname, 'delete' => 0);
 300          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
 301          $this->assertTrue($co->prepare());
 302          $co->proceed();
 303          $this->assertTrue($DB->record_exists('course', array('shortname' => $c1->shortname)));
 304  
 305          // Try delete when requested.
 306          $importoptions = array('candelete' => true);
 307          $data = array('shortname' => $c1->shortname, 'delete' => 1);
 308          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
 309          $this->assertTrue($co->prepare());
 310          $co->proceed();
 311          $this->assertFalse($DB->record_exists('course', array('shortname' => $c1->shortname)));
 312          $this->assertTrue($DB->record_exists('course', array('shortname' => $c2->shortname)));
 313  
 314          // Try deleting non-existing record, this should not fail.
 315          $data = array('shortname' => 'DoesNotExist', 'delete' => 1);
 316          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
 317          $this->assertFalse($co->prepare());
 318          $this->assertArrayHasKey('cannotdeletecoursenotexist', $co->get_errors());
 319      }
 320  
 321      public function test_update() {
 322          global $DB;
 323          $this->resetAfterTest(true);
 324  
 325          $c1 = $this->getDataGenerator()->create_course(array('shortname' => 'c1'));
 326  
 327          // Try to update with existing shortnames, not allowing creation, and updating nothing.
 328          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
 329          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
 330          $data = array('shortname' => 'c1', 'fullname' => 'New fullname');
 331          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 332          $this->assertFalse($co->prepare());
 333          $this->assertArrayHasKey('updatemodedoessettonothing', $co->get_errors());
 334  
 335          // Try to update with non-existing shortnames.
 336          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
 337          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 338          $data = array('shortname' => 'DoesNotExist', 'fullname' => 'New fullname');
 339          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 340          $this->assertFalse($co->prepare());
 341          $this->assertArrayHasKey('coursedoesnotexistandcreatenotallowed', $co->get_errors());
 342  
 343          // Try a proper update.
 344          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
 345          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 346          $data = array('shortname' => 'c1', 'fullname' => 'New fullname');
 347          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 348          $this->assertTrue($co->prepare());
 349          $co->proceed();
 350          $this->assertEquals('New fullname', $DB->get_field_select('course', 'fullname', 'shortname = :s', array('s' => 'c1')));
 351  
 352          // Try a proper update with defaults.
 353          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
 354          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_OR_DEFAUTLS;
 355          $data = array('shortname' => 'c1', 'fullname' => 'Another fullname');
 356          $defaults = array('fullname' => 'Not this one', 'summary' => 'Awesome summary');
 357          $co = new tool_uploadcourse_course($mode, $updatemode, $data, $defaults);
 358          $this->assertTrue($co->prepare());
 359          $co->proceed();
 360          $this->assertEquals('Another fullname', $DB->get_field_select('course', 'fullname', 'shortname = :s', array('s' => 'c1')));
 361          $this->assertEquals('Awesome summary', $DB->get_field_select('course', 'summary', 'shortname = :s', array('s' => 'c1')));
 362  
 363          // Try a proper update missing only.
 364          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
 365          $updatemode = tool_uploadcourse_processor::UPDATE_MISSING_WITH_DATA_OR_DEFAUTLS;
 366          $DB->set_field('course', 'summary', '', array('shortname' => 'c1'));
 367          $this->assertEquals('', $DB->get_field_select('course', 'summary', 'shortname = :s', array('s' => 'c1')));
 368          $data = array('shortname' => 'c1', 'summary' => 'Fill in summary');
 369          $defaults = array('summary' => 'Do not use this summary');
 370          $co = new tool_uploadcourse_course($mode, $updatemode, $data, $defaults);
 371          $this->assertTrue($co->prepare());
 372          $co->proceed();
 373          $this->assertEquals('Fill in summary', $DB->get_field_select('course', 'summary', 'shortname = :s', array('s' => 'c1')));
 374  
 375          // Try a proper update missing only using defaults.
 376          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
 377          $updatemode = tool_uploadcourse_processor::UPDATE_MISSING_WITH_DATA_OR_DEFAUTLS;
 378          $DB->set_field('course', 'summary', '', array('shortname' => 'c1'));
 379          $this->assertEquals('', $DB->get_field_select('course', 'summary', 'shortname = :s', array('s' => 'c1')));
 380          $data = array('shortname' => 'c1');
 381          $defaults = array('summary' => 'Use this summary');
 382          $co = new tool_uploadcourse_course($mode, $updatemode, $data, $defaults);
 383          $this->assertTrue($co->prepare());
 384          $co->proceed();
 385          $this->assertEquals('Use this summary', $DB->get_field_select('course', 'summary', 'shortname = :s', array('s' => 'c1')));
 386  
 387          // Update course format option.
 388          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
 389          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 390          $data = array('shortname' => 'c1', 'coursedisplay' => 1);
 391          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 392          $this->assertTrue($co->prepare());
 393          $co->proceed();
 394          $course = $DB->get_record('course', array('shortname' => 'c1'), '*', MUST_EXIST);
 395          $this->assertEquals(1, course_get_format($course)->get_course()->coursedisplay);
 396      }
 397  
 398      public function test_data_saved() {
 399          global $DB;
 400  
 401          $this->resetAfterTest(true);
 402          $this->setAdminUser(); // To avoid warnings related to 'moodle/course:setforcedlanguage' capability check.
 403  
 404          set_config('downloadcoursecontentallowed', 1);
 405  
 406          // Create.
 407          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 408          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
 409          $data = array(
 410              'shortname' => 'c1',
 411              'fullname' => 'Fullname',
 412              'category' => '1',
 413              'visible' => '0',
 414              'downloadcontent' => DOWNLOAD_COURSE_CONTENT_DISABLED,
 415              'idnumber' => '123abc',
 416              'summary' => 'Summary',
 417              'format' => 'topics',
 418              'theme' => 'afterburner',
 419              'lang' => 'en',
 420              'newsitems' => '7',
 421              'showgrades' => '0',
 422              'showreports' => '1',
 423              'legacyfiles' => '1',
 424              'maxbytes' => '1234',
 425              'groupmode' => '2',
 426              'groupmodeforce' => '1',
 427              'enablecompletion' => '1',
 428              'showactivitydates' => '1',
 429              'tags' => 'Cat, Dog',
 430  
 431              'role_teacher' => 'Knight',
 432              'role_manager' => 'Jedi',
 433  
 434              'enrolment_1' => 'guest',
 435              'enrolment_2' => 'self',
 436              'enrolment_2_roleid' => '1',
 437              'enrolment_3' => 'manual',
 438              'enrolment_3_disable' => '1',
 439          );
 440  
 441          // There should be a start date if there is a end date.
 442          $data['enddate'] = '7 June 1990';
 443          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 444          $this->assertFalse($co->prepare());
 445          $this->assertArrayHasKey('nostartdatenoenddate', $co->get_errors());
 446  
 447          $data['startdate'] = '8 June 1990';
 448          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 449          $this->assertFalse($co->prepare());
 450          $this->assertArrayHasKey('enddatebeforestartdate', $co->get_errors());
 451  
 452          // They are correct now.
 453          $data['enddate'] = '18 June 1990';
 454  
 455          $this->assertFalse($DB->record_exists('course', array('shortname' => 'c1')));
 456          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 457          $this->assertTrue($co->prepare());
 458          $co->proceed();
 459          $this->assertTrue($DB->record_exists('course', array('shortname' => 'c1')));
 460          $course = $DB->get_record('course', array('shortname' => 'c1'));
 461          $ctx = \context_course::instance($course->id);
 462  
 463          $this->assertEquals($data['fullname'], $course->fullname);
 464          $this->assertEquals($data['category'], $course->category);
 465          $this->assertEquals($data['visible'], $course->visible);
 466          $this->assertEquals($data['downloadcontent'], $course->downloadcontent);
 467          $this->assertEquals(mktime(0, 0, 0, 6, 8, 1990), $course->startdate);
 468          $this->assertEquals(mktime(0, 0, 0, 6, 18, 1990), $course->enddate);
 469          $this->assertEquals($data['idnumber'], $course->idnumber);
 470          $this->assertEquals($data['summary'], $course->summary);
 471          $this->assertEquals($data['format'], $course->format);
 472          $this->assertEquals($data['theme'], $course->theme);
 473          $this->assertEquals($data['lang'], $course->lang);
 474          $this->assertEquals($data['newsitems'], $course->newsitems);
 475          $this->assertEquals($data['showgrades'], $course->showgrades);
 476          $this->assertEquals($data['showreports'], $course->showreports);
 477          $this->assertEquals($data['legacyfiles'], $course->legacyfiles);
 478          $this->assertEquals($data['maxbytes'], $course->maxbytes);
 479          $this->assertEquals($data['groupmode'], $course->groupmode);
 480          $this->assertEquals($data['groupmodeforce'], $course->groupmodeforce);
 481          $this->assertEquals($data['enablecompletion'], $course->enablecompletion);
 482          $this->assertEquals($data['showactivitydates'], $course->showactivitydates);
 483          $this->assertEquals($data['tags'], join(', ', \core_tag_tag::get_item_tags_array('core', 'course', $course->id)));
 484  
 485          // Roles.
 486          $roleids = array();
 487          $roles = get_all_roles();
 488          foreach ($roles as $role) {
 489              $roleids[$role->shortname] = $role->id;
 490          }
 491          $this->assertEquals('Knight', $DB->get_field_select('role_names', 'name',
 492              'roleid = :roleid AND contextid = :ctxid', array('ctxid' => $ctx->id, 'roleid' => $roleids['teacher'])));
 493          $this->assertEquals('Jedi', $DB->get_field_select('role_names', 'name',
 494              'roleid = :roleid AND contextid = :ctxid', array('ctxid' => $ctx->id, 'roleid' => $roleids['manager'])));
 495  
 496          // Enrolment methods.
 497          $enroldata = array();
 498          $instances = enrol_get_instances($course->id, false);
 499          $this->assertCount(3, $instances);
 500          foreach ($instances as $instance) {
 501              $enroldata[$instance->enrol] = $instance;
 502          }
 503  
 504          $this->assertNotEmpty($enroldata['guest']);
 505          $this->assertEquals(ENROL_INSTANCE_ENABLED, $enroldata['guest']->status);
 506          $this->assertNotEmpty($enroldata['self']);
 507          $this->assertEquals($data['enrolment_2_roleid'], $enroldata['self']->roleid);
 508          $this->assertEquals(ENROL_INSTANCE_ENABLED, $enroldata['self']->status);
 509          $this->assertNotEmpty($enroldata['manual']);
 510          $this->assertEquals(ENROL_INSTANCE_DISABLED, $enroldata['manual']->status);
 511  
 512          // Update existing course.
 513          $cat = $this->getDataGenerator()->create_category();
 514          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
 515          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 516          $data = array(
 517              'shortname' => 'c1',
 518              'fullname' => 'Fullname 2',
 519              'category' => $cat->id,
 520              'visible' => '1',
 521              'downloadcontent' => DOWNLOAD_COURSE_CONTENT_ENABLED,
 522              'idnumber' => 'changeidn',
 523              'summary' => 'Summary 2',
 524              'format' => 'topics',
 525              'theme' => 'classic',
 526              'lang' => '',
 527              'newsitems' => '2',
 528              'showgrades' => '1',
 529              'showreports' => '0',
 530              'legacyfiles' => '0',
 531              'maxbytes' => '4321',
 532              'groupmode' => '1',
 533              'groupmodeforce' => '0',
 534              'enablecompletion' => '0',
 535              'showactivitydates' => '0',
 536  
 537              'role_teacher' => 'Teacher',
 538              'role_manager' => 'Manager',
 539  
 540              'enrolment_1' => 'guest',
 541              'enrolment_1_disable' => '1',
 542              'enrolment_2' => 'self',
 543              'enrolment_2_roleid' => '2',
 544              'enrolment_3' => 'manual',
 545              'enrolment_3_delete' => '1',
 546          );
 547  
 548          $this->assertTrue($DB->record_exists('course', array('shortname' => 'c1')));
 549  
 550          $data['enddate'] = '31 June 1984';
 551          // Previous start and end dates are 8 and 18 June 1990.
 552          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 553          $this->assertFalse($co->prepare());
 554          $this->assertArrayHasKey('enddatebeforestartdate', $co->get_errors());
 555  
 556          $data['startdate'] = '19 June 1990';
 557          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 558          $this->assertFalse($co->prepare());
 559          $this->assertArrayHasKey('enddatebeforestartdate', $co->get_errors());
 560  
 561          // They are correct now.
 562          $data['startdate'] = '11 June 1984';
 563  
 564          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 565          $this->assertTrue($co->prepare());
 566          $co->proceed();
 567          $course = $DB->get_record('course', array('shortname' => 'c1'));
 568          $ctx = \context_course::instance($course->id);
 569  
 570          $this->assertEquals($data['fullname'], $course->fullname);
 571          $this->assertEquals($data['category'], $course->category);
 572          $this->assertEquals($data['visible'], $course->visible);
 573          $this->assertEquals($data['downloadcontent'], $course->downloadcontent);
 574          $this->assertEquals(mktime(0, 0, 0, 6, 11, 1984), $course->startdate);
 575          $this->assertEquals(mktime(0, 0, 0, 6, 31, 1984), $course->enddate);
 576          $this->assertEquals($data['idnumber'], $course->idnumber);
 577          $this->assertEquals($data['summary'], $course->summary);
 578          $this->assertEquals($data['format'], $course->format);
 579          $this->assertEquals($data['theme'], $course->theme);
 580          $this->assertEquals($data['lang'], $course->lang);
 581          $this->assertEquals($data['newsitems'], $course->newsitems);
 582          $this->assertEquals($data['showgrades'], $course->showgrades);
 583          $this->assertEquals($data['showreports'], $course->showreports);
 584          $this->assertEquals($data['legacyfiles'], $course->legacyfiles);
 585          $this->assertEquals($data['maxbytes'], $course->maxbytes);
 586          $this->assertEquals($data['groupmode'], $course->groupmode);
 587          $this->assertEquals($data['groupmodeforce'], $course->groupmodeforce);
 588          $this->assertEquals($data['enablecompletion'], $course->enablecompletion);
 589          $this->assertEquals($data['showactivitydates'], $course->showactivitydates);
 590  
 591          // Roles.
 592          $roleids = array();
 593          $roles = get_all_roles();
 594          foreach ($roles as $role) {
 595              $roleids[$role->shortname] = $role->id;
 596          }
 597          $this->assertEquals('Teacher', $DB->get_field_select('role_names', 'name',
 598              'roleid = :roleid AND contextid = :ctxid', array('ctxid' => $ctx->id, 'roleid' => $roleids['teacher'])));
 599          $this->assertEquals('Manager', $DB->get_field_select('role_names', 'name',
 600              'roleid = :roleid AND contextid = :ctxid', array('ctxid' => $ctx->id, 'roleid' => $roleids['manager'])));
 601  
 602          // Enrolment methods.
 603          $enroldata = array();
 604          $instances = enrol_get_instances($course->id, false);
 605          $this->assertCount(2, $instances);
 606          foreach ($instances as $instance) {
 607              $enroldata[$instance->enrol] = $instance;
 608          }
 609  
 610          $this->assertNotEmpty($enroldata['guest']);
 611          $this->assertEquals(ENROL_INSTANCE_DISABLED, $enroldata['guest']->status);
 612          $this->assertNotEmpty($enroldata['self']);
 613          $this->assertEquals($data['enrolment_2_roleid'], $enroldata['self']->roleid);
 614          $this->assertEquals(ENROL_INSTANCE_ENABLED, $enroldata['self']->status);
 615      }
 616  
 617      public function test_default_data_saved() {
 618          global $DB;
 619  
 620          $this->resetAfterTest(true);
 621          $this->setAdminUser();
 622  
 623          set_config('downloadcoursecontentallowed', 1);
 624  
 625          // Create.
 626          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 627          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
 628          $data = array(
 629              'shortname' => 'c1',
 630          );
 631          $defaultdata = array(
 632              'fullname' => 'Fullname',
 633              'category' => '1',
 634              'visible' => '0',
 635              'downloadcontent' => DOWNLOAD_COURSE_CONTENT_DISABLED,
 636              'startdate' => 644803200,
 637              'enddate' => 645667200,
 638              'idnumber' => '123abc',
 639              'summary' => 'Summary',
 640              'format' => 'topics',
 641              'theme' => 'afterburner',
 642              'lang' => 'en',
 643              'newsitems' => '7',
 644              'showgrades' => '0',
 645              'showreports' => '1',
 646              'legacyfiles' => '1',
 647              'maxbytes' => '1234',
 648              'groupmode' => '2',
 649              'groupmodeforce' => '1',
 650              'enablecompletion' => '1',
 651              'showactivitydates' => '1',
 652          );
 653  
 654          $this->assertFalse($DB->record_exists('course', array('shortname' => 'c1')));
 655          $co = new tool_uploadcourse_course($mode, $updatemode, $data, $defaultdata);
 656          $this->assertTrue($co->prepare());
 657          $co->proceed();
 658          $this->assertTrue($DB->record_exists('course', array('shortname' => 'c1')));
 659          $course = $DB->get_record('course', array('shortname' => 'c1'));
 660          $ctx = \context_course::instance($course->id);
 661  
 662          $this->assertEquals($defaultdata['fullname'], $course->fullname);
 663          $this->assertEquals($defaultdata['category'], $course->category);
 664          $this->assertEquals($defaultdata['visible'], $course->visible);
 665          $this->assertEquals($defaultdata['downloadcontent'], $course->downloadcontent);
 666          $this->assertEquals($defaultdata['startdate'], $course->startdate);
 667          $this->assertEquals($defaultdata['enddate'], $course->enddate);
 668          $this->assertEquals($defaultdata['idnumber'], $course->idnumber);
 669          $this->assertEquals($defaultdata['summary'], $course->summary);
 670          $this->assertEquals($defaultdata['format'], $course->format);
 671          $this->assertEquals($defaultdata['theme'], $course->theme);
 672          $this->assertEquals($defaultdata['lang'], $course->lang);
 673          $this->assertEquals($defaultdata['newsitems'], $course->newsitems);
 674          $this->assertEquals($defaultdata['showgrades'], $course->showgrades);
 675          $this->assertEquals($defaultdata['showreports'], $course->showreports);
 676          $this->assertEquals($defaultdata['legacyfiles'], $course->legacyfiles);
 677          $this->assertEquals($defaultdata['maxbytes'], $course->maxbytes);
 678          $this->assertEquals($defaultdata['groupmode'], $course->groupmode);
 679          $this->assertEquals($defaultdata['groupmodeforce'], $course->groupmodeforce);
 680          $this->assertEquals($defaultdata['enablecompletion'], $course->enablecompletion);
 681          $this->assertEquals($defaultdata['showactivitydates'], $course->showactivitydates);
 682  
 683          // Update.
 684          $cat = $this->getDataGenerator()->create_category();
 685          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
 686          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_OR_DEFAUTLS;
 687          $data = array(
 688              'shortname' => 'c1',
 689          );
 690          $defaultdata = array(
 691              'fullname' => 'Fullname 2',
 692              'category' => $cat->id,
 693              'visible' => '1',
 694              'downloadcontent' => DOWNLOAD_COURSE_CONTENT_ENABLED,
 695              'startdate' => 455760000,
 696              'enddate' => 457488000,
 697              'idnumber' => 'changedid',
 698              'summary' => 'Summary 2',
 699              'format' => 'topics',
 700              'theme' => 'classic',
 701              'lang' => '',
 702              'newsitems' => '2',
 703              'showgrades' => '1',
 704              'showreports' => '0',
 705              'legacyfiles' => '0',
 706              'maxbytes' => '1111',
 707              'groupmode' => '1',
 708              'groupmodeforce' => '0',
 709              'enablecompletion' => '0',
 710              'showactivitydates' => '0',
 711          );
 712  
 713          $this->assertTrue($DB->record_exists('course', array('shortname' => 'c1')));
 714          $co = new tool_uploadcourse_course($mode, $updatemode, $data, $defaultdata);
 715          $this->assertTrue($co->prepare());
 716          $co->proceed();
 717          $this->assertTrue($DB->record_exists('course', array('shortname' => 'c1')));
 718          $course = $DB->get_record('course', array('shortname' => 'c1'));
 719          $ctx = \context_course::instance($course->id);
 720  
 721          $this->assertEquals($defaultdata['fullname'], $course->fullname);
 722          $this->assertEquals($defaultdata['category'], $course->category);
 723          $this->assertEquals($defaultdata['visible'], $course->visible);
 724          $this->assertEquals($defaultdata['downloadcontent'], $course->downloadcontent);
 725          $this->assertEquals($defaultdata['startdate'], $course->startdate);
 726          $this->assertEquals($defaultdata['enddate'], $course->enddate);
 727          $this->assertEquals($defaultdata['idnumber'], $course->idnumber);
 728          $this->assertEquals($defaultdata['summary'], $course->summary);
 729          $this->assertEquals($defaultdata['format'], $course->format);
 730          $this->assertEquals($defaultdata['theme'], $course->theme);
 731          $this->assertEquals($defaultdata['lang'], $course->lang);
 732          $this->assertEquals($defaultdata['newsitems'], $course->newsitems);
 733          $this->assertEquals($defaultdata['showgrades'], $course->showgrades);
 734          $this->assertEquals($defaultdata['showreports'], $course->showreports);
 735          $this->assertEquals($defaultdata['legacyfiles'], $course->legacyfiles);
 736          $this->assertEquals($defaultdata['maxbytes'], $course->maxbytes);
 737          $this->assertEquals($defaultdata['groupmode'], $course->groupmode);
 738          $this->assertEquals($defaultdata['groupmodeforce'], $course->groupmodeforce);
 739          $this->assertEquals($defaultdata['enablecompletion'], $course->enablecompletion);
 740          $this->assertEquals($defaultdata['showactivitydates'], $course->showactivitydates);
 741      }
 742  
 743      public function test_rename() {
 744          global $DB;
 745          $this->resetAfterTest(true);
 746  
 747          $c1 = $this->getDataGenerator()->create_course(array('shortname' => 'c1'));
 748          $c2 = $this->getDataGenerator()->create_course(array('shortname' => 'c2'));
 749  
 750          // Cannot rename when creating.
 751          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 752          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 753          $importoptions = array('canrename' => true);
 754          $data = array('shortname' => 'c1', 'rename' => 'newshortname');
 755          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
 756          $this->assertFalse($co->prepare());
 757          $this->assertArrayHasKey('courseexistsanduploadnotallowed', $co->get_errors());
 758  
 759          // Cannot rename when creating.
 760          $mode = tool_uploadcourse_processor::MODE_CREATE_ALL;
 761          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 762          $importoptions = array('canrename' => true);
 763          $data = array('shortname' => 'c1', 'rename' => 'newshortname', 'category' => 1, 'summary' => 'S', 'fullname' => 'F');
 764          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
 765          $this->assertFalse($co->prepare());
 766          $this->assertArrayHasKey('canonlyrenameinupdatemode', $co->get_errors());
 767  
 768          // Error when not allowed to rename the course.
 769          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
 770          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 771          $importoptions = array('canrename' => false);
 772          $data = array('shortname' => 'c1', 'rename' => 'newshortname');
 773          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
 774          $this->assertFalse($co->prepare());
 775          $this->assertArrayHasKey('courserenamingnotallowed', $co->get_errors());
 776  
 777          // Can rename when updating.
 778          $mode = tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE;
 779          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 780          $importoptions = array('canrename' => true);
 781          $data = array('shortname' => 'c1', 'rename' => 'newshortname');
 782          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
 783          $this->assertTrue($co->prepare());
 784          $co->proceed();
 785          $this->assertEquals('newshortname', $DB->get_field_select('course', 'shortname', 'id = :id', array('id' => $c1->id)));
 786  
 787          // Can rename when updating.
 788          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
 789          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 790          $importoptions = array('canrename' => true);
 791          $data = array('shortname' => 'newshortname', 'rename' => 'newshortname2');
 792          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
 793          $this->assertTrue($co->prepare());
 794          $co->proceed();
 795          $this->assertEquals('newshortname2', $DB->get_field_select('course', 'shortname', 'id = :id', array('id' => $c1->id)));
 796  
 797          // Error when course does not exist.
 798          $mode = tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE;
 799          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 800          $importoptions = array('canrename' => true);
 801          $data = array('shortname' => 'DoesNotExist', 'rename' => 'c1', 'category' => 1, 'summary' => 'S', 'fullname' => 'F');
 802          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
 803          $this->assertFalse($co->prepare());
 804          $this->assertArrayHasKey('cannotrenamecoursenotexist', $co->get_errors());
 805  
 806          // Renaming still updates the other values.
 807          $mode = tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE;
 808          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_OR_DEFAUTLS;
 809          $importoptions = array('canrename' => true);
 810          $data = array('shortname' => 'newshortname2', 'rename' => 'c1', 'fullname' => 'Another fullname!');
 811          $defaultdata = array('summary' => 'New summary!');
 812          $co = new tool_uploadcourse_course($mode, $updatemode, $data, $defaultdata, $importoptions);
 813          $this->assertTrue($co->prepare());
 814          $co->proceed();
 815          $this->assertEquals('c1', $DB->get_field_select('course', 'shortname', 'id = :id', array('id' => $c1->id)));
 816          $this->assertEquals('New summary!', $DB->get_field_select('course', 'summary', 'id = :id', array('id' => $c1->id)));
 817          $this->assertEquals('Another fullname!', $DB->get_field_select('course', 'fullname', 'id = :id', array('id' => $c1->id)));
 818  
 819          // Renaming with invalid shortname.
 820          $mode = tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE;
 821          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 822          $importoptions = array('canrename' => true);
 823          $data = array('shortname' => 'c1', 'rename' => '<span>invalid</span>');
 824          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
 825          $this->assertFalse($co->prepare());
 826          $this->assertArrayHasKey('invalidshortname', $co->get_errors());
 827  
 828          // Renaming with invalid shortname.
 829          $mode = tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE;
 830          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 831          $importoptions = array('canrename' => true);
 832          $data = array('shortname' => 'c1', 'rename' => 'c2');
 833          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
 834          $this->assertFalse($co->prepare());
 835          $this->assertArrayHasKey('cannotrenameshortnamealreadyinuse', $co->get_errors());
 836      }
 837  
 838      public function test_restore_course() {
 839          global $DB;
 840          $this->resetAfterTest(true);
 841          $this->setAdminUser();
 842  
 843          $c1 = $this->getDataGenerator()->create_course();
 844          $c1f1 = $this->getDataGenerator()->create_module('forum', array('course' => $c1->id));
 845  
 846          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 847          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 848          $data = array('shortname' => 'A1', 'templatecourse' => $c1->shortname, 'summary' => 'A', 'category' => 1,
 849              'fullname' => 'A1');
 850          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 851          $this->assertTrue($co->prepare());
 852          $co->proceed();
 853          $course = $DB->get_record('course', array('shortname' => 'A1'));
 854          $modinfo = get_fast_modinfo($course);
 855          $found = false;
 856          foreach ($modinfo->get_cms() as $cmid => $cm) {
 857              if ($cm->modname == 'forum' && $cm->name == $c1f1->name) {
 858                  $found = true;
 859                  break;
 860              }
 861          }
 862          $this->assertTrue($found);
 863  
 864          // Restoring twice from the same course should work.
 865          $data = array('shortname' => 'B1', 'templatecourse' => $c1->shortname, 'summary' => 'B', 'category' => 1,
 866              'fullname' => 'B1');
 867          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 868          $this->assertTrue($co->prepare());
 869          $co->proceed();
 870          $course = $DB->get_record('course', array('shortname' => 'B1'));
 871          $modinfo = get_fast_modinfo($course);
 872          $found = false;
 873          foreach ($modinfo->get_cms() as $cmid => $cm) {
 874              if ($cm->modname == 'forum' && $cm->name == $c1f1->name) {
 875                  $found = true;
 876                  break;
 877              }
 878          }
 879          $this->assertTrue($found);
 880      }
 881  
 882      public function test_restore_file() {
 883          global $DB;
 884          $this->resetAfterTest(true);
 885          $this->setAdminUser();
 886  
 887          $c1 = $this->getDataGenerator()->create_course();
 888          $c1f1 = $this->getDataGenerator()->create_module('forum', array('course' => $c1->id));
 889  
 890          // Restore from a file, checking that the file takes priority over the templatecourse.
 891          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 892          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 893          $data = array('shortname' => 'A1', 'backupfile' => __DIR__ . '/fixtures/backup.mbz',
 894              'summary' => 'A', 'category' => 1, 'fullname' => 'A1', 'templatecourse' => $c1->shortname);
 895          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 896          $this->assertTrue($co->prepare());
 897          $co->proceed();
 898          $course = $DB->get_record('course', array('shortname' => 'A1'));
 899          $modinfo = get_fast_modinfo($course);
 900          $found = false;
 901          foreach ($modinfo->get_cms() as $cmid => $cm) {
 902              if ($cm->modname == 'glossary' && $cm->name == 'Imported Glossary') {
 903                  $found = true;
 904              } else if ($cm->modname == 'forum' && $cm->name == $c1f1->name) {
 905                  // We should not find this!
 906                  $this->assertTrue(false);
 907              }
 908          }
 909          $this->assertTrue($found);
 910  
 911          // Restoring twice from the same file should work.
 912          $data = array('shortname' => 'B1', 'backupfile' => __DIR__ . '/fixtures/backup.mbz',
 913              'summary' => 'B', 'category' => 1, 'fullname' => 'B1');
 914          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 915          $this->assertTrue($co->prepare());
 916          $co->proceed();
 917          $course = $DB->get_record('course', array('shortname' => 'B1'));
 918          $modinfo = get_fast_modinfo($course);
 919          $found = false;
 920          foreach ($modinfo->get_cms() as $cmid => $cm) {
 921              if ($cm->modname == 'glossary' && $cm->name == 'Imported Glossary') {
 922                  $found = true;
 923              } else if ($cm->modname == 'forum' && $cm->name == $c1f1->name) {
 924                  // We should not find this!
 925                  $this->assertTrue(false);
 926              }
 927          }
 928          $this->assertTrue($found);
 929      }
 930  
 931      /**
 932       * Test that specifying course template respects default restore settings
 933       */
 934      public function test_restore_file_settings() {
 935          global $DB;
 936          $this->resetAfterTest(true);
 937          $this->setAdminUser();
 938  
 939          // Set admin config setting so that activities are not restored by default.
 940          set_config('restore_general_activities', 0, 'restore');
 941  
 942          $c1 = $this->getDataGenerator()->create_course();
 943  
 944          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 945          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 946          $data = array('shortname' => 'A1', 'backupfile' => __DIR__ . '/fixtures/backup.mbz',
 947              'summary' => 'A', 'category' => 1, 'fullname' => 'A1', 'templatecourse' => $c1->shortname);
 948          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 949          $this->assertTrue($co->prepare());
 950          $co->proceed();
 951          $course = $DB->get_record('course', array('shortname' => 'A1'));
 952  
 953          // Make sure the glossary is not restored.
 954          $modinfo = get_fast_modinfo($course);
 955          $this->assertEmpty($modinfo->get_instances_of('glossary'));
 956      }
 957  
 958      public function test_restore_invalid_file() {
 959          $this->resetAfterTest();
 960  
 961          // Restore from a non-existing file should not be allowed.
 962          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 963          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 964          $data = array('shortname' => 'A1', 'backupfile' => '/lead/no/where',
 965              'category' => 1, 'fullname' => 'A1');
 966          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 967          $this->assertFalse($co->prepare());
 968          $this->assertArrayHasKey('cannotreadbackupfile', $co->get_errors());
 969  
 970          // Restore from an invalid file should not be allowed.
 971          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 972          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 973          $data = array('shortname' => 'A1', 'backupfile' => __FILE__,
 974              'category' => 1, 'fullname' => 'A1');
 975          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 976  
 977          $this->assertFalse($co->prepare());
 978          $this->assertArrayHasKey('invalidbackupfile', $co->get_errors());
 979  
 980          // Zip packer throws a debugging message, this assertion is only here to prevent
 981          // the message from being displayed.
 982          $this->assertDebuggingCalled();
 983      }
 984  
 985      public function test_restore_invalid_course() {
 986          $this->resetAfterTest();
 987  
 988          // Restore from an invalid file should not be allowed.
 989          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
 990          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
 991          $data = array('shortname' => 'A1', 'templatecourse' => 'iamnotavalidcourse',
 992              'category' => 1, 'fullname' => 'A1');
 993          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
 994          $this->assertFalse($co->prepare());
 995          $this->assertArrayHasKey('coursetorestorefromdoesnotexist', $co->get_errors());
 996      }
 997  
 998      /**
 999       * Testing the reset on groups, group members and enrolments.
1000       */
1001      public function test_reset() {
1002          global $DB;
1003          $this->resetAfterTest(true);
1004  
1005          $c1 = $this->getDataGenerator()->create_course();
1006          $c1ctx = \context_course::instance($c1->id);
1007          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1008          $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1009  
1010          $u1 = $this->getDataGenerator()->create_user();
1011          $this->getDataGenerator()->enrol_user($u1->id, $c1->id, $studentrole->id);
1012          $this->assertCount(1, get_enrolled_users($c1ctx));
1013  
1014          $g1 = $this->getDataGenerator()->create_group(array('courseid' => $c1->id));
1015          $this->getDataGenerator()->create_group_member(array('groupid' => $g1->id, 'userid' => $u1->id));
1016          $this->assertEquals(1, $DB->count_records('groups', array('courseid' => $c1->id)));
1017          $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $g1->id, 'userid' => $u1->id)));
1018  
1019          // Wrong mode.
1020          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
1021          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1022          $data = array('shortname' => 'DoesNotExist', 'reset' => '1', 'summary' => 'summary', 'fullname' => 'FN', 'category' => 1);
1023          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
1024          $this->assertFalse($co->prepare());
1025          $this->assertArrayHasKey('canonlyresetcourseinupdatemode', $co->get_errors());
1026          $this->assertTrue($DB->record_exists('groups', array('id' => $g1->id)));
1027          $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $g1->id, 'userid' => $u1->id)));
1028          $this->assertCount(1, get_enrolled_users($c1ctx));
1029  
1030          // Reset not allowed.
1031          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
1032          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1033          $data = array('shortname' => $c1->shortname, 'reset' => '1');
1034          $importoptions = array('canreset' => false);
1035          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
1036          $this->assertFalse($co->prepare());
1037          $this->assertArrayHasKey('courseresetnotallowed', $co->get_errors());
1038          $this->assertTrue($DB->record_exists('groups', array('id' => $g1->id)));
1039          $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $g1->id, 'userid' => $u1->id)));
1040          $this->assertCount(1, get_enrolled_users($c1ctx));
1041  
1042          // Reset allowed but not requested.
1043          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
1044          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1045          $data = array('shortname' => $c1->shortname, 'reset' => '0');
1046          $importoptions = array('canreset' => true);
1047          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
1048          $this->assertTrue($co->prepare());
1049          $co->proceed();
1050          $this->assertTrue($DB->record_exists('groups', array('id' => $g1->id)));
1051          $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $g1->id, 'userid' => $u1->id)));
1052          $this->assertCount(1, get_enrolled_users($c1ctx));
1053  
1054          // Reset passed as a default parameter, should not be taken in account.
1055          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
1056          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1057          $data = array('shortname' => $c1->shortname);
1058          $importoptions = array('canreset' => true);
1059          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array('reset' => 1), $importoptions);
1060          $this->assertTrue($co->prepare());
1061          $co->proceed();
1062          $this->assertTrue($DB->record_exists('groups', array('id' => $g1->id)));
1063          $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $g1->id, 'userid' => $u1->id)));
1064          $this->assertCount(1, get_enrolled_users($c1ctx));
1065  
1066          // Reset executed from data.
1067          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
1068          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1069          $data = array('shortname' => $c1->shortname, 'reset' => 1);
1070          $importoptions = array('canreset' => true);
1071          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
1072          $this->assertTrue($co->prepare());
1073          $co->proceed();
1074          $this->assertFalse($DB->record_exists('groups', array('id' => $g1->id)));
1075          $this->assertFalse($DB->record_exists('groups_members', array('groupid' => $g1->id, 'userid' => $u1->id)));
1076          $this->assertCount(0, get_enrolled_users($c1ctx));
1077  
1078          // Reset executed from import option.
1079          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
1080          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1081          $data = array('shortname' => $c1->shortname, 'reset' => 0);
1082          $importoptions = array('reset' => 1, 'canreset' => true);
1083          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
1084  
1085          $g1 = $this->getDataGenerator()->create_group(array('courseid' => $c1->id));
1086          $this->getDataGenerator()->create_group_member(array('groupid' => $g1->id, 'userid' => $u1->id));
1087          $this->assertEquals(1, $DB->count_records('groups', array('courseid' => $c1->id)));
1088          $this->assertTrue($co->prepare());
1089          $co->proceed();
1090          $this->assertFalse($DB->record_exists('groups', array('id' => $g1->id)));
1091      }
1092  
1093      public function test_create_bad_category() {
1094          global $DB;
1095          $this->resetAfterTest(true);
1096  
1097          // Ensure fails when category cannot be resolved upon creation.
1098          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
1099          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1100          $data = array('shortname' => 'c1', 'summary' => 'summary', 'fullname' => 'FN', 'category' => 'Wrong cat');
1101          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
1102          $this->assertFalse($co->prepare());
1103          $this->assertArrayHasKey('couldnotresolvecatgorybyid', $co->get_errors());
1104  
1105          // Ensure fails when category is 0 on create.
1106          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
1107          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1108          $data = array('shortname' => 'c1', 'summary' => 'summary', 'fullname' => 'FN', 'category' => '0');
1109          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
1110          $this->assertFalse($co->prepare());
1111          $this->assertArrayHasKey('missingmandatoryfields', $co->get_errors());
1112  
1113          // Ensure fails when category cannot be resolved upon update.
1114          $c1 = $this->getDataGenerator()->create_course();
1115          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
1116          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1117          $data = array('shortname' => $c1->shortname, 'category' => 'Wrong cat');
1118          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
1119          $this->assertFalse($co->prepare());
1120          $this->assertArrayHasKey('couldnotresolvecatgorybyid', $co->get_errors());
1121  
1122          // Ensure does not update the category when it is 0.
1123          $c1 = $this->getDataGenerator()->create_course();
1124          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
1125          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1126          $data = array('shortname' => $c1->shortname, 'category' => '0');
1127          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
1128          $this->assertTrue($co->prepare());
1129          $this->assertEmpty($co->get_errors());
1130          $this->assertEmpty($co->get_statuses());
1131          $co->proceed();
1132          $this->assertEquals($c1->category, $DB->get_field('course', 'category', array('id' => $c1->id)));
1133  
1134          // Ensure does not update the category when it is set to 0 in the defaults.
1135          $c1 = $this->getDataGenerator()->create_course();
1136          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
1137          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_OR_DEFAUTLS;
1138          $data = array('shortname' => $c1->shortname);
1139          $defaults = array('category' => '0');
1140          $co = new tool_uploadcourse_course($mode, $updatemode, $data, $defaults);
1141          $this->assertTrue($co->prepare());
1142          $this->assertEmpty($co->get_errors());
1143          $this->assertEmpty($co->get_statuses());
1144          $co->proceed();
1145          $this->assertEquals($c1->category, $DB->get_field('course', 'category', array('id' => $c1->id)));
1146      }
1147  
1148      public function test_enrolment_data() {
1149          $this->resetAfterTest(true);
1150  
1151          // We need to set the current user as one with the capability to edit manual enrolment instances in the new course.
1152          $this->setAdminUser();
1153  
1154          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
1155          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1156          $data = array('shortname' => 'c1', 'summary' => 'S', 'fullname' => 'FN', 'category' => '1');
1157          $data['enrolment_1'] = 'manual';
1158          $data['enrolment_1_role'] = 'teacher';
1159          $data['enrolment_1_startdate'] = '2nd July 2013';
1160          $data['enrolment_1_enddate'] = '2nd August 2013';
1161          $data['enrolment_1_enrolperiod'] = '10 days';
1162          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
1163          $this->assertTrue($co->prepare());
1164          $co->proceed();
1165  
1166          // Enrolment methods.
1167          $enroldata = array();
1168          $instances = enrol_get_instances($co->get_id(), false);
1169          foreach ($instances as $instance) {
1170              $enroldata[$instance->enrol] = $instance;
1171          }
1172  
1173          $this->assertNotEmpty($enroldata['manual']);
1174          $this->assertEquals(ENROL_INSTANCE_ENABLED, $enroldata['manual']->status);
1175          $this->assertEquals(strtotime($data['enrolment_1_startdate']), $enroldata['manual']->enrolstartdate);
1176          $this->assertEquals(strtotime('1970-01-01 GMT + ' . $data['enrolment_1_enrolperiod']), $enroldata['manual']->enrolperiod);
1177          $this->assertEquals(strtotime('12th July 2013'), $enroldata['manual']->enrolenddate);
1178      }
1179  
1180      /**
1181       * Data provider for testing enrolment errors
1182       *
1183       * @return array
1184       */
1185      public function enrolment_uploaddata_error_provider(): array {
1186          return [
1187              ['errorcannotcreateorupdateenrolment', [
1188                  'shortname' => 'C1',
1189                  'enrolment_1' => 'manual',
1190              ]],
1191              ['errorcannotdeleteenrolment', [
1192                  'shortname' => 'C1',
1193                  'enrolment_1' => 'manual',
1194                  'enrolment_1_delete' => '1',
1195              ]],
1196              ['errorcannotdisableenrolment', [
1197                  'shortname' => 'C1',
1198                  'enrolment_1' => 'manual',
1199                  'enrolment_1_disable' => '1',
1200              ]],
1201          ];
1202      }
1203  
1204      /**
1205       * Test that user without permission, cannot modify enrolment instances when creating courses
1206       *
1207       * @param string $expectederror
1208       * @param array $uploaddata
1209       *
1210       * @dataProvider enrolment_uploaddata_error_provider
1211       */
1212      public function test_enrolment_error_create_course(string $expectederror, array $uploaddata): void {
1213          global $DB;
1214  
1215          $this->resetAfterTest();
1216  
1217          // Create category in which to create the new course.
1218          $category = $this->getDataGenerator()->create_category();
1219          $categorycontext = \context_coursecat::instance($category->id);
1220  
1221          $user = $this->getDataGenerator()->create_user();
1222          $this->setUser($user);
1223  
1224          // Assign the user as a manager of the category, disable ability to configure manual enrolment instances.
1225          $roleid = $DB->get_field('role', 'id', ['shortname' => 'manager']);
1226          role_assign($roleid, $user->id, $categorycontext);
1227          role_change_permission($roleid, $categorycontext, 'enrol/manual:config', CAP_PROHIBIT);
1228  
1229          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
1230          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1231  
1232          $upload = new tool_uploadcourse_course($mode, $updatemode, array_merge($uploaddata, [
1233              'category' => $category->id,
1234              'fullname' => 'My course',
1235          ]));
1236  
1237          // Enrolment validation isn't performed during 'prepare' for new courses.
1238          $this->assertTrue($upload->prepare());
1239          $upload->proceed();
1240  
1241          $errors = $upload->get_errors();
1242          $this->assertArrayHasKey($expectederror, $errors);
1243  
1244          $this->assertEquals(get_string($expectederror, 'tool_uploadcourse', 'Manual enrolments'),
1245              (string) $errors[$expectederror]);
1246      }
1247  
1248      /**
1249       * Test that user without permission, cannot modify enrolment instances when updating courses
1250       *
1251       * @param string $expectederror
1252       * @param array $uploaddata
1253       *
1254       * @dataProvider enrolment_uploaddata_error_provider
1255       */
1256      public function test_enrolment_error_update_course(string $expectederror, array $uploaddata): void {
1257          global $DB;
1258  
1259          $this->resetAfterTest();
1260  
1261          // Create category in which to create the new course.
1262          $category = $this->getDataGenerator()->create_category();
1263          $categorycontext = \context_coursecat::instance($category->id);
1264  
1265          $course = $this->getDataGenerator()->create_course([
1266              'category' => $category->id,
1267              'shortname' => $uploaddata['shortname'],
1268          ]);
1269  
1270          $user = $this->getDataGenerator()->create_user();
1271          $this->setUser($user);
1272  
1273          // Assign the user as a manager of the category, disable ability to configure manual enrolment instances.
1274          $roleid = $DB->get_field('role', 'id', ['shortname' => 'manager']);
1275          role_assign($roleid, $user->id, $categorycontext);
1276          role_change_permission($roleid, $categorycontext, 'enrol/manual:config', CAP_PROHIBIT);
1277  
1278          // Sanity check.
1279          $instances = enrol_get_instances($course->id, true);
1280          $this->assertCount(1, $instances);
1281          $this->assertEquals('manual', reset($instances)->enrol);
1282  
1283          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
1284          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1285  
1286          $upload = new tool_uploadcourse_course($mode, $updatemode, $uploaddata);
1287  
1288          $this->assertFalse($upload->prepare());
1289  
1290          $errors = $upload->get_errors();
1291          $this->assertArrayHasKey($expectederror, $errors);
1292  
1293          $this->assertEquals(get_string($expectederror, 'tool_uploadcourse', 'Manual enrolments'),
1294              (string) $errors[$expectederror]);
1295      }
1296  
1297      /**
1298       * Test upload processing of course custom fields
1299       */
1300      public function test_custom_fields_data() {
1301          $this->resetAfterTest();
1302          $this->setAdminUser();
1303  
1304          $course = $this->getDataGenerator()->create_course(['shortname' => 'C1']);
1305  
1306          // Create our custom fields.
1307          $category = $this->get_customfield_generator()->create_category();
1308          $this->create_custom_field($category, 'date', 'mydatefield');
1309          $this->create_custom_field($category, 'text', 'mytextfield');
1310          $this->create_custom_field($category, 'textarea', 'mytextareafield');
1311  
1312          // Perform upload.
1313          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
1314          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1315          $dataupload = [
1316              'shortname' => $course->shortname,
1317              'customfield_mydatefield' => '2020-04-01 16:00',
1318              'customfield_mytextfield' => 'Hello',
1319              'customfield_mytextareafield' => 'Is it me you\'re looking for?',
1320          ];
1321  
1322          $uploader = new tool_uploadcourse_course($mode, $updatemode, $dataupload);
1323          $this->assertTrue($uploader->prepare());
1324          $uploader->proceed();
1325  
1326          // Confirm presence of course custom fields.
1327          $data = \core_course\customfield\course_handler::create()->export_instance_data_object($course->id);
1328          $this->assertEqualsIgnoringCase('Wednesday, 1 April 2020, 4:00 PM', $data->mydatefield);
1329          $this->assertEquals($dataupload['customfield_mytextfield'], $data->mytextfield);
1330          $this->assertStringContainsString($dataupload['customfield_mytextareafield'], $data->mytextareafield);
1331      }
1332  
1333      /**
1334       * Test upload processing of course custom field that is required but empty
1335       */
1336      public function test_custom_fields_data_required() {
1337          $this->resetAfterTest();
1338          $this->setAdminUser();
1339  
1340          $course = $this->getDataGenerator()->create_course(['shortname' => 'C1']);
1341  
1342          // Create our custom field.
1343          $category = $this->get_customfield_generator()->create_category();
1344          $this->create_custom_field($category, 'select', 'myselect', ['required' => true, 'options' => "Cat\nDog"]);
1345  
1346          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
1347          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1348          $dataupload = [
1349              'shortname' => $course->shortname,
1350              'customfield_myselect' => null,
1351          ];
1352  
1353          $uploader = new tool_uploadcourse_course($mode, $updatemode, $dataupload);
1354          $this->assertFalse($uploader->prepare());
1355          $this->assertArrayHasKey('customfieldinvalid', $uploader->get_errors());
1356  
1357          // Try again with a default value.
1358          $defaults = [
1359              'customfield_myselect' => 2, // Our second option: Dog.
1360          ];
1361  
1362          $uploader = new tool_uploadcourse_course($mode, $updatemode, $dataupload, $defaults);
1363          $this->assertTrue($uploader->prepare());
1364          $uploader->proceed();
1365  
1366          // Confirm presence of course custom fields.
1367          $data = \core_course\customfield\course_handler::create()->export_instance_data_object($course->id);
1368          $this->assertEquals('Dog', $data->myselect);
1369      }
1370  
1371      /**
1372       * Test upload processing of course custom field with an invalid select option
1373       */
1374      public function test_custom_fields_data_invalid_select_option() {
1375          $this->resetAfterTest();
1376          $this->setAdminUser();
1377  
1378          $course = $this->getDataGenerator()->create_course(['shortname' => 'C1']);
1379  
1380          // Create our custom field.
1381          $category = $this->get_customfield_generator()->create_category();
1382          $this->create_custom_field($category, 'select', 'myselect',
1383              ['required' => true, 'options' => "Cat\nDog", 'defaultvalue' => 'Cat']);
1384  
1385          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
1386          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1387          $dataupload = [
1388              'shortname' => $course->shortname,
1389              'customfield_myselect' => 'Fish', // No, invalid.
1390          ];
1391  
1392          $uploader = new tool_uploadcourse_course($mode, $updatemode, $dataupload);
1393          $this->assertTrue($uploader->prepare());
1394          $uploader->proceed();
1395  
1396          // Confirm presence of course custom fields.
1397          $data = \core_course\customfield\course_handler::create()->export_instance_data_object($course->id);
1398          $this->assertEquals('Cat', $data->myselect);
1399      }
1400  
1401      /**
1402       * Test upload processing of course custom field with an out of range date
1403       */
1404      public function test_custom_fields_data_invalid_date() {
1405          $this->resetAfterTest();
1406          $this->setAdminUser();
1407  
1408          $course = $this->getDataGenerator()->create_course(['shortname' => 'C1']);
1409  
1410          // Create our custom field.
1411          $category = $this->get_customfield_generator()->create_category();
1412          $this->create_custom_field($category, 'date', 'mydate',
1413              ['mindate' => strtotime('2020-04-01'), 'maxdate' => '2020-04-30']);
1414  
1415          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
1416          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1417          $dataupload = [
1418              'shortname' => $course->shortname,
1419              'customfield_mydate' => '2020-05-06', // Out of range.
1420          ];
1421  
1422          $uploader = new tool_uploadcourse_course($mode, $updatemode, $dataupload);
1423          $this->assertFalse($uploader->prepare());
1424          $this->assertArrayHasKey('customfieldinvalid', $uploader->get_errors());
1425      }
1426  
1427      public function test_idnumber_problems() {
1428          $this->resetAfterTest(true);
1429  
1430          $c1 = $this->getDataGenerator()->create_course(array('shortname' => 'sntaken', 'idnumber' => 'taken'));
1431          $c2 = $this->getDataGenerator()->create_course();
1432  
1433          // Create with existing ID number.
1434          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
1435          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1436          $data = array('shortname' => 'c2', 'summary' => 'summary', 'fullname' => 'FN', 'category' => '1',
1437              'idnumber' => $c1->idnumber);
1438          $co = new tool_uploadcourse_course($mode, $updatemode, $data);
1439          $this->assertFalse($co->prepare());
1440          $this->assertArrayHasKey('idnumberalreadyinuse', $co->get_errors());
1441  
1442          // Rename to existing ID number.
1443          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
1444          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1445          $data = array('shortname' => $c2->shortname, 'rename' => 'SN', 'idnumber' => $c1->idnumber);
1446          $importoptions = array('canrename' => true);
1447          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
1448          $this->assertFalse($co->prepare());
1449          $this->assertArrayHasKey('cannotrenameidnumberconflict', $co->get_errors());
1450  
1451          // Incrementing shortname increments idnumber.
1452          $mode = tool_uploadcourse_processor::MODE_CREATE_ALL;
1453          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
1454          $data = array('shortname' => $c1->shortname, 'idnumber' => $c1->idnumber, 'summary' => 'S', 'fullname' => 'F',
1455              'category' => 1);
1456          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), array());
1457          $this->assertTrue($co->prepare());
1458          $this->assertArrayHasKey('courseshortnameincremented', $co->get_statuses());
1459          $this->assertArrayHasKey('courseidnumberincremented', $co->get_statuses());
1460          $data = $co->get_data();
1461          $this->assertEquals('sntaken_2', $data['shortname']);
1462          $this->assertEquals('taken_2', $data['idnumber']);
1463  
1464          // Incrementing shortname increments idnumber unless available.
1465          $mode = tool_uploadcourse_processor::MODE_CREATE_ALL;
1466          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
1467          $data = array('shortname' => $c1->shortname, 'idnumber' => 'nottaken', 'summary' => 'S', 'fullname' => 'F',
1468              'category' => 1);
1469          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), array());
1470          $this->assertTrue($co->prepare());
1471          $this->assertArrayHasKey('courseshortnameincremented', $co->get_statuses());
1472          $this->assertArrayNotHasKey('courseidnumberincremented', $co->get_statuses());
1473          $data = $co->get_data();
1474          $this->assertEquals('sntaken_2', $data['shortname']);
1475          $this->assertEquals('nottaken', $data['idnumber']);
1476      }
1477  
1478      public function test_generate_shortname() {
1479          $this->resetAfterTest(true);
1480  
1481          $c1 = $this->getDataGenerator()->create_course(array('shortname' => 'taken'));
1482  
1483          // Generate a shortname.
1484          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
1485          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
1486          $data = array('summary' => 'summary', 'fullname' => 'FN', 'category' => '1', 'idnumber' => 'IDN');
1487          $importoptions = array('shortnametemplate' => '%i');
1488          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
1489          $this->assertTrue($co->prepare());
1490          $this->assertArrayHasKey('courseshortnamegenerated', $co->get_statuses());
1491  
1492          // Generate a shortname without a template.
1493          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
1494          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
1495          $data = array('summary' => 'summary', 'fullname' => 'FN', 'category' => '1');
1496          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), array());
1497          $this->assertFalse($co->prepare());
1498          $this->assertArrayHasKey('missingshortnamenotemplate', $co->get_errors());
1499  
1500          // Generate a shortname in update mode.
1501          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
1502          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1503          $data = array('summary' => 'summary', 'fullname' => 'FN', 'category' => '1');
1504          $importoptions = array('shortnametemplate' => '%f');
1505          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
1506          $this->assertFalse($co->prepare());
1507          // Commented because we never get here as the course without shortname does not exist.
1508          // $this->assertArrayHasKey('cannotgenerateshortnameupdatemode', $co->get_errors());
1509  
1510          // Generate a shortname to a course that already exists.
1511          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
1512          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
1513          $data = array('summary' => 'summary', 'fullname' => 'taken', 'category' => '1');
1514          $importoptions = array('shortnametemplate' => '%f');
1515          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
1516          $this->assertFalse($co->prepare());
1517          $this->assertArrayHasKey('generatedshortnamealreadyinuse', $co->get_errors());
1518  
1519          // Generate a shortname to a course that already exists will be incremented.
1520          $mode = tool_uploadcourse_processor::MODE_CREATE_ALL;
1521          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
1522          $data = array('summary' => 'summary', 'fullname' => 'taken', 'category' => '1');
1523          $importoptions = array('shortnametemplate' => '%f');
1524          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
1525          $this->assertTrue($co->prepare());
1526          $this->assertArrayHasKey('courseshortnamegenerated', $co->get_statuses());
1527          $this->assertArrayHasKey('courseshortnameincremented', $co->get_statuses());
1528      }
1529  
1530      public function test_mess_with_frontpage() {
1531          global $SITE;
1532          $this->resetAfterTest(true);
1533  
1534          // Updating the front page.
1535          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
1536          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1537          $data = array('shortname' => $SITE->shortname, 'idnumber' => 'NewIDN');
1538          $importoptions = array();
1539          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
1540          $this->assertFalse($co->prepare());
1541          $this->assertArrayHasKey('cannotupdatefrontpage', $co->get_errors());
1542  
1543          // Updating the front page.
1544          $mode = tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE;
1545          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1546          $data = array('shortname' => $SITE->shortname, 'idnumber' => 'NewIDN');
1547          $importoptions = array();
1548          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
1549          $this->assertFalse($co->prepare());
1550          $this->assertArrayHasKey('cannotupdatefrontpage', $co->get_errors());
1551  
1552          // Generating a shortname should not be allowed in update mode, and so we cannot update the front page.
1553          $mode = tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE;
1554          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1555          $data = array('idnumber' => 'NewIDN', 'fullname' => 'FN', 'category' => 1);
1556          $importoptions = array('shortnametemplate' => $SITE->shortname);
1557          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
1558          $this->assertFalse($co->prepare());
1559          $this->assertArrayHasKey('cannotgenerateshortnameupdatemode', $co->get_errors());
1560  
1561          // Renaming to the front page should not be allowed.
1562          $c1 = $this->getDataGenerator()->create_course();
1563          $mode = tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE;
1564          $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
1565          $data = array('shortname' => $c1->shortname, 'fullname' => 'FN', 'idnumber' => 'NewIDN', 'rename' => $SITE->shortname);
1566          $importoptions = array('canrename' => true);
1567          $co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
1568          $this->assertFalse($co->prepare());
1569          $this->assertArrayHasKey('cannotrenameshortnamealreadyinuse', $co->get_errors());
1570      }
1571  
1572      /**
1573       * Test when role doesn't exist.
1574       *
1575       * @covers \tool_uploadcourse_course::prepare
1576       */
1577      public function test_role_not_exist() {
1578          $this->resetAfterTest();
1579          $this->setAdminUser();
1580  
1581          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
1582          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
1583  
1584          $upload = new tool_uploadcourse_course($mode, $updatemode, [
1585              'category' => 1,
1586              'fullname' => 'Testing',
1587              'shortname' => 'T101',
1588              'enrolment_1' => 'manual',
1589              'enrolment_1_role' => 'notexist'
1590          ]);
1591  
1592          $this->assertFalse($upload->prepare());
1593          $this->assertArrayHasKey('invalidroles', $upload->get_errors());
1594      }
1595  
1596      /**
1597       * Test when role not allowed in course context.
1598       *
1599       * @covers \tool_uploadcourse_course::proceed
1600       */
1601      public function test_role_not_allowed() {
1602          $this->resetAfterTest();
1603          $this->setAdminUser();
1604  
1605          $roleid = create_role('New student role', 'student2', 'New student description', 'student');
1606          set_role_contextlevels($roleid, [CONTEXT_BLOCK]);
1607  
1608          $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
1609          $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
1610  
1611          $upload = new tool_uploadcourse_course($mode, $updatemode, [
1612              'category' => 1,
1613              'fullname' => 'Testing',
1614              'shortname' => 'T101',
1615              'enrolment_1' => 'manual',
1616              'enrolment_1_role' => 'student2'
1617          ]);
1618  
1619          $this->assertFalse($upload->prepare());
1620          $this->assertArrayHasKey('contextrolenotallowed', $upload->get_errors());
1621      }
1622  
1623      /**
1624       * Test when role is allowed.
1625       *
1626       * @covers \tool_uploadcourse_course::proceed
1627       */
1628      public function test_role_allowed() {
1629          global $DB;
1630  
1631          $this->resetAfterTest();
1632          $this->setAdminUser();
1633  
1634          $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
1635          $updatemode = tool_uploadcourse_processor::UPDATE_MISSING_WITH_DATA_OR_DEFAUTLS;
1636  
1637          $course = $this->getDataGenerator()->create_course([
1638              'shortname' => 'c1',
1639          ]);
1640  
1641          $instances = enrol_get_instances($course->id, true);
1642          $studentrole = $DB->get_record('role', ['shortname' => 'student']);
1643          $teacherrole = $DB->get_record('role', ['shortname' => 'teacher']);
1644          $instance = reset($instances);
1645          $this->assertEquals($studentrole->id, $instance->roleid);
1646  
1647          $upload = new tool_uploadcourse_course($mode, $updatemode, [
1648              'shortname' => 'c1',
1649              'enrolment_1' => 'manual',
1650              'enrolment_1_role' => 'teacher'
1651          ]);
1652  
1653          $this->assertTrue($upload->prepare());
1654          $upload->proceed();
1655          $instances = enrol_get_instances($course->id, true);
1656          $instance = reset($instances);
1657          $this->assertEquals($teacherrole->id, $instance->roleid);
1658      }
1659  
1660      /**
1661       * Get custom field plugin generator
1662       *
1663       * @return core_customfield_generator
1664       */
1665      protected function get_customfield_generator() : \core_customfield_generator {
1666          return $this->getDataGenerator()->get_plugin_generator('core_customfield');
1667      }
1668  
1669      /**
1670       * Helper method to create custom course field
1671       *
1672       * @param \core_customfield\category_controller $category
1673       * @param string $type
1674       * @param string $shortname
1675       * @param array $configdata
1676       * @return \core_customfield\field_controller
1677       */
1678      protected function create_custom_field(\core_customfield\category_controller $category, string $type, string $shortname,
1679              array $configdata = []) : \core_customfield\field_controller {
1680  
1681          return $this->get_customfield_generator()->create_field([
1682              'categoryid' => $category->get('id'),
1683              'type' => $type,
1684              'shortname' => $shortname,
1685              'configdata' => $configdata,
1686          ]);
1687      }
1688  }