Merge branch 'MDL-50175-master' of git://github.com/danpoltawski/moodle
authorAndrew Nicols <andrew@nicols.co.uk>
Tue, 1 Mar 2016 00:07:56 +0000 (08:07 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Tue, 1 Mar 2016 00:07:56 +0000 (08:07 +0800)
126 files changed:
admin/tool/task/scheduledtasks.php
admin/tool/templatelibrary/tests/externallib_test.php
admin/user/user_bulk_cohortadd.php
badges/action.php
cache/README.md
cache/classes/definition.php
cache/classes/dummystore.php
cache/classes/loaders.php
cache/classes/store.php
cache/forms.php
cache/locallib.php
cache/stores/file/lib.php
cache/stores/memcache/lib.php
cache/stores/memcached/lib.php
cache/stores/mongodb/lib.php
cache/tests/cache_test.php
cache/tests/fixtures/lib.php
cache/upgrade.txt
course/delete.php
enrol/cohort/edit.php [deleted file]
enrol/cohort/edit_form.php [deleted file]
enrol/cohort/lib.php
enrol/editinstance.php [moved from enrol/guest/edit.php with 59% similarity]
enrol/editinstance_form.php [new file with mode: 0644]
enrol/guest/classes/enrol_guest_edit_form.php [deleted file]
enrol/guest/lib.php
enrol/guest/locallib.php
enrol/instances.php
enrol/manual/edit.php [deleted file]
enrol/manual/edit_form.php [deleted file]
enrol/manual/lib.php
enrol/meta/addinstance.php [deleted file]
enrol/meta/addinstance_form.php [deleted file]
enrol/meta/lib.php
enrol/meta/tests/behat/enrol_meta.feature
enrol/meta/tests/plugin_test.php
enrol/mnet/addinstance.php [deleted file]
enrol/mnet/addinstance_form.php [deleted file]
enrol/mnet/lib.php
enrol/paypal/edit.php [deleted file]
enrol/paypal/edit_form.php [deleted file]
enrol/paypal/lib.php
enrol/self/edit.php [deleted file]
enrol/self/edit_form.php [deleted file]
enrol/self/lib.php
enrol/upgrade.txt
lang/en/cache.php
lib/amd/build/notification.min.js
lib/amd/src/notification.js
lib/classes/notification.php [new file with mode: 0644]
lib/classes/output/notification.php
lib/classes/session/manager.php
lib/db/caches.php
lib/db/services.php
lib/ddl/sql_generator.php
lib/deprecatedlib.php
lib/dml/database_column_info.php
lib/dml/moodle_database.php
lib/dml/moodle_temptables.php
lib/dml/mssql_native_moodle_database.php
lib/dml/mysqli_native_moodle_database.php
lib/dml/oci_native_moodle_database.php
lib/dml/pgsql_native_moodle_database.php
lib/dml/sqlite3_pdo_moodle_database.php
lib/dml/sqlsrv_native_moodle_database.php
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js
lib/editor/atto/yui/src/editor/js/selection.js
lib/enrollib.php
lib/external/externallib.php
lib/medialib.php
lib/outputrenderers.php
lib/templates/notification_error.mustache [moved from lib/templates/notification_redirect.mustache with 57% similarity]
lib/templates/notification_info.mustache [moved from lib/templates/notification_message.mustache with 57% similarity]
lib/templates/notification_success.mustache
lib/templates/notification_warning.mustache [moved from lib/templates/notification_problem.mustache with 57% similarity]
lib/tests/notification_test.php [new file with mode: 0644]
lib/tests/session_manager_test.php
lib/tests/sessionlib_test.php
lib/upgrade.txt
lib/weblib.php
mod/assign/feedback/comments/locallib.php
mod/assign/feedback/comments/tests/comments_test.php [new file with mode: 0644]
mod/assign/feedback/editpdf/locallib.php
mod/assign/feedback/editpdf/tests/editpdf_test.php
mod/assign/feedback/file/locallib.php
mod/assign/feedback/file/tests/file_test.php [new file with mode: 0644]
mod/assign/feedbackplugin.php
mod/assign/locallib.php
mod/assign/tests/locallib_test.php
mod/assign/upgrade.txt
mod/feedback/item/multichoice/lib.php
mod/feedback/item/multichoice/multichoice_form.php
mod/forum/index.php
mod/forum/maildigest.php
mod/forum/post.php
mod/forum/subscribe.php
mod/forum/tests/behat/discussion_subscriptions.feature
mod/forum/tests/behat/forum_subscriptions.feature
mod/forum/tests/behat/forum_subscriptions_default.feature
mod/imscp/view.php
mod/quiz/report/overview/report.php
mod/workshop/backup/moodle2/backup_workshop_stepslib.php
mod/workshop/db/install.xml
mod/workshop/db/upgrade.php
mod/workshop/exsubmission.php
mod/workshop/form/assessment_form.php
mod/workshop/lang/en/workshop.php
mod/workshop/lib.php
mod/workshop/locallib.php
mod/workshop/mod_form.php
mod/workshop/submission.php
mod/workshop/submission_form.php
mod/workshop/tests/generator/lib.php
mod/workshop/tests/locallib_test.php
mod/workshop/version.php
my/tests/behat/reset_all_pages.feature
tag/tests/behat/flag_tags.feature
theme/base/templates/core/notification_error.mustache [moved from theme/base/templates/core/notification_problem.mustache with 100% similarity]
theme/base/templates/core/notification_info.mustache [moved from theme/base/templates/core/notification_redirect.mustache with 100% similarity]
theme/base/templates/core/notification_warning.mustache [moved from theme/base/templates/core/notification_message.mustache with 100% similarity]
theme/upgrade.txt
user/emailupdate.php
user/view.php
version.php

index 4b6f344..ed6e53c 100644 (file)
@@ -87,13 +87,9 @@ if ($mform && ($mform->is_cancelled() || !empty($CFG->preventscheduledtaskchange
 
         try {
             \core\task\manager::configure_scheduled_task($task);
-            $url = $PAGE->url;
-            $url->params(array('success'=>get_string('changessaved')));
-            redirect($url);
+            redirect($PAGE->url, get_string('changessaved'), null, \core\output\notification::NOTIFY_SUCCESS);
         } catch (Exception $e) {
-            $url = $PAGE->url;
-            $url->params(array('error'=>$e->getMessage()));
-            redirect($url);
+            redirect($PAGE->url, $e->getMessage(), null, \core\output\notification::NOTIFY_ERROR);
         }
     } else {
         echo $OUTPUT->header();
@@ -104,19 +100,7 @@ if ($mform && ($mform->is_cancelled() || !empty($CFG->preventscheduledtaskchange
 
 } else {
     echo $OUTPUT->header();
-    $error = optional_param('error', '', PARAM_NOTAGS);
-    if ($error) {
-        echo $OUTPUT->notification($error, 'notifyerror');
-    }
-    $success = optional_param('success', '', PARAM_NOTAGS);
-    if ($success) {
-        echo $OUTPUT->notification($success, 'notifysuccess');
-    }
     $tasks = core\task\manager::get_all_scheduled_tasks();
     echo $renderer->scheduled_tasks_table($tasks);
     echo $OUTPUT->footer();
 }
-
-
-
-
index 9b09541..ac31ab6 100644 (file)
@@ -72,10 +72,10 @@ class tool_templatelibrary_external_testcase extends externallib_advanced_testca
         // Change the theme to 'base' because it overrides these templates.
         $CFG->theme = 'base';
 
-        $template = external::load_canonical_template('core', 'notification_problem');
+        $template = external::load_canonical_template('core', 'notification_error');
 
         // Only the base template should contain the docs.
-        $this->assertContains('@template core/notification_problem', $template);
+        $this->assertContains('@template core/notification_error', $template);
 
         // Restore the original theme.
         $CFG->theme = $originaltheme;
index 958d6c1..7e2d4c5 100644 (file)
@@ -60,12 +60,7 @@ foreach ($allcohorts as $c) {
 unset($allcohorts);
 
 if (count($cohorts) < 2) {
-    echo $OUTPUT->header();
-    echo $OUTPUT->heading(get_string('bulkadd', 'core_cohort'));
-    echo $OUTPUT->notification(get_string('bulknocohort', 'core_cohort'));
-    echo $OUTPUT->continue_button(new moodle_url('/admin/user/user_bulk.php'));
-    echo $OUTPUT->footer();
-    die;
+    redirect(new moodle_url('/admin/user/user_bulk.php'), get_string('bulknocohort', 'core_cohort'));
 }
 
 $countries = get_string_manager()->get_list_of_countries(true);
index 31843a2..db43172 100644 (file)
@@ -115,8 +115,7 @@ if ($activate) {
     $url = new moodle_url('/badges/action.php', $params);
 
     if (!$badge->has_criteria()) {
-        echo $OUTPUT->notification(get_string('error:cannotact', 'badges') . get_string('nocriteria', 'badges'));
-        echo $OUTPUT->continue_button($returnurl);
+        redirect($returnurl, get_string('error:cannotact', 'badges') . get_string('nocriteria', 'badges'), null, \core\output\notification::NOTIFY_ERROR);
     } else {
         $message = get_string('reviewconfirm', 'badges', $badge->name);
         echo $OUTPUT->confirm($message, $url, $returnurl);
index 71f44f5..7676171 100644 (file)
@@ -31,6 +31,7 @@ A definition:
             'invalidationevents' => array(            // Optional
                 'contextmarkeddirty'
             ),
+            'canuselocalstore' => false               // Optional
             'sharingoptions' => null                  // Optional
             'defaultsharing' => null                  // Optional
         )
@@ -151,7 +152,7 @@ The following optional settings can also be defined:
 * invalidationevents - An array of events that should trigger this cache to invalidate.
 * sharingoptions - The sum of the possible sharing options that are applicable to the definition. An advanced setting.
 * defaultsharing - The default sharing option to use. It's highly recommended that you don't set this unless there is a very specific reason not to use the system default.
-
+* canuselocalstore - The default is to required a shared cache location for all nodes in a multi webserver environment.  If the cache uses revisions and never updates key data, administrators can use a local storage cache for this cache.
 It's important to note that internally the definition is also aware of the component. This is picked up when the definition is read, based upon the location of the caches.php file.
 
 The staticacceleration option.
@@ -269,4 +270,4 @@ There are a couple of considerations to using this method:
 
 Please be aware that if you are using Memcache or Memcached it is recommended to use dedicated Memcached servers.
 When caches get purged the memcached servers you have configured get purged, any data stored within them whether it belongs to Moodle or not will be removed.
-If you are using Memcached for sessions as well as caching/testing and caches get purged your sessions will be removed prematurely and users will be need to start again.
\ No newline at end of file
+If you are using Memcached for sessions as well as caching/testing and caches get purged your sessions will be removed prematurely and users will be need to start again.
index a5fd64a..21c85f2 100644 (file)
@@ -100,6 +100,11 @@ defined('MOODLE_INTERNAL') || die();
  *     + defaultsharing
  *          [int] The default sharing option to use. It's highly recommended that you don't set this unless there is a very
  *          specific reason not to use the system default.
+ *     + canuselocalstore
+ *          [bool] The cache is able to safely run with multiple copies on different webservers without any need for administrator
+ *                 intervention to ensure that data stays in sync across nodes.  This is usually managed by a revision
+ *                 system as seen in modinfo cache or language cache.  Requiring purge on upgrade is not sufficient as
+ *                 it requires administrator intervention on each node to make it work.
  *
  * For examples take a look at lib/db/caches.php
  *
@@ -308,6 +313,12 @@ class cache_definition {
      */
     protected $sharingoptions;
 
+    /**
+     * Whether this cache supports local storages.
+     * @var bool
+     */
+    protected $canuselocalstore = false;
+
     /**
      * The selected sharing option.
      * @var int One of self::SHARING_*
@@ -367,6 +378,7 @@ class cache_definition {
         $sharingoptions = self::SHARING_DEFAULT;
         $selectedsharingoption = self::SHARING_DEFAULT;
         $userinputsharingkey = '';
+        $canuselocalstore = false;
 
         if (array_key_exists('simplekeys', $definition)) {
             $simplekeys = (bool)$definition['simplekeys'];
@@ -453,6 +465,9 @@ class cache_definition {
                 $selectedsharingoption = self::SHARING_ALL;
             }
         }
+        if (array_key_exists('canuselocalstore', $definition)) {
+            $canuselocalstore = (bool)$definition['canuselocalstore'];
+        }
 
         if (array_key_exists('userinputsharingkey', $definition) && !empty($definition['userinputsharingkey'])) {
             $userinputsharingkey = (string)$definition['userinputsharingkey'];
@@ -529,6 +544,7 @@ class cache_definition {
         $cachedefinition->sharingoptions = $sharingoptions;
         $cachedefinition->selectedsharingoption = $selectedsharingoption;
         $cachedefinition->userinputsharingkey = $userinputsharingkey;
+        $cachedefinition->canuselocalstore = $canuselocalstore;
 
         return $cachedefinition;
     }
@@ -732,6 +748,15 @@ class cache_definition {
         return $this->requirelockingwrite;
     }
 
+    /**
+     * Returns true if this definition allows local storage to be used for caching.
+     * @since Moodle 3.1.0
+     * @return bool
+     */
+    public function can_use_localstore() {
+        return $this->canuselocalstore;
+    }
+
     /**
      * Returns true if this definition requires a searchable cache.
      * @since Moodle 2.4.4
@@ -766,13 +791,14 @@ class cache_definition {
      * Sets the identifiers for this definition, or updates them if they have already been set.
      *
      * @param array $identifiers
+     * @return bool false if no identifiers where changed, true otherwise.
      * @throws coding_exception
      */
     public function set_identifiers(array $identifiers = array()) {
         // If we are setting the exact same identifiers then just return as nothing really changed.
         // We don't care about order as cache::make will use the same definition order all the time.
         if ($identifiers === $this->identifiers) {
-            return;
+            return false;
         }
 
         foreach ($this->requireidentifiers as $identifier) {
@@ -791,6 +817,8 @@ class cache_definition {
         // Reset the key prefix's they need updating now.
         $this->keyprefixsingle = null;
         $this->keyprefixmulti = null;
+
+        return true;
     }
 
     /**
index 14eecd8..4050838 100644 (file)
@@ -190,9 +190,9 @@ class cachestore_dummy extends cache_store {
             foreach ($keyvaluearray as $pair) {
                 $this->store[$pair['key']] = $pair['value'];
             }
-            return count($keyvaluearray);
+
         }
-        return 0;
+        return count($keyvaluearray);
     }
 
     /**
index e0a7723..544718c 100644 (file)
@@ -264,7 +264,15 @@ class cache implements cache_loader {
      * @param array $identifiers
      */
     public function set_identifiers(array $identifiers) {
-        $this->definition->set_identifiers($identifiers);
+        if ($this->definition->set_identifiers($identifiers)) {
+            // As static acceleration uses input keys and not parsed keys
+            // it much be cleared when the identifier set is changed.
+            $this->staticaccelerationarray = array();
+            if ($this->staticaccelerationsize !== false) {
+                $this->staticaccelerationkeys = array();
+                $this->staticaccelerationcount = 0;
+            }
+        }
     }
 
     /**
@@ -278,23 +286,19 @@ class cache implements cache_loader {
      * @throws coding_exception
      */
     public function get($key, $strictness = IGNORE_MISSING) {
-        // 1. Parse the key.
-        $parsedkey = $this->parse_key($key);
-        // 2. Get it from the static acceleration array if we can (only when it is enabled and it has already been requested/set).
-        $result = false;
-        if ($this->use_static_acceleration()) {
-            $result = $this->static_acceleration_get($parsedkey);
-        }
-        if ($result !== false) {
-            if (!is_scalar($result)) {
-                // If data is an object it will be a reference.
-                // If data is an array if may contain references.
-                // We want to break references so that the cache cannot be modified outside of itself.
-                // Call the function to unreference it (in the best way possible).
-                $result = $this->unref($result);
+        // 1. Get it from the static acceleration array if we can (only when it is enabled and it has already been requested/set).
+        $usesstaticacceleration = $this->use_static_acceleration();
+
+        if ($usesstaticacceleration) {
+            $result = $this->static_acceleration_get($key);
+            if ($result !== false) {
+                return $result;
             }
-            return $result;
         }
+
+        // 2. Parse the key.
+        $parsedkey = $this->parse_key($key);
+
         // 3. Get it from the store. Obviously wasn't in the static acceleration array.
         $result = $this->store->get($parsedkey);
         if ($result !== false) {
@@ -309,10 +313,11 @@ class cache implements cache_loader {
             if ($result instanceof cache_cached_object) {
                 $result = $result->restore_object();
             }
-            if ($this->use_static_acceleration()) {
-                $this->static_acceleration_set($parsedkey, $result);
+            if ($usesstaticacceleration) {
+                $this->static_acceleration_set($key, $result);
             }
         }
+
         // 4. Load if from the loader/datasource if we don't already have it.
         $setaftervalidation = false;
         if ($result === false) {
@@ -341,7 +346,7 @@ class cache implements cache_loader {
         }
         // 7. Make sure we don't pass back anything that could be a reference.
         //    We don't want people modifying the data in the cache.
-        if (!is_scalar($result)) {
+        if (!$this->store->supports_dereferencing_objects() && !is_scalar($result)) {
             // If data is an object it will be a reference.
             // If data is an array if may contain references.
             // We want to break references so that the cache cannot be modified outside of itself.
@@ -385,7 +390,7 @@ class cache implements cache_loader {
             $parsedkeys[$pkey] = $key;
             $keystofind[$pkey] = $key;
             if ($isusingpersist) {
-                $value = $this->static_acceleration_get($pkey);
+                $value = $this->static_acceleration_get($key);
                 if ($value !== false) {
                     $resultpersist[$pkey] = $value;
                     unset($keystofind[$pkey]);
@@ -409,7 +414,7 @@ class cache implements cache_loader {
                     $value = $value->restore_object();
                 }
                 if ($value !== false && $this->use_static_acceleration()) {
-                    $this->static_acceleration_set($key, $value);
+                    $this->static_acceleration_set($keystofind[$key], $value);
                 }
                 $resultstore[$key] = $value;
             }
@@ -450,6 +455,13 @@ class cache implements cache_loader {
         // Create an array with the original keys and the found values. This will be what we return.
         $fullresult = array();
         foreach ($result as $key => $value) {
+            if (!is_scalar($value)) {
+                // If data is an object it will be a reference.
+                // If data is an array if may contain references.
+                // We want to break references so that the cache cannot be modified outside of itself.
+                // Call the function to unreference it (in the best way possible).
+                $value = $this->unref($value);
+            }
             $fullresult[$parsedkeys[$key]] = $value;
         }
         unset($result);
@@ -507,22 +519,27 @@ class cache implements cache_loader {
             // We have to let the loader do its own parsing of data as it may be unique.
             $this->loader->set($key, $data);
         }
+        $usestaticacceleration = $this->use_static_acceleration();
+
         if (is_object($data) && $data instanceof cacheable_object) {
             $data = new cache_cached_object($data);
-        } else if (!is_scalar($data)) {
+        } else if (!$this->store->supports_dereferencing_objects() && !is_scalar($data)) {
             // If data is an object it will be a reference.
             // If data is an array if may contain references.
             // We want to break references so that the cache cannot be modified outside of itself.
             // Call the function to unreference it (in the best way possible).
             $data = $this->unref($data);
         }
+
+        if ($usestaticacceleration) {
+            $this->static_acceleration_set($key, $data);
+        }
+
         if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
             $data = new cache_ttl_wrapper($data, $this->definition->get_ttl());
         }
         $parsedkey = $this->parse_key($key);
-        if ($this->use_static_acceleration()) {
-            $this->static_acceleration_set($parsedkey, $data);
-        }
+
         return $this->store->set($parsedkey, $data);
     }
 
@@ -626,16 +643,20 @@ class cache implements cache_loader {
         $data = array();
         $simulatettl = $this->has_a_ttl() && !$this->store_supports_native_ttl();
         $usestaticaccelerationarray = $this->use_static_acceleration();
+        $needsdereferencing = !$this->store->supports_dereferencing_objects();
         foreach ($keyvaluearray as $key => $value) {
             if (is_object($value) && $value instanceof cacheable_object) {
                 $value = new cache_cached_object($value);
-            } else if (!is_scalar($value)) {
+            } else if ($needsdereferencing && !is_scalar($value)) {
                 // If data is an object it will be a reference.
                 // If data is an array if may contain references.
                 // We want to break references so that the cache cannot be modified outside of itself.
                 // Call the function to unreference it (in the best way possible).
                 $value = $this->unref($value);
             }
+            if ($usestaticaccelerationarray) {
+                $this->static_acceleration_set($key, $value);
+            }
             if ($simulatettl) {
                 $value = new cache_ttl_wrapper($value, $this->definition->get_ttl());
             }
@@ -643,9 +664,6 @@ class cache implements cache_loader {
                 'key' => $this->parse_key($key),
                 'value' => $value
             );
-            if ($usestaticaccelerationarray) {
-                $this->static_acceleration_set($data[$key]['key'], $value);
-            }
         }
         $successfullyset = $this->store->set_many($data);
         if ($this->perfdebug && $successfullyset) {
@@ -676,11 +694,12 @@ class cache implements cache_loader {
      * @return bool True if the cache has the requested key, false otherwise.
      */
     public function has($key, $tryloadifpossible = false) {
-        $parsedkey = $this->parse_key($key);
-        if ($this->static_acceleration_has($parsedkey)) {
+        if ($this->static_acceleration_has($key)) {
             // Hoorah, that was easy. It exists in the static acceleration array so we definitely have it.
             return true;
         }
+        $parsedkey = $this->parse_key($key);
+
         if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
             // The data has a TTL and the store doesn't support it natively.
             // We must fetch the data and expect a ttl wrapper.
@@ -760,17 +779,13 @@ class cache implements cache_loader {
         }
 
         if ($this->use_static_acceleration()) {
-            $parsedkeys = array();
             foreach ($keys as $id => $key) {
-                $parsedkey = $this->parse_key($key);
-                if ($this->static_acceleration_has($parsedkey)) {
+                if ($this->static_acceleration_has($key)) {
                     return true;
                 }
-                $parsedkeys[] = $parsedkey;
             }
-        } else {
-            $parsedkeys = array_map(array($this, 'parse_key'), $keys);
         }
+        $parsedkeys = array_map(array($this, 'parse_key'), $keys);
         return $this->store->has_any($parsedkeys);
     }
 
@@ -783,12 +798,12 @@ class cache implements cache_loader {
      * @return bool True of success, false otherwise.
      */
     public function delete($key, $recurse = true) {
-        $parsedkey = $this->parse_key($key);
-        $this->static_acceleration_delete($parsedkey);
+        $this->static_acceleration_delete($key);
         if ($recurse && $this->loader !== false) {
             // Delete from the bottom of the stack first.
             $this->loader->delete($key, $recurse);
         }
+        $parsedkey = $this->parse_key($key);
         return $this->store->delete($parsedkey);
     }
 
@@ -801,16 +816,16 @@ class cache implements cache_loader {
      * @return int The number of items successfully deleted.
      */
     public function delete_many(array $keys, $recurse = true) {
-        $parsedkeys = array_map(array($this, 'parse_key'), $keys);
         if ($this->use_static_acceleration()) {
-            foreach ($parsedkeys as $parsedkey) {
-                $this->static_acceleration_delete($parsedkey);
+            foreach ($keys as $key) {
+                $this->static_acceleration_delete($key);
             }
         }
         if ($recurse && $this->loader !== false) {
             // Delete from the bottom of the stack first.
             $this->loader->delete_many($keys, $recurse);
         }
+        $parsedkeys = array_map(array($this, 'parse_key'), $keys);
         return $this->store->delete_many($parsedkeys);
     }
 
@@ -974,19 +989,11 @@ class cache implements cache_loader {
      * @return bool
      */
     protected function static_acceleration_has($key) {
-        // This method of checking if an array was supplied is faster than is_array.
-        if ($key === (array)$key) {
-            $key = $key['key'];
-        }
         // This could be written as a single line, however it has been split because the ttl check is faster than the instanceof
         // and has_expired calls.
-        if (!$this->staticacceleration || !array_key_exists($key, $this->staticaccelerationarray)) {
+        if (!$this->staticacceleration || !isset($this->staticaccelerationarray[$key])) {
             return false;
         }
-        if ($this->has_a_ttl() && $this->store_supports_native_ttl()) {
-             return !($this->staticaccelerationarray[$key] instanceof cache_ttl_wrapper &&
-                      $this->staticaccelerationarray[$key]->has_expired());
-        }
         return true;
     }
 
@@ -1007,34 +1014,20 @@ class cache implements cache_loader {
      * Returns the item from the static acceleration array if it exists there.
      *
      * @param string $key The parsed key
-     * @return mixed|false The data from the static acceleration array or false if it wasn't there.
+     * @return mixed|false Dereferenced data from the static acceleration array or false if it wasn't there.
      */
     protected function static_acceleration_get($key) {
-        // This method of checking if an array was supplied is faster than is_array.
-        if ($key === (array)$key) {
-            $key = $key['key'];
-        }
-        // This isset check is faster than array_key_exists but will return false
-        // for null values, meaning null values will come from backing store not
-        // the static acceleration array. We think this okay because null usage should be
-        // very rare (see comment in MDL-39472).
         if (!$this->staticacceleration || !isset($this->staticaccelerationarray[$key])) {
             $result = false;
         } else {
-            $data = $this->staticaccelerationarray[$key];
-            if (!$this->has_a_ttl() || !$data instanceof cache_ttl_wrapper) {
-                if ($data instanceof cache_cached_object) {
-                    $data = $data->restore_object();
-                }
-                $result = $data;
-            } else if ($data->has_expired()) {
-                $this->static_acceleration_delete($key);
-                $result = false;
+            $data = $this->staticaccelerationarray[$key]['data'];
+
+            if ($data instanceof cache_cached_object) {
+                $result = $data->restore_object();
+            } else if ($this->staticaccelerationarray[$key]['serialized']) {
+                $result = unserialize($data);
             } else {
-                if ($data instanceof cache_cached_object) {
-                    $data = $data->restore_object();
-                }
-                $result = $data->data;
+                $result = $data;
             }
         }
         if ($result) {
@@ -1081,15 +1074,23 @@ class cache implements cache_loader {
      * @return bool
      */
     protected function static_acceleration_set($key, $data) {
-        // This method of checking if an array was supplied is faster than is_array.
-        if ($key === (array)$key) {
-            $key = $key['key'];
-        }
         if ($this->staticaccelerationsize !== false && isset($this->staticaccelerationkeys[$key])) {
             $this->staticaccelerationcount--;
             unset($this->staticaccelerationkeys[$key]);
         }
-        $this->staticaccelerationarray[$key] = $data;
+
+        // We serialize anything that's not;
+        // 1. A known scalar safe value.
+        // 2. A definition that says it's simpledata.  We trust it that it doesn't contain dangerous references.
+        // 3. An object that handles dereferencing by itself.
+        if (is_scalar($data) || $this->definition->uses_simple_data()
+                || $data instanceof cache_cached_object) {
+            $this->staticaccelerationarray[$key]['data'] = $data;
+            $this->staticaccelerationarray[$key]['serialized'] = false;
+        } else {
+            $this->staticaccelerationarray[$key]['data'] = serialize($data);
+            $this->staticaccelerationarray[$key]['serialized'] = true;
+        }
         if ($this->staticaccelerationsize !== false) {
             $this->staticaccelerationcount++;
             $this->staticaccelerationkeys[$key] = $key;
@@ -1123,12 +1124,9 @@ class cache implements cache_loader {
      */
     protected function static_acceleration_delete($key) {
         unset($this->staticaccelerationarray[$key]);
-        if ($this->staticaccelerationsize !== false) {
-            $dropkey = array_search($key, $this->staticaccelerationkeys);
-            if ($dropkey) {
-                unset($this->staticaccelerationkeys[$dropkey]);
-                $this->staticaccelerationcount--;
-            }
+        if ($this->staticaccelerationsize !== false && isset($this->staticaccelerationkeys[$key])) {
+            unset($this->staticaccelerationkeys[$key]);
+            $this->staticaccelerationcount--;
         }
         return true;
     }
@@ -1806,7 +1804,7 @@ class cache_session extends cache {
         }
         // 6. Make sure we don't pass back anything that could be a reference.
         //    We don't want people modifying the data in the cache.
-        if (!is_scalar($result)) {
+        if (!$this->get_store()->supports_dereferencing_objects() && !is_scalar($result)) {
             // If data is an object it will be a reference.
             // If data is an array if may contain references.
             // We want to break references so that the cache cannot be modified outside of itself.
@@ -1846,7 +1844,7 @@ class cache_session extends cache {
         }
         if (is_object($data) && $data instanceof cacheable_object) {
             $data = new cache_cached_object($data);
-        } else if (!is_scalar($data)) {
+        } else if (!$this->get_store()->supports_dereferencing_objects() && !is_scalar($data)) {
             // If data is an object it will be a reference.
             // If data is an array if may contain references.
             // We want to break references so that the cache cannot be modified outside of itself.
@@ -1922,6 +1920,12 @@ class cache_session extends cache {
             if ($value instanceof cache_cached_object) {
                 /* @var cache_cached_object $value */
                 $value = $value->restore_object();
+            } else if (!$this->get_store()->supports_dereferencing_objects() && !is_scalar($value)) {
+                // If data is an object it will be a reference.
+                // If data is an array if may contain references.
+                // We want to break references so that the cache cannot be modified outside of itself.
+                // Call the function to unreference it (in the best way possible).
+                $value = $this->unref($value);
             }
             $return[$key] = $value;
             if ($value === false) {
@@ -2027,7 +2031,7 @@ class cache_session extends cache {
         foreach ($keyvaluearray as $key => $value) {
             if (is_object($value) && $value instanceof cacheable_object) {
                 $value = new cache_cached_object($value);
-            } else if (!is_scalar($value)) {
+            } else if (!$this->get_store()->supports_dereferencing_objects() && !is_scalar($value)) {
                 // If data is an object it will be a reference.
                 // If data is an array if may contain references.
                 // We want to break references so that the cache cannot be modified outside of itself.
index 2c4b099..c415ba9 100644 (file)
@@ -126,6 +126,14 @@ abstract class cache_store implements cache_store_interface {
      */
     const IS_SEARCHABLE = 8;
 
+    /**
+     * The cache store dereferences objects.
+     *
+     * When set, loaders will assume that all data coming from this store has already had all references
+     * resolved.  So even for complex object structures it will not try to remove references again.
+     */
+    const DEREFERENCES_OBJECTS = 16;
+
     // Constants for the modes of a cache store
 
     /**
@@ -334,6 +342,15 @@ abstract class cache_store implements cache_store_interface {
         return in_array('cache_is_searchable', class_implements($this));
     }
 
+    /**
+     * Returns true if the store automatically dereferences objects.
+     *
+     * @return bool
+     */
+    public function supports_dereferencing_objects() {
+        return $this::get_supported_features() & self::DEREFERENCES_OBJECTS;
+    }
+
     /**
      * Creates a clone of this store instance ready to be initialised.
      *
index 5a2ef3b..e482702 100644 (file)
@@ -132,6 +132,8 @@ class cache_definition_mappings_form extends moodleform {
      * The definition of the form
      */
     protected final function definition() {
+        global $OUTPUT;
+
         $definition = $this->_customdata['definition'];
         $form = $this->_form;
 
@@ -139,6 +141,14 @@ class cache_definition_mappings_form extends moodleform {
         list($currentstores, $storeoptions, $defaults) =
                 cache_administration_helper::get_definition_store_options($component, $area);
 
+        $storedata = cache_administration_helper::get_definition_summaries();
+        if ($storedata[$definition]['mode'] != cache_store::MODE_REQUEST) {
+            if (isset($storedata[$definition]['canuselocalstore']) && $storedata[$definition]['canuselocalstore']) {
+                $form->addElement('html', $OUTPUT->notification(get_string('localstorenotification', 'cache'), 'notifymessage'));
+            } else {
+                $form->addElement('html', $OUTPUT->notification(get_string('sharedstorenotification', 'cache'), 'notifymessage'));
+            }
+        }
         $form->addElement('hidden', 'definition', $definition);
         $form->setType('definition', PARAM_SAFEPATH);
         $form->addElement('hidden', 'action', 'editdefinitionmapping');
index 58dcdd8..2e6813e 100644 (file)
@@ -806,6 +806,7 @@ abstract class cache_administration_helper extends cache_helper {
                 'component' => $definition->get_component(),
                 'area' => $definition->get_area(),
                 'mappings' => $mappings,
+                'canuselocalstore' => $definition->can_use_localstore(),
                 'sharingoptions' => self::get_definition_sharing_options($definition->get_sharing_options(), false),
                 'selectedsharingoption' => self::get_definition_sharing_options($definition->get_selected_sharing_option(), true),
                 'userinputsharingkey' => $definition->get_user_input_sharing_key()
index e044c91..67a6642 100644 (file)
@@ -211,7 +211,8 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
     public static function get_supported_features(array $configuration = array()) {
         $supported = self::SUPPORTS_DATA_GUARANTEE +
                      self::SUPPORTS_NATIVE_TTL +
-                     self::IS_SEARCHABLE;
+                     self::IS_SEARCHABLE +
+                     self::DEREFERENCES_OBJECTS;
         return $supported;
     }
 
index 77efd21..e4ead7f 100644 (file)
@@ -271,7 +271,7 @@ class cachestore_memcache extends cache_store implements cache_is_configurable {
      * @return int
      */
     public static function get_supported_features(array $configuration = array()) {
-        return self::SUPPORTS_NATIVE_TTL;
+        return self::SUPPORTS_NATIVE_TTL + self::DEREFERENCES_OBJECTS;
     }
 
     /**
index 0ed6623..5e8250d 100644 (file)
@@ -258,7 +258,7 @@ class cachestore_memcached extends cache_store implements cache_is_configurable
      * @return int
      */
     public static function get_supported_features(array $configuration = array()) {
-        return self::SUPPORTS_NATIVE_TTL;
+        return self::SUPPORTS_NATIVE_TTL + self::DEREFERENCES_OBJECTS;
     }
 
     /**
index 7ea787d..641b051 100644 (file)
@@ -175,7 +175,7 @@ class cachestore_mongodb extends cache_store implements cache_is_configurable {
      * @return int
      */
     public static function get_supported_features(array $configuration = array()) {
-        $supports = self::SUPPORTS_DATA_GUARANTEE;
+        $supports = self::SUPPORTS_DATA_GUARANTEE + self::DEREFERENCES_OBJECTS;
         if (array_key_exists('extendedmode', $configuration) && $configuration['extendedmode']) {
             $supports += self::SUPPORTS_MULTIPLE_IDENTIFIERS;
         }
index 6c9f14c..26ab145 100644 (file)
@@ -407,7 +407,7 @@ class core_cache_testcase extends advanced_testcase {
         $this->assertEquals('pork', $var->subobj->subobj->key);
         $this->assertTrue($cache->delete('obj'));
 
-        // Death reference test... basicaly we don't want this to die.
+        // Death reference test... basically we don't want this to die.
         $obj = new stdClass;
         $obj->key = 'value';
         $obj->self =& $obj;
@@ -433,6 +433,32 @@ class core_cache_testcase extends advanced_testcase {
 
         $this->assertTrue($cache->delete('obj'));
 
+        // Death reference test on get_many... basically we don't want this to die.
+        $obj = new stdClass;
+        $obj->key = 'value';
+        $obj->self =& $obj;
+        $this->assertEquals(1, $cache->set_many(array('obj' => $obj)));
+        $var = $cache->get_many(array('obj'));
+        $this->assertInstanceOf('stdClass', $var['obj']);
+        $this->assertEquals('value', $var['obj']->key);
+
+        // Reference test after retrieve.
+        $obj = new stdClass;
+        $obj->key = 'value';
+        $this->assertEquals(1, $cache->set_many(array('obj' => $obj)));
+
+        $var1 = $cache->get_many(array('obj'));
+        $this->assertInstanceOf('stdClass', $var1['obj']);
+        $this->assertEquals('value', $var1['obj']->key);
+        $var1['obj']->key = 'eulav';
+        $this->assertEquals('eulav', $var1['obj']->key);
+
+        $var2 = $cache->get_many(array('obj'));
+        $this->assertInstanceOf('stdClass', $var2['obj']);
+        $this->assertEquals('value', $var2['obj']->key);
+
+        $this->assertTrue($cache->delete('obj'));
+
         // Test strictness exceptions.
         try {
             $cache->get('exception', MUST_EXIST);
index 8b3ef55..bae500d 100644 (file)
@@ -423,7 +423,6 @@ class cache_phpunit_application extends cache_application {
      * @return false|mixed
      */
     public function phpunit_static_acceleration_get($key) {
-        $key = $this->parse_key($key);
         return $this->static_acceleration_get($key);
     }
 }
index 1c1b133..0b4cbb9 100644 (file)
@@ -1,6 +1,12 @@
 This files describes API changes in /cache/stores/* - cache store plugins.
 Information provided here is intended especially for developers.
 
+=== 3.1 ===
+* Cache stores has a new feature DEREFERENCES_OBJECTS.
+  This allows the cache loader to decide if it needs to handle dereferencing or whether the data
+  coming directly to it has already had references resolved.
+  - see supports_dereferencing_objects in store.php.
+
 === 2.9 ===
 * Cache data source aggregation functionality has been removed. This functionality was found to be broken and unused.
   It was decided that rather than fixing it it should be removed.
index 1ddc12d..95bb480 100644 (file)
@@ -61,6 +61,8 @@ if ($delete === md5($course->timemodified)) {
 
     echo $OUTPUT->header();
     echo $OUTPUT->heading($strdeletingcourse);
+    // This might take a while. Raise the execution time limit.
+    core_php_time_limit::raise();
     // We do this here because it spits out feedback as it goes.
     delete_course($course);
     echo $OUTPUT->heading( get_string("deletedcourse", "", $courseshortname) );
diff --git a/enrol/cohort/edit.php b/enrol/cohort/edit.php
deleted file mode 100644 (file)
index 3affaa9..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Adds new instance of enrol_cohort to specified course.
- *
- * @package    enrol_cohort
- * @copyright  2010 Petr Skoda {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-require('../../config.php');
-require_once("$CFG->dirroot/enrol/cohort/edit_form.php");
-require_once("$CFG->dirroot/enrol/cohort/locallib.php");
-require_once("$CFG->dirroot/group/lib.php");
-
-$courseid = required_param('courseid', PARAM_INT);
-$instanceid = optional_param('id', 0, PARAM_INT);
-$message = optional_param('message', null, PARAM_TEXT);
-
-$course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
-$context = context_course::instance($course->id, MUST_EXIST);
-
-require_login($course);
-require_capability('moodle/course:enrolconfig', $context);
-require_capability('enrol/cohort:config', $context);
-
-$PAGE->set_url('/enrol/cohort/edit.php', array('courseid'=>$course->id, 'id'=>$instanceid));
-$PAGE->set_pagelayout('admin');
-
-$returnurl = new moodle_url('/enrol/instances.php', array('id'=>$course->id));
-if (!enrol_is_enabled('cohort')) {
-    redirect($returnurl);
-}
-
-$enrol = enrol_get_plugin('cohort');
-
-if ($instanceid) {
-    $instance = $DB->get_record('enrol', array('courseid'=>$course->id, 'enrol'=>'cohort', 'id'=>$instanceid), '*', MUST_EXIST);
-
-} else {
-    // No instance yet, we have to add new instance.
-    if (!$enrol->get_newinstance_link($course->id)) {
-        redirect($returnurl);
-    }
-    navigation_node::override_active_url(new moodle_url('/enrol/instances.php', array('id'=>$course->id)));
-    $instance = new stdClass();
-    $instance->id         = null;
-    $instance->courseid   = $course->id;
-    $instance->enrol      = 'cohort';
-    $instance->customint1 = ''; // Cohort id.
-    $instance->customint2 = 0;  // Optional group id.
-}
-
-// Try and make the manage instances node on the navigation active.
-$courseadmin = $PAGE->settingsnav->get('courseadmin');
-if ($courseadmin && $courseadmin->get('users') && $courseadmin->get('users')->get('manageinstances')) {
-    $courseadmin->get('users')->get('manageinstances')->make_active();
-}
-
-
-$mform = new enrol_cohort_edit_form(null, array($instance, $enrol, $course));
-
-if ($mform->is_cancelled()) {
-    redirect($returnurl);
-
-} else if ($data = $mform->get_data()) {
-    if ($data->id) {
-        // NOTE: no cohort changes here!!!
-        if ($data->roleid != $instance->roleid) {
-            // The sync script can only add roles, for perf reasons it does not modify them.
-            role_unassign_all(array('contextid'=>$context->id, 'roleid'=>$instance->roleid, 'component'=>'enrol_cohort', 'itemid'=>$instance->id));
-        }
-        $instance->name         = $data->name;
-        $instance->status       = $data->status;
-        $instance->roleid       = $data->roleid;
-        $instance->customint2   = $data->customint2;
-        $instance->timemodified = time();
-        // Create a new group for the cohort if requested.
-        if ($data->customint2 == COHORT_CREATE_GROUP) {
-            require_capability('moodle/course:managegroups', $context);
-            $groupid = enrol_cohort_create_new_group($course->id, $data->customint1);
-            $instance->customint2 = $groupid;
-        }
-        $DB->update_record('enrol', $instance);
-        \core\event\enrol_instance_updated::create_from_record($instance)->trigger();
-    }  else {
-        // Create a new group for the cohort if requested.
-        if ($data->customint2 == COHORT_CREATE_GROUP) {
-            require_capability('moodle/course:managegroups', $context);
-            $groupid = enrol_cohort_create_new_group($course->id, $data->customint1);
-            $enrol->add_instance($course, array('name' => $data->name, 'status' => $data->status,
-                'customint1' => $data->customint1, 'roleid' => $data->roleid, 'customint2' => $groupid));
-        } else {
-            $enrol->add_instance($course, array('name' => $data->name, 'status' => $data->status,
-                'customint1' => $data->customint1, 'roleid' => $data->roleid, 'customint2' => $data->customint2));
-        }
-        if (!empty($data->submitbuttonnext)) {
-            $returnurl = new moodle_url($PAGE->url);
-            $returnurl->param('message', 'added');
-        }
-    }
-    $trace = new null_progress_trace();
-    enrol_cohort_sync($trace, $course->id);
-    $trace->finished();
-    redirect($returnurl);
-}
-
-$PAGE->set_heading($course->fullname);
-$PAGE->set_title(get_string('pluginname', 'enrol_cohort'));
-
-echo $OUTPUT->header();
-if ($message === 'added') {
-    echo $OUTPUT->notification(get_string('instanceadded', 'enrol'), 'notifysuccess');
-}
-$mform->display();
-echo $OUTPUT->footer();
diff --git a/enrol/cohort/edit_form.php b/enrol/cohort/edit_form.php
deleted file mode 100644 (file)
index 2a02a29..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Adds instance form
- *
- * @package    enrol_cohort
- * @copyright  2010 Petr Skoda {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-require_once("$CFG->libdir/formslib.php");
-
-class enrol_cohort_edit_form extends moodleform {
-
-    function definition() {
-        global $CFG, $DB;
-
-        $mform = $this->_form;
-
-        list($instance, $plugin, $course) = $this->_customdata;
-        $coursecontext = context_course::instance($course->id);
-
-        $enrol = enrol_get_plugin('cohort');
-
-        $groups = array(0 => get_string('none'));
-        if (has_capability('moodle/course:managegroups', $coursecontext)) {
-            $groups[COHORT_CREATE_GROUP] = get_string('creategroup', 'enrol_cohort');
-        }
-
-        foreach (groups_get_all_groups($course->id) as $group) {
-            $groups[$group->id] = format_string($group->name, true, array('context'=>$coursecontext));
-        }
-
-        $mform->addElement('header','general', get_string('pluginname', 'enrol_cohort'));
-
-        $mform->addElement('text', 'name', get_string('custominstancename', 'enrol'));
-        $mform->setType('name', PARAM_TEXT);
-
-        $options = array(ENROL_INSTANCE_ENABLED  => get_string('yes'),
-                         ENROL_INSTANCE_DISABLED => get_string('no'));
-        $mform->addElement('select', 'status', get_string('status', 'enrol_cohort'), $options);
-
-        if ($instance->id) {
-            if ($cohort = $DB->get_record('cohort', array('id'=>$instance->customint1))) {
-                $cohorts = array($instance->customint1=>format_string($cohort->name, true, array('context'=>context::instance_by_id($cohort->contextid))));
-            } else {
-                $cohorts = array($instance->customint1=>get_string('error'));
-            }
-            $mform->addElement('select', 'customint1', get_string('cohort', 'cohort'), $cohorts);
-            $mform->setConstant('customint1', $instance->customint1);
-            $mform->hardFreeze('customint1', $instance->customint1);
-
-        } else {
-            $cohorts = array('' => get_string('choosedots'));
-            $allcohorts = cohort_get_available_cohorts($coursecontext, 0, 0, 0);
-            foreach ($allcohorts as $c) {
-                $cohorts[$c->id] = format_string($c->name);
-            }
-            $mform->addElement('select', 'customint1', get_string('cohort', 'cohort'), $cohorts);
-            $mform->addRule('customint1', get_string('required'), 'required', null, 'client');
-        }
-
-        $roles = get_assignable_roles($coursecontext);
-        $roles[0] = get_string('none');
-        $roles = array_reverse($roles, true); // Descending default sortorder.
-        $mform->addElement('select', 'roleid', get_string('assignrole', 'enrol_cohort'), $roles);
-        $mform->setDefault('roleid', $enrol->get_config('roleid'));
-        if ($instance->id and !isset($roles[$instance->roleid])) {
-            if ($role = $DB->get_record('role', array('id'=>$instance->roleid))) {
-                $roles = role_fix_names($roles, $coursecontext, ROLENAME_ALIAS, true);
-                $roles[$instance->roleid] = role_get_name($role, $coursecontext);
-            } else {
-                $roles[$instance->roleid] = get_string('error');
-            }
-        }
-        $mform->addElement('select', 'customint2', get_string('addgroup', 'enrol_cohort'), $groups);
-
-        $mform->addElement('hidden', 'courseid', null);
-        $mform->setType('courseid', PARAM_INT);
-
-        $mform->addElement('hidden', 'id', null);
-        $mform->setType('id', PARAM_INT);
-
-        if ($instance->id) {
-            $this->add_action_buttons(true);
-        } else {
-            $this->add_add_buttons();
-        }
-
-        $this->set_data($instance);
-    }
-
-    /**
-     * Adds buttons on create new method form
-     */
-    protected function add_add_buttons() {
-        $mform = $this->_form;
-        $buttonarray = array();
-        $buttonarray[0] = $mform->createElement('submit', 'submitbutton', get_string('addinstance', 'enrol'));
-        $buttonarray[1] = $mform->createElement('submit', 'submitbuttonnext', get_string('addinstanceanother', 'enrol'));
-        $buttonarray[2] = $mform->createElement('cancel');
-        $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
-        $mform->closeHeaderBefore('buttonar');
-    }
-
-    function validation($data, $files) {
-        global $DB;
-
-        $errors = parent::validation($data, $files);
-
-        $params = array('roleid'=>$data['roleid'], 'customint1'=>$data['customint1'], 'courseid'=>$data['courseid'], 'id'=>$data['id']);
-        if ($DB->record_exists_select('enrol', "roleid = :roleid AND customint1 = :customint1 AND courseid = :courseid AND enrol = 'cohort' AND id <> :id", $params)) {
-            $errors['roleid'] = get_string('instanceexists', 'enrol_cohort');
-        }
-
-        return $errors;
-    }
-}
index 61be2ad..da38760 100644 (file)
@@ -79,19 +79,6 @@ class enrol_cohort_plugin extends enrol_plugin {
         }
     }
 
-    /**
-     * Returns link to page which may be used to add new instance of enrolment plugin in course.
-     * @param int $courseid
-     * @return moodle_url page url
-     */
-    public function get_newinstance_link($courseid) {
-        if (!$this->can_add_new_instances($courseid)) {
-            return NULL;
-        }
-        // Multiple instances supported - multiple parent courses linked.
-        return new moodle_url('/enrol/cohort/edit.php', array('courseid'=>$courseid));
-    }
-
     /**
      * Given a courseid this function returns true if the user is able to enrol or configure cohorts.
      * AND there are cohorts that the user can view.
@@ -99,7 +86,7 @@ class enrol_cohort_plugin extends enrol_plugin {
      * @param int $courseid
      * @return bool
      */
-    protected function can_add_new_instances($courseid) {
+    public function can_add_instance($courseid) {
         global $CFG;
         require_once($CFG->dirroot . '/cohort/lib.php');
         $coursecontext = context_course::instance($courseid);
@@ -110,27 +97,51 @@ class enrol_cohort_plugin extends enrol_plugin {
     }
 
     /**
-     * Returns edit icons for the page with list of instances.
-     * @param stdClass $instance
-     * @return array
+     * Add new instance of enrol plugin.
+     * @param object $course
+     * @param array $fields instance fields
+     * @return int id of new instance, null if can not be created
      */
-    public function get_action_icons(stdClass $instance) {
-        global $OUTPUT;
-
-        if ($instance->enrol !== 'cohort') {
-            throw new coding_exception('invalid enrol instance!');
+    public function add_instance($course, array $fields = null) {
+
+        if (!empty($fields['customint2']) && $fields['customint2'] == COHORT_CREATE_GROUP) {
+            // Create a new group for the cohort if requested.
+            $context = context_course::instance($course->id);
+            require_capability('moodle/course:managegroups', $context);
+            $groupid = enrol_cohort_create_new_group($course->id, $fields['customint1']);
+            $fields['customint2'] = $groupid;
         }
-        $context = context_course::instance($instance->courseid);
 
-        $icons = array();
+        return parent::add_instance($course, $fields);
+    }
 
-        if (has_capability('enrol/cohort:config', $context)) {
-            $editlink = new moodle_url("/enrol/cohort/edit.php", array('courseid'=>$instance->courseid, 'id'=>$instance->id));
-            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
-                    array('class' => 'iconsmall')));
+    /**
+     * Update instance of enrol plugin.
+     * @param stdClass $instance
+     * @param stdClass $data modified instance fields
+     * @return boolean
+     */
+    public function update_instance($instance, $data) {
+        // NOTE: no cohort changes here!!!
+        $context = context_course::instance($instance->courseid);
+        if ($data->roleid != $instance->roleid) {
+            // The sync script can only add roles, for perf reasons it does not modify them.
+            $params = array(
+                'contextid' => $context->id,
+                'roleid' => $instance->roleid,
+                'component' => 'enrol_cohort',
+                'itemid' => $instance->id
+            );
+            role_unassign_all($params);
+        }
+        // Create a new group for the cohort if requested.
+        if ($data->customint2 == COHORT_CREATE_GROUP) {
+            require_capability('moodle/course:managegroups', $context);
+            $groupid = enrol_cohort_create_new_group($instance->courseid, $data->customint1);
+            $data->customint2 = $groupid;
         }
 
-        return $icons;
+        return parent::update_instance($instance, $data);
     }
 
     /**
@@ -316,6 +327,176 @@ class enrol_cohort_plugin extends enrol_plugin {
         $context = context_course::instance($instance->courseid);
         return has_capability('enrol/cohort:config', $context);
     }
+
+    /**
+     * Return an array of valid options for the status.
+     *
+     * @return array
+     */
+    protected function get_status_options() {
+        $options = array(ENROL_INSTANCE_ENABLED  => get_string('yes'),
+                         ENROL_INSTANCE_DISABLED => get_string('no'));
+        return $options;
+    }
+
+    /**
+     * Return an array of valid options for the cohorts.
+     *
+     * @param stdClass $instance
+     * @param context $context
+     * @return array
+     */
+    protected function get_cohort_options($instance, $context) {
+        global $DB, $CFG;
+
+        require_once($CFG->dirroot . '/cohort/lib.php');
+
+        $cohorts = array();
+
+        if ($instance->id) {
+            if ($cohort = $DB->get_record('cohort', array('id' => $instance->customint1))) {
+                $name = format_string($cohort->name, true, array('context' => context::instance_by_id($cohort->contextid)));
+                $cohorts = array($instance->customint1 => $name);
+            } else {
+                $cohorts = array($instance->customint1 => get_string('error'));
+            }
+        } else {
+            $cohorts = array('' => get_string('choosedots'));
+            $allcohorts = cohort_get_available_cohorts($context, 0, 0, 0);
+            foreach ($allcohorts as $c) {
+                $cohorts[$c->id] = format_string($c->name);
+            }
+        }
+        return $cohorts;
+    }
+
+    /**
+     * Return an array of valid options for the roles.
+     *
+     * @param stdClass $instance
+     * @param context $coursecontext
+     * @return array
+     */
+    protected function get_role_options($instance, $coursecontext) {
+        global $DB;
+
+        $roles = get_assignable_roles($coursecontext);
+        $roles[0] = get_string('none');
+        $roles = array_reverse($roles, true); // Descending default sortorder.
+        if ($instance->id and !isset($roles[$instance->roleid])) {
+            if ($role = $DB->get_record('role', array('id' => $instance->roleid))) {
+                $roles = role_fix_names($roles, $coursecontext, ROLENAME_ALIAS, true);
+                $roles[$instance->roleid] = role_get_name($role, $coursecontext);
+            } else {
+                $roles[$instance->roleid] = get_string('error');
+            }
+        }
+
+        return $roles;
+    }
+
+    /**
+     * Return an array of valid options for the groups.
+     *
+     * @param context $coursecontext
+     * @return array
+     */
+    protected function get_group_options($coursecontext) {
+        $groups = array(0 => get_string('none'));
+        if (has_capability('moodle/course:managegroups', $coursecontext)) {
+            $groups[COHORT_CREATE_GROUP] = get_string('creategroup', 'enrol_cohort');
+        }
+
+        foreach (groups_get_all_groups($coursecontext->instanceid) as $group) {
+            $groups[$group->id] = format_string($group->name, true, array('context' => $coursecontext));
+        }
+
+        return $groups;
+    }
+
+    /**
+     * We are a good plugin and don't invent our own UI/validation code path.
+     *
+     * @return boolean
+     */
+    public function use_standard_editing_ui() {
+        return true;
+    }
+
+    /**
+     * Add elements to the edit instance form.
+     *
+     * @param stdClass $instance
+     * @param MoodleQuickForm $mform
+     * @param context $coursecontext
+     * @return bool
+     */
+    public function edit_instance_form($instance, MoodleQuickForm $mform, $coursecontext) {
+        global $DB;
+
+        $mform->addElement('text', 'name', get_string('custominstancename', 'enrol'));
+        $mform->setType('name', PARAM_TEXT);
+
+        $options = $this->get_status_options();
+        $mform->addElement('select', 'status', get_string('status', 'enrol_cohort'), $options);
+
+        $options = $this->get_cohort_options($instance, $coursecontext);
+        $mform->addElement('select', 'customint1', get_string('cohort', 'cohort'), $options);
+        if ($instance->id) {
+            $mform->setConstant('customint1', $instance->customint1);
+            $mform->hardFreeze('customint1', $instance->customint1);
+        } else {
+            $mform->addRule('customint1', get_string('required'), 'required', null, 'client');
+        }
+
+        $roles = $this->get_role_options($instance, $coursecontext);
+        $mform->addElement('select', 'roleid', get_string('assignrole', 'enrol_cohort'), $roles);
+        $mform->setDefault('roleid', $this->get_config('roleid'));
+        $groups = $this->get_group_options($coursecontext);
+        $mform->addElement('select', 'customint2', get_string('addgroup', 'enrol_cohort'), $groups);
+    }
+
+    /**
+     * Perform custom validation of the data used to edit the instance.
+     *
+     * @param array $data array of ("fieldname" => value) of submitted data
+     * @param array $files array of uploaded files "element_name" => tmp_file_path
+     * @param object $instance The instance loaded from the DB
+     * @param context $context The context of the instance we are editing
+     * @return array of "element_name" => "error_description" if there are errors,
+     *         or an empty array if everything is OK.
+     * @return void
+     */
+    public function edit_instance_validation($data, $files, $instance, $context) {
+        global $DB;
+        $errors = array();
+
+        $params = array(
+            'roleid' => $data['roleid'],
+            'customint1' => $data['customint1'],
+            'courseid' => $data['courseid'],
+            'id' => $data['id']
+        );
+        $sql = "roleid = :roleid AND customint1 = :customint1 AND courseid = :courseid AND enrol = 'cohort' AND id <> :id";
+        if ($DB->record_exists_select('enrol', $sql, $params)) {
+            $errors['roleid'] = get_string('instanceexists', 'enrol_cohort');
+        }
+        $validstatus = array_keys($this->get_status_options());
+        $validcohorts = array_keys($this->get_cohort_options($instance, $context));
+        $validroles = array_keys($this->get_role_options($instance, $context));
+        $validgroups = array_keys($this->get_group_options($context));
+        $tovalidate = array(
+            'name' => PARAM_TEXT,
+            'status' => $validstatus,
+            'customint1' => $validcohorts,
+            'roleid' => $validroles,
+            'customint2' => $validgroups
+        );
+        $typeerrors = $this->validate_param_types($data, $tovalidate);
+        $errors = array_merge($errors, $typeerrors);
+
+        return $errors;
+    }
 }
 
 /**
@@ -337,7 +518,9 @@ function enrol_cohort_allow_group_member_remove($itemid, $groupid, $userid) {
  * @return int $groupid Group ID for this cohort.
  */
 function enrol_cohort_create_new_group($courseid, $cohortid) {
-    global $DB;
+    global $DB, $CFG;
+
+    require_once($CFG->dirroot . '/group/lib.php');
 
     $groupname = $DB->get_field('cohort', 'name', array('id' => $cohortid), MUST_EXIST);
     $a = new stdClass();
similarity index 59%
rename from enrol/guest/edit.php
rename to enrol/editinstance.php
index 975f250..38d3ae7 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Edit instance of enrol_guest.
+ * Adds new instance of an enrolment plugin to specified course or edits current instance.
  *
- * Adds new instance of enrol_guest to specified course
- * or edits current instance.
- *
- * @package    enrol_guest
- * @copyright  2015 Andrew Hancox <andrewdchancox@googlemail.com>
+ * @package    core_enrol
+ * @copyright  2015 Damyon Wiese
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-require('../../config.php');
+require('../config.php');
+require_once('editinstance_form.php');
 
 $courseid   = required_param('courseid', PARAM_INT);
+$type   = required_param('type', PARAM_COMPONENT);
 $instanceid = optional_param('id', 0, PARAM_INT);
 
 $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
 $context = context_course::instance($course->id, MUST_EXIST);
 
+$plugin = enrol_get_plugin($type);
+if (!$plugin) {
+    throw new moodle_exception('invaliddata', 'error');
+}
+
 require_login($course);
-require_capability('enrol/guest:config', $context);
+require_capability('enrol/' . $type . ':config', $context);
 
-$PAGE->set_url('/enrol/guest/edit.php', array('courseid' => $course->id, 'id' => $instanceid));
+$PAGE->set_url('/enrol/editinstance.php', array('courseid' => $course->id, 'id' => $instanceid, 'type' => $type));
 $PAGE->set_pagelayout('admin');
 
 $return = new moodle_url('/enrol/instances.php', array('id' => $course->id));
-if (!enrol_is_enabled('guest')) {
+if (!enrol_is_enabled($type)) {
     redirect($return);
 }
 
-$plugin = enrol_get_plugin('guest');
-
 if ($instanceid) {
-    $conditions = array('courseid' => $course->id, 'enrol' => 'guest', 'id' => $instanceid);
-    $instance = $DB->get_record('enrol', $conditions, '*', MUST_EXIST);
+    $instance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => $type, 'id' => $instanceid), '*', MUST_EXIST);
+
 } else {
     require_capability('moodle/course:enrolconfig', $context);
     // No instance yet, we have to add new instance.
@@ -57,10 +59,10 @@ if ($instanceid) {
     $instance = (object)$plugin->get_instance_defaults();
     $instance->id       = null;
     $instance->courseid = $course->id;
+    $instance->status   = ENROL_INSTANCE_ENABLED; // Do not use default for automatically created instances here.
 }
 
-$mform = new \enrol_guest\enrol_guest_edit_form(null, array($instance, $plugin));
-$mform->set_data($instance);
+$mform = new enrol_instance_edit_form(null, array($instance, $plugin, $context, $type));
 
 if ($mform->is_cancelled()) {
     redirect($return);
@@ -68,22 +70,25 @@ if ($mform->is_cancelled()) {
 } else if ($data = $mform->get_data()) {
 
     if ($instance->id) {
-        $reset = ($instance->status != $data->status);
+        $reset = false;
+        if (isset($data->status)) {
+            $reset = ($instance->status != $data->status);
+        }
+
+        foreach ($data as $key => $value) {
+            $instance->$key = $value;
+        }
 
-        $instance->status         = $data->status;
-        $instance->password       = $data->password;
         $instance->timemodified   = time();
-        $DB->update_record('enrol', $instance);
+
+        $plugin->update_instance($instance, $data);
 
         if ($reset) {
             $context->mark_dirty();
         }
 
-        \core\event\enrol_instance_updated::create_from_record($instance)->trigger();
     } else {
-        $fields = array(
-            'status'          => $data->status,
-            'password'        => $data->password);
+        $fields = (array) $data;
         $plugin->add_instance($course, $fields);
     }
 
@@ -91,9 +96,9 @@ if ($mform->is_cancelled()) {
 }
 
 $PAGE->set_heading($course->fullname);
-$PAGE->set_title(get_string('pluginname', 'enrol_guest'));
+$PAGE->set_title(get_string('pluginname', 'enrol_' . $type));
 
 echo $OUTPUT->header();
-echo $OUTPUT->heading(get_string('pluginname', 'enrol_guest'));
+echo $OUTPUT->heading(get_string('pluginname', 'enrol_' . $type));
 $mform->display();
 echo $OUTPUT->footer();
diff --git a/enrol/editinstance_form.php b/enrol/editinstance_form.php
new file mode 100644 (file)
index 0000000..728a7e8
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Adds new instance of enrol_plugin to specified course or edits current instance.
+ *
+ * @package    core_enrol
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir.'/formslib.php');
+
+/**
+ * Standard edit form shared by all enrol plugins.
+ *
+ * @package    core_enrol
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class enrol_instance_edit_form extends moodleform {
+
+    /**
+     * Called to define this moodle form
+     *
+     * @return void
+     */
+    public function definition() {
+        global $DB;
+
+        $mform = $this->_form;
+
+        list($instance, $plugin, $context, $type) = $this->_customdata;
+
+        $mform->addElement('header', 'header', get_string('pluginname', 'enrol_' . $type));
+
+        $plugin->edit_instance_form($instance, $mform, $context);
+
+        $mform->addElement('hidden', 'id');
+        $mform->setType('id', PARAM_INT);
+        $mform->addElement('hidden', 'courseid');
+        $mform->setType('courseid', PARAM_INT);
+
+        $mform->addElement('hidden', 'type');
+        $mform->setType('type', PARAM_COMPONENT);
+        $instance->type = $type;
+
+        $this->add_action_buttons(true, ($instance->id ? null : get_string('addinstance', 'enrol')));
+
+        $this->set_data($instance);
+    }
+
+    /**
+     * Validate this form. Calls plugin validation method.
+     *
+     * @param array $data
+     * @param array $files
+     * @return array
+     */
+    public function validation($data, $files) {
+        $errors = parent::validation($data, $files);
+
+        list($instance, $plugin, $context, $type) = $this->_customdata;
+
+        $pluginerrors = $plugin->edit_instance_validation($data, $files, $instance, $context);
+
+        $errors = array_merge($errors, $pluginerrors);
+
+        return $errors;
+    }
+
+}
diff --git a/enrol/guest/classes/enrol_guest_edit_form.php b/enrol/guest/classes/enrol_guest_edit_form.php
deleted file mode 100644 (file)
index 4eb6ef1..0000000
+++ /dev/null
@@ -1,119 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Guest access plugin.
- *
- * Adds new instance of enrol_guest to specified course
- * or edits current instance.
- *
- * @package    enrol_guest
- * @copyright  2015 Andrew Hancox <andrewdchancox@googlemail.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-namespace enrol_guest;
-use moodleform;
-
-defined('MOODLE_INTERNAL') || die();
-
-require_once($CFG->libdir.'/formslib.php');
-
-/**
- * Class enrol_guest_edit_form
- * @copyright  2015 Andrew Hancox <andrewdchancox@googlemail.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class enrol_guest_edit_form extends moodleform {
-    /**
-     * Form definition
-     */
-    public function definition() {
-
-        $mform = $this->_form;
-
-        list($instance, $plugin) = $this->_customdata;
-
-        $mform->addElement('header', 'header', get_string('pluginname', 'enrol_guest'));
-
-        $options = array(ENROL_INSTANCE_ENABLED  => get_string('yes'),
-                         ENROL_INSTANCE_DISABLED => get_string('no'));
-        $mform->addElement('select', 'status', get_string('status', 'enrol_guest'), $options);
-        $mform->addHelpButton('status', 'status', 'enrol_guest');
-        $mform->setDefault('status', $plugin->get_config('status'));
-        $mform->setAdvanced('status', $plugin->get_config('status_adv'));
-
-        $mform->addElement('passwordunmask', 'password', get_string('password', 'enrol_guest'));
-        $mform->addHelpButton('password', 'password', 'enrol_guest');
-
-        // If we have a new instance and the password is required - make sure it is set. For existing
-        // instances we do not force the password to be required as it may have been set to empty before
-        // the password was required. We check in the validation function whether this check is required
-        // for existing instances.
-        if (empty($instance->id) && $plugin->get_config('requirepassword')) {
-            $mform->addRule('password', get_string('required'), 'required', null);
-        }
-
-        $mform->addElement('hidden', 'id');
-        $mform->setType('id', PARAM_INT);
-        $mform->addElement('hidden', 'courseid');
-        $mform->setType('courseid', PARAM_INT);
-
-        $this->add_action_buttons(true, ($instance->id ? null : get_string('addinstance', 'enrol')));
-    }
-
-    /**
-     * Form validation
-     *
-     * @param array $data
-     * @param array $files
-     * @return array
-     */
-    public function validation($data, $files) {
-        $errors = parent::validation($data, $files);
-
-        list($instance, $plugin) = $this->_customdata;
-        $checkpassword = false;
-
-        if ($data['id']) {
-            // Check the password if we are enabling the plugin again.
-            if (($instance->status == ENROL_INSTANCE_DISABLED) && ($data['status'] == ENROL_INSTANCE_ENABLED)) {
-                $checkpassword = true;
-            }
-
-            // Check the password if the instance is enabled and the password has changed.
-            if (($data['status'] == ENROL_INSTANCE_ENABLED) && ($instance->password !== $data['password'])) {
-                $checkpassword = true;
-            }
-        } else {
-            $checkpassword = true;
-        }
-
-        if ($checkpassword) {
-            $require = $plugin->get_config('requirepassword');
-            $policy  = $plugin->get_config('usepasswordpolicy');
-            if ($require && trim($data['password']) === '') {
-                $errors['password'] = get_string('required');
-            } else if (!empty($data['password']) && $policy) {
-                $errmsg = '';
-                if (!check_password_policy($data['password'], $errmsg)) {
-                    $errors['password'] = $errmsg;
-                }
-            }
-        }
-
-        return $errors;
-    }
-}
index 0c69877..696ae6b 100644 (file)
@@ -84,51 +84,6 @@ class enrol_guest_plugin extends enrol_plugin {
         return;
     }
 
-    /**
-     * Sets up navigation entries.
-     *
-     * @param stdClass $instancesnode
-     * @param stdClass $instance
-     * @return void
-     * @throws coding_exception
-     */
-    public function add_course_navigation($instancesnode, stdClass $instance) {
-        if ($instance->enrol !== 'guest') {
-             throw new coding_exception('Invalid enrol instance type!');
-        }
-
-        $context = context_course::instance($instance->courseid);
-        if (has_capability('enrol/guest:config', $context)) {
-            $managelink = new moodle_url('/enrol/guest/edit.php', array('courseid' => $instance->courseid, 'id' => $instance->id));
-            $instancesnode->add($this->get_instance_name($instance), $managelink, navigation_node::TYPE_SETTING);
-        }
-    }
-
-    /**
-     * Returns edit icons for the page with list of instances
-     * @param stdClass $instance
-     * @return array
-     * @throws coding_exception
-     */
-    public function get_action_icons(stdClass $instance) {
-        global $OUTPUT;
-
-        if ($instance->enrol !== 'guest') {
-            throw new coding_exception('invalid enrol instance!');
-        }
-        $context = context_course::instance($instance->courseid);
-
-        $icons = array();
-
-        if (has_capability('enrol/guest:config', $context)) {
-            $editlink = new moodle_url("/enrol/guest/edit.php", array('courseid' => $instance->courseid, 'id' => $instance->id));
-            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
-                array('class' => 'iconsmall')));
-        }
-
-        return $icons;
-    }
-
     /**
      * Attempt to automatically gain temporary guest access to course,
      * calling code has to make sure the plugin and instance are active.
@@ -161,24 +116,24 @@ class enrol_guest_plugin extends enrol_plugin {
     }
 
     /**
-     * Returns link to page which may be used to add new instance of enrolment plugin in course.
+     * Returns true if the current user can add a new instance of enrolment plugin in course.
      * @param int $courseid
-     * @return moodle_url page url
+     * @return boolean
      */
-    public function get_newinstance_link($courseid) {
+    public function can_add_instance($courseid) {
         global $DB;
 
         $context = context_course::instance($courseid, MUST_EXIST);
 
         if (!has_capability('moodle/course:enrolconfig', $context) or !has_capability('enrol/guest:config', $context)) {
-            return NULL;
+            return false;
         }
 
         if ($DB->record_exists('enrol', array('courseid'=>$courseid, 'enrol'=>'guest'))) {
-            return NULL;
+            return false;
         }
 
-        return new moodle_url('/enrol/guest/edit.php', array('courseid' => $courseid));
+        return true;
     }
 
     /**
@@ -420,4 +375,108 @@ class enrol_guest_plugin extends enrol_plugin {
         }
         return $instanceinfo;
     }
+
+    /**
+     * Return an array of valid options for the status.
+     *
+     * @return array
+     */
+    protected function get_status_options() {
+        $options = array(ENROL_INSTANCE_ENABLED  => get_string('yes'),
+                         ENROL_INSTANCE_DISABLED => get_string('no'));
+        return $options;
+    }
+
+    /**
+     * Add elements to the edit instance form.
+     *
+     * @param stdClass $instance
+     * @param MoodleQuickForm $mform
+     * @param context $context
+     * @return bool
+     */
+    public function edit_instance_form($instance, MoodleQuickForm $mform, $context) {
+        global $CFG;
+
+        $options = $this->get_status_options();
+        $mform->addElement('select', 'status', get_string('status', 'enrol_guest'), $options);
+        $mform->addHelpButton('status', 'status', 'enrol_guest');
+        $mform->setDefault('status', $this->get_config('status'));
+        $mform->setAdvanced('status', $this->get_config('status_adv'));
+
+        $mform->addElement('passwordunmask', 'password', get_string('password', 'enrol_guest'));
+        $mform->addHelpButton('password', 'password', 'enrol_guest');
+
+        // If we have a new instance and the password is required - make sure it is set. For existing
+        // instances we do not force the password to be required as it may have been set to empty before
+        // the password was required. We check in the validation function whether this check is required
+        // for existing instances.
+        if (empty($instance->id) && $this->get_config('requirepassword')) {
+            $mform->addRule('password', get_string('required'), 'required', null);
+        }
+    }
+
+    /**
+     * We are a good plugin and don't invent our own UI/validation code path.
+     *
+     * @return boolean
+     */
+    public function use_standard_editing_ui() {
+        return true;
+    }
+
+    /**
+     * Perform custom validation of the data used to edit the instance.
+     *
+     * @param array $data array of ("fieldname"=>value) of submitted data
+     * @param array $files array of uploaded files "element_name"=>tmp_file_path
+     * @param object $instance The instance loaded from the DB
+     * @param context $context The context of the instance we are editing
+     * @return array of "element_name"=>"error_description" if there are errors,
+     *         or an empty array if everything is OK.
+     * @return void
+     */
+    public function edit_instance_validation($data, $files, $instance, $context) {
+        $errors = array();
+
+        $checkpassword = false;
+
+        if ($data['id']) {
+            // Check the password if we are enabling the plugin again.
+            if (($instance->status == ENROL_INSTANCE_DISABLED) && ($data['status'] == ENROL_INSTANCE_ENABLED)) {
+                $checkpassword = true;
+            }
+
+            // Check the password if the instance is enabled and the password has changed.
+            if (($data['status'] == ENROL_INSTANCE_ENABLED) && ($instance->password !== $data['password'])) {
+                $checkpassword = true;
+            }
+        } else {
+            $checkpassword = true;
+        }
+
+        if ($checkpassword) {
+            $require = $this->get_config('requirepassword');
+            $policy  = $this->get_config('usepasswordpolicy');
+            if ($require && trim($data['password']) === '') {
+                $errors['password'] = get_string('required');
+            } else if (!empty($data['password']) && $policy) {
+                $errmsg = '';
+                if (!check_password_policy($data['password'], $errmsg)) {
+                    $errors['password'] = $errmsg;
+                }
+            }
+        }
+
+        $validstatus = array_keys($this->get_status_options());
+        $tovalidate = array(
+            'status' => $validstatus
+        );
+        $typeerrors = $this->validate_param_types($data, $tovalidate);
+        $errors = array_merge($errors, $typeerrors);
+
+        return $errors;
+    }
+
+
 }
index 2fecf89..b1757a7 100644 (file)
@@ -71,4 +71,4 @@ class enrol_guest_enrol_form extends moodleform {
 
         return $errors;
     }
-}
\ No newline at end of file
+}
index ec95e75..52b5142 100644 (file)
@@ -277,10 +277,19 @@ echo html_writer::table($table);
 // access security is in each plugin
 $candidates = array();
 foreach (enrol_get_plugins(true) as $name=>$plugin) {
-    if (!$link = $plugin->get_newinstance_link($course->id)) {
-        continue;
+    if ($plugin->use_standard_editing_ui()) {
+        if ($plugin->can_add_instance($course->id)) {
+            // Standard add/edit UI.
+            $params = array('type' => $name, 'courseid' => $course->id);
+            $url = new moodle_url('/enrol/editinstance.php', $params);
+            $link = $url->out(false);
+            $candidates[$link] = get_string('pluginname', 'enrol_'.$name);
+        }
+    } else if ($url = $plugin->get_newinstance_link($course->id)) {
+        // Old custom UI.
+        $link = $url->out(false);
+        $candidates[$link] = get_string('pluginname', 'enrol_'.$name);
     }
-    $candidates[$link->out(false)] = get_string('pluginname', 'enrol_'.$name);
 }
 
 if ($candidates) {
diff --git a/enrol/manual/edit.php b/enrol/manual/edit.php
deleted file mode 100644 (file)
index 27e1de7..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Adds new instance of enrol_manual to specified course
- * or edits current instance.
- *
- * @package    enrol_manual
- * @copyright  2010 Petr Skoda  {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-require('../../config.php');
-require_once('edit_form.php');
-
-$courseid = required_param('courseid', PARAM_INT);
-
-$course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
-$context = context_course::instance($course->id, MUST_EXIST);
-
-require_login($course);
-require_capability('enrol/manual:config', $context);
-
-$PAGE->set_url('/enrol/manual/edit.php', array('courseid'=>$course->id));
-$PAGE->set_pagelayout('admin');
-
-$return = new moodle_url('/enrol/instances.php', array('id'=>$course->id));
-if (!enrol_is_enabled('manual')) {
-    redirect($return);
-}
-
-$plugin = enrol_get_plugin('manual');
-
-if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol'=>'manual'), 'id ASC')) {
-    $instance = array_shift($instances);
-    if ($instances) {
-        // Oh - we allow only one instance per course!!
-        foreach ($instances as $del) {
-            $plugin->delete_instance($del);
-        }
-    }
-    // Merge these two settings to one value for the single selection element.
-    if ($instance->notifyall and $instance->expirynotify) {
-        $instance->expirynotify = 2;
-    }
-    unset($instance->notifyall);
-
-} else {
-    require_capability('moodle/course:enrolconfig', $context);
-    // No instance yet, we have to add new instance.
-    navigation_node::override_active_url(new moodle_url('/enrol/instances.php', array('id'=>$course->id)));
-    $instance = new stdClass();
-    $instance->id              = null;
-    $instance->courseid        = $course->id;
-    $instance->expirynotify    = $plugin->get_config('expirynotify');
-    $instance->expirythreshold = $plugin->get_config('expirythreshold');
-}
-
-$mform = new enrol_manual_edit_form(null, array($instance, $plugin, $context));
-
-if ($mform->is_cancelled()) {
-    redirect($return);
-
-} else if ($data = $mform->get_data()) {
-    if ($data->expirynotify == 2) {
-        $data->expirynotify = 1;
-        $data->notifyall = 1;
-    } else {
-        $data->notifyall = 0;
-    }
-    if (!$data->expirynotify) {
-        // Keep previous/default value of disabled expirythreshold option.
-        $data->expirythreshold = $instance->expirythreshold;
-    }
-    if ($instance->id) {
-        $instance->roleid          = $data->roleid;
-        $instance->enrolperiod     = $data->enrolperiod;
-        $instance->expirynotify    = $data->expirynotify;
-        $instance->notifyall       = $data->notifyall;
-        $instance->expirythreshold = $data->expirythreshold;
-        $instance->timemodified    = time();
-        $markdirty = ($instance->status != $data->status);
-        $instance->status = $data->status;
-
-        $DB->update_record('enrol', $instance);
-        \core\event\enrol_instance_updated::create_from_record($instance)->trigger();
-
-        if ($markdirty) {
-            $context->mark_dirty();
-        }
-
-    } else {
-        $fields = array(
-            'status'          => $data->status,
-            'roleid'          => $data->roleid,
-            'enrolperiod'     => $data->enrolperiod,
-            'expirynotify'    => $data->expirynotify,
-            'notifyall'       => $data->notifyall,
-            'expirythreshold' => $data->expirythreshold);
-        $plugin->add_instance($course, $fields);
-    }
-
-    redirect($return);
-}
-
-$PAGE->set_title(get_string('pluginname', 'enrol_manual'));
-$PAGE->set_heading($course->fullname);
-
-echo $OUTPUT->header();
-echo $OUTPUT->heading(get_string('pluginname', 'enrol_manual'));
-$mform->display();
-echo $OUTPUT->footer();
diff --git a/enrol/manual/edit_form.php b/enrol/manual/edit_form.php
deleted file mode 100644 (file)
index bfc09a7..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Adds new instance of enrol_manual to specified course
- * or edits current instance.
- *
- * @package    enrol_manual
- * @copyright  2010 Petr Skoda  {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-require_once($CFG->libdir.'/formslib.php');
-
-class enrol_manual_edit_form extends moodleform {
-
-    function definition() {
-        $mform = $this->_form;
-
-        list($instance, $plugin, $context) = $this->_customdata;
-
-        $mform->addElement('header', 'header', get_string('pluginname', 'enrol_manual'));
-
-        $options = array(ENROL_INSTANCE_ENABLED  => get_string('yes'),
-                         ENROL_INSTANCE_DISABLED => get_string('no'));
-        $mform->addElement('select', 'status', get_string('status', 'enrol_manual'), $options);
-        $mform->addHelpButton('status', 'status', 'enrol_manual');
-        $mform->setDefault('status', $plugin->get_config('status'));
-
-        if ($instance->id) {
-            $roles = get_default_enrol_roles($context, $instance->roleid);
-        } else {
-            $roles = get_default_enrol_roles($context, $plugin->get_config('roleid'));
-        }
-        $mform->addElement('select', 'roleid', get_string('defaultrole', 'role'), $roles);
-        $mform->setDefault('roleid', $plugin->get_config('roleid'));
-
-        $mform->addElement('duration', 'enrolperiod', get_string('defaultperiod', 'enrol_manual'), array('optional' => true, 'defaultunit' => 86400));
-        $mform->setDefault('enrolperiod', $plugin->get_config('enrolperiod'));
-        $mform->addHelpButton('enrolperiod', 'defaultperiod', 'enrol_manual');
-
-        $options = array(0 => get_string('no'), 1 => get_string('expirynotifyenroller', 'core_enrol'), 2 => get_string('expirynotifyall', 'core_enrol'));
-        $mform->addElement('select', 'expirynotify', get_string('expirynotify', 'core_enrol'), $options);
-        $mform->addHelpButton('expirynotify', 'expirynotify', 'core_enrol');
-
-        $mform->addElement('duration', 'expirythreshold', get_string('expirythreshold', 'core_enrol'), array('optional' => false, 'defaultunit' => 86400));
-        $mform->addHelpButton('expirythreshold', 'expirythreshold', 'core_enrol');
-        $mform->disabledIf('expirythreshold', 'expirynotify', 'eq', 0);
-
-        $mform->addElement('hidden', 'courseid');
-        $mform->setType('courseid', PARAM_INT);
-
-        if (enrol_accessing_via_instance($instance)) {
-            $mform->addElement('static', 'selfwarn', get_string('instanceeditselfwarning', 'core_enrol'), get_string('instanceeditselfwarningtext', 'core_enrol'));
-        }
-
-        $this->add_action_buttons(true, ($instance->id ? null : get_string('addinstance', 'enrol')));
-
-        $this->set_data($instance);
-    }
-
-    function validation($data, $files) {
-        global $DB;
-
-        $errors = parent::validation($data, $files);
-
-        if ($data['expirynotify'] > 0 and $data['expirythreshold'] < 86400) {
-            $errors['expirythreshold'] = get_string('errorthresholdlow', 'core_enrol');
-        }
-
-        return $errors;
-    }
-}
index b50e3cc..445b769 100644 (file)
@@ -78,24 +78,18 @@ class enrol_manual_plugin extends enrol_plugin {
     }
 
     /**
-     * Returns enrolment instance manage link.
+     * Return true if we can add a new instance to this course.
      *
-     * By defaults looks for manage.php file and tests for manage capability.
-     *
-     * @param navigation_node $instancesnode
-     * @param stdClass $instance
-     * @return moodle_url;
+     * @param int $courseid
+     * @return boolean
      */
-    public function add_course_navigation($instancesnode, stdClass $instance) {
-        if ($instance->enrol !== 'manual') {
-             throw new coding_exception('Invalid enrol instance type!');
-        }
-
-        $context = context_course::instance($instance->courseid);
-        if (has_capability('enrol/manual:config', $context)) {
-            $managelink = new moodle_url('/enrol/manual/edit.php', array('courseid'=>$instance->courseid));
-            $instancesnode->add($this->get_instance_name($instance), $managelink, navigation_node::TYPE_SETTING);
+    public function can_add_instance($courseid) {
+        $context = context_course::instance($courseid, MUST_EXIST);
+        if (!has_capability('moodle/course:enrolconfig', $context) or !has_capability('enrol/manual:config', $context)) {
+            return false;
         }
+        // Multiple instances supported - multiple parent courses linked.
+        return true;
     }
 
     /**
@@ -106,47 +100,18 @@ class enrol_manual_plugin extends enrol_plugin {
     public function get_action_icons(stdClass $instance) {
         global $OUTPUT;
 
-        if ($instance->enrol !== 'manual') {
-            throw new coding_exception('invalid enrol instance!');
-        }
         $context = context_course::instance($instance->courseid);
 
         $icons = array();
-
         if (has_capability('enrol/manual:enrol', $context) or has_capability('enrol/manual:unenrol', $context)) {
             $managelink = new moodle_url("/enrol/manual/manage.php", array('enrolid'=>$instance->id));
             $icons[] = $OUTPUT->action_icon($managelink, new pix_icon('t/enrolusers', get_string('enrolusers', 'enrol_manual'), 'core', array('class'=>'iconsmall')));
         }
-        if (has_capability('enrol/manual:config', $context)) {
-            $editlink = new moodle_url("/enrol/manual/edit.php", array('courseid'=>$instance->courseid));
-            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
-                    array('class' => 'iconsmall')));
-        }
+        $icons = $icons + parent::get_action_icons($instance);
 
         return $icons;
     }
 
-    /**
-     * Returns link to page which may be used to add new instance of enrolment plugin in course.
-     * @param int $courseid
-     * @return moodle_url page url
-     */
-    public function get_newinstance_link($courseid) {
-        global $DB;
-
-        $context = context_course::instance($courseid, MUST_EXIST);
-
-        if (!has_capability('moodle/course:enrolconfig', $context) or !has_capability('enrol/manual:config', $context)) {
-            return NULL;
-        }
-
-        if ($DB->record_exists('enrol', array('courseid'=>$courseid, 'enrol'=>'manual'))) {
-            return NULL;
-        }
-
-        return new moodle_url('/enrol/manual/edit.php', array('courseid'=>$courseid));
-    }
-
     /**
      * Add new instance of enrol plugin with default settings.
      * @param stdClass $course
@@ -188,6 +153,26 @@ class enrol_manual_plugin extends enrol_plugin {
         return parent::add_instance($course, $fields);
     }
 
+    /**
+     * Update instance of enrol plugin.
+     * @param stdClass $instance
+     * @param stdClass $data modified instance fields
+     * @return boolean
+     */
+    public function update_instance($instance, $data) {
+        global $DB;
+
+        // Delete all other instances, leaving only one.
+        if ($instances = $DB->get_records('enrol', array('courseid' => $instance->courseid, 'enrol' => 'manual'), 'id ASC')) {
+            foreach ($instances as $anotherinstance) {
+                if ($anotherinstance->id != $instance->id) {
+                    $this->delete_instance($anotherinstance);
+                }
+            }
+        }
+        return parent::update_instance($instance, $data);
+    }
+
     /**
      * Returns a button to manually enrol users through the manual enrolment plugin.
      *
@@ -610,4 +595,130 @@ class enrol_manual_plugin extends enrol_plugin {
             $this->enrol_user($instance, $userid, $roleid, $timestart, $timeend, $status, $recovergrades);
         }
     }
+
+    /**
+     * We are a good plugin and don't invent our own UI/validation code path.
+     *
+     * @return boolean
+     */
+    public function use_standard_editing_ui() {
+        return true;
+    }
+
+    /**
+     * Return an array of valid options for the status.
+     *
+     * @return array
+     */
+    protected function get_status_options() {
+        $options = array(ENROL_INSTANCE_ENABLED  => get_string('yes'),
+                         ENROL_INSTANCE_DISABLED => get_string('no'));
+        return $options;
+    }
+
+    /**
+     * Return an array of valid options for the roleid.
+     *
+     * @param stdClass $instance
+     * @param context $context
+     * @return array
+     */
+    protected function get_roleid_options($instance, $context) {
+        if ($instance->id) {
+            $roles = get_default_enrol_roles($context, $instance->roleid);
+        } else {
+            $roles = get_default_enrol_roles($context, $this->get_config('roleid'));
+        }
+        return $roles;
+    }
+
+    /**
+     * Return an array of valid options for the expirynotify.
+     *
+     * @return array
+     */
+    protected function get_expirynotify_options() {
+        $options = array(
+            0 => get_string('no'),
+            1 => get_string('expirynotifyenroller', 'core_enrol'),
+            2 => get_string('expirynotifyall', 'core_enrol')
+        );
+        return $options;
+    }
+
+    /**
+     * Add elements to the edit instance form.
+     *
+     * @param stdClass $instance
+     * @param MoodleQuickForm $mform
+     * @param context $context
+     * @return bool
+     */
+    public function edit_instance_form($instance, MoodleQuickForm $mform, $context) {
+
+        $options = $this->get_status_options();
+        $mform->addElement('select', 'status', get_string('status', 'enrol_manual'), $options);
+        $mform->addHelpButton('status', 'status', 'enrol_manual');
+        $mform->setDefault('status', $this->get_config('status'));
+
+        $roles = $this->get_roleid_options($instance, $context);
+        $mform->addElement('select', 'roleid', get_string('defaultrole', 'role'), $roles);
+        $mform->setDefault('roleid', $this->get_config('roleid'));
+
+        $options = array('optional' => true, 'defaultunit' => 86400);
+        $mform->addElement('duration', 'enrolperiod', get_string('defaultperiod', 'enrol_manual'), $options);
+        $mform->setDefault('enrolperiod', $this->get_config('enrolperiod'));
+        $mform->addHelpButton('enrolperiod', 'defaultperiod', 'enrol_manual');
+
+        $options = $this->get_expirynotify_options();
+        $mform->addElement('select', 'expirynotify', get_string('expirynotify', 'core_enrol'), $options);
+        $mform->addHelpButton('expirynotify', 'expirynotify', 'core_enrol');
+
+        $options = array('optional' => false, 'defaultunit' => 86400);
+        $mform->addElement('duration', 'expirythreshold', get_string('expirythreshold', 'core_enrol'), $options);
+        $mform->addHelpButton('expirythreshold', 'expirythreshold', 'core_enrol');
+        $mform->disabledIf('expirythreshold', 'expirynotify', 'eq', 0);
+
+        if (enrol_accessing_via_instance($instance)) {
+            $warntext = get_string('instanceeditselfwarningtext', 'core_enrol');
+            $mform->addElement('static', 'selfwarn', get_string('instanceeditselfwarning', 'core_enrol'), $warntext);
+        }
+    }
+
+    /**
+     * Perform custom validation of the data used to edit the instance.
+     *
+     * @param array $data array of ("fieldname"=>value) of submitted data
+     * @param array $files array of uploaded files "element_name"=>tmp_file_path
+     * @param object $instance The instance loaded from the DB
+     * @param context $context The context of the instance we are editing
+     * @return array of "element_name"=>"error_description" if there are errors,
+     *         or an empty array if everything is OK.
+     * @return void
+     */
+    public function edit_instance_validation($data, $files, $instance, $context) {
+        $errors = array();
+
+        if ($data['expirynotify'] > 0 and $data['expirythreshold'] < 86400) {
+            $errors['expirythreshold'] = get_string('errorthresholdlow', 'core_enrol');
+        }
+
+        $validstatus = array_keys($this->get_status_options());
+        $validroles = array_keys($this->get_roleid_options($instance, $context));
+        $validexpirynotify = array_keys($this->get_expirynotify_options());
+
+        $tovalidate = array(
+            'status' => $validstatus,
+            'roleid' => $validroles,
+            'enrolperiod' => PARAM_INT,
+            'expirynotify' => $validexpirynotify,
+            'expirythreshold' => PARAM_INT
+        );
+
+        $typeerrors = $this->validate_param_types($data, $tovalidate);
+        $errors = array_merge($errors, $typeerrors);
+
+        return $errors;
+    }
+
 }
diff --git a/enrol/meta/addinstance.php b/enrol/meta/addinstance.php
deleted file mode 100644 (file)
index aea6adb..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Adds new instance of enrol_meta to specified course.
- *
- * @package    enrol_meta
- * @copyright  2010 Petr Skoda {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-require('../../config.php');
-require_once("$CFG->dirroot/enrol/meta/addinstance_form.php");
-require_once("$CFG->dirroot/enrol/meta/locallib.php");
-
-$id = required_param('id', PARAM_INT); // course id
-$message = optional_param('message', null, PARAM_TEXT);
-$instanceid = optional_param('enrolid', 0, PARAM_INT);
-
-$course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
-$context = context_course::instance($course->id, MUST_EXIST);
-
-$PAGE->set_url('/enrol/meta/addinstance.php', array('id'=>$course->id));
-$PAGE->set_pagelayout('admin');
-
-navigation_node::override_active_url(new moodle_url('/enrol/instances.php', array('id'=>$course->id)));
-
-require_login($course);
-require_capability('moodle/course:enrolconfig', $context);
-
-$enrol = enrol_get_plugin('meta');
-if ($instanceid) {
-    require_capability('enrol/meta:config', $context);
-    $instance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'meta',
-        'id' => $instanceid), '*', MUST_EXIST);
-
-} else {
-    if (!$enrol->get_newinstance_link($course->id)) {
-        redirect(new moodle_url('/enrol/instances.php', array('id' => $course->id)));
-    }
-    $instance = null;
-}
-
-$mform = new enrol_meta_addinstance_form(null, array('course' => $course, 'instance' => $instance));
-
-if ($mform->is_cancelled()) {
-    redirect(new moodle_url('/enrol/instances.php', array('id'=>$course->id)));
-
-} else if ($data = $mform->get_data()) {
-    if (!empty($data->customint2) && $data->customint2 == ENROL_META_CREATE_GROUP) {
-        $data->customint2 = enrol_meta_create_new_group($course->id, $data->link);
-    }
-    if ($instance) {
-        if ($data->customint2 != $instance->customint2) {
-            $DB->update_record('enrol', array('id' => $instance->id, 'customint2' => $data->customint2));
-            enrol_meta_sync($course->id);
-        }
-    } else {
-        $eid = $enrol->add_instance($course, array('customint1' => $data->link,
-                                               'customint2' => $data->customint2));
-        enrol_meta_sync($course->id);
-        if (!empty($data->submitbuttonnext)) {
-            redirect(new moodle_url('/enrol/meta/addinstance.php',
-                    array('id' => $course->id, 'message' => 'added')));
-        }
-    }
-    redirect(new moodle_url('/enrol/instances.php', array('id' => $course->id)));
-}
-
-$PAGE->set_heading($course->fullname);
-$PAGE->set_title(get_string('pluginname', 'enrol_meta'));
-
-echo $OUTPUT->header();
-
-if ($message === 'added') {
-    echo $OUTPUT->notification(get_string('instanceadded', 'enrol'), 'notifysuccess');
-}
-
-$mform->display();
-
-echo $OUTPUT->footer();
diff --git a/enrol/meta/addinstance_form.php b/enrol/meta/addinstance_form.php
deleted file mode 100644 (file)
index c28a762..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Adds instance form
- *
- * @package    enrol_meta
- * @copyright  2010 Petr Skoda {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-require_once("$CFG->libdir/formslib.php");
-
-class enrol_meta_addinstance_form extends moodleform {
-    protected $course;
-
-    function definition() {
-        global $CFG, $DB;
-
-        $mform  = $this->_form;
-        $course = $this->_customdata['course'];
-        $instance = $this->_customdata['instance'];
-        $this->course = $course;
-
-        if ($instance) {
-            $where = 'WHERE c.id = :courseid';
-            $params = array('courseid' => $instance->customint1);
-            $existing = array();
-        } else {
-            $where = '';
-            $params = array();
-            $existing = $DB->get_records('enrol', array('enrol' => 'meta', 'courseid' => $course->id), '', 'customint1, id');
-        }
-
-        // TODO: this has to be done via ajax or else it will fail very badly on large sites!
-        $courses = array('' => get_string('choosedots'));
-        $select = ', ' . context_helper::get_preload_record_columns_sql('ctx');
-        $join = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
-
-        $plugin = enrol_get_plugin('meta');
-        $sortorder = 'c.' . $plugin->get_config('coursesort', 'sortorder') . ' ASC';
-
-        $sql = "SELECT c.id, c.fullname, c.shortname, c.visible $select FROM {course} c $join $where ORDER BY $sortorder";
-        $rs = $DB->get_recordset_sql($sql, array('contextlevel' => CONTEXT_COURSE) + $params);
-        foreach ($rs as $c) {
-            if ($c->id == SITEID or $c->id == $course->id or isset($existing[$c->id])) {
-                continue;
-            }
-            context_helper::preload_from_record($c);
-            $coursecontext = context_course::instance($c->id);
-            if (!$c->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
-                continue;
-            }
-            if (!has_capability('enrol/meta:selectaslinked', $coursecontext)) {
-                continue;
-            }
-            $courses[$c->id] = $coursecontext->get_context_name(false);
-        }
-        $rs->close();
-
-        $groups = array(0 => get_string('none'));
-        if (has_capability('moodle/course:managegroups', context_course::instance($course->id))) {
-            $groups[ENROL_META_CREATE_GROUP] = get_string('creategroup', 'enrol_meta');
-        }
-        foreach (groups_get_all_groups($course->id) as $group) {
-            $groups[$group->id] = format_string($group->name, true, array('context' => context_course::instance($course->id)));
-        }
-
-        $mform->addElement('header','general', get_string('pluginname', 'enrol_meta'));
-
-        $mform->addElement('select', 'link', get_string('linkedcourse', 'enrol_meta'), $courses);
-        $mform->addRule('link', get_string('required'), 'required', null, 'client');
-
-        $mform->addElement('select', 'customint2', get_string('addgroup', 'enrol_meta'), $groups);
-
-        $mform->addElement('hidden', 'id', null);
-        $mform->setType('id', PARAM_INT);
-
-        $mform->addElement('hidden', 'enrolid');
-        $mform->setType('enrolid', PARAM_INT);
-
-        $data = array('id' => $course->id);
-        if ($instance) {
-            $data['link'] = $instance->customint1;
-            $data['enrolid'] = $instance->id;
-            $data['customint2'] = $instance->customint2;
-            $mform->freeze('link');
-            $this->add_action_buttons();
-        } else {
-            $this->add_add_buttons();
-        }
-        $this->set_data($data);
-    }
-
-    /**
-     * Adds buttons on create new method form
-     */
-    protected function add_add_buttons() {
-        $mform = $this->_form;
-        $buttonarray = array();
-        $buttonarray[0] = $mform->createElement('submit', 'submitbutton', get_string('addinstance', 'enrol'));
-        $buttonarray[1] = $mform->createElement('submit', 'submitbuttonnext', get_string('addinstanceanother', 'enrol'));
-        $buttonarray[2] = $mform->createElement('cancel');
-        $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
-        $mform->closeHeaderBefore('buttonar');
-    }
-
-    function validation($data, $files) {
-        global $DB, $CFG;
-
-        $errors = parent::validation($data, $files);
-
-        if ($this->_customdata['instance']) {
-            // Nothing to validate in case of editing.
-            return $errors;
-        }
-
-        // TODO: this is duplicated here because it may be necessary once we implement ajax course selection element
-
-        if (!$c = $DB->get_record('course', array('id'=>$data['link']))) {
-            $errors['link'] = get_string('required');
-        } else {
-            $coursecontext = context_course::instance($c->id);
-            $existing = $DB->get_records('enrol', array('enrol'=>'meta', 'courseid'=>$this->course->id), '', 'customint1, id');
-            if (!$c->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
-                $errors['link'] = get_string('error');
-            } else if (!has_capability('enrol/meta:selectaslinked', $coursecontext)) {
-                $errors['link'] = get_string('error');
-            } else if ($c->id == SITEID or $c->id == $this->course->id or isset($existing[$c->id])) {
-                $errors['link'] = get_string('error');
-            }
-        }
-
-        return $errors;
-    }
-}
-
index 538fd1f..5659d9f 100644 (file)
@@ -64,17 +64,18 @@ class enrol_meta_plugin extends enrol_plugin {
     }
 
     /**
-     * Returns link to page which may be used to add new instance of enrolment plugin in course.
+     * Returns true if we can add a new instance to this course.
+     *
      * @param int $courseid
-     * @return moodle_url page url
+     * @return boolean
      */
-    public function get_newinstance_link($courseid) {
+    public function can_add_instance($courseid) {
         $context = context_course::instance($courseid, MUST_EXIST);
         if (!has_capability('moodle/course:enrolconfig', $context) or !has_capability('enrol/meta:config', $context)) {
-            return NULL;
+            return false;
         }
-        // multiple instances supported - multiple parent courses linked
-        return new moodle_url('/enrol/meta/addinstance.php', array('id'=>$courseid));
+        // Multiple instances supported - multiple parent courses linked.
+        return true;
     }
 
     /**
@@ -127,6 +128,56 @@ class enrol_meta_plugin extends enrol_plugin {
         // We should probably add some sync button to the course enrol methods overview page.
     }
 
+    /**
+     * Add new instance of enrol plugin.
+     * @param object $course
+     * @param array $fields instance fields
+     * @return int id of new instance, null if can not be created
+     */
+    public function add_instance($course, array $fields = null) {
+        global $CFG;
+
+        require_once("$CFG->dirroot/enrol/meta/locallib.php");
+
+        if (!empty($fields['customint2']) && $fields['customint2'] == ENROL_META_CREATE_GROUP) {
+            $context = context_course::instance($course->id);
+            require_capability('moodle/course:managegroups', $context);
+            $groupid = enrol_meta_create_new_group($course->id, $fields['customint1']);
+            $fields['customint2'] = $groupid;
+        }
+
+        $result = parent::add_instance($course, $fields);
+
+        enrol_meta_sync($course->id);
+
+        return $result;
+    }
+
+    /**
+     * Update instance of enrol plugin.
+     * @param stdClass $instance
+     * @param stdClass $data modified instance fields
+     * @return boolean
+     */
+    public function update_instance($instance, $data) {
+        global $CFG;
+
+        require_once("$CFG->dirroot/enrol/meta/locallib.php");
+
+        if (!empty($data->customint2) && $data->customint2 == ENROL_META_CREATE_GROUP) {
+            $context = context_course::instance($instance->courseid);
+            require_capability('moodle/course:managegroups', $context);
+            $groupid = enrol_meta_create_new_group($instance->courseid, $data->customint1);
+            $data->customint2 = $groupid;
+        }
+
+        $result = parent::update_instance($instance, $data);
+
+        enrol_meta_sync($instance->courseid);
+
+        return $result;
+    }
+
     /**
      * Update instance status
      *
@@ -176,6 +227,153 @@ class enrol_meta_plugin extends enrol_plugin {
         return has_capability('enrol/meta:config', $context);
     }
 
+    /**
+     * We are a good plugin and don't invent our own UI/validation code path.
+     *
+     * @return boolean
+     */
+    public function use_standard_editing_ui() {
+        return true;
+    }
+
+    /**
+     * Return an array of valid options for the courses.
+     *
+     * @param stdClass $instance
+     * @param context $coursecontext
+     * @return array
+     */
+    protected function get_course_options($instance, $coursecontext) {
+        global $DB;
+
+        if ($instance->id) {
+            $where = 'WHERE c.id = :courseid';
+            $params = array('courseid' => $instance->customint1);
+            $existing = array();
+        } else {
+            $where = '';
+            $params = array();
+            $instanceparams = array('enrol' => 'meta', 'courseid' => $instance->courseid);
+            $existing = $DB->get_records('enrol', $instanceparams, '', 'customint1, id');
+        }
+
+        // TODO: this has to be done via ajax or else it will fail very badly on large sites!
+        $courses = array('' => get_string('choosedots'));
+        $select = ', ' . context_helper::get_preload_record_columns_sql('ctx');
+        $join = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
+
+        $sortorder = 'c.' . $this->get_config('coursesort', 'sortorder') . ' ASC';
+
+        $sql = "SELECT c.id, c.fullname, c.shortname, c.visible $select FROM {course} c $join $where ORDER BY $sortorder";
+        $rs = $DB->get_recordset_sql($sql, array('contextlevel' => CONTEXT_COURSE) + $params);
+        foreach ($rs as $c) {
+            if ($c->id == SITEID or $c->id == $instance->courseid or isset($existing[$c->id])) {
+                continue;
+            }
+            context_helper::preload_from_record($c);
+            $coursecontext = context_course::instance($c->id);
+            if (!$c->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
+                continue;
+            }
+            if (!has_capability('enrol/meta:selectaslinked', $coursecontext)) {
+                continue;
+            }
+            $courses[$c->id] = $coursecontext->get_context_name(false);
+        }
+        $rs->close();
+        return $courses;
+    }
+
+    /**
+     * Return an array of valid options for the groups.
+     *
+     * @param context $coursecontext
+     * @return array
+     */
+    protected function get_group_options($coursecontext) {
+        $groups = array(0 => get_string('none'));
+        $courseid = $coursecontext->instanceid;
+        if (has_capability('moodle/course:managegroups', $coursecontext)) {
+            $groups[ENROL_META_CREATE_GROUP] = get_string('creategroup', 'enrol_meta');
+        }
+        foreach (groups_get_all_groups($courseid) as $group) {
+            $groups[$group->id] = format_string($group->name, true, array('context' => $coursecontext));
+        }
+        return $groups;
+    }
+
+    /**
+     * Add elements to the edit instance form.
+     *
+     * @param stdClass $instance
+     * @param MoodleQuickForm $mform
+     * @param context $coursecontext
+     * @return bool
+     */
+    public function edit_instance_form($instance, MoodleQuickForm $mform, $coursecontext) {
+        global $DB;
+
+        $courses = $this->get_course_options($instance, $coursecontext);
+        $groups = $this->get_group_options($coursecontext);
+
+        $mform->addElement('select', 'customint1', get_string('linkedcourse', 'enrol_meta'), $courses);
+        $mform->addRule('customint1', get_string('required'), 'required', null, 'client');
+        if (!empty($instance->id)) {
+            $mform->freeze('customint1');
+        }
+
+        $mform->addElement('select', 'customint2', get_string('addgroup', 'enrol_meta'), $groups);
+    }
+
+    /**
+     * Perform custom validation of the data used to edit the instance.
+     *
+     * @param array $data array of ("fieldname"=>value) of submitted data
+     * @param array $files array of uploaded files "element_name"=>tmp_file_path
+     * @param object $instance The instance loaded from the DB
+     * @param context $context The context of the instance we are editing
+     * @return array of "element_name"=>"error_description" if there are errors,
+     *         or an empty array if everything is OK.
+     * @return void
+     */
+    public function edit_instance_validation($data, $files, $instance, $context) {
+        global $DB;
+        $errors = array();
+        $thiscourseid = $context->instanceid;
+        $c = false;
+
+        if (!empty($data['customint1'])) {
+            $c = $DB->get_record('course', array('id' => $data['customint1']));
+        }
+
+        if (!$c) {
+            $errors['customint1'] = get_string('required');
+        } else {
+            $coursecontext = context_course::instance($c->id);
+            $existing = $DB->get_records('enrol', array('enrol' => 'meta', 'courseid' => $thiscourseid), '', 'customint1, id');
+            if (!$c->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
+                $errors['customint1'] = get_string('error');
+            } else if (!has_capability('enrol/meta:selectaslinked', $coursecontext)) {
+                $errors['customint1'] = get_string('error');
+            } else if ($c->id == SITEID or $c->id == $thiscourseid or isset($existing[$c->id])) {
+                $errors['customint1'] = get_string('error');
+            }
+        }
+
+        $validcourses = array_keys($this->get_course_options($instance, $context));
+        $validgroups = array_keys($this->get_group_options($context));
+
+        $tovalidate = array(
+            'customint1' => $validcourses,
+            'customint2' => $validgroups
+        );
+        $typeerrors = $this->validate_param_types($data, $tovalidate);
+        $errors = array_merge($errors, $typeerrors);
+
+        return $errors;
+    }
+
+
     /**
      * Restore instance and map settings.
      *
@@ -254,28 +452,4 @@ class enrol_meta_plugin extends enrol_plugin {
         return;
     }
 
-    /**
-     * Returns edit icons for the page with list of instances.
-     * @param stdClass $instance
-     * @return array
-     */
-    public function get_action_icons(stdClass $instance) {
-        global $OUTPUT;
-
-        if ($instance->enrol !== 'meta') {
-            throw new coding_exception('invalid enrol instance!');
-        }
-        $context = context_course::instance($instance->courseid);
-
-        $icons = array();
-
-        if (has_capability('enrol/meta:config', $context)) {
-            $editlink = new moodle_url("/enrol/meta/addinstance.php",
-                array('id' => $instance->courseid, 'enrolid' => $instance->id));
-            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
-                array('class' => 'iconsmall')));
-        }
-
-        return $icons;
-    }
 }
index 6566759..9a07371 100644 (file)
@@ -46,12 +46,13 @@ Feature: Enrolments are synchronised with meta courses
   Scenario: Add meta enrolment instance with groups
     When I follow "Course 3"
     And I navigate to "Enrolment methods" node in "Course administration > Users"
-    And I set the field "Add method" to "Course meta link"
-    And I press "Go"
+    And I select "Course meta link" from the "Add method" singleselect
     And I set the following fields to these values:
       | Link course  | Course 1      |
       | Add to group | Groupcourse 1 |
-    And I press "Add method and create another"
+    And I press "Add method"
+    And I set the field "Add method" to "Course meta link"
+    And I press "Go"
     And I set the following fields to these values:
       | Link course  | Course 2      |
       | Add to group | Groupcourse 2 |
@@ -91,7 +92,8 @@ Feature: Enrolments are synchronised with meta courses
     And I set the following fields to these values:
       | Link course  | Course 1      |
       | Add to group | Groupcourse 1 |
-    And I press "Add method and create another"
+    And I press "Add method"
+    And I select "Course meta link" from the "Add method" singleselect
     And I set the following fields to these values:
       | Link course  | Course 2      |
     And I press "Add method"
index 9f0fb8a..5f292e5 100644 (file)
@@ -128,12 +128,15 @@ class enrol_meta_plugin_testcase extends advanced_testcase {
         $this->assertEquals(7, $DB->count_records('user_enrolments'));
         $this->assertEquals(6, $DB->count_records('role_assignments'));
 
+        // Disable the plugin to prevent add_instance from calling enrol_meta_sync.
+        $this->disable_plugin();
         $e1 = $metalplugin->add_instance($course3, array('customint1'=>$course1->id));
         $e2 = $metalplugin->add_instance($course3, array('customint1'=>$course2->id));
         $e3 = $metalplugin->add_instance($course4, array('customint1'=>$course2->id));
         $enrol1 = $DB->get_record('enrol', array('id'=>$e1));
         $enrol2 = $DB->get_record('enrol', array('id'=>$e2));
         $enrol3 = $DB->get_record('enrol', array('id'=>$e3));
+        $this->enable_plugin();
 
         enrol_meta_sync($course4->id, false);
         $this->assertEquals(9, $DB->count_records('user_enrolments'));
diff --git a/enrol/mnet/addinstance.php b/enrol/mnet/addinstance.php
deleted file mode 100644 (file)
index 9678b40..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Adds new instance of enrol_mnet into the specified course
- *
- * @package    enrol_mnet
- * @copyright  2010 David Mudrak <david@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-require(dirname(dirname(dirname(__FILE__))).'/config.php');
-require_once($CFG->dirroot.'/enrol/mnet/addinstance_form.php');
-require_once($CFG->dirroot.'/mnet/service/enrol/locallib.php');
-
-$id = required_param('id', PARAM_INT); // course id
-
-$course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
-$context = context_course::instance($course->id, MUST_EXIST);
-
-require_login($course);
-require_capability('moodle/course:enrolconfig', $context);
-
-$PAGE->set_url('/enrol/mnet/addinstance.php', array('id'=>$course->id));
-$PAGE->set_pagelayout('standard');
-
-// Try and make the manage instances node on the navigation active
-$courseadmin = $PAGE->settingsnav->get('courseadmin');
-if ($courseadmin && $courseadmin->get('users') && $courseadmin->get('users')->get('manageinstances')) {
-    $courseadmin->get('users')->get('manageinstances')->make_active();
-}
-
-$enrol = enrol_get_plugin('mnet');
-// make sure we were allowed to get here form the Enrolment methods page
-if (!$enrol->get_newinstance_link($course->id)) {
-    redirect(new moodle_url('/enrol/instances.php', array('id'=>$course->id)));
-}
-$service = mnetservice_enrol::get_instance();
-$mform = new enrol_mnet_addinstance_form(null, array('course'=>$course, 'enrol'=>$enrol, 'service'=>$service));
-
-if ($mform->is_cancelled()) {
-    redirect(new moodle_url('/enrol/instances.php', array('id'=>$course->id)));
-
-} else if ($data = $mform->get_data()) {
-    $enrol->add_instance($course, array('customint1'=>$data->hostid, 'roleid'=>$data->roleid, 'name'=>$data->name));
-    redirect(new moodle_url('/enrol/instances.php', array('id'=>$course->id)));
-}
-
-$PAGE->set_heading($course->fullname);
-$PAGE->set_title(get_string('pluginname', 'enrol_mnet'));
-
-echo $OUTPUT->header();
-$mform->display();
-echo $OUTPUT->footer();
diff --git a/enrol/mnet/addinstance_form.php b/enrol/mnet/addinstance_form.php
deleted file mode 100644 (file)
index 436e92b..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Form to add an instance of enrol_mnet plugin
- *
- * @package    enrol_mnet
- * @copyright  2010 David Mudrak <david@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-require_once("$CFG->libdir/formslib.php");
-
-class enrol_mnet_addinstance_form extends moodleform {
-    function definition() {
-        global $CFG, $DB;
-
-        $mform   = $this->_form;
-        $course  = $this->_customdata['course'];
-        $enrol   = $this->_customdata['enrol'];
-        $service = $this->_customdata['service'];
-        $coursecontext = context_course::instance($course->id);
-
-        $subscribers = $service->get_remote_subscribers();
-        $hosts = array(0 => get_string('remotesubscribersall', 'enrol_mnet'));
-        foreach ($subscribers as $hostid => $subscriber) {
-            $hosts[$hostid] = $subscriber->appname.': '.$subscriber->hostname.' ('.$subscriber->hosturl.')';
-        }
-        $roles = get_assignable_roles($coursecontext);
-
-        $mform->addElement('header','general', get_string('pluginname', 'enrol_mnet'));
-
-        $mform->addElement('select', 'hostid', get_string('remotesubscriber', 'enrol_mnet'), $hosts);
-        $mform->addHelpButton('hostid', 'remotesubscriber', 'enrol_mnet');
-        $mform->addRule('hostid', get_string('required'), 'required', null, 'client');
-
-        $mform->addElement('select', 'roleid', get_string('roleforremoteusers', 'enrol_mnet'), $roles);
-        $mform->addHelpButton('roleid', 'roleforremoteusers', 'enrol_mnet');
-        $mform->addRule('roleid', get_string('required'), 'required', null, 'client');
-        $mform->setDefault('roleid', $enrol->get_config('roleid'));
-
-        $mform->addElement('text', 'name', get_string('instancename', 'enrol_mnet'));
-        $mform->addHelpButton('name', 'instancename', 'enrol_mnet');
-        $mform->setType('name', PARAM_TEXT);
-
-        $mform->addElement('hidden', 'id', null);
-        $mform->setType('id', PARAM_INT);
-
-        $this->add_action_buttons();
-
-        $this->set_data(array('id'=>$course->id));
-    }
-
-    /**
-     * Do not allow multiple instances for single remote host
-     *
-     * @param array $data raw form data
-     * @param array $files
-     * @return array of errors
-     */
-    function validation($data, $files) {
-        global $DB;
-
-        $errors = array();
-
-        if ($DB->record_exists('enrol', array('enrol' => 'mnet', 'courseid' => $data['id'], 'customint1' => $data['hostid']))) {
-            $errors['hostid'] = get_string('error_multiplehost', 'enrol_mnet');
-        }
-
-        return $errors;
-    }
-}
index 419228b..bc33fe4 100644 (file)
@@ -62,31 +62,31 @@ class enrol_mnet_plugin extends enrol_plugin {
     }
 
     /**
-     * Returns link to page which may be used to add new instance of enrolment plugin into the course
+     * Returns true if a new instance can be added to this course.
      *
      * The link is returned only if there are some MNet peers that we publish enrolment service to.
      *
      * @param int $courseid id of the course to add the instance to
-     * @return moodle_url|null page url or null if instance can not be created
+     * @return boolean
      */
-    public function get_newinstance_link($courseid) {
+    public function can_add_instance($courseid) {
         global $CFG, $DB;
         require_once($CFG->dirroot.'/mnet/service/enrol/locallib.php');
 
         $service = mnetservice_enrol::get_instance();
         if (!$service->is_available()) {
-            return null;
+            return false;
         }
         $coursecontext = context_course::instance($courseid);
         if (!has_capability('moodle/course:enrolconfig', $coursecontext)) {
-            return null;
+            return false;
         }
         $subscribers = $service->get_remote_subscribers();
         if (empty($subscribers)) {
-            return null;
+            return false;
         }
 
-        return new moodle_url('/enrol/mnet/addinstance.php', array('id'=>$courseid));
+        return true;
     }
 
     /**
@@ -110,4 +110,104 @@ class enrol_mnet_plugin extends enrol_plugin {
         $context = context_course::instance($instance->courseid);
         return has_capability('enrol/mnet:config', $context);
     }
+
+    /**
+     * Return an array of valid options for the hosts property.
+     *
+     * @return array
+     */
+    protected function get_valid_hosts_options() {
+        global $CFG;
+        require_once($CFG->dirroot.'/mnet/service/enrol/locallib.php');
+
+        $service = mnetservice_enrol::get_instance();
+
+        $subscribers = $service->get_remote_subscribers();
+        $hosts = array(0 => get_string('remotesubscribersall', 'enrol_mnet'));
+        foreach ($subscribers as $hostid => $subscriber) {
+            $hosts[$hostid] = $subscriber->appname.': '.$subscriber->hostname.' ('.$subscriber->hosturl.')';
+        }
+        return $hosts;
+    }
+
+    /**
+     * Return an array of valid options for the roles property.
+     *
+     * @param context $context
+     * @return array
+     */
+    protected function get_valid_roles_options($context) {
+        $roles = get_assignable_roles($context);
+        return $roles;
+    }
+
+    /**
+     * Add elements to the edit instance form.
+     *
+     * @param stdClass $instance
+     * @param MoodleQuickForm $mform
+     * @param context $context
+     * @return bool
+     */
+    public function edit_instance_form($instance, MoodleQuickForm $mform, $context) {
+        global $CFG;
+
+        $hosts = $this->get_valid_hosts_options();
+        $mform->addElement('select', 'customint1', get_string('remotesubscriber', 'enrol_mnet'), $hosts);
+        $mform->addHelpButton('customint1', 'remotesubscriber', 'enrol_mnet');
+        $mform->addRule('customint1', get_string('required'), 'required', null, 'client');
+
+        $roles = $this->get_valid_roles_options($context);
+        $mform->addElement('select', 'roleid', get_string('roleforremoteusers', 'enrol_mnet'), $roles);
+        $mform->addHelpButton('roleid', 'roleforremoteusers', 'enrol_mnet');
+        $mform->addRule('roleid', get_string('required'), 'required', null, 'client');
+        $mform->setDefault('roleid', $this->get_config('roleid'));
+
+        $mform->addElement('text', 'name', get_string('instancename', 'enrol_mnet'));
+        $mform->addHelpButton('name', 'instancename', 'enrol_mnet');
+        $mform->setType('name', PARAM_TEXT);
+    }
+
+    /**
+     * We are a good plugin and don't invent our own UI/validation code path.
+     *
+     * @return boolean
+     */
+    public function use_standard_editing_ui() {
+        return true;
+    }
+
+    /**
+     * Perform custom validation of the data used to edit the instance.
+     *
+     * @param array $data array of ("fieldname"=>value) of submitted data
+     * @param array $files array of uploaded files "element_name"=>tmp_file_path
+     * @param object $instance The instance loaded from the DB
+     * @param context $context The context of the instance we are editing
+     * @return array of "element_name"=>"error_description" if there are errors,
+     *         or an empty array if everything is OK.
+     * @return void
+     */
+    public function edit_instance_validation($data, $files, $instance, $context) {
+        global $DB;
+        $errors = array();
+
+        $validroles = array_keys($this->get_valid_roles_options($context));
+        $validhosts = array_keys($this->get_valid_hosts_options());
+
+        $params = array('enrol' => 'mnet', 'courseid' => $instance->courseid, 'customint1' => $data['customint1']);
+        if ($DB->record_exists('enrol', $params)) {
+            $errors['customint1'] = get_string('error_multiplehost', 'enrol_mnet');
+        }
+
+        $tovalidate = array(
+            'customint1' => $validhosts,
+            'roleid' => $validroles,
+            'name' => PARAM_TEXT
+        );
+        $typeerrors = $this->validate_param_types($data, $tovalidate);
+        $errors = array_merge($errors, $typeerrors);
+
+        return $errors;
+    }
 }
diff --git a/enrol/paypal/edit.php b/enrol/paypal/edit.php
deleted file mode 100644 (file)
index 9c0541a..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Adds new instance of enrol_paypal to specified course
- * or edits current instance.
- *
- * @package    enrol_paypal
- * @copyright  2010 Petr Skoda  {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-require('../../config.php');
-require_once('edit_form.php');
-
-$courseid   = required_param('courseid', PARAM_INT);
-$instanceid = optional_param('id', 0, PARAM_INT); // instanceid
-
-$course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
-$context = context_course::instance($course->id, MUST_EXIST);
-
-require_login($course);
-require_capability('enrol/paypal:config', $context);
-
-$PAGE->set_url('/enrol/paypal/edit.php', array('courseid'=>$course->id, 'id'=>$instanceid));
-$PAGE->set_pagelayout('admin');
-
-$return = new moodle_url('/enrol/instances.php', array('id'=>$course->id));
-if (!enrol_is_enabled('paypal')) {
-    redirect($return);
-}
-
-$plugin = enrol_get_plugin('paypal');
-
-if ($instanceid) {
-    $instance = $DB->get_record('enrol', array('courseid'=>$course->id, 'enrol'=>'paypal', 'id'=>$instanceid), '*', MUST_EXIST);
-    $instance->cost = format_float($instance->cost, 2, true);
-} else {
-    require_capability('moodle/course:enrolconfig', $context);
-    // no instance yet, we have to add new instance
-    navigation_node::override_active_url(new moodle_url('/enrol/instances.php', array('id'=>$course->id)));
-    $instance = new stdClass();
-    $instance->id       = null;
-    $instance->courseid = $course->id;
-}
-
-$mform = new enrol_paypal_edit_form(NULL, array($instance, $plugin, $context));
-
-if ($mform->is_cancelled()) {
-    redirect($return);
-
-} else if ($data = $mform->get_data()) {
-    if ($instance->id) {
-        $reset = ($instance->status != $data->status);
-
-        $instance->status         = $data->status;
-        $instance->name           = $data->name;
-        $instance->cost           = unformat_float($data->cost);
-        $instance->currency       = $data->currency;
-        $instance->roleid         = $data->roleid;
-        $instance->enrolperiod    = $data->enrolperiod;
-        $instance->enrolstartdate = $data->enrolstartdate;
-        $instance->enrolenddate   = $data->enrolenddate;
-        $instance->timemodified   = time();
-        $DB->update_record('enrol', $instance);
-        \core\event\enrol_instance_updated::create_from_record($instance)->trigger();
-
-        if ($reset) {
-            $context->mark_dirty();
-        }
-
-    } else {
-        $fields = array('status'=>$data->status, 'name'=>$data->name, 'cost'=>unformat_float($data->cost), 'currency'=>$data->currency, 'roleid'=>$data->roleid,
-                        'enrolperiod'=>$data->enrolperiod, 'enrolstartdate'=>$data->enrolstartdate, 'enrolenddate'=>$data->enrolenddate);
-        $plugin->add_instance($course, $fields);
-    }
-
-    redirect($return);
-}
-
-$PAGE->set_heading($course->fullname);
-$PAGE->set_title(get_string('pluginname', 'enrol_paypal'));
-
-echo $OUTPUT->header();
-echo $OUTPUT->heading(get_string('pluginname', 'enrol_paypal'));
-$mform->display();
-echo $OUTPUT->footer();
diff --git a/enrol/paypal/edit_form.php b/enrol/paypal/edit_form.php
deleted file mode 100644 (file)
index 8aa8059..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Adds new instance of enrol_paypal to specified course
- * or edits current instance.
- *
- * @package    enrol_paypal
- * @copyright  2010 Petr Skoda  {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-require_once($CFG->libdir.'/formslib.php');
-
-class enrol_paypal_edit_form extends moodleform {
-
-    function definition() {
-        $mform = $this->_form;
-
-        list($instance, $plugin, $context) = $this->_customdata;
-
-        $mform->addElement('header', 'header', get_string('pluginname', 'enrol_paypal'));
-
-        $mform->addElement('text', 'name', get_string('custominstancename', 'enrol'));
-        $mform->setType('name', PARAM_TEXT);
-
-        $options = array(ENROL_INSTANCE_ENABLED  => get_string('yes'),
-                         ENROL_INSTANCE_DISABLED => get_string('no'));
-        $mform->addElement('select', 'status', get_string('status', 'enrol_paypal'), $options);
-        $mform->setDefault('status', $plugin->get_config('status'));
-
-        $mform->addElement('text', 'cost', get_string('cost', 'enrol_paypal'), array('size'=>4));
-        $mform->setType('cost', PARAM_RAW); // Use unformat_float to get real value.
-        $mform->setDefault('cost', format_float($plugin->get_config('cost'), 2, true));
-
-        $paypalcurrencies = $plugin->get_currencies();
-        $mform->addElement('select', 'currency', get_string('currency', 'enrol_paypal'), $paypalcurrencies);
-        $mform->setDefault('currency', $plugin->get_config('currency'));
-
-        if ($instance->id) {
-            $roles = get_default_enrol_roles($context, $instance->roleid);
-        } else {
-            $roles = get_default_enrol_roles($context, $plugin->get_config('roleid'));
-        }
-        $mform->addElement('select', 'roleid', get_string('assignrole', 'enrol_paypal'), $roles);
-        $mform->setDefault('roleid', $plugin->get_config('roleid'));
-
-
-        $mform->addElement('duration', 'enrolperiod', get_string('enrolperiod', 'enrol_paypal'), array('optional' => true, 'defaultunit' => 86400));
-        $mform->setDefault('enrolperiod', $plugin->get_config('enrolperiod'));
-        $mform->addHelpButton('enrolperiod', 'enrolperiod', 'enrol_paypal');
-
-        $mform->addElement('date_time_selector', 'enrolstartdate', get_string('enrolstartdate', 'enrol_paypal'), array('optional' => true));
-        $mform->setDefault('enrolstartdate', 0);
-        $mform->addHelpButton('enrolstartdate', 'enrolstartdate', 'enrol_paypal');
-
-        $mform->addElement('date_time_selector', 'enrolenddate', get_string('enrolenddate', 'enrol_paypal'), array('optional' => true));
-        $mform->setDefault('enrolenddate', 0);
-        $mform->addHelpButton('enrolenddate', 'enrolenddate', 'enrol_paypal');
-
-        $mform->addElement('hidden', 'id');
-        $mform->setType('id', PARAM_INT);
-
-        $mform->addElement('hidden', 'courseid');
-        $mform->setType('courseid', PARAM_INT);
-
-        if (enrol_accessing_via_instance($instance)) {
-            $mform->addElement('static', 'selfwarn', get_string('instanceeditselfwarning', 'core_enrol'), get_string('instanceeditselfwarningtext', 'core_enrol'));
-        }
-
-        $this->add_action_buttons(true, ($instance->id ? null : get_string('addinstance', 'enrol')));
-
-        $this->set_data($instance);
-    }
-
-    function validation($data, $files) {
-        global $DB, $CFG;
-        $errors = parent::validation($data, $files);
-
-        list($instance, $plugin, $context) = $this->_customdata;
-
-        if (!empty($data['enrolenddate']) and $data['enrolenddate'] < $data['enrolstartdate']) {
-            $errors['enrolenddate'] = get_string('enrolenddaterror', 'enrol_paypal');
-        }
-
-        $cost = str_replace(get_string('decsep', 'langconfig'), '.', $data['cost']);
-        if (!is_numeric($cost)) {
-            $errors['cost'] = get_string('costerror', 'enrol_paypal');
-        }
-
-        return $errors;
-    }
-}
index 845e2e4..87ecfe5 100644 (file)
@@ -97,61 +97,54 @@ class enrol_paypal_plugin extends enrol_plugin {
     }
 
     /**
-     * Sets up navigation entries.
-     *
-     * @param object $instance
-     * @return void
+     * Returns true if the user can add a new instance in this course.
+     * @param int $courseid
+     * @return boolean
      */
-    public function add_course_navigation($instancesnode, stdClass $instance) {
-        if ($instance->enrol !== 'paypal') {
-             throw new coding_exception('Invalid enrol instance type!');
-        }
+    public function can_add_instance($courseid) {
+        $context = context_course::instance($courseid, MUST_EXIST);
 
-        $context = context_course::instance($instance->courseid);
-        if (has_capability('enrol/paypal:config', $context)) {
-            $managelink = new moodle_url('/enrol/paypal/edit.php', array('courseid'=>$instance->courseid, 'id'=>$instance->id));
-            $instancesnode->add($this->get_instance_name($instance), $managelink, navigation_node::TYPE_SETTING);
+        if (!has_capability('moodle/course:enrolconfig', $context) or !has_capability('enrol/paypal:config', $context)) {
+            return false;
         }
+
+        // multiple instances supported - different cost for different roles
+        return true;
     }
 
     /**
-     * Returns edit icons for the page with list of instances
-     * @param stdClass $instance
-     * @return array
+     * We are a good plugin and don't invent our own UI/validation code path.
+     *
+     * @return boolean
      */
-    public function get_action_icons(stdClass $instance) {
-        global $OUTPUT;
-
-        if ($instance->enrol !== 'paypal') {
-            throw new coding_exception('invalid enrol instance!');
-        }
-        $context = context_course::instance($instance->courseid);
-
-        $icons = array();
+    public function use_standard_editing_ui() {
+        return true;
+    }
 
-        if (has_capability('enrol/paypal:config', $context)) {
-            $editlink = new moodle_url("/enrol/paypal/edit.php", array('courseid'=>$instance->courseid, 'id'=>$instance->id));
-            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
-                    array('class' => 'iconsmall')));
+    /**
+     * Add new instance of enrol plugin.
+     * @param object $course
+     * @param array $fields instance fields
+     * @return int id of new instance, null if can not be created
+     */
+    public function add_instance($course, array $fields = null) {
+        if ($fields && !empty($fields['cost'])) {
+            $fields['cost'] = unformat_float($fields['cost']);
         }
-
-        return $icons;
+        return parent::add_instance($course, $fields);
     }
 
     /**
-     * Returns link to page which may be used to add new instance of enrolment plugin in course.
-     * @param int $courseid
-     * @return moodle_url page url
+     * Update instance of enrol plugin.
+     * @param stdClass $instance
+     * @param stdClass $data modified instance fields
+     * @return boolean
      */
-    public function get_newinstance_link($courseid) {
-        $context = context_course::instance($courseid, MUST_EXIST);
-
-        if (!has_capability('moodle/course:enrolconfig', $context) or !has_capability('enrol/paypal:config', $context)) {
-            return NULL;
+    public function update_instance($instance, $data) {
+        if ($data) {
+            $data->cost = unformat_float($data->cost);
         }
-
-        // multiple instances supported - different cost for different roles
-        return new moodle_url('/enrol/paypal/edit.php', array('courseid'=>$courseid));
+        return parent::update_instance($instance, $data);
     }
 
     /**
@@ -312,6 +305,126 @@ class enrol_paypal_plugin extends enrol_plugin {
         $this->process_expirations($trace);
     }
 
+    /**
+     * Return an array of valid options for the status.
+     *
+     * @return array
+     */
+    protected function get_status_options() {
+        $options = array(ENROL_INSTANCE_ENABLED  => get_string('yes'),
+                         ENROL_INSTANCE_DISABLED => get_string('no'));
+        return $options;
+    }
+
+    /**
+     * Return an array of valid options for the roleid.
+     *
+     * @param stdClass $instance
+     * @param context $context
+     * @return array
+     */
+    protected function get_roleid_options($instance, $context) {
+        if ($instance->id) {
+            $roles = get_default_enrol_roles($context, $instance->roleid);
+        } else {
+            $roles = get_default_enrol_roles($context, $this->get_config('roleid'));
+        }
+        return $roles;
+    }
+
+
+    /**
+     * Add elements to the edit instance form.
+     *
+     * @param stdClass $instance
+     * @param MoodleQuickForm $mform
+     * @param context $context
+     * @return bool
+     */
+    public function edit_instance_form($instance, MoodleQuickForm $mform, $context) {
+
+        $mform->addElement('text', 'name', get_string('custominstancename', 'enrol'));
+        $mform->setType('name', PARAM_TEXT);
+
+        $options = $this->get_status_options();
+        $mform->addElement('select', 'status', get_string('status', 'enrol_paypal'), $options);
+        $mform->setDefault('status', $this->get_config('status'));
+
+        $mform->addElement('text', 'cost', get_string('cost', 'enrol_paypal'), array('size' => 4));
+        $mform->setType('cost', PARAM_RAW);
+        $mform->setDefault('cost', format_float($this->get_config('cost'), 2, true));
+
+        $paypalcurrencies = $this->get_currencies();
+        $mform->addElement('select', 'currency', get_string('currency', 'enrol_paypal'), $paypalcurrencies);
+        $mform->setDefault('currency', $this->get_config('currency'));
+
+        $roles = $this->get_roleid_options($instance, $context);
+        $mform->addElement('select', 'roleid', get_string('assignrole', 'enrol_paypal'), $roles);
+        $mform->setDefault('roleid', $this->get_config('roleid'));
+
+        $options = array('optional' => true, 'defaultunit' => 86400);
+        $mform->addElement('duration', 'enrolperiod', get_string('enrolperiod', 'enrol_paypal'), $options);
+        $mform->setDefault('enrolperiod', $this->get_config('enrolperiod'));
+        $mform->addHelpButton('enrolperiod', 'enrolperiod', 'enrol_paypal');
+
+        $options = array('optional' => true);
+        $mform->addElement('date_time_selector', 'enrolstartdate', get_string('enrolstartdate', 'enrol_paypal'), $options);
+        $mform->setDefault('enrolstartdate', 0);
+        $mform->addHelpButton('enrolstartdate', 'enrolstartdate', 'enrol_paypal');
+
+        $options = array('optional' => true);
+        $mform->addElement('date_time_selector', 'enrolenddate', get_string('enrolenddate', 'enrol_paypal'), $options);
+        $mform->setDefault('enrolenddate', 0);
+        $mform->addHelpButton('enrolenddate', 'enrolenddate', 'enrol_paypal');
+
+        if (enrol_accessing_via_instance($instance)) {
+            $warningtext = get_string('instanceeditselfwarningtext', 'core_enrol');
+            $mform->addElement('static', 'selfwarn', get_string('instanceeditselfwarning', 'core_enrol'), $warningtext);
+        }
+    }
+
+    /**
+     * Perform custom validation of the data used to edit the instance.
+     *
+     * @param array $data array of ("fieldname"=>value) of submitted data
+     * @param array $files array of uploaded files "element_name"=>tmp_file_path
+     * @param object $instance The instance loaded from the DB
+     * @param context $context The context of the instance we are editing
+     * @return array of "element_name"=>"error_description" if there are errors,
+     *         or an empty array if everything is OK.
+     * @return void
+     */
+    public function edit_instance_validation($data, $files, $instance, $context) {
+        $errors = array();
+
+        if (!empty($data['enrolenddate']) and $data['enrolenddate'] < $data['enrolstartdate']) {
+            $errors['enrolenddate'] = get_string('enrolenddaterror', 'enrol_paypal');
+        }
+
+        $cost = str_replace(get_string('decsep', 'langconfig'), '.', $data['cost']);
+        if (!is_numeric($cost)) {
+            $errors['cost'] = get_string('costerror', 'enrol_paypal');
+        }
+
+        $validstatus = array_keys($this->get_status_options());
+        $validcurrency = array_keys($this->get_currencies());
+        $validroles = array_keys($this->get_roleid_options($instance, $context));
+        $tovalidate = array(
+            'name' => PARAM_TEXT,
+            'status' => $validstatus,
+            'currency' => $validcurrency,
+            'roleid' => $validroles,
+            'enrolperiod' => PARAM_INT,
+            'enrolstartdate' => PARAM_INT,
+            'enrolenddate' => PARAM_INT
+        );
+
+        $typeerrors = $this->validate_param_types($data, $tovalidate);
+        $errors = array_merge($errors, $typeerrors);
+
+        return $errors;
+    }
+
     /**
      * Execute synchronisation.
      * @param progress_trace $trace
diff --git a/enrol/self/edit.php b/enrol/self/edit.php
deleted file mode 100644 (file)
index f429c39..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Adds new instance of enrol_self to specified course
- * or edits current instance.
- *
- * @package    enrol_self
- * @copyright  2010 Petr Skoda  {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-require('../../config.php');
-require_once('edit_form.php');
-
-$courseid   = required_param('courseid', PARAM_INT);
-$instanceid = optional_param('id', 0, PARAM_INT);
-
-$course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
-$context = context_course::instance($course->id, MUST_EXIST);
-
-require_login($course);
-require_capability('enrol/self:config', $context);
-
-$PAGE->set_url('/enrol/self/edit.php', array('courseid'=>$course->id, 'id'=>$instanceid));
-$PAGE->set_pagelayout('admin');
-
-$return = new moodle_url('/enrol/instances.php', array('id'=>$course->id));
-if (!enrol_is_enabled('self')) {
-    redirect($return);
-}
-
-/** @var enrol_self_plugin $plugin */
-$plugin = enrol_get_plugin('self');
-
-if ($instanceid) {
-    $instance = $DB->get_record('enrol', array('courseid'=>$course->id, 'enrol'=>'self', 'id'=>$instanceid), '*', MUST_EXIST);
-
-} else {
-    require_capability('moodle/course:enrolconfig', $context);
-    // No instance yet, we have to add new instance.
-    navigation_node::override_active_url(new moodle_url('/enrol/instances.php', array('id'=>$course->id)));
-
-    $instance = (object)$plugin->get_instance_defaults();
-    $instance->id       = null;
-    $instance->courseid = $course->id;
-    $instance->status   = ENROL_INSTANCE_ENABLED; // Do not use default for automatically created instances here.
-}
-
-// Merge these two settings to one value for the single selection element.
-if ($instance->notifyall and $instance->expirynotify) {
-    $instance->expirynotify = 2;
-}
-unset($instance->notifyall);
-
-$mform = new enrol_self_edit_form(NULL, array($instance, $plugin, $context));
-
-if ($mform->is_cancelled()) {
-    redirect($return);
-
-} else if ($data = $mform->get_data()) {
-    if ($data->expirynotify == 2) {
-        $data->expirynotify = 1;
-        $data->notifyall = 1;
-    } else {
-        $data->notifyall = 0;
-    }
-    if (!$data->expirynotify) {
-        // Keep previous/default value of disabled expirythreshold option.
-        $data->expirythreshold = $instance->expirythreshold;
-    }
-    if (!isset($data->customint6)) {
-        // Add previous value of newenrols if disabled.
-        $data->customint6 = $instance->customint6;
-    }
-
-    if ($instance->id) {
-        $reset = ($instance->status != $data->status);
-
-        $instance->status         = $data->status;
-        $instance->name           = $data->name;
-        $instance->password       = $data->password;
-        $instance->customint1     = $data->customint1;
-        $instance->customint2     = $data->customint2;
-        $instance->customint3     = $data->customint3;
-        $instance->customint4     = $data->customint4;
-        $instance->customint5     = $data->customint5;
-        $instance->customint6     = $data->customint6;
-        $instance->customtext1    = $data->customtext1;
-        $instance->roleid         = $data->roleid;
-        $instance->enrolperiod    = $data->enrolperiod;
-        $instance->expirynotify   = $data->expirynotify;
-        $instance->notifyall      = $data->notifyall;
-        $instance->expirythreshold = $data->expirythreshold;
-        $instance->enrolstartdate = $data->enrolstartdate;
-        $instance->enrolenddate   = $data->enrolenddate;
-        $instance->timemodified   = time();
-        $DB->update_record('enrol', $instance);
-        \core\event\enrol_instance_updated::create_from_record($instance)->trigger();
-
-        if ($reset) {
-            $context->mark_dirty();
-        }
-
-    } else {
-        $fields = array(
-            'status'          => $data->status,
-            'name'            => $data->name,
-            'password'        => $data->password,
-            'customint1'      => $data->customint1,
-            'customint2'      => $data->customint2,
-            'customint3'      => $data->customint3,
-            'customint4'      => $data->customint4,
-            'customint5'      => $data->customint5,
-            'customint6'      => $data->customint6,
-            'customtext1'     => $data->customtext1,
-            'roleid'          => $data->roleid,
-            'enrolperiod'     => $data->enrolperiod,
-            'expirynotify'    => $data->expirynotify,
-            'notifyall'       => $data->notifyall,
-            'expirythreshold' => $data->expirythreshold,
-            'enrolstartdate'  => $data->enrolstartdate,
-            'enrolenddate'    => $data->enrolenddate);
-        $plugin->add_instance($course, $fields);
-    }
-
-    redirect($return);
-}
-
-$PAGE->set_heading($course->fullname);
-$PAGE->set_title(get_string('pluginname', 'enrol_self'));
-
-echo $OUTPUT->header();
-echo $OUTPUT->heading(get_string('pluginname', 'enrol_self'));
-$mform->display();
-echo $OUTPUT->footer();
diff --git a/enrol/self/edit_form.php b/enrol/self/edit_form.php
deleted file mode 100644 (file)
index af10050..0000000
+++ /dev/null
@@ -1,223 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Adds new instance of enrol_self to specified course
- * or edits current instance.
- *
- * @package    enrol_self
- * @copyright  2010 Petr Skoda  {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-require_once($CFG->libdir.'/formslib.php');
-require_once($CFG->dirroot.'/cohort/lib.php');
-
-class enrol_self_edit_form extends moodleform {
-
-    function definition() {
-        global $DB;
-
-        $mform = $this->_form;
-
-        list($instance, $plugin, $context) = $this->_customdata;
-
-        $mform->addElement('header', 'header', get_string('pluginname', 'enrol_self'));
-
-        $nameattribs = array('size' => '20', 'maxlength' => '255');
-        $mform->addElement('text', 'name', get_string('custominstancename', 'enrol'), $nameattribs);
-        $mform->setType('name', PARAM_TEXT);
-        $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'server');
-
-        $options = array(ENROL_INSTANCE_ENABLED  => get_string('yes'),
-                         ENROL_INSTANCE_DISABLED => get_string('no'));
-        $mform->addElement('select', 'status', get_string('status', 'enrol_self'), $options);
-        $mform->addHelpButton('status', 'status', 'enrol_self');
-
-        $options = array(1 => get_string('yes'), 0 => get_string('no'));
-        $mform->addElement('select', 'customint6', get_string('newenrols', 'enrol_self'), $options);
-        $mform->addHelpButton('customint6', 'newenrols', 'enrol_self');
-        $mform->disabledIf('customint6', 'status', 'eq', ENROL_INSTANCE_DISABLED);
-
-        $passattribs = array('size' => '20', 'maxlength' => '50');
-        $mform->addElement('passwordunmask', 'password', get_string('password', 'enrol_self'), $passattribs);
-        $mform->addHelpButton('password', 'password', 'enrol_self');
-        if (empty($instance->id) and $plugin->get_config('requirepassword')) {
-            $mform->addRule('password', get_string('required'), 'required', null, 'client');
-        }
-        $mform->addRule('password', get_string('maximumchars', '', 50), 'maxlength', 50, 'server');
-
-        $options = array(1 => get_string('yes'),
-                         0 => get_string('no'));
-        $mform->addElement('select', 'customint1', get_string('groupkey', 'enrol_self'), $options);
-        $mform->addHelpButton('customint1', 'groupkey', 'enrol_self');
-
-        $roles = $this->extend_assignable_roles($context, $instance->roleid);
-        $mform->addElement('select', 'roleid', get_string('role', 'enrol_self'), $roles);
-
-        $mform->addElement('duration', 'enrolperiod', get_string('enrolperiod', 'enrol_self'), array('optional' => true, 'defaultunit' => 86400));
-        $mform->addHelpButton('enrolperiod', 'enrolperiod', 'enrol_self');
-
-        $options = array(0 => get_string('no'), 1 => get_string('expirynotifyenroller', 'core_enrol'), 2 => get_string('expirynotifyall', 'core_enrol'));
-        $mform->addElement('select', 'expirynotify', get_string('expirynotify', 'core_enrol'), $options);
-        $mform->addHelpButton('expirynotify', 'expirynotify', 'core_enrol');
-
-        $mform->addElement('duration', 'expirythreshold', get_string('expirythreshold', 'core_enrol'), array('optional' => false, 'defaultunit' => 86400));
-        $mform->addHelpButton('expirythreshold', 'expirythreshold', 'core_enrol');
-        $mform->disabledIf('expirythreshold', 'expirynotify', 'eq', 0);
-
-        $mform->addElement('date_time_selector', 'enrolstartdate', get_string('enrolstartdate', 'enrol_self'), array('optional' => true));
-        $mform->setDefault('enrolstartdate', 0);
-        $mform->addHelpButton('enrolstartdate', 'enrolstartdate', 'enrol_self');
-
-        $mform->addElement('date_time_selector', 'enrolenddate', get_string('enrolenddate', 'enrol_self'), array('optional' => true));
-        $mform->setDefault('enrolenddate', 0);
-        $mform->addHelpButton('enrolenddate', 'enrolenddate', 'enrol_self');
-
-        $options = array(0 => get_string('never'),
-                 1800 * 3600 * 24 => get_string('numdays', '', 1800),
-                 1000 * 3600 * 24 => get_string('numdays', '', 1000),
-                 365 * 3600 * 24 => get_string('numdays', '', 365),
-                 180 * 3600 * 24 => get_string('numdays', '', 180),
-                 150 * 3600 * 24 => get_string('numdays', '', 150),
-                 120 * 3600 * 24 => get_string('numdays', '', 120),
-                 90 * 3600 * 24 => get_string('numdays', '', 90),
-                 60 * 3600 * 24 => get_string('numdays', '', 60),
-                 30 * 3600 * 24 => get_string('numdays', '', 30),
-                 21 * 3600 * 24 => get_string('numdays', '', 21),
-                 14 * 3600 * 24 => get_string('numdays', '', 14),
-                 7 * 3600 * 24 => get_string('numdays', '', 7));
-        $mform->addElement('select', 'customint2', get_string('longtimenosee', 'enrol_self'), $options);
-        $mform->addHelpButton('customint2', 'longtimenosee', 'enrol_self');
-
-        $mform->addElement('text', 'customint3', get_string('maxenrolled', 'enrol_self'));
-        $mform->addHelpButton('customint3', 'maxenrolled', 'enrol_self');
-        $mform->setType('customint3', PARAM_INT);
-
-        $cohorts = array(0 => get_string('no'));
-        $allcohorts = cohort_get_available_cohorts($context, 0, 0, 0);
-        if ($instance->customint5 && !isset($allcohorts[$instance->customint5]) &&
-                ($c = $DB->get_record('cohort', array('id' => $instance->customint5), 'id, name, idnumber, contextid, visible', IGNORE_MISSING))) {
-            // Current cohort was not found because current user can not see it. Still keep it.
-            $allcohorts[$instance->customint5] = $c;
-        }
-        foreach ($allcohorts as $c) {
-            $cohorts[$c->id] = format_string($c->name, true, array('context' => context::instance_by_id($c->contextid)));
-            if ($c->idnumber) {
-                $cohorts[$c->id] .= ' ['.s($c->idnumber).']';
-            }
-        }
-        if ($instance->customint5 && !isset($allcohorts[$instance->customint5])) {
-            // Somebody deleted a cohort, better keep the wrong value so that random ppl can not enrol.
-            $cohorts[$instance->customint5] = get_string('unknowncohort', 'cohort', $instance->customint5);
-        }
-        if (count($cohorts) > 1) {
-            $mform->addElement('select', 'customint5', get_string('cohortonly', 'enrol_self'), $cohorts);
-            $mform->addHelpButton('customint5', 'cohortonly', 'enrol_self');
-        } else {
-            $mform->addElement('hidden', 'customint5');
-            $mform->setType('customint5', PARAM_INT);
-            $mform->setConstant('customint5', 0);
-        }
-
-        $mform->addElement('advcheckbox', 'customint4', get_string('sendcoursewelcomemessage', 'enrol_self'));
-        $mform->addHelpButton('customint4', 'sendcoursewelcomemessage', 'enrol_self');
-
-        $mform->addElement('textarea', 'customtext1', get_string('customwelcomemessage', 'enrol_self'), array('cols'=>'60', 'rows'=>'8'));
-        $mform->addHelpButton('customtext1', 'customwelcomemessage', 'enrol_self');
-
-        $mform->addElement('hidden', 'id');
-        $mform->setType('id', PARAM_INT);
-        $mform->addElement('hidden', 'courseid');
-        $mform->setType('courseid', PARAM_INT);
-
-        if (enrol_accessing_via_instance($instance)) {
-            $mform->addElement('static', 'selfwarn', get_string('instanceeditselfwarning', 'core_enrol'), get_string('instanceeditselfwarningtext', 'core_enrol'));
-        }
-
-        $this->add_action_buttons(true, ($instance->id ? null : get_string('addinstance', 'enrol')));
-
-        $this->set_data($instance);
-    }
-
-    function validation($data, $files) {
-        global $DB, $CFG;
-        $errors = parent::validation($data, $files);
-
-        list($instance, $plugin, $context) = $this->_customdata;
-        $checkpassword = false;
-
-        if ($instance->id) {
-            // Check the password if we are enabling the plugin again.
-            if (($instance->status == ENROL_INSTANCE_DISABLED) && ($data['status'] == ENROL_INSTANCE_ENABLED)) {
-                $checkpassword = true;
-            }
-
-            // Check the password if the instance is enabled and the password has changed.
-            if (($data['status'] == ENROL_INSTANCE_ENABLED) && ($instance->password !== $data['password'])) {
-                $checkpassword = true;
-            }
-        } else {
-            $checkpassword = true;
-        }
-
-        if ($checkpassword) {
-            $require = $plugin->get_config('requirepassword');
-            $policy  = $plugin->get_config('usepasswordpolicy');
-            if ($require and trim($data['password']) === '') {
-                $errors['password'] = get_string('required');
-            } else if (!empty($data['password']) && $policy) {
-                $errmsg = '';
-                if (!check_password_policy($data['password'], $errmsg)) {
-                    $errors['password'] = $errmsg;
-                }
-            }
-        }
-
-        if ($data['status'] == ENROL_INSTANCE_ENABLED) {
-            if (!empty($data['enrolenddate']) and $data['enrolenddate'] < $data['enrolstartdate']) {
-                $errors['enrolenddate'] = get_string('enrolenddaterror', 'enrol_self');
-            }
-        }
-
-        if ($data['expirynotify'] > 0 and $data['expirythreshold'] < 86400) {
-            $errors['expirythreshold'] = get_string('errorthresholdlow', 'core_enrol');
-        }
-
-        return $errors;
-    }
-
-    /**
-    * Gets a list of roles that this user can assign for the course as the default for self-enrolment.
-    *
-    * @param context $context the context.
-    * @param integer $defaultrole the id of the role that is set as the default for self-enrolment
-    * @return array index is the role id, value is the role name
-    */
-    function extend_assignable_roles($context, $defaultrole) {
-        global $DB;
-
-        $roles = get_assignable_roles($context, ROLENAME_BOTH);
-        if (!isset($roles[$defaultrole])) {
-            if ($role = $DB->get_record('role', array('id'=>$defaultrole))) {
-                $roles[$defaultrole] = role_get_name($role, $context, ROLENAME_BOTH);
-            }
-        }
-        return $roles;
-    }
-}
index fef3f6f..7b2827e 100644 (file)
@@ -118,61 +118,19 @@ class enrol_self_plugin extends enrol_plugin {
     }
 
     /**
-     * Sets up navigation entries.
+     * Return true if we can add a new instance to this course.
      *
-     * @param stdClass $instancesnode
-     * @param stdClass $instance
-     * @return void
-     */
-    public function add_course_navigation($instancesnode, stdClass $instance) {
-        if ($instance->enrol !== 'self') {
-             throw new coding_exception('Invalid enrol instance type!');
-        }
-
-        $context = context_course::instance($instance->courseid);
-        if (has_capability('enrol/self:config', $context)) {
-            $managelink = new moodle_url('/enrol/self/edit.php', array('courseid'=>$instance->courseid, 'id'=>$instance->id));
-            $instancesnode->add($this->get_instance_name($instance), $managelink, navigation_node::TYPE_SETTING);
-        }
-    }
-
-    /**
-     * Returns edit icons for the page with list of instances
-     * @param stdClass $instance
-     * @return array
-     */
-    public function get_action_icons(stdClass $instance) {
-        global $OUTPUT;
-
-        if ($instance->enrol !== 'self') {
-            throw new coding_exception('invalid enrol instance!');
-        }
-        $context = context_course::instance($instance->courseid);
-
-        $icons = array();
-
-        if (has_capability('enrol/self:config', $context)) {
-            $editlink = new moodle_url("/enrol/self/edit.php", array('courseid'=>$instance->courseid, 'id'=>$instance->id));
-            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
-                array('class' => 'iconsmall')));
-        }
-
-        return $icons;
-    }
-
-    /**
-     * Returns link to page which may be used to add new instance of enrolment plugin in course.
      * @param int $courseid
-     * @return moodle_url page url
+     * @return boolean
      */
-    public function get_newinstance_link($courseid) {
+    public function can_add_instance($courseid) {
         $context = context_course::instance($courseid, MUST_EXIST);
 
         if (!has_capability('moodle/course:enrolconfig', $context) or !has_capability('enrol/self:config', $context)) {
-            return NULL;
+            return false;
         }
-        // Multiple instances supported - different roles with different password.
-        return new moodle_url('/enrol/self/edit.php', array('courseid'=>$courseid));
+
+        return true;
     }
 
     /**
@@ -236,7 +194,7 @@ class enrol_self_plugin extends enrol_plugin {
 
         if (true === $enrolstatus) {
             // This user can self enrol using this instance.
-            $form = new enrol_self_enrol_form(NULL, $instance);
+            $form = new enrol_self_enrol_form(null, $instance);
             $instanceid = optional_param('instance', 0, PARAM_INT);
             if ($instance->id == $instanceid) {
                 if ($data = $form->get_data()) {
@@ -708,4 +666,357 @@ class enrol_self_plugin extends enrol_plugin {
 
         return true;
     }
+
+    /**
+     * Return an array of valid options for the status.
+     *
+     * @return array
+     */
+    protected function get_status_options() {
+        $options = array(ENROL_INSTANCE_ENABLED  => get_string('yes'),
+                         ENROL_INSTANCE_DISABLED => get_string('no'));
+        return $options;
+    }
+
+    /**
+     * Return an array of valid options for the newenrols property.
+     *
+     * @return array
+     */
+    protected function get_newenrols_options() {
+        $options = array(1 => get_string('yes'), 0 => get_string('no'));
+        return $options;
+    }
+
+    /**
+     * Return an array of valid options for the groupkey property.
+     *
+     * @return array
+     */
+    protected function get_groupkey_options() {
+        $options = array(1 => get_string('yes'), 0 => get_string('no'));
+        return $options;
+    }
+
+    /**
+     * Return an array of valid options for the expirynotify property.
+     *
+     * @return array
+     */
+    protected function get_expirynotify_options() {
+        $options = array(0 => get_string('no'),
+                         1 => get_string('expirynotifyenroller', 'core_enrol'),
+                         2 => get_string('expirynotifyall', 'core_enrol'));
+        return $options;
+    }
+
+    /**
+     * Return an array of valid options for the longtimenosee property.
+     *
+     * @return array
+     */
+    protected function get_longtimenosee_options() {
+        $options = array(0 => get_string('never'),
+                         1800 * 3600 * 24 => get_string('numdays', '', 1800),
+                         1000 * 3600 * 24 => get_string('numdays', '', 1000),
+                         365 * 3600 * 24 => get_string('numdays', '', 365),
+                         180 * 3600 * 24 => get_string('numdays', '', 180),
+                         150 * 3600 * 24 => get_string('numdays', '', 150),
+                         120 * 3600 * 24 => get_string('numdays', '', 120),
+                         90 * 3600 * 24 => get_string('numdays', '', 90),
+                         60 * 3600 * 24 => get_string('numdays', '', 60),
+                         30 * 3600 * 24 => get_string('numdays', '', 30),
+                         21 * 3600 * 24 => get_string('numdays', '', 21),
+                         14 * 3600 * 24 => get_string('numdays', '', 14),
+                         7 * 3600 * 24 => get_string('numdays', '', 7));
+        return $options;
+    }
+
+    /**
+     * Add elements to the edit instance form.
+     *
+     * @param stdClass $instance
+     * @param MoodleQuickForm $mform
+     * @param context $context
+     * @return bool
+     */
+    public function edit_instance_form($instance, MoodleQuickForm $mform, $context) {
+        global $CFG;
+
+        // Merge these two settings to one value for the single selection element.
+        if ($instance->notifyall and $instance->expirynotify) {
+            $instance->expirynotify = 2;
+        }
+        unset($instance->notifyall);
+
+        $nameattribs = array('size' => '20', 'maxlength' => '255');
+        $mform->addElement('text', 'name', get_string('custominstancename', 'enrol'), $nameattribs);
+        $mform->setType('name', PARAM_TEXT);
+        $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'server');
+
+        $options = $this->get_status_options();
+        $mform->addElement('select', 'status', get_string('status', 'enrol_self'), $options);
+        $mform->addHelpButton('status', 'status', 'enrol_self');
+
+        $options = $this->get_newenrols_options();
+        $mform->addElement('select', 'customint6', get_string('newenrols', 'enrol_self'), $options);
+        $mform->addHelpButton('customint6', 'newenrols', 'enrol_self');
+        $mform->disabledIf('customint6', 'status', 'eq', ENROL_INSTANCE_DISABLED);
+
+        $passattribs = array('size' => '20', 'maxlength' => '50');
+        $mform->addElement('passwordunmask', 'password', get_string('password', 'enrol_self'), $passattribs);
+        $mform->addHelpButton('password', 'password', 'enrol_self');
+        if (empty($instance->id) and $this->get_config('requirepassword')) {
+            $mform->addRule('password', get_string('required'), 'required', null, 'client');
+        }
+        $mform->addRule('password', get_string('maximumchars', '', 50), 'maxlength', 50, 'server');
+
+        $options = $this->get_groupkey_options();
+        $mform->addElement('select', 'customint1', get_string('groupkey', 'enrol_self'), $options);
+        $mform->addHelpButton('customint1', 'groupkey', 'enrol_self');
+
+        $roles = $this->extend_assignable_roles($context, $instance->roleid);
+        $mform->addElement('select', 'roleid', get_string('role', 'enrol_self'), $roles);
+
+        $options = array('optional' => true, 'defaultunit' => 86400);
+        $mform->addElement('duration', 'enrolperiod', get_string('enrolperiod', 'enrol_self'), $options);
+        $mform->addHelpButton('enrolperiod', 'enrolperiod', 'enrol_self');
+
+        $options = $this->get_expirynotify_options();
+        $mform->addElement('select', 'expirynotify', get_string('expirynotify', 'core_enrol'), $options);
+        $mform->addHelpButton('expirynotify', 'expirynotify', 'core_enrol');
+
+        $options = array('optional' => false, 'defaultunit' => 86400);
+        $mform->addElement('duration', 'expirythreshold', get_string('expirythreshold', 'core_enrol'), $options);
+        $mform->addHelpButton('expirythreshold', 'expirythreshold', 'core_enrol');
+        $mform->disabledIf('expirythreshold', 'expirynotify', 'eq', 0);
+
+        $options = array('optional' => true);
+        $mform->addElement('date_time_selector', 'enrolstartdate', get_string('enrolstartdate', 'enrol_self'), $options);
+        $mform->setDefault('enrolstartdate', 0);
+        $mform->addHelpButton('enrolstartdate', 'enrolstartdate', 'enrol_self');
+
+        $options = array('optional' => true);
+        $mform->addElement('date_time_selector', 'enrolenddate', get_string('enrolenddate', 'enrol_self'), $options);
+        $mform->setDefault('enrolenddate', 0);
+        $mform->addHelpButton('enrolenddate', 'enrolenddate', 'enrol_self');
+
+        $options = $this->get_longtimenosee_options();
+        $mform->addElement('select', 'customint2', get_string('longtimenosee', 'enrol_self'), $options);
+        $mform->addHelpButton('customint2', 'longtimenosee', 'enrol_self');
+
+        $mform->addElement('text', 'customint3', get_string('maxenrolled', 'enrol_self'));
+        $mform->addHelpButton('customint3', 'maxenrolled', 'enrol_self');
+        $mform->setType('customint3', PARAM_INT);
+
+        require_once($CFG->dirroot.'/cohort/lib.php');
+
+        $cohorts = array(0 => get_string('no'));
+        $allcohorts = cohort_get_available_cohorts($context, 0, 0, 0);
+        if ($instance->customint5 && !isset($allcohorts[$instance->customint5])) {
+            $c = $DB->get_record('cohort',
+                                 array('id' => $instance->customint5),
+                                 'id, name, idnumber, contextid, visible',
+                                 IGNORE_MISSING);
+            if ($c) {
+                // Current cohort was not found because current user can not see it. Still keep it.
+                $allcohorts[$instance->customint5] = $c;
+            }
+        }
+        foreach ($allcohorts as $c) {
+            $cohorts[$c->id] = format_string($c->name, true, array('context' => context::instance_by_id($c->contextid)));
+            if ($c->idnumber) {
+                $cohorts[$c->id] .= ' ['.s($c->idnumber).']';
+            }
+        }
+        if ($instance->customint5 && !isset($allcohorts[$instance->customint5])) {
+            // Somebody deleted a cohort, better keep the wrong value so that random ppl can not enrol.
+            $cohorts[$instance->customint5] = get_string('unknowncohort', 'cohort', $instance->customint5);
+        }
+        if (count($cohorts) > 1) {
+            $mform->addElement('select', 'customint5', get_string('cohortonly', 'enrol_self'), $cohorts);
+            $mform->addHelpButton('customint5', 'cohortonly', 'enrol_self');
+        } else {
+            $mform->addElement('hidden', 'customint5');
+            $mform->setType('customint5', PARAM_INT);
+            $mform->setConstant('customint5', 0);
+        }
+
+        $mform->addElement('advcheckbox', 'customint4', get_string('sendcoursewelcomemessage', 'enrol_self'));
+        $mform->addHelpButton('customint4', 'sendcoursewelcomemessage', 'enrol_self');
+
+        $options = array('cols' => '60', 'rows' => '8');
+        $mform->addElement('textarea', 'customtext1', get_string('customwelcomemessage', 'enrol_self'), $options);
+        $mform->addHelpButton('customtext1', 'customwelcomemessage', 'enrol_self');
+
+        if (enrol_accessing_via_instance($instance)) {
+            $warntext = get_string('instanceeditselfwarningtext', 'core_enrol');
+            $mform->addElement('static', 'selfwarn', get_string('instanceeditselfwarning', 'core_enrol'), $warntext);
+        }
+    }
+
+    /**
+     * We are a good plugin and don't invent our own UI/validation code path.
+     *
+     * @return boolean
+     */
+    public function use_standard_editing_ui() {
+        return true;
+    }
+
+    /**
+     * Perform custom validation of the data used to edit the instance.
+     *
+     * @param array $data array of ("fieldname"=>value) of submitted data
+     * @param array $files array of uploaded files "element_name"=>tmp_file_path
+     * @param object $instance The instance loaded from the DB
+     * @param context $context The context of the instance we are editing
+     * @return array of "element_name"=>"error_description" if there are errors,
+     *         or an empty array if everything is OK.
+     * @return void
+     */
+    public function edit_instance_validation($data, $files, $instance, $context) {
+        $errors = array();
+
+        $checkpassword = false;
+
+        if ($instance->id) {
+            // Check the password if we are enabling the plugin again.
+            if (($instance->status == ENROL_INSTANCE_DISABLED) && ($data['status'] == ENROL_INSTANCE_ENABLED)) {
+                $checkpassword = true;
+            }
+
+            // Check the password if the instance is enabled and the password has changed.
+            if (($data['status'] == ENROL_INSTANCE_ENABLED) && ($instance->password !== $data['password'])) {
+                $checkpassword = true;
+            }
+        } else {
+            $checkpassword = true;
+        }
+
+        if ($checkpassword) {
+            $require = $this->get_config('requirepassword');
+            $policy  = $this->get_config('usepasswordpolicy');
+            if ($require and trim($data['password']) === '') {
+                $errors['password'] = get_string('required');
+            } else if (!empty($data['password']) && $policy) {
+                $errmsg = '';
+                if (!check_password_policy($data['password'], $errmsg)) {
+                    $errors['password'] = $errmsg;
+                }
+            }
+        }
+
+        if ($data['status'] == ENROL_INSTANCE_ENABLED) {
+            if (!empty($data['enrolenddate']) and $data['enrolenddate'] < $data['enrolstartdate']) {
+                $errors['enrolenddate'] = get_string('enrolenddaterror', 'enrol_self');
+            }
+        }
+
+        if ($data['expirynotify'] > 0 and $data['expirythreshold'] < 86400) {
+            $errors['expirythreshold'] = get_string('errorthresholdlow', 'core_enrol');
+        }
+
+        // Now these ones are checked by quickforms, but we may be called by the upload enrolments tool, or a webservive.
+        if (core_text::strlen($data['name']) > 255) {
+            $errors['name'] = get_string('err_maxlength', 'form', 255);
+        }
+        $validstatus = array_keys($this->get_status_options());
+        $validnewenrols = array_keys($this->get_newenrols_options());
+        if (core_text::strlen($data['password']) > 50) {
+            $errors['name'] = get_string('err_maxlength', 'form', 50);
+        }
+        $validgroupkey = array_keys($this->get_groupkey_options());
+        $context = context_course::instance($instance->courseid);
+        $validroles = array_keys($this->extend_assignable_roles($context, $instance->roleid));
+        $validexpirynotify = array_keys($this->get_expirynotify_options());
+        $validlongtimenosee = array_keys($this->get_longtimenosee_options());
+        $tovalidate = array(
+            'enrolstartdate' => PARAM_INT,
+            'enrolenddate' => PARAM_INT,
+            'name' => PARAM_TEXT,
+            'customint1' => $validgroupkey,
+            'customint2' => $validlongtimenosee,
+            'customint3' => PARAM_INT,
+            'customint4' => PARAM_BOOL,
+            'customint5' => PARAM_INT,
+            'customint6' => $validnewenrols,
+            'status' => $validstatus,
+            'enrolperiod' => PARAM_INT,
+            'expirynotify' => $validexpirynotify,
+            'roleid' => $validroles
+        );
+        if ($data['expirynotify'] != 0) {
+            $tovalidate['expirythreshold'] = PARAM_INT;
+        }
+        $typeerrors = $this->validate_param_types($data, $tovalidate);
+        $errors = array_merge($errors, $typeerrors);
+
+        return $errors;
+    }
+
+    /**
+     * Add new instance of enrol plugin.
+     * @param object $course
+     * @param array $fields instance fields
+     * @return int id of new instance, null if can not be created
+     */
+    public function add_instance($course, array $fields = null) {
+        // In the form we are representing 2 db columns with one field.
+        if (!empty($fields) && !empty($fields['expirynotify'])) {
+            if ($fields['expirynotify'] == 2) {
+                $fields['expirynotify'] = 1;
+                $fields['notifyall'] = 1;
+            } else {
+                $fields['notifyall'] = 0;
+            }
+        }
+
+        return parent::add_instance($course, $fields);
+    }
+
+    /**
+     * Update instance of enrol plugin.
+     * @param stdClass $instance
+     * @param stdClass $data modified instance fields
+     * @return boolean
+     */
+    public function update_instance($instance, $data) {
+        // In the form we are representing 2 db columns with one field.
+        if ($data->expirynotify == 2) {
+            $data->expirynotify = 1;
+            $data->notifyall = 1;
+        } else {
+            $data->notifyall = 0;
+        }
+        // Keep previous/default value of disabled expirythreshold option.
+        if (!$data->expirynotify) {
+            $data->expirythreshold = $instance->expirythreshold;
+        }
+        // Add previous value of newenrols if disabled.
+        if (!isset($data->customint6)) {
+            $data->customint6 = $instance->customint6;
+        }
+
+        return parent::update_instance($instance, $data);
+    }
+
+    /**
+     * Gets a list of roles that this user can assign for the course as the default for self-enrolment.
+     *
+     * @param context $context the context.
+     * @param integer $defaultrole the id of the role that is set as the default for self-enrolment
+     * @return array index is the role id, value is the role name
+     */
+    public function extend_assignable_roles($context, $defaultrole) {
+        global $DB;
+
+        $roles = get_assignable_roles($context, ROLENAME_BOTH);
+        if (!isset($roles[$defaultrole])) {
+            if ($role = $DB->get_record('role', array('id' => $defaultrole))) {
+                $roles[$defaultrole] = role_get_name($role, $context, ROLENAME_BOTH);
+            }
+        }
+        return $roles;
+    }
 }
index 38a41e1..1fb1729 100644 (file)
@@ -1,5 +1,10 @@
 This files describes API changes in /enrol/* - plugins,
 information provided here is intended especially for developers.
+=== 3.0 ===
+Enrolment plugins UI have been consolidated. Plugins can implement use_standard_editing_ui() function
+and add edit_instance_form() and edit_instance_validation() methods instead of providing their own edit.php and form. They can
+then rely on the default implementation of get_action_icons and get_course_navigation. In future
+this will mean they can be called by webservices/user upload tools because they can validate their data.
 
 === 3.1 ===
 * core_enrol_external::get_enrolled_users now supports two additional parameters for ordering: sortby and sortdirection.
index 6f6cfd8..6c34d47 100644 (file)
@@ -102,6 +102,7 @@ $string['inadequatestoreformapping'] = 'This store doesn\'t meet the requirement
 $string['invalidlock'] = 'Invalid lock';
 $string['invalidplugin'] = 'Invalid plugin';
 $string['invalidstore'] = 'Invalid cache store provided';
+$string['localstorenotification'] = 'This cache can be safely mapped to a store that is local to each webserver';
 $string['lockdefault'] = 'Default';
 $string['locking'] = 'Locking';
 $string['locking_help'] = 'Locking is a mechanism that restricts access to cached data to one process at a time to prevent the data from being overwritten. The locking method determines how the lock is acquired and checked.';
@@ -131,6 +132,7 @@ $string['requestcount'] = 'Test with {$a} requests';
 $string['rescandefinitions'] = 'Rescan definitions';
 $string['result'] = 'Result';
 $string['set'] = 'Set';
+$string['sharedstorenotification'] = 'This cache must be mapped to a store that is shared to all webservers';
 $string['sharing'] = 'Sharing';
 $string['sharing_all'] = 'Everyone.';
 $string['sharing_input'] = 'Custom key (entered below)';
index 37952ec..7df55c2 100644 (file)
Binary files a/lib/amd/build/notification.min.js and b/lib/amd/build/notification.min.js differ
index 1880b80..845a2e6 100644 (file)
@@ -14,6 +14,8 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
+ * A system for displaying notifications to users from the session.
+ *
  * Wrapper for the YUI M.core.notification class. Allows us to
  * use the YUI version in AMD code until it is replaced.
  *
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  * @since      2.9
  */
-define(['core/yui'], function(Y) {
+define(['core/yui', 'jquery', 'theme_bootstrapbase/bootstrap', 'core/templates', 'core/ajax', 'core/log'],
+function(Y, $, bootstrap, templates, ajax, log) {
+    var notificationModule = {
+        types: {
+            'success':  'core/notification_success',
+            'info':     'core/notification_info',
+            'warning':  'core/notification_warning',
+            'error':    'core/notification_error',
+        },
 
-    // Private variables and functions.
+        fieldName: 'user-notifications',
+
+        fetchNotifications: function() {
+            var promises = ajax.call([{
+                methodname: 'core_fetch_notifications',
+                args: {
+                    contextid: notificationModule.contextid
+                }
+            }]);
+
+            promises[0]
+                .done(notificationModule.addNotifications)
+                ;
+
+        },
+
+        addNotifications: function(notifications) {
+            if (!notifications) {
+                notifications = [];
+            }
+
+            $.each(notifications, function(i, notification) {
+                notificationModule.renderNotification(notification.template, notification.variables);
+            });
+        },
+
+        setupTargetRegion: function() {
+            var targetRegion = $('#' + notificationModule.fieldName);
+            if (targetRegion.length) {
+                return;
+            }
+
+            var newRegion = $('<span>').attr('id', notificationModule.fieldName);
+
+            targetRegion = $('#region-main');
+            if (targetRegion.length) {
+                return targetRegion.prepend(newRegion);
+            }
+
+            targetRegion = $('[role="main"]');
+            if (targetRegion.length) {
+                return targetRegion.prepend(newRegion);
+            }
+
+            targetRegion = $('body');
+            return targetRegion.prepend(newRegion);
+        },
+
+        addNotification: function(notification) {
+            var template = notificationModule.types.error;
+
+            notification = $.extend({
+                    closebutton:    true,
+                    announce:       true,
+                    type:           'error'
+                }, notification);
+
+            if (notification.template) {
+                template = notification.template;
+                delete notification.template;
+            } else if (notification.type){
+                if (typeof notificationModule.types[notification.type] !== 'undefined') {
+                    template = notificationModule.types[notification.type];
+                }
+                delete notification.type;
+            }
+
+            return notificationModule.renderNotification(template, notification);
+        },
+
+        renderNotification: function(template, variables) {
+            if (typeof variables.message === 'undefined' || !variables.message) {
+                log.debug('Notification received without content. Skipping.');
+                return;
+            }
+            templates.render(template, variables)
+                .done(function(html) {
+                    $('#' + notificationModule.fieldName).prepend(html);
+                })
+                .fail(notificationModule.exception)
+                ;
+        },
 
-    return /** @alias module:core/notification */ {
-        // Public variables and functions.
-        /**
-         * Wrap M.core.alert.
-         *
-         * @method alert
-         * @param {string} title
-         * @param {string} message
-         * @param {string} yesLabel
-         */
         alert: function(title, message, yesLabel) {
             // Here we are wrapping YUI. This allows us to start transitioning, but
             // wait for a good alternative without having inconsistent dialogues.
@@ -52,16 +133,6 @@ define(['core/yui'], function(Y) {
             });
         },
 
-        /**
-         * Wrap M.core.confirm.
-         *
-         * @method confirm
-         * @param {string} title
-         * @param {string} question
-         * @param {string} yesLabel
-         * @param {string} noLabel
-         * @param {function} callback
-         */
         confirm: function(title, question, yesLabel, noLabel, callback) {
             // Here we are wrapping YUI. This allows us to start transitioning, but
             // wait for a good alternative without having inconsistent dialogues.
@@ -80,12 +151,6 @@ define(['core/yui'], function(Y) {
             });
         },
 
-        /**
-         * Wrap M.core.exception.
-         *
-         * @method exception
-         * @param {Error} ex
-         */
         exception: function(ex) {
             // Fudge some parameters.
             if (ex.backtrace) {
@@ -102,4 +167,73 @@ define(['core/yui'], function(Y) {
             });
         }
     };
+
+    return /** @alias module:core/notification */{
+        init: function(contextid, notifications) {
+            notificationModule.contextid = contextid;
+
+            // Setup the message target region if it isn't setup already
+            notificationModule.setupTargetRegion();
+
+            // Setup closing of bootstrap alerts.
+            $().alert();
+
+            // Add provided notifications.
+            notificationModule.addNotifications(notifications);
+
+            // Poll for any new notifications.
+            notificationModule.fetchNotifications();
+        },
+
+        /**
+         * Poll the server for any new notifications.
+         *
+         * @method fetchNotifications
+         */
+        fetchNotifications: notificationModule.fetchNotifications,
+
+        /**
+         * Add a notification to the page.
+         *
+         * Note: This does not cause the notification to be added to the session.
+         *
+         * @method addNotification
+         * @param {Object}  notification                The notification to add.
+         * @param {string}  notification.message        The body of the notification
+         * @param {string}  notification.type           The type of notification to add (error, warning, info, success).
+         * @param {Boolean} notification.closebutton    Whether to show the close button.
+         * @param {Boolean} notification.announce       Whether to announce to screen readers.
+         */
+        addNotification: notificationModule.addNotification,
+
+        /**
+         * Wrap M.core.alert.
+         *
+         * @method alert
+         * @param {string} title
+         * @param {string} message
+         * @param {string} yesLabel
+         */
+        alert: notificationModule.alert,
+
+        /**
+         * Wrap M.core.confirm.
+         *
+         * @method confirm
+         * @param {string} title
+         * @param {string} question
+         * @param {string} yesLabel
+         * @param {string} noLabel
+         * @param {function} callback
+         */
+        confirm: notificationModule.confirm,
+
+        /**
+         * Wrap M.core.exception.
+         *
+         * @method exception
+         * @param {Error} ex
+         */
+        exception: notificationModule.exception
+    };
 });
diff --git a/lib/classes/notification.php b/lib/classes/notification.php
new file mode 100644 (file)
index 0000000..aeb7e03
--- /dev/null
@@ -0,0 +1,165 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace core;
+
+/**
+ * User Alert notifications.
+ *
+ * @package    core
+ * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+class notification {
+    /**
+     * A notification of level 'success'.
+     */
+    const SUCCESS = 'success';
+
+    /**
+     * A notification of level 'warning'.
+     */
+    const WARNING = 'warning';
+
+    /**
+     * A notification of level 'info'.
+     */
+    const INFO = 'info';
+
+    /**
+     * A notification of level 'error'.
+     */
+    const ERROR = 'error';
+
+    /**
+     * Add a message to the session notification stack.
+     *
+     * @param string $message The message to add to the stack
+     * @param string $level   The type of message to add to the stack
+     */
+    public static function add($message, $level = null) {
+        global $PAGE, $SESSION;
+
+        if ($PAGE && $PAGE->state === \moodle_page::STATE_IN_BODY) {
+            // Currently in the page body - just render and exit immediately.
+            // We insert some code to immediately insert this into the user-notifications created by the header.
+            $id = uniqid();
+            echo \html_writer::span(
+                $PAGE->get_renderer('core')->render(new \core\output\notification($message, $level)),
+                '', array('id' => $id));
+
+            // Insert this JS here using a script directly rather than waiting for the page footer to load to avoid
+            // ensure that the message is added to the user-notifications section as soon as possible after it is created.
+            echo \html_writer::script(
+                    "(function() {" .
+                        "var notificationHolder = document.getElementById('user-notifications');" .
+                        "if (!notificationHolder) { return; }" .
+                        "var thisNotification = document.getElementById('{$id}');" .
+                        "if (!thisNotification) { return; }" .
+                        "notificationHolder.appendChild(thisNotification.firstChild);" .
+                        "thisNotification.remove();" .
+                    "})();"
+                );
+            return;
+        }
+
+        // Add the notification directly to the session.
+        // This will either be fetched in the header, or by JS in the footer.
+        $SESSION->notifications[] = (object) array(
+            'message'   => $message,
+            'type'      => $level,
+        );
+    }
+
+    /**
+     * Fetch all of the notifications in the stack and clear the stack.
+     *
+     * @return array All of the notifications in the stack
+     */
+    public static function fetch() {
+        global $SESSION;
+
+        if (!isset($SESSION) || !isset($SESSION->notifications)) {
+            return [];
+        }
+
+        $notifications = $SESSION->notifications;
+        $SESSION->notifications = [];
+
+        $renderables = [];
+        foreach ($notifications as $notification) {
+            $renderable = new \core\output\notification($notification->message, $notification->type);
+            $renderables[] = $renderable;
+        }
+
+        return $renderables;
+    }
+
+    /**
+     * Fetch all of the notifications in the stack and clear the stack.
+     *
+     * @return array All of the notifications in the stack
+     */
+    public static function fetch_as_array(\renderer_base $renderer) {
+        $notifications = [];
+        foreach (self::fetch() as $notification) {
+            $notifications[] = [
+                'template'  => $notification->get_template_name(),
+                'variables' => $notification->export_for_template($renderer),
+            ];
+        }
+        return $notifications;
+    }
+
+    /**
+     * Add a success message to the notification stack.
+     *
+     * @param string $message The message to add to the stack
+     */
+    public static function success($message) {
+        return self::add($message, self::SUCCESS);
+    }
+
+    /**
+     * Add a info message to the notification stack.
+     *
+     * @param string $message The message to add to the stack
+     */
+    public static function info($message) {
+        return self::add($message, self::INFO);
+    }
+
+    /**
+     * Add a warning message to the notification stack.
+     *
+     * @param string $message The message to add to the stack
+     */
+    public static function warning($message) {
+        return self::add($message, self::WARNING);
+    }
+
+    /**
+     * Add a error message to the notification stack.
+     *
+     * @param string $message The message to add to the stack
+     */
+    public static function error($message) {
+        return self::add($message, self::ERROR);
+    }
+}
index 6cf19ed..12c9aaf 100644 (file)
@@ -23,7 +23,6 @@
  */
 
 namespace core\output;
-use stdClass;
 
 /**
  * Data structure representing a notification.
@@ -37,31 +36,67 @@ use stdClass;
 class notification implements \renderable, \templatable {
 
     /**
-     * A generic message.
+     * A notification of level 'success'.
      */
-    const NOTIFY_MESSAGE = 'message';
+    const NOTIFY_SUCCESS = 'success';
+
     /**
-     * A message notifying the user of a successful operation.
+     * A notification of level 'warning'.
      */
-    const NOTIFY_SUCCESS = 'success';
+    const NOTIFY_WARNING = 'warning';
+
+    /**
+     * A notification of level 'info'.
+     */
+    const NOTIFY_INFO = 'info';
+
+    /**
+     * A notification of level 'error'.
+     */
+    const NOTIFY_ERROR = 'error';
+
     /**
+     * @deprecated
+     * A generic message.
+     */
+    const NOTIFY_MESSAGE = 'message';
+
+    /**
+     * @deprecated
      * A message notifying the user that a problem occurred.
      */
     const NOTIFY_PROBLEM = 'problem';
+
     /**
-     * A message to display during a redirect..
+     * @deprecated
+     * A notification of level 'redirect'.
      */
     const NOTIFY_REDIRECT = 'redirect';
 
     /**
      * @var string Message payload.
      */
-    private $message = '';
+    protected $message = '';
 
     /**
      * @var string Message type.
      */
-    private $messagetype = self::NOTIFY_PROBLEM;
+    protected $messagetype = self::NOTIFY_WARNING;
+
+    /**
+     * @var bool $announce Whether this notification should be announced assertively to screen readers.
+     */
+    protected $announce = true;
+
+    /**
+     * @var bool $closebutton Whether this notification should inlcude a button to dismiss itself.
+     */
+    protected $closebutton = true;
+
+    /**
+     * @var array $extraclasses A list of any extra classes that may be required.
+     */
+    protected $extraclasses = array();
 
     /**
      * Notification constructor.
@@ -69,11 +104,57 @@ class notification implements \renderable, \templatable {
      * @param string $message the message to print out
      * @param string $messagetype normally NOTIFY_PROBLEM or NOTIFY_SUCCESS.
      */
-    public function __construct($message, $messagetype = self::NOTIFY_PROBLEM) {
+    public function __construct($message, $messagetype = null) {
+        $this->message = $message;
+
+        if (empty($messagetype)) {
+            $messagetype = self::NOTIFY_ERROR;
+        }
 
-        $this->message = clean_text($message);
         $this->messagetype = $messagetype;
 
+        switch ($messagetype) {
+            case self::NOTIFY_PROBLEM:
+            case self::NOTIFY_REDIRECT:
+            case self::NOTIFY_MESSAGE:
+                debugging('Use of ' . $messagetype . ' has been deprecated. Please switch to an alternative type.');
+        }
+    }
+
+    /**
+     * Set whether this notification should be announced assertively to screen readers.
+     *
+     * @param bool $announce
+     * @return $this
+     */
+    public function set_announce($announce = false) {
+        $this->announce = (bool) $announce;
+
+        return $this;
+    }
+
+    /**
+     * Set whether this notification should include a button to disiss itself.
+     *
+     * @param bool $button
+     * @return $this
+     */
+    public function set_show_closebutton($button = false) {
+        $this->closebutton = (bool) $button;
+
+        return $this;
+    }
+
+    /**
+     * Add any extra classes that this notification requires.
+     *
+     * @param array $classes
+     * @return $this
+     */
+    public function set_extra_classes($classes = array()) {
+        $this->extraclasses = $classes;
+
+        return $this;
     }
 
     /**
@@ -83,12 +164,26 @@ class notification implements \renderable, \templatable {
      * @return stdClass data context for a mustache template
      */
     public function export_for_template(\renderer_base $output) {
+        return array(
+            'message'       => clean_text($this->message),
+            'extraclasses'  => implode(' ', $this->extraclasses),
+            'announce'      => $this->announce,
+            'closebutton'   => $this->closebutton,
+        );
+    }
 
-        $data = new stdClass();
-
-        $data->type = $this->messagetype;
-        $data->message = $this->message;
+    public function get_template_name() {
+        $templatemappings = [
+            // Current types mapped to template names.
+            'success'           => 'core/notification_success',
+            'info'              => 'core/notification_info',
+            'warning'           => 'core/notification_warning',
+            'error'             => 'core/notification_error',
+        ];
 
-        return $data;
+        if (isset($templatemappings[$this->messagetype])) {
+            return $templatemappings[$this->messagetype];
+        }
+        return $templatemappings['error'];
     }
 }
index d565a22..4dd205e 100644 (file)
@@ -157,10 +157,18 @@ class manager {
     public static function init_empty_session() {
         global $CFG;
 
+        // Backup notifications. These should be preserved across session changes until the user fetches and clears them.
+        $notifications = [];
+        if (isset($GLOBALS['SESSION']->notifications)) {
+            $notifications = $GLOBALS['SESSION']->notifications;
+        }
         $GLOBALS['SESSION'] = new \stdClass();
 
         $GLOBALS['USER'] = new \stdClass();
         $GLOBALS['USER']->id = 0;
+
+        // Restore notifications.
+        $GLOBALS['SESSION']->notifications = $notifications;
         if (isset($CFG->mnet_localhost_id)) {
             $GLOBALS['USER']->mnethostid = $CFG->mnet_localhost_id;
         } else {
index 2c3835f..f1fd1d3 100644 (file)
@@ -31,22 +31,22 @@ $definitions = array(
     // Used to store processed lang files.
     // The keys used are the revision, lang and component of the string file.
     // The static acceleration size has been based upon student access of the site.
-    // NOTE: this data may be safely stored in local caches on cluster nodes.
     'string' => array(
         'mode' => cache_store::MODE_APPLICATION,
         'simplekeys' => true,
         'simpledata' => true,
         'staticacceleration' => true,
-        'staticaccelerationsize' => 30
+        'staticaccelerationsize' => 30,
+        'canuselocalstore' => true,
     ),
 
     // Used to store cache of all available translations.
-    // NOTE: this data may be safely stored in local caches on cluster nodes.
     'langmenu' => array(
         'mode' => cache_store::MODE_APPLICATION,
         'simplekeys' => true,
         'simpledata' => true,
         'staticacceleration' => true,
+        'canuselocalstore' => true,
     ),
 
     // Used to store database meta information.
@@ -58,6 +58,7 @@ $definitions = array(
         'requireidentifiers' => array(
             'dbfamily'
         ),
+        'simpledata' => true, // This is a read only class, so leaving references in place is safe.
         'staticacceleration' => true,
         'staticaccelerationsize' => 15
     ),
@@ -91,9 +92,9 @@ $definitions = array(
     // This caches the html purifier cleaned text. This is done because the text is usually cleaned once for every user
     // and context combo. Text caching handles caching for the combination, this cache is responsible for caching the
     // cleaned text which is shareable.
-    // NOTE: this data may be safely stored in local caches on cluster nodes.
     'htmlpurifier' => array(
         'mode' => cache_store::MODE_APPLICATION,
+        'canuselocalstore' => true,
     ),
 
     // Used to store data from the config + config_plugins table in the database.
@@ -206,6 +207,7 @@ $definitions = array(
     'coursemodinfo' => array(
         'mode' => cache_store::MODE_APPLICATION,
         'simplekeys' => true,
+        'canuselocalstore' => true,
     ),
     // This is the session user selections cache.
     // It's a special cache that is used to record user selections that should persist for the lifetime of the session.
index 25c193e..fd42f35 100644 (file)
@@ -1067,7 +1067,17 @@ $functions = array(
         'description' => 'Generic service to update title',
         'type'        => 'write',
         'loginrequired' => true,
-        'ajax'        => true
+        'ajax'        => true,
+    ),
+
+    'core_fetch_notifications' => array(
+        'classname'   => 'core_external',
+        'methodname'  => 'fetch_notifications',
+        'classpath'   => 'lib/external/externallib.php',
+        'description' => 'Return a list of notifications for the current session',
+        'type'        => 'read',
+        'loginrequired' => false,
+        'ajax'        => true,
     ),
 
     // === Calendar related functions ===
index dc6e1b7..8e50fe1 100644 (file)
@@ -226,11 +226,10 @@ abstract class sql_generator {
             $tablename = $table->getName();
         }
 
-        // get all tables in moodle database
+        // Get all tables in moodle database.
         $tables = $this->mdb->get_tables();
-        $exists = in_array($tablename, $tables);
 
-        return $exists;
+        return isset($tables[$tablename]);
     }
 
     /**
index 6222822..f71453c 100644 (file)
@@ -912,14 +912,14 @@ function print_container_end($return=false) {
  * @param bool $return whether to return an output string or echo now
  * @return string|bool Depending on $result
  */
-function notify($message, $classes = 'notifyproblem', $align = 'center', $return = false) {
+function notify($message, $classes = 'error', $align = 'center', $return = false) {
     global $OUTPUT;
 
     debugging('notify() is deprecated, please use $OUTPUT->notification() instead', DEBUG_DEVELOPER);
 
     if ($classes == 'green') {
-        debugging('Use of deprecated class name "green" in notify. Please change to "notifysuccess".', DEBUG_DEVELOPER);
-        $classes = 'notifysuccess'; // Backward compatible with old color system
+        debugging('Use of deprecated class name "green" in notify. Please change to "success".', DEBUG_DEVELOPER);
+        $classes = 'success'; // Backward compatible with old color system.
     }
 
     $output = $OUTPUT->notification($message, $classes);
index 4aee79a..c58979e 100644 (file)
@@ -33,128 +33,110 @@ defined('MOODLE_INTERNAL') || die();
  * @package    core_dml
  * @copyright  2008 Petr Skoda (http://skodak.org)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ *
+ * @property-read string $name Name of column - lowercase.
+ * @property-read string $type Driver dependent native data type. Not standardised, it's used to find meta_type.
+ *
+ * Max length:
+ *  character type - number of characters
+ *  blob - number of bytes
+ *  integer - number of digits
+ *  float - digits left from floating point
+ *  boolean - 1
+ * @property-read int    $max_length size of the database field, eg how much data can you put in there.
+ *
+ * @property-read int    $scale Scale of field, decimal points (float), null otherwise.
+ * @property-read bool   $not_null true if the field is set to NOT NULL.
+ * @property-read bool   $primary_key true if the field is the primary key. (usually 'id').
+ * @property-read bool   $auto_increment True if field is autoincrementing or sequence.
+ * @property-read bool   $binary True if the field is binary.
+ * @property-read bool   $has_default True if the default value is defined.
+ * @property-read string $default_value The default value (if defined).
+ * @property-read bool   $unique True if the field values are unique, false if not.
+ *
+ * Standardised one character column type, uppercased and enumerated as follows:
+ * R - counter (integer primary key)
+ * I - integers
+ * N - numbers (floats)
+ * C - characters and strings
+ * X - texts
+ * B - binary blobs
+ * L - boolean (1 bit)
+ * T - timestamp - unsupported
+ * D - date - unsupported
+ * @property-read string $meta_type Standardised one character column type, uppercased and enumerated: R,I,N,C,X,B,L,T,D
  */
 class database_column_info {
-    /**
-     * Name of column - lowercase.
-     * @var string
-     */
-    public $name;
-
-    /**
-     * Driver dependent native data type.
-     * Not standardised, its used to find meta_type.
-     * @var string
-     */
-    public $type;
-
-    /**
-     * Max length:
-     *  character type - number of characters
-     *  blob - number of bytes
-     *  integer - number of digits
-     *  float - digits left from floating point
-     *  boolean - 1
-     * @var int
-     */
-    public $max_length;
-
-    /**
-     * Scale
-     * float - decimal points
-     * other - null
-     * @var int
-     */
-    public $scale;
-
-    /**
-     * True if not null, false otherwise
-     * @var bool
-     */
-    public $not_null;
 
     /**
-     * True if column is primary key.
-     * (usually 'id').
-     * @var bool
+     * @var array The internal storage of column data.
      */
-    public $primary_key;
+    protected $data;
 
     /**
-     * True if filed autoincrementing
-     * (usually 'id' only)
-     * @var bool
+     * Magic set function.  This is a read only object and you aren't allowed to write to any variables.
+     *
+     * @param string $name ignored.
+     * @param mixed $value ignored.
+     * @throws coding_exception You are not allowed to set data on database_column_info
      */
-    public $auto_increment;
-
-    /**
-     * True if binary
-     * @var bool
-     */
-    public $binary;
-
-    /**
-     * True if integer unsigned, false if signed.
-     * Null for other types
-     * @var integer
-     * @deprecated since 2.3
-     */
-    public $unsigned;
-
-    /**
-     * True if the default value is defined.
-     * @var bool
-     */
-    public $has_default;
-
-    /**
-     * The default value (if defined).
-     * @var string
-     */
-    public $default_value;
+    public function __set($name, $value) {
+        throw new coding_exception('database_column_info is a ready only object to allow for faster caching.');
+    }
 
     /**
-     * True if field values are unique, false if not.
-     * @var bool
+     * Magic get function.
+     *
+     * @param string $variablename variable name to return the value of.
+     * @return mixed The variable contents.
+     *
+     * @throws coding_exception You cannot request a variable that is not allowed.
      */
-    public $unique;
+    public function __get($variablename) {
+        if (isset($this->data[$variablename]) || array_key_exists($variablename, $this->data)) {
+            return $this->data[$variablename];
+        }
+        throw new coding_exception('Asked for a variable that is not available . ('.$variablename.').');
+    }
 
     /**
-     * Standardised one character column type, uppercased and enumerated as follows:
-     * R - counter (integer primary key)
-     * I - integers
-     * N - numbers (floats)
-     * C - characters and strings
-     * X - texts
-     * B - binary blobs
-     * L - boolean (1 bit)
-     * T - timestamp - unsupported
-     * D - date - unsupported
-     * @var string
+     * Magic isset function.
+     *
+     * @param string $variablename The name of the property to test if isset().
+     * @return bool Whether the value is set or not.
      */
-    public $meta_type;
+    public function __isset($variablename) {
+        return isset($this->data[$variablename]);
+    }
 
     /**
      * Constructor
+     *
      * @param mixed $data object or array with properties
      */
     public function __construct($data) {
-        foreach ($data as $key=>$value) {
-            if (property_exists($this, $key)) {
-                $this->$key = $value;
+        // Initialize all the allowed variables to null so the array key exists.
+        $validelements = array('name', 'type', 'max_length', 'scale', 'not_null', 'primary_key',
+                               'auto_increment', 'binary', 'has_default',  'default_value',
+                               'unique', 'meta_type');
+        foreach ($validelements as $element) {
+            if (isset($data->$element)) {
+                $this->data[$element] = $data->$element;
+            } else {
+                $this->data[$element] = null;
             }
         }
 
-        switch ($this->meta_type) {
+        switch ($this->data['meta_type']) {
             case 'R': // normalise counters (usually 'id')
-                $this->binary         = false;
-                $this->has_default    = false;
-                $this->default_value  = null;
-                $this->unique         = true;
+                $this->data['binary']         = false;
+                $this->data['has_default']    = false;
+                $this->data['default_value']  = null;
+                $this->data['unique']         = true;
                 break;
             case 'C':
-                $this->auto_increment = false;
-                $this->binary         = false;
+                $this->data['auto_increment'] = false;
+                $this->data['binary']         = false;
                 break;
         }
     }
index ed9ab8e..1573ef5 100644 (file)
@@ -324,6 +324,18 @@ abstract class moodle_database {
         return $this->settingshash;
     }
 
+    /**
+     * Handle the creation and caching of the databasemeta information for all databases.
+     *
+     * TODO MDL-53267 impelement caching of cache::make() results when it's safe to do so.
+     *
+     * @return cache_application The databasemeta cachestore to complete operations on.
+     */
+    protected function get_metacache() {
+        $properties = array('dbfamily' => $this->get_dbfamily(), 'settings' => $this->get_settings_hash());
+        return cache::make('core', 'databasemeta', $properties);
+    }
+
     /**
      * Attempt to create the database
      * @param string $dbhost The database host.
@@ -1032,9 +1044,9 @@ abstract class moodle_database {
      */
     public function reset_caches() {
         $this->tables = null;
-        // Purge MUC as well
-        $identifiers = array('dbfamily' => $this->get_dbfamily(), 'settings' => $this->get_settings_hash());
-        cache_helper::purge_by_definition('core', 'databasemeta', $identifiers);
+        // Purge MUC as well.
+        $this->get_metacache()->purge();
+        $this->metacache = null;
     }
 
     /**
index 8a586fe..9b94da3 100644 (file)
@@ -90,7 +90,7 @@ class moodle_temptables {
      * @return array containing all the tablenames in the store (tablename both key and value)
      */
     public function get_temptables() {
-        return array_keys($this->temptables);
+        return $this->temptables;
     }
 
     /**
index ba6dcf6..6c9506c 100644 (file)
@@ -438,9 +438,7 @@ class mssql_native_moodle_database extends moodle_database {
     public function get_columns($table, $usecache=true) {
 
         if ($usecache) {
-            $properties = array('dbfamily' => $this->get_dbfamily(), 'settings' => $this->get_settings_hash());
-            $cache = cache::make('core', 'databasemeta', $properties);
-            if ($data = $cache->get($table)) {
+            if ($data = $this->get_metacache()->get($table)) {
                 return $data;
             }
         }
@@ -536,7 +534,7 @@ class mssql_native_moodle_database extends moodle_database {
         $this->free_result($result);
 
         if ($usecache) {
-            $cache->set($table, $structure);
+            $this->get_metacache()->set($table, $structure);
         }
 
         return $structure;
index 1874481..32a5db7 100644 (file)
@@ -578,9 +578,7 @@ class mysqli_native_moodle_database extends moodle_database {
     public function get_columns($table, $usecache=true) {
 
         if ($usecache) {
-            $properties = array('dbfamily' => $this->get_dbfamily(), 'settings' => $this->get_settings_hash());
-            $cache = cache::make('core', 'databasemeta', $properties);
-            if ($data = $cache->get($table)) {
+            if ($data = $this->get_metacache()->get($table)) {
                 return $data;
             }
         }
@@ -686,7 +684,7 @@ class mysqli_native_moodle_database extends moodle_database {
         }
 
         if ($usecache) {
-            $cache->set($table, $structure);
+            $this->get_metacache()->set($table, $structure);
         }
 
         return $structure;
index d8f8d67..c19373d 100644 (file)
@@ -470,9 +470,7 @@ class oci_native_moodle_database extends moodle_database {
     public function get_columns($table, $usecache=true) {
 
         if ($usecache) {
-            $properties = array('dbfamily' => $this->get_dbfamily(), 'settings' => $this->get_settings_hash());
-            $cache = cache::make('core', 'databasemeta', $properties);
-            if ($data = $cache->get($table)) {
+            if ($data = $this->get_metacache()->get($table)) {
                 return $data;
             }
         }
@@ -664,7 +662,7 @@ class oci_native_moodle_database extends moodle_database {
         }
 
         if ($usecache) {
-            $cache->set($table, $structure);
+            $this->get_metacache()->set($table, $structure);
         }
 
         return $structure;
index 5052886..21f86e7 100644 (file)
@@ -390,9 +390,7 @@ class pgsql_native_moodle_database extends moodle_database {
      */
     public function get_columns($table, $usecache=true) {
         if ($usecache) {
-            $properties = array('dbfamily' => $this->get_dbfamily(), 'settings' => $this->get_settings_hash());
-            $cache = cache::make('core', 'databasemeta', $properties);
-            if ($data = $cache->get($table)) {
+            if ($data = $this->get_metacache()->get($table)) {
                 return $data;
             }
         }
@@ -596,7 +594,7 @@ class pgsql_native_moodle_database extends moodle_database {
         pg_free_result($result);
 
         if ($usecache) {
-            $cache->set($table, $structure);
+            $this->get_metacache()->set($table, $structure);
         }
 
         return $structure;
index fcfe446..1196d67 100644 (file)
@@ -201,9 +201,7 @@ class sqlite3_pdo_moodle_database extends pdo_moodle_database {
     public function get_columns($table, $usecache=true) {
 
         if ($usecache) {
-            $properties = array('dbfamily' => $this->get_dbfamily(), 'settings' => $this->get_settings_hash());
-            $cache = cache::make('core', 'databasemeta', $properties);
-            if ($data = $cache->get($table)) {
+            if ($data = $this->get_metacache()->get($table)) {
                 return $data;
             }
         }
@@ -300,7 +298,7 @@ class sqlite3_pdo_moodle_database extends pdo_moodle_database {
         }
 
         if ($usecache) {
-            $cache->set($table, $structure);
+            $this->get_metacache()->set($table, $structure);
         }
 
         return $structure;
index 2543336..904e7e3 100644 (file)
@@ -508,9 +508,7 @@ class sqlsrv_native_moodle_database extends moodle_database {
      */
     public function get_columns($table, $usecache = true) {
         if ($usecache) {
-            $properties = array('dbfamily' => $this->get_dbfamily(), 'settings' => $this->get_settings_hash());
-            $cache = cache::make('core', 'databasemeta', $properties);
-            if ($data = $cache->get($table)) {
+            if ($data = $this->get_metacache()->get($table)) {
                 return $data;
             }
         }
@@ -606,7 +604,7 @@ class sqlsrv_native_moodle_database extends moodle_database {
         $this->free_result($result);
 
         if ($usecache) {
-            $cache->set($table, $structure);
+            $this->get_metacache()->set($table, $structure);
         }
 
         return $structure;
index 09ab53f..d5ed2a6 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js differ
index b6ff702..6d77779 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js differ
index bbc68c7..d15b0a7 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js differ
index e90b30a..3f6c460 100644 (file)
@@ -118,7 +118,10 @@ EditorSelection.prototype = {
                 return;
             }
             Y.soon(Y.bind(this._hasSelectionChanged, this, e));
-        }, null, this);
+        }, {
+            // Standalone will make sure all editors receive the end event.
+            standAlone: true
+        }, this);
 
         return this;
     },
index 426c9a2..764dc9e 100644 (file)
@@ -1539,6 +1539,44 @@ abstract class enrol_plugin {
         // override if necessary
     }
 
+    /**
+     * This returns false for backwards compatibility, but it is really recommended.
+     *
+     * @since Moodle 3.1
+     * @return boolean
+     */
+    public function use_standard_editing_ui() {
+        return false;
+    }
+
+    /**
+     * Return whether or not, given the current state, it is possible to add a new instance
+     * of this enrolment plugin to the course.
+     *
+     * Default implementation is just for backwards compatibility.
+     *
+     * @param int $courseid
+     * @return boolean
+     */
+    public function can_add_instance($courseid) {
+        $link = $this->get_newinstance_link($courseid);
+        return !empty($link);
+    }
+
+    /**
+     * Return whether or not, given the current state, it is possible to edit an instance
+     * of this enrolment plugin in the course. Used by the standard editing UI
+     * to generate a link to the edit instance form if editing is allowed.
+     *
+     * @param stdClass $instance
+     * @return boolean
+     */
+    public function can_edit_instance($instance) {
+        $context = context_course::instance($instance->courseid);
+
+        return has_capability('enrol/' . $instance->enrol . ':config', $context);
+    }
+
     /**
      * Returns link to page which may be used to add new instance of enrolment plugin in course.
      * @param int $courseid
@@ -1645,6 +1683,36 @@ abstract class enrol_plugin {
         // override - usually at least enable/disable switch, has to add own form header
     }
 
+    /**
+     * Adds form elements to add/edit instance form.
+     *
+     * @since Moodle 3.1
+     * @param object $instance enrol instance or null if does not exist yet
+     * @param MoodleQuickForm $mform
+     * @param context $context
+     * @return void
+     */
+    public function edit_instance_form($instance, MoodleQuickForm $mform, $context) {
+        // Do nothing by default.
+    }
+
+    /**
+     * Perform custom validation of the data used to edit the instance.
+     *
+     * @since Moodle 3.1
+     * @param array $data array of ("fieldname"=>value) of submitted data
+     * @param array $files array of uploaded files "element_name"=>tmp_file_path
+     * @param object $instance The instance data loaded from the DB.
+     * @param context $context The context of the instance we are editing
+     * @return array of "element_name"=>"error_description" if there are errors,
+     *         or an empty array if everything is OK.
+     */
+    public function edit_instance_validation($data, $files, $instance, $context) {
+        // No errors by default.
+        debugging('enrol_plugin::edit_instance_validation() is missing. This plugin has no validation!', DEBUG_DEVELOPER);
+        return array();
+    }
+
     /**
      * Validates course edit form data
      *
@@ -1711,6 +1779,37 @@ abstract class enrol_plugin {
         return $instance->id;
     }
 
+    /**
+     * Update instance of enrol plugin.
+     *
+     * @since Moodle 3.1
+     * @param stdClass $instance
+     * @param stdClass $data modified instance fields
+     * @return boolean
+     */
+    public function update_instance($instance, $data) {
+        global $DB;
+        $properties = array('status', 'name', 'password', 'customint1', 'customint2', 'customint3',
+                            'customint4', 'customint5', 'customint6', 'customint7', 'customint8',
+                            'customchar1', 'customchar2', 'customchar3', 'customdec1', 'customdec2',
+                            'customtext1', 'customtext2', 'customtext3', 'customtext4', 'roleid',
+                            'enrolperiod', 'expirynotify', 'notifyall', 'expirythreshold',
+                            'enrolstartdate', 'enrolenddate', 'cost', 'currency');
+
+        foreach ($properties as $key) {
+            if (isset($data->$key)) {
+                $instance->$key = $data->$key;
+            }
+        }
+        $instance->timemodified = time();
+
+        $update = $DB->update_record('enrol', $instance);
+        if ($update) {
+            \core\event\enrol_instance_updated::create_from_record($instance)->trigger();
+        }
+        return $update;
+    }
+
     /**
      * Add new instance of enrol plugin with default settings,
      * called when adding new instance manually or when adding new course.
@@ -1825,7 +1924,15 @@ abstract class enrol_plugin {
      * @return void
      */
     public function add_course_navigation($instancesnode, stdClass $instance) {
-        // usually adds manage users
+        if ($this->use_standard_editing_ui()) {
+            $context = context_course::instance($instance->courseid);
+            $cap = 'enrol/' . $instance->enrol . ':config';
+            if (has_capability($cap, $context)) {
+                $linkparams = array('courseid' => $instance->courseid, 'id' => $instance->id, 'type' => $instance->enrol);
+                $managelink = new moodle_url('/enrol/editinstance.php', $linkparams);
+                $instancesnode->add($this->get_instance_name($instance), $managelink, navigation_node::TYPE_SETTING);
+            }
+        }
     }
 
     /**
@@ -1834,7 +1941,16 @@ abstract class enrol_plugin {
      * @return array
      */
     public function get_action_icons(stdClass $instance) {
-        return array();
+        global $OUTPUT;
+
+        $icons = array();
+        if ($this->use_standard_editing_ui()) {
+            $linkparams = array('courseid' => $instance->courseid, 'id' => $instance->id, 'type' => $instance->enrol);
+            $editlink = new moodle_url("/enrol/editinstance.php", $linkparams);
+            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
+                array('class' => 'iconsmall')));
+        }
+        return $icons;
     }
 
     /**
@@ -2359,4 +2475,39 @@ abstract class enrol_plugin {
         // Implement if you want to restore protected group memberships,
         // usually this is not necessary because plugins should be able to recreate the memberships automatically.
     }
+
+    /**
+     * Returns defaults for new instances.
+     * @since Moodle 3.1
+     * @return array
+     */
+    public function get_instance_defaults() {
+        return array();
+    }
+
+    /**
+     * Validate a list of parameter names and types.
+     * @since Moodle 3.1
+     *
+     * @param array $data array of ("fieldname"=>value) of submitted data
+     * @param array $rules array of ("fieldname"=>PARAM_X types - or "fieldname"=>array( list of valid options )
+     * @return array of "element_name"=>"error_description" if there are errors,
+     *         or an empty array if everything is OK.
+     */
+    public function validate_param_types($data, $rules) {
+        $errors = array();
+        $invalidstr = get_string('invaliddata', 'error');
+        foreach ($rules as $fieldname => $rule) {
+            if (is_array($rule)) {
+                if (!in_array($data[$fieldname], $rule)) {
+                    $errors[$fieldname] = $invalidstr;
+                }
+            } else {
+                if ($data[$fieldname] != clean_param($data[$fieldname], $rule)) {
+                    $errors[$fieldname] = $invalidstr;
+                }
+            }
+        }
+        return $errors;
+    }
 }
index 7c38b77..44b75e1 100644 (file)
@@ -407,4 +407,58 @@ class core_external extends external_api {
             )
         );
     }
+
+    /**
+     * Returns description of fetch_notifications() parameters.
+     *
+     * @return external_function_parameters
+     * @since Moodle 3.1
+     */
+    public static function fetch_notifications_parameters() {
+        return new external_function_parameters(
+            array(
+                'contextid' => new external_value(PARAM_INT, 'Context ID', VALUE_REQUIRED),
+            ));
+    }
+
+    /**
+     * Returns description of fetch_notifications() result value.
+     *
+     * @return external_description
+     * @since Moodle 3.1
+     */
+    public static function fetch_notifications_returns() {
+        return new external_multiple_structure(
+            new external_single_structure(
+                array(
+                    'template'      => new external_value(PARAM_RAW, 'Name of the template'),
+                    'variables'     => new external_single_structure(array(
+                        'message'       => new external_value(PARAM_RAW, 'HTML content of the Notification'),
+                        'extraclasses'  => new external_value(PARAM_RAW, 'Extra classes to provide to the tmeplate'),
+                        'announce'      => new external_value(PARAM_RAW, 'Whether to announce'),
+                        'closebutton'   => new external_value(PARAM_RAW, 'Whether to close'),
+                    )),
+                )
+            )
+        );
+    }
+
+    /**
+     * Returns the list of notifications against the current session.
+     *
+     * @return array
+     * @since Moodle 3.1
+     */
+    public static function fetch_notifications($contextid) {
+        global $PAGE;
+
+        self::validate_parameters(self::fetch_notifications_parameters(), [
+                'contextid' => $contextid,
+            ]);
+
+        $context = \context::instance_by_id($contextid);
+        $PAGE->set_context($context);
+
+        return \core\notification::fetch_as_array($PAGE->get_renderer('core'));
+    }
 }
index 7b36416..393769d 100644 (file)
@@ -1177,7 +1177,7 @@ class core_media_player_html5audio extends core_media_player {
         $fallback = core_media_player::PLACEHOLDER;
 
         return <<<OET
-<audio controls="true" $size class="mediaplugin mediaplugin_html5audio" preload="no" title="$title">
+<audio controls="true" $size class="mediaplugin mediaplugin_html5audio" preload="none" title="$title">
 $sources
 $fallback
 </audio>
index 066f82e..b8a655e 100644 (file)
@@ -871,10 +871,13 @@ class core_renderer extends renderer_base {
      * @param boolean $debugdisableredirect this redirect has been disabled for
      *         debugging purposes. Display a message that explains, and don't
      *         trigger the redirect.
+     * @param string $messagetype The type of notification to show the message in.
+     *         See constants on \core\output\notification.
      * @return string The HTML to display to the user before dying, may contain
      *         meta refresh, javascript refresh, and may have set header redirects
      */
-    public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect) {
+    public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect,
+                                     $messagetype = \core\output\notification::NOTIFY_INFO) {
         global $CFG;
         $url = str_replace('&amp;', '&', $encodedurl);
 
@@ -905,7 +908,7 @@ class core_renderer extends renderer_base {
                 throw new coding_exception('You cannot redirect after the entire page has been generated');
                 break;
         }
-        $output .= $this->notification($message, 'redirectmessage');
+        $output .= $this->notification($message, $messagetype);
         $output .= '<div class="continuebutton">(<a href="'. $encodedurl .'">'. get_string('continue') .'</a>)</div>';
         if ($debugdisableredirect) {
             $output .= '<p><strong>'.get_string('erroroutput', 'error').'</strong></p>';
@@ -1032,7 +1035,7 @@ class core_renderer extends renderer_base {
      * @return string HTML fragment
      */
     public function footer() {
-        global $CFG, $DB;
+        global $CFG, $DB, $PAGE;
 
         $output = $this->container_end_all(true);
 
@@ -1057,6 +1060,7 @@ class core_renderer extends renderer_base {
         }
         $footer = str_replace($this->unique_performance_info_token, $performanceinfo, $footer);
 
+        $this->page->requires->js_call_amd('core/notification', 'init', array($PAGE->context->id, \core\notification::fetch_as_array($this)));
         $footer = str_replace($this->unique_end_html_token, $this->page->requires->get_end_code(), $footer);
 
         $this->page->set_state(moodle_page::STATE_DONE);
@@ -1086,22 +1090,37 @@ class core_renderer extends renderer_base {
      */
     public function course_content_header($onlyifnotcalledbefore = false) {
         global $CFG;
-        if ($this->page->course->id == SITEID) {
-            // return immediately and do not include /course/lib.php if not necessary
-            return '';
-        }
         static $functioncalled = false;
         if ($functioncalled && $onlyifnotcalledbefore) {
             // we have already output the content header
             return '';
         }
+
+        // Output any session notification.
+        $notifications = \core\notification::fetch();
+
+        $bodynotifications = '';
+        foreach ($notifications as $notification) {
+            $bodynotifications .= $this->render_from_template(
+                    $notification->get_template_name(),
+                    $notification->export_for_template($this)
+                );
+        }
+
+        $output = html_writer::span($bodynotifications, 'notifications', array('id' => 'user-notifications'));
+
+        if ($this->page->course->id == SITEID) {
+            // return immediately and do not include /course/lib.php if not necessary
+            return $output;
+        }
+
         require_once($CFG->dirroot.'/course/lib.php');
         $functioncalled = true;
         $courseformat = course_get_format($this->page->course);
         if (($obj = $courseformat->course_content_header()) !== null) {
-            return html_writer::div($courseformat->get_renderer($this->page)->render($obj), 'course-content-header');
+            $output .= html_writer::div($courseformat->get_renderer($this->page)->render($obj), 'course-content-header');
         }
-        return '';
+        return $output;
     }
 
     /**
@@ -2778,38 +2797,65 @@ EOD;
     }
 
     /**
-     * Output a notification (that is, a status message about something that has
-     * just happened).
+     * Output a notification (that is, a status message about something that has just happened).
      *
-     * @param string $message the message to print out
-     * @param string $classes normally 'notifyproblem' or 'notifysuccess'.
+     * Note: \core\notification::add() may be more suitable for your usage.
+     *
+     * @param string $message The message to print out.
+     * @param string $type    The type of notification. See constants on \core\output\notification.
      * @return string the HTML to output.
      */
-    public function notification($message, $classes = 'notifyproblem') {
-
-        $classmappings = array(
-            'notifyproblem' => \core\output\notification::NOTIFY_PROBLEM,
-            'notifytiny' => \core\output\notification::NOTIFY_PROBLEM,
-            'notifysuccess' => \core\output\notification::NOTIFY_SUCCESS,
-            'notifymessage' => \core\output\notification::NOTIFY_MESSAGE,
-            'redirectmessage' => \core\output\notification::NOTIFY_REDIRECT
-        );
-
-        // Identify what type of notification this is.
-        $type = \core\output\notification::NOTIFY_PROBLEM;
-        $classarray = explode(' ', self::prepare_classes($classes));
-        if (count($classarray) > 0) {
-            foreach ($classarray as $class) {
-                if (isset($classmappings[$class])) {
-                    $type = $classmappings[$class];
-                    break;
+    public function notification($message, $type = null) {
+        $typemappings = [
+            // Valid types.
+            'success'           => \core\output\notification::NOTIFY_SUCCESS,
+            'info'              => \core\output\notification::NOTIFY_INFO,
+            'warning'           => \core\output\notification::NOTIFY_WARNING,
+            'error'             => \core\output\notification::NOTIFY_ERROR,
+
+            // Legacy types mapped to current types.
+            'notifyproblem'     => \core\output\notification::NOTIFY_ERROR,
+            'notifytiny'        => \core\output\notification::NOTIFY_ERROR,
+            'notifyerror'       => \core\output\notification::NOTIFY_ERROR,
+            'notifysuccess'     => \core\output\notification::NOTIFY_SUCCESS,
+            'notifymessage'     => \core\output\notification::NOTIFY_INFO,
+            'notifyredirect'    => \core\output\notification::NOTIFY_INFO,
+            'redirectmessage'   => \core\output\notification::NOTIFY_INFO,
+        ];
+
+        $extraclasses = [];
+
+        if ($type) {
+            if (strpos($type, ' ') === false) {
+                // No spaces in the list of classes, therefore no need to loop over and determine the class.
+                if (isset($typemappings[$type])) {
+                    $type = $typemappings[$type];
+                } else {
+                    // The value provided did not match a known type. It must be an extra class.
+                    $extraclasses = [$type];
+                }
+            } else {
+                // Identify what type of notification this is.
+                $classarray = explode(' ', self::prepare_classes($type));
+
+                // Separate out the type of notification from the extra classes.
+                foreach ($classarray as $class) {
+                    if (isset($typemappings[$class])) {
+                        $type = $typemappings[$class];
+                    } else {
+                        $extraclasses[] = $class;
+                    }
                 }
             }
         }
 
-        $n = new \core\output\notification($message, $type);
-        return $this->render($n);
+        $notification = new \core\output\notification($message, $type);
+        if (count($extraclasses)) {
+            $notification->set_extra_classes($extraclasses);
+        }
 
+        // Return the rendered template.
+        return $this->render_from_template($notification->get_template_name(), $notification->export_for_template($this));
     }
 
     /**
@@ -2817,9 +2863,15 @@ EOD;
      *
      * @param string $message the message to print out
      * @return string HTML fragment.
+     * @deprecated since Moodle 3.1 MDL-30811 - please do not use this function any more.
+     * @todo MDL-53113 This will be removed in Moodle 3.5.
+     * @see \core\output\notification
      */
     public function notify_problem($message) {
-        $n = new \core\output\notification($message, \core\output\notification::NOTIFY_PROBLEM);
+        debugging(__FUNCTION__ . ' is deprecated.' .
+            'Please use \core\notification::add, or \core\output\notification as required',
+            DEBUG_DEVELOPER);
+        $n = new \core\output\notification($message, \core\output\notification::NOTIFY_ERROR);
         return $this->render($n);
     }
 
@@ -2828,8 +2880,14 @@ EOD;
      *
      * @param string $message the message to print out
      * @return string HTML fragment.
+     * @deprecated since Moodle 3.1 MDL-30811 - please do not use this function any more.
+     * @todo MDL-53113 This will be removed in Moodle 3.5.
+     * @see \core\output\notification
      */
     public function notify_success($message) {
+        debugging(__FUNCTION__ . ' is deprecated.' .
+            'Please use \core\notification::add, or \core\output\notification as required',
+            DEBUG_DEVELOPER);
         $n = new \core\output\notification($message, \core\output\notification::NOTIFY_SUCCESS);
         return $this->render($n);
     }
@@ -2839,9 +2897,15 @@ EOD;
      *
      * @param string $message the message to print out
      * @return string HTML fragment.
+     * @deprecated since Moodle 3.1 MDL-30811 - please do not use this function any more.
+     * @todo MDL-53113 This will be removed in Moodle 3.5.
+     * @see \core\output\notification
      */
     public function notify_message($message) {
-        $n = new \core\output\notification($message, \core\output\notification::NOTIFY_MESSAGE);
+        debugging(__FUNCTION__ . ' is deprecated.' .
+            'Please use \core\notification::add, or \core\output\notification as required',
+            DEBUG_DEVELOPER);
+        $n = new \core\output\notification($message, \core\output\notification::NOTIFY_INFO);
         return $this->render($n);
     }
 
@@ -2850,9 +2914,15 @@ EOD;
      *
      * @param string $message the message to print out
      * @return string HTML fragment.
+     * @deprecated since Moodle 3.1 MDL-30811 - please do not use this function any more.
+     * @todo MDL-53113 This will be removed in Moodle 3.5.
+     * @see \core\output\notification
      */
     public function notify_redirect($message) {
-        $n = new \core\output\notification($message, \core\output\notification::NOTIFY_REDIRECT);
+        debugging(__FUNCTION__ . ' is deprecated.' .
+            'Please use \core\notification::add, or \core\output\notification as required',
+            DEBUG_DEVELOPER);
+        $n = new \core\output\notification($message, \core\output\notification::NOTIFY_INFO);
         return $this->render($n);
     }
 
@@ -2864,30 +2934,7 @@ EOD;
      * @return string the HTML to output.
      */
     protected function render_notification(\core\output\notification $notification) {
-
-        $data = $notification->export_for_template($this);
-
-        $templatename = '';
-        switch($data->type) {
-            case \core\output\notification::NOTIFY_MESSAGE:
-                $templatename = 'core/notification_message';
-                break;
-            case \core\output\notification::NOTIFY_SUCCESS:
-                $templatename = 'core/notification_success';
-                break;
-            case \core\output\notification::NOTIFY_PROBLEM:
-                $templatename = 'core/notification_problem';
-                break;
-            case \core\output\notification::NOTIFY_REDIRECT:
-                $templatename = 'core/notification_redirect';
-                break;
-            default:
-                $templatename = 'core/notification_message';
-                break;
-        }
-
-        return self::render_from_template($templatename, $data);
-
+        return $this->render_from_template($notification->get_template_name(), $notification->export_for_template($this));
     }
 
     /**
@@ -4251,13 +4298,13 @@ class core_renderer_cli extends core_renderer {
     /**
      * Returns a template fragment representing a notification.
      *
-     * @param string $message The message to include
-     * @param string $classes A space-separated list of CSS classes
+     * @param string $message The message to print out.
+     * @param string $type    The type of notification. See constants on \core\output\notification.
      * @return string A template fragment for a notification
      */
-    public function notification($message, $classes = 'notifyproblem') {
+    public function notification($message, $type = null) {
         $message = clean_text($message);
-        if ($classes === 'notifysuccess') {
+        if ($type === 'notifysuccess' || $type === 'success') {
             return "++ $message ++\n";
         }
         return "!! $message !!\n";
@@ -4325,10 +4372,10 @@ class core_renderer_ajax extends core_renderer {
      * Used to display a notification.
      * For the AJAX notifications are discarded.
      *
-     * @param string $message
-     * @param string $classes
+     * @param string $message The message to print out.
+     * @param string $type    The type of notification. See constants on \core\output\notification.
      */
-    public function notification($message, $classes = 'notifyproblem') {}
+    public function notification($message, $type = null) {}
 
     /**
      * Used to display a redirection message.
@@ -4339,8 +4386,11 @@ class core_renderer_ajax extends core_renderer {
      * @param string $message
      * @param int $delay
      * @param bool $debugdisableredirect
+     * @param string $messagetype The type of notification to show the message in.
+     *         See constants on \core\output\notification.
      */
-    public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect) {}
+    public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect,
+                                     $messagetype = \core\output\notification::NOTIFY_INFO) {}
 
     /**
      * Prepares the start of an AJAX output.
similarity index 57%
rename from lib/templates/notification_redirect.mustache
rename to lib/templates/notification_error.mustache
index 4181bd0..0e7a8c8 100644 (file)
     along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 }}
 {{!
-    @template core/notification_redirect
+    @template core/notification_error
 
     Moodle notification template.
 
-    The purpose of this template is to render a message notification.
+    The purpose of this template is to render an error notification.
 
     Classes required for JS:
     * none
 
     Context variables required for this template:
     * message A cleaned string (use clean_text()) to display.
+    * extraclasses Additional classes to apply to the notification.
+    * closebutton Whether a close button should be displayed to dismiss the message.
+    * announce Whether the notification should be announced to screen readers.
 
     Example context (json):
-    { "message": "Your pants are on fire!" }
+    { "message": "Your pants are on fire!", "closebutton": 1, "announce": 1, "extraclasses": "foo bar"}
 }}
-<div class="alert alert-block alert-info">{{{message}}}</div>
+<div class="alert alert-error alert-block fade in {{ extraclasses }}" {{!
+    }}{{# announce }} aria-live="assertive"{{/ announce }}{{!
+    }}>
+    {{# closebutton }}<button type="button" class="close" data-dismiss="alert">&times;</button>{{/ closebutton }}
+    {{{ message }}}
+</div>
similarity index 57%
rename from lib/templates/notification_message.mustache
rename to lib/templates/notification_info.mustache
index 16d87b7..39cd151 100644 (file)
     along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 }}
 {{!
-    @template core/notification_message
+    @template core/notification_info
 
     Moodle notification template.
 
-    The purpose of this template is to render a message notification.
+    The purpose of this template is to render an info notification.
 
     Classes required for JS:
     * none
 
     Context variables required for this template:
     * message A cleaned string (use clean_text()) to display.
+    * extraclasses Additional classes to apply to the notification.
+    * closebutton Whether a close button should be displayed to dismiss the message.
+    * announce Whether the notification should be announced to screen readers.
 
     Example context (json):
-    { "message": "Your pants are on fire!" }
+    { "message": "Your pants are on fire!", "closebutton": 1, "announce": 1, "extraclasses": "foo bar"}
 }}
-<div class="alert alert-info">{{{message}}}</div>
+<div class="alert alert-info alert-block fade in {{ extraclasses }}" {{!
+    }}{{# announce }} aria-live="assertive"{{/ announce }}{{!
+    }}>
+    {{# closebutton }}<button type="button" class="close" data-dismiss="alert">&times;</button>{{/ closebutton }}
+    {{{ message }}}
+</div>
index 1f806f0..65b7e48 100644 (file)
 
     Context variables required for this template:
     * message A cleaned string (use clean_text()) to display.
+    * extraclasses Additional classes to apply to the notification.
+    * closebutton Whether a close button should be displayed to dismiss the message.
+    * announce Whether the notification should be announced to screen readers.
 
     Example context (json):
-    { "message": "Your pants are on fire!" }
+    { "message": "Your pants are on fire!", "closebutton": 1, "announce": 1, "extraclasses": "foo bar"}
 }}
-<div class="alert alert-success">{{{message}}}</div>
+<div class="alert alert-success alert-block fade in {{ extraclasses }}" {{!
+    }}{{# announce }} aria-live="assertive"{{/ announce }}{{!
+    }}>
+    {{# closebutton }}<button type="button" class="close" data-dismiss="alert">&times;</button>{{/ closebutton }}
+    {{{ message }}}
+</div>
similarity index 57%
rename from lib/templates/notification_problem.mustache
rename to lib/templates/notification_warning.mustache
index 67b6516..b359d83 100644 (file)
     along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 }}
 {{!
-    @template core/notification_problem
+    @template core/notification_warning
 
     Moodle notification template.
 
-    The purpose of this template is to render a problem notification.
+    The purpose of this template is to render a warning notification.
 
     Classes required for JS:
     * none
 
     Context variables required for this template:
     * message A cleaned string (use clean_text()) to display.
+    * extraclasses Additional classes to apply to the notification.
+    * closebutton Whether a close button should be displayed to dismiss the message.
+    * announce Whether the notification should be announced to screen readers.
 
     Example context (json):
-    { "message": "Your pants are on fire!" }
+    { "message": "Your pants are on fire!", "closebutton": 1, "announce": 1, "extraclasses": "foo bar"}
 }}
-<div class="alert alert-error">{{{message}}}</div>
+<div class="alert alert-warning alert-block fade in {{ extraclasses }}" {{!
+    }}{{# announce }} aria-live="assertive"{{/ announce }}{{!
+    }}>
+    {{# closebutton }}<button type="button" class="close" data-dismiss="alert">&times;</button>{{/ closebutton }}
+    {{{ message }}}
+</div>
diff --git a/lib/tests/notification_test.php b/lib/tests/notification_test.php
new file mode 100644 (file)
index 0000000..2cbc131
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for core\notification.
+ *
+ * @package   core
+ * @category  phpunit
+ * @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Unit tests for core\notification.
+ *
+ * @package   core
+ * @category  phpunit
+ * @category  phpunit
+ * @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_notification_testcase extends advanced_testcase {
+
+    /**
+     * Setup required for all notification tests.
+     *
+     * This includes emptying the list of notifications on the session, resetting any session which exists, and setting
+     * up a new moodle_page object.
+     */
+    public function setUp() {
+        global $PAGE, $SESSION;
+
+        parent::setUp();
+        $PAGE = new moodle_page();
+        \core\session\manager::init_empty_session();
+        $SESSION->notifications = [];
+    }
+
+    /**
+     * Tear down required for all notification tests.
+     *
+     * This includes emptying the list of notifications on the session, resetting any session which exists, and setting
+     * up a new moodle_page object.
+     */
+    public function tearDown() {
+        global $PAGE, $SESSION;
+
+        $PAGE = null;
+        \core\session\manager::init_empty_session();
+        $SESSION->notifications = [];
+        parent::tearDown();
+    }
+
+    /**
+     * Test the way in which notifications are added to the session in different stages of the page load.
+     */
+    public function test_add_during_output_stages() {
+        global $PAGE, $SESSION;
+
+        \core\notification::add('Example before header', \core\notification::INFO);
+        $this->assertCount(1, $SESSION->notifications);
+
+        $PAGE->set_state(\moodle_page::STATE_PRINTING_HEADER);
+        \core\notification::add('Example during header', \core\notification::INFO);
+        $this->assertCount(2, $SESSION->notifications);
+
+        $PAGE->set_state(\moodle_page::STATE_IN_BODY);
+        \core\notification::add('Example in body', \core\notification::INFO);
+        $this->expectOutputRegex('/Example in body/');
+        $this->assertCount(2, $SESSION->notifications);
+
+        $PAGE->set_state(\moodle_page::STATE_DONE);
+        \core\notification::add('Example after page', \core\notification::INFO);
+        $this->assertCount(3, $SESSION->notifications);
+    }
+
+    /**
+     * Test fetching of notifications from the session.
+     */
+    public function test_fetch() {
+        // Initially there won't be any notifications.
+        $this->assertCount(0, \core\notification::fetch());
+
+        // Adding a notification should make one available to fetch.
+        \core\notification::success('Notification created');
+        $this->assertCount(1, \core\notification::fetch());
+        $this->assertCount(0, \core\notification::fetch());
+    }
+
+    /**
+     * Test that session notifications are persisted across session clears.
+     */
+    public function test_session_persistance() {
+        global $PAGE, $SESSION;
+
+        // Initially there won't be any notifications.
+        $this->assertCount(0, $SESSION->notifications);
+
+        // Adding a notification should make one available to fetch.
+        \core\notification::success('Notification created');
+        $this->assertCount(1, $SESSION->notifications);
+
+        // Re-creating the session will not empty the notification bag.
+        \core\session\manager::init_empty_session();
+        $this->assertCount(1, $SESSION->notifications);
+    }
+}
index a5a15d9..66a9af1 100644 (file)
@@ -59,7 +59,7 @@ class core_session_manager_testcase extends advanced_testcase {
         \core\session\manager::init_empty_session();
 
         $this->assertInstanceOf('stdClass', $SESSION);
-        $this->assertEmpty((array)$SESSION);
+        $this->assertCount(1, (array)$SESSION);
         $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
         $this->assertSame($GLOBALS['SESSION'], $SESSION);
 
@@ -149,7 +149,7 @@ class core_session_manager_testcase extends advanced_testcase {
         $this->assertEquals(0, $USER->id);
 
         $this->assertInstanceOf('stdClass', $SESSION);
-        $this->assertEmpty((array)$SESSION);
+        $this->assertCount(1, (array)$SESSION);
         $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
         $this->assertSame($GLOBALS['SESSION'], $SESSION);
 
index 6879ae5..891f61b 100644 (file)
@@ -76,7 +76,7 @@ class core_sessionlib_testcase extends advanced_testcase {
         $this->assertSame($PAGE->context, context_course::instance($SITE->id));
         $this->assertNotSame($adminsession, $SESSION);
         $this->assertObjectNotHasAttribute('test1', $SESSION);
-        $this->assertEmpty((array)$SESSION);
+        $this->assertCount(1, (array)$SESSION);
         $usersession1 = $SESSION;
         $SESSION->test2 = true;
         $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
@@ -99,7 +99,7 @@ class core_sessionlib_testcase extends advanced_testcase {
         $this->assertSame($PAGE->context, context_course::instance($SITE->id));
         $this->assertNotSame($adminsession, $SESSION);
         $this->assertNotSame($usersession1, $SESSION);
-        $this->assertEmpty((array)$SESSION);
+        $this->assertCount(1, (array)$SESSION);
         $usersession2 = $SESSION;
         $usersession2->test3 = true;
         $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
@@ -123,7 +123,7 @@ class core_sessionlib_testcase extends advanced_testcase {
         $this->assertSame($PAGE->context, context_course::instance($SITE->id));
         $this->assertNotSame($adminsession, $SESSION);
         $this->assertNotSame($usersession1, $SESSION);
-        $this->assertEmpty((array)$SESSION);
+        $this->assertCount(1, (array)$SESSION);
         $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
         $this->assertSame($GLOBALS['SESSION'], $SESSION);
         $this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
index 68ac67a..c6796a4 100644 (file)
@@ -3,6