From 79f15d1a36b9afd642ea316129e4a9245f6de8bf Mon Sep 17 00:00:00 2001 From: Dmitrii Metelkin Date: Tue, 3 Sep 2024 20:00:03 +1000 Subject: [PATCH 01/10] issue #4: process adding a tag --- classes/helper.php | 226 ++++++++++++++++++++++++++++++++++++++++ classes/observer.php | 82 +++++++++++++++ db/events.php | 32 ++++++ tests/observer_test.php | 149 ++++++++++++++++++++++++++ 4 files changed, 489 insertions(+) create mode 100644 classes/helper.php create mode 100644 classes/observer.php create mode 100644 db/events.php create mode 100644 tests/observer_test.php diff --git a/classes/helper.php b/classes/helper.php new file mode 100644 index 0000000..b5821a0 --- /dev/null +++ b/classes/helper.php @@ -0,0 +1,226 @@ +. + +namespace tool_enrolprofile; + +use context_system; +use stdClass; +use tool_dynamic_cohorts\cohort_manager; +use tool_dynamic_cohorts\condition_base; +use tool_dynamic_cohorts\rule; + +/** + * Helper class. + * + * @package tool_enrolprofile + * @copyright 2024 Dmitrii Metelkin + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class helper { + + /** + * Profile field category. + */ + public const PROFILE_CATEGORY = 'Profile field'; + + /** + * Field shortname + */ + public const FIELD_TAG = 'tag'; + + /** + * Field shortname + */ + public const FIELD_CATEGORY = 'category'; + + /** + * Field shortname + */ + public const FIELD_COURSE = 'course'; + + /** + * Field shortname + */ + public const FIELD_ENROLLED_UNTIL = 'enrolleduntil'; + + /** + * Course fiel name. + */ + public const COURSE_NAME = 'fullname'; + + /** + * Field shortname + */ + public const COHORT_FIELD_ID = 'id'; + + /** + * Field shortname + */ + public const COHORT_FIELD_TYPE = 'type'; + + /** + * Role shortname. + */ + public const STUDENT_ROLE = 'student'; + + + /** + * Get cohort by provided item type and item id. + * + * @param int $itemid Item ID. + * @param string $itemtype Item type. + * + * @return stdClass|null + */ + public static function get_cohort_by_item(int $itemid, string $itemtype): ?stdClass { + $systemcontext = context_system::instance(); + + $allcohorts = cohort_get_cohorts($systemcontext->id, 0, 0, '', true); + // Load custom fields data and filter bby custom field type and id. + $cohorts = array_filter($allcohorts['cohorts'], function ($cohortdata) use ($itemid, $itemtype) { + foreach ($cohortdata->customfields as $customfield) { + $name = 'customfield_' . $customfield->get_field()->get('shortname'); + $cohortdata->$name = $customfield->export_value(); + } + return $cohortdata->customfield_type == $itemtype && $cohortdata->customfield_id == $itemid; + }); + + if (!empty($cohorts)) { + return reset($cohorts); + } else { + return null; + } + } + + /** + * Helper method to add enrolment method to a course. + * + * @param stdClass $course Course. + * @param stdClass $cohort Cohort. + * + * @return void + */ + public static function add_enrolment_method(stdClass $course, stdClass $cohort): void { + global $DB; + + $studentrole = $DB->get_record('role', ['shortname' => self::STUDENT_ROLE]); + + $fields = [ + 'customint1' => $cohort->id, + 'roleid' => $studentrole->id, + 'courseid' => $course->id, + ]; + + if (!$DB->record_exists('enrol', $fields)) { + $enrol = enrol_get_plugin('cohort'); + $enrol->add_instance($course, $fields); + } + } + + /** + * Update profile field with new item. + * + * @param string $shortname Field short name. + * @param string $newitem A new item to add to the field. + * + * @return void + */ + public static function update_profile_field(string $shortname, string $newitem): void { + global $DB; + + $field = $DB->get_record('user_info_field', ['shortname' => $shortname]); + $fielddata = []; + if (!empty($field->param1)) { + $fielddata = explode("\n", $field->param1); + } + + if (!in_array($newitem, $fielddata)) { + $fielddata[] = $newitem; + sort($fielddata); + $field->param1 = implode("\n", $fielddata); + $DB->update_record('user_info_field', $field); + } + } + + /** + * Add cohort. + * + * @param stdClass $cohort Cohort. + * + * @return int + */ + public static function add_cohort(stdClass $cohort): int { + global $DB; + if (!$existingcohort = $DB->get_record('cohort', ['name' => $cohort->name])) { + return cohort_add_cohort($cohort); + } else { + return $existingcohort->id; + } + } + + /** + * A helper method to set up rule for given cohort. + * + * @param stdClass $cohort Cohort. + * @param string $fieldshortname Related profile field shortname. + * + * @return void + */ + public static function add_rule(stdClass $cohort, string $fieldshortname): void { + if (rule::get_record(['cohortid' => $cohort->id])) { + return; + } + + cohort_manager::manage_cohort($cohort->id); + $rule = new rule(0, (object)[ + 'name' => $cohort->name, + 'cohortid' => $cohort->id, + 'description' => $cohort->description, + ]); + $rule->save(); + + $condition = condition_base::get_instance(0, (object)[ + 'classname' => 'tool_dynamic_cohorts\local\tool_dynamic_cohorts\condition\user_custom_profile', + ]); + + $fieldname = 'profile_field_' . $fieldshortname; + $condition->set_config_data([ + 'profilefield' => $fieldname, + $fieldname . '_operator' => condition_base::TEXT_IS_EQUAL_TO, + $fieldname . '_value' => $cohort->name, + ]); + $condition->get_record()->set('ruleid', $rule->get('id')); + $condition->get_record()->set('sortorder', 0); + $condition->get_record()->save(); + + $condition = condition_base::get_instance(0, (object)[ + 'classname' => 'tool_dynamic_cohorts\local\tool_dynamic_cohorts\condition\user_custom_profile', + ]); + + $fieldname = 'profile_field_' . self::FIELD_ENROLLED_UNTIL; + $condition->set_config_data([ + 'profilefield' => $fieldname, + $fieldname . '_operator' => condition_base::DATE_IN_THE_FUTURE, + $fieldname . '_value' => 0, + ]); + $condition->get_record()->set('ruleid', $rule->get('id')); + $condition->get_record()->set('sortorder', 0); + $condition->get_record()->save(); + + $rule->set('enabled', 1); + $rule->save(); + } +} diff --git a/classes/observer.php b/classes/observer.php new file mode 100644 index 0000000..c5024ac --- /dev/null +++ b/classes/observer.php @@ -0,0 +1,82 @@ +. + +namespace tool_enrolprofile; + +use core\event\tag_added; +use stdClass; +use context_system; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/cohort/lib.php'); + +/** + * Event observer class. + * + * @package tool_enrolprofile + * @copyright 2024 Dmitrii Metelkin + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class observer { + + /** + * Process tag_added event. + * + * @param tag_added $event The event. + */ + public static function tag_added(tag_added $event): void { + // Check context is course context. + $context = $event->get_context(); + if ($context->contextlevel != CONTEXT_COURSE && $event->other['itemtype'] != 'course') { + return; + } + + $tagid = $event->other['tagid']; + $tagname = $event->other['tagrawname']; + + $courseid = $event->other['itemid']; + $course = get_course($courseid); + + $cohort = new stdClass(); + $cohort->contextid = context_system::instance()->id; + $cohort->name = $tagname; + $cohort->idnumber = $tagname; + $cohort->description = 'Tag related'; + $typefieled = 'customfield_' . helper::COHORT_FIELD_TYPE; + $cohort->$typefieled = 'tag'; + $idfieled = 'customfield_' . helper::COHORT_FIELD_ID; + $cohort->$idfieled = $tagid; + + // Check if cohort already exists. + $existingcohort = helper::get_cohort_by_item($tagid, 'tag'); + + // If not. + if (empty($existingcohort)) { + // Create a new cohort. + $cohort->id = helper::add_cohort($cohort); + // Create a dynamic cohort rule associated with this cohort. + helper::add_rule($cohort, helper::FIELD_TAG); + // Add a tag to a custom profile field. + helper::update_profile_field(helper::FIELD_TAG, $tagname); + // Create enrolment method for the cohort for a given course. + helper::add_enrolment_method($course, $cohort); + } else { + // If yes, create enrolment method for the cohort for a given course. + helper::add_enrolment_method($course, $existingcohort); + } + } +} diff --git a/db/events.php b/db/events.php new file mode 100644 index 0000000..0b0db27 --- /dev/null +++ b/db/events.php @@ -0,0 +1,32 @@ +. + +/** + * Plugin event observers are registered here. + * + * @package tool_enrolprofile + * @copyright 2024 Dmitrii Metelkin + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$observers = [ + [ + 'eventname' => '\core\event\tag_added', + 'callback' => '\tool_enrolprofile\observer::tag_added', + ], +]; diff --git a/tests/observer_test.php b/tests/observer_test.php new file mode 100644 index 0000000..4ae3b3c --- /dev/null +++ b/tests/observer_test.php @@ -0,0 +1,149 @@ +. + +namespace tool_enrolprofile; + +use advanced_testcase; +use core_customfield\field_controller; +use core_tag_tag; +use context_course; + +/** + * Unit tests for observer class. + * + * @package tool_dynamic_cohorts + * @copyright 2024 Catalyst IT + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * + * @covers \tool_enrolprofile\observer + */ +class observer_test extends advanced_testcase { + + /** + * A helper function to create a custom profile field. + * + * @param string $shortname Short name of the field. + * @param string $datatype Type of the field, e.g. text, checkbox, datetime, menu and etc. + * @param array $extras A list of extra fields for the field (e.g. forceunique, param1 and etc) + * + * @return \stdClass + */ + protected function add_user_profile_field(string $shortname, string $datatype, array $extras = []): \stdClass { + global $DB; + + $data = new \stdClass(); + $data->shortname = $shortname; + $data->datatype = $datatype; + $data->name = 'Test ' . $shortname; + $data->description = 'This is a test field'; + $data->required = false; + $data->locked = false; + $data->forceunique = false; + $data->signup = false; + $data->visible = '0'; + $data->categoryid = '0'; + + foreach ($extras as $name => $value) { + $data->{$name} = $value; + } + + $data->id = $DB->insert_record('user_info_field', $data); + + return $data; + } + + /** + * Create cohort custom field for testing. + * + * @param string $shortname Field shortname + * @param string $datatype $field data type. + * + * @return field_controller + */ + protected function create_cohort_custom_field(string $shortname, string $datatype = 'text'): field_controller { + $fieldcategory = self::getDataGenerator()->create_custom_field_category([ + 'component' => 'core_cohort', + 'area' => 'cohort', + 'name' => 'Other fields', + ]); + + return self::getDataGenerator()->create_custom_field([ + 'shortname' => $shortname, + 'name' => 'Custom field ' . $shortname, + 'type' => $datatype, + 'categoryid' => $fieldcategory->get('id'), + ]); + } + + /** + * Check logic when adding a tag. + * @return void + */ + public function test_tag_added() { + global $DB; + + $this->resetAfterTest(); + $this->setAdminUser(); + + $prodilefield = $this->add_user_profile_field(helper::FIELD_TAG, 'autocomplete'); + $this->create_cohort_custom_field(helper::COHORT_FIELD_ID); + $this->create_cohort_custom_field(helper::COHORT_FIELD_TYPE); + $course1 = $this->getDataGenerator()->create_course(); + $course2 = $this->getDataGenerator()->create_course(); + $tagname = 'A tag'; + + $this->assertEmpty($DB->get_record('cohort', ['name' => $tagname])); + $this->assertEmpty($DB->get_field('user_info_field', 'param1', ['id' => $prodilefield->id])); + $this->assertEmpty($DB->get_record('tag', ['name' => $tagname])); + $this->assertEmpty($DB->get_record('tool_dynamic_cohorts', ['name' => $tagname])); + $this->assertEmpty($DB->get_record('tool_dynamic_cohorts', ['name' => $tagname])); + $this->assertCount(0, $DB->get_records('enrol', ['courseid' => $course1->id, 'enrol' => 'cohort'])); + + core_tag_tag::set_item_tags('core', 'course', $course1->id, \context_course::instance($course1->id), [$tagname]); + + $tag = $DB->get_record('tag', ['name' => $tagname]); + $cohort = $DB->get_record('cohort', ['name' => $tagname]); + $this->assertNotEmpty($cohort); + + $cohort = cohort_get_cohort($cohort->id, context_course::instance($course1->id), true); + foreach ($cohort->customfields as $customfield) { + if ($customfield->get_field()->get('shortname') == helper::COHORT_FIELD_ID) { + $this->assertSame($tag->id, $customfield->export_value()); + } + if ($customfield->get_field()->get('shortname') == helper::COHORT_FIELD_TYPE) { + $this->assertSame('tag', $customfield->export_value()); + } + } + + $profilefielddata = $DB->get_field('user_info_field', 'param1', ['id' => $prodilefield->id]); + $this->assertNotEmpty($profilefielddata); + $this->assertTrue(in_array($tagname, explode("\n", $profilefielddata))); + + $rule = $DB->get_record('tool_dynamic_cohorts', ['name' => $tagname]); + $this->assertNotEmpty($rule); + $this->assertEquals($cohort->id, $rule->cohortid); + $this->assertEquals(1, $rule->enabled); + $conditions = $DB->get_records('tool_dynamic_cohorts_c', ['ruleid' => $rule->id]); + $this->assertCount(2, $conditions); + + $this->assertCount(1, $DB->get_records('enrol', ['courseid' => $course1->id, 'enrol' => 'cohort'])); + $enrol = $DB->get_record('enrol', ['courseid' => $course1->id, 'enrol' => 'cohort']); + $this->assertNotEmpty($enrol); + $this->assertEquals($cohort->id, $enrol->customint1); + + core_tag_tag::set_item_tags('core', 'course', $course2->id, \context_course::instance($course1->id), [$tagname]); + } +} From 7438c9f18f0b767d2f5a0a3f6b1d3701512ce9f3 Mon Sep 17 00:00:00 2001 From: Dmitrii Metelkin Date: Tue, 3 Sep 2024 20:32:01 +1000 Subject: [PATCH 02/10] issue #4: install dynamic cohorts as part of plugin CI --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d646a17..2669a5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,3 +9,4 @@ jobs: with: disable_behat: true disable_phpcpd: true + extra_plugin_runners: 'moodle-plugin-ci add-plugin --branch MOODLE_404_STABLE catalyst/moodle-tool_dynamic_cohorts' From d926542cc3e7a28e74cc22c457c27aebc5c515a4 Mon Sep 17 00:00:00 2001 From: Dmitrii Metelkin Date: Tue, 3 Sep 2024 20:51:57 +1000 Subject: [PATCH 03/10] issue #4: fix copyright --- lang/en/tool_enrolprofile.php | 2 +- tests/observer_test.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lang/en/tool_enrolprofile.php b/lang/en/tool_enrolprofile.php index ab312c1..1d5fe01 100644 --- a/lang/en/tool_enrolprofile.php +++ b/lang/en/tool_enrolprofile.php @@ -19,7 +19,7 @@ * * @package tool_enrolprofile * @category string - * @copyright 2024 Your Name + * @copyright 2024 Dmitrii Metelkin * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/tests/observer_test.php b/tests/observer_test.php index 4ae3b3c..728df3e 100644 --- a/tests/observer_test.php +++ b/tests/observer_test.php @@ -24,8 +24,8 @@ /** * Unit tests for observer class. * - * @package tool_dynamic_cohorts - * @copyright 2024 Catalyst IT + * @package tool_enrolprofile + * @copyright 2024 Dmitrii Metelkin * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * @covers \tool_enrolprofile\observer From 7a2cfd623d34277f620f1c67ecb29e1bfcfe534f Mon Sep 17 00:00:00 2001 From: Dmitrii Metelkin Date: Tue, 3 Sep 2024 21:20:33 +1000 Subject: [PATCH 04/10] issue #4: add an extra assertion --- tests/observer_test.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/observer_test.php b/tests/observer_test.php index 728df3e..24be36c 100644 --- a/tests/observer_test.php +++ b/tests/observer_test.php @@ -115,6 +115,8 @@ public function test_tag_added() { core_tag_tag::set_item_tags('core', 'course', $course1->id, \context_course::instance($course1->id), [$tagname]); $tag = $DB->get_record('tag', ['name' => $tagname]); + $this->assertNotEmpty($tag); + $cohort = $DB->get_record('cohort', ['name' => $tagname]); $this->assertNotEmpty($cohort); From 906bcf170f30f5eb6ce54b3b17e41edfb0a2dcb1 Mon Sep 17 00:00:00 2001 From: Dmitrii Metelkin Date: Tue, 3 Sep 2024 21:27:35 +1000 Subject: [PATCH 05/10] issue #4: add test for get_cohort_by_item --- classes/helper.php | 5 ++- tests/helper_test.php | 91 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 tests/helper_test.php diff --git a/classes/helper.php b/classes/helper.php index b5821a0..9600406 100644 --- a/classes/helper.php +++ b/classes/helper.php @@ -95,7 +95,10 @@ public static function get_cohort_by_item(int $itemid, string $itemtype): ?stdCl $name = 'customfield_' . $customfield->get_field()->get('shortname'); $cohortdata->$name = $customfield->export_value(); } - return $cohortdata->customfield_type == $itemtype && $cohortdata->customfield_id == $itemid; + $typefieled = 'customfield_' . helper::COHORT_FIELD_TYPE; + $idfieled = 'customfield_' . helper::COHORT_FIELD_ID; + + return $cohortdata->$typefieled == $itemtype && $cohortdata->$idfieled == $itemid; }); if (!empty($cohorts)) { diff --git a/tests/helper_test.php b/tests/helper_test.php new file mode 100644 index 0000000..ac51a76 --- /dev/null +++ b/tests/helper_test.php @@ -0,0 +1,91 @@ +. + +namespace tool_enrolprofile; + +use advanced_testcase; +use core_customfield\field_controller; + +/** + * Unit tests for helper class. + * + * @package tool_enrolprofile + * @copyright 2024 Dmitrii Metelkin + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * + * @covers \tool_enrolprofile\observer + */ +class helper_test extends advanced_testcase { + + /** + * Create cohort custom field for testing. + * + * @param string $shortname Field shortname + * @param string $datatype $field data type. + * + * @return field_controller + */ + protected function create_cohort_custom_field(string $shortname, string $datatype = 'text'): field_controller { + $fieldcategory = self::getDataGenerator()->create_custom_field_category([ + 'component' => 'core_cohort', + 'area' => 'cohort', + 'name' => 'Other fields', + ]); + + return self::getDataGenerator()->create_custom_field([ + 'shortname' => $shortname, + 'name' => 'Custom field ' . $shortname, + 'type' => $datatype, + 'categoryid' => $fieldcategory->get('id'), + ]); + } + + /** + * Test getting a cohort by item id. + */ + public function test_get_cohort_by_item(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $this->create_cohort_custom_field(helper::COHORT_FIELD_ID); + $this->create_cohort_custom_field(helper::COHORT_FIELD_TYPE); + + $typefieled = 'customfield_' . helper::COHORT_FIELD_TYPE; + $idfieled = 'customfield_' . helper::COHORT_FIELD_ID; + + $cohort1 = self::getDataGenerator()->create_cohort([ + $typefieled => 'tag', + $idfieled => 12, + ]); + + $cohort2 = self::getDataGenerator()->create_cohort([ + $typefieled => 'tag', + $idfieled => 13, + ]); + + $cohort3 = self::getDataGenerator()->create_cohort([ + $typefieled => 'course', + $idfieled => 13, + ]); + + $this->assertNull(helper::get_cohort_by_item(1, 'tag')); + $this->assertNull(helper::get_cohort_by_item(1, 'course')); + + $this->assertEquals($cohort1->id, helper::get_cohort_by_item(12, 'tag')->id); + $this->assertEquals($cohort2->id, helper::get_cohort_by_item(13, 'tag')->id); + $this->assertEquals($cohort3->id, helper::get_cohort_by_item(13, 'course')->id); + } +} From 003d2391d5e35dfdcb9957b5a0f697160310fcd1 Mon Sep 17 00:00:00 2001 From: Dmitrii Metelkin Date: Tue, 3 Sep 2024 21:42:53 +1000 Subject: [PATCH 06/10] issue #4: fix a bug with tag name --- tests/observer_test.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/observer_test.php b/tests/observer_test.php index 24be36c..bb3e577 100644 --- a/tests/observer_test.php +++ b/tests/observer_test.php @@ -107,14 +107,14 @@ public function test_tag_added() { $this->assertEmpty($DB->get_record('cohort', ['name' => $tagname])); $this->assertEmpty($DB->get_field('user_info_field', 'param1', ['id' => $prodilefield->id])); - $this->assertEmpty($DB->get_record('tag', ['name' => $tagname])); + $this->assertEmpty($DB->get_record('tag', ['rawname' => $tagname])); $this->assertEmpty($DB->get_record('tool_dynamic_cohorts', ['name' => $tagname])); $this->assertEmpty($DB->get_record('tool_dynamic_cohorts', ['name' => $tagname])); $this->assertCount(0, $DB->get_records('enrol', ['courseid' => $course1->id, 'enrol' => 'cohort'])); - core_tag_tag::set_item_tags('core', 'course', $course1->id, \context_course::instance($course1->id), [$tagname]); + core_tag_tag::set_item_tags('core', 'course', $course1->id, context_course::instance($course1->id), [$tagname]); - $tag = $DB->get_record('tag', ['name' => $tagname]); + $tag = $DB->get_record('tag', ['rawname' => $tagname]); $this->assertNotEmpty($tag); $cohort = $DB->get_record('cohort', ['name' => $tagname]); From 6fc367d544cf2495e677491a660638ba273e62c5 Mon Sep 17 00:00:00 2001 From: Dmitrii Metelkin Date: Tue, 3 Sep 2024 21:46:54 +1000 Subject: [PATCH 07/10] issue #4: move cohort lib include to helper --- classes/helper.php | 4 ++++ classes/observer.php | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/classes/helper.php b/classes/helper.php index 9600406..470b8ae 100644 --- a/classes/helper.php +++ b/classes/helper.php @@ -22,6 +22,10 @@ use tool_dynamic_cohorts\condition_base; use tool_dynamic_cohorts\rule; +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/cohort/lib.php'); + /** * Helper class. * diff --git a/classes/observer.php b/classes/observer.php index c5024ac..c2ba8be 100644 --- a/classes/observer.php +++ b/classes/observer.php @@ -20,10 +20,6 @@ use stdClass; use context_system; -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/cohort/lib.php'); - /** * Event observer class. * From d7906c8e8807c55d0ced62262eff8bd4c4ea6593 Mon Sep 17 00:00:00 2001 From: Dmitrii Metelkin Date: Wed, 4 Sep 2024 19:28:06 +1000 Subject: [PATCH 08/10] issue #5: process a new course --- classes/helper.php | 52 +++++++++++---- classes/observer.php | 44 +++++-------- db/events.php | 4 ++ tests/observer_test.php | 138 ++++++++++++++++++++++++++++++++++------ 4 files changed, 179 insertions(+), 59 deletions(-) diff --git a/classes/helper.php b/classes/helper.php index 470b8ae..b64c6bf 100644 --- a/classes/helper.php +++ b/classes/helper.php @@ -36,24 +36,19 @@ class helper { /** - * Profile field category. + * Tag item type. */ - public const PROFILE_CATEGORY = 'Profile field'; + public const ITEM_TYPE_TAG = 'tag'; /** - * Field shortname - */ - public const FIELD_TAG = 'tag'; - - /** - * Field shortname + * Category item type. */ - public const FIELD_CATEGORY = 'category'; + public const ITEM_TYPE_CATEGORY = 'category'; /** - * Field shortname + * Course item type. */ - public const FIELD_COURSE = 'course'; + public const ITEM_TYPE_COURSE = 'course'; /** * Field shortname @@ -61,7 +56,7 @@ class helper { public const FIELD_ENROLLED_UNTIL = 'enrolleduntil'; /** - * Course fiel name. + * Course field name. */ public const COURSE_NAME = 'fullname'; @@ -80,6 +75,39 @@ class helper { */ public const STUDENT_ROLE = 'student'; + /** + * Set up configuration item. + * + * @param stdClass $course Course to set up it for. + * @param int $itemid Item ID number + * @param string $itemtype Item type (tag, course, category)/ + * @param string $itemname Item name. + */ + public static function set_up_item(stdClass $course, int $itemid, string $itemtype, string $itemname): void { + $cohort = helper::get_cohort_by_item($itemid, $itemtype); + + if (empty($cohort)) { + $cohort = new stdClass(); + $cohort->contextid = context_system::instance()->id; + $cohort->name = $itemname; + $cohort->idnumber = $itemname; + $cohort->description = ucfirst($itemtype) . ' related'; + $typefieled = 'customfield_' . helper::COHORT_FIELD_TYPE; + $cohort->$typefieled = $itemtype; + $idfieled = 'customfield_' . helper::COHORT_FIELD_ID; + $cohort->$idfieled = $itemid; + + // Create a new cohort. + $cohort->id = helper::add_cohort($cohort); + } + + // Create a dynamic cohort rule associated with this cohort. + helper::add_rule($cohort, $itemtype); + // Add a tag to a custom profile field. + helper::update_profile_field($itemtype, $itemname); + // If yes, create enrolment method for the cohort for a given course. + helper::add_enrolment_method($course, $cohort); + } /** * Get cohort by provided item type and item id. diff --git a/classes/observer.php b/classes/observer.php index c2ba8be..e3ad72f 100644 --- a/classes/observer.php +++ b/classes/observer.php @@ -16,9 +16,8 @@ namespace tool_enrolprofile; +use core\event\course_created; use core\event\tag_added; -use stdClass; -use context_system; /** * Event observer class. @@ -43,36 +42,23 @@ public static function tag_added(tag_added $event): void { $tagid = $event->other['tagid']; $tagname = $event->other['tagrawname']; + $course = get_course($event->other['itemid']); - $courseid = $event->other['itemid']; - $course = get_course($courseid); + helper::set_up_item($course, $tagid, 'tag', $tagname); + } - $cohort = new stdClass(); - $cohort->contextid = context_system::instance()->id; - $cohort->name = $tagname; - $cohort->idnumber = $tagname; - $cohort->description = 'Tag related'; - $typefieled = 'customfield_' . helper::COHORT_FIELD_TYPE; - $cohort->$typefieled = 'tag'; - $idfieled = 'customfield_' . helper::COHORT_FIELD_ID; - $cohort->$idfieled = $tagid; + /** + * Process course_created event. + * + * @param course_created $event The event. + */ + public static function course_created(course_created $event): void { + global $DB; - // Check if cohort already exists. - $existingcohort = helper::get_cohort_by_item($tagid, 'tag'); + $course = get_course($event->courseid); + helper::set_up_item($course, $course->id, 'course', $course->{helper::COURSE_NAME}); - // If not. - if (empty($existingcohort)) { - // Create a new cohort. - $cohort->id = helper::add_cohort($cohort); - // Create a dynamic cohort rule associated with this cohort. - helper::add_rule($cohort, helper::FIELD_TAG); - // Add a tag to a custom profile field. - helper::update_profile_field(helper::FIELD_TAG, $tagname); - // Create enrolment method for the cohort for a given course. - helper::add_enrolment_method($course, $cohort); - } else { - // If yes, create enrolment method for the cohort for a given course. - helper::add_enrolment_method($course, $existingcohort); - } + $category = $DB->get_record('course_categories', ['id' => $course->category]); + helper::set_up_item($course, $category->id, 'category', $category->name); } } diff --git a/db/events.php b/db/events.php index 0b0db27..5744525 100644 --- a/db/events.php +++ b/db/events.php @@ -29,4 +29,8 @@ 'eventname' => '\core\event\tag_added', 'callback' => '\tool_enrolprofile\observer::tag_added', ], + [ + 'eventname' => '\core\event\course_created', + 'callback' => '\tool_enrolprofile\observer::course_created', + ], ]; diff --git a/tests/observer_test.php b/tests/observer_test.php index bb3e577..93ae625 100644 --- a/tests/observer_test.php +++ b/tests/observer_test.php @@ -32,6 +32,41 @@ */ class observer_test extends advanced_testcase { + /** + * Tag profile field for testing. + * @var \stdClass + */ + protected $tagprofilefield; + + /** + * Course profile field for testing. + * @var \stdClass + */ + protected $courseprofilefield; + + /** + * Category profile field for testing. + * @var \stdClass + */ + protected $categoryprofilefield; + + /** + * Set up before every test. + * + * @return void + */ + public function setUp(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $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->create_cohort_custom_field(helper::COHORT_FIELD_ID); + $this->create_cohort_custom_field(helper::COHORT_FIELD_TYPE); + } + /** * A helper function to create a custom profile field. * @@ -95,24 +130,18 @@ protected function create_cohort_custom_field(string $shortname, string $datatyp public function test_tag_added() { global $DB; - $this->resetAfterTest(); - $this->setAdminUser(); - - $prodilefield = $this->add_user_profile_field(helper::FIELD_TAG, 'autocomplete'); - $this->create_cohort_custom_field(helper::COHORT_FIELD_ID); - $this->create_cohort_custom_field(helper::COHORT_FIELD_TYPE); - $course1 = $this->getDataGenerator()->create_course(); - $course2 = $this->getDataGenerator()->create_course(); + $course = $this->getDataGenerator()->create_course(); $tagname = 'A tag'; $this->assertEmpty($DB->get_record('cohort', ['name' => $tagname])); - $this->assertEmpty($DB->get_field('user_info_field', 'param1', ['id' => $prodilefield->id])); + $this->assertEmpty($DB->get_field('user_info_field', 'param1', ['id' => $this->tagprofilefield->id])); $this->assertEmpty($DB->get_record('tag', ['rawname' => $tagname])); $this->assertEmpty($DB->get_record('tool_dynamic_cohorts', ['name' => $tagname])); - $this->assertEmpty($DB->get_record('tool_dynamic_cohorts', ['name' => $tagname])); - $this->assertCount(0, $DB->get_records('enrol', ['courseid' => $course1->id, 'enrol' => 'cohort'])); - core_tag_tag::set_item_tags('core', 'course', $course1->id, context_course::instance($course1->id), [$tagname]); + // Should be already course and category cohorts. + $this->assertCount(2, $DB->get_records('enrol', ['courseid' => $course->id, 'enrol' => 'cohort'])); + + core_tag_tag::set_item_tags('core', 'course', $course->id, context_course::instance($course->id), [$tagname]); $tag = $DB->get_record('tag', ['rawname' => $tagname]); $this->assertNotEmpty($tag); @@ -120,7 +149,7 @@ public function test_tag_added() { $cohort = $DB->get_record('cohort', ['name' => $tagname]); $this->assertNotEmpty($cohort); - $cohort = cohort_get_cohort($cohort->id, context_course::instance($course1->id), true); + $cohort = cohort_get_cohort($cohort->id, context_course::instance($course->id), true); foreach ($cohort->customfields as $customfield) { if ($customfield->get_field()->get('shortname') == helper::COHORT_FIELD_ID) { $this->assertSame($tag->id, $customfield->export_value()); @@ -130,7 +159,7 @@ public function test_tag_added() { } } - $profilefielddata = $DB->get_field('user_info_field', 'param1', ['id' => $prodilefield->id]); + $profilefielddata = $DB->get_field('user_info_field', 'param1', ['id' => $this->tagprofilefield->id]); $this->assertNotEmpty($profilefielddata); $this->assertTrue(in_array($tagname, explode("\n", $profilefielddata))); @@ -141,11 +170,84 @@ public function test_tag_added() { $conditions = $DB->get_records('tool_dynamic_cohorts_c', ['ruleid' => $rule->id]); $this->assertCount(2, $conditions); - $this->assertCount(1, $DB->get_records('enrol', ['courseid' => $course1->id, 'enrol' => 'cohort'])); - $enrol = $DB->get_record('enrol', ['courseid' => $course1->id, 'enrol' => 'cohort']); + $this->assertCount(3, $DB->get_records('enrol', ['courseid' => $course->id, 'enrol' => 'cohort'])); + $enrol = $DB->get_record('enrol', ['courseid' => $course->id, 'enrol' => 'cohort', 'customint1' => $cohort->id]); + $this->assertNotEmpty($enrol); + } + + /** + * Check logic when creating a course. + * @return void + */ + public function test_course_created() { + global $DB; + + $coursename = 'Course name'; + $this->assertEmpty($DB->get_record('cohort', ['name' => $coursename])); + $this->assertEmpty($DB->get_field('user_info_field', 'param1', ['id' => $this->courseprofilefield->id])); + $this->assertEmpty($DB->get_record('tag', ['rawname' => $coursename])); + $this->assertEmpty($DB->get_record('tool_dynamic_cohorts', ['name' => $coursename])); + + $course = $this->getDataGenerator()->create_course(['fullname' => $coursename]); + + // Should be course and category cohorts. + $this->assertCount(2, $DB->get_records('enrol', ['courseid' => $course->id, 'enrol' => 'cohort'])); + + // Check everything about course cohort. + $coursecohort = $DB->get_record('cohort', ['name' => $coursename]); + $this->assertNotEmpty($coursecohort); + + $cohort = cohort_get_cohort($coursecohort->id, context_course::instance($course->id), true); + foreach ($cohort->customfields as $customfield) { + if ($customfield->get_field()->get('shortname') == helper::COHORT_FIELD_ID) { + $this->assertSame($course->id, $customfield->export_value()); + } + if ($customfield->get_field()->get('shortname') == helper::COHORT_FIELD_TYPE) { + $this->assertSame('course', $customfield->export_value()); + } + } + + $profilefielddata = $DB->get_field('user_info_field', 'param1', ['id' => $this->courseprofilefield->id]); + $this->assertNotEmpty($profilefielddata); + $this->assertTrue(in_array($coursename, explode("\n", $profilefielddata))); + + $rule = $DB->get_record('tool_dynamic_cohorts', ['name' => $coursename]); + $this->assertNotEmpty($rule); + $this->assertEquals($cohort->id, $rule->cohortid); + $this->assertEquals(1, $rule->enabled); + $conditions = $DB->get_records('tool_dynamic_cohorts_c', ['ruleid' => $rule->id]); + $this->assertCount(2, $conditions); + + $enrol = $DB->get_record('enrol', ['courseid' => $course->id, 'enrol' => 'cohort', 'customint1' => $cohort->id]); $this->assertNotEmpty($enrol); - $this->assertEquals($cohort->id, $enrol->customint1); - core_tag_tag::set_item_tags('core', 'course', $course2->id, \context_course::instance($course1->id), [$tagname]); + // Check everything about category cohort. + $category = $DB->get_record('course_categories', ['id' => $course->category]); + $categorycohort = $DB->get_record('cohort', ['name' => $category->name]); + $this->assertNotEmpty($categorycohort); + + $cohort = cohort_get_cohort($categorycohort->id, context_course::instance($category->id), true); + foreach ($cohort->customfields as $customfield) { + if ($customfield->get_field()->get('shortname') == helper::COHORT_FIELD_ID) { + $this->assertSame($category->id, $customfield->export_value()); + } + if ($customfield->get_field()->get('shortname') == helper::COHORT_FIELD_TYPE) { + $this->assertSame('category', $customfield->export_value()); + } + } + + $profilefielddata = $DB->get_field('user_info_field', 'param1', ['id' => $this->categoryprofilefield->id]); + $this->assertNotEmpty($profilefielddata); + $this->assertTrue(in_array($category->name, explode("\n", $profilefielddata))); + + $rule = $DB->get_record('tool_dynamic_cohorts', ['name' => $category->name]); + $this->assertNotEmpty($rule); + $this->assertEquals($cohort->id, $rule->cohortid); + $this->assertEquals(1, $rule->enabled); + $conditions = $DB->get_records('tool_dynamic_cohorts_c', ['ruleid' => $rule->id]); + $this->assertCount(2, $conditions); + + $enrol = $DB->get_record('enrol', ['courseid' => $course->id, 'enrol' => 'cohort', 'customint1' => $cohort->id]); + $this->assertNotEmpty($enrol); } } From 1819a94367eecce9f78ed866a282af37088d7b28 Mon Sep 17 00:00:00 2001 From: Dmitrii Metelkin Date: Wed, 4 Sep 2024 20:25:29 +1000 Subject: [PATCH 09/10] issue #4: use self instead of helper --- classes/helper.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/classes/helper.php b/classes/helper.php index b64c6bf..9f84ea4 100644 --- a/classes/helper.php +++ b/classes/helper.php @@ -84,7 +84,7 @@ class helper { * @param string $itemname Item name. */ public static function set_up_item(stdClass $course, int $itemid, string $itemtype, string $itemname): void { - $cohort = helper::get_cohort_by_item($itemid, $itemtype); + $cohort = self::get_cohort_by_item($itemid, $itemtype); if (empty($cohort)) { $cohort = new stdClass(); @@ -92,21 +92,21 @@ public static function set_up_item(stdClass $course, int $itemid, string $itemty $cohort->name = $itemname; $cohort->idnumber = $itemname; $cohort->description = ucfirst($itemtype) . ' related'; - $typefieled = 'customfield_' . helper::COHORT_FIELD_TYPE; + $typefieled = 'customfield_' . self::COHORT_FIELD_TYPE; $cohort->$typefieled = $itemtype; - $idfieled = 'customfield_' . helper::COHORT_FIELD_ID; + $idfieled = 'customfield_' . self::COHORT_FIELD_ID; $cohort->$idfieled = $itemid; // Create a new cohort. - $cohort->id = helper::add_cohort($cohort); + $cohort->id = self::add_cohort($cohort); } // Create a dynamic cohort rule associated with this cohort. - helper::add_rule($cohort, $itemtype); + self::add_rule($cohort, $itemtype); // Add a tag to a custom profile field. - helper::update_profile_field($itemtype, $itemname); + self::update_profile_field($itemtype, $itemname); // If yes, create enrolment method for the cohort for a given course. - helper::add_enrolment_method($course, $cohort); + self::add_enrolment_method($course, $cohort); } /** @@ -127,8 +127,8 @@ public static function get_cohort_by_item(int $itemid, string $itemtype): ?stdCl $name = 'customfield_' . $customfield->get_field()->get('shortname'); $cohortdata->$name = $customfield->export_value(); } - $typefieled = 'customfield_' . helper::COHORT_FIELD_TYPE; - $idfieled = 'customfield_' . helper::COHORT_FIELD_ID; + $typefieled = 'customfield_' . self::COHORT_FIELD_TYPE; + $idfieled = 'customfield_' . self::COHORT_FIELD_ID; return $cohortdata->$typefieled == $itemtype && $cohortdata->$idfieled == $itemid; }); From cb6588a8e3efaa2a053785cbcca1d44a8a25e461 Mon Sep 17 00:00:00 2001 From: Dmitrii Metelkin Date: Wed, 4 Sep 2024 21:35:04 +1000 Subject: [PATCH 10/10] issue #6: process a new category --- classes/helper.php | 14 +++++++---- classes/observer.php | 19 ++++++++++++--- db/events.php | 4 ++++ tests/observer_test.php | 52 ++++++++++++++++++++++++++++++++++------- 4 files changed, 73 insertions(+), 16 deletions(-) diff --git a/classes/helper.php b/classes/helper.php index 9f84ea4..adbb414 100644 --- a/classes/helper.php +++ b/classes/helper.php @@ -78,12 +78,13 @@ class helper { /** * Set up configuration item. * - * @param stdClass $course Course to set up it for. * @param int $itemid Item ID number - * @param string $itemtype Item type (tag, course, category)/ + * @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. + * @return void */ - public static function set_up_item(stdClass $course, int $itemid, string $itemtype, string $itemname): void { + public static function set_up_item(int $itemid, string $itemtype, string $itemname, ?stdClass $course = null): void { $cohort = self::get_cohort_by_item($itemid, $itemtype); if (empty($cohort)) { @@ -105,8 +106,11 @@ public static function set_up_item(stdClass $course, int $itemid, string $itemty self::add_rule($cohort, $itemtype); // Add a tag to a custom profile field. self::update_profile_field($itemtype, $itemname); - // If yes, create enrolment method for the cohort for a given course. - self::add_enrolment_method($course, $cohort); + + // Create enrolment method for the cohort for a given course. + if (!empty($course)) { + self::add_enrolment_method($course, $cohort); + } } /** diff --git a/classes/observer.php b/classes/observer.php index e3ad72f..094795b 100644 --- a/classes/observer.php +++ b/classes/observer.php @@ -16,6 +16,7 @@ namespace tool_enrolprofile; +use core\event\course_category_created; use core\event\course_created; use core\event\tag_added; @@ -44,7 +45,7 @@ public static function tag_added(tag_added $event): void { $tagname = $event->other['tagrawname']; $course = get_course($event->other['itemid']); - helper::set_up_item($course, $tagid, 'tag', $tagname); + helper::set_up_item($tagid, helper::ITEM_TYPE_TAG, $tagname, $course); } /** @@ -56,9 +57,21 @@ public static function course_created(course_created $event): void { global $DB; $course = get_course($event->courseid); - helper::set_up_item($course, $course->id, 'course', $course->{helper::COURSE_NAME}); + helper::set_up_item($course->id, helper::ITEM_TYPE_COURSE, $course->{helper::COURSE_NAME}, $course); $category = $DB->get_record('course_categories', ['id' => $course->category]); - helper::set_up_item($course, $category->id, 'category', $category->name); + helper::set_up_item($category->id, helper::ITEM_TYPE_CATEGORY, $category->name, $course); + } + + /** + * Process course_category_created event. + * + * @param course_category_created $event The event. + */ + public static function course_category_created(course_category_created $event): void { + global $DB; + + $category = $DB->get_record('course_categories', ['id' => $event->objectid]); + helper::set_up_item($category->id, helper::ITEM_TYPE_CATEGORY, $category->name); } } diff --git a/db/events.php b/db/events.php index 5744525..8d5dd1e 100644 --- a/db/events.php +++ b/db/events.php @@ -33,4 +33,8 @@ 'eventname' => '\core\event\course_created', 'callback' => '\tool_enrolprofile\observer::course_created', ], + [ + 'eventname' => '\core\event\course_category_created', + 'callback' => '\tool_enrolprofile\observer::course_category_created', + ], ]; diff --git a/tests/observer_test.php b/tests/observer_test.php index 93ae625..ee5e586 100644 --- a/tests/observer_test.php +++ b/tests/observer_test.php @@ -125,9 +125,8 @@ protected function create_cohort_custom_field(string $shortname, string $datatyp /** * Check logic when adding a tag. - * @return void */ - public function test_tag_added() { + public function test_tag_added(): void { global $DB; $course = $this->getDataGenerator()->create_course(); @@ -155,7 +154,7 @@ public function test_tag_added() { $this->assertSame($tag->id, $customfield->export_value()); } if ($customfield->get_field()->get('shortname') == helper::COHORT_FIELD_TYPE) { - $this->assertSame('tag', $customfield->export_value()); + $this->assertSame(helper::ITEM_TYPE_TAG, $customfield->export_value()); } } @@ -177,15 +176,13 @@ public function test_tag_added() { /** * Check logic when creating a course. - * @return void */ - public function test_course_created() { + public function test_course_created(): void { global $DB; $coursename = 'Course name'; $this->assertEmpty($DB->get_record('cohort', ['name' => $coursename])); $this->assertEmpty($DB->get_field('user_info_field', 'param1', ['id' => $this->courseprofilefield->id])); - $this->assertEmpty($DB->get_record('tag', ['rawname' => $coursename])); $this->assertEmpty($DB->get_record('tool_dynamic_cohorts', ['name' => $coursename])); $course = $this->getDataGenerator()->create_course(['fullname' => $coursename]); @@ -203,7 +200,7 @@ public function test_course_created() { $this->assertSame($course->id, $customfield->export_value()); } if ($customfield->get_field()->get('shortname') == helper::COHORT_FIELD_TYPE) { - $this->assertSame('course', $customfield->export_value()); + $this->assertSame(helper::ITEM_TYPE_COURSE, $customfield->export_value()); } } @@ -232,7 +229,7 @@ public function test_course_created() { $this->assertSame($category->id, $customfield->export_value()); } if ($customfield->get_field()->get('shortname') == helper::COHORT_FIELD_TYPE) { - $this->assertSame('category', $customfield->export_value()); + $this->assertSame(helper::ITEM_TYPE_CATEGORY, $customfield->export_value()); } } @@ -250,4 +247,43 @@ public function test_course_created() { $enrol = $DB->get_record('enrol', ['courseid' => $course->id, 'enrol' => 'cohort', 'customint1' => $cohort->id]); $this->assertNotEmpty($enrol); } + + /** + * Check logic when creating a course category. + */ + public function test_course_category_created(): void { + global $DB; + + $categoryname = 'Category name'; + $this->assertEmpty($DB->get_record('cohort', ['name' => $categoryname])); + $this->assertEmpty($DB->get_field('user_info_field', 'param1', ['id' => $this->categoryprofilefield->id])); + $this->assertEmpty($DB->get_record('tool_dynamic_cohorts', ['name' => $categoryname])); + + $category = $this->getDataGenerator()->create_category(['name' => $categoryname]); + + // Check everything about category cohort. + $categorycohort = $DB->get_record('cohort', ['name' => $category->name]); + $this->assertNotEmpty($categorycohort); + + $cohort = cohort_get_cohort($categorycohort->id, \context_coursecat::instance($category->id), true); + foreach ($cohort->customfields as $customfield) { + if ($customfield->get_field()->get('shortname') == helper::COHORT_FIELD_ID) { + $this->assertSame($category->id, $customfield->export_value()); + } + if ($customfield->get_field()->get('shortname') == helper::COHORT_FIELD_TYPE) { + $this->assertSame(helper::ITEM_TYPE_CATEGORY, $customfield->export_value()); + } + } + + $profilefielddata = $DB->get_field('user_info_field', 'param1', ['id' => $this->categoryprofilefield->id]); + $this->assertNotEmpty($profilefielddata); + $this->assertTrue(in_array($categoryname, explode("\n", $profilefielddata))); + + $rule = $DB->get_record('tool_dynamic_cohorts', ['name' => $categoryname]); + $this->assertNotEmpty($rule); + $this->assertEquals($cohort->id, $rule->cohortid); + $this->assertEquals(1, $rule->enabled); + $conditions = $DB->get_records('tool_dynamic_cohorts_c', ['ruleid' => $rule->id]); + $this->assertCount(2, $conditions); + } }