Merge branch 'MDL-56501-master' of git://github.com/lameze/moodle
authorAndrew Nicols <andrew@nicols.co.uk>
Mon, 31 Oct 2016 06:10:37 +0000 (14:10 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Mon, 31 Oct 2016 06:10:37 +0000 (14:10 +0800)
30 files changed:
cache/classes/dummystore.php
cache/classes/factory.php
cache/classes/helper.php
cache/classes/store.php
cache/locallib.php
cache/stores/apcu/lib.php
cache/stores/apcu/tests/apcu_test.php
cache/stores/file/lib.php
cache/stores/memcache/lib.php
cache/stores/memcache/tests/memcache_test.php
cache/stores/memcached/lib.php
cache/stores/memcached/tests/memcached_test.php
cache/stores/mongodb/lib.php
cache/stores/mongodb/tests/mongodb_test.php
cache/stores/session/lib.php
cache/stores/static/lib.php
cache/tests/cache_test.php
cache/tests/fixtures/lib.php
cache/tests/fixtures/stores.php
cache/upgrade.txt
lib/form/select.php
lib/form/selectgroups.php
lib/form/selectwithlink.php
lib/testing/classes/util.php
mod/assign/externallib.php
theme/boost/templates/core_form/element-select-inline.mustache
theme/boost/templates/core_form/element-select.mustache
theme/boost/templates/core_form/element-selectgroups-inline.mustache
theme/boost/templates/core_form/element-selectgroups.mustache
theme/boost/templates/core_form/element-selectwithlink.mustache

index 1b925a4..c11e197 100644 (file)
@@ -262,6 +262,15 @@ class cachestore_dummy extends cache_store {
         return $cache;
     }
 
+    /**
+     * Generates the appropriate configuration required for unit testing.
+     *
+     * @return array Array of unit test configuration data to be used by initialise().
+     */
+    public static function unit_test_configuration() {
+        return [];
+    }
+
     /**
      * Returns the name of this instance.
      * @return string
index ac20bec..4ba6565 100644 (file)
@@ -277,6 +277,9 @@ class cache_factory {
         if (!array_key_exists($name, $this->stores)) {
             // Properties: name, plugin, configuration, class.
             $class = $details['class'];
+            if (!$class::are_requirements_met()) {
+                return false;
+            }
             $store = new $class($details['name'], $details['configuration']);
             $this->stores[$name] = $store;
         }
index c25369e..30f8235 100644 (file)
@@ -502,15 +502,18 @@ class cache_helper {
         $store = $stores[$storename];
         $class = $store['class'];
 
+
+        // We check are_requirements_met although we expect is_ready is going to check as well.
+        if (!$class::are_requirements_met()) {
+            return false;
+        }
         // Found the store: is it ready?
         /* @var cache_store $instance */
         $instance = new $class($store['name'], $store['configuration']);
-        // We check are_requirements_met although we expect is_ready is going to check as well.
-        if (!$instance::are_requirements_met() || !$instance->is_ready()) {
+        if (!$instance->is_ready()) {
             unset($instance);
             return false;
         }
-
         foreach ($config->get_definitions_by_store($storename) as $id => $definition) {
             $definition = cache_definition::load($id, $definition);
             $definitioninstance = clone($instance);
index eec660f..4fcb03f 100644 (file)
@@ -81,15 +81,11 @@ interface cache_store_interface {
     public static function initialise_test_instance(cache_definition $definition);
 
     /**
-     * Initialises a test instance for unit tests.
+     * Generates the appropriate configuration required for unit testing.
      *
-     * This differs from initialise_test_instance in that it doesn't rely on interacting with the config table.
-     *
-     * @since 2.8
-     * @param cache_definition $definition
-     * @return cache_store|false
+     * @return array Array of unit test configuration data to be used by initialise().
      */
-    public static function initialise_unit_test_instance(cache_definition $definition);
+    public static function unit_test_configuration();
 }
 
 /**
@@ -369,20 +365,6 @@ abstract class cache_store implements cache_store_interface {
         return clone($this);
     }
 
-    /**
-     * Initialises a test instance for unit tests.
-     *
-     * This differs from initialise_test_instance in that it doesn't rely on interacting with the config table.
-     * By default however it calls initialise_test_instance to support backwards compatibility.
-     *
-     * @since 2.8
-     * @param cache_definition $definition
-     * @return cache_store|false
-     */
-    public static function initialise_unit_test_instance(cache_definition $definition) {
-        return static::initialise_test_instance($definition);
-    }
-
     /**
      * Can be overridden to return any warnings this store instance should make to the admin.
      *
index e97dd56..90a8a8b 100644 (file)
@@ -684,33 +684,36 @@ abstract class cache_administration_helper extends cache_helper {
         $locks = $instance->get_locks();
         foreach ($stores as $name => $details) {
             $class = $details['class'];
-            $store = new $class($details['name'], $details['configuration']);
+            $store = false;
+            if ($class::are_requirements_met()) {
+                $store = new $class($details['name'], $details['configuration']);
+            }
             $lock = (isset($details['lock'])) ? $locks[$details['lock']] : $instance->get_default_lock();
             $record = array(
                 'name' => $name,
                 'plugin' => $details['plugin'],
                 'default' => $details['default'],
-                'isready' => $store->is_ready(),
+                'isready' => $store ? $store->is_ready() : false,
                 'requirementsmet' => $class::are_requirements_met(),
                 'mappings' => 0,
                 'lock' => $lock,
                 'modes' => array(
                     cache_store::MODE_APPLICATION =>
-                        ($store->get_supported_modes($return) & cache_store::MODE_APPLICATION) == cache_store::MODE_APPLICATION,
+                        ($class::get_supported_modes($return) & cache_store::MODE_APPLICATION) == cache_store::MODE_APPLICATION,
                     cache_store::MODE_SESSION =>
-                        ($store->get_supported_modes($return) & cache_store::MODE_SESSION) == cache_store::MODE_SESSION,
+                        ($class::get_supported_modes($return) & cache_store::MODE_SESSION) == cache_store::MODE_SESSION,
                     cache_store::MODE_REQUEST =>
-                        ($store->get_supported_modes($return) & cache_store::MODE_REQUEST) == cache_store::MODE_REQUEST,
+                        ($class::get_supported_modes($return) & cache_store::MODE_REQUEST) == cache_store::MODE_REQUEST,
                 ),
                 'supports' => array(
-                    'multipleidentifiers' => $store->supports_multiple_identifiers(),
-                    'dataguarantee' => $store->supports_data_guarantee(),
-                    'nativettl' => $store->supports_native_ttl(),
+                    'multipleidentifiers' => $store ? $store->supports_multiple_identifiers() : false,
+                    'dataguarantee' => $store ? $store->supports_data_guarantee() : false,
+                    'nativettl' => $store ? $store->supports_native_ttl() : false,
                     'nativelocking' => ($store instanceof cache_is_lockable),
                     'keyawareness' => ($store instanceof cache_is_key_aware),
                     'searchable' => ($store instanceof cache_is_searchable)
                 ),
-                'warnings' => $store->get_warnings()
+                'warnings' => $store ? $store->get_warnings() : array()
             );
             if (empty($details['default'])) {
                 $return[$name] = $record;
index 27cd2e6..cbb20db 100644 (file)
@@ -369,23 +369,12 @@ class cachestore_apcu extends cache_store implements cache_is_key_aware, cache_i
     }
 
     /**
-     * Generates an instance of the cache store that can be used for testing.
+     * Generates the appropriate configuration required for unit testing.
      *
-     * @param cache_definition $definition
-     * @return cachestore_apcu|false
+     * @return array Array of unit test configuration data to be used by initialise().
      */
-    public static function initialise_unit_test_instance(cache_definition $definition) {
-        if (!self::are_requirements_met()) {
-            return false;
-        }
-
-        $store = new cachestore_apcu('Test APCu', array('prefix' => 'phpunit'));
-        if (!$store->is_ready()) {
-            return false;
-        }
-        $store->initialise($definition);
-
-        return $store;
+    public static function unit_test_configuration() {
+        return array('prefix' => 'phpunit');
     }
 
     /**
@@ -416,4 +405,16 @@ class cachestore_apcu extends cache_store implements cache_is_key_aware, cache_i
         }
         $editform->set_data($data);
     }
+
+    /**
+     * Returns true if this cache store instance is both suitable for testing, and ready for testing.
+     *
+     * Cache stores that support being used as the default store for unit and acceptance testing should
+     * override this function and return true if there requirements have been met.
+     *
+     * @return bool
+     */
+    public static function ready_to_be_used_for_testing() {
+        return true;
+    }
 }
index 36fc1ae..4903d3b 100644 (file)
@@ -57,7 +57,8 @@ class cachestore_apcu_test extends cachestore_tests {
      */
     public function test_cross_application_interaction() {
         $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_apcu', 'phpunit_test');
-        $instance = cachestore_apcu::initialise_unit_test_instance($definition);
+        $instance = new cachestore_apcu('Test', cachestore_apcu::unit_test_configuration());
+        $instance->initialise($definition);
 
         // Test purge with custom data.
         $this->assertTrue($instance->set('test', 'monster'));
@@ -75,9 +76,12 @@ class cachestore_apcu_test extends cachestore_tests {
 
     public function test_different_caches_have_different_prefixes() {
         $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_apcu', 'phpunit_test');
-        $instance = cachestore_apcu::initialise_unit_test_instance($definition);
+        $instance = new cachestore_apcu('Test', cachestore_apcu::unit_test_configuration());
+        $instance->initialise($definition);
+
         $definition2 = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_apcu', 'phpunit_test2');
-        $instance2 = cachestore_apcu::initialise_unit_test_instance($definition2);
+        $instance2 = new cachestore_apcu('Test', cachestore_apcu::unit_test_configuration());
+        $instance2->initialise($definition2);
 
         $instance->set('test1', 1);
         $this->assertFalse($instance2->get('test1'));
index 67a6642..91a0839 100644 (file)
@@ -677,6 +677,15 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
         return $cache;
     }
 
+    /**
+     * Generates the appropriate configuration required for unit testing.
+     *
+     * @return array Array of unit test configuration data to be used by initialise().
+     */
+    public static function unit_test_configuration() {
+        return array();
+    }
+
     /**
      * Writes your madness to a file.
      *
index e4ead7f..6f89d1a 100644 (file)
@@ -582,24 +582,16 @@ class cachestore_memcache extends cache_store implements cache_is_configurable {
     }
 
     /**
-     * Creates a test instance for unit tests if possible.
-     * @param cache_definition $definition
-     * @return bool|cachestore_memcache
+     * Generates the appropriate configuration required for unit testing.
+     *
+     * @return array Array of unit test configuration data to be used by initialise().
      */
-    public static function initialise_unit_test_instance(cache_definition $definition) {
-        if (!self::are_requirements_met()) {
-            return false;
-        }
+    public static function unit_test_configuration() {
+        // If the configuration is not defined correctly, return only the configuration know about.
         if (!defined('TEST_CACHESTORE_MEMCACHE_TESTSERVERS')) {
-            return false;
+            return [];
         }
-        $configuration = array();
-        $configuration['servers'] = explode("\n", TEST_CACHESTORE_MEMCACHE_TESTSERVERS);
-
-        $store = new cachestore_memcache('Test memcache', $configuration);
-        $store->initialise($definition);
-
-        return $store;
+        return ['servers' => explode("\n", TEST_CACHESTORE_MEMCACHE_TESTSERVERS)];
     }
 
     /**
index 0cba975..f88f5fb 100644 (file)
@@ -57,7 +57,8 @@ class cachestore_memcache_test extends cachestore_tests {
         $this->resetAfterTest(true);
 
         $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_memcache', 'phpunit_test');
-        $instance = cachestore_memcache::initialise_unit_test_instance($definition);
+        $instance = new cachestore_memcache('Memcache Test', cachestore_memcache::unit_test_configuration());
+        $instance->initialise($definition);
 
         if (!$instance) { // Something prevented memcache store to be inited (extension, TEST_CACHESTORE_MEMCACHE_TESTSERVERS...).
             $this->markTestSkipped();
index 84ec315..0c7be7b 100644 (file)
@@ -739,25 +739,16 @@ class cachestore_memcached extends cache_store implements cache_is_configurable
     }
 
     /**
-     * Creates a test instance for unit tests if possible.
-     * @param cache_definition $definition
-     * @return bool|cachestore_memcached
+     * Generates the appropriate configuration required for unit testing.
+     *
+     * @return array Array of unit test configuration data to be used by initialise().
      */
-    public static function initialise_unit_test_instance(cache_definition $definition) {
-        if (!self::are_requirements_met()) {
-            return false;
-        }
+    public static function unit_test_configuration() {
+        // If the configuration is not defined correctly, return only the configuration know about.
         if (!defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS')) {
-            return false;
+            return [];
         }
-
-        $configuration = array();
-        $configuration['servers'] = explode("\n", TEST_CACHESTORE_MEMCACHED_TESTSERVERS);
-
-        $store = new cachestore_memcached('Test memcached', $configuration);
-        $store->initialise($definition);
-
-        return $store;
+        return ['servers' => explode("\n", TEST_CACHESTORE_MEMCACHED_TESTSERVERS)];
     }
 
     /**
index 6a58844..08ce677 100644 (file)
@@ -61,9 +61,11 @@ class cachestore_memcached_test extends cachestore_tests {
         $this->resetAfterTest(true);
 
         $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_memcached', 'phpunit_test');
-        $instance = cachestore_memcached::initialise_unit_test_instance($definition);
+        $instance = new cachestore_memcached('Memcached Test', cachestore_memcached::unit_test_configuration());
+        $instance->initialise($definition);
 
-        if (!$instance) { // Something prevented memcached store to be inited (extension, TEST_CACHESTORE_MEMCACHED_TESTSERVERS...).
+        if (!$instance->is_ready()) {
+            // Something prevented memcached store to be inited (extension, TEST_CACHESTORE_MEMCACHED_TESTSERVERS...).
             $this->markTestSkipped();
         }
 
index 641b051..0f1d352 100644 (file)
@@ -571,25 +571,16 @@ class cachestore_mongodb extends cache_store implements cache_is_configurable {
      * @param cache_definition $definition
      * @return false
      */
-    public static function initialise_unit_test_instance(cache_definition $definition) {
-        if (!self::are_requirements_met()) {
-            return false;
-        }
-        if (!defined('TEST_CACHESTORE_MONGODB_TESTSERVER')) {
-            return false;
-        }
-
+    public static function unit_test_configuration() {
         $configuration = array();
-        $configuration['servers'] = explode("\n", TEST_CACHESTORE_MONGODB_TESTSERVER);
         $configuration['usesafe'] = 1;
 
-        $store = new cachestore_mongodb('Test mongodb', $configuration);
-        if (!$store->is_ready()) {
-            return false;
+        // If the configuration is not defined correctly, return only the configuration know about.
+        if (defined('TEST_CACHESTORE_MONGODB_TESTSERVER')) {
+            $configuration['servers'] = explode("\n", TEST_CACHESTORE_MONGODB_TESTSERVER);
         }
-        $store->initialise($definition);
 
-        return $store;
+        return $configuration;
     }
 
     /**
index 5186175..edb7f76 100644 (file)
@@ -56,9 +56,10 @@ class cachestore_mongodb_test extends cachestore_tests {
     public function test_collection_name() {
         // This generates a definition that has a hash starting with a number. MDL-46208.
         $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_mongodb', 'abc');
-        $instance = cachestore_mongodb::initialise_unit_test_instance($definition);
+        $instance = new cachestore_mongodb('MongoDB_Test', cachestore_mongodb::unit_test_configuration());
+        $instance->initialise($definition);
 
-        if (!$instance) {
+        if (!$instance->is_ready()) {
             $this->markTestSkipped();
         }
 
index d320b8c..f1284f1 100644 (file)
@@ -516,6 +516,14 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
         return $cache;
     }
 
+    /**
+     * Generates the appropriate configuration required for unit testing.
+     *
+     * @return array Array of unit test configuration data to be used by initialise().
+     */
+    public static function unit_test_configuration() {
+        return array();
+    }
     /**
      * Returns the name of this instance.
      * @return string
index d1f6f71..ecf06dd 100644 (file)
@@ -492,6 +492,15 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
         return $cache;
     }
 
+    /**
+     * Generates the appropriate configuration required for unit testing.
+     *
+     * @return array Array of unit test configuration data to be used by initialise().
+     */
+    public static function unit_test_configuration() {
+        return array();
+    }
+
     /**
      * Returns the name of this instance.
      * @return string
index 255cbf3..eedc37d 100644 (file)
@@ -1315,8 +1315,10 @@ class core_cache_testcase extends advanced_testcase {
 
         $configfile = $CFG->dataroot.'/muc/config.php';
 
-        // That's right, we're deleting the config file.
-        $this->assertTrue(@unlink($configfile));
+        // The config file will not exist yet as we've not done anything with the cache.
+        // reset_all_data removes the file and without a call to create a configuration it doesn't exist
+        // as yet.
+        $this->assertFileNotExists($configfile);
 
         // Disable the cache
         cache_phpunit_factory::phpunit_disable();
index 1bdf7d7..c6b405b 100644 (file)
@@ -73,13 +73,13 @@ class cache_config_testing extends cache_config_writer {
             if (class_exists($class) && $class::ready_to_be_used_for_testing()) {
                 /* @var cache_store $class */
                 $writer->configstores['test_application'] = array(
-                    'use_test_store' => true,
                     'name' => 'test_application',
                     'plugin' => $expectedstore,
-                    'alt' => $writer->configstores[$defaultapplication],
                     'modes' => $class::get_supported_modes(),
-                    'features' => $class::get_supported_features()
+                    'features' => $class::get_supported_features(),
+                    'configuration' => $class::unit_test_configuration()
                 );
+
                 $defaultapplication = 'test_application';
             }
         }
@@ -535,47 +535,4 @@ class cache_phpunit_factory extends cache_factory {
     public static function phpunit_disable() {
         parent::disable();
     }
-
-    /**
-     * @var bool Whether the warning notice about alternative cache store used has been displayed.
-     */
-    protected $altcachestorenotice = false;
-
-    /**
-     * Creates a store instance given its name and configuration.
-     *
-     * If the store has already been instantiated then the original object will be returned. (reused)
-     *
-     * @param string $name The name of the store (must be unique remember)
-     * @param array $details
-     * @param cache_definition $definition The definition to instantiate it for.
-     * @return boolean|cache_store
-     */
-    public function create_store_from_config($name, array $details, cache_definition $definition) {
-
-        if (isset($details['use_test_store'])) {
-            // name, plugin, alt
-            $class = 'cachestore_'.$details['plugin'];
-            $method = 'initialise_unit_test_instance';
-            if (class_exists($class) && method_exists($class, $method)) {
-                $instance = $class::$method($definition);
-
-                if ($instance) {
-                    return $instance;
-                }
-            }
-
-            // Notify user that alternative store is being used, so action can be taken.
-            if (!$this->altcachestorenotice) {
-                echo PHP_EOL . "++ WARNING: " . 'Failed to use "' . $details['plugin'] . '" cache store, alt "' .
-                    $details['alt']['plugin'] . '" cache store is used.' . PHP_EOL . PHP_EOL;
-                $this->altcachestorenotice = true;
-            }
-            $details = $details['alt'];
-            $details['class'] = 'cachestore_'.$details['plugin'];
-            $name = $details['name'];
-        }
-
-        return parent::create_store_from_config($name, $details, $definition);
-    }
 }
\ No newline at end of file
index 1dbe75a..56f94e7 100644 (file)
@@ -43,18 +43,28 @@ abstract class cachestore_tests extends advanced_testcase {
     abstract protected function get_class_name();
 
     /**
-     * Run the unit tests for the store.
+     * Sets up the fixture, for example, open a network connection.
+     * This method is called before a test is executed.
      */
-    public function test_test_instance() {
+    public function setUp() {
         $class = $this->get_class_name();
-        if (!class_exists($class) || !method_exists($class, 'initialise_test_instance') || !$class::are_requirements_met()) {
+        if (!class_exists($class) || !$class::are_requirements_met()) {
             $this->markTestSkipped('Could not test '.$class.'. Requirements are not met.');
         }
+        parent::setUp();
+    }
+    /**
+     * Run the unit tests for the store.
+     */
+    public function test_test_instance() {
+        $class = $this->get_class_name();
 
         $modes = $class::get_supported_modes();
         if ($modes & cache_store::MODE_APPLICATION) {
             $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, $class, 'phpunit_test');
-            $instance = $class::initialise_unit_test_instance($definition);
+            $instance = new $class($class.'_test', $class::unit_test_configuration());
+            $instance->initialise($definition);
+
             if (!$instance) {
                 $this->markTestSkipped('Could not test '.$class.'. No test instance configured for application caches.');
             } else {
@@ -63,7 +73,9 @@ abstract class cachestore_tests extends advanced_testcase {
         }
         if ($modes & cache_store::MODE_SESSION) {
             $definition = cache_definition::load_adhoc(cache_store::MODE_SESSION, $class, 'phpunit_test');
-            $instance = $class::initialise_unit_test_instance($definition);
+            $instance = new $class($class.'_test', $class::unit_test_configuration());
+            $instance->initialise($definition);
+
             if (!$instance) {
                 $this->markTestSkipped('Could not test '.$class.'. No test instance configured for session caches.');
             } else {
@@ -72,7 +84,9 @@ abstract class cachestore_tests extends advanced_testcase {
         }
         if ($modes & cache_store::MODE_REQUEST) {
             $definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, $class, 'phpunit_test');
-            $instance = $class::initialise_unit_test_instance($definition);
+            $instance = new $class($class.'_test', $class::unit_test_configuration());
+            $instance->initialise($definition);
+
             if (!$instance) {
                 $this->markTestSkipped('Could not test '.$class.'. No test instance configured for request caches.');
             } else {
index c2649c0..e995cff 100644 (file)
@@ -13,6 +13,8 @@ Information provided here is intended especially for developers.
   - cache_store::cleanup()
 * cachestore_dummy::cleanup() has been deprecated.
 * cachestore_dummy::instance_deleted() implemented in lieu of cachestore_dummy::cleanup().
+* Added cache_store::unit_test_configuration() to calculate unit testing configuration.
+* Remove cache_store:initialise_unit_test_instance() as it is incompatible with cache_helper purge functions.
 
 === 3.1 ===
 * Cache stores has a new feature DEREFERENCES_OBJECTS.
index d0a03dc..e0f8cf9 100644 (file)
@@ -200,6 +200,8 @@ class MoodleQuickForm_select extends HTML_QuickForm_select implements templatabl
         $context = $this->export_for_template_base($output);
 
         $options = [];
+        // Standard option attributes.
+        $standardoptionattributes = ['text', 'value', 'selected', 'disabled'];
         foreach ($this->_options as $option) {
             if (is_array($this->_values) && in_array( (string) $option['attr']['value'], $this->_values)) {
                 $this->_updateAttrArray($option['attr'], ['selected' => 'selected']);
@@ -207,8 +209,17 @@ class MoodleQuickForm_select extends HTML_QuickForm_select implements templatabl
             $o = [
                 'text' => $option['text'],
                 'value' => $option['attr']['value'],
-                'selected' => !empty($option['attr']['selected'])
+                'selected' => !empty($option['attr']['selected']),
+                'disabled' => !empty($option['attr']['disabled']),
             ];
+            // Set other attributes.
+            $otheroptionattributes = [];
+            foreach ($option['attr'] as $attr => $value) {
+                if (!in_array($attr, $standardoptionattributes) && $attr != 'class' && !is_object($value)) {
+                    $otheroptionattributes[] = $attr . '="' . s($value) . '"';
+                }
+            }
+            $o['optionattributes'] = implode(' ', $otheroptionattributes);
             $options[] = $o;
         }
         $context['options'] = $options;
index ab547cf..9ed4d05 100644 (file)
@@ -522,6 +522,8 @@ class MoodleQuickForm_selectgroups extends HTML_QuickForm_element implements tem
             ];
         }
 
+        // Standard option attributes.
+        $standardoptionattributes = ['text', 'value', 'selected', 'disabled'];
         foreach ($this->_optGroups as $group) {
             $options = [];
 
@@ -533,6 +535,15 @@ class MoodleQuickForm_selectgroups extends HTML_QuickForm_element implements tem
                     $o['selected'] = false;
                 }
                 $o['text'] = $option['text'];
+                $o['disabled'] = !empty($option['attr']['disabled']);
+                // Set other attributes.
+                $otheroptionattributes = [];
+                foreach ($option['attr'] as $attr => $value) {
+                    if (!in_array($attr, $standardoptionattributes) && $attr != 'class' && !is_object($value)) {
+                        $otheroptionattributes[] = $attr . '="' . s($value) . '"';
+                    }
+                }
+                $o['optionattributes'] = implode(' ', $otheroptionattributes);
                 $options[] = $o;
             }
 
index 1643e6f..fc4a915 100644 (file)
@@ -231,6 +231,8 @@ class MoodleQuickForm_selectwithlink extends HTML_QuickForm_select implements te
         $context = $this->export_for_template_base($output);
 
         $options = [];
+        // Standard option attributes.
+        $standardoptionattributes = ['text', 'value', 'selected', 'disabled'];
         foreach ($this->_options as $option) {
             if (is_array($this->_values) && in_array( (string) $option['attr']['value'], $this->_values)) {
                 $this->_updateAttrArray($option['attr'], ['selected' => 'selected']);
@@ -238,8 +240,17 @@ class MoodleQuickForm_selectwithlink extends HTML_QuickForm_select implements te
             $o = [
                 'text' => $option['text'],
                 'value' => $option['attr']['value'],
-                'selected' => !empty($option['attr']['selected'])
+                'selected' => !empty($option['attr']['selected']),
+                'disabled' => !empty($option['attr']['disabled']),
             ];
+            // Set other attributes.
+            $otheroptionattributes = [];
+            foreach ($option['attr'] as $attr => $value) {
+                if (!in_array($attr, $standardoptionattributes) && $attr != 'class' && !is_object($value)) {
+                    $otheroptionattributes[] = $attr . '="' . s($value) . '"';
+                }
+            }
+            $o['optionattributes'] = implode(' ', $otheroptionattributes);
             $options[] = $o;
         }
         $context['options'] = $options;
index f3c04a3..4a8b682 100644 (file)
@@ -814,12 +814,13 @@ abstract class testing_util {
         make_temp_directory('');
         make_cache_directory('');
         make_localcache_directory('');
-        // Reset the cache API so that it recreates it's required directories as well.
-        cache_factory::reset();
-        // Purge all data from the caches. This is required for consistency.
+        // Purge all data from the caches. This is required for consistency between tests.
         // Any file caches that happened to be within the data root will have already been clearer (because we just deleted cache)
-        // and now we will purge any other caches as well.
+        // and now we will purge any other caches as well.  This must be done before the cache_factory::reset() as that
+        // removes all definitions of caches and purge does not have valid caches to operate on.
         cache_helper::purge_all();
+        // Reset the cache API so that it recreates it's required directories as well.
+        cache_factory::reset();
     }
 
     /**
index 1700011..b190040 100644 (file)
@@ -2376,6 +2376,9 @@ class mod_assign_external extends external_api {
                                                                             $lastattempt->submissiongroupmemberswhoneedtosubmit);
             }
 
+            // Can edit its own submission?
+            $lastattempt->caneditowner = $assign->submissions_open($user->id) && $assign->is_any_submission_plugin_enabled();
+
             $result['lastattempt'] = $lastattempt;
         }
 
@@ -2470,6 +2473,7 @@ class mod_assign_external extends external_api {
                         'locked' => new external_value(PARAM_BOOL, 'Whether new submissions are locked.'),
                         'graded' => new external_value(PARAM_BOOL, 'Whether the submission is graded.'),
                         'canedit' => new external_value(PARAM_BOOL, 'Whether the user can edit the current submission.'),
+                        'caneditowner' => new external_value(PARAM_BOOL, 'Whether the owner of the submission can edit it.'),
                         'cansubmit' => new external_value(PARAM_BOOL, 'Whether the user can submit.'),
                         'extensionduedate' => new external_value(PARAM_INT, 'Extension due date.'),
                         'blindmarking' => new external_value(PARAM_BOOL, 'Whether blind marking is enabled.'),
index 155f101..6362a7a 100644 (file)
@@ -9,7 +9,7 @@
         {{/error}}
         {{element.attributes}} >
         {{#element.options}}
-        <option value="{{value}}" {{#selected}}selected{{/selected}}>{{text}}</option>
+        <option value="{{value}}" {{#selected}}selected{{/selected}} {{#disabled}}disabled{{/disabled}} {{{optionattributes}}}>{{text}}</option>
         {{/element.options}}
     </select>
         {{/element.frozen}}
index 4ac6560..6947361 100644 (file)
@@ -9,7 +9,7 @@
             {{/error}}
             {{element.attributes}} >
             {{#element.options}}
-            <option value="{{value}}" {{#selected}}selected{{/selected}}>{{text}}</option>
+            <option value="{{value}}" {{#selected}}selected{{/selected}} {{#disabled}}disabled{{/disabled}} {{{optionattributes}}}>{{text}}</option>
             {{/element.options}}
         </select>
         {{/element.frozen}}
index 3889a87..72e767a 100644 (file)
@@ -13,7 +13,7 @@
         <optgroup label="{{text}}">
         {{/text}}
         {{#options}}
-        <option value="{{value}}" {{#selected}}selected{{/selected}}>{{{text}}}</option>
+        <option value="{{value}}" {{#selected}}selected{{/selected}} {{#disabled}}disabled{{/disabled}} {{{optionattributes}}}>{{{text}}}</option>
         {{/options}}
         {{#text}}
         </optgroup>
index 99b93e2..88fef74 100644 (file)
@@ -13,7 +13,7 @@
             <optgroup label="{{text}}">
             {{/text}}
             {{#options}}
-            <option value="{{value}}" {{#selected}}selected{{/selected}}>{{{text}}}</option>
+            <option value="{{value}}" {{#selected}}selected{{/selected}} {{#disabled}}disabled{{/disabled}} {{{optionattributes}}}>{{{text}}}</option>
             {{/options}}
             {{/element.optiongroups}}
             {{#text}}
index f561e5a..66cbe9c 100644 (file)
@@ -9,7 +9,7 @@
             {{/error}}
             {{element.attributes}} >
             {{#element.options}}
-            <option value="{{value}}" {{#selected}}selected{{/selected}}>{{text}}</option>
+            <option value="{{value}}" {{#selected}}selected{{/selected}} {{#disabled}}disabled{{/disabled}} {{{optionattributes}}}>{{text}}</option>
             {{/element.options}}
         </select>
         {{/element.frozen}}