From c11d4284be616210d1a9714527d92e5278381e72 Mon Sep 17 00:00:00 2001 From: Dmitrii Metelkin Date: Tue, 7 Mar 2023 13:28:02 +1100 Subject: [PATCH] MDL-77130 cohort: add custom fields --- admin/settings/users.php | 10 +- cohort/classes/customfield/cohort_handler.php | 124 ++++++++++ cohort/customfield.php | 40 ++++ cohort/edit_form.php | 16 ++ cohort/externallib.php | 113 +++++++++ cohort/lib.php | 114 +++++++-- cohort/tests/behat/customfields.feature | 39 ++++ .../tests/customfield/cohort_handler_test.php | 182 +++++++++++++++ cohort/tests/externallib_test.php | 172 ++++++++++++-- cohort/tests/lib_test.php | 216 +++++++++++++++++- cohort/upgrade.txt | 3 + lang/en/admin.php | 1 + lang/en/role.php | 1 + lib/db/access.php | 7 + lib/phpunit/classes/util.php | 3 + version.php | 2 +- 16 files changed, 1008 insertions(+), 35 deletions(-) create mode 100644 cohort/classes/customfield/cohort_handler.php create mode 100644 cohort/customfield.php create mode 100644 cohort/tests/behat/customfields.feature create mode 100644 cohort/tests/customfield/cohort_handler_test.php diff --git a/admin/settings/users.php b/admin/settings/users.php index 92c483af2ee72..60ddda4590887 100644 --- a/admin/settings/users.php +++ b/admin/settings/users.php @@ -101,7 +101,15 @@ $ADMIN->add('accounts', new admin_externalpage('profilefields', new lang_string('profilefields','admin'), "$CFG->wwwroot/user/profile/index.php", 'moodle/site:config')); $ADMIN->add('accounts', new admin_externalpage('cohorts', new lang_string('cohorts', 'cohort'), $CFG->wwwroot . '/cohort/index.php', array('moodle/cohort:manage', 'moodle/cohort:view'))); - + $ADMIN->add( + 'accounts', + new admin_externalpage( + 'cohort_customfield', + new lang_string('cohort_customfield', 'admin'), + $CFG->wwwroot . '/cohort/customfield.php', + ['moodle/cohort:configurecustomfields'] + ) + ); // Stuff under the "roles" subcategory. diff --git a/cohort/classes/customfield/cohort_handler.php b/cohort/classes/customfield/cohort_handler.php new file mode 100644 index 0000000000000..99d8545ec5e33 --- /dev/null +++ b/cohort/classes/customfield/cohort_handler.php @@ -0,0 +1,124 @@ +. + +namespace core_cohort\customfield; + +use core_customfield\handler; +use core_customfield\field_controller; + +/** + * Cohort handler for custom fields. + * + * @package core_cohort + * @copyright 2023 Dmitrii Metelkin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cohort_handler extends handler { + + /** + * @var cohort_handler + */ + static protected $singleton; + + /** + * Returns a singleton. + * + * @param int $itemid + * @return \core_customfield\handler + */ + public static function create(int $itemid = 0): handler { + if (static::$singleton === null) { + self::$singleton = new static(0); + } + return self::$singleton; + } + + /** + * Run reset code after unit tests to reset the singleton usage. + */ + public static function reset_caches(): void { + if (!PHPUNIT_TEST) { + throw new \coding_exception('This feature is only intended for use in unit tests'); + } + + static::$singleton = null; + } + + /** + * The current user can configure custom fields on this component. + * + * @return bool true if the current can configure custom fields, false otherwise + */ + public function can_configure(): bool { + return has_capability('moodle/cohort:configurecustomfields', $this->get_configuration_context()); + } + + /** + * The current user can edit custom fields on the given cohort. + * + * @param field_controller $field + * @param int $instanceid id of the cohort to test edit permission + * @return bool true if the current can edit custom field, false otherwise + */ + public function can_edit(field_controller $field, int $instanceid = 0): bool { + return has_capability('moodle/cohort:manage', $this->get_instance_context($instanceid)); + } + + /** + * The current user can view custom fields on the given cohort. + * + * @param field_controller $field + * @param int $instanceid id of the cohort to test edit permission + * @return bool true if the current can view custom field, false otherwise + */ + public function can_view(field_controller $field, int $instanceid): bool { + return has_any_capability(['moodle/cohort:manage', 'moodle/cohort:view'], $this->get_instance_context($instanceid)); + } + + /** + * Context that should be used for new categories created by this handler. + * + * @return \context the context for configuration + */ + public function get_configuration_context(): \context { + return \context_system::instance(); + } + + /** + * URL for configuration of the fields on this handler. + * + * @return \moodle_url The URL to configure custom fields for this component + */ + public function get_configuration_url(): \moodle_url { + return new \moodle_url('/cohort/customfield.php'); + } + + /** + * Returns the context for the data associated with the given instanceid. + * + * @param int $instanceid id of the record to get the context for + * @return \context the context for the given record + */ + public function get_instance_context(int $instanceid = 0): \context { + global $DB; + if ($instanceid > 0) { + $cohort = $DB->get_record('cohort', ['id' => $instanceid], '*', MUST_EXIST); + return \context::instance_by_id($cohort->contextid, MUST_EXIST); + } else { + return \context_system::instance(); + } + } +} diff --git a/cohort/customfield.php b/cohort/customfield.php new file mode 100644 index 0000000000000..352f6a1da5327 --- /dev/null +++ b/cohort/customfield.php @@ -0,0 +1,40 @@ +. + +/** + * Manage cohort custom fields + * + * @package core_cohort + * @copyright 2023 Dmitrii Metelkin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use core_cohort\customfield\cohort_handler; +use core_customfield\output\management; + +require_once('../config.php'); +require_once($CFG->libdir . '/adminlib.php'); + +admin_externalpage_setup('cohort_customfield'); + +$output = $PAGE->get_renderer('core_customfield'); +$handler = cohort_handler::create(); +$outputpage = new management($handler); + +echo $output->header(), + $output->heading(new lang_string('cohort_customfield', 'admin')), + $output->render($outputpage), + $output->footer(); diff --git a/cohort/edit_form.php b/cohort/edit_form.php index 55e7a54284de7..9c5edc856b65f 100644 --- a/cohort/edit_form.php +++ b/cohort/edit_form.php @@ -69,8 +69,12 @@ public function definition() { $mform->setType('returnurl', PARAM_LOCALURL); } + $handler = core_cohort\customfield\cohort_handler::create(); + $handler->instance_form_definition($mform, empty($cohort->id) ? 0 : $cohort->id); + $this->add_action_buttons(); + $handler->instance_form_before_set_data($cohort); $this->set_data($cohort); } @@ -97,6 +101,9 @@ public function validation($data, $files) { } } + $handler = core_cohort\customfield\cohort_handler::create(); + $errors = array_merge($errors, $handler->instance_form_validation($data, $files)); + return $errors; } @@ -118,5 +125,14 @@ protected function get_category_options($currentcontextid) { } return $options; } + + /** + * Apply a logic after data is set. + */ + public function definition_after_data() { + $cohortid = $this->_form->getElementValue('id'); + $handler = core_cohort\customfield\cohort_handler::create(); + $handler->instance_form_definition_after_data($this->_form, empty($cohortid) ? 0 : $cohortid); + } } diff --git a/cohort/externallib.php b/cohort/externallib.php index 74a32fe7fd08a..6a31ed39e29f1 100644 --- a/cohort/externallib.php +++ b/cohort/externallib.php @@ -23,6 +23,10 @@ use core_external\external_warnings; use core_external\util; +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/cohort/lib.php'); + /** * External cohort API * @@ -62,6 +66,7 @@ public static function create_cohorts_parameters() { 'the cohort theme. The allowcohortthemes setting must be enabled on Moodle', VALUE_OPTIONAL ), + 'customfields' => self::build_custom_fields_parameters_structure(), ) ) ) @@ -128,6 +133,16 @@ public static function create_cohorts($cohorts) { // Validate format. $cohort->descriptionformat = util::validate_format($cohort->descriptionformat); + + // Custom fields. + if (!empty($cohort->customfields)) { + foreach ($cohort->customfields as $field) { + $fieldname = self::build_custom_field_name($field['shortname']); + $cohort->{$fieldname} = $field['value']; + } + unset($cohort->customfields); + } + $cohort->id = cohort_add_cohort($cohort); list($cohort->description, $cohort->descriptionformat) = @@ -249,10 +264,14 @@ public static function get_cohorts($cohortids = array()) { if (empty($cohortids)) { $cohorts = $DB->get_records('cohort'); + if (!empty($cohorts)) { + $cohortids = array_keys($cohorts); + } } else { $cohorts = $DB->get_records_list('cohort', 'id', $params['cohortids']); } + $customfieldsdata = self::get_custom_fields_data($cohortids); $cohortsinfo = array(); foreach ($cohorts as $cohort) { // Now security checks. @@ -274,6 +293,7 @@ public static function get_cohorts($cohortids = array()) { \core_external\util::format_text($cohort->description, $cohort->descriptionformat, $context, 'cohort', 'description', $cohort->id); + $cohort->customfields = !empty($customfieldsdata[$cohort->id]) ? $customfieldsdata[$cohort->id] : []; $cohortsinfo[] = (array) $cohort; } return $cohortsinfo; @@ -297,6 +317,7 @@ public static function get_cohorts_returns() { 'descriptionformat' => new external_format_value('description'), 'visible' => new external_value(PARAM_BOOL, 'cohort visible'), 'theme' => new external_value(PARAM_THEME, 'cohort theme', VALUE_OPTIONAL), + 'customfields' => self::build_custom_fields_returns_structure(), ) ) ); @@ -391,6 +412,12 @@ public static function search_cohorts($query, $context, $includes = 'parents', $ } $cohorts = array(); + + if (!empty($results)) { + $cohortids = array_keys($results); + $customfieldsdata = self::get_custom_fields_data($cohortids); + } + foreach ($results as $key => $cohort) { $cohortcontext = context::instance_by_id($cohort->contextid); @@ -410,6 +437,8 @@ public static function search_cohorts($query, $context, $includes = 'parents', $ \core_external\util::format_text($cohort->description, $cohort->descriptionformat, $cohortcontext, 'cohort', 'description', $cohort->id); + $cohort->customfields = !empty($customfieldsdata[$cohort->id]) ? $customfieldsdata[$cohort->id] : []; + $cohorts[$key] = $cohort; } @@ -432,6 +461,7 @@ public static function search_cohorts_returns() { 'descriptionformat' => new external_format_value('description'), 'visible' => new external_value(PARAM_BOOL, 'cohort visible'), 'theme' => new external_value(PARAM_THEME, 'cohort theme', VALUE_OPTIONAL), + 'customfields' => self::build_custom_fields_returns_structure(), )) ) )); @@ -469,6 +499,7 @@ public static function update_cohorts_parameters() { 'the cohort theme. The allowcohortthemes setting must be enabled on Moodle', VALUE_OPTIONAL ), + 'customfields' => self::build_custom_fields_parameters_structure(), ) ) ) @@ -541,6 +572,15 @@ public static function update_cohorts($cohorts) { $cohort->descriptionformat = util::validate_format($cohort->descriptionformat); } + // Custom fields. + if (!empty($cohort->customfields)) { + foreach ($cohort->customfields as $field) { + $fieldname = self::build_custom_field_name($field['shortname']); + $cohort->{$fieldname} = $field['value']; + } + unset($cohort->customfields); + } + cohort_update_cohort($cohort); } @@ -825,4 +865,77 @@ public static function get_cohort_members_returns() { ) ); } + + /** + * Builds a structure for custom fields parameters. + * + * @return \core_external\external_multiple_structure + */ + protected static function build_custom_fields_parameters_structure(): external_multiple_structure { + return new external_multiple_structure( + new external_single_structure( + array( + 'shortname' => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'), + 'value' => new external_value(PARAM_RAW, 'The value of the custom field'), + ) + ), 'Custom fields for the cohort', VALUE_OPTIONAL + ); + } + + /** + * Builds a structure for custom fields returns. + * + * @return \core_external\external_multiple_structure + */ + protected static function build_custom_fields_returns_structure(): external_multiple_structure { + return new external_multiple_structure( + new external_single_structure( + array( + 'name' => new external_value(PARAM_RAW, 'The name of the custom field'), + 'shortname' => new external_value(PARAM_RAW, + 'The shortname of the custom field - to be able to build the field class in the code'), + 'type' => new external_value(PARAM_ALPHANUMEXT, + 'The type of the custom field - text field, checkbox...'), + 'valueraw' => new external_value(PARAM_RAW, 'The raw value of the custom field'), + 'value' => new external_value(PARAM_RAW, 'The value of the custom field'), + ) + ), 'Custom fields', VALUE_OPTIONAL + ); + } + + /** + * Returns custom fields data for provided cohorts. + * + * @param array $cohortids a list of cohort IDs to provide data for. + * @return array + */ + protected static function get_custom_fields_data(array $cohortids): array { + $result = []; + + $customfieldsdata = cohort_get_custom_fields_data($cohortids); + + foreach ($customfieldsdata as $cohortid => $fieldcontrollers) { + foreach ($fieldcontrollers as $fieldcontroller) { + $result[$cohortid][] = [ + 'type' => $fieldcontroller->get_field()->get('type'), + 'value' => $fieldcontroller->export_value(), + 'valueraw' => $fieldcontroller->get_value(), + 'name' => $fieldcontroller->get_field()->get('name'), + 'shortname' => $fieldcontroller->get_field()->get('shortname'), + ]; + } + } + + return $result; + } + + /** + * Builds a suitable name of a custom field for a custom field handler based on provided shortname. + * + * @param string $shortname shortname to use. + * @return string + */ + protected static function build_custom_field_name(string $shortname): string { + return 'customfield_' . $shortname; + } } diff --git a/cohort/lib.php b/cohort/lib.php index e5a991ab20373..6d61d04eb20ec 100644 --- a/cohort/lib.php +++ b/cohort/lib.php @@ -73,6 +73,9 @@ function cohort_add_cohort($cohort) { $cohort->id = $DB->insert_record('cohort', $cohort); + $handler = core_cohort\customfield\cohort_handler::create(); + $handler->instance_form_save($cohort, true); + $event = \core\event\cohort_created::create(array( 'context' => context::instance_by_id($cohort->contextid), 'objectid' => $cohort->id, @@ -99,6 +102,11 @@ function cohort_update_cohort($cohort) { unset($cohort->theme); } $cohort->timemodified = time(); + + // Update custom fields if there are any of them in the form. + $handler = core_cohort\customfield\cohort_handler::create(); + $handler->instance_form_save($cohort); + $DB->update_record('cohort', $cohort); $event = \core\event\cohort_updated::create(array( @@ -120,6 +128,9 @@ function cohort_delete_cohort($cohort) { // TODO: add component delete callback } + $handler = core_cohort\customfield\cohort_handler::create(); + $handler->delete_instance($cohort->id); + $DB->delete_records('cohort_members', array('cohortid'=>$cohort->id)); $DB->delete_records('cohort', array('id'=>$cohort->id)); @@ -234,9 +245,11 @@ function cohort_is_member($cohortid, $userid) { * @param int $offset * @param int $limit * @param string $search + * @param bool $withcustomfields if set to yes, then cohort custom fields will be included in the results. * @return array */ -function cohort_get_available_cohorts($currentcontext, $withmembers = 0, $offset = 0, $limit = 25, $search = '') { +function cohort_get_available_cohorts($currentcontext, $withmembers = 0, $offset = 0, $limit = 25, + $search = '', $withcustomfields = false) { global $DB; $params = array(); @@ -312,7 +325,18 @@ function($a) { ORDER BY c.name, c.idnumber"; } - return $DB->get_records_sql($sql, $params, $offset, $limit); + $cohorts = $DB->get_records_sql($sql, $params, $offset, $limit); + + if ($withcustomfields) { + $cohortids = array_keys($cohorts); + $customfieldsdata = cohort_get_custom_fields_data($cohortids); + + foreach ($cohorts as $cohort) { + $cohort->customfields = !empty($customfieldsdata[$cohort->id]) ? $customfieldsdata[$cohort->id] : []; + } + } + + return $cohorts; } /** @@ -347,9 +371,10 @@ function cohort_can_view_cohort($cohortorid, $currentcontext) { * * @param stdClass|int $cohortorid cohort object or id * @param context $currentcontext current context (course) where visibility is checked + * @param bool $withcustomfields if set to yes, then cohort custom fields will be included in the results. * @return stdClass|boolean */ -function cohort_get_cohort($cohortorid, $currentcontext) { +function cohort_get_cohort($cohortorid, $currentcontext, $withcustomfields = false) { global $DB; if (is_numeric($cohortorid)) { $cohort = $DB->get_record('cohort', array('id' => $cohortorid), 'id, contextid, visible'); @@ -358,15 +383,22 @@ function cohort_get_cohort($cohortorid, $currentcontext) { } if ($cohort && in_array($cohort->contextid, $currentcontext->get_parent_context_ids())) { - if ($cohort->visible) { - return $cohort; - } - $cohortcontext = context::instance_by_id($cohort->contextid); - if (has_capability('moodle/cohort:view', $cohortcontext)) { - return $cohort; + if (!$cohort->visible) { + $cohort = false; + } else { + $cohortcontext = context::instance_by_id($cohort->contextid); + if (!has_capability('moodle/cohort:view', $cohortcontext)) { + $cohort = false; + } } } - return false; + + if ($cohort && $withcustomfields) { + $customfieldsdata = cohort_get_custom_fields_data([$cohort->id]); + $cohort->customfields = !empty($customfieldsdata[$cohort->id]) ? $customfieldsdata[$cohort->id] : []; + } + + return $cohort; } /** @@ -413,9 +445,10 @@ function cohort_get_search_query($search, $tablealias = '') { * @param int $page number of the current page * @param int $perpage items per page * @param string $search search string + * @param bool $withcustomfields if set to yes, then cohort custom fields will be included in the results. * @return array Array(totalcohorts => int, cohorts => array, allcohorts => int) */ -function cohort_get_cohorts($contextid, $page = 0, $perpage = 25, $search = '') { +function cohort_get_cohorts($contextid, $page = 0, $perpage = 25, $search = '', $withcustomfields = false) { global $DB; $fields = "SELECT *"; @@ -437,6 +470,15 @@ function cohort_get_cohorts($contextid, $page = 0, $perpage = 25, $search = '') } $cohorts = $DB->get_records_sql($fields . $sql . $order, $params, $page*$perpage, $perpage); + if ($withcustomfields) { + $cohortids = array_keys($cohorts); + $customfieldsdata = cohort_get_custom_fields_data($cohortids); + + foreach ($cohorts as $cohort) { + $cohort->customfields = !empty($customfieldsdata[$cohort->id]) ? $customfieldsdata[$cohort->id] : []; + } + } + return array('totalcohorts' => $totalcohorts, 'cohorts' => $cohorts, 'allcohorts' => $allcohorts); } @@ -450,9 +492,10 @@ function cohort_get_cohorts($contextid, $page = 0, $perpage = 25, $search = '') * @param int $page number of the current page * @param int $perpage items per page * @param string $search search string + * @param bool $withcustomfields if set to yes, then cohort custom fields will be included in the results. * @return array Array(totalcohorts => int, cohorts => array, allcohorts => int) */ -function cohort_get_all_cohorts($page = 0, $perpage = 25, $search = '') { +function cohort_get_all_cohorts($page = 0, $perpage = 25, $search = '', $withcustomfields = false) { global $DB; $fields = "SELECT c.*, ".context_helper::get_preload_record_columns_sql('ctx'); @@ -480,9 +523,17 @@ function cohort_get_all_cohorts($page = 0, $perpage = 25, $search = '') { $order = " ORDER BY c.name ASC, c.idnumber ASC"; $cohorts = $DB->get_records_sql($fields . $sql . $wheresql . $order, $params, $page*$perpage, $perpage); - // Preload used contexts, they will be used to check view/manage/assign capabilities and display categories names. - foreach (array_keys($cohorts) as $key) { - context_helper::preload_from_record($cohorts[$key]); + if ($withcustomfields) { + $cohortids = array_keys($cohorts); + $customfieldsdata = cohort_get_custom_fields_data($cohortids); + } + + foreach ($cohorts as $cohort) { + // Preload used contexts, they will be used to check view/manage/assign capabilities and display categories names. + context_helper::preload_from_record($cohort); + if ($withcustomfields) { + $cohort->customfields = !empty($customfieldsdata[$cohort->id]) ? $customfieldsdata[$cohort->id] : []; + } } return array('totalcohorts' => $totalcohorts, 'cohorts' => $cohorts, 'allcohorts' => $allcohorts); @@ -492,16 +543,28 @@ function cohort_get_all_cohorts($page = 0, $perpage = 25, $search = '') { * Get all the cohorts where the given user is member of. * * @param int $userid + * @param bool $withcustomfields if set to yes, then cohort custom fields will be included in the results. * @return array Array */ -function cohort_get_user_cohorts($userid) { +function cohort_get_user_cohorts($userid, $withcustomfields = false) { global $DB; $sql = 'SELECT c.* FROM {cohort} c JOIN {cohort_members} cm ON c.id = cm.cohortid WHERE cm.userid = ? AND c.visible = 1'; - return $DB->get_records_sql($sql, array($userid)); + $cohorts = $DB->get_records_sql($sql, array($userid)); + + if ($withcustomfields) { + $cohortids = array_keys($cohorts); + $customfieldsdata = cohort_get_custom_fields_data($cohortids); + + foreach ($cohorts as $cohort) { + $cohort->customfields = !empty($customfieldsdata[$cohort->id]) ? $customfieldsdata[$cohort->id] : []; + } + } + + return $cohorts; } /** @@ -635,3 +698,20 @@ function cohort_get_list_of_themes() { } return $themes; } + +/** + * Returns custom fields data for provided cohorts. + * + * @param array $cohortids a list of cohort IDs to provide data for. + * @return \core_customfield\data_controller[] + */ +function cohort_get_custom_fields_data(array $cohortids): array { + $result = []; + + if (!empty($cohortids)) { + $handler = core_cohort\customfield\cohort_handler::create(); + $result = $handler->get_instances_data($cohortids, true); + } + + return $result; +} diff --git a/cohort/tests/behat/customfields.feature b/cohort/tests/behat/customfields.feature new file mode 100644 index 0000000000000..4d1bccbc2e2ec --- /dev/null +++ b/cohort/tests/behat/customfields.feature @@ -0,0 +1,39 @@ +@core @core_cohort @core_customfield @javascript +Feature: Add and use cohort custom fields + In order to store an extra information about cohorts + As an admin + I need to create cohort customs fields and be able to populate them on cohort creation + + Background: + Given the following "custom field categories" exist: + | name | component | area | itemid | + | Category for test | core_cohort | cohort | 0 | + + Scenario: Create a new cohort custom field and use the field for a new cohort + When I log in as "admin" + And I navigate to "Users > Accounts > Cohort custom fields" in site administration + And I click on "Add a new custom field" "link" + And I click on "Short text" "link" + And I set the following fields to these values: + | Name | Test field | + | Short name | testfield | + And I click on "Save changes" "button" in the "Adding a new Short text" "dialogue" + Then the following should exist in the "generaltable" table: + | Custom field | Short name | Type | + | Test field | testfield | Short text | + And I navigate to "Users > Accounts > Cohorts" in site administration + And I follow "Add new cohort" + Then I should see "Category for test" + And I should see "Test field" + And I set the following fields to these values: + | Name | My new cohort | + | Context | System | + | Cohort ID | mynewcohort | + | Description | My new cohort description | + | Test field | Custom field text | + And I press "Save changes" + Then the following should exist in the "generaltable" table: + | Name | Cohort ID | Description | + | My new cohort | mynewcohort | My new cohort description | + And I press "Edit" action in the "My new cohort" report row + And the field "Test field" matches value "Custom field text" diff --git a/cohort/tests/customfield/cohort_handler_test.php b/cohort/tests/customfield/cohort_handler_test.php new file mode 100644 index 0000000000000..ffcd5a13f5d47 --- /dev/null +++ b/cohort/tests/customfield/cohort_handler_test.php @@ -0,0 +1,182 @@ +. + +namespace core_cohort\customfield; + +use advanced_testcase; +use context_system; +use context_coursecat; +use moodle_url; +use core_customfield\field_controller; + +/** + * Unit tests for cohort custom field handler. + * + * @package core_cohort + * @covers \core_cohort\customfield\cohort_handler + * @copyright 2022 Dmitrii Metelkin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cohort_handler_test extends advanced_testcase { + /** + * Test custom field handler. + * @var \core_customfield\handler + */ + protected $handler; + + /** + * Setup. + */ + public function setUp(): void { + $this->handler = cohort_handler::create(); + } + + /** + * Create Cohort custom field for testing. + * + * @return field_controller + */ + protected function create_cohort_custom_field(): field_controller { + $fieldcategory = self::getDataGenerator()->create_custom_field_category([ + 'component' => 'core_cohort', + 'area' => 'cohort', + ]); + + return self::getDataGenerator()->create_custom_field([ + 'shortname' => 'testfield1', + 'type' => 'text', + 'categoryid' => $fieldcategory->get('id'), + ]); + } + + /** + * Test configuration context. + */ + public function test_get_configuration_context() { + $this->assertInstanceOf(context_system::class, $this->handler->get_configuration_context()); + } + + /** + * Test getting config URL. + */ + public function test_get_configuration_url() { + $this->assertInstanceOf(moodle_url::class, $this->handler->get_configuration_url()); + $this->assertEquals('/cohort/customfield.php', $this->handler->get_configuration_url()->out_as_local_url()); + } + + /** + * Test can configure check. + */ + public function test_can_configure() { + $this->resetAfterTest(); + + $user = self::getDataGenerator()->create_user(); + self::setUser($user); + + $this->assertFalse($this->handler->can_configure()); + + $roleid = self::getDataGenerator()->create_role(); + assign_capability('moodle/cohort:configurecustomfields', CAP_ALLOW, $roleid, context_system::instance()->id, true); + role_assign($roleid, $user->id, context_system::instance()->id); + + $this->assertTrue($this->handler->can_configure()); + } + + /** + * Test getting instance context. + */ + public function test_get_instance_context() { + $this->resetAfterTest(); + + $category = self::getDataGenerator()->create_category(); + $catcontext = context_coursecat::instance($category->id); + $systemcontext = context_system::instance(); + $cohortsystem = self::getDataGenerator()->create_cohort(); + $cohortcategory = self::getDataGenerator()->create_cohort(['contextid' => $catcontext->id]); + + $this->assertInstanceOf(context_system::class, $this->handler->get_instance_context($cohortsystem->id)); + $this->assertSame($systemcontext, $this->handler->get_instance_context($cohortsystem->id)); + + $this->assertInstanceOf(context_coursecat::class, $this->handler->get_instance_context($cohortcategory->id)); + $this->assertSame($catcontext, $this->handler->get_instance_context($cohortcategory->id)); + } + + /** + * Test can edit functionality. + */ + public function test_can_edit() { + $this->resetAfterTest(); + + $roleid = self::getDataGenerator()->create_role(); + assign_capability('moodle/cohort:manage', CAP_ALLOW, $roleid, context_system::instance()->id, true); + + $field = $this->create_cohort_custom_field(); + + $user = self::getDataGenerator()->create_user(); + self::setUser($user); + + $this->assertFalse($this->handler->can_edit($field, 0)); + + role_assign($roleid, $user->id, context_system::instance()->id); + $this->assertTrue($this->handler->can_edit($field, 0)); + } + + /** + * Test can view functionality. + */ + public function test_can_view() { + $this->resetAfterTest(); + + $manageroleid = self::getDataGenerator()->create_role(); + assign_capability('moodle/cohort:manage', CAP_ALLOW, $manageroleid, context_system::instance()->id, true); + + $viewroleid = self::getDataGenerator()->create_role(); + assign_capability('moodle/cohort:view', CAP_ALLOW, $viewroleid, context_system::instance()->id, true); + + $viewandmanageroleid = self::getDataGenerator()->create_role(); + assign_capability('moodle/cohort:manage', CAP_ALLOW, $viewandmanageroleid, context_system::instance()->id, true); + assign_capability('moodle/cohort:view', CAP_ALLOW, $viewandmanageroleid, context_system::instance()->id, true); + + $field = $this->create_cohort_custom_field(); + $cohort = self::getDataGenerator()->create_cohort(); + + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + $user3 = self::getDataGenerator()->create_user(); + + self::setUser($user1); + $this->assertFalse($this->handler->can_view($field, $cohort->id)); + + self::setUser($user2); + $this->assertFalse($this->handler->can_view($field, $cohort->id)); + + self::setUser($user3); + $this->assertFalse($this->handler->can_view($field, $cohort->id)); + + role_assign($manageroleid, $user1->id, context_system::instance()->id); + role_assign($viewroleid, $user2->id, context_system::instance()->id); + role_assign($viewandmanageroleid, $user3->id, context_system::instance()->id); + + self::setUser($user1); + $this->assertTrue($this->handler->can_view($field, $cohort->id)); + + self::setUser($user2); + $this->assertTrue($this->handler->can_view($field, $cohort->id)); + + self::setUser($user3); + $this->assertTrue($this->handler->can_view($field, $cohort->id)); + } +} diff --git a/cohort/tests/externallib_test.php b/cohort/tests/externallib_test.php index 75310b0c528f0..0e1bc5f0b2398 100644 --- a/cohort/tests/externallib_test.php +++ b/cohort/tests/externallib_test.php @@ -19,6 +19,7 @@ use core_cohort_external; use core_external\external_api; use externallib_advanced_testcase; +use core_cohort\customfield\cohort_handler; defined('MOODLE_INTERNAL') || die(); @@ -37,11 +38,34 @@ */ class externallib_test extends externallib_advanced_testcase { + /** + * Create cohort custom fields for testing. + */ + protected function create_custom_fields(): void { + $fieldcategory = self::getDataGenerator()->create_custom_field_category([ + 'component' => 'core_cohort', + 'area' => 'cohort', + 'name' => 'Other fields', + ]); + self::getDataGenerator()->create_custom_field([ + 'shortname' => 'testfield1', + 'name' => 'Custom field', + 'type' => 'text', + 'categoryid' => $fieldcategory->get('id'), + ]); + self::getDataGenerator()->create_custom_field([ + 'shortname' => 'testfield2', + 'name' => 'Custom field', + 'type' => 'text', + 'categoryid' => $fieldcategory->get('id'), + ]); + } + /** * Test create_cohorts */ public function test_create_cohorts() { - global $USER, $CFG, $DB; + global $DB; $this->resetAfterTest(true); @@ -50,6 +74,9 @@ public function test_create_cohorts() { $contextid = \context_system::instance()->id; $category = $this->getDataGenerator()->create_category(); + // Custom fields. + $this->create_custom_fields(); + $cohort1 = array( 'categorytype' => array('type' => 'id', 'value' => $category->id), 'name' => 'cohort test 1', @@ -82,6 +109,23 @@ public function test_create_cohorts() { 'theme' => 'classic' ); + $cohort5 = array( + 'categorytype' => array('type' => 'id', 'value' => $category->id), + 'name' => 'cohort test 5 (with custom fields)', + 'idnumber' => 'cohorttest5', + 'description' => 'This is a description for cohorttest5', + 'customfields' => array( + array( + 'shortname' => 'testfield1', + 'value' => 'Test value 1', + ), + array( + 'shortname' => 'testfield2', + 'value' => 'Test value 2', + ), + ), + ); + // Call the external function. $this->setCurrentTimeStart(); $createdcohorts = core_cohort_external::create_cohorts(array($cohort1, $cohort2)); @@ -115,6 +159,21 @@ public function test_create_cohorts() { $this->assertTimeCurrent($dbcohort->timemodified); } + $createdcohorts = core_cohort_external::create_cohorts(array($cohort5)); + $createdcohorts = external_api::clean_returnvalue(core_cohort_external::create_cohorts_returns(), $createdcohorts); + + $this->assertCount(1, $createdcohorts); + $createdcohort = reset($createdcohorts); + $dbcohort = $DB->get_record('cohort', array('id' => $createdcohort['id'])); + $this->assertEquals($cohort5['name'], $dbcohort->name); + $this->assertEquals($cohort5['description'], $dbcohort->description); + $this->assertEquals(1, $dbcohort->visible); + $this->assertEquals('', $dbcohort->theme); + + $data = cohort_handler::create()->export_instance_data_object($createdcohort['id'], true); + $this->assertEquals('Test value 1', $data->testfield1); + $this->assertEquals('Test value 2', $data->testfield2); + // Call when $CFG->allowcohortthemes is disabled. set_config('allowcohortthemes', 0); $createdcohorts = core_cohort_external::create_cohorts(array($cohort4)); @@ -174,10 +233,11 @@ public function test_delete_cohorts() { * Test get_cohorts */ public function test_get_cohorts() { - global $USER, $CFG; - $this->resetAfterTest(true); + // Custom fields. + $this->create_custom_fields(); + set_config('allowcohortthemes', 1); $cohort1 = array( @@ -185,8 +245,14 @@ public function test_get_cohorts() { 'name' => 'cohortnametest1', 'idnumber' => 'idnumbertest1', 'description' => 'This is a description for cohort 1', - 'theme' => 'classic' - ); + 'theme' => 'classic', + 'customfield_testfield1' => 'Test value 1', + 'customfield_testfield2' => 'Test value 2', + ); + + // We need a site admin to be able to populate cohorts custom fields. + $this->setAdminUser(); + $cohort1 = self::getDataGenerator()->create_cohort($cohort1); $cohort2 = self::getDataGenerator()->create_cohort(); @@ -207,6 +273,19 @@ public function test_get_cohorts() { $this->assertEquals($cohort1->description, $enrolledcohort['description']); $this->assertEquals($cohort1->visible, $enrolledcohort['visible']); $this->assertEquals($cohort1->theme, $enrolledcohort['theme']); + $this->assertIsArray($enrolledcohort['customfields']); + $this->assertCount(2, $enrolledcohort['customfields']); + $actual = []; + foreach ($enrolledcohort['customfields'] as $customfield) { + $this->assertArrayHasKey('name', $customfield); + $this->assertArrayHasKey('shortname', $customfield); + $this->assertArrayHasKey('type', $customfield); + $this->assertArrayHasKey('valueraw', $customfield); + $this->assertArrayHasKey('value', $customfield); + $actual[$customfield['shortname']] = $customfield; + } + $this->assertEquals('Test value 1', $actual['testfield1']['value']); + $this->assertEquals('Test value 2', $actual['testfield2']['value']); } } @@ -237,22 +316,39 @@ public function test_get_cohorts() { * Test update_cohorts */ public function test_update_cohorts() { - global $USER, $CFG, $DB; + global $DB; $this->resetAfterTest(true); + // Custom fields. + $this->create_custom_fields(); + set_config('allowcohortthemes', 0); $cohort1 = self::getDataGenerator()->create_cohort(array('visible' => 0)); + $data = cohort_handler::create()->export_instance_data_object($cohort1->id, true); + $this->assertNull($data->testfield1); + $this->assertNull($data->testfield2); + $cohort1 = array( 'id' => $cohort1->id, 'categorytype' => array('type' => 'id', 'value' => '1'), 'name' => 'cohortnametest1', 'idnumber' => 'idnumbertest1', 'description' => 'This is a description for cohort 1', - 'theme' => 'classic' - ); + 'theme' => 'classic', + 'customfields' => array( + array( + 'shortname' => 'testfield1', + 'value' => 'Test value 1', + ), + array( + 'shortname' => 'testfield2', + 'value' => 'Test value 2', + ), + ), + ); $context = \context_system::instance(); $roleid = $this->assignUserCapability('moodle/cohort:manage', $context->id); @@ -269,6 +365,9 @@ public function test_update_cohorts() { $this->assertEquals($dbcohort->description, $cohort1['description']); $this->assertEquals($dbcohort->visible, 0); $this->assertEmpty($dbcohort->theme); + $data = cohort_handler::create()->export_instance_data_object($cohort1['id'], true); + $this->assertEquals('Test value 1', $data->testfield1); + $this->assertEquals('Test value 2', $data->testfield2); // Since field 'visible' was added in 2.8, make sure that update works correctly with and without this parameter. core_cohort_external::update_cohorts(array($cohort1 + array('visible' => 1))); @@ -290,6 +389,22 @@ public function test_update_cohorts() { $dbcohort = $DB->get_record('cohort', array('id' => $cohort1['id'])); $this->assertEquals('classic', $dbcohort->theme); + // Updating custom fields. + $cohort1['customfields'] = array( + array( + 'shortname' => 'testfield1', + 'value' => 'Test value 1 updated', + ), + array( + 'shortname' => 'testfield2', + 'value' => 'Test value 2 updated', + ), + ); + core_cohort_external::update_cohorts(array($cohort1)); + $data = cohort_handler::create()->export_instance_data_object($cohort1['id'], true); + $this->assertEquals('Test value 1 updated', $data->testfield1); + $this->assertEquals('Test value 2 updated', $data->testfield2); + // Call without required capability. $this->unassignUserCapability('moodle/cohort:manage', $context->id, $roleid); $this->expectException(\required_capability_exception::class); @@ -523,6 +638,7 @@ public function test_search_cohorts() { global $DB, $CFG; $this->resetAfterTest(true); + $this->create_custom_fields(); $creator = $this->getDataGenerator()->create_user(); $user = $this->getDataGenerator()->create_user(); $catuser = $this->getDataGenerator()->create_user(); @@ -562,10 +678,19 @@ public function test_search_cohorts() { $catcontext = array('contextid' => \context_coursecat::instance($category->id)->id); $othercatcontext = array('contextid' => \context_coursecat::instance($othercategory->id)->id); $coursecontext = array('contextid' => \context_course::instance($course->id)->id); + $customfields = array( + 'contextid' => \context_system::instance()->id, + 'customfield_testfield1' => 'Test value 1', + 'customfield_testfield2' => 'Test value 2', + ); + + // We need a site admin to be able to populate cohorts custom fields. + $this->setAdminUser(); $cohort1 = $this->getDataGenerator()->create_cohort(array_merge($syscontext, array('name' => 'Cohortsearch 1'))); $cohort2 = $this->getDataGenerator()->create_cohort(array_merge($catcontext, array('name' => 'Cohortsearch 2'))); $cohort3 = $this->getDataGenerator()->create_cohort(array_merge($othercatcontext, array('name' => 'Cohortsearch 3'))); + $cohort4 = $this->getDataGenerator()->create_cohort(array_merge($customfields, array('name' => 'Cohortsearch 4'))); // A user without permission in the system. $this->setUser($user); @@ -587,14 +712,31 @@ public function test_search_cohorts() { // A user with permissions in the system. $this->setUser($creator); - $result = core_cohort_external::search_cohorts("Cohortsearch", $syscontext, 'parents'); - $this->assertEquals(1, count($result['cohorts'])); - $this->assertEquals('Cohortsearch 1', $result['cohorts'][$cohort1->id]->name); + $result = core_cohort_external::search_cohorts("Cohortsearch 4", $syscontext, 'parents'); + $this->assertCount(1, $result['cohorts']); + $this->assertEquals('Cohortsearch 4', $result['cohorts'][$cohort4->id]->name); + + $result = core_cohort_external::search_cohorts("Cohortsearch 4", $syscontext, 'parents'); + $this->assertCount(1, $result['cohorts']); + $this->assertEquals('Cohortsearch 4', $result['cohorts'][$cohort4->id]->name); + $this->assertIsArray($result['cohorts'][$cohort4->id]->customfields); + $this->assertCount(2, $result['cohorts'][$cohort4->id]->customfields); + $actual = []; + foreach ($result['cohorts'][$cohort4->id]->customfields as $customfield) { + $this->assertArrayHasKey('name', $customfield); + $this->assertArrayHasKey('shortname', $customfield); + $this->assertArrayHasKey('type', $customfield); + $this->assertArrayHasKey('valueraw', $customfield); + $this->assertArrayHasKey('value', $customfield); + $actual[$customfield['shortname']] = $customfield; + } + $this->assertEquals('Test value 1', $actual['testfield1']['value']); + $this->assertEquals('Test value 2', $actual['testfield2']['value']); // A user with permissions in the category. $this->setUser($catcreator); $result = core_cohort_external::search_cohorts("Cohortsearch", $catcontext, 'parents'); - $this->assertEquals(2, count($result['cohorts'])); + $this->assertCount(3, $result['cohorts']); $cohorts = array(); foreach ($result['cohorts'] as $cohort) { $cohorts[] = $cohort->name; @@ -604,18 +746,18 @@ public function test_search_cohorts() { // Check for parameter $includes = 'self'. $this->setUser($creator); $result = core_cohort_external::search_cohorts("Cohortsearch", $othercatcontext, 'self'); - $this->assertEquals(1, count($result['cohorts'])); + $this->assertCount(1, $result['cohorts']); $this->assertEquals('Cohortsearch 3', $result['cohorts'][$cohort3->id]->name); // Check for parameter $includes = 'all'. $this->setUser($creator); $result = core_cohort_external::search_cohorts("Cohortsearch", $syscontext, 'all'); - $this->assertEquals(3, count($result['cohorts'])); + $this->assertCount(4, $result['cohorts']); // A user in the course context with the system cohort:view capability. Check that all the system cohorts are returned. $this->setUser($courseuser); $result = core_cohort_external::search_cohorts("Cohortsearch", $coursecontext, 'all'); - $this->assertEquals(1, count($result['cohorts'])); + $this->assertCount(2, $result['cohorts']); $this->assertEquals('Cohortsearch 1', $result['cohorts'][$cohort1->id]->name); // Detect invalid parameter $includes. diff --git a/cohort/tests/lib_test.php b/cohort/tests/lib_test.php index e5dc044e32fba..f1ba6dcd4dc47 100644 --- a/cohort/tests/lib_test.php +++ b/cohort/tests/lib_test.php @@ -16,6 +16,9 @@ namespace core_cohort; +use core_cohort\customfield\cohort_handler; +use core_customfield\data_controller; + defined('MOODLE_INTERNAL') || die(); global $CFG; @@ -32,10 +35,33 @@ */ class lib_test extends \advanced_testcase { + /** + * Create Cohort custom field for testing. + * + * @return \core_customfield\field_controller + */ + protected function create_cohort_custom_field(): \core_customfield\field_controller { + $fieldcategory = self::getDataGenerator()->create_custom_field_category([ + 'component' => 'core_cohort', + 'area' => 'cohort', + 'name' => 'Other fields', + ]); + + return self::getDataGenerator()->create_custom_field([ + 'shortname' => 'testfield1', + 'name' => 'Custom field', + 'type' => 'text', + 'categoryid' => $fieldcategory->get('id'), + ]); + } + public function test_cohort_add_cohort() { global $DB; $this->resetAfterTest(); + $this->setAdminUser(); + + $this->create_cohort_custom_field(); $cohort = new \stdClass(); $cohort->contextid = \context_system::instance()->id; @@ -43,6 +69,7 @@ public function test_cohort_add_cohort() { $cohort->idnumber = 'testid'; $cohort->description = 'test cohort desc'; $cohort->descriptionformat = FORMAT_HTML; + $cohort->customfield_testfield1 = 'Test value 1'; $id = cohort_add_cohort($cohort); $this->assertNotEmpty($id); @@ -56,6 +83,10 @@ public function test_cohort_add_cohort() { $this->assertSame($newcohort->component, ''); $this->assertSame($newcohort->theme, ''); $this->assertSame($newcohort->timecreated, $newcohort->timemodified); + + $handler = cohort_handler::create(); + $customfieldsdata = $handler->export_instance_data_object($id); + $this->assertEquals('Test value 1', $customfieldsdata->testfield1); } public function test_cohort_add_cohort_missing_name() { @@ -109,6 +140,9 @@ public function test_cohort_update_cohort() { global $DB; $this->resetAfterTest(); + $this->setAdminUser(); + + $this->create_cohort_custom_field(); $cohort = new \stdClass(); $cohort->contextid = \context_system::instance()->id; @@ -116,6 +150,8 @@ public function test_cohort_update_cohort() { $cohort->idnumber = 'testid'; $cohort->description = 'test cohort desc'; $cohort->descriptionformat = FORMAT_HTML; + $cohort->customfield_testfield1 = 'Test value 1'; + $id = cohort_add_cohort($cohort); $this->assertNotEmpty($id); $DB->set_field('cohort', 'timecreated', $cohort->timecreated - 10, array('id'=>$id)); @@ -123,6 +159,8 @@ public function test_cohort_update_cohort() { $cohort = $DB->get_record('cohort', array('id'=>$id)); $cohort->name = 'test cohort 2'; + $cohort->customfield_testfield1 = 'Test value updated'; + cohort_update_cohort($cohort); $newcohort = $DB->get_record('cohort', array('id'=>$id)); @@ -136,6 +174,10 @@ public function test_cohort_update_cohort() { $this->assertSame($newcohort->theme, ''); $this->assertGreaterThan($newcohort->timecreated, $newcohort->timemodified); $this->assertLessThanOrEqual(time(), $newcohort->timemodified); + + $handler = cohort_handler::create(); + $customfieldsdata = $handler->export_instance_data_object($id); + $this->assertEquals('Test value updated', $customfieldsdata->testfield1); } public function test_cohort_update_cohort_event() { @@ -185,12 +227,17 @@ public function test_cohort_delete_cohort() { global $DB; $this->resetAfterTest(); + $this->setAdminUser(); - $cohort = $this->getDataGenerator()->create_cohort(); + $field = $this->create_cohort_custom_field(); + + $cohort = $this->getDataGenerator()->create_cohort(['customfield_testfield1' => 'Test value 1']); + $this->assertTrue($DB->record_exists('customfield_data', ['instanceid' => $cohort->id, 'fieldid' => $field->get('id')])); cohort_delete_cohort($cohort); $this->assertFalse($DB->record_exists('cohort', array('id'=>$cohort->id))); + $this->assertFalse($DB->record_exists('customfield_data', ['instanceid' => $cohort->id, 'fieldid' => $field->get('id')])); } public function test_cohort_delete_cohort_event() { @@ -643,6 +690,142 @@ public function test_cohort_get_available_cohorts() { $this->assertEquals(array($cohort1->id, $cohort2->id, $cohort4->id), array_keys($result)); } + /** + * Test that all get functions return custom fields data. + * + * @covers \cohort_get_cohort, \cohort_get_cohorts, \cohort_get_all_cohorts + * @covers \cohort_get_available_cohorts, \cohort_get_user_cohorts + */ + public function test_get_functions_return_custom_fields() { + $this->resetAfterTest(); + $this->setAdminUser(); + + $user = self::getDataGenerator()->create_user(); + $course = self::getDataGenerator()->create_course(); + $coursectx = \context_course::instance(($course->id)); + + $this->create_cohort_custom_field(); + + $cohort1 = $this->getDataGenerator()->create_cohort(['customfield_testfield1' => 'Test value 1']); + $cohort2 = $this->getDataGenerator()->create_cohort(); + + // Test cohort_get_cohort. + $result = cohort_get_cohort($cohort1->id, \context_system::instance(), true); + $this->assertObjectHasAttribute('customfields', $result); + $this->assertCount(1, $result->customfields); + $field = reset($result->customfields); + $this->assertInstanceOf(data_controller::class, $field); + $this->assertEquals('testfield1', $field->get_field()->get('shortname')); + $this->assertEquals('Test value 1', $field->get_value()); + + // Test custom fields are not returned if not needed. + $result = cohort_get_cohort($cohort1->id, \context_system::instance()); + $this->assertObjectNotHasAttribute('customfields', $result); + + // Test cohort_get_cohorts. + $result = cohort_get_cohorts(\context_system::instance()->id, 0, 25, '', true); + $this->assertEquals(2, $result['totalcohorts']); + $this->assertEquals(2, $result['allcohorts']); + foreach ($result['cohorts'] as $cohort) { + $this->assertObjectHasAttribute('customfields', $cohort); + $this->assertCount(1, $cohort->customfields); + $field = reset($cohort->customfields); + $this->assertInstanceOf(data_controller::class, $field); + $this->assertEquals('testfield1', $field->get_field()->get('shortname')); + + if ($cohort->id == $cohort1->id ) { + $this->assertEquals('Test value 1', $field->get_value()); + } else { + $this->assertEquals('', $field->get_value()); + } + } + + // Test custom fields are not returned if not needed. + $result = cohort_get_cohorts(\context_system::instance()->id, 0, 25, ''); + $this->assertEquals(2, $result['totalcohorts']); + $this->assertEquals(2, $result['allcohorts']); + foreach ($result['cohorts'] as $cohort) { + $this->assertObjectNotHasAttribute('customfields', $cohort); + } + + // Test test_cohort_get_all_cohorts. + $result = cohort_get_all_cohorts(0, 100, '', true); + $this->assertEquals(2, $result['totalcohorts']); + $this->assertEquals(2, $result['allcohorts']); + foreach ($result['cohorts'] as $cohort) { + $this->assertObjectHasAttribute('customfields', $cohort); + $this->assertCount(1, $cohort->customfields); + $field = reset($cohort->customfields); + $this->assertInstanceOf(data_controller::class, $field); + $this->assertEquals('testfield1', $field->get_field()->get('shortname')); + + if ($cohort->id == $cohort1->id ) { + $this->assertEquals('Test value 1', $field->get_value()); + } else { + $this->assertEquals('', $field->get_value()); + } + } + + // Test custom fields are not returned if not needed. + $result = cohort_get_all_cohorts(0, 100, ''); + $this->assertEquals(2, $result['totalcohorts']); + $this->assertEquals(2, $result['allcohorts']); + foreach ($result['cohorts'] as $cohort) { + $this->assertObjectNotHasAttribute('customfields', $cohort); + } + + // Test cohort_get_available_cohorts. + $result = cohort_get_available_cohorts($coursectx, COHORT_ALL, 0, 25, '', true); + $this->assertCount(2, $result); + foreach ($result as $cohort) { + $this->assertObjectHasAttribute('customfields', $cohort); + $this->assertCount(1, $cohort->customfields); + $field = reset($cohort->customfields); + $this->assertInstanceOf(data_controller::class, $field); + $this->assertEquals('testfield1', $field->get_field()->get('shortname')); + + if ($cohort->id == $cohort1->id ) { + $this->assertEquals('Test value 1', $field->get_value()); + } else { + $this->assertEquals('', $field->get_value()); + } + } + + // Test custom fields are not returned if not needed. + $result = cohort_get_available_cohorts($coursectx, COHORT_ALL, 0, 25, ''); + $this->assertCount(2, $result); + foreach ($result as $cohort) { + $this->assertObjectNotHasAttribute('customfields', $cohort); + } + + // Test cohort_get_user_cohorts. + cohort_add_member($cohort1->id, $user->id); + cohort_add_member($cohort2->id, $user->id); + + $result = cohort_get_user_cohorts($user->id, true); + $this->assertCount(2, $result); + foreach ($result as $cohort) { + $this->assertObjectHasAttribute('customfields', $cohort); + $this->assertCount(1, $cohort->customfields); + $field = reset($cohort->customfields); + $this->assertInstanceOf(data_controller::class, $field); + $this->assertEquals('testfield1', $field->get_field()->get('shortname')); + + if ($cohort->id == $cohort1->id ) { + $this->assertEquals('Test value 1', $field->get_value()); + } else { + $this->assertEquals('', $field->get_value()); + } + } + + // Test that there is no custom fields returned if not required. + $result = cohort_get_user_cohorts($user->id); + $this->assertCount(2, $result); + foreach ($result as $cohort) { + $this->assertObjectNotHasAttribute('customfields', $cohort); + } + } + /** * Create a cohort with allowcohortthemes enabled/disabled. */ @@ -725,4 +908,35 @@ public function test_cohort_update_theme_cohort() { $this->assertNotEmpty($updatedcohort->theme); $this->assertSame($cohort1->theme, $updatedcohort->theme); } + + /** + * Test that lib function returns custom field data for a cohorts. + * + * @covers \cohort_get_custom_fields_data + */ + public function test_cohort_get_custom_fields_data() { + $this->resetAfterTest(); + $this->setAdminUser(); + + $this->create_cohort_custom_field(); + + $cohort1 = $this->getDataGenerator()->create_cohort(['customfield_testfield1' => 'Test value 1']); + $cohort2 = $this->getDataGenerator()->create_cohort(); + + $result = cohort_get_custom_fields_data([$cohort1->id, $cohort2->id, 777]); + $this->assertArrayHasKey($cohort1->id, $result); + $this->assertArrayHasKey($cohort2->id, $result); + $this->assertArrayHasKey(777, $result); + + foreach ($result as $cohortid => $fieldcontrollers) { + foreach ($fieldcontrollers as $fieldcontroller) { + $this->assertInstanceOf(data_controller::class, $fieldcontroller); + if ($cohortid == $cohort1->id) { + $this->assertSame('Test value 1', $fieldcontroller->export_value()); + } else { + $this->assertNull($fieldcontroller->export_value()); + } + } + } + } } diff --git a/cohort/upgrade.txt b/cohort/upgrade.txt index eb612470a2646..a2083f9cf0a5e 100644 --- a/cohort/upgrade.txt +++ b/cohort/upgrade.txt @@ -1,6 +1,9 @@ This files describes API changes in /cohort/ information provided here is intended especially for developers. +=== 4.2 === +* Added cohort custom fields. + === 3.1 === * The Webservice core_cohort_get_cohorts now has the added functionality of getting all cohorts by not passing any parameters diff --git a/lang/en/admin.php b/lang/en/admin.php index bc42f1cae3cf9..b7e1c3242ac71 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -146,6 +146,7 @@ $string['cliupgradepending'] = 'An upgrade is pending'; $string['cliyesnoprompt'] = 'type y (means yes) or n (means no)'; $string['close'] = 'Close'; +$string['cohort_customfield'] = 'Cohort custom fields'; $string['commentsperpage'] = 'Comments displayed per page'; $string['commonactivitysettings'] = 'Common activity settings'; $string['commonfiltersettings'] = 'Common filter settings'; diff --git a/lang/en/role.php b/lang/en/role.php index 884b247e4683c..0febe58a301df 100644 --- a/lang/en/role.php +++ b/lang/en/role.php @@ -107,6 +107,7 @@ $string['cohort:assign'] = 'Add and remove cohort members'; $string['cohort:view'] = 'View site-wide cohorts'; $string['cohort:manage'] = 'Create, delete and move cohorts'; +$string['cohort:configurecustomfields'] = 'Configure cohort custom fields'; $string['comment:delete'] = 'Delete comments'; $string['comment:post'] = 'Post comments'; $string['comment:view'] = 'View comments'; diff --git a/lib/db/access.php b/lib/db/access.php index 1d54febf4dce3..6cccb63042ac0 100644 --- a/lib/db/access.php +++ b/lib/db/access.php @@ -781,6 +781,13 @@ ) ), + 'moodle/cohort:configurecustomfields' => array( + 'riskbitmask' => RISK_SPAM, + 'captype' => 'write', + 'contextlevel' => CONTEXT_SYSTEM, + 'clonepermissionsfrom' => 'moodle/site:config' + ), + 'moodle/course:create' => array( 'riskbitmask' => RISK_XSS, diff --git a/lib/phpunit/classes/util.php b/lib/phpunit/classes/util.php index 6b478152c0470..33583fafa225b 100644 --- a/lib/phpunit/classes/util.php +++ b/lib/phpunit/classes/util.php @@ -258,6 +258,9 @@ public static function reset_all_data($detectchanges = false) { if (class_exists('\core_reportbuilder\manager')) { \core_reportbuilder\manager::reset_caches(); } + if (class_exists('\core_cohort\customfield\cohort_handler')) { + \core_cohort\customfield\cohort_handler::reset_caches(); + } // Clear static cache within restore. if (class_exists('restore_section_structure_step')) { diff --git a/version.php b/version.php index 650fd0c9ac0ed..b9ca2a4e2c9ee 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2023032800.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2023032800.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.2dev+ (Build: 20230328)'; // Human-friendly version name