MDL-61601 cohort: Add theme support for cohorts
authorSara Arjona <sara@moodle.com>
Tue, 23 Jan 2018 13:54:18 +0000 (14:54 +0100)
committerSara Arjona <sara@moodle.com>
Fri, 6 Apr 2018 06:28:01 +0000 (08:28 +0200)
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.

18 files changed:
admin/settings/appearance.php
cohort/classes/external/cohort_summary_exporter.php
cohort/edit_form.php
cohort/externallib.php
cohort/lib.php
cohort/tests/behat/upload_cohorts.feature
cohort/tests/cohortlib_test.php
cohort/tests/externallib_test.php
cohort/tests/fixtures/uploadcohorts4.csv [new file with mode: 0644]
cohort/upload_form.php
config-dist.php
lang/en/admin.php
lang/en/cohort.php
lib/db/install.xml
lib/db/upgrade.php
lib/moodlelib.php
lib/pagelib.php
version.php

index 7486db8..db05c1d 100644 (file)
@@ -22,6 +22,7 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) { // sp
     $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));
index 55e7529..cbd2101 100644 (file)
@@ -64,6 +64,10 @@ class cohort_summary_exporter extends \core\external\exporter {
             ),
             'visible' => array(
                 'type' => PARAM_BOOL,
+            ),
+            'theme' => array(
+                'type' => PARAM_THEME,
+                'null' => NULL_ALLOWED
             )
         );
     }
index d4abb88..83df3a7 100644 (file)
@@ -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 @@ class cohort_edit_form extends moodleform {
         $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);
 
index 7c38a8f..08f62ee 100644 (file)
@@ -54,6 +54,10 @@ class core_cohort_external extends external_api {
                             '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 @@ class core_cohort_external extends external_api {
 
         $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 @@ class core_cohort_external extends external_api {
             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 @@ class core_cohort_external extends external_api {
                     '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 @@ class core_cohort_external extends external_api {
      * @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 @@ class core_cohort_external extends external_api {
                 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 @@ class core_cohort_external extends external_api {
                     '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 @@ class core_cohort_external extends external_api {
         $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 @@ class core_cohort_external extends external_api {
                     '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 @@ class core_cohort_external extends external_api {
                             '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 @@ class core_cohort_external extends external_api {
 
         $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 @@ class core_cohort_external extends external_api {
                 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);
             }
index f3c3a16..09d17dc 100644 (file)
@@ -38,7 +38,7 @@ define('COHORT_WITH_NOTENROLLED_MEMBERS_ONLY', 23);
  * @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
index a400b01..2cc5074 100644 (file)
@@ -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 |
index 04b37b1..107f40f 100644 (file)
@@ -61,6 +61,7 @@ class core_cohort_cohortlib_testcase extends advanced_testcase {
         $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 @@ class core_cohort_cohortlib_testcase extends advanced_testcase {
         $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 @@ class core_cohort_cohortlib_testcase extends advanced_testcase {
         $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 @@ class core_cohort_cohortlib_testcase extends advanced_testcase {
 
         // 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 @@ class core_cohort_cohortlib_testcase extends advanced_testcase {
         $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',
+          ],
+        ];
+    }
 }
index 85b2f02..a6717b8 100644 (file)
@@ -42,6 +42,8 @@ class core_cohort_externallib_testcase extends externallib_advanced_testcase {
 
         $this->resetAfterTest(true);
 
+        set_config('allowcohortthemes', 1);
+
         $contextid = context_system::instance()->id;
         $category = $this->getDataGenerator()->create_category();
 
@@ -49,7 +51,8 @@ class core_cohort_externallib_testcase extends externallib_advanced_testcase {
             '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 @@ class core_cohort_externallib_testcase extends externallib_advanced_testcase {
             );
         $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 @@ class core_cohort_externallib_testcase extends externallib_advanced_testcase {
                 $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 @@ class core_cohort_externallib_testcase extends externallib_advanced_testcase {
             $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 @@ class core_cohort_externallib_testcase extends externallib_advanced_testcase {
 
         $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 @@ class core_cohort_externallib_testcase extends externallib_advanced_testcase {
                 $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 @@ class core_cohort_externallib_testcase extends externallib_advanced_testcase {
 
         // 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 @@ class core_cohort_externallib_testcase extends externallib_advanced_testcase {
 
         $this->resetAfterTest(true);
 
+        set_config('allowcohortthemes', 0);
+
         $cohort1 = self::getDataGenerator()->create_cohort(array('visible' => 0));
 
         $cohort1 = array(
@@ -200,7 +249,8 @@ class core_cohort_externallib_testcase extends externallib_advanced_testcase {
             '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 @@ class core_cohort_externallib_testcase extends externallib_advanced_testcase {
         $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 @@ class core_cohort_externallib_testcase extends externallib_advanced_testcase {
         $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 (file)
index 0000000..1aba4cc
--- /dev/null
@@ -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
index a7ee3a8..dddb26e 100644 (file)
@@ -359,7 +359,7 @@ class cohort_upload_form extends moodleform {
         $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 @@ class cohort_upload_form extends moodleform {
                 $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 @@ class cohort_upload_form extends moodleform {
                         $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;
             }
         }
     }
index 5308052..7240c16 100644 (file)
@@ -418,8 +418,10 @@ $CFG->admin = 'admin';
 // 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.
index eebf74f..f8289e0 100644 (file)
@@ -49,6 +49,7 @@ $string['allowbeforeblock'] = 'Allowed list will be processed first';
 $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['configallcountrycodes'] = 'This is the list of countries that may be se
 $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.';
index 49e7845..ee7503b 100644 (file)
@@ -58,6 +58,7 @@ $string['eventcohortmemberadded'] = 'User added to a cohort';
 $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';
index abd5c93..b9b9003 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20180222" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20180403" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
         <FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false" COMMENT="Component (plugintype_pluignname) that manages the cohort, manual modifications are allowed only when set to NULL"/>
         <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="theme" TYPE="char" LENGTH="50" NOTNULL="false" SEQUENCE="false"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
index 08f2f75..edafff2 100644 (file)
@@ -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;
 }
index d6a5dab..0d48e7e 100644 (file)
@@ -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)) {
index bd0c9f0..dab291a 100644 (file)
@@ -1608,7 +1608,7 @@ class moodle_page {
         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 @@ class moodle_page {
                     }
                 break;
 
+                case 'cohort':
+                    if (!empty($CFG->allowcohortthemes) && !empty($USER->cohorttheme) && !$hascustomdevicetheme) {
+                        return $USER->cohorttheme;
+                    }
+                break;
+
                 case 'site':
                     if ($mnetpeertheme) {
                         return $mnetpeertheme;
index f6a151f..d0a4304 100644 (file)
@@ -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.