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 6903e74855d92..e3156b05f85eb 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.