Skip to content

Commit

Permalink
add class to properly work with moodles course category path
Browse files Browse the repository at this point in the history
  • Loading branch information
Glutamat42 committed Apr 2, 2024
1 parent eb0a714 commit 4d860ba
Show file tree
Hide file tree
Showing 2 changed files with 327 additions and 0 deletions.
115 changes: 115 additions & 0 deletions classes/local/course_category_path.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

namespace local_adler\local;

use core_course_category;
use Countable;
use invalid_parameter_exception;
use moodle_exception;

class course_category_path implements Countable {
private array $path;

/**
* @param string|null $path the path in moodle (with spaces around the /) or UNIX format (without spaces), can be empty string or null
*/
public function __construct(string|null $path) {
if ($path === null || strlen($path) === 0) {
$this->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);
}

}
212 changes: 212 additions & 0 deletions tests/local/course_category_path_test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
<?php

namespace local_adler\local;

global $CFG;

use invalid_parameter_exception;
use local_adler\lib\adler_testcase;
use Mockery;
use moodle_exception;

require_once($CFG->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());
}
}

0 comments on commit 4d860ba

Please sign in to comment.