Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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  /**
  18   * File containing tests for the helper.
  19   *
  20   * @package    tool_uploadcourse
  21   * @copyright  2013 Frédéric Massart
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  global $CFG;
  28  
  29  /**
  30   * Helper test case.
  31   *
  32   * @package    tool_uploadcourse
  33   * @copyright  2013 Frédéric Massart
  34   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class tool_uploadcourse_helper_testcase extends advanced_testcase {
  37  
  38      public function test_generate_shortname() {
  39          $data = (object) array('fullname' => 'Ah bh Ch 01 02 03', 'idnumber' => 'ID123');
  40  
  41          $this->assertSame($data->fullname, tool_uploadcourse_helper::generate_shortname($data, '%f'));
  42          $this->assertSame($data->idnumber, tool_uploadcourse_helper::generate_shortname($data, '%i'));
  43          $this->assertSame('Ah Bh Ch', tool_uploadcourse_helper::generate_shortname($data, '%~8f'));
  44          $this->assertSame('AH BH CH', tool_uploadcourse_helper::generate_shortname($data, '%+8f'));
  45          $this->assertSame('id123', tool_uploadcourse_helper::generate_shortname($data, '%-i'));
  46          $this->assertSame('[Ah bh Ch] = ID123', tool_uploadcourse_helper::generate_shortname($data, '[%8f] = %i'));
  47          $this->assertSame('0', tool_uploadcourse_helper::generate_shortname($data, '0'));
  48          $this->assertSame('%unknown', tool_uploadcourse_helper::generate_shortname($data, '%unknown'));
  49  
  50          $this->assertNull(tool_uploadcourse_helper::generate_shortname($data, ''));
  51          $this->assertNull(tool_uploadcourse_helper::generate_shortname(array(), '%f'));
  52      }
  53  
  54      public function test_get_course_formats() {
  55          $result = tool_uploadcourse_helper::get_course_formats();
  56          $this->assertSame(array_keys(core_component::get_plugin_list('format')), $result);
  57          // Should be similar as first result, as cached.
  58          $this->assertSame($result, tool_uploadcourse_helper::get_course_formats());
  59      }
  60  
  61      public function test_get_enrolment_data() {
  62          $this->resetAfterTest(true);
  63          $data = array(
  64              'enrolment_1' => 'unknown',
  65              'enrolment_1_foo' => '1',
  66              'enrolment_1_bar' => '2',
  67              'enrolment_2' => 'self',
  68              'enrolment_2_delete' => '1',
  69              'enrolment_2_foo' => 'a',
  70              'enrolment_2_bar' => '1',
  71              'enrolment_3' => 'manual',
  72              'enrolment_3_disable' => '2',
  73              'enrolment_3_foo' => 'b',
  74              'enrolment_3_bar' => '2',
  75              'enrolment_4' => 'database',
  76              'enrolment_4_foo' => 'x',
  77              'enrolment_4_bar' => '3',
  78              'enrolment_5_test3' => 'test3',
  79              'enrolment_5_test2' => 'test2',
  80              'enrolment_5_test1' => 'test1',
  81              'enrolment_5' => 'flatfile',
  82          );
  83          $expected = array(
  84              'self' => array(
  85                  'delete' => '1',
  86                  'foo' => 'a',
  87                  'bar' => '1',
  88              ),
  89              'manual' => array(
  90                  'disable' => '2',
  91                  'foo' => 'b',
  92                  'bar' => '2',
  93              ),
  94              'database' => array(
  95                  'foo' => 'x',
  96                  'bar' => '3',
  97              ),
  98              'flatfile' => array(
  99                  'test3' => 'test3',
 100                  'test2' => 'test2',
 101                  'test1' => 'test1',
 102              )
 103          );
 104          $this->assertSame(tool_uploadcourse_helper::get_enrolment_data($data), $expected);
 105      }
 106  
 107      public function test_get_enrolment_plugins() {
 108          $this->resetAfterTest(true);
 109          $actual = tool_uploadcourse_helper::get_enrolment_plugins();
 110          $this->assertSame(array_keys(enrol_get_plugins(false)), array_keys($actual));
 111          // This should be identical as cached.
 112          $secondactual = tool_uploadcourse_helper::get_enrolment_plugins();
 113          $this->assertEquals($actual, $secondactual);
 114      }
 115  
 116      public function test_get_restore_content_dir() {
 117          global $CFG;
 118          $this->resetAfterTest(true);
 119          $this->setAdminUser();
 120  
 121          $c1 = $this->getDataGenerator()->create_course();
 122          $c2 = $this->getDataGenerator()->create_course((object) array('shortname' => 'Yay'));
 123  
 124          // Creating backup file.
 125          $bc = new backup_controller(backup::TYPE_1COURSE, $c1->id, backup::FORMAT_MOODLE,
 126              backup::INTERACTIVE_NO, backup::MODE_GENERAL, 2);
 127          $bc->execute_plan();
 128          $result = $bc->get_results();
 129          $this->assertTrue(isset($result['backup_destination']));
 130          $c1backupfile = $result['backup_destination']->copy_content_to_temp();
 131          $bc->destroy();
 132  
 133          // Creating backup file.
 134          $bc = new backup_controller(backup::TYPE_1COURSE, $c2->id, backup::FORMAT_MOODLE,
 135              backup::INTERACTIVE_NO, backup::MODE_GENERAL, 2);
 136          $bc->execute_plan();
 137          $result = $bc->get_results();
 138          $this->assertTrue(isset($result['backup_destination']));
 139          $c2backupfile = $result['backup_destination']->copy_content_to_temp();
 140          $bc->destroy();
 141  
 142          $oldcfg = isset($CFG->keeptempdirectoriesonbackup) ? $CFG->keeptempdirectoriesonbackup : false;
 143          $CFG->keeptempdirectoriesonbackup = true;
 144  
 145          // Checking restore dir.
 146          $dir = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null);
 147          $bcinfo = backup_general_helper::get_backup_information($dir);
 148          $this->assertEquals($bcinfo->original_course_id, $c1->id);
 149          $this->assertEquals($bcinfo->original_course_fullname, $c1->fullname);
 150  
 151          // Do it again, it should be the same directory.
 152          $dir2 = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null);
 153          $this->assertEquals($dir, $dir2);
 154  
 155          // Get the second course.
 156          $dir = tool_uploadcourse_helper::get_restore_content_dir($c2backupfile, null);
 157          $bcinfo = backup_general_helper::get_backup_information($dir);
 158          $this->assertEquals($bcinfo->original_course_id, $c2->id);
 159          $this->assertEquals($bcinfo->original_course_fullname, $c2->fullname);
 160  
 161          // Checking with a shortname.
 162          $dir = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname);
 163          $bcinfo = backup_general_helper::get_backup_information($dir);
 164          $this->assertEquals($bcinfo->original_course_id, $c1->id);
 165          $this->assertEquals($bcinfo->original_course_fullname, $c1->fullname);
 166  
 167          // Do it again, it should be the same directory.
 168          $dir2 = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname);
 169          $this->assertEquals($dir, $dir2);
 170  
 171          // Get the second course.
 172          $dir = tool_uploadcourse_helper::get_restore_content_dir(null, $c2->shortname);
 173          $bcinfo = backup_general_helper::get_backup_information($dir);
 174          $this->assertEquals($bcinfo->original_course_id, $c2->id);
 175          $this->assertEquals($bcinfo->original_course_fullname, $c2->fullname);
 176  
 177          // Get a course that does not exist.
 178          $errors = array();
 179          $dir = tool_uploadcourse_helper::get_restore_content_dir(null, 'DoesNotExist', $errors);
 180          $this->assertFalse($dir);
 181          $this->assertArrayHasKey('coursetorestorefromdoesnotexist', $errors);
 182  
 183          // Trying again without caching. $CFG->keeptempdirectoriesonbackup is required for caching.
 184          $CFG->keeptempdirectoriesonbackup = false;
 185  
 186          // Checking restore dir.
 187          $dir = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null);
 188          $dir2 = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null);
 189          $this->assertNotEquals($dir, $dir2);
 190  
 191          // Checking with a shortname.
 192          $dir = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname);
 193          $dir2 = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname);
 194          $this->assertNotEquals($dir, $dir2);
 195  
 196          // Get a course that does not exist.
 197          $errors = array();
 198          $dir = tool_uploadcourse_helper::get_restore_content_dir(null, 'DoesNotExist', $errors);
 199          $this->assertFalse($dir);
 200          $this->assertArrayHasKey('coursetorestorefromdoesnotexist', $errors);
 201          $dir2 = tool_uploadcourse_helper::get_restore_content_dir(null, 'DoesNotExist', $errors);
 202          $this->assertEquals($dir, $dir2);
 203  
 204          $CFG->keeptempdirectoriesonbackup = $oldcfg;
 205      }
 206  
 207      public function test_get_role_ids() {
 208          $this->getDataGenerator();
 209          // Mimic function result.
 210          $expected = array();
 211          $roles = get_all_roles();
 212          foreach ($roles as $role) {
 213              $expected[$role->shortname] = $role->id;
 214          }
 215  
 216          $actual = tool_uploadcourse_helper::get_role_ids();
 217          $this->assertSame($actual, $expected);
 218  
 219          // Check cache.
 220          $this->assertSame($actual, tool_uploadcourse_helper::get_role_ids());
 221      }
 222  
 223      public function test_get_role_names() {
 224          $this->resetAfterTest(true);
 225  
 226          create_role('Villain', 'villain', 'The bad guys');
 227          $data = array(
 228              'role_student' => 'Padawan',
 229              'role_teacher' => 'Guardian',
 230              'role_editingteacher' => 'Knight',
 231              'role_manager' => 'Master',
 232              'role_villain' => 'Jabba the Hutt',
 233              'role_android' => 'R2D2',
 234          );
 235  
 236          // Get the role IDs, but need to force the cache reset as a new role is defined.
 237          $roleids = tool_uploadcourse_helper::get_role_ids(true);
 238  
 239          $expected = array(
 240              'role_' . $roleids['student'] => 'Padawan',
 241              'role_' . $roleids['teacher'] => 'Guardian',
 242              'role_' . $roleids['editingteacher'] => 'Knight',
 243              'role_' . $roleids['manager'] => 'Master',
 244              'role_' . $roleids['villain'] => 'Jabba the Hutt',
 245          );
 246  
 247          $errors = array();
 248          $actual = tool_uploadcourse_helper::get_role_names($data, $errors);
 249          $this->assertSame($actual, $expected);
 250          $this->assertArrayHasKey('invalidroles', $errors);
 251      }
 252  
 253      /**
 254       * Test custom field data processing
 255       */
 256      public function test_get_custom_course_field_data() {
 257          global $DB;
 258  
 259          $this->resetAfterTest();
 260  
 261          // Create all the fields!
 262          $category = $this->get_customfield_generator()->create_category();
 263  
 264          $checkboxfield = $this->create_custom_field($category, 'checkbox', 'mycheckbox');
 265          $datefield = $this->create_custom_field($category, 'date', 'mydate');
 266          $selectfield = $this->create_custom_field($category, 'select', 'myselect', ['options' => "Red\nGreen\nBlue"]);
 267          $textfield = $this->create_custom_field($category, 'text', 'mytext', ['locked' => 1]);
 268          $textareafield = $this->create_custom_field($category, 'textarea', 'mytextarea');
 269  
 270          $fields = tool_uploadcourse_helper::get_custom_course_fields();
 271          $this->assertCount(5, $fields);
 272  
 273          $this->assertArrayHasKey($checkboxfield->get('shortname'), $fields);
 274          $this->assertInstanceOf(customfield_checkbox\field_controller::class, $fields[$checkboxfield->get('shortname')]);
 275  
 276          $this->assertArrayHasKey($datefield->get('shortname'), $fields);
 277          $this->assertInstanceOf(customfield_date\field_controller::class, $fields[$datefield->get('shortname')]);
 278  
 279          $this->assertArrayHasKey($selectfield->get('shortname'), $fields);
 280          $this->assertInstanceOf(customfield_select\field_controller::class, $fields[$selectfield->get('shortname')]);
 281  
 282          $this->assertArrayHasKey($textfield->get('shortname'), $fields);
 283          $this->assertInstanceOf(customfield_text\field_controller::class, $fields[$textfield->get('shortname')]);
 284  
 285          $this->assertArrayHasKey($textareafield->get('shortname'), $fields);
 286          $this->assertInstanceOf(customfield_textarea\field_controller::class, $fields[$textareafield->get('shortname')]);
 287  
 288          $data = [
 289              'customfield_mycheckbox' => '1',
 290              'customfield_mydate' => '2019-10-01',
 291              'customfield_myselect' => 'Green',
 292              'customfield_mytext' => 'Hello',
 293              'customfield_myunknownfield' => 'Goodbye',
 294          ];
 295  
 296          $expected = [
 297              'customfield_mycheckbox' => '1',
 298              'customfield_mydate' => strtotime('2019-10-01'),
 299              'customfield_myselect' => 2,
 300              'customfield_mytext' => 'Hello',
 301          ];
 302  
 303          $course = $this->getDataGenerator()->create_course();
 304          $user = $this->getDataGenerator()->create_and_enrol($course, 'manager');
 305          $this->setUser($user);
 306  
 307          $context = context_course::instance($course->id);
 308  
 309          $this->assertEquals($expected, tool_uploadcourse_helper::get_custom_course_field_data($data, [], $context));
 310  
 311          // Now add our custom textarea field (separately because the value of it's 'itemid' element is unknown).
 312          $data['customfield_mytextarea'] = 'Something';
 313          $fields = tool_uploadcourse_helper::get_custom_course_field_data($data, [], $context);
 314          $this->assertArrayHasKey('customfield_mytextarea_editor', $fields);
 315          $this->assertArrayHasKey('text', $fields['customfield_mytextarea_editor']);
 316          $this->assertEquals('Something', $fields['customfield_mytextarea_editor']['text']);
 317  
 318          // Now prohibit the capability to change locked fields for the manager role.
 319          $managerrole = $DB->get_record('role', ['shortname' => 'manager']);
 320          role_change_permission($managerrole->id, $context, 'moodle/course:changelockedcustomfields', CAP_PROHIBIT);
 321  
 322          // The locked 'mytext' custom field should not be returned.
 323          $fields = tool_uploadcourse_helper::get_custom_course_field_data($data, [], $context);
 324          $this->assertCount(4, $fields);
 325          $this->assertArrayNotHasKey('customfield_mytext', $fields);
 326      }
 327  
 328      public function test_increment_idnumber() {
 329          $this->resetAfterTest(true);
 330  
 331          $c1 = $this->getDataGenerator()->create_course(array('idnumber' => 'C1'));
 332          $c2 = $this->getDataGenerator()->create_course(array('idnumber' => 'C2'));
 333          $c3 = $this->getDataGenerator()->create_course(array('idnumber' => 'Yo'));
 334  
 335          $this->assertEquals('C3', tool_uploadcourse_helper::increment_idnumber('C1'));
 336          $this->assertEquals('Yo_2', tool_uploadcourse_helper::increment_idnumber('Yo'));
 337          $this->assertEquals('DoesNotExist', tool_uploadcourse_helper::increment_idnumber('DoesNotExist'));
 338      }
 339  
 340      public function test_increment_shortname() {
 341          $this->resetAfterTest(true);
 342  
 343          $c1 = $this->getDataGenerator()->create_course(array('shortname' => 'C1'));
 344          $c2 = $this->getDataGenerator()->create_course(array('shortname' => 'C2'));
 345          $c3 = $this->getDataGenerator()->create_course(array('shortname' => 'Yo'));
 346  
 347          // FYI: increment_shortname assumes that the course exists, and so increment the shortname immediately.
 348          $this->assertEquals('C3', tool_uploadcourse_helper::increment_shortname('C1'));
 349          $this->assertEquals('Yo_2', tool_uploadcourse_helper::increment_shortname('Yo'));
 350          $this->assertEquals('DoesNotExist_2', tool_uploadcourse_helper::increment_shortname('DoesNotExist'));
 351      }
 352  
 353      public function test_resolve_category() {
 354          $this->resetAfterTest(true);
 355  
 356          $c1 = $this->getDataGenerator()->create_category(array('name' => 'First level'));
 357          $c2 = $this->getDataGenerator()->create_category(array('name' => 'Second level', 'parent' => $c1->id));
 358          $c3 = $this->getDataGenerator()->create_category(array('idnumber' => 'C3'));
 359  
 360          $data = array(
 361              'category' => $c1->id,
 362              'category_path' => $c1->name . ' / ' . $c2->name,
 363              'category_idnumber' => $c3->idnumber,
 364          );
 365  
 366          $this->assertEquals($c1->id, tool_uploadcourse_helper::resolve_category($data));
 367          unset($data['category']);
 368          $this->assertEquals($c3->id, tool_uploadcourse_helper::resolve_category($data));
 369          unset($data['category_idnumber']);
 370          $this->assertEquals($c2->id, tool_uploadcourse_helper::resolve_category($data));
 371  
 372          // Adding unexisting data.
 373          $errors = array();
 374          $data['category_idnumber'] = 1234;
 375          $this->assertEquals($c2->id, tool_uploadcourse_helper::resolve_category($data, $errors));
 376          $this->assertArrayHasKey('couldnotresolvecatgorybyidnumber', $errors);
 377          $errors = array();
 378          $data['category'] = 1234;
 379          $this->assertEquals($c2->id, tool_uploadcourse_helper::resolve_category($data, $errors));
 380          $this->assertArrayHasKey('couldnotresolvecatgorybyid', $errors);
 381          $errors = array();
 382          $data['category_path'] = 'Not exist';
 383          $this->assertEmpty(tool_uploadcourse_helper::resolve_category($data, $errors));
 384          $this->assertArrayHasKey('couldnotresolvecatgorybypath', $errors);
 385      }
 386  
 387      public function test_resolve_category_by_idnumber() {
 388          $this->resetAfterTest(true);
 389  
 390          $c1 = $this->getDataGenerator()->create_category(array('idnumber' => 'C1'));
 391          $c2 = $this->getDataGenerator()->create_category(array('idnumber' => 'C2'));
 392  
 393          // Doubled for cache check.
 394          $this->assertEquals($c1->id, tool_uploadcourse_helper::resolve_category_by_idnumber('C1'));
 395          $this->assertEquals($c1->id, tool_uploadcourse_helper::resolve_category_by_idnumber('C1'));
 396          $this->assertEquals($c2->id, tool_uploadcourse_helper::resolve_category_by_idnumber('C2'));
 397          $this->assertEquals($c2->id, tool_uploadcourse_helper::resolve_category_by_idnumber('C2'));
 398          $this->assertEmpty(tool_uploadcourse_helper::resolve_category_by_idnumber('DoesNotExist'));
 399          $this->assertEmpty(tool_uploadcourse_helper::resolve_category_by_idnumber('DoesNotExist'));
 400      }
 401  
 402      public function test_resolve_category_by_path() {
 403          $this->resetAfterTest(true);
 404  
 405          $cat1 = $this->getDataGenerator()->create_category(array('name' => 'Cat 1'));
 406          $cat1_1 = $this->getDataGenerator()->create_category(array('name' => 'Cat 1.1', 'parent' => $cat1->id));
 407          $cat1_1_1 = $this->getDataGenerator()->create_category(array('name' => 'Cat 1.1.1', 'parent' => $cat1_1->id));
 408          $cat1_1_2 = $this->getDataGenerator()->create_category(array('name' => 'Cat 1.1.2', 'parent' => $cat1_1->id));
 409          $cat1_2 = $this->getDataGenerator()->create_category(array('name' => 'Cat 1.2', 'parent' => $cat1->id));
 410  
 411          $cat2 = $this->getDataGenerator()->create_category(array('name' => 'Cat 2'));
 412          $cat2_1 = $this->getDataGenerator()->create_category(array('name' => 'Cat 2.1', 'parent' => $cat2->id, 'visible' => false));
 413          $cat2_1_1 = $this->getDataGenerator()->create_category(array('name' => 'Cat 2.1.1', 'parent' => $cat2_1->id));
 414          $cat2_1_2 = $this->getDataGenerator()->create_category(array('name' => 'Cat 2.1.2', 'parent' => $cat2_1->id));
 415          $cat2_2 = $this->getDataGenerator()->create_category(array('name' => 'Cat 2.2', 'parent' => $cat2->id));
 416  
 417          $cat3 = $this->getDataGenerator()->create_category(array('name' => 'Cat 3'));
 418          $cat3_1 = $this->getDataGenerator()->create_category(array('name' => 'Cat 3.1 Doubled', 'parent' => $cat3->id));
 419          $cat3_1b = $this->getDataGenerator()->create_category(array('name' => 'Cat 3.1 Doubled', 'parent' => $cat3->id));
 420          $cat3_1_1 = $this->getDataGenerator()->create_category(array('name' => 'Cat 3.1.1', 'parent' => $cat3_1->id));
 421          $cat3_fakedouble = $this->getDataGenerator()->create_category(array('name' => 'Cat 3.1.1', 'parent' => $cat3->id));
 422  
 423          // Existing categories. Doubled for cache testing.
 424          $path = array('Cat 1');
 425          $this->assertEquals($cat1->id, tool_uploadcourse_helper::resolve_category_by_path($path));
 426          $this->assertEquals($cat1->id, tool_uploadcourse_helper::resolve_category_by_path($path));
 427  
 428          $path = array('Cat 1', 'Cat 1.1', 'Cat 1.1.2');
 429          $this->assertEquals($cat1_1_2->id, tool_uploadcourse_helper::resolve_category_by_path($path));
 430          $this->assertEquals($cat1_1_2->id, tool_uploadcourse_helper::resolve_category_by_path($path));
 431  
 432          $path = array('Cat 1', 'Cat 1.2');
 433          $this->assertEquals($cat1_2->id, tool_uploadcourse_helper::resolve_category_by_path($path));
 434          $this->assertEquals($cat1_2->id, tool_uploadcourse_helper::resolve_category_by_path($path));
 435  
 436          $path = array('Cat 2');
 437          $this->assertEquals($cat2->id, tool_uploadcourse_helper::resolve_category_by_path($path));
 438          $this->assertEquals($cat2->id, tool_uploadcourse_helper::resolve_category_by_path($path));
 439  
 440          // Hidden category.
 441          $path = array('Cat 2', 'Cat 2.1');
 442          $this->assertEquals($cat2_1->id, tool_uploadcourse_helper::resolve_category_by_path($path));
 443          $this->assertEquals($cat2_1->id, tool_uploadcourse_helper::resolve_category_by_path($path));
 444  
 445          // Hidden parent.
 446          $path = array('Cat 2', 'Cat 2.1', 'Cat 2.1.2');
 447          $this->assertEquals($cat2_1_2->id, tool_uploadcourse_helper::resolve_category_by_path($path));
 448          $this->assertEquals($cat2_1_2->id, tool_uploadcourse_helper::resolve_category_by_path($path));
 449  
 450          // Does not exist.
 451          $path = array('No cat 3', 'Cat 1.2');
 452          $this->assertEmpty(tool_uploadcourse_helper::resolve_category_by_path($path));
 453          $this->assertEmpty(tool_uploadcourse_helper::resolve_category_by_path($path));
 454  
 455          $path = array('Cat 2', 'Cat 2.x');
 456          $this->assertEmpty(tool_uploadcourse_helper::resolve_category_by_path($path));
 457          $this->assertEmpty(tool_uploadcourse_helper::resolve_category_by_path($path));
 458  
 459          // Name conflict.
 460          $path = array('Cat 3', 'Cat 3.1 Doubled');
 461          $this->assertEmpty(tool_uploadcourse_helper::resolve_category_by_path($path));
 462          $this->assertEmpty(tool_uploadcourse_helper::resolve_category_by_path($path));
 463  
 464          $path = array('Cat 3', 'Cat 3.1 Doubled', 'Cat 3.1.1');
 465          $this->assertEmpty(tool_uploadcourse_helper::resolve_category_by_path($path));
 466          $this->assertEmpty(tool_uploadcourse_helper::resolve_category_by_path($path));
 467  
 468          $path = array('Cat 3', 'Cat 3.1.1');
 469          $this->assertEquals($cat3_fakedouble->id, tool_uploadcourse_helper::resolve_category_by_path($path));
 470          $this->assertEquals($cat3_fakedouble->id, tool_uploadcourse_helper::resolve_category_by_path($path));
 471      }
 472  
 473      /**
 474       * Get custom field plugin generator
 475       *
 476       * @return core_customfield_generator
 477       */
 478      protected function get_customfield_generator() : core_customfield_generator {
 479          return $this->getDataGenerator()->get_plugin_generator('core_customfield');
 480      }
 481  
 482      /**
 483       * Helper method to create custom course field
 484       *
 485       * @param \core_customfield\category_controller $category
 486       * @param string $type
 487       * @param string $shortname
 488       * @param array $configdata
 489       * @return \core_customfield\field_controller
 490       */
 491      protected function create_custom_field(\core_customfield\category_controller $category, string $type, string $shortname,
 492              array $configdata = []) : \core_customfield\field_controller {
 493  
 494          return $this->get_customfield_generator()->create_field([
 495              'categoryid' => $category->get('id'),
 496              'type' => $type,
 497              'shortname' => $shortname,
 498              'configdata' => $configdata,
 499          ]);
 500      }
 501  }