From 4d860ba9bd0d24fbd572db99f5ea338a21b42b33 Mon Sep 17 00:00:00 2001 From: Markus Heck Date: Tue, 2 Apr 2024 14:27:33 +0200 Subject: [PATCH] add class to properly work with moodles course category path --- classes/local/course_category_path.php | 115 ++++++++++++ tests/local/course_category_path_test.php | 212 ++++++++++++++++++++++ 2 files changed, 327 insertions(+) create mode 100644 classes/local/course_category_path.php create mode 100644 tests/local/course_category_path_test.php diff --git a/classes/local/course_category_path.php b/classes/local/course_category_path.php new file mode 100644 index 0000000..52e8f55 --- /dev/null +++ b/classes/local/course_category_path.php @@ -0,0 +1,115 @@ +path = []; + } else { + $this->path = $this->split_and_trim_path($path); + } + } + + /** + * @return string the path in moodle format (with spaces around the /) + */ + public function __toString(): string { + return implode(' / ', $this->path); + } + + public function get_path(): array { + return $this->path; + } + + public function count(): int { + return count($this->path); + } + + public function exists(): bool { + try { + $this->get_category_id(); + return true; + } catch (moodle_exception $e) { + return false; + } + } + + /** + * @throws moodle_exception if the category already exists + * @throws invalid_parameter_exception if the path is empty + */ + public function create(): int { + if(count($this) === 0) { + throw new invalid_parameter_exception('path must not be empty'); + } + + if($this->exists()) { + throw new moodle_exception('category_already_exists', 'local_adler'); + } + + $current_category_id = 0; // top level category + $current_category_path = new course_category_path(''); // this will be used to check if the category already exists + + foreach ($this->get_path() as $category_path_part) { + // update current category path + $current_category_path->append_to_path($category_path_part); + + // check if category already exists + if ($current_category_path->exists()) { + $current_category_id = $current_category_path->get_category_id(); + } else { + $current_category_id = core_course_category::create([ + 'name' => (string)$category_path_part, + 'parent' => $current_category_id, + 'visible' => 1, + ])->id; + } + } + + return $current_category_id; + } + + + /** + * @throws moodle_exception if the category does not exist + */ + public function get_category_id(): int { + $categories = core_course_category::make_categories_list(); + $key = array_search((string)$this, $categories); + if ($key === false) { + throw new moodle_exception('category_not_found', 'local_adler'); + } + return $key; + } + + /** + * Append a path part to the end of the path. + * @throws invalid_parameter_exception if $path_part is empty + */ + public function append_to_path(string $path_part): void { + if (strlen($path_part) === 0) { + throw new invalid_parameter_exception('path_part must not be empty'); + } + $this->path = array_merge($this->path, $this->split_and_trim_path($path_part)); + } + + private function split_and_trim_path(string $path): array { + // remove preceding and trailing / + $path = trim($path, ' /'); + + $path_parts = explode('/', $path); + return array_map('trim', $path_parts); + } + +} diff --git a/tests/local/course_category_path_test.php b/tests/local/course_category_path_test.php new file mode 100644 index 0000000..798620f --- /dev/null +++ b/tests/local/course_category_path_test.php @@ -0,0 +1,212 @@ +dirroot . '/local/adler/tests/lib/adler_testcase.php'); + +class course_category_path_test extends adler_testcase { + public function test_constructor_with_null() { + $path = new course_category_path(null); + $this->assertEquals(0, count($path)); + } + + public function test_constructor_with_empty_string() { + $path = new course_category_path(''); + $this->assertEquals(0, count($path)); + } + + public function test_constructor_with_spaces() { + $path = new course_category_path('category1 / category2'); + $this->assertEquals(2, count($path)); + } + + public function test_constructor_without_spaces() { + $path = new course_category_path('category1/category2'); + $this->assertEquals(2, count($path)); + } + + public function test_constructor_with_single_element() { + $path = new course_category_path('category1'); + $this->assertEquals(1, count($path)); + } + + public function test_constructor_with_preceding_and_trailing_slashes() { + $path = new course_category_path('/category1/category2/'); + $this->assertEquals(2, count($path)); + } + + public function test_constructor_with_spaces_around_slashes() { + $path = new course_category_path(' category1 / category2 '); + $this->assertEquals(2, count($path)); + } + + public function test_to_string_method() { + $path = new course_category_path('category1/category2'); + $this->assertEquals('category1 / category2', $path->__toString()); + } + + public function test_get_path_method() { + $path = new course_category_path('category1/category2'); + $this->assertEquals(['category1', 'category2'], $path->get_path()); + } + + public function test_count_method() { + $path = new course_category_path('category1/category2'); + $this->assertEquals(2, $path->count()); + } + + private function setup_make_categories_list_mock() { + $mock = Mockery::mock('alias:core_course_category'); + $mock->shouldReceive('make_categories_list')->andReturn([ + 1 => 'category1 / category2', + 2 => 'category3 / category4', + ]); + } + + /** + * @runInSeparateProcess + */ + public function test_get_category_id_method_category_exists() { + $this->setup_make_categories_list_mock(); + + $path = new course_category_path('category1/category2'); + $this->assertEquals(1, $path->get_category_id()); + } + + /** + * @runInSeparateProcess + */ + public function test_get_category_id_method_category_does_not_exists() { + $this->setup_make_categories_list_mock(); + + $path = new course_category_path('category5/category6'); + $this->expectException(moodle_exception::class); + $path->get_category_id(); + } + + /** + * @runInSeparateProcess + */ + public function test_exists_method_category_exists() { + $this->setup_make_categories_list_mock(); + + $path = new course_category_path('category1/category2'); + $this->assertTrue($path->exists()); + } + + /** + * @runInSeparateProcess + */ + public function test_exists_method_category_does_not_exist() { + $this->setup_make_categories_list_mock(); + + $path = new course_category_path('category5/category6'); + $this->assertFalse($path->exists()); + } + + public function test_append_to_path_with_valid_path_part(): void { + $path = new course_category_path('test/path'); + $path->append_to_path('new_part'); + $this->assertEquals('test / path / new_part', (string)$path); + } + + public function test_append_to_path_with_empty_path_part(): void { + $this->expectException(invalid_parameter_exception::class); + $path = new course_category_path('test/path'); + $path->append_to_path(''); + } + + + public function test_append_to_path_with_spaces_around_path_part(): void { + $path = new course_category_path('test/path'); + $path->append_to_path(' new_part '); + $this->assertEquals('test / path / new_part', (string)$path); + } + + public function test_append_to_path_with_multiple_parts_in_path_part(): void { + $path = new course_category_path('test/path'); + $path->append_to_path('new_part1/new_part2'); + $this->assertEquals('test / path / new_part1 / new_part2', (string)$path); + $this->assertEquals(4, $path->count()); + } + + public function test_count_zero_exists_false(): void { + $path = new course_category_path(''); + $this->assertEquals(0, $path->count()); + $this->assertFalse($path->exists()); + } + + public function test_create_with_count_zero(): void { + $this->expectException(invalid_parameter_exception::class); + $path = new course_category_path(''); + $path->create(); + } + + public function test_create_with_empty_path(): void { + $this->expectException(invalid_parameter_exception::class); + $path = new course_category_path(''); + $path->create(); + } + + /** + * @runInSeparateProcess + */ + public function test_create_category_already_exists(): void { + $this->setup_make_categories_list_mock(); + + $path = new course_category_path('category1/category2'); + $this->expectException(moodle_exception::class); + $this->expectExceptionMessage('category_already_exists'); + $path->create(); + } + + + /** + * @runInSeparateProcess + */ + public function test_create_with_one_segment(): void { + $mock = Mockery::mock('alias:core_course_category'); + $mock->shouldReceive('make_categories_list')->andReturn([]); + $mock->shouldReceive('create')->andReturn((object) ['id' => 1]); + + $path = new course_category_path('segment1'); + $this->assertEquals(1, $path->count()); + $this->assertFalse($path->exists()); + $this->assertIsInt($path->create()); + } + + /** + * @runInSeparateProcess + */ + public function test_create_with_two_segments_first_exists(): void { + $mock = Mockery::mock('alias:core_course_category'); + $mock->shouldReceive('make_categories_list')->andReturn([42 => 'segment1']); + $mock->shouldReceive('create')->andReturn((object) ['id' => 1]); + + $path = new course_category_path('segment1/segment2'); + $this->assertEquals(2, $path->count()); + $this->assertFalse($path->exists()); + $this->assertIsInt($path->create()); + } + + /** + * @runInSeparateProcess + */ + public function test_create_with_two_segments_none_exists(): void { + $mock = Mockery::mock('alias:core_course_category'); + $mock->shouldReceive('make_categories_list')->andReturn([]); + $mock->shouldReceive('create')->andReturn((object) ['id' => 1]); + + $path = new course_category_path('segment1/segment2'); + $this->assertEquals(2, $path->count()); + $this->assertFalse($path->exists()); + $this->assertIsInt($path->create()); + } +} \ No newline at end of file