From 88cb8b781d83e6aff3ef275a719023dcd2ea8499 Mon Sep 17 00:00:00 2001 From: Sara Arjona Date: Tue, 23 Jan 2018 14:54:18 +0100 Subject: [PATCH] MDL-61601 cohort: Add theme support for cohorts If enabled $CFG->allowcohortthemes, then themes can be set at the cohort level. This will affect all users with only one cohort or more than one but with the same theme. The default theme order will be: course, category, session, user, cohort, site. --- admin/settings/appearance.php | 1 + .../external/cohort_summary_exporter.php | 4 + cohort/edit_form.php | 6 + cohort/externallib.php | 45 +++- cohort/lib.php | 71 ++++++- cohort/tests/behat/upload_cohorts.feature | 40 +++- cohort/tests/cohortlib_test.php | 193 ++++++++++++++++++ cohort/tests/externallib_test.php | 69 ++++++- cohort/tests/fixtures/uploadcohorts4.csv | 7 + cohort/upload_form.php | 12 +- config-dist.php | 6 +- lang/en/admin.php | 2 + lang/en/cohort.php | 1 + lib/db/install.xml | 3 +- lib/db/upgrade.php | 16 ++ lib/moodlelib.php | 8 + lib/pagelib.php | 8 +- version.php | 2 +- 18 files changed, 478 insertions(+), 16 deletions(-) create mode 100644 cohort/tests/fixtures/uploadcohorts4.csv diff --git a/admin/settings/appearance.php b/admin/settings/appearance.php index 7486db855c32f..db05c1d916cb5 100644 --- a/admin/settings/appearance.php +++ b/admin/settings/appearance.php @@ -22,6 +22,7 @@ $temp->add(new admin_setting_configcheckbox('allowuserthemes', new lang_string('allowuserthemes', 'admin'), new lang_string('configallowuserthemes', 'admin'), 0)); $temp->add(new admin_setting_configcheckbox('allowcoursethemes', new lang_string('allowcoursethemes', 'admin'), new lang_string('configallowcoursethemes', 'admin'), 0)); $temp->add(new admin_setting_configcheckbox('allowcategorythemes', new lang_string('allowcategorythemes', 'admin'), new lang_string('configallowcategorythemes', 'admin'), 0)); + $temp->add(new admin_setting_configcheckbox('allowcohortthemes', new lang_string('allowcohortthemes', 'admin'), new lang_string('configallowcohortthemes', 'admin'), 0)); $temp->add(new admin_setting_configcheckbox('allowthemechangeonurl', new lang_string('allowthemechangeonurl', 'admin'), new lang_string('configallowthemechangeonurl', 'admin'), 0)); $temp->add(new admin_setting_configcheckbox('allowuserblockhiding', new lang_string('allowuserblockhiding', 'admin'), new lang_string('configallowuserblockhiding', 'admin'), 1)); $temp->add(new admin_setting_configcheckbox('allowblockstodock', new lang_string('allowblockstodock', 'admin'), new lang_string('configallowblockstodock', 'admin'), 1)); diff --git a/cohort/classes/external/cohort_summary_exporter.php b/cohort/classes/external/cohort_summary_exporter.php index 55e7529844d8e..cbd2101717008 100644 --- a/cohort/classes/external/cohort_summary_exporter.php +++ b/cohort/classes/external/cohort_summary_exporter.php @@ -64,6 +64,10 @@ public static function define_properties() { ), 'visible' => array( 'type' => PARAM_BOOL, + ), + 'theme' => array( + 'type' => PARAM_THEME, + 'null' => NULL_ALLOWED ) ); } diff --git a/cohort/edit_form.php b/cohort/edit_form.php index d4abb887a842d..83df3a7273c95 100644 --- a/cohort/edit_form.php +++ b/cohort/edit_form.php @@ -32,6 +32,7 @@ class cohort_edit_form extends moodleform { * Define the cohort edit form */ public function definition() { + global $CFG; $mform = $this->_form; $editoroptions = $this->_customdata['editoroptions']; @@ -54,6 +55,11 @@ public function definition() { $mform->addElement('editor', 'description_editor', get_string('description', 'cohort'), null, $editoroptions); $mform->setType('description_editor', PARAM_RAW); + if (!empty($CFG->allowcohortthemes)) { + $themes = array_merge(array('' => get_string('forceno')), cohort_get_list_of_themes()); + $mform->addElement('select', 'theme', get_string('forcetheme'), $themes); + } + $mform->addElement('hidden', 'id'); $mform->setType('id', PARAM_INT); diff --git a/cohort/externallib.php b/cohort/externallib.php index 7c38a8fd21ba7..08f62ee78f24c 100644 --- a/cohort/externallib.php +++ b/cohort/externallib.php @@ -54,6 +54,10 @@ public static function create_cohorts_parameters() { 'description' => new external_value(PARAM_RAW, 'cohort description', VALUE_OPTIONAL), 'descriptionformat' => new external_format_value('description', VALUE_DEFAULT), 'visible' => new external_value(PARAM_BOOL, 'cohort visible', VALUE_OPTIONAL, true), + 'theme' => new external_value(PARAM_THEME, + 'the cohort theme. The allowcohortthemes setting must be enabled on Moodle', + VALUE_OPTIONAL + ), ) ) ) @@ -74,6 +78,8 @@ public static function create_cohorts($cohorts) { $params = self::validate_parameters(self::create_cohorts_parameters(), array('cohorts' => $cohorts)); + $availablethemes = cohort_get_list_of_themes(); + $transaction = $DB->start_delegated_transaction(); $syscontext = context_system::instance(); @@ -107,6 +113,15 @@ public static function create_cohorts($cohorts) { self::validate_context($context); require_capability('moodle/cohort:manage', $context); + // Make sure theme is valid. + if (isset($cohort->theme)) { + if (!empty($CFG->allowcohortthemes)) { + if (empty($availablethemes[$cohort->theme])) { + throw new moodle_exception('errorinvalidparam', 'webservice', '', 'theme'); + } + } + } + // Validate format. $cohort->descriptionformat = external_validate_format($cohort->descriptionformat); $cohort->id = cohort_add_cohort($cohort); @@ -137,6 +152,7 @@ public static function create_cohorts_returns() { 'description' => new external_value(PARAM_RAW, 'cohort description'), 'descriptionformat' => new external_format_value('description'), 'visible' => new external_value(PARAM_BOOL, 'cohort visible'), + 'theme' => new external_value(PARAM_THEME, 'cohort theme', VALUE_OPTIONAL), ) ) ); @@ -223,7 +239,7 @@ public static function get_cohorts_parameters() { * @since Moodle 2.5 */ public static function get_cohorts($cohortids = array()) { - global $DB; + global $DB, $CFG; $params = self::validate_parameters(self::get_cohorts_parameters(), array('cohortids' => $cohortids)); @@ -245,6 +261,11 @@ public static function get_cohorts($cohortids = array()) { throw new required_capability_exception($context, 'moodle/cohort:view', 'nopermissions', ''); } + // Only return theme when $CFG->allowcohortthemes is enabled. + if (!empty($cohort->theme) && empty($CFG->allowcohortthemes)) { + $cohort->theme = null; + } + list($cohort->description, $cohort->descriptionformat) = external_format_text($cohort->description, $cohort->descriptionformat, $context->id, 'cohort', 'description', $cohort->id); @@ -271,6 +292,7 @@ public static function get_cohorts_returns() { 'description' => new external_value(PARAM_RAW, 'cohort description'), 'descriptionformat' => new external_format_value('description'), 'visible' => new external_value(PARAM_BOOL, 'cohort visible'), + 'theme' => new external_value(PARAM_THEME, 'cohort theme', VALUE_OPTIONAL), ) ) ); @@ -367,6 +389,12 @@ public static function search_cohorts($query, $context, $includes = 'parents', $ $cohorts = array(); foreach ($results as $key => $cohort) { $cohortcontext = context::instance_by_id($cohort->contextid); + + // Only return theme when $CFG->allowcohortthemes is enabled. + if (!empty($cohort->theme) && empty($CFG->allowcohortthemes)) { + $cohort->theme = null; + } + if (!isset($cohort->description)) { $cohort->description = ''; } @@ -399,6 +427,7 @@ public static function search_cohorts_returns() { 'description' => new external_value(PARAM_RAW, 'cohort description'), 'descriptionformat' => new external_format_value('description'), 'visible' => new external_value(PARAM_BOOL, 'cohort visible'), + 'theme' => new external_value(PARAM_THEME, 'cohort theme', VALUE_OPTIONAL), )) ) )); @@ -432,6 +461,10 @@ public static function update_cohorts_parameters() { 'description' => new external_value(PARAM_RAW, 'cohort description', VALUE_OPTIONAL), 'descriptionformat' => new external_format_value('description', VALUE_DEFAULT), 'visible' => new external_value(PARAM_BOOL, 'cohort visible', VALUE_OPTIONAL), + 'theme' => new external_value(PARAM_THEME, + 'the cohort theme. The allowcohortthemes setting must be enabled on Moodle', + VALUE_OPTIONAL + ), ) ) ) @@ -452,6 +485,8 @@ public static function update_cohorts($cohorts) { $params = self::validate_parameters(self::update_cohorts_parameters(), array('cohorts' => $cohorts)); + $availablethemes = cohort_get_list_of_themes(); + $transaction = $DB->start_delegated_transaction(); $syscontext = context_system::instance(); @@ -490,6 +525,14 @@ public static function update_cohorts($cohorts) { require_capability('moodle/cohort:manage', $context); } + // Make sure theme is valid. + if (!empty($cohort->theme) && !empty($CFG->allowcohortthemes)) { + if (empty($availablethemes[$cohort->theme])) { + $debuginfo = 'The following cohort theme is not installed on this site: '.$cohort->theme; + throw new moodle_exception('errorinvalidparam', 'webservice', '', 'theme', $debuginfo); + } + } + if (!empty($cohort->description)) { $cohort->descriptionformat = external_validate_format($cohort->descriptionformat); } diff --git a/cohort/lib.php b/cohort/lib.php index f3c3a16a933f1..09d17dcc6b929 100644 --- a/cohort/lib.php +++ b/cohort/lib.php @@ -38,7 +38,7 @@ * @return int new cohort id */ function cohort_add_cohort($cohort) { - global $DB; + global $DB, $CFG; if (!isset($cohort->name)) { throw new coding_exception('Missing cohort name in cohort_add_cohort().'); @@ -58,6 +58,12 @@ function cohort_add_cohort($cohort) { if (empty($cohort->component)) { $cohort->component = ''; } + if (empty($CFG->allowcohortthemes) && isset($cohort->theme)) { + unset($cohort->theme); + } + if (empty($cohort->theme) || empty($CFG->allowcohortthemes)) { + $cohort->theme = ''; + } if (!isset($cohort->timecreated)) { $cohort->timecreated = time(); } @@ -83,11 +89,15 @@ function cohort_add_cohort($cohort) { * @return void */ function cohort_update_cohort($cohort) { - global $DB; + global $DB, $CFG; if (property_exists($cohort, 'component') and empty($cohort->component)) { // prevent NULLs $cohort->component = ''; } + // Only unset the cohort theme if allowcohortthemes is enabled to prevent the value from being overwritten. + if (empty($CFG->allowcohortthemes) && isset($cohort->theme)) { + unset($cohort->theme); + } $cohort->timemodified = time(); $DB->update_record('cohort', $cohort); @@ -478,6 +488,47 @@ function cohort_get_all_cohorts($page = 0, $perpage = 25, $search = '') { return array('totalcohorts' => $totalcohorts, 'cohorts' => $cohorts, 'allcohorts' => $allcohorts); } +/** + * Get all the cohorts where the given user is member of. + * + * @param int $userid + * @return array Array + */ +function cohort_get_user_cohorts($userid) { + 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)); +} + +/** + * Get the user cohort theme. + * + * If the user is member of one cohort, will return this cohort theme (if defined). + * If the user is member of 2 or more cohorts, will return the theme if all them have the same + * theme (null themes are ignored). + * + * @param int $userid + * @return string|null + */ +function cohort_get_user_cohort_theme($userid) { + $cohorts = cohort_get_user_cohorts($userid); + $theme = null; + foreach ($cohorts as $cohort) { + if (!empty($cohort->theme)) { + if (null === $theme) { + $theme = $cohort->theme; + } else if ($theme != $cohort->theme) { + return null; + } + } + } + return $theme; +} + /** * Returns list of contexts where cohorts are present but current user does not have capability to view/manage them. * @@ -568,3 +619,19 @@ function core_cohort_inplace_editable($itemtype, $itemid, $newvalue) { return \core_cohort\output\cohortidnumber::update($itemid, $newvalue); } } + +/** + * Returns a list of valid themes which can be displayed in a selector. + * + * @return array as (string)themename => (string)get_string_theme + */ +function cohort_get_list_of_themes() { + $themes = array(); + $allthemes = get_list_of_themes(); + foreach ($allthemes as $key => $theme) { + if (empty($theme->hidefromselector)) { + $themes[$key] = get_string('pluginname', 'theme_'.$theme->name); + } + } + return $themes; +} \ No newline at end of file diff --git a/cohort/tests/behat/upload_cohorts.feature b/cohort/tests/behat/upload_cohorts.feature index a400b019f3a62..2cc5074d2a230 100644 --- a/cohort/tests/behat/upload_cohorts.feature +++ b/cohort/tests/behat/upload_cohorts.feature @@ -50,7 +50,7 @@ Feature: A privileged user can create cohorts using a CSV file And the "class" attribute of "cohort name 5" "table_row" should contain "dimmed_text" And ".dimmed_text" "css_element" should not exist in the "cohort name 6" "table_row" - @javascript + @javascript @_file_upload Scenario: Upload cohorts with default category context as admin When I log in as "admin" And I navigate to "Cohorts" node in "Site administration > Users > Accounts" @@ -81,7 +81,7 @@ Feature: A privileged user can create cohorts using a CSV file | Cat 2 | cohort name 5 | cohortid5 | | 0 | Created manually | | Cat 3 | cohort name 6 | cohortid6 | | 0 | Created manually | - @javascript + @javascript @_file_upload Scenario: Upload cohorts with default category context as manager Given the following "users" exist: | username | firstname | lastname | email | @@ -107,7 +107,7 @@ Feature: A privileged user can create cohorts using a CSV file And I press "Upload cohorts" And I should see "Uploaded 6 cohorts" - @javascript + @javascript @_file_upload Scenario: Upload cohorts with conflicting id number Given the following "cohorts" exist: | name | idnumber | @@ -128,7 +128,7 @@ Feature: A privileged user can create cohorts using a CSV file | cohort name 6 | cohortid6 | | Cat 3 | | And "Upload cohorts" "button" should not exist - @javascript + @javascript @_file_upload Scenario: Upload cohorts with different ways of specifying context When I log in as "admin" And I navigate to "Cohorts" node in "Site administration > Users > Accounts" @@ -161,3 +161,35 @@ Feature: A privileged user can create cohorts using a CSV file And I should not see "not found or you" And I press "Upload cohorts" And I should see "Uploaded 5 cohorts" + + @javascript @_file_upload + Scenario: Upload cohorts with theme + When I log in as "admin" + And I navigate to "Cohorts" node in "Site administration > Users > Accounts" + And I follow "Upload cohorts" + And I upload "cohort/tests/fixtures/uploadcohorts4.csv" file to "File" filemanager + And I click on "Preview" "button" + Then the following should exist in the "previewuploadedcohorts" table: + | name | idnumber | description | Context | visible | theme | Status | + | cohort name 1 | cohortid1 | first description | System | 1 | boost | | + | cohort name 2 | cohortid2 | | System | 1 | | | + | cohort name 3 | cohortid3 | | Miscellaneous | 0 | boost | | + | cohort name 4 | cohortid4 | | Cat 1 | 1 | clean | | + | cohort name 5 | cohortid5 | | Cat 2 | 0 | | | + | cohort name 6 | cohortid6 | | Cat 3 | 1 | clean | | + And I press "Upload cohorts" + And I should see "Uploaded 6 cohorts" + And I press "Continue" + And the following should exist in the "cohorts" table: + | Name | Cohort ID | Description | Cohort size | Source | + | cohort name 1 | cohortid1 | first description | 0 | Created manually | + | cohort name 2 | cohortid2 | | 0 | Created manually | + And I follow "All cohorts" + And the following should exist in the "cohorts" table: + | Category | Name | Cohort ID | Description | Cohort size | Source | + | System | cohort name 1 | cohortid1 | first description | 0 | Created manually | + | System | cohort name 2 | cohortid2 | | 0 | Created manually | + | Miscellaneous | cohort name 3 | cohortid3 | | 0 | Created manually | + | Cat 1 | cohort name 4 | cohortid4 | | 0 | Created manually | + | Cat 2 | cohort name 5 | cohortid5 | | 0 | Created manually | + | Cat 3 | cohort name 6 | cohortid6 | | 0 | Created manually | diff --git a/cohort/tests/cohortlib_test.php b/cohort/tests/cohortlib_test.php index 04b37b1938520..107f40fc5460b 100644 --- a/cohort/tests/cohortlib_test.php +++ b/cohort/tests/cohortlib_test.php @@ -61,6 +61,7 @@ public function test_cohort_add_cohort() { $this->assertEquals($cohort->descriptionformat, $newcohort->descriptionformat); $this->assertNotEmpty($newcohort->timecreated); $this->assertSame($newcohort->component, ''); + $this->assertSame($newcohort->theme, ''); $this->assertSame($newcohort->timecreated, $newcohort->timemodified); } @@ -142,6 +143,7 @@ public function test_cohort_update_cohort() { $this->assertSame($cohort->descriptionformat, $newcohort->descriptionformat); $this->assertSame($cohort->timecreated, $newcohort->timecreated); $this->assertSame($cohort->component, $newcohort->component); + $this->assertSame($newcohort->theme, ''); $this->assertGreaterThan($newcohort->timecreated, $newcohort->timemodified); $this->assertLessThanOrEqual(time(), $newcohort->timemodified); } @@ -158,6 +160,7 @@ public function test_cohort_update_cohort_event() { $cohort->idnumber = 'testid'; $cohort->description = 'test cohort desc'; $cohort->descriptionformat = FORMAT_HTML; + $cohort->theme = ''; $id = cohort_add_cohort($cohort); $this->assertNotEmpty($id); @@ -168,6 +171,8 @@ public function test_cohort_update_cohort_event() { // Peform the update. cohort_update_cohort($cohort); + // Add again theme property to the cohort object for comparing it to the event snapshop. + $cohort->theme = ''; $events = $sink->get_events(); $sink->close(); @@ -651,4 +656,192 @@ public function test_cohort_get_available_cohorts() { $result = cohort_get_available_cohorts($course1ctx, COHORT_ALL, 0, 0, ''); $this->assertEquals(array($cohort1->id, $cohort2->id, $cohort4->id), array_keys($result)); } + + /** + * Create a cohort with allowcohortthemes enabled/disabled. + */ + public function test_cohort_add_theme_cohort() { + global $DB; + + $this->resetAfterTest(); + + // Theme is added when allowcohortthemes is enabled. + set_config('allowcohortthemes', 1); + set_config('theme', 'boost'); + + $systemctx = context_system::instance(); + $cohort1 = $this->getDataGenerator()->create_cohort(array('contextid' => $systemctx->id, 'name' => 'test cohort 1', + 'idnumber' => 'testid1', 'description' => 'test cohort desc', 'descriptionformat' => FORMAT_HTML, 'theme' => 'clean')); + + $id = cohort_add_cohort($cohort1); + $this->assertNotEmpty($id); + $newcohort = $DB->get_record('cohort', array('id' => $id)); + $this->assertEquals($cohort1->contextid, $newcohort->contextid); + $this->assertSame($cohort1->name, $newcohort->name); + $this->assertSame($cohort1->description, $newcohort->description); + $this->assertEquals($cohort1->descriptionformat, $newcohort->descriptionformat); + $this->assertNotEmpty($newcohort->theme); + $this->assertSame($cohort1->theme, $newcohort->theme); + $this->assertNotEmpty($newcohort->timecreated); + $this->assertSame($newcohort->component, ''); + $this->assertSame($newcohort->timecreated, $newcohort->timemodified); + + // Theme is not added when allowcohortthemes is disabled. + set_config('allowcohortthemes', 0); + + $cohort2 = $this->getDataGenerator()->create_cohort(array('contextid' => $systemctx->id, 'name' => 'test cohort 2', + 'idnumber' => 'testid2', 'description' => 'test cohort desc', 'descriptionformat' => FORMAT_HTML, 'theme' => 'clean')); + + $id = cohort_add_cohort($cohort2); + $this->assertNotEmpty($id); + $newcohort = $DB->get_record('cohort', array('id' => $id)); + $this->assertSame($cohort2->name, $newcohort->name); + $this->assertEmpty($newcohort->theme); + } + + /** + * Update a cohort with allowcohortthemes enabled/disabled. + */ + public function test_cohort_update_theme_cohort() { + global $DB; + + $this->resetAfterTest(); + + // Enable cohort themes. + set_config('allowcohortthemes', 1); + set_config('theme', 'boost'); + + $systemctx = context_system::instance(); + $cohort1 = $this->getDataGenerator()->create_cohort(array('contextid' => $systemctx->id, 'name' => 'test cohort 1', + 'idnumber' => 'testid1', 'description' => 'test cohort desc', 'descriptionformat' => FORMAT_HTML, 'theme' => 'clean')); + $id = cohort_add_cohort($cohort1); + $this->assertNotEmpty($id); + + // Theme is updated when allowcohortthemes is enabled. + $cohort1 = $DB->get_record('cohort', array('id' => $id)); + $cohort1->name = 'test cohort 1 updated'; + $cohort1->theme = 'more'; + cohort_update_cohort($cohort1); + $updatedcohort = $DB->get_record('cohort', array('id' => $id)); + $this->assertEquals($cohort1->contextid, $updatedcohort->contextid); + $this->assertSame($cohort1->name, $updatedcohort->name); + $this->assertSame($cohort1->description, $updatedcohort->description); + $this->assertNotEmpty($updatedcohort->theme); + $this->assertSame($cohort1->theme, $updatedcohort->theme); + + // Theme is not updated neither overwritten when allowcohortthemes is disabled. + set_config('allowcohortthemes', 0); + $cohort2 = $DB->get_record('cohort', array('id' => $id)); + $cohort2->theme = 'clean'; + cohort_update_cohort($cohort2); + $updatedcohort = $DB->get_record('cohort', array('id' => $id)); + $this->assertEquals($cohort2->contextid, $updatedcohort->contextid); + $this->assertNotEmpty($updatedcohort->theme); + $this->assertSame($cohort1->theme, $updatedcohort->theme); + } + + /** + * Validate the theme value depending on the user theme and cohorts. + */ + public function test_cohort_get_user_theme() { + global $DB, $PAGE, $USER; + + $this->resetAfterTest(); + + // Enable cohort themes. + set_config('allowuserthemes', 1); + set_config('allowcohortthemes', 1); + + $systemctx = context_system::instance(); + + $usercases = $this->get_user_theme_cases(); + foreach ($usercases as $casename => $casevalues) { + set_config('theme', $casevalues['sitetheme']); + // Create user. + $user = $this->getDataGenerator()->create_user(array('theme' => $casevalues['usertheme'])); + + // Create cohorts and add user as member. + $cohorts = array(); + foreach ($casevalues['cohorts'] as $cohorttheme) { + $cohort = $this->getDataGenerator()->create_cohort(array('contextid' => $systemctx->id, 'name' => 'Cohort', + 'idnumber' => '', 'description' => '', 'theme' => $cohorttheme)); + $cohorts[] = $cohort; + cohort_add_member($cohort->id, $user->id); + } + + // Get the theme and compare to the expected. + $this->setUser($user); + // Initialise user theme. + $USER = get_complete_user_data('id', $user->id); + // Initialise site theme. + $PAGE->reset_theme_and_output(); + $PAGE->initialise_theme_and_output(); + $result = $PAGE->__get('theme')->name; + $this->assertEquals($casevalues['expected'], $result, $casename); + } + } + + /** + * Some user cases for validating the expected theme depending on the cohorts, site and user values. + * + * The result is an array of: + * 'User case description' => [ + * 'usertheme' => '', // User theme. + * 'sitetheme' => '', // Site theme. + * 'cohorts' => [], // Cohort themes. + * 'expected' => '', // Expected value returned by cohort_get_user_cohort_theme. + * ] + * + * @return array + */ + private function get_user_theme_cases() { + return [ + 'User not a member of any cohort' => [ + 'usertheme' => '', + 'sitetheme' => 'boost', + 'cohorts' => [], + 'expected' => 'boost', + ], + 'User member of one cohort which has a theme set' => [ + 'usertheme' => '', + 'sitetheme' => 'boost', + 'cohorts' => [ + 'clean', + ], + 'expected' => 'clean', + ], + 'User member of one cohort which has a theme set, and one without a theme' => [ + 'usertheme' => '', + 'sitetheme' => 'boost', + 'cohorts' => [ + 'clean', + '', + ], + 'expected' => 'clean', + ], + 'User member of one cohort which has a theme set, and one with a different theme' => [ + 'usertheme' => '', + 'sitetheme' => 'boost', + 'cohorts' => [ + 'clean', + 'someother', + ], + 'expected' => 'boost', + ], + 'User with a theme but not a member of any cohort' => [ + 'usertheme' => 'more', + 'sitetheme' => 'boost', + 'cohorts' => [], + 'expected' => 'more', + ], + 'User with a theme and member of one cohort which has a theme set' => [ + 'usertheme' => 'more', + 'sitetheme' => 'boost', + 'cohorts' => [ + 'clean', + ], + 'expected' => 'more', + ], + ]; + } } diff --git a/cohort/tests/externallib_test.php b/cohort/tests/externallib_test.php index 85b2f024b1d8d..a6717b87b116a 100644 --- a/cohort/tests/externallib_test.php +++ b/cohort/tests/externallib_test.php @@ -42,6 +42,8 @@ public function test_create_cohorts() { $this->resetAfterTest(true); + set_config('allowcohortthemes', 1); + $contextid = context_system::instance()->id; $category = $this->getDataGenerator()->create_category(); @@ -49,7 +51,8 @@ public function test_create_cohorts() { 'categorytype' => array('type' => 'id', 'value' => $category->id), 'name' => 'cohort test 1', 'idnumber' => 'cohorttest1', - 'description' => 'This is a description for cohorttest1' + 'description' => 'This is a description for cohorttest1', + 'theme' => 'clean' ); $cohort2 = array( @@ -68,6 +71,14 @@ public function test_create_cohorts() { ); $roleid = $this->assignUserCapability('moodle/cohort:manage', $contextid); + $cohort4 = array( + 'categorytype' => array('type' => 'id', 'value' => $category->id), + 'name' => 'cohort test 4', + 'idnumber' => 'cohorttest4', + 'description' => 'This is a description for cohorttest4', + 'theme' => 'clean' + ); + // Call the external function. $this->setCurrentTimeStart(); $createdcohorts = core_cohort_external::create_cohorts(array($cohort1, $cohort2)); @@ -85,11 +96,15 @@ public function test_create_cohorts() { $this->assertEquals($dbcohort->name, $cohort1['name']); $this->assertEquals($dbcohort->description, $cohort1['description']); $this->assertEquals($dbcohort->visible, 1); // Field was not specified, ensure it is visible by default. + // As $CFG->allowcohortthemes is enabled, theme must be initialised. + $this->assertEquals($dbcohort->theme, $cohort1['theme']); } else if ($createdcohort['idnumber'] == $cohort2['idnumber']) { $this->assertEquals($dbcohort->contextid, context_system::instance()->id); $this->assertEquals($dbcohort->name, $cohort2['name']); $this->assertEquals($dbcohort->description, $cohort2['description']); $this->assertEquals($dbcohort->visible, $cohort2['visible']); + // Although $CFG->allowcohortthemes is enabled, no theme is defined for this cohort. + $this->assertEquals($dbcohort->theme, ''); } else { $this->fail('Unrecognised cohort found'); } @@ -97,6 +112,23 @@ public function test_create_cohorts() { $this->assertTimeCurrent($dbcohort->timemodified); } + // Call when $CFG->allowcohortthemes is disabled. + set_config('allowcohortthemes', 0); + $createdcohorts = core_cohort_external::create_cohorts(array($cohort4)); + $createdcohorts = external_api::clean_returnvalue(core_cohort_external::create_cohorts_returns(), $createdcohorts); + foreach ($createdcohorts as $createdcohort) { + $dbcohort = $DB->get_record('cohort', array('id' => $createdcohort['id'])); + if ($createdcohort['idnumber'] == $cohort4['idnumber']) { + $conid = $DB->get_field('context', 'id', array('instanceid' => $cohort4['categorytype']['value'], + 'contextlevel' => CONTEXT_COURSECAT)); + $this->assertEquals($dbcohort->contextid, $conid); + $this->assertEquals($dbcohort->name, $cohort4['name']); + $this->assertEquals($dbcohort->description, $cohort4['description']); + $this->assertEquals($dbcohort->visible, 1); // Field was not specified, ensure it is visible by default. + $this->assertEquals($dbcohort->theme, ''); // As $CFG->allowcohortthemes is disabled, theme must be empty. + } + } + // Call without required capability. $this->unassignUserCapability('moodle/cohort:manage', $contextid, $roleid); $createdcohorts = core_cohort_external::create_cohorts(array($cohort3)); @@ -143,11 +175,14 @@ public function test_get_cohorts() { $this->resetAfterTest(true); + set_config('allowcohortthemes', 1); + $cohort1 = array( 'contextid' => 1, 'name' => 'cohortnametest1', 'idnumber' => 'idnumbertest1', - 'description' => 'This is a description for cohort 1' + 'description' => 'This is a description for cohort 1', + 'theme' => 'clean' ); $cohort1 = self::getDataGenerator()->create_cohort($cohort1); $cohort2 = self::getDataGenerator()->create_cohort(); @@ -168,6 +203,7 @@ public function test_get_cohorts() { $this->assertEquals($cohort1->name, $enrolledcohort['name']); $this->assertEquals($cohort1->description, $enrolledcohort['description']); $this->assertEquals($cohort1->visible, $enrolledcohort['visible']); + $this->assertEquals($cohort1->theme, $enrolledcohort['theme']); } } @@ -181,6 +217,17 @@ public function test_get_cohorts() { // Check we retrieve the good total number of enrolled cohorts + no error on capability. $this->assertEquals(2, count($returnedcohorts)); + + // Check when allowcohortstheme is disabled, theme is not returned. + set_config('allowcohortthemes', 0); + $returnedcohorts = core_cohort_external::get_cohorts(array( + $cohort1->id)); + $returnedcohorts = external_api::clean_returnvalue(core_cohort_external::get_cohorts_returns(), $returnedcohorts); + foreach ($returnedcohorts as $enrolledcohort) { + if ($enrolledcohort['idnumber'] == $cohort1->idnumber) { + $this->assertNull($enrolledcohort['theme']); + } + } } /** @@ -193,6 +240,8 @@ public function test_update_cohorts() { $this->resetAfterTest(true); + set_config('allowcohortthemes', 0); + $cohort1 = self::getDataGenerator()->create_cohort(array('visible' => 0)); $cohort1 = array( @@ -200,7 +249,8 @@ public function test_update_cohorts() { 'categorytype' => array('type' => 'id', 'value' => '1'), 'name' => 'cohortnametest1', 'idnumber' => 'idnumbertest1', - 'description' => 'This is a description for cohort 1' + 'description' => 'This is a description for cohort 1', + 'theme' => 'clean' ); $context = context_system::instance(); @@ -217,6 +267,7 @@ public function test_update_cohorts() { $this->assertEquals($dbcohort->idnumber, $cohort1['idnumber']); $this->assertEquals($dbcohort->description, $cohort1['description']); $this->assertEquals($dbcohort->visible, 0); + $this->assertEmpty($dbcohort->theme); // 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))); @@ -226,6 +277,18 @@ public function test_update_cohorts() { $dbcohort = $DB->get_record('cohort', array('id' => $cohort1['id'])); $this->assertEquals(1, $dbcohort->visible); + // Call when $CFG->allowcohortthemes is enabled. + set_config('allowcohortthemes', 1); + core_cohort_external::update_cohorts(array($cohort1 + array('theme' => 'clean'))); + $dbcohort = $DB->get_record('cohort', array('id' => $cohort1['id'])); + $this->assertEquals('clean', $dbcohort->theme); + + // Call when $CFG->allowcohortthemes is disabled. + set_config('allowcohortthemes', 0); + core_cohort_external::update_cohorts(array($cohort1 + array('theme' => 'more'))); + $dbcohort = $DB->get_record('cohort', array('id' => $cohort1['id'])); + $this->assertEquals('clean', $dbcohort->theme); + // Call without required capability. $this->unassignUserCapability('moodle/cohort:manage', $context->id, $roleid); core_cohort_external::update_cohorts(array($cohort1)); diff --git a/cohort/tests/fixtures/uploadcohorts4.csv b/cohort/tests/fixtures/uploadcohorts4.csv new file mode 100644 index 0000000000000..1aba4ccd42ac6 --- /dev/null +++ b/cohort/tests/fixtures/uploadcohorts4.csv @@ -0,0 +1,7 @@ +name,idnumber,description,category,visible,theme +cohort name 1,cohortid1,first description,,,boost +cohort name 2,cohortid2,,,, +cohort name 3,cohortid3,,Miscellaneous,no,boost +cohort name 4,cohortid4,,CAT1,yes,clean +cohort name 5,cohortid5,,CAT2,0, +cohort name 6,cohortid6,,CAT3,1,clean diff --git a/cohort/upload_form.php b/cohort/upload_form.php index a7ee3a81afb4d..dddb26ecda2cf 100644 --- a/cohort/upload_form.php +++ b/cohort/upload_form.php @@ -359,7 +359,7 @@ protected function process_upload_file($file, $encoding, $delimiter, $defaultcon $columns = $cir->get_columns(); // Check that columns include 'name' and warn about extra columns. - $allowedcolumns = array('contextid', 'name', 'idnumber', 'description', 'descriptionformat', 'visible'); + $allowedcolumns = array('contextid', 'name', 'idnumber', 'description', 'descriptionformat', 'visible', 'theme'); $additionalcolumns = array('context', 'category', 'category_id', 'category_idnumber', 'category_path'); $displaycolumns = array(); $extracolumns = array(); @@ -424,6 +424,13 @@ protected function process_upload_file($file, $encoding, $delimiter, $defaultcon $cohorts[$rownum]['errors'][] = new lang_string('namefieldempty', 'cohort'); } + if (!empty($hash['theme']) && !empty($CFG->allowcohortthemes)) { + $availablethemes = cohort_get_list_of_themes(); + if (empty($availablethemes[$hash['theme']])) { + $cohorts[$rownum]['errors'][] = new lang_string('invalidtheme', 'cohort'); + } + } + $cohorts[$rownum]['data'] = array_intersect_key($hash, $cohorts[0]['data']); $haserrors = $haserrors || !empty($cohorts[$rownum]['errors']); $haswarnings = $haswarnings || !empty($cohorts[$rownum]['warnings']); @@ -466,6 +473,9 @@ protected function clean_cohort_data(&$hash) { $hash[$key] = clean_param($value, PARAM_BOOL) ? 1 : 0; } break; + case 'theme': + $hash[$key] = core_text::substr(clean_param($value, PARAM_TEXT), 0, 50); + break; } } } diff --git a/config-dist.php b/config-dist.php index 5308052c9156d..7240c16566393 100644 --- a/config-dist.php +++ b/config-dist.php @@ -418,8 +418,10 @@ // example) in sites where the user theme should override all other theme // settings for accessibility reasons. You can also disable types of themes // (other than site) by removing them from the array. The default setting is: -// $CFG->themeorder = array('course', 'category', 'session', 'user', 'site'); -// NOTE: course, category, session, user themes still require the +// +// $CFG->themeorder = array('course', 'category', 'session', 'user', 'cohort', 'site'); +// +// NOTE: course, category, session, user, cohort themes still require the // respective settings to be enabled // // It is possible to add extra themes directory stored outside of $CFG->dirroot. diff --git a/lang/en/admin.php b/lang/en/admin.php index eebf74f97f10d..f8289e01835d6 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -49,6 +49,7 @@ $string['allowbeforeblockdesc'] = 'By default, entries in the blocked IPs list are matched first. If this option is enabled, entries in the allowed IPs list are processed before the blocked list.'; $string['allowblockstodock'] = 'Allow blocks to use the dock'; $string['allowcategorythemes'] = 'Allow category themes'; +$string['allowcohortthemes'] = 'Allow cohort themes'; $string['allowcoursethemes'] = 'Allow course themes'; $string['allowediplist'] = 'Allowed IP list'; $string['allowedemaildomains'] = 'Allowed email domains'; @@ -144,6 +145,7 @@ $string['configallowassign'] = 'You can allow people who have the roles on the left side to assign some of the column roles to other people'; $string['configallowblockstodock'] = 'If enabled and supported by the selected theme users can choose to move blocks to a special dock.'; $string['configallowcategorythemes'] = 'If you enable this, then themes can be set at the category level. This will affect all child categories and courses unless they have specifically set their own theme. WARNING: Enabling category themes may affect performance.'; +$string['configallowcohortthemes'] = 'If you enable this, then themes can be set at the cohort level. This will affect all users with only one cohort or more than one but with the same theme.'; $string['configallowcoursethemes'] = 'If you enable this, then courses will be allowed to set their own themes. Course themes override all other theme choices (site, user, or session themes)'; $string['configallowedemaildomains'] = 'List email domains that are allowed to be disclosed in the "From" section of outgoing email. The default of "Empty" will use the No-reply address for all outgoing email. The use of wildcards is allowed e.g. *.example.com will allow emails sent from any subdomain of example.com, but not example.com itself. This will require separate entry.'; $string['configallowemailaddresses'] = 'To restrict new email addresses to particular domains, list them here separated by spaces. All other domains will be rejected. To allow subdomains, add the domain with a preceding \'.\'. To allow a root domain together with its subdomains, add the domain twice - once with a preceding \'.\' and once without e.g. .ourcollege.edu.au ourcollege.edu.au.'; diff --git a/lang/en/cohort.php b/lang/en/cohort.php index 49e78455da3db..ee7503b30c510 100644 --- a/lang/en/cohort.php +++ b/lang/en/cohort.php @@ -58,6 +58,7 @@ $string['eventcohortmemberremoved'] = 'User removed from a cohort'; $string['eventcohortupdated'] = 'Cohort updated'; $string['external'] = 'External cohort'; +$string['invalidtheme'] = 'Cohort theme does not exist'; $string['idnumber'] = 'Cohort ID'; $string['memberscount'] = 'Cohort size'; $string['name'] = 'Name'; diff --git a/lib/db/install.xml b/lib/db/install.xml index abd5c93d475bd..b9b900372d2c0 100644 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -1,5 +1,5 @@ - @@ -2254,6 +2254,7 @@ + diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 08f2f750a90a9..edafff2028b3e 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -2200,5 +2200,21 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2018032700.00); } + if ($oldversion < 2018040500.01) { + + // Define field indexpriority to be added to search_index_requests. Allow null initially. + $table = new xmldb_table('cohort'); + $field = new xmldb_field('theme', XMLDB_TYPE_CHAR, '50', + null, null, null, null, 'timemodified'); + + // Conditionally add field. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Main savepoint reached. + upgrade_main_savepoint(true, 2018040500.01); + } + return true; } diff --git a/lib/moodlelib.php b/lib/moodlelib.php index d6a5dab7fa9ef..0d48e7e3a0412 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -4694,6 +4694,14 @@ function get_complete_user_data($field, $value, $mnethostid = null) { } } + // Add cohort theme. + if (!empty($CFG->allowcohortthemes)) { + require_once($CFG->dirroot . '/cohort/lib.php'); + if ($cohorttheme = cohort_get_user_cohort_theme($user->id)) { + $user->cohorttheme = $cohorttheme; + } + } + // Add the custom profile fields to the user record. $user->profile = array(); if (!isguestuser($user)) { diff --git a/lib/pagelib.php b/lib/pagelib.php index bd0c9f063f9bc..dab291a2047bd 100644 --- a/lib/pagelib.php +++ b/lib/pagelib.php @@ -1608,7 +1608,7 @@ protected function resolve_theme() { global $CFG, $USER, $SESSION; if (empty($CFG->themeorder)) { - $themeorder = array('course', 'category', 'session', 'user', 'site'); + $themeorder = array('course', 'category', 'session', 'user', 'cohort', 'site'); } else { $themeorder = $CFG->themeorder; // Just in case, make sure we always use the site theme if nothing else matched. @@ -1666,6 +1666,12 @@ protected function resolve_theme() { } break; + case 'cohort': + if (!empty($CFG->allowcohortthemes) && !empty($USER->cohorttheme) && !$hascustomdevicetheme) { + return $USER->cohorttheme; + } + break; + case 'site': if ($mnetpeertheme) { return $mnetpeertheme; diff --git a/version.php b/version.php index f6a151f0d845d..d0a43048912f6 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2018040500.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2018040500.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes.