diff --git a/classes/event/preset_created.php b/classes/event/preset_created.php new file mode 100644 index 0000000..f29dc1e --- /dev/null +++ b/classes/event/preset_created.php @@ -0,0 +1,82 @@ +. + +namespace tool_enrolprofile\event; + +use core\event\base; +use moodle_url; + +/** + * Event triggered when a preset created. + * + * @package tool_enrolprofile + * @copyright 2024 Dmitrii Metelkin + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class preset_created extends base { + + /** + * Initialise the rule data. + */ + protected function init() { + $this->data['edulevel'] = self::LEVEL_OTHER; + $this->data['crud'] = 'c'; + } + + /** + * Return localised event name. + * + * @return string + */ + public static function get_name(): string { + return get_string('event:preset_created', 'tool_enrolprofile'); + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description(): string { + return "User with id '{$this->userid}' created preset with id '{$this->other['presetid']}'"; + } + + /** + * Get URL related to the action. + * + * @return moodle_url + */ + public function get_url(): moodle_url { + return new moodle_url("/admin/tool/enrolprofile/index.php", ['id' => $this->other['presetid']]); + } + + /** + * Validates the custom data. + * + * @throws \coding_exception if missing required data. + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->other['presetid'])) { + throw new \coding_exception('The \'presetid\' value must be set in other.'); + } + + if (!isset($this->other['presetname'])) { + throw new \coding_exception('The \'presetname\' value must be set in other.'); + } + } +} diff --git a/classes/event/preset_deleted.php b/classes/event/preset_deleted.php new file mode 100644 index 0000000..cc683dc --- /dev/null +++ b/classes/event/preset_deleted.php @@ -0,0 +1,82 @@ +. + +namespace tool_enrolprofile\event; + +use core\event\base; +use moodle_url; + +/** + * Event triggered when a preset deleted. + * + * @package tool_enrolprofile + * @copyright 2024 Dmitrii Metelkin + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class preset_deleted extends base { + + /** + * Initialise the rule data. + */ + protected function init() { + $this->data['edulevel'] = self::LEVEL_OTHER; + $this->data['crud'] = 'd'; + } + + /** + * Return localised event name. + * + * @return string + */ + public static function get_name(): string { + return get_string('event:preset_deleted', 'tool_enrolprofile'); + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description(): string { + return "User with id '{$this->userid}' deleted preset with id '{$this->other['presetid']}'"; + } + + /** + * Get URL related to the action. + * + * @return moodle_url + */ + public function get_url(): moodle_url { + return new moodle_url("/admin/tool/enrolprofile/index.php", ['id' => $this->other['presetid']]); + } + + /** + * Validates the custom data. + * + * @throws \coding_exception if missing required data. + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->other['presetid'])) { + throw new \coding_exception('The \'presetid\' value must be set in other.'); + } + + if (!isset($this->other['presetname'])) { + throw new \coding_exception('The \'presetname\' value must be set in other.'); + } + } +} diff --git a/classes/event/preset_updated.php b/classes/event/preset_updated.php new file mode 100644 index 0000000..ea8b6e1 --- /dev/null +++ b/classes/event/preset_updated.php @@ -0,0 +1,82 @@ +. + +namespace tool_enrolprofile\event; + +use core\event\base; +use moodle_url; + +/** + * Event triggered when a preset updated. + * + * @package tool_enrolprofile + * @copyright 2024 Dmitrii Metelkin + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class preset_updated extends base { + + /** + * Initialise the rule data. + */ + protected function init() { + $this->data['edulevel'] = self::LEVEL_OTHER; + $this->data['crud'] = 'u'; + } + + /** + * Return localised event name. + * + * @return string + */ + public static function get_name(): string { + return get_string('event:preset_updated', 'tool_enrolprofile'); + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description(): string { + return "User with id '{$this->userid}' updated preset with id '{$this->other['presetid']}'"; + } + + /** + * Get URL related to the action. + * + * @return moodle_url + */ + public function get_url(): moodle_url { + return new moodle_url("/admin/tool/enrolprofile/index.php", ['id' => $this->other['presetid']]); + } + + /** + * Validates the custom data. + * + * @throws \coding_exception if missing required data. + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->other['presetid'])) { + throw new \coding_exception('The \'presetid\' value must be set in other.'); + } + + if (!isset($this->other['presetname'])) { + throw new \coding_exception('The \'presetname\' value must be set in other.'); + } + } +} diff --git a/classes/external/delete_preset.php b/classes/external/delete_preset.php index cf74963..9bb063a 100644 --- a/classes/external/delete_preset.php +++ b/classes/external/delete_preset.php @@ -16,10 +16,12 @@ namespace tool_enrolprofile\external; +use core\context\system; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_value; use invalid_parameter_exception; +use tool_enrolprofile\event\preset_deleted; use tool_enrolprofile\preset; /** @@ -57,7 +59,16 @@ public static function execute(int $id): void { throw new invalid_parameter_exception('Invalid preset'); } + $presetid = $preset->get('id'); $preset->delete(); + + preset_deleted::create([ + 'context' => system::instance(), + 'other' => [ + 'presetid' => $presetid, + 'presetname' => $preset->get('name'), + ] + ])->trigger(); } /** diff --git a/classes/helper.php b/classes/helper.php index dac38fc..ba4227c 100644 --- a/classes/helper.php +++ b/classes/helper.php @@ -53,6 +53,11 @@ class helper { */ public const ITEM_TYPE_COURSE = 'course'; + /** + * Preset item type. + */ + public const ITEM_TYPE_PRESET = 'preset'; + /** * Field shortname */ @@ -84,10 +89,10 @@ class helper { * @param int $itemid Item ID number * @param string $itemtype Item type (tag, course, category). * @param string $itemname Item name. - * @param stdClass|null $course Course to set up enrolment method. If not set, the no enrolment method will be created. + * @param array $courses Course to set up enrolment method. If not set, the no enrolment method will be created. * @return void */ - public static function add_item(int $itemid, string $itemtype, string $itemname, ?stdClass $course = null): void { + public static function add_item(int $itemid, string $itemtype, string $itemname, array $courses = []): void { $cohort = self::get_cohort_by_item($itemid, $itemtype); if (empty($cohort)) { @@ -111,8 +116,12 @@ public static function add_item(int $itemid, string $itemtype, string $itemname, self::add_profile_field_item($itemtype, $itemname); // Create enrolment method for the cohort for a given course. - if (!empty($course)) { - self::add_enrolment_method($course, $cohort); + if (!empty($courses)) { + foreach ($courses as $course) { + if (!empty($course)) { + self::add_enrolment_method($course, $cohort); + } + } } } @@ -515,7 +524,7 @@ public static function update_course_category(int $courseid, int $newcategoryid) */ public static function validate_task_custom_data(stdClass $data, array $fields = ['itemid', 'itemtype', 'itemname']): void { foreach ($fields as $field) { - if (empty($data->$field)) { + if (!object_property_exists($data, $field)) { throw new coding_exception('Missing required field: ' . $field); } } @@ -538,9 +547,10 @@ public static function get_course_tags(int $courseid = 0): array { } $sql = "SELECT DISTINCT t.id, t.rawname - FROM {tag} t - JOIN {tag_instance} ti ON t.id = ti.tagid - WHERE ti.itemtype = 'course' $where ORDER BY t.id, t.rawname"; + FROM {tag} t + JOIN {tag_instance} ti ON t.id = ti.tagid + WHERE ti.itemtype = 'course' $where + ORDER BY t.id, t.rawname"; return $DB->get_records_sql($sql, $params); } @@ -565,4 +575,39 @@ public static function get_categories(): array { return $DB->get_records('course_categories', ['visible' => 1], 'name'); } + + /** + * Return a list of courses for a given categories. + * + * @param array $categoryids A list of category IDs. + * @return array + */ + public static function get_courses_by_categories(array $categoryids): array { + global $DB; + + list($sql, $params) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED); + $select = 'category ' . $sql; + + return $DB->get_records_select('course', $select, $params); + } + + /** + * Return a list of courses for a given tags. + * + * @param array $tagids A list of tag IDs. + * @return array + */ + public static function get_courses_by_tags(array $tagids): array { + global $DB; + + list($select, $params) = $DB->get_in_or_equal($tagids, SQL_PARAMS_NAMED); + $where = 'ti.tagid '. $select; + + $sql = "SELECT DISTINCT c.* + FROM {course} c + JOIN {tag_instance} ti ON ti.itemid = c.id + WHERE ti.itemtype = 'course' AND $where"; + + return $DB->get_records_sql($sql, $params); + } } diff --git a/classes/observer.php b/classes/observer.php index 0bcb9a9..6099148 100644 --- a/classes/observer.php +++ b/classes/observer.php @@ -27,6 +27,9 @@ use core\event\tag_deleted; use core\event\tag_updated; use core\task\manager; +use tool_enrolprofile\event\preset_created; +use tool_enrolprofile\event\preset_deleted; +use tool_enrolprofile\event\preset_updated; /** * Event observer class. @@ -66,7 +69,7 @@ public static function tag_added(tag_added $event): void { 'itemid' => $event->other['tagid'], 'itemtype' => helper::ITEM_TYPE_TAG, 'itemname' => $event->other['tagrawname'], - 'courseid' => $event->other['itemid'], + 'courseids' => [$event->other['itemid']], ]); } @@ -133,7 +136,7 @@ public static function course_created(course_created $event): void { 'itemid' => $course->id, 'itemtype' => helper::ITEM_TYPE_COURSE, 'itemname' => $course->{helper::COURSE_NAME}, - 'courseid' => $course->id, + 'courseids' => [$course->id], ]); $category = $DB->get_record('course_categories', ['id' => $course->category]); @@ -141,7 +144,7 @@ public static function course_created(course_created $event): void { 'itemid' => $category->id, 'itemtype' => helper::ITEM_TYPE_CATEGORY, 'itemname' => $category->name, - 'courseid' => $course->id, + 'courseids' => [$course->id], ]); } @@ -232,4 +235,86 @@ public static function course_category_deleted(course_category_deleted $event): 'itemname' => $categoryname, ]); } + + /** + * Process preset_created event. + * + * @param preset_created $event The event. + */ + public static function preset_created(preset_created $event): void { + $courseids = []; + + if (!empty($event->other['categories'])) { + $catcourses = array_keys( + helper::get_courses_by_categories(explode(',', $event->other['categories'])) + ); + + $courseids = array_unique(array_merge($courseids, $catcourses)); + } + + if (!empty($event->other['courses'])) { + $courses = explode(',', $event->other['courses']); + $courseids = array_unique(array_merge($courseids, $courses)); + + } + + if (!empty($event->other['tags'])) { + $tagcourses = array_keys( + helper::get_courses_by_tags(explode(',', $event->other['tags'])) + ); + $courseids = array_values(array_unique(array_merge($courseids, $tagcourses))); + } + + self::queue_adhoc_task('add_item', [ + 'itemid' => $event->other['presetid'], + 'itemtype' => helper::ITEM_TYPE_PRESET, + 'itemname' => $event->other['presetname'], + 'courseids' => $courseids, + ]); + } + + /** + * Process preset_deleted event. + * + * @param preset_deleted $event The event. + */ + public static function preset_deleted(preset_deleted $event): void { + + $presetid = $event->other['presetid']; + $presetname = $event->other['presetname']; + + self::queue_adhoc_task('remove_item', [ + 'itemid' => $presetid, + 'itemtype' => helper::ITEM_TYPE_PRESET, + 'itemname' => $presetname, + ]); + } + + /** + * Process preset_updated event. + * + * @param preset_updated $event The event. + */ + public static function preset_updated(preset_updated $event): void { + $presetid = $event->other['presetid']; + $presetname = $event->other['presetname']; + + self::queue_adhoc_task('rename_item', [ + 'itemid' => $presetid, + 'itemtype' => helper::ITEM_TYPE_PRESET, + 'itemname' => $presetname, + ]); + + self::queue_adhoc_task('update_preset_data', [ + 'itemid' => $presetid, + 'itemtype' => helper::ITEM_TYPE_PRESET, + 'itemname' => $presetname, + 'categories' => $event->other['categories'], + 'oldcategories' => $event->other['oldcategories'], + 'courses' => $event->other['courses'], + 'oldcourses' => $event->other['oldcourses'], + 'tags' => $event->other['tags'], + 'oldtags' => $event->other['oldtags'], + ]); + } } diff --git a/classes/preset_form.php b/classes/preset_form.php index f569983..ca9ad60 100644 --- a/classes/preset_form.php +++ b/classes/preset_form.php @@ -20,6 +20,8 @@ use core\context\system; use core_form\dynamic_form; use moodle_url; +use tool_enrolprofile\event\preset_created; +use tool_enrolprofile\event\preset_updated; /** * Preset form class. @@ -87,9 +89,16 @@ protected function get_categories_options(): array { * @return array */ protected function get_courses_options(): array { + global $COURSE; + $options = []; foreach (helper::get_courses() as $option) { + // Skip front page course. + if ($option->id == $COURSE->id) { + continue; + } + $options[$option->id] = $option->fullname; } @@ -161,6 +170,7 @@ protected function check_access_for_dynamic_submission(): void { */ public function process_dynamic_submission(): \stdClass { $data = $this->get_submitted_data(); + $olddata = new \stdClass(); if (!empty($data->id)) { $preset = preset::get_record(['id' => $data->id]); @@ -175,12 +185,38 @@ public function process_dynamic_submission(): \stdClass { $data->$type = null; } + $olddata->$type = $preset->get($type); $preset->set($type, $data->$type); } $preset->set('name', $data->name); $preset->save(); + $other = [ + 'presetid' => $preset->get('id'), + 'presetname' => $preset->get('name'), + ]; + + foreach ($this->get_field_types() as $type) { + $other[$type] = $data->$type; + $other['old' . $type] = $olddata->$type; + } + + if (empty($data->id)) { + preset_created::create([ + 'context' => $this->get_context_for_dynamic_submission(), + 'other' => $other, + ])->trigger(); + } else { + foreach ($this->get_field_types() as $type) { + $other['old' . $type] = $olddata->$type; + } + preset_updated::create([ + 'context' => $this->get_context_for_dynamic_submission(), + 'other' => $other, + ])->trigger(); + } + return $preset->to_record(); } diff --git a/classes/task/add_item.php b/classes/task/add_item.php index 25cfa34..8c8f748 100644 --- a/classes/task/add_item.php +++ b/classes/task/add_item.php @@ -35,17 +35,25 @@ class add_item extends adhoc_task { public function execute() { global $DB; - $course = null; + $courses = []; $data = $this->get_custom_data(); helper::validate_task_custom_data($data); $transaction = $DB->start_delegated_transaction(); try { + // Covert to a new format if there is existing task with old format of data. if (!empty($data->courseid)) { - $course = get_course($data->courseid); + $data->courseids = [$data->courseid]; } - helper::add_item($data->itemid, $data->itemtype, $data->itemname, $course); + + if (!empty($data->courseids) && is_array($data->courseids)) { + list($sql, $params) = $DB->get_in_or_equal($data->courseids, SQL_PARAMS_NAMED); + $select = 'id ' . $sql; + $courses = $DB->get_records_select('course', $select, $params); + } + + helper::add_item($data->itemid, $data->itemtype, $data->itemname, $courses); $transaction->allow_commit(); } catch (Exception $exception) { $transaction->rollback($exception); diff --git a/classes/task/update_preset_data.php b/classes/task/update_preset_data.php new file mode 100644 index 0000000..ecfceb0 --- /dev/null +++ b/classes/task/update_preset_data.php @@ -0,0 +1,183 @@ +. + +namespace tool_enrolprofile\task; + +use core\task\adhoc_task; +use Exception; +use tool_enrolprofile\helper; +use stdClass; + +/** + * Update preset adhoc task. + * + * @package tool_enrolprofile + * @copyright 2024 Dmitrii Metelkin + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class update_preset_data extends adhoc_task { + + /** + * Cohort bases on preset. + * + * @var stdClass + */ + private $cohort; + + /** + * A list of courses to add cohort enrolment method to. + * @var array + */ + private $coursesadd = []; + + /** + * A list of courses to remove cohort enrolment method from. + * @var array + */ + private $coursesremove = []; + + /** + * Task execution + */ + public function execute() { + global $DB; + + $data = $this->get_custom_data(); + $this->validate_custom_data($data); + + $transaction = $DB->start_delegated_transaction(); + + try { + $this->cohort = helper::get_cohort_by_item($data->itemid, $data->itemtype); + + if (empty($this->cohort)) { + throw new \moodle_exception('Cohort not found for item type ' . $data->itemtype + . ' id ' . $data->itemid); + } + + $this->process_categories($data); + $this->process_courses($data); + $this->process_tags($data); + + $this->update_enrolments($data->itemid, $data->itemtype); + + $transaction->allow_commit(); + } catch (Exception $exception) { + $transaction->rollback($exception); + } + } + + /** + * Validate data. + * + * @param stdClass $data Task data, + * @return void + */ + private function validate_custom_data(stdClass $data): void { + helper::validate_task_custom_data( + $data, + [ + 'itemid', 'itemtype', 'itemname', 'categories', 'oldcategories', + 'courses', 'oldcourses', 'tags', 'oldtags' + ] + ); + } + + /** + * Process categories items. + * + * @param stdClass $data Categories data. + */ + private function process_categories(stdClass $data): void { + $categories = $this->explode_data($data->categories); + $oldcategories = $this->explode_data($data->oldcategories); + + $this->update_course_lists($categories, $oldcategories, 'get_courses_by_categories'); + } + + /** + * Process courses items. + * + * @param stdClass $data Courses data. + */ + private function process_courses(stdClass $data): void { + $courses = $this->explode_data($data->courses); + $oldcourses = $this->explode_data($data->oldcourses); + + $this->update_course_lists($courses, $oldcourses); + } + + /** + * Process tag items. + * + * @param \stdClass $data Tags data. + */ + private function process_tags(stdClass $data): void { + $tags = $this->explode_data($data->tags); + $oldtags = $this->explode_data($data->oldtags); + + $this->update_course_lists($tags, $oldtags, 'get_courses_by_tags'); + } + + /** + * A tiny helper method to convert list of items from string to array. + * + * @param string $data + * @return array + */ + private function explode_data(string $data): array { + return !empty($data) ? explode(',', $data) : []; + } + + /** + * Update list of courses based on new and old items. + * + * @param array $newitems A list of new items. + * @param array $olditems A list of old items. + * @param string|null $helpermethod A helper method to call to get courses based on items ids. + */ + private function update_course_lists(array $newitems, array $olditems, string $helpermethod = null): void { + $removeditems = array_diff($olditems, $newitems); + $addeditems = array_diff($newitems, $olditems); + + if (!empty($removeditems)) { + $removedcourses = $helpermethod ? array_keys(helper::$helpermethod($removeditems)) : $removeditems; + $this->coursesremove = array_unique(array_merge($this->coursesremove, $removedcourses)); + } + + if (!empty($addeditems)) { + $addedcourses = $helpermethod ? array_keys(helper::$helpermethod($addeditems)) : $addeditems; + $this->coursesadd = array_unique(array_merge($this->coursesadd, $addedcourses)); + } + } + + /** + * Update enrolment methods. + * + * @param int $itemid Item ID + * @param string $itemtype Item type. + */ + private function update_enrolments(int $itemid, string $itemtype): void { + foreach ($this->coursesremove as $courseid) { + helper::remove_enrolment_method($itemid, $itemtype, $courseid); + } + + foreach ($this->coursesadd as $courseid) { + $course = get_course($courseid); + helper::add_enrolment_method($course, $this->cohort); + } + } +} diff --git a/db/events.php b/db/events.php index 49a7dbe..d58bf8e 100644 --- a/db/events.php +++ b/db/events.php @@ -65,4 +65,16 @@ 'eventname' => '\core\event\course_category_deleted', 'callback' => '\tool_enrolprofile\observer::course_category_deleted', ], + [ + 'eventname' => '\tool_enrolprofile\event\preset_created', + 'callback' => '\tool_enrolprofile\observer::preset_created', + ], + [ + 'eventname' => '\tool_enrolprofile\event\preset_deleted', + 'callback' => '\tool_enrolprofile\observer::preset_deleted', + ], + [ + 'eventname' => '\tool_enrolprofile\event\preset_updated', + 'callback' => '\tool_enrolprofile\observer::preset_updated', + ], ]; diff --git a/db/install.php b/db/install.php index d5ab991..17affe1 100644 --- a/db/install.php +++ b/db/install.php @@ -44,6 +44,7 @@ function xmldb_tool_enrolprofile_install() { helper::ITEM_TYPE_COURSE => 'Course', helper::ITEM_TYPE_CATEGORY => 'Category', helper::ITEM_TYPE_TAG => 'Tag', + helper::ITEM_TYPE_PRESET => 'Preset', ]; foreach ($profilefields as $shortname => $name) { install_helper::add_user_profile_field( diff --git a/lang/en/tool_enrolprofile.php b/lang/en/tool_enrolprofile.php index 120f179..6fa19e8 100644 --- a/lang/en/tool_enrolprofile.php +++ b/lang/en/tool_enrolprofile.php @@ -45,3 +45,6 @@ $string['fieldtype:courses'] = 'Courses'; $string['fieldtype:tags'] = 'Tags'; $string['mustselectentities'] = 'You must select at least one of the following entities'; +$string['event:preset_created'] = 'Preset created'; +$string['event:preset_updated'] = 'Preset updated'; +$string['event:preset_deleted'] = 'Preset deleted'; diff --git a/tests/helper_test.php b/tests/helper_test.php index 3108001..89ff3b4 100644 --- a/tests/helper_test.php +++ b/tests/helper_test.php @@ -17,7 +17,9 @@ namespace tool_enrolprofile; use advanced_testcase; +use core\context\course; use core_customfield\field_controller; +use core_tag_tag; /** * Unit tests for helper class. @@ -88,4 +90,148 @@ public function test_get_cohort_by_item(): void { $this->assertEquals($cohort2->id, helper::get_cohort_by_item(13, 'tag')->id); $this->assertEquals($cohort3->id, helper::get_cohort_by_item(13, 'course')->id); } + + /** + * Test getting courses by categories. + */ + public function test_get_courses_by_categories() { + $this->resetAfterTest(); + + $category1 = $this->getDataGenerator()->create_category(); + $category2 = $this->getDataGenerator()->create_category(); + $category3 = $this->getDataGenerator()->create_category(); + + $course11 = $this->getDataGenerator()->create_course(['category' => $category1->id]); + $course12 = $this->getDataGenerator()->create_course(['category' => $category1->id]); + $course13 = $this->getDataGenerator()->create_course(['category' => $category1->id]); + + $course21 = $this->getDataGenerator()->create_course(['category' => $category2->id]); + $course22 = $this->getDataGenerator()->create_course(['category' => $category2->id]); + + $courses = helper::get_courses_by_categories([$category1->id]); + $this->assertCount(3, $courses); + $this->assertArrayHasKey($course11->id, $courses); + $this->assertArrayHasKey($course12->id, $courses); + $this->assertArrayHasKey($course13->id, $courses); + + $courses = helper::get_courses_by_categories([$category2->id]); + $this->assertCount(2, $courses); + $this->assertArrayHasKey($course21->id, $courses); + $this->assertArrayHasKey($course22->id, $courses); + + $courses = helper::get_courses_by_categories([$category3->id]); + $this->assertCount(0, $courses); + + $courses = helper::get_courses_by_categories([$category2->id, $category1->id]); + $this->assertCount(5, $courses); + $this->assertArrayHasKey($course11->id, $courses); + $this->assertArrayHasKey($course12->id, $courses); + $this->assertArrayHasKey($course13->id, $courses); + $this->assertArrayHasKey($course21->id, $courses); + $this->assertArrayHasKey($course12->id, $courses); + } + + /** + * Test getting courses by tags. + */ + public function test_get_courses_by_tags() { + global $DB; + + $this->resetAfterTest(); + + $course1 = $this->getDataGenerator()->create_course(); + $course2 = $this->getDataGenerator()->create_course(); + $course3 = $this->getDataGenerator()->create_course(); + $course4 = $this->getDataGenerator()->create_course(); + $course5 = $this->getDataGenerator()->create_course(); + + core_tag_tag::set_item_tags('core', 'course', $course1->id, course::instance($course1->id), [ + 'tag1', + 'tag2', + 'tag3', + 'tag4', + ]); + + core_tag_tag::set_item_tags('core', 'course', $course2->id, course::instance($course2->id), [ + 'tag2', + 'tag4', + ]); + + core_tag_tag::set_item_tags('core', 'course', $course3->id, course::instance($course3->id), [ + 'tag1', + 'tag2', + 'tag4', + ]); + + core_tag_tag::set_item_tags('core', 'course', $course4->id, course::instance($course4->id), [ + 'tag5', + ]); + + $tag1 = $DB->get_record('tag', ['rawname' => 'tag1']); + $tag2 = $DB->get_record('tag', ['rawname' => 'tag2']); + $tag3 = $DB->get_record('tag', ['rawname' => 'tag3']); + $tag4 = $DB->get_record('tag', ['rawname' => 'tag4']); + $tag5 = $DB->get_record('tag', ['rawname' => 'tag5']); + + $courses = helper::get_courses_by_tags([77777]); + $this->assertCount(0, $courses); + + $courses = helper::get_courses_by_tags([$tag1->id]); + $this->assertCount(2, $courses); + $this->assertArrayHasKey($course1->id, $courses); + $this->assertArrayHasKey($course3->id, $courses); + + $courses = helper::get_courses_by_tags([$tag1->id, $tag2->id]); + $this->assertCount(3, $courses); + $this->assertArrayHasKey($course1->id, $courses); + $this->assertArrayHasKey($course2->id, $courses); + $this->assertArrayHasKey($course3->id, $courses); + + $courses = helper::get_courses_by_tags([$tag4->id, $tag5->id]); + $this->assertCount(4, $courses); + $this->assertArrayHasKey($course1->id, $courses); + $this->assertArrayHasKey($course2->id, $courses); + $this->assertArrayHasKey($course3->id, $courses); + $this->assertArrayHasKey($course4->id, $courses); + + $courses = helper::get_courses_by_tags([$tag3->id]); + $this->assertCount(1, $courses); + $this->assertArrayHasKey($course1->id, $courses); + } + + /** + * Data provider for test_validate_task_custom_data. + * + * @return array[] + */ + public function validate_task_custom_data_data_provider(): array { + return [ + [['itemid' => 1, 'itemtype' => 'type'], ['itemid', 'itemtype'], ''], + [['itemtype' => 'type'], ['itemid', 'itemtype'], 'Missing required field: itemid'], + [['itemid' => null, 'itemtype' => 'type'], ['itemid', 'itemtype'], ''], + [['itemid' => 0, 'itemtype' => 'type'], ['itemid', 'itemtype'], ''], + [['itemid' => '', 'itemtype' => 'type'], ['itemid', 'itemtype'], ''], + [['itemid' => ''], ['itemid', 'itemtype'], 'Missing required field: itemtype'], + + ]; + } + + /** + * Test validate_task_custom_data. + * + * @dataProvider validate_task_custom_data_data_provider + * + * @param array $data Data to validate. + * @param array $fields Fields to validate against + * @param string $message Expected exception message. If empty, then no exception is expected, + * @return void + */ + public function test_validate_task_custom_data(array $data, array $fields, string $message = '') { + if (!empty($message)) { + $this->expectException(\coding_exception::class); + $this->expectExceptionMessage($message); + } + + helper::validate_task_custom_data((object)$data, $fields); + } } diff --git a/tests/observer_test.php b/tests/observer_test.php index cbd4fc0..dfdc348 100644 --- a/tests/observer_test.php +++ b/tests/observer_test.php @@ -19,9 +19,13 @@ use advanced_testcase; use core\context\course; use core\context\coursecat; +use core\context\system; use core_customfield\field_controller; use core_tag_tag; use core\task\manager; +use tool_enrolprofile\event\preset_created; +use tool_enrolprofile\event\preset_deleted; +use tool_enrolprofile\event\preset_updated; /** @@ -53,6 +57,12 @@ class observer_test extends advanced_testcase { */ protected $categoryprofilefield; + /** + * Category profile field for testing. + * @var \stdClass + */ + protected $presetprofilefield; + /** * Set up before every test. * @@ -65,6 +75,7 @@ public function setUp(): void { $this->tagprofilefield = $this->add_user_profile_field(helper::ITEM_TYPE_TAG, 'autocomplete'); $this->courseprofilefield = $this->add_user_profile_field(helper::ITEM_TYPE_COURSE, 'autocomplete'); $this->categoryprofilefield = $this->add_user_profile_field(helper::ITEM_TYPE_CATEGORY, 'autocomplete'); + $this->presetprofilefield = $this->add_user_profile_field(helper::ITEM_TYPE_PRESET, 'autocomplete'); $this->create_cohort_custom_field(helper::COHORT_FIELD_ID); $this->create_cohort_custom_field(helper::COHORT_FIELD_TYPE); @@ -872,4 +883,394 @@ public function test_course_category_deleted(): void { $enrol = $DB->get_record('enrol', ['courseid' => $course->id, 'enrol' => 'cohort', 'customint1' => $cohort->id]); $this->assertEmpty($enrol); } + + /** + * Check logic when preset created. + */ + public function test_preset_logic() { + global $DB; + + $category1 = $this->getDataGenerator()->create_category(); + $category2 = $this->getDataGenerator()->create_category(); + $category3 = $this->getDataGenerator()->create_category(); + + $course11 = $this->getDataGenerator()->create_course(['category' => $category1->id]); + $course12 = $this->getDataGenerator()->create_course(['category' => $category1->id]); + $course21 = $this->getDataGenerator()->create_course(['category' => $category2->id]); + $course22 = $this->getDataGenerator()->create_course(['category' => $category2->id]); + $course31 = $this->getDataGenerator()->create_course(['category' => $category3->id]); + $course32 = $this->getDataGenerator()->create_course(['category' => $category3->id]); + + core_tag_tag::set_item_tags('core', 'course', $course11->id, course::instance($course11->id), [ + 'tag1', + 'tag2', + ]); + + core_tag_tag::set_item_tags('core', 'course', $course21->id, course::instance($course21->id), [ + 'tag3', + 'tag4', + ]); + + core_tag_tag::set_item_tags('core', 'course', $course31->id, course::instance($course31->id), [ + 'tag5', + ]); + + $tag1 = $DB->get_record('tag', ['rawname' => 'tag1']); + $tag2 = $DB->get_record('tag', ['rawname' => 'tag2']); + $tag3 = $DB->get_record('tag', ['rawname' => 'tag3']); + $tag4 = $DB->get_record('tag', ['rawname' => 'tag4']); + $tag5 = $DB->get_record('tag', ['rawname' => 'tag5']); + + // Preset with categories only. + $presetname = 'Preset1'; + $this->assertEmpty($DB->get_record('cohort', ['name' => $presetname])); + $this->assertEmpty($DB->get_record('tool_dynamic_cohorts', ['name' => $presetname])); + + $preset = new preset(); + $preset->set('name', $presetname); + $preset->set('categories', implode(',', [$category1->id])); + $preset->save(); + + preset_created::create([ + 'context' => system::instance(), + 'other' => [ + 'presetid' => $preset->get('id'), + 'presetname' => $preset->get('name'), + 'categories' => $preset->get('categories'), + 'oldcategories' => null, + 'courses' => $preset->get('courses'), + 'oldcourses' => null, + 'tags' => $preset->get('tags'), + 'oldtags' => null, + ] + ])->trigger(); + + $this->execute_tasks(); + + $presetcohort = $DB->get_record('cohort', ['name' => $presetname]); + $this->assertNotEmpty($presetcohort); + + $presetcohort = cohort_get_cohort($presetcohort->id, coursecat::instance($category1->id), true); + + $profilefielddata = $DB->get_field('user_info_field', 'param1', ['id' => $this->presetprofilefield->id]); + $this->assertNotEmpty($profilefielddata); + $this->assertTrue(in_array($presetname, explode("\n", $profilefielddata))); + + $rule = $DB->get_record('tool_dynamic_cohorts', ['name' => $presetname]); + $this->assertNotEmpty($rule); + $this->assertEquals($presetcohort->id, $rule->cohortid); + $this->assertEquals(1, $rule->enabled); + $conditions = $DB->get_records('tool_dynamic_cohorts_c', ['ruleid' => $rule->id]); + $this->assertCount(2, $conditions); + + $this->assertNotEmpty( + $DB->get_record('enrol', ['courseid' => $course11->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertNotEmpty( + $DB->get_record('enrol', ['courseid' => $course12->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course21->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course22->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course31->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course32->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + + // Preset with courses only. + $presetname = 'Preset2'; + $this->assertEmpty($DB->get_record('cohort', ['name' => $presetname])); + $this->assertEmpty($DB->get_record('tool_dynamic_cohorts', ['name' => $presetname])); + + $preset = new preset(); + $preset->set('name', $presetname); + $preset->set('courses', implode(',', [$course11->id, $course22->id, $course31->id])); + $preset->save(); + + preset_created::create([ + 'context' => system::instance(), + 'other' => [ + 'presetid' => $preset->get('id'), + 'presetname' => $preset->get('name'), + 'categories' => $preset->get('categories'), + 'oldcategories' => null, + 'courses' => $preset->get('courses'), + 'oldcourses' => null, + 'tags' => $preset->get('tags'), + 'oldtags' => null, + ] + ])->trigger(); + + $this->execute_tasks(); + + $presetcohort = $DB->get_record('cohort', ['name' => $presetname]); + $this->assertNotEmpty($presetcohort); + + $presetcohort = cohort_get_cohort($presetcohort->id, coursecat::instance($category1->id), true); + + $profilefielddata = $DB->get_field('user_info_field', 'param1', ['id' => $this->presetprofilefield->id]); + $this->assertNotEmpty($profilefielddata); + $this->assertTrue(in_array($presetname, explode("\n", $profilefielddata))); + + $rule = $DB->get_record('tool_dynamic_cohorts', ['name' => $presetname]); + $this->assertNotEmpty($rule); + $this->assertEquals($presetcohort->id, $rule->cohortid); + $this->assertEquals(1, $rule->enabled); + $conditions = $DB->get_records('tool_dynamic_cohorts_c', ['ruleid' => $rule->id]); + $this->assertCount(2, $conditions); + + $this->assertNotEmpty( + $DB->get_record('enrol', ['courseid' => $course11->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course12->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course21->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertNotEmpty( + $DB->get_record('enrol', ['courseid' => $course22->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertNotEmpty( + $DB->get_record('enrol', ['courseid' => $course31->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course32->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + + // Preset with tags only. + $presetname = 'Preset3'; + $this->assertEmpty($DB->get_record('cohort', ['name' => $presetname])); + $this->assertEmpty($DB->get_record('tool_dynamic_cohorts', ['name' => $presetname])); + + $preset = new preset(); + $preset->set('name', $presetname); + $preset->set('tags', implode(',', [$tag1->id, $tag2->id, $tag4->id])); + $preset->save(); + + preset_created::create([ + 'context' => system::instance(), + 'other' => [ + 'presetid' => $preset->get('id'), + 'presetname' => $preset->get('name'), + 'categories' => $preset->get('categories'), + 'oldcategories' => null, + 'courses' => $preset->get('courses'), + 'oldcourses' => null, + 'tags' => $preset->get('tags'), + 'oldtags' => null, + ] + ])->trigger(); + + $this->execute_tasks(); + + $presetcohort = $DB->get_record('cohort', ['name' => $presetname]); + $this->assertNotEmpty($presetcohort); + + $presetcohort = cohort_get_cohort($presetcohort->id, coursecat::instance($category1->id), true); + + $profilefielddata = $DB->get_field('user_info_field', 'param1', ['id' => $this->presetprofilefield->id]); + $this->assertNotEmpty($profilefielddata); + $this->assertTrue(in_array($presetname, explode("\n", $profilefielddata))); + + $rule = $DB->get_record('tool_dynamic_cohorts', ['name' => $presetname]); + $this->assertNotEmpty($rule); + $this->assertEquals($presetcohort->id, $rule->cohortid); + $this->assertEquals(1, $rule->enabled); + $conditions = $DB->get_records('tool_dynamic_cohorts_c', ['ruleid' => $rule->id]); + $this->assertCount(2, $conditions); + + $this->assertNotEmpty( + $DB->get_record('enrol', ['courseid' => $course11->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course12->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertNotEmpty( + $DB->get_record('enrol', ['courseid' => $course21->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course22->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course31->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course32->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + + // Preset with mix of data. + $presetname = 'Preset4'; + $this->assertEmpty($DB->get_record('cohort', ['name' => $presetname])); + $this->assertEmpty($DB->get_record('tool_dynamic_cohorts', ['name' => $presetname])); + + $preset = new preset(); + $preset->set('name', $presetname); + $preset->set('categories', implode(',', [$category1->id])); + $preset->set('courses', implode(',', [$course21->id, $course31->id])); + $preset->set('tags', implode(',', [$tag1->id, $tag5->id])); + $preset->save(); + + preset_created::create([ + 'context' => system::instance(), + 'other' => [ + 'presetid' => $preset->get('id'), + 'presetname' => $preset->get('name'), + 'categories' => $preset->get('categories'), + 'oldcategories' => null, + 'courses' => $preset->get('courses'), + 'oldcourses' => null, + 'tags' => $preset->get('tags'), + 'oldtags' => null + ] + ])->trigger(); + + $this->execute_tasks(); + + $presetcohort = $DB->get_record('cohort', ['name' => $presetname]); + $this->assertNotEmpty($presetcohort); + + $presetcohort = cohort_get_cohort($presetcohort->id, coursecat::instance($category1->id), true); + + $profilefielddata = $DB->get_field('user_info_field', 'param1', ['id' => $this->presetprofilefield->id]); + $this->assertNotEmpty($profilefielddata); + $this->assertTrue(in_array($presetname, explode("\n", $profilefielddata))); + + $rule = $DB->get_record('tool_dynamic_cohorts', ['name' => $presetname]); + $this->assertNotEmpty($rule); + $this->assertEquals($presetcohort->id, $rule->cohortid); + $this->assertEquals(1, $rule->enabled); + $conditions = $DB->get_records('tool_dynamic_cohorts_c', ['ruleid' => $rule->id]); + $this->assertCount(2, $conditions); + + $this->assertNotEmpty( + $DB->get_record('enrol', ['courseid' => $course11->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertNotEmpty( + $DB->get_record('enrol', ['courseid' => $course12->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertNotEmpty( + $DB->get_record('enrol', ['courseid' => $course21->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course22->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertNotEmpty( + $DB->get_record('enrol', ['courseid' => $course31->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course32->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + + // UPDATE last preset. + $presetoldname = $preset->get('name'); + $presetname = 'Updated Preset4'; + $preset->set('name', $presetname); + $preset->set('categories', implode(',', [$category3->id])); + $preset->set('courses', implode(',', [$course11->id, $course31->id])); + $preset->set('tags', implode(',', [$tag3->id])); + $preset->save(); + + preset_updated::create([ + 'context' => system::instance(), + 'other' => [ + 'presetid' => $preset->get('id'), + 'presetname' => $preset->get('name'), + 'categories' => $preset->get('categories'), + 'oldcategories' => implode(',', [$category1->id]), + 'courses' => $preset->get('courses'), + 'oldcourses' => implode(',', [$course21->id, $course31->id]), + 'tags' => $preset->get('tags'), + 'oldtags' => implode(',', [$tag1->id, $tag5->id]), + ] + ])->trigger(); + + $this->execute_tasks(); + + $presetcohort = $DB->get_record('cohort', ['name' => $presetname]); + $this->assertNotEmpty($presetcohort); + + $presetcohort = cohort_get_cohort($presetcohort->id, coursecat::instance($category1->id), true); + + $profilefielddata = $DB->get_field('user_info_field', 'param1', ['id' => $this->presetprofilefield->id]); + $this->assertNotEmpty($profilefielddata); + $this->assertTrue(in_array($presetname, explode("\n", $profilefielddata))); + $this->assertFalse(in_array($presetoldname, explode("\n", $profilefielddata))); + + $rule = $DB->get_record('tool_dynamic_cohorts', ['name' => $presetname]); + $this->assertNotEmpty($rule); + $this->assertEquals($presetcohort->id, $rule->cohortid); + + $this->assertNotEmpty( + $DB->get_record('enrol', ['courseid' => $course11->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course12->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertNotEmpty( + $DB->get_record('enrol', ['courseid' => $course21->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course22->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertNotEmpty( + $DB->get_record('enrol', ['courseid' => $course31->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + $this->assertNotEmpty( + $DB->get_record('enrol', ['courseid' => $course32->id, 'enrol' => 'cohort', 'customint1' => $presetcohort->id]) + ); + + // Now delete preset. + $presetid = $preset->get('id'); + $presetcohortid = $presetcohort->id; + $preset->delete(); + + preset_deleted::create([ + 'context' => system::instance(), + 'other' => [ + 'presetid' => $presetid, + 'presetname' => $preset->get('name'), + ] + ])->trigger(); + + $this->execute_tasks(); + + $presetcohort = $DB->get_record('cohort', ['name' => $presetname]); + $this->assertEmpty($presetcohort); + + $presetcohort = cohort_get_cohort($presetcohortid, coursecat::instance($category1->id), true); + $this->assertEmpty($presetcohort); + + $profilefielddata = $DB->get_field('user_info_field', 'param1', ['id' => $this->presetprofilefield->id]); + $this->assertNotEmpty($profilefielddata); + $this->assertFalse(in_array($presetname, explode("\n", $profilefielddata))); + $this->assertFalse(in_array($presetoldname, explode("\n", $profilefielddata))); + + $rule = $DB->get_record('tool_dynamic_cohorts', ['name' => $presetname]); + $this->assertEmpty($rule); + + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course11->id, 'enrol' => 'cohort', 'customint1' => $presetcohortid]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course12->id, 'enrol' => 'cohort', 'customint1' => $presetcohortid]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course21->id, 'enrol' => 'cohort', 'customint1' => $presetcohortid]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course22->id, 'enrol' => 'cohort', 'customint1' => $presetcohortid]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course31->id, 'enrol' => 'cohort', 'customint1' => $presetcohortid]) + ); + $this->assertEmpty( + $DB->get_record('enrol', ['courseid' => $course32->id, 'enrol' => 'cohort', 'customint1' => $presetcohortid]) + ); + } } diff --git a/version.php b/version.php index 59aee02..a11e5e1 100644 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ $plugin->component = 'tool_enrolprofile'; $plugin->release = '0.1.0'; -$plugin->version = 2024091702; +$plugin->version = 2024091705; $plugin->requires = 2022112800; $plugin->maturity = MATURITY_ALPHA; $plugin->supported = [404, 404];