Merge branch 'MDL-14908-master' of git://github.com/FMCorz/moodle
authorDan Poltawski <dan@moodle.com>
Mon, 8 Dec 2014 13:22:16 +0000 (13:22 +0000)
committerDan Poltawski <dan@moodle.com>
Mon, 8 Dec 2014 13:22:16 +0000 (13:22 +0000)
283 files changed:
admin/mnet/peer_forms.php
admin/mnet/peers.php
admin/mnet/testclient.php
admin/roles/module.js
admin/settings/appearance.php
admin/settings/security.php
admin/settings/server.php
admin/tool/assignmentupgrade/module.js
admin/tool/health/index.php
admin/tool/health/locallib.php [new file with mode: 0644]
admin/tool/health/tests/healthlib_test.php [new file with mode: 0644]
admin/tool/log/store/legacy/classes/log/store.php
admin/tool/log/store/standard/classes/log/store.php
admin/tool/monitor/tests/eventobservers_test.php
admin/tool/spamcleaner/module.js
admin/tool/xmldb/styles_bootstrapbase.css [new file with mode: 0644]
auth/email/auth.php
auth/ldap/auth.php
auth/upgrade.txt
availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-debug.js
availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-min.js
availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form.js
availability/condition/completion/yui/src/form/js/form.js
availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-debug.js
availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-min.js
availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form.js
availability/condition/date/yui/src/form/js/form.js
availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form-debug.js
availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form-min.js
availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form.js
availability/condition/grade/yui/src/form/js/form.js
availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form-debug.js
availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form-min.js
availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form.js
availability/condition/group/yui/src/form/js/form.js
availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form-debug.js
availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form-min.js
availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form.js
availability/condition/grouping/yui/src/form/js/form.js
availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form-debug.js
availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form-min.js
availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form.js
availability/condition/profile/yui/src/form/js/form.js
availability/tests/behat/edit_availability.feature
availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-debug.js
availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-min.js
availability/yui/build/moodle-core_availability-form/moodle-core_availability-form.js
availability/yui/src/form/js/form.js
backup/moodle2/restore_stepslib.php
backup/util/factories/backup_factory.class.php
backup/util/helper/backup_cron_helper.class.php
backup/util/helper/backup_helper.class.php
backup/util/ui/tests/behat/restore_moodle2_courses.feature
badges/tests/behat/award_badge.feature
blocks/comments/tests/behat/add_comment.feature
blocks/comments/tests/behat/behat_block_comments.php
blocks/completionstatus/block_completionstatus.php
blocks/navigation/tests/behat/expand_courses_node.feature
blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-debug.js
blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-min.js
blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation.js
blocks/navigation/yui/src/navigation/js/navigation.js
blocks/news_items/tests/behat/display_news.feature
blocks/site_main_menu/block_site_main_menu.php
blocks/site_main_menu/styles.css
blocks/tests/behat/configure_block_throughout_site.feature
cache/forms.php
cache/renderer.php
calendar/externallib.php
calendar/tests/externallib_test.php
calendar/upgrade.txt
comment/lib.php
comment/locallib.php
completion/tests/behat/behat_completion.php
completion/tests/behat/enable_manual_complete_mark.feature
completion/tests/behat/restrict_section_availability.feature
completion/tests/behat/teacher_manual_completion.feature [new file with mode: 0644]
course/classes/management/helper.php
course/completion.js
course/edit.php
course/edit_form.php
course/format/lib.php
course/format/topics/lib.php
course/format/weeks/lib.php
course/moodleform_mod.php
course/tests/behat/activities_group_icons.feature
course/tests/behat/behat_course.php
course/tests/behat/category_change_visibility.feature
course/tests/behat/category_resort.feature
course/tests/behat/course_category_management_listing.feature
course/tests/behat/course_change_visibility.feature
course/tests/behat/course_creation.feature
course/tests/behat/course_resort.feature
course/tests/behat/create_delete_course.feature
course/tests/behat/edit_settings.feature
course/tests/behat/force_group_mode.feature
course/tests/behat/move_activities.feature
course/tests/behat/move_sections.feature
course/tests/behat/rename_roles.feature
course/togglecompletion.php
enrol/guest/tests/behat/guest_access.feature
enrol/manual/yui/quickenrolment/quickenrolment.js
enrol/meta/addinstance_form.php
enrol/meta/lang/en/enrol_meta.php
enrol/meta/settings.php
enrol/meta/version.php
enrol/self/db/access.php
enrol/self/lang/en/enrol_self.php
enrol/self/locallib.php
enrol/self/version.php
enrol/yui/otherusersmanager/otherusersmanager.js
enrol/yui/rolemanager/rolemanager.js
grade/grading/form/guide/js/guideeditor.js
grade/grading/form/rubric/js/rubriceditor.js
grade/report/grader/module.js
grade/report/singleview/classes/local/screen/grade.php
grade/report/singleview/classes/local/screen/screen.php
grade/report/singleview/classes/local/screen/user.php
grade/report/singleview/index.php
grade/report/singleview/lib.php
grade/report/singleview/tests/fixtures/screen.php [new file with mode: 0644]
grade/report/singleview/tests/screen_test.php [new file with mode: 0644]
grade/report/upgrade.txt
group/index.php
install/lang/nl/install.php
install/lang/pt/install.php
lang/en/access.php
lang/en/admin.php
lang/en/auth.php
lang/en/availability.php
lang/en/backup.php
lang/en/badges.php
lang/en/block.php
lang/en/blog.php
lang/en/bulkusers.php
lang/en/cache.php
lang/en/calendar.php
lang/en/cohort.php
lang/en/completion.php
lang/en/countries.php
lang/en/currencies.php
lang/en/dbtransfer.php
lang/en/debug.php
lang/en/deprecated.txt
lang/en/editor.php
lang/en/edufields.php
lang/en/enrol.php
lang/en/error.php
lang/en/filters.php
lang/en/form.php
lang/en/grades.php
lang/en/grading.php
lang/en/group.php
lang/en/hub.php
lang/en/imscc.php
lang/en/install.php
lang/en/iso6392.php
lang/en/langconfig.php
lang/en/license.php
lang/en/mathslib.php
lang/en/message.php
lang/en/mimetypes.php
lang/en/mnet.php
lang/en/moodle.php
lang/en/my.php
lang/en/notes.php
lang/en/pagetype.php
lang/en/pix.php
lang/en/plagiarism.php
lang/en/portfolio.php
lang/en/question.php
lang/en/rating.php
lang/en/repository.php
lang/en/role.php
lang/en/search.php
lang/en/table.php
lang/en/tag.php
lang/en/timezones.php
lang/en/userkey.php
lang/en/webservice.php
lib/blocklib.php
lib/classes/task/file_temp_cleanup_task.php
lib/db/install.xml
lib/db/upgrade.php
lib/deprecatedlib.php
lib/editor/atto/upgrade.txt [new file with mode: 0644]
lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-min.js
lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin.js
lib/editor/atto/yui/src/editor/js/editor-plugin-buttons.js
lib/editor/tinymce/plugins/managefiles/module.js
lib/form/dndupload.js
lib/form/filemanager.js
lib/form/yui/build/moodle-form-showadvanced/moodle-form-showadvanced-debug.js
lib/form/yui/build/moodle-form-showadvanced/moodle-form-showadvanced-min.js
lib/form/yui/build/moodle-form-showadvanced/moodle-form-showadvanced.js
lib/form/yui/src/showadvanced/js/showadvanced.js
lib/moodlelib.php
lib/testing/generator/data_generator.php
lib/testing/tests/generator_test.php
lib/tests/behat/behat_general.php
lib/tests/blocklib_test.php
lib/thirdpartylibs.xml
lib/yui/build/moodle-core-dock/moodle-core-dock-debug.js
lib/yui/build/moodle-core-dock/moodle-core-dock-min.js
lib/yui/build/moodle-core-dock/moodle-core-dock.js
lib/yui/build/moodle-core-maintenancemodetimer/moodle-core-maintenancemodetimer-debug.js
lib/yui/build/moodle-core-maintenancemodetimer/moodle-core-maintenancemodetimer-min.js
lib/yui/build/moodle-core-maintenancemodetimer/moodle-core-maintenancemodetimer.js
lib/yui/src/dock/js/block.js
lib/yui/src/dock/js/dock.js
lib/yui/src/dock/js/dockeditem.js
lib/yui/src/maintenancemodetimer/js/maintenancemodetimer.js
login/change_password.php
login/change_password_form.php
login/lib.php
login/set_password_form.php
message/module.js
mnet/peer.php
mnet/xmlrpc/client.php
mod/assign/tests/behat/group_submission.feature
mod/chat/gui_ajax/module.js
mod/feedback/backup/moodle1/lib.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/subscribe.php
mod/forum/tests/behat/completion_condition_number_discussions.feature
mod/forum/tests/behat/discussion_subscriptions.feature
mod/forum/tests/mail_test.php
mod/imscp/backup/moodle1/lib.php
mod/imscp/backup/moodle2/backup_imscp_activity_task.class.php
mod/imscp/backup/moodle2/backup_imscp_stepslib.php
mod/imscp/backup/moodle2/restore_imscp_activity_task.class.php
mod/imscp/backup/moodle2/restore_imscp_stepslib.php
mod/imscp/db/install.php
mod/imscp/db/log.php
mod/imscp/db/upgrade.php
mod/imscp/index.php
mod/imscp/lang/en/imscp.php
mod/imscp/lib.php
mod/imscp/locallib.php
mod/imscp/mod_form.php
mod/imscp/module.js
mod/imscp/settings.php
mod/imscp/styles.css
mod/imscp/tests/generator_test.php
mod/imscp/version.php
mod/imscp/view.php
mod/lesson/format.php
mod/lti/mod_form.js
mod/quiz/lang/en/quiz.php
mod/quiz/module.js
mod/quiz/tests/behat/behat_mod_quiz.php
mod/quiz/tests/behat/completion_condition_attempts_used.feature
mod/quiz/tests/behat/completion_condition_passing_grade.feature
mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop-debug.js
mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop-min.js
mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop.js
mod/quiz/yui/src/dragdrop/js/resource.js
mod/scorm/module.js
mod/scorm/view.js
mod/survey/survey.js
pix/t/viewdetails.png [new file with mode: 0644]
pix/t/viewdetails.svg [new file with mode: 0644]
question/format.php
question/format/gift/tests/behat/import_export.feature
question/format/xml/tests/behat/import_export.feature
report/backups/index.php
report/backups/lang/en/report_backups.php
report/completion/index.php
report/completion/lang/en/report_completion.php
report/log/classes/table_log.php
repository/filepicker.js
tag/tag.js
theme/base/style/user.css
theme/bootstrapbase/less/moodle/user.less
theme/bootstrapbase/style/moodle.css
user/edit_form.php
user/lib.php
user/selector/module.js
user/tests/userlib_test.php
version.php
webservice/renderer.php

index ce1c1c0..a9339b0 100644 (file)
@@ -94,6 +94,15 @@ class mnet_review_host_form extends moodleform {
         $mform->setType('wwwroot', PARAM_URL);
         $mform->addRule('wwwroot', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
 
+        $options = array(
+            mnet_peer::SSL_NONE => get_string('none'),
+            mnet_peer::SSL_HOST => get_string('verifyhostonly', 'core_mnet'),
+            mnet_peer::SSL_HOST_AND_PEER => get_string('verifyhostandpeer', 'core_mnet')
+        );
+        $mform->addElement('select', 'sslverification', get_string('sslverification', 'core_mnet'), $options);
+        $mform->setDefault('sslverification', mnet_peer::SSL_HOST_AND_PEER);
+        $mform->addHelpButton('sslverification', 'sslverification', 'core_mnet');
+
         $themes = array('' => get_string('forceno'));
         foreach (array_keys(core_component::get_plugin_list('theme')) as $themename) {
             $themes[$themename] = get_string('pluginname', 'theme_'.$themename);
index 40759db..24ef9b6 100644 (file)
@@ -172,6 +172,7 @@ if ($formdata = $reviewform->get_data()) {
     $mnet_peer->public_key          = $formdata->public_key;
     $credentials                    = $mnet_peer->check_credentials($mnet_peer->public_key);
     $mnet_peer->public_key_expires  = $credentials['validTo_time_t'];
+    $mnet_peer->sslverification     = $formdata->sslverification;
 
     if ($mnet_peer->commit()) {
         redirect(new moodle_url('/admin/mnet/peers.php', array('hostid' => $mnet_peer->id)), get_string('changessaved'));
index 89734e3..f43225c 100644 (file)
@@ -66,12 +66,19 @@ if (!empty($hostid) && array_key_exists($hostid, $hosts)) {
 
     $mnet_request->set_method('system/listServices');
     $mnet_request->send($mnet_peer);
+
     $services = $mnet_request->response;
     $yesno = array('No', 'Yes');
     $servicenames = array();
 
     echo $OUTPUT->heading(get_string('servicesavailableonhost', 'mnet', $host->wwwroot));
 
+    if (!empty($mnet_request->error)) {
+        echo $OUTPUT->heading(get_string('error'), 3);
+        echo html_writer::alist($mnet_request->error);
+        $services = array();
+    }
+
     $table = new html_table();
     $table->head = array(
         get_string('serviceid', 'mnet'),
@@ -127,6 +134,7 @@ if (!empty($hostid) && array_key_exists($hostid, $hosts)) {
     echo html_writer::table($table);
 
 
+    $mnet_request = new mnet_xmlrpc_client();
     $mnet_request->set_method('system/listMethods');
     if (isset($servicename) && array_key_exists($servicename, $serviceinfo)) {
         echo $OUTPUT->heading(get_string('methodsavailableonhostinservice', 'mnet', (object)array('host' => $host->wwwroot, 'service' => $servicename)));
@@ -139,6 +147,11 @@ if (!empty($hostid) && array_key_exists($hostid, $hosts)) {
     $mnet_request->send($mnet_peer);
     $methods = $mnet_request->response;
 
+    if (!empty($mnet_request->error)) {
+        echo $OUTPUT->heading(get_string('error'), 3);
+        echo html_writer::alist($mnet_request->error);
+        $methods = array();
+    }
 
     $table = new html_table();
     $table->head = array(
@@ -171,6 +184,12 @@ if (!empty($hostid) && array_key_exists($hostid, $hosts)) {
 
         echo $OUTPUT->heading(get_string('methodsignature', 'mnet', $method));
 
+        if (!empty($mnet_request->error)) {
+            echo $OUTPUT->heading(get_string('error'), 3);
+            echo html_writer::alist($mnet_request->error);
+            $signature = array();
+        }
+
         $table = new html_table();
         $table->head = array(
             get_string('position', 'mnet'),
index 0cac01a..9c331f1 100644 (file)
@@ -53,9 +53,9 @@ M.core_role.init_cap_table_filter = function(Y, tableid, contextid) {
             // Create the capability search input.
             this.input = Y.Node.create('<input type="text" id="'+this.table.get('id')+'capabilitysearch" value="'+Y.Escape.html(filtervalue)+'" />');
             // Create a label for the search input.
-            this.label = Y.Node.create('<label for="'+this.input.get('id')+'">'+M.str.moodle.filter+' </label>');
+            this.label = Y.Node.create('<label for="'+this.input.get('id')+'">'+M.util.get_string('filter', 'moodle')+' </label>');
             // Create a clear button to clear the input.
-            this.button = Y.Node.create('<input type="button" value="'+M.str.moodle.clear+'" />').set('disabled', filtervalue=='');
+            this.button = Y.Node.create('<input type="button" value="'+M.util.get_string('clear', 'moodle')+'" />').set('disabled', filtervalue=='');
 
             // Tie it all together
             this.div.append(this.label).append(this.input).append(this.button);
index b904da2..062df28 100644 (file)
@@ -144,6 +144,7 @@ mybadges,badges|/badges/mybadges.php|award',
     $temp->add(new admin_setting_configtext('navcourselimit',new lang_string('navcourselimit','admin'),new lang_string('confignavcourselimit', 'admin'),20,PARAM_INT));
     $temp->add(new admin_setting_configcheckbox('usesitenameforsitepages', new lang_string('usesitenameforsitepages', 'admin'), new lang_string('configusesitenameforsitepages', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('linkadmincategories', new lang_string('linkadmincategories', 'admin'), new lang_string('linkadmincategories_help', 'admin'), 0));
+    $temp->add(new admin_setting_configcheckbox('linkcoursesections', new lang_string('linkcoursesections', 'admin'), new lang_string('linkcoursesections_help', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('navshowfrontpagemods', new lang_string('navshowfrontpagemods', 'admin'), new lang_string('navshowfrontpagemods_help', 'admin'), 1));
     $temp->add(new admin_setting_configcheckbox('navadduserpostslinks', new lang_string('navadduserpostslinks', 'admin'), new lang_string('navadduserpostslinks_help', 'admin'), 1));
 
index b455946..af1e6c8 100644 (file)
@@ -70,6 +70,11 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $temp->add(new admin_setting_configtext('minpasswordupper', new lang_string('minpasswordupper', 'admin'), new lang_string('configminpasswordupper', 'admin'), 1, PARAM_INT));
     $temp->add(new admin_setting_configtext('minpasswordnonalphanum', new lang_string('minpasswordnonalphanum', 'admin'), new lang_string('configminpasswordnonalphanum', 'admin'), 1, PARAM_INT));
     $temp->add(new admin_setting_configtext('maxconsecutiveidentchars', new lang_string('maxconsecutiveidentchars', 'admin'), new lang_string('configmaxconsecutiveidentchars', 'admin'), 0, PARAM_INT));
+
+    $temp->add(new admin_setting_configtext('passwordreuselimit',
+        new lang_string('passwordreuselimit', 'admin'),
+        new lang_string('passwordreuselimit_desc', 'admin'), 0, PARAM_INT));
+
     $pwresetoptions = array(
         300 => new lang_string('numminutes', '', 5),
         900 => new lang_string('numminutes', '', 15),
index 29a5966..4b3b11e 100644 (file)
@@ -153,6 +153,19 @@ $temp->add(new admin_setting_configselect('gradehistorylifetime', new lang_strin
                                                                                                      60 => new lang_string('numdays', '', 60),
                                                                                                      30 => new lang_string('numdays', '', 30))));
 
+$temp->add(new admin_setting_configselect('tempdatafoldercleanup', new lang_string('tempdatafoldercleanup', 'admin'),
+        new lang_string('configtempdatafoldercleanup', 'admin'), 168, array(
+            1 => new lang_string('numhours', '', 1),
+            3 => new lang_string('numhours', '', 3),
+            6 => new lang_string('numhours', '', 6),
+            9 => new lang_string('numhours', '', 9),
+            12 => new lang_string('numhours', '', 12),
+            18 => new lang_string('numhours', '', 18),
+            24 => new lang_string('numhours', '', 24),
+            48 => new lang_string('numdays', '', 2),
+            168 => new lang_string('numdays', '', 7),
+)));
+
 $ADMIN->add('server', $temp);
 
 
index 933408a..ab7f670 100644 (file)
@@ -55,7 +55,7 @@ M.tool_assignmentupgrade = {
             assignmentsinput = Y.one('input.selectedassignments');
             assignmentsinput.set('value', selectedassignments.join(','));
             if (selectedassignments.length == 0) {
-                alert(M.str.tool_assignmentupgrade.noassignmentsselected);
+                alert(M.util.get_string('noassignmentsselected', 'tool_assignmentupgrade'));
                 e.preventDefault();
             }
         });
index f0fe6c8..1e5b147 100644 (file)
@@ -28,6 +28,7 @@
     $extraws = ob_get_clean();
 
     require_once($CFG->libdir.'/adminlib.php');
+    require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/health/locallib.php');
 
     admin_externalpage_setup('toolhealth');
 
@@ -603,34 +604,10 @@ class problem_000017 extends problem_base {
             $categories = $DB->get_records('question_categories', array(), 'id');
 
             // Look for missing parents.
-            $missingparent = array();
-            foreach ($categories as $category) {
-                if ($category->parent != 0 && !array_key_exists($category->parent, $categories)) {
-                    $missingparent[$category->id] = $category;
-                }
-            }
+            $missingparent = tool_health_category_find_missing_parents($categories);
 
             // Look for loops.
-            $loops = array();
-            while (!empty($categories)) {
-                $current = array_pop($categories);
-                $thisloop = array($current->id => $current);
-                while (true) {
-                    if (isset($thisloop[$current->parent])) {
-                        // Loop detected
-                        $loops[$current->id] = $thisloop;
-                        break;
-                    } else if (!isset($categories[$current->parent])) {
-                        // Got to the top level, or a category we already know is OK.
-                        break;
-                    } else {
-                        // Continue following the path.
-                        $current = $categories[$current->parent];
-                        $thisloop[$current->id] = $current;
-                        unset($categories[$current->id]);
-                    }
-                }
-            }
+            $loops = tool_health_category_find_loops($categories);
 
             $answer = array($missingparent, $loops);
         }
@@ -651,29 +628,126 @@ class problem_000017 extends problem_base {
                 ' structures by the question_categories.parent field. Sometimes ' .
                 ' this tree structure gets messed up.</p>';
 
+        $description .= tool_health_category_list_missing_parents($missingparent);
+        $description .= tool_health_category_list_loops($loops);
+
+        return $description;
+    }
+
+    /**
+     * Outputs resolutions to problems outlined in MDL-34684 with items having themselves as parent
+     *
+     * @link https://tracker.moodle.org/browse/MDL-34684
+     * @return string Formatted html to be output to the browser with instructions and sql statements to run
+     */
+    public function solution() {
+        global $CFG;
+        list($missingparent, $loops) = $this->find_problems();
+
+        $solution = '<p>Consider executing the following SQL queries. These fix ' .
+                'the problem by moving some categories to the top level.</p>';
+
         if (!empty($missingparent)) {
-            $description .= '<p>The following categories are missing their parents:</p><ul>';
-            foreach ($missingparent as $cat) {
-                $description .= "<li>Category $cat->id: " . s($cat->name) . "</li>\n";
-            }
-            $description .= "</ul>\n";
+            $solution .= "<pre>UPDATE " . $CFG->prefix . "question_categories\n" .
+                    "        SET parent = 0\n" .
+                    "        WHERE id IN (" . implode(',', array_keys($missingparent)) . ");</pre>\n";
         }
 
         if (!empty($loops)) {
-            $description .= '<p>The following categories form a loop of parents:</p><ul>';
-            foreach ($loops as $loop) {
-                $description .= "<li><ul>\n";
-                foreach ($loop as $cat) {
-                    $description .= "<li>Category $cat->id: " . s($cat->name) . " has parent $cat->parent</li>\n";
-                }
-                $description .= "</ul></li>\n";
-            }
-            $description .= "</ul>\n";
+            $solution .= "<pre>UPDATE " . $CFG->prefix . "question_categories\n" .
+                    "        SET parent = 0\n" .
+                    "        WHERE id IN (" . implode(',', array_keys($loops)) . ");</pre>\n";
+        }
+
+        return $solution;
+    }
+}
+
+/**
+ * Check course categories tree structure for problems.
+ *
+ * @copyright  2013 Marko Vidberg
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class problem_000018 extends problem_base {
+    /**
+     * Generate title for this problem.
+     *
+     * @return string Title of problem.
+     */
+    public function title() {
+        return 'Course categories tree structure';
+    }
+
+    /**
+     * Search for problems in the course categories.
+     *
+     * @uses $DB
+     * @return array List of categories that contain missing parents or loops.
+     */
+    public function find_problems() {
+        global $DB;
+        static $answer = null;
+
+        if (is_null($answer)) {
+            $categories = $DB->get_records('course_categories', array(), 'id');
+
+            // Look for missing parents.
+            $missingparent = tool_health_category_find_missing_parents($categories);
+
+            // Look for loops.
+            $loops = tool_health_category_find_loops($categories);
+
+            $answer = array($missingparent, $loops);
         }
 
+        return $answer;
+    }
+
+    /**
+     * Check if the problem exists.
+     *
+     * @return boolean True if either missing parents or loops found
+     */
+    public function exists() {
+        list($missingparent, $loops) = $this->find_problems();
+        return !empty($missingparent) || !empty($loops);
+    }
+
+    /**
+     * Set problem severity.
+     *
+     * @return constant Problem severity.
+     */
+    public function severity() {
+        return SEVERITY_SIGNIFICANT;
+    }
+
+    /**
+     * Generate problem description.
+     *
+     * @return string HTML containing details of the problem.
+     */
+    public function description() {
+        list($missingparent, $loops) = $this->find_problems();
+
+        $description = '<p>The course categories should be arranged into tree ' .
+                ' structures by the course_categories.parent field. Sometimes ' .
+                ' this tree structure gets messed up.</p>';
+
+        $description .= tool_health_category_list_missing_parents($missingparent);
+        $description .= tool_health_category_list_loops($loops);
+
         return $description;
     }
-    function solution() {
+
+    /**
+     * Generate solution text.
+     *
+     * @uses $CFG
+     * @return string HTML containing the suggested solution.
+     */
+    public function solution() {
         global $CFG;
         list($missingparent, $loops) = $this->find_problems();
 
@@ -681,14 +755,14 @@ class problem_000017 extends problem_base {
                 'the problem by moving some categories to the top level.</p>';
 
         if (!empty($missingparent)) {
-            $solution .= "<pre>UPDATE " . $CFG->prefix . "question_categories\n" .
-                    "        SET parent = 0\n" .
+            $solution .= "<pre>UPDATE " . $CFG->prefix . "course_categories\n" .
+                    "        SET parent = 0, depth = 1, path = CONCAT('/', id)\n" .
                     "        WHERE id IN (" . implode(',', array_keys($missingparent)) . ");</pre>\n";
         }
 
         if (!empty($loops)) {
-            $solution .= "<pre>UPDATE " . $CFG->prefix . "question_categories\n" .
-                    "        SET parent = 0\n" .
+            $solution .= "<pre>UPDATE " . $CFG->prefix . "course_categories\n" .
+                    "        SET parent = 0, depth = 1, path = CONCAT('/', id)\n" .
                     "        WHERE id IN (" . implode(',', array_keys($loops)) . ");</pre>\n";
         }
 
diff --git a/admin/tool/health/locallib.php b/admin/tool/health/locallib.php
new file mode 100644 (file)
index 0000000..9eb28f9
--- /dev/null
@@ -0,0 +1,128 @@
+<?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/>.
+
+/**
+ * Functions used by the health tool.
+ *
+ * @package    tool_health
+ * @copyright  2013 Marko Vidberg
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Given a list of categories, this function searches for ones
+ * that have a missing parent category.
+ *
+ * @param array $categories List of categories.
+ * @return array List of categories with missing parents.
+ */
+function tool_health_category_find_missing_parents($categories) {
+    $missingparent = array();
+
+    foreach ($categories as $category) {
+        if ($category->parent != 0 && !array_key_exists($category->parent, $categories)) {
+            $missingparent[$category->id] = $category;
+        }
+    }
+
+    return $missingparent;
+}
+
+/**
+ * Generates a list of categories with missing parents.
+ *
+ * @param array $missingparent List of categories with missing parents.
+ * @return string Bullet point list of categories with missing parents.
+ */
+function tool_health_category_list_missing_parents($missingparent) {
+    $description = '';
+
+    if (!empty($missingparent)) {
+        $description .= '<p>The following categories are missing their parents:</p><ul>';
+        foreach ($missingparent as $cat) {
+            $description .= "<li>Category $cat->id: " . s($cat->name) . "</li>\n";
+        }
+        $description .= "</ul>\n";
+    }
+
+    return $description;
+}
+
+/**
+ * Given a list of categories, this function searches for ones
+ * that have loops to previous parent categories.
+ *
+ * @param array $categories List of categories.
+ * @return array List of categories with loops.
+ */
+function tool_health_category_find_loops($categories) {
+    $loops = array();
+
+    while (!empty($categories)) {
+
+        $current = array_pop($categories);
+        $thisloop = array($current->id => $current);
+
+        while (true) {
+            if (isset($thisloop[$current->parent])) {
+                // Loop detected.
+                $loops = $loops + $thisloop;
+                break;
+            } else if ($current->parent === 0) {
+                // Top level.
+                break;
+            } else if (isset($loops[$current->parent])) {
+                // If the parent is in a loop we should also update this category.
+                $loops = $loops + $thisloop;
+                break;
+            } else if (!isset($categories[$current->parent])) {
+                // We already checked this category and is correct.
+                break;
+            } else {
+                // Continue following the path.
+                $current = $categories[$current->parent];
+                $thisloop[$current->id] = $current;
+                unset($categories[$current->id]);
+            }
+        }
+    }
+
+    return $loops;
+}
+
+/**
+ * Generates a list of categories with loops.
+ *
+ * @param array $loops List of categories with loops.
+ * @return string Bullet point list of categories with loops.
+ */
+function tool_health_category_list_loops($loops) {
+    $description = '';
+
+    if (!empty($loops)) {
+        $description .= '<p>The following categories form a loop of parents:</p><ul>';
+        foreach ($loops as $loop) {
+            $description .= "<li>\n";
+            $description .= "Category $loop->id: " . s($loop->name) . " has parent $loop->parent\n";
+            $description .= "</li>\n";
+        }
+        $description .= "</ul>\n";
+    }
+
+    return $description;
+}
diff --git a/admin/tool/health/tests/healthlib_test.php b/admin/tool/health/tests/healthlib_test.php
new file mode 100644 (file)
index 0000000..0190af9
--- /dev/null
@@ -0,0 +1,218 @@
+<?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 tool_health.
+ *
+ * @package    tool_health
+ * @copyright  2013 Marko Vidberg
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/health/locallib.php');
+
+/**
+ * Health lib testcase.
+ *
+ * @package    tool_health
+ * @copyright  2013 Marko Vidberg
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class healthlib_testcase extends advanced_testcase {
+
+    /**
+     * Data provider for test_tool_health_category_find_loops.
+     */
+    public static function provider_loop_categories() {
+        return array(
+            // One item loop including root.
+            0 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 1)
+                ),
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 1)
+                ),
+            ),
+            // One item loop not including root.
+            1 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 0),
+                    '2' => (object) array('id' => 2, 'parent' => 2)
+                ),
+                array(
+                    '2' => (object) array('id' => 2, 'parent' => 2)
+                ),
+            ),
+            // Two item loop including root.
+            2 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 1)
+                ),
+                array(
+                    '2' => (object) array('id' => 2, 'parent' => 1),
+                    '1' => (object) array('id' => 1, 'parent' => 2),
+                )
+            ),
+            // Two item loop not including root.
+            3 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 0),
+                    '2' => (object) array('id' => 2, 'parent' => 3),
+                    '3' => (object) array('id' => 3, 'parent' => 2),
+                ),
+                array(
+                    '3' => (object) array('id' => 3, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 3),
+                )
+            ),
+            // Three item loop including root.
+            4 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 3),
+                    '3' => (object) array('id' => 3, 'parent' => 1),
+                ),
+                array(
+                    '3' => (object) array('id' => 3, 'parent' => 1),
+                    '1' => (object) array('id' => 1, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 3),
+                )
+            ),
+            // Three item loop not including root.
+            5 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 0),
+                    '2' => (object) array('id' => 2, 'parent' => 3),
+                    '3' => (object) array('id' => 3, 'parent' => 4),
+                    '4' => (object) array('id' => 4, 'parent' => 2)
+                ),
+                array(
+                    '4' => (object) array('id' => 4, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 3),
+                    '3' => (object) array('id' => 3, 'parent' => 4),
+                )
+            ),
+            // Multi-loop.
+            6 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 1),
+                    '3' => (object) array('id' => 3, 'parent' => 4),
+                    '4' => (object) array('id' => 4, 'parent' => 5),
+                    '5' => (object) array('id' => 5, 'parent' => 3),
+                    '6' => (object) array('id' => 6, 'parent' => 6),
+                    '7' => (object) array('id' => 7, 'parent' => 1),
+                    '8' => (object) array('id' => 8, 'parent' => 7),
+                ),
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 1),
+                    '8' => (object) array('id' => 8, 'parent' => 7),
+                    '7' => (object) array('id' => 7, 'parent' => 1),
+                    '6' => (object) array('id' => 6, 'parent' => 6),
+                    '5' => (object) array('id' => 5, 'parent' => 3),
+                    '3' => (object) array('id' => 3, 'parent' => 4),
+                    '4' => (object) array('id' => 4, 'parent' => 5),
+                )
+            ),
+            // Double-loop
+            7 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 1),
+                    '3' => (object) array('id' => 3, 'parent' => 2),
+                    '4' => (object) array('id' => 4, 'parent' => 2),
+                ),
+                array(
+                    '4' => (object) array('id' => 4, 'parent' => 2),
+                    '3' => (object) array('id' => 3, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 1),
+                    '1' => (object) array('id' => 1, 'parent' => 2),
+                )
+            )
+        );
+    }
+
+    /**
+     * Data provider for test_tool_health_category_find_missing_parents.
+     */
+    public static function provider_missing_parent_categories() {
+        return array(
+           // Test for two items, both with direct ancestor (parent) missing.
+            0 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 0),
+                    '2' => (object) array('id' => 2, 'parent' => 3),
+                    '4' => (object) array('id' => 4, 'parent' => 5),
+                    '6' => (object) array('id' => 6, 'parent' => 2)
+                ),
+                array(
+                    '4' => (object) array('id' => 4, 'parent' => 5),
+                    '2' => (object) array('id' => 2, 'parent' => 3)
+                ),
+            )
+        );
+    }
+
+    /**
+     * Test finding loops between two items referring to each other.
+     *
+     * @param array $categories
+     * @param array $expected
+     * @dataProvider provider_loop_categories
+     */
+    public function test_tool_health_category_find_loops($categories, $expected) {
+        $loops = tool_health_category_find_loops($categories);
+        $this->assertEquals($expected, $loops);
+    }
+
+    /**
+     * Test finding missing parent categories.
+     *
+     * @param array $categories
+     * @param array $expected
+     * @dataProvider provider_missing_parent_categories
+     */
+    public function test_tool_health_category_find_missing_parents($categories, $expected) {
+        $missingparent = tool_health_category_find_missing_parents($categories);
+        $this->assertEquals($expected, $missingparent);
+    }
+
+    /**
+     * Test listing missing parent categories.
+     */
+    public function test_tool_health_category_list_missing_parents() {
+        $missingparent = array((object) array('id' => 2, 'parent' => 3, 'name' => 'test'),
+                               (object) array('id' => 4, 'parent' => 5, 'name' => 'test2'));
+        $result = tool_health_category_list_missing_parents($missingparent);
+        $this->assertRegExp('/Category 2: test/', $result);
+        $this->assertRegExp('/Category 4: test2/', $result);
+    }
+
+    /**
+     * Test listing loop categories.
+     */
+    public function test_tool_health_category_list_loops() {
+        $loops = array((object) array('id' => 2, 'parent' => 3, 'name' => 'test'));
+        $result = tool_health_category_list_loops($loops);
+        $this->assertRegExp('/Category 2: test/', $result);
+    }
+}
index 48d561e..faabf71 100644 (file)
@@ -95,7 +95,7 @@ class store implements \tool_log\log\store, \core\log\sql_select_reader {
         $records = array();
 
         try {
-            $records = $DB->get_records_select('log', $selectwhere, $params, $sort, '*', $limitfrom, $limitnum);
+            $records = $DB->get_recordset_select('log', $selectwhere, $params, $sort, '*', $limitfrom, $limitnum);
         } catch (\moodle_exception $ex) {
             debugging("error converting legacy event data " . $ex->getMessage() . $ex->debuginfo, DEBUG_DEVELOPER);
         }
@@ -104,6 +104,8 @@ class store implements \tool_log\log\store, \core\log\sql_select_reader {
             $events[$data->id] = \logstore_legacy\event\legacy_logged::restore_legacy($data);
         }
 
+        $records->close();
+
         return $events;
     }
 
index 992d4cf..44c66de 100644 (file)
@@ -72,7 +72,7 @@ class store implements \tool_log\log\writer, \core\log\sql_internal_reader {
         $sort = self::tweak_sort_by_id($sort);
 
         $events = array();
-        $records = $DB->get_records_select('logstore_standard_log', $selectwhere, $params, $sort, '*', $limitfrom, $limitnum);
+        $records = $DB->get_recordset_select('logstore_standard_log', $selectwhere, $params, $sort, '*', $limitfrom, $limitnum);
 
         foreach ($records as $data) {
             $extra = array('origin' => $data->origin, 'ip' => $data->ip, 'realuserid' => $data->realuserid);
@@ -94,6 +94,8 @@ class store implements \tool_log\log\writer, \core\log\sql_internal_reader {
             }
         }
 
+        $records->close();
+
         return $events;
     }
 
index 73b684f..79356c6 100644 (file)
@@ -404,12 +404,11 @@ class tool_monitor_eventobservers_testcase extends advanced_testcase {
      * Run adhoc tasks.
      */
     protected function run_adhock_tasks() {
-        ob_start();
         while ($task = \core\task\manager::get_next_adhoc_task(time())) {
             $task->execute();
             \core\task\manager::adhoc_task_complete($task);
         }
-        ob_clean(); // Suppress mtrace debugging info.
+        $this->expectOutputRegex("/^Sending message to the user with id \d+ for the subscription with id \d+\.\.\..Sent./ms");
     }
 
     /**
index d7cd018..166d5cd 100644 (file)
@@ -6,7 +6,7 @@ M.tool_spamcleaner = {
     del_all: function() {
         var context = M.tool_spamcleaner;
 
-        var yes = confirm(M.str.tool_spamcleaner.spamdeleteallconfirm);
+        var yes = confirm(M.util.get_string('spamdeleteallconfirm', 'tool_spamcleaner'));
         if (yes) {
             var cfg = {
                 method: "POST",
@@ -15,7 +15,7 @@ M.tool_spamcleaner = {
                         try {
                             var resp = context.Y.JSON.parse(o.responseText);
                         } catch(e) {
-                            alert(M.str.tool_spamcleaner.spaminvalidresult);
+                            alert(M.util.get_string('spaminvalidresult', 'tool_spamcleaner'));
                             return;
                         }
                         if (resp == true) {
@@ -36,7 +36,7 @@ M.tool_spamcleaner = {
             return;
         }
 
-        var yes = confirm(M.str.tool_spamcleaner.spamdeleteconfirm);
+        var yes = confirm(M.util.get_string('spamdeleteconfirm', 'tool_spamcleaner'));
         if (yes) {
             context.row = obj;
             var cfg = {
@@ -46,7 +46,7 @@ M.tool_spamcleaner = {
                         try {
                             var resp = context.Y.JSON.parse(o.responseText);
                         } catch(e) {
-                            alert(M.str.tool_spamcleaner.spaminvalidresult);
+                            alert(M.util.get_string('spaminvalidresult', 'tool_spamcleaner'));
                             return;
                         }
                         if (context.row) {
@@ -57,7 +57,7 @@ M.tool_spamcleaner = {
                                 context.row.parentNode.removeChild(context.row);
                                 context.row = null;
                             } else {
-                                alert(M.str.tool_spamcleaner.spamcannotdelete);
+                                alert(M.util.get_string('spamcannotdelete', 'tool_spamcleaner'));
                             }
                         }
                     }
@@ -83,7 +83,7 @@ M.tool_spamcleaner = {
                     try {
                         var resp = context.Y.JSON.parse(o.responseText);
                     } catch(e) {
-                        alert(M.str.tool_spamcleaner.spaminvalidresult);
+                        alert(M.util.get_string('spaminvalidresult', 'tool_spamcleaner'));
                         return;
                     }
                     if (context.row) {
diff --git a/admin/tool/xmldb/styles_bootstrapbase.css b/admin/tool/xmldb/styles_bootstrapbase.css
new file mode 100644 (file)
index 0000000..ae530cb
--- /dev/null
@@ -0,0 +1,3 @@
+.path-admin-tool-xmldb a[name="lastused"] {
+    padding-top: 50px;
+}
index 8ab97ae..f244c45 100644 (file)
@@ -89,6 +89,7 @@ class auth_plugin_email extends auth_plugin_base {
         require_once($CFG->dirroot.'/user/profile/lib.php');
         require_once($CFG->dirroot.'/user/lib.php');
 
+        $plainpassword = $user->password;
         $user->password = hash_internal_user_password($user->password);
         if (empty($user->calendartype)) {
             $user->calendartype = $CFG->calendartype;
@@ -96,6 +97,8 @@ class auth_plugin_email extends auth_plugin_base {
 
         $user->id = user_create_user($user, false, false);
 
+        user_add_password_history($user->id, $plainpassword);
+
         // Save any custom profile field information.
         profile_save_data($user);
 
index f80ab19..03d483b 100644 (file)
@@ -539,6 +539,7 @@ class auth_plugin_ldap extends auth_plugin_base {
         global $CFG, $DB, $PAGE, $OUTPUT;
 
         require_once($CFG->dirroot.'/user/profile/lib.php');
+        require_once($CFG->dirroot.'/user/lib.php');
 
         if ($this->user_exists($user->username)) {
             print_error('auth_ldap_user_exists', 'auth_ldap');
@@ -553,6 +554,8 @@ class auth_plugin_ldap extends auth_plugin_base {
 
         $user->id = user_create_user($user, false, false);
 
+        user_add_password_history($user->id, $plainslashedpassword);
+
         // Save any custom profile field information
         profile_save_data($user);
 
index 0c6d453..a2acc9f 100644 (file)
@@ -5,6 +5,8 @@ information provided here is intended especially for developers.
 
 * Do not update user->firstaccess from any auth plugin, the complete_user_login() does it automatically.
 
+* Add user_add_password_history() to user_signup() method.
+
 === 2.8 ===
 
 * \core\session\manager::session_exists() now verifies the session is active
index 27350c4..803f565 100644 (file)
Binary files a/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-debug.js and b/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-debug.js differ
index eb98c96..17f1813 100644 (file)
Binary files a/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-min.js and b/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-min.js differ
index 27350c4..803f565 100644 (file)
Binary files a/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form.js and b/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form.js differ
index 926a9e4..1416260 100644 (file)
@@ -23,22 +23,21 @@ M.availability_completion.form.initInner = function(cms) {
 
 M.availability_completion.form.getNode = function(json) {
     // Create HTML structure.
-    var strings = M.str.availability_completion;
-    var html = strings.title + ' <span class="availability-group"><label>' +
-            '<span class="accesshide">' + strings.label_cm + ' </span>' +
-            '<select name="cm" title="' + strings.label_cm + '">' +
-            '<option value="0">' + M.str.moodle.choosedots + '</option>';
+    var html = M.util.get_string('title', 'availability_completion') + ' <span class="availability-group"><label>' +
+            '<span class="accesshide">' + M.util.get_string('label_cm', 'availability_completion') + ' </span>' +
+            '<select name="cm" title="' + M.util.get_string('label_cm', 'availability_completion') + '">' +
+            '<option value="0">' + M.util.get_string('choosedots', 'moodle') + '</option>';
     for (var i = 0; i < this.cms.length; i++) {
         var cm = this.cms[i];
         // String has already been escaped using format_string.
         html += '<option value="' + cm.id + '">' + cm.name + '</option>';
     }
-    html += '</select></label> <label><span class="accesshide">' + strings.label_completion +
-            ' </span><select name="e" title="' + strings.label_completion + '">' +
-            '<option value="1">' + strings.option_complete + '</option>' +
-            '<option value="0">' + strings.option_incomplete + '</option>' +
-            '<option value="2">' + strings.option_pass + '</option>' +
-            '<option value="3">' + strings.option_fail + '</option>' +
+    html += '</select></label> <label><span class="accesshide">' + M.util.get_string('label_completion', 'availability_completion') +
+            ' </span><select name="e" title="' + M.util.get_string('label_completion', 'availability_completion') + '">' +
+            '<option value="1">' + M.util.get_string('option_complete', 'availability_completion') + '</option>' +
+            '<option value="0">' + M.util.get_string('option_incomplete', 'availability_completion') + '</option>' +
+            '<option value="2">' + M.util.get_string('option_pass', 'availability_completion') + '</option>' +
+            '<option value="3">' + M.util.get_string('option_fail', 'availability_completion') + '</option>' +
             '</select></label></span>';
     var node = Y.Node.create('<span>' + html + '</span>');
 
index d84da8c..684d208 100644 (file)
Binary files a/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-debug.js and b/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-debug.js differ
index 7447c99..eeb12a3 100644 (file)
Binary files a/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-min.js and b/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-min.js differ
index d84da8c..684d208 100644 (file)
Binary files a/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form.js and b/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form.js differ
index 580c6eb..ca45ffc 100644 (file)
@@ -27,12 +27,11 @@ M.availability_date.form.initInner = function(html, defaultTime) {
 };
 
 M.availability_date.form.getNode = function(json) {
-    var strings = M.str.availability_date;
-    var html = strings.direction_before + ' <span class="availability-group">' +
-            '<label><span class="accesshide">' + strings.direction_label + ' </span>' +
+    var html = M.util.get_string('direction_before', 'availability_date') + ' <span class="availability-group">' +
+            '<label><span class="accesshide">' + M.util.get_string('direction_label', 'availability_date') + ' </span>' +
             '<select name="direction">' +
-            '<option value="&gt;=">' + strings.direction_from + '</option>' +
-            '<option value="&lt;">' + strings.direction_until + '</option>' +
+            '<option value="&gt;=">' + M.util.get_string('direction_from', 'availability_date') + '</option>' +
+            '<option value="&lt;">' + M.util.get_string('direction_until', 'availability_date') + '</option>' +
             '</select></label></span> ' + this.html;
     var node = Y.Node.create('<span>' + html + '</span>');
 
@@ -56,7 +55,7 @@ M.availability_date.form.getNode = function(json) {
                 }
             },
             failure : function() {
-                window.alert(M.str.availability_date.ajaxerror);
+                window.alert(M.util.get_string('ajaxerror', 'availability_date'));
             }
         }});
     } else {
@@ -130,7 +129,7 @@ M.availability_date.form.updateTime = function(node) {
             M.core_availability.form.update();
         },
         failure : function() {
-            window.alert(M.str.availability_date.ajaxerror);
+            window.alert(M.util.get_string('ajaxerror', 'availability_date'));
         }
     }});
 };
index 28f5f81..ad24176 100644 (file)
Binary files a/availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form-debug.js and b/availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form-debug.js differ
index 83c5e5a..57654e4 100644 (file)
Binary files a/availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form-min.js and b/availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form-min.js differ
index 28f5f81..ad24176 100644 (file)
Binary files a/availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form.js and b/availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form.js differ
index e0b0fe9..92108aa 100644 (file)
@@ -35,24 +35,23 @@ M.availability_grade.form.getNode = function(json) {
     this.nodesSoFar++;
 
     // Create HTML structure.
-    var strings = M.str.availability_grade;
-    var html = '<label>' + strings.title + ' <span class="availability-group">' +
-            '<select name="id"><option value="0">' + M.str.moodle.choosedots + '</option>';
+    var html = '<label>' + M.util.get_string('title', 'availability_grade') + ' <span class="availability-group">' +
+            '<select name="id"><option value="0">' + M.util.get_string('choosedots', 'moodle') + '</option>';
     for (var i = 0; i < this.grades.length; i++) {
         var grade = this.grades[i];
         // String has already been escaped using format_string.
         html += '<option value="' + grade.id + '">' + grade.name + '</option>';
     }
     html += '</select></span></label> <span class="availability-group">' +
-            '<label><input type="checkbox" name="min"/>' + strings.option_min +
-            '</label> <label><span class="accesshide">' + strings.label_min +
+            '<label><input type="checkbox" name="min"/>' + M.util.get_string('option_min', 'availability_grade') +
+            '</label> <label><span class="accesshide">' + M.util.get_string('label_min', 'availability_grade') +
             '</span><input type="text" name="minval" title="' +
-            strings.label_min + '"/></label>%</span>' +
+            M.util.get_string('label_min', 'availability_grade') + '"/></label>%</span>' +
             '<span class="availability-group">' +
-            '<label><input type="checkbox" name="max"/>' + strings.option_max +
-            '</label> <label><span class="accesshide">' + strings.label_max +
+            '<label><input type="checkbox" name="max"/>' + M.util.get_string('option_max', 'availability_grade') +
+            '</label> <label><span class="accesshide">' + M.util.get_string('label_max', 'availability_grade') +
             '</span><input type="text" name="maxval" title="' +
-            strings.label_max + '"/></label>%</span>';
+            M.util.get_string('label_max', 'availability_grade') + '"/></label>%</span>';
     var node = Y.Node.create('<span>' + html + '</span>');
 
     // Set initial values.
index a968d4e..a1b8a65 100644 (file)
Binary files a/availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form-debug.js and b/availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form-debug.js differ
index a5fe42f..3949118 100644 (file)
Binary files a/availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form-min.js and b/availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form-min.js differ
index a968d4e..a1b8a65 100644 (file)
Binary files a/availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form.js and b/availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form.js differ
index e68a703..f9ec44f 100644 (file)
@@ -31,11 +31,10 @@ M.availability_group.form.initInner = function(groups) {
 
 M.availability_group.form.getNode = function(json) {
     // Create HTML structure.
-    var strings = M.str.availability_group;
-    var html = '<label>' + strings.title + ' <span class="availability-group">' +
+    var html = '<label>' + M.util.get_string('title', 'availability_group') + ' <span class="availability-group">' +
             '<select name="id">' +
-            '<option value="choose">' + M.str.moodle.choosedots + '</option>' +
-            '<option value="any">' + strings.anygroup + '</option>';
+            '<option value="choose">' + M.util.get_string('choosedots', 'moodle') + '</option>' +
+            '<option value="any">' + M.util.get_string('anygroup', 'availability_group') + '</option>';
     for (var i = 0; i < this.groups.length; i++) {
         var group = this.groups[i];
         // String has already been escaped using format_string.
index 4c0a4ab..f3d616a 100644 (file)
Binary files a/availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form-debug.js and b/availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form-debug.js differ
index 305d8d5..34c9759 100644 (file)
Binary files a/availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form-min.js and b/availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form-min.js differ
index 4c0a4ab..f3d616a 100644 (file)
Binary files a/availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form.js and b/availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form.js differ
index dc09e83..b0fd7c9 100644 (file)
@@ -31,10 +31,9 @@ M.availability_grouping.form.initInner = function(groupings) {
 
 M.availability_grouping.form.getNode = function(json) {
     // Create HTML structure.
-    var strings = M.str.availability_grouping;
-    var html = '<label>' + strings.title + ' <span class="availability-group">' +
+    var html = '<label>' + M.util.get_string('title', 'availability_grouping') + ' <span class="availability-group">' +
             '<select name="id">' +
-            '<option value="choose">' + M.str.moodle.choosedots + '</option>';
+            '<option value="choose">' + M.util.get_string('choosedots', 'moodle') + '</option>';
     for (var i = 0; i < this.groupings.length; i++) {
         var grouping = this.groupings[i];
         // String has already been escaped using format_string.
index c4b906d..be46a7e 100644 (file)
Binary files a/availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form-debug.js and b/availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form-debug.js differ
index 6251004..63a87d3 100644 (file)
Binary files a/availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form-min.js and b/availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form-min.js differ
index c4b906d..be46a7e 100644 (file)
Binary files a/availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form.js and b/availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form.js differ
index aa01d6a..5336589 100644 (file)
@@ -33,10 +33,9 @@ M.availability_profile.form.initInner = function(standardFields, customFields) {
 
 M.availability_profile.form.getNode = function(json) {
     // Create HTML structure.
-    var strings = M.str.availability_profile;
-    var html = '<span class="availability-group"><label>' + strings.conditiontitle + ' ' +
+    var html = '<span class="availability-group"><label>' + M.util.get_string('conditiontitle', 'availability_profile') + ' ' +
             '<select name="field">' +
-            '<option value="choose">' + M.str.moodle.choosedots + '</option>';
+            '<option value="choose">' + M.util.get_string('choosedots', 'moodle') + '</option>';
     var fieldInfo;
     for (var i = 0; i < this.standardFields.length; i++) {
         fieldInfo = this.standardFields[i];
@@ -48,17 +47,17 @@ M.availability_profile.form.getNode = function(json) {
         // String has already been escaped using format_string.
         html += '<option value="cf_' + fieldInfo.field + '">' + fieldInfo.display + '</option>';
     }
-    html += '</select></label> <label><span class="accesshide">' + strings.label_operator +
-            ' </span><select name="op" title="' + strings.label_operator + '">';
+    html += '</select></label> <label><span class="accesshide">' + M.util.get_string('label_operator', 'availability_profile') +
+            ' </span><select name="op" title="' + M.util.get_string('label_operator', 'availability_profile') + '">';
     var operators = ['isequalto', 'contains', 'doesnotcontain', 'startswith', 'endswith',
             'isempty', 'isnotempty'];
     for (i = 0; i < operators.length; i++) {
         html += '<option value="' + operators[i] + '">' +
-                strings['op_' + operators[i]] + '</option>';
+                M.util.get_string('op_' + operators[i], 'availability_profile') + '</option>';
     }
-    html += '</select></label> <label><span class="accesshide">' + strings.label_value +
+    html += '</select></label> <label><span class="accesshide">' + M.util.get_string('label_value', 'availability_profile') +
             '</span><input name="value" type="text" style="width: 10em" title="' +
-            strings.label_value + '"/></label></span>';
+            M.util.get_string('label_value', 'availability_profile') + '"/></label></span>';
     var node = Y.Node.create('<span>' + html + '</span>');
 
     // Set initial values if specified.
index 3f2e3a2..82f3662 100644 (file)
@@ -172,3 +172,62 @@ Feature: edit_availability
     And I should not see "None" in the "Restrict access" "fieldset"
     And "Restriction type" "select" should be visible
     And I should see "Date" in the "Restrict access" "fieldset"
+
+  @javascript
+  Scenario: 'Add group/grouping access restriction' button unavailable
+    # Button does not exist when conditional access restrictions are turned off.
+    Given I log in as "admin"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "Forum" to section "1"
+    When I expand all fieldsets
+    Then "Add group/grouping access restriction" "button" should not exist
+
+  @javascript
+  Scenario: Use the 'Add group/grouping access restriction' button
+    # Button should initially be disabled.
+    Given I log in as "admin"
+    And I set the following administration settings values:
+      | Enable conditional access | 1 |
+    And the following "groupings" exist:
+      | name | course | idnumber |
+      | GX1  | C1     | GXI1     |
+    And I am on homepage
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "Forum" to section "1"
+    And I set the following fields to these values:
+      | Forum name  | MyForum |
+      | Description | x       |
+    When I expand all fieldsets
+    Then the "Add group/grouping access restriction" "button" should be disabled
+
+    # Turn on separate groups.
+    And I set the field "Group mode" to "Separate groups"
+    And the "Add group/grouping access restriction" "button" should be enabled
+
+    # Press the button and check it adds a restriction and disables itself.
+    And I should see "None" in the "Restrict access" "fieldset"
+    And I press "Add group/grouping access restriction"
+    And I should see "Group" in the "Restrict access" "fieldset"
+    And the "Add group/grouping access restriction" "button" should be disabled
+
+    # Delete the restriction and check it is enabled again.
+    And I click on "Delete" "link" in the "Restrict access" "fieldset"
+    And the "Add group/grouping access restriction" "button" should be enabled
+
+    # Try a grouping instead.
+    And I set the field "Grouping" to "GX1"
+    And I press "Add group/grouping access restriction"
+    And I should see "Grouping" in the "Restrict access" "fieldset"
+
+    # Check the button still works after saving and editing.
+    And I press "Save and display"
+    And I navigate to "Edit settings" node in "Forum administration"
+    And I expand all fieldsets
+    And the "Add group/grouping access restriction" "button" should be disabled
+    And I should see "Grouping" in the "Restrict access" "fieldset"
+
+    # And check it's still active if I delete the condition.
+    And I click on "Delete" "link" in the "Restrict access" "fieldset"
+    And the "Add group/grouping access restriction" "button" should be enabled
index 185800c..61b4781 100644 (file)
Binary files a/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-debug.js and b/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-debug.js differ
index 9c578a0..92740e7 100644 (file)
Binary files a/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-min.js and b/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-min.js differ
index 185800c..61b4781 100644 (file)
Binary files a/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form.js and b/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form.js differ
index 099fe5e..c247441 100644 (file)
@@ -64,6 +64,14 @@ M.core_availability.form = {
      */
     idCounter : 0,
 
+    /**
+     * The 'Restrict by group' button if present.
+     *
+     * @property restrictByGroup
+     * @type Y.Node
+     */
+    restrictByGroup : null,
+
     /**
      * Called to initialise the system when the page loads. This method will
      * also call the init method for each plugin.
@@ -119,6 +127,22 @@ M.core_availability.form = {
         this.field.ancestor('form').on('submit', function() {
             this.mainDiv.all('input,textarea,select').set('disabled', true);
         }, this);
+
+        // If the form has group mode and/or grouping options, there is a
+        // 'add restriction' button there.
+        this.restrictByGroup = Y.one('#restrictbygroup');
+        if (this.restrictByGroup) {
+            this.restrictByGroup.on('click', this.addRestrictByGroup, this);
+            var groupmode = Y.one('#id_groupmode');
+            var groupingid = Y.one('#id_groupingid');
+            if (groupmode) {
+                groupmode.on('change', this.updateRestrictByGroup, this);
+            }
+            if (groupingid) {
+                groupingid.on('change', this.updateRestrictByGroup, this);
+            }
+            this.updateRestrictByGroup();
+        }
     },
 
     /**
@@ -141,6 +165,75 @@ M.core_availability.form = {
 
         // Set into hidden form field, JS-encoded.
         this.field.set('value', Y.JSON.stringify(jsValue));
+
+        // Also update the restrict by group button if present.
+        this.updateRestrictByGroup();
+    },
+
+    /**
+     * Updates the status of the 'restrict by group' button (enables or disables
+     * it) based on current availability restrictions and group/grouping settings.
+     */
+    updateRestrictByGroup : function() {
+        if (!this.restrictByGroup) {
+            return;
+        }
+
+        // If the root list is anything other than the default 'and' type, disable.
+        if (this.rootList.getValue().op !== '&') {
+            this.restrictByGroup.set('disabled', true);
+            return;
+        }
+
+        // If there's already a group restriction, disable it.
+        var alreadyGot = this.rootList.hasItemOfType('group') ||
+                this.rootList.hasItemOfType('grouping');
+        if (alreadyGot) {
+            this.restrictByGroup.set('disabled', true);
+            return;
+        }
+
+        // If the groupmode and grouping id aren't set, disable it.
+        var groupmode = Y.one('#id_groupmode');
+        var groupingid = Y.one('#id_groupingid');
+        if ((!groupmode || Number(groupmode.get('value')) === 0) &&
+                (!groupingid || Number(groupingid.get('value')) === 0)) {
+            this.restrictByGroup.set('disabled', true);
+            return;
+        }
+
+        this.restrictByGroup.set('disabled', false);
+    },
+
+    /**
+     * Called when the user clicks on the 'restrict by group' button. This is
+     * a special case that adds a group or grouping restriction.
+     *
+     * By default this restriction is not shown which makes it similar to the
+     *
+     * @param e Button click event
+     */
+    addRestrictByGroup : function(e) {
+        // If you don't prevent default, it submits the form for some reason.
+        e.preventDefault();
+
+        // Add the condition.
+        var groupingid = Y.one('#id_groupingid');
+        var newChild;
+        if (groupingid && Number(groupingid.get('value')) !== 0) {
+            // Add a grouping restriction if one is specified.
+            newChild = new M.core_availability.Item(
+                    {type : 'grouping', id : Number(groupingid.get('value'))}, true);
+        } else {
+            // Otherwise just add a group restriction.
+            newChild = new M.core_availability.Item({type : 'group'}, true);
+        }
+
+        // Refresh HTML.
+        this.rootList.addChild(newChild);
+        this.update();
+        this.rootList.renumber();
+        this.rootList.updateHtml();
     }
 };
 
@@ -258,24 +351,23 @@ M.core_availability.List = function(json, root, parentRoot) {
     if (root !== undefined) {
         this.root = root;
     }
-    var strings = M.str.availability;
     // Create DIV structure (without kids).
     this.node = Y.Node.create('<div class="availability-list"><h3 class="accesshide"></h3>' +
             '<div class="availability-inner">' +
-            '<div class="availability-header">' + strings.listheader_sign_before +
-            ' <label><span class="accesshide">' + strings.label_sign +
-            ' </span><select class="availability-neg" title="' + strings.label_sign + '">' +
-            '<option value="">' + strings.listheader_sign_pos + '</option>' +
-            '<option value="!">' + strings.listheader_sign_neg + '</option></select></label> ' +
-            '<span class="availability-single">' + strings.listheader_single + '</span>' +
-            '<span class="availability-multi">' + strings.listheader_multi_before +
-            ' <label><span class="accesshide">' + strings.label_multi + ' </span>' +
-            '<select class="availability-op" title="' + strings.label_multi + '"><option value="&">' +
-            strings.listheader_multi_and + '</option>' +
-            '<option value="|">' + strings.listheader_multi_or + '</option></select></label> ' +
-            strings.listheader_multi_after + '</span></div>' +
+            '<div class="availability-header">' + M.util.get_string('listheader_sign_before', 'availability') +
+            ' <label><span class="accesshide">' + M.util.get_string('label_sign', 'availability') +
+            ' </span><select class="availability-neg" title="' + M.util.get_string('label_sign', 'availability') + '">' +
+            '<option value="">' + M.util.get_string('listheader_sign_pos', 'availability') + '</option>' +
+            '<option value="!">' + M.util.get_string('listheader_sign_neg', 'availability') + '</option></select></label> ' +
+            '<span class="availability-single">' + M.util.get_string('listheader_single', 'availability') + '</span>' +
+            '<span class="availability-multi">' + M.util.get_string('listheader_multi_before', 'availability') +
+            ' <label><span class="accesshide">' + M.util.get_string('label_multi', 'availability') + ' </span>' +
+            '<select class="availability-op" title="' + M.util.get_string('label_multi', 'availability') + '"><option value="&">' +
+            M.util.get_string('listheader_multi_and', 'availability') + '</option>' +
+            '<option value="|">' + M.util.get_string('listheader_multi_or', 'availability') + '</option></select></label> ' +
+            M.util.get_string('listheader_multi_after', 'availability') + '</span></div>' +
             '<div class="availability-children"></div>' +
-            '<div class="availability-none">' + M.str.moodle.none + '</div>' +
+            '<div class="availability-none">' + M.util.get_string('none', 'moodle') + '</div>' +
             '<div class="availability-button"></div></div></div>');
     if (!root) {
         this.node.addClass('availability-childlist');
@@ -311,12 +403,12 @@ M.core_availability.List = function(json, root, parentRoot) {
 
         // Also if it's not the root, none is actually invalid, so add a label.
         noneNode.appendChild(Y.Node.create('<span class="label label-warning">' +
-                M.str.availability.invalid + '</span>'));
+                M.util.get_string('invalid', 'availability') + '</span>'));
     }
 
     // Create the button and add it.
     var button = Y.Node.create('<button type="button" class="btn btn-default">' +
-            M.str.availability.addrestriction + '</button>');
+            M.util.get_string('addrestriction', 'availability') + '</button>');
     button.on("click", function() { this.clickAdd(); }, this);
     this.node.one('div.availability-button').appendChild(button);
 
@@ -507,9 +599,9 @@ M.core_availability.List.prototype.updateHtml = function() {
     // Update connector text.
     var connectorText;
     if (this.inner.one('.availability-op').get('value') === '&') {
-        connectorText = M.str.availability.and;
+        connectorText = M.util.get_string('and', 'availability');
     } else {
-        connectorText = M.str.availability.or;
+        connectorText = M.util.get_string('or', 'availability');
     }
     this.inner.all('> .availability-children > .availability-connector span.label').each(function(span) {
         span.set('innerHTML', connectorText);
@@ -571,7 +663,7 @@ M.core_availability.List.prototype.clickAdd = function() {
     var content = Y.Node.create('<div>' +
             '<ul class="list-unstyled"></ul>' +
             '<div class="availability-buttons mdl-align">' +
-            '<button type="button" class="btn btn-default">' + M.str.moodle.cancel +
+            '<button type="button" class="btn btn-default">' + M.util.get_string('cancel', 'moodle') +
             '</button></div></div>');
     var cancel = content.one('button');
 
@@ -587,13 +679,12 @@ M.core_availability.List.prototype.clickAdd = function() {
         // Add entry for plugin.
         li = Y.Node.create('<li class="clearfix"></li>');
         id = 'availability_addrestriction_' + type;
-        var pluginStrings = M.str['availability_' + type];
         button = Y.Node.create('<button type="button" class="btn btn-default"' +
-                'id="' + id + '">' + pluginStrings.title + '</button>');
+                'id="' + id + '">' + M.util.get_string('title', 'availability_' + type) + '</button>');
         button.on('click', this.getAddHandler(type, dialogRef), this);
         li.appendChild(button);
         label = Y.Node.create('<label for="' + id + '">' +
-                pluginStrings.description + '</label>');
+                M.util.get_string('description', 'availability_' + type) + '</label>');
         li.appendChild(label);
         ul.appendChild(li);
     }
@@ -601,16 +692,16 @@ M.core_availability.List.prototype.clickAdd = function() {
     li = Y.Node.create('<li class="clearfix"></li>');
     id = 'availability_addrestriction_list_';
     button = Y.Node.create('<button type="button" class="btn btn-default"' +
-            'id="' + id + '">' + M.str.availability.condition_group + '</button>');
+            'id="' + id + '">' + M.util.get_string('condition_group', 'availability') + '</button>');
     button.on('click', this.getAddHandler(null, dialogRef), this);
     li.appendChild(button);
     label = Y.Node.create('<label for="' + id + '">' +
-            M.str.availability.condition_group_info + '</label>');
+            M.util.get_string('condition_group_info', 'availability') + '</label>');
     li.appendChild(label);
     ul.appendChild(li);
 
     var config = {
-        headerContent : M.str.availability.addrestriction,
+        headerContent : M.util.get_string('addrestriction', 'availability'),
         bodyContent : content,
         additionalBaseClass : 'availability-dialogue',
         draggable : true,
@@ -709,6 +800,31 @@ M.core_availability.List.prototype.fillErrors = function(errors) {
     }
 };
 
+/**
+ * Checks whether the list contains any items of the given type name.
+ *
+ * @method hasItemOfType
+ * @param {String} pluginType Required plugin type (name)
+ * @return {Boolean} True if there is one
+ */
+M.core_availability.List.prototype.hasItemOfType = function(pluginType) {
+    // Check each item.
+    for (var i = 0; i < this.children.length; i++) {
+        var child = this.children[i];
+        if (child instanceof M.core_availability.List) {
+            // Recursive call.
+            if (child.hasItemOfType(pluginType)) {
+                return true;
+            }
+        } else {
+            if (child.pluginType === pluginType) {
+                return true;
+            }
+        }
+    }
+    return false;
+};
+
 /**
  * Eye icon for this list (null if none).
  *
@@ -764,7 +880,7 @@ M.core_availability.Item = function(json, root) {
         // Handle undefined plugins.
         this.plugin = null;
         this.pluginNode = Y.Node.create('<div class="availability-warning">' +
-                M.str.availability.missingplugin + '</div>');
+                M.util.get_string('missingplugin', 'availability') + '</div>');
     } else {
         // Plugin is known.
         this.plugin = M.core_availability.form.plugins[json.type];
@@ -835,7 +951,7 @@ M.core_availability.Item.prototype.fillErrors = function(errors) {
     // If any errors were added, add the marker to this item.
     var errorLabel = this.node.one('> .label-warning');
     if (errors.length !== before && !errorLabel.get('firstChild')) {
-        errorLabel.appendChild(document.createTextNode(M.str.availability.invalid));
+        errorLabel.appendChild(document.createTextNode(M.util.get_string('invalid', 'availability')));
     } else if (errors.length === before && errorLabel.get('firstChild')) {
         errorLabel.get('firstChild').remove();
     }
@@ -851,7 +967,7 @@ M.core_availability.Item.prototype.renumber = function(number) {
     // Update heading for item.
     var headingParams = { number: number };
     if (this.plugin) {
-        headingParams.type = M.str['availability_' + this.pluginType].title;
+        headingParams.type = M.util.get_string('title', 'availability_' + this.pluginType);
     } else {
         headingParams.type = '[' + this.pluginType + ']';
     }
@@ -930,19 +1046,21 @@ M.core_availability.EyeIcon = function(individual, shown) {
     this.span.appendChild(icon);
 
     // Set up button text and icon.
-    var suffix = individual ? '_individual' : '_all';
-    var setHidden = function() {
-        icon.set('src', M.util.image_url('i/show', 'core'));
-        icon.set('alt', M.str.availability['hidden' + suffix]);
-        this.span.set('title', M.str.availability['hidden' + suffix] + ' \u2022 ' +
-                M.str.availability.show_verb);
-    };
-    var setShown = function() {
-        icon.set('src', M.util.image_url('i/hide', 'core'));
-        icon.set('alt', M.str.availability['shown' + suffix]);
-        this.span.set('title', M.str.availability['shown' + suffix] + ' \u2022 ' +
-                M.str.availability.hide_verb);
-    };
+    var suffix = individual ? '_individual' : '_all',
+        setHidden = function() {
+            var hiddenStr = M.util.get_string('hidden' + suffix, 'availability');
+            icon.set('src', M.util.image_url('i/show', 'core'));
+            icon.set('alt', hiddenStr);
+            this.span.set('title', hiddenStr + ' \u2022 ' +
+                    M.util.get_string('show_verb', 'availability'));
+        },
+        setShown = function() {
+            var shownStr = M.util.get_string('shown' + suffix, 'availability');
+            icon.set('src', M.util.image_url('i/hide', 'core'));
+            icon.set('alt', shownStr);
+            this.span.set('title', shownStr + ' \u2022 ' +
+                    M.util.get_string('hide_verb', 'availability'));
+        };
     if(shown) {
         setShown.call(this);
     } else {
@@ -987,8 +1105,8 @@ M.core_availability.EyeIcon.prototype.span = null;
  * @return {Boolean} True if this icon is set to 'hidden'
  */
 M.core_availability.EyeIcon.prototype.isHidden = function() {
-    var suffix = this.individual ? '_individual' : '_all';
-    var compare = M.str.availability['hidden' + suffix];
+    var suffix = this.individual ? '_individual' : '_all',
+        compare = M.util.get_string('hidden' + suffix, 'availability');
     return this.span.one('img').get('alt') === compare;
 };
 
@@ -1002,9 +1120,9 @@ M.core_availability.EyeIcon.prototype.isHidden = function() {
  */
 M.core_availability.DeleteIcon = function(toDelete) {
     this.span = Y.Node.create('<a class="availability-delete" href="#" title="' +
-            M.str.moodle['delete'] + '" role="button">');
+            M.util.get_string('delete', 'moodle') + '" role="button">');
     var img = Y.Node.create('<img src="' + M.util.image_url('t/delete', 'core') +
-            '" alt="' + M.str.moodle['delete'] + '" />');
+            '" alt="' + M.util.get_string('delete', 'moodle') + '" />');
     this.span.appendChild(img);
     var click = function(e) {
         e.preventDefault();
index ee4fa9d..7df7a2e 100644 (file)
@@ -3187,11 +3187,52 @@ class restore_block_instance_structure_step extends restore_structure_step {
         }
 
         if (!$bi->instance_allow_multiple()) {
-            if ($DB->record_exists_sql("SELECT bi.id
-                                          FROM {block_instances} bi
-                                          JOIN {block} b ON b.name = bi.blockname
-                                         WHERE bi.parentcontextid = ?
-                                           AND bi.blockname = ?", array($data->parentcontextid, $data->blockname))) {
+            // The block cannot be added twice, so we will check if the same block is already being
+            // displayed on the same page. For this, rather than mocking a page and using the block_manager
+            // we use a similar query to the one in block_manager::load_blocks(), this will give us
+            // a very good idea of the blocks already displayed in the context.
+            $params =  array(
+                'blockname' => $data->blockname
+            );
+
+            // Context matching test.
+            $context = context::instance_by_id($data->parentcontextid);
+            $contextsql = 'bi.parentcontextid = :contextid';
+            $params['contextid'] = $context->id;
+
+            $parentcontextids = $context->get_parent_context_ids();
+            if ($parentcontextids) {
+                list($parentcontextsql, $parentcontextparams) =
+                        $DB->get_in_or_equal($parentcontextids, SQL_PARAMS_NAMED);
+                $contextsql = "($contextsql OR (bi.showinsubcontexts = 1 AND bi.parentcontextid $parentcontextsql))";
+                $params = array_merge($params, $parentcontextparams);
+            }
+
+            // Page type pattern test.
+            $pagetypepatterns = matching_page_type_patterns_from_pattern($data->pagetypepattern);
+            list($pagetypepatternsql, $pagetypepatternparams) =
+                $DB->get_in_or_equal($pagetypepatterns, SQL_PARAMS_NAMED);
+            $params = array_merge($params, $pagetypepatternparams);
+
+            // Sub page pattern test.
+            $subpagepatternsql = 'bi.subpagepattern IS NULL';
+            if ($data->subpagepattern !== null) {
+                $subpagepatternsql = "($subpagepatternsql OR bi.subpagepattern = :subpagepattern)";
+                $params['subpagepattern'] = $data->subpagepattern;
+            }
+
+            $exists = $DB->record_exists_sql("SELECT bi.id
+                                                FROM {block_instances} bi
+                                                JOIN {block} b ON b.name = bi.blockname
+                                               WHERE bi.blockname = :blockname
+                                                 AND $contextsql
+                                                 AND bi.pagetypepattern $pagetypepatternsql
+                                                 AND $subpagepatternsql", $params);
+            if ($exists) {
+                // There is at least one very similar block visible on the page where we
+                // are trying to restore the block. In these circumstances the block API
+                // would not allow the user to add another instance of the block, so we
+                // apply the same rule here.
                 return false;
             }
         }
index 6f8ba31..38c72a8 100644 (file)
@@ -66,7 +66,7 @@ abstract class backup_factory {
 
         // Create database_logger, observing $CFG->backup_database_logger_level and defaulting to LOG_WARNING
         // and pointing to the backup_logs table
-        $dllevel = isset($CFG->backup_database_logger_level) ? $CFG->backup_database_logger_level : backup::LOG_WARNING;
+        $dllevel = isset($CFG->backup_database_logger_level) ? $CFG->backup_database_logger_level : $dfltloglevel;
         $columns = array('backupid' => $backupid);
         $enabledloggers[] = new database_logger($dllevel, 'timecreated', 'loglevel', 'message', 'backup_logs', $columns);
 
index 7f76cff..5adb973 100644 (file)
@@ -396,18 +396,36 @@ abstract class backup_cron_automated_helper {
             $results = $bc->get_results();
             $outcome = self::outcome_from_results($results);
             $file = $results['backup_destination']; // May be empty if file already moved to target location.
-            if (!file_exists($dir) || !is_dir($dir) || !is_writable($dir)) {
+
+            if (empty($dir) && $storage !== 0) {
+                // This is intentionally left as a warning instead of an error because of the current behaviour of backup settings.
+                // See MDL-48266 for details.
+                $bc->log('No directory specified for automated backups',
+                        backup::LOG_WARNING);
+                $outcome = self::BACKUP_STATUS_WARNING;
+            } else if (!file_exists($dir) || !is_dir($dir) || !is_writable($dir) && $storage !== 0) {
+                // If we need to copy the backup file to an external dir and it is not writable, change status to error.
+                $bc->log('Specified backup directory is not writable - ',
+                        backup::LOG_ERROR, $dir);
                 $dir = null;
+                $outcome = self::BACKUP_STATUS_ERROR;
             }
+
             // Copy file only if there was no error.
             if ($file && !empty($dir) && $storage !== 0 && $outcome != self::BACKUP_STATUS_ERROR) {
                 $filename = backup_plan_dbops::get_default_backup_filename($format, $type, $course->id, $users, $anonymised,
                         !$config->backup_shortname);
                 if (!$file->copy_content_to($dir.'/'.$filename)) {
+                    $bc->log('Attempt to copy backup file to the specified directory failed - ',
+                            backup::LOG_ERROR, $dir);
                     $outcome = self::BACKUP_STATUS_ERROR;
                 }
                 if ($outcome != self::BACKUP_STATUS_ERROR && $storage === 1) {
-                    $file->delete();
+                    if (!$file->delete()) {
+                        $outcome = self::BACKUP_STATUS_WARNING;
+                        $bc->log('Attempt to delete the backup file from course automated backup area failed - ',
+                                backup::LOG_WARNING, $file->get_filename());
+                    }
                 }
             }
 
index 963e471..4096371 100644 (file)
@@ -298,6 +298,10 @@ abstract class backup_helper {
                     @chmod($filedest, $CFG->filepermissions); // may fail because the permissions may not make sense outside of dataroot
                     unlink($filepath);
                     return null;
+                } else {
+                    $bc = backup_controller::load_controller($backupid);
+                    $bc->log('Attempt to copy backup file to the specified directory using filesystem failed - ',
+                            backup::LOG_WARNING, $dir);
                 }
                 // bad luck, try to deal with the file the old way - keep backup in file area if we can not copy to ext system
             }
index e5476ca..934e011 100644 (file)
@@ -90,7 +90,7 @@ Feature: Restore Moodle 2 course backups
       | id_startdate_month | January |
       | id_startdate_year | 2020 |
       | id_format | Weekly format |
-    And I press "Save changes"
+    And I press "Save and display"
     And I should see "1 January - 7 January"
     And I should see "Test forum name"
     And I click on "Edit settings" "link" in the "Administration" "block"
@@ -98,7 +98,7 @@ Feature: Restore Moodle 2 course backups
     And the field "id_format" matches value "Weekly format"
     And I set the following fields to these values:
       | id_format | Social format |
-    And I press "Save changes"
+    And I press "Save and display"
     And I should see "An open forum for chatting about anything you want to"
     And I click on "Edit settings" "link" in the "Administration" "block"
     And I expand all fieldsets
index 209e992..bf504b0 100644 (file)
@@ -132,7 +132,7 @@ Feature: Award badges
     And I follow "Edit settings"
     And I set the following fields to these values:
       | Enable completion tracking | Yes |
-    And I press "Save changes"
+    And I press "Save and display"
     And I turn editing mode on
     And I add a "Assignment" to section "1" and I fill the form with:
       | Assignment name | Test assignment name |
@@ -185,7 +185,7 @@ Feature: Award badges
     And I follow "Edit settings"
     And I set the following fields to these values:
       | Enable completion tracking | Yes |
-    And I press "Save changes"
+    And I press "Save and display"
     And I turn editing mode on
     And I add a "Assignment" to section "1" and I fill the form with:
       | Assignment name | Test assignment name |
index 1fd16c6..3619914 100644 (file)
@@ -33,3 +33,67 @@ Feature: Add a comment to the comments block
     When I follow "Show comments"
     And I add "I'm a comment from student1" comment to comments block
     Then I should see "I'm a comment from student1"
+
+  @javascript
+  Scenario: Test comment block pagination
+    When I add "Super test comment 01" comment to comments block
+    And I add "Super test comment 02" comment to comments block
+    And I add "Super test comment 03" comment to comments block
+    And I add "Super test comment 04" comment to comments block
+    And I add "Super test comment 05" comment to comments block
+    And I add "Super test comment 06" comment to comments block
+    And I add "Super test comment 07" comment to comments block
+    And I add "Super test comment 08" comment to comments block
+    And I add "Super test comment 09" comment to comments block
+    And I add "Super test comment 10" comment to comments block
+    And I add "Super test comment 11" comment to comments block
+    And I add "Super test comment 12" comment to comments block
+    And I add "Super test comment 13" comment to comments block
+    And I add "Super test comment 14" comment to comments block
+    And I add "Super test comment 15" comment to comments block
+    And I add "Super test comment 16" comment to comments block
+    And I add "Super test comment 17" comment to comments block
+    And I add "Super test comment 18" comment to comments block
+    And I add "Super test comment 19" comment to comments block
+    And I add "Super test comment 20" comment to comments block
+    And I add "Super test comment 21" comment to comments block
+    And I add "Super test comment 22" comment to comments block
+    And I add "Super test comment 23" comment to comments block
+    And I add "Super test comment 24" comment to comments block
+    And I add "Super test comment 25" comment to comments block
+    And I add "Super test comment 26" comment to comments block
+    And I add "Super test comment 27" comment to comments block
+    And I add "Super test comment 28" comment to comments block
+    And I add "Super test comment 29" comment to comments block
+    And I add "Super test comment 30" comment to comments block
+    And I add "Super test comment 31" comment to comments block
+    Then I should see "Super test comment 01"
+    And I should see "Super test comment 31"
+    And I follow "Course 1"
+    And I should not see "Super test comment 01"
+    And I should not see "Super test comment 02"
+    And I should not see "Super test comment 16"
+    And I should see "Super test comment 17"
+    And I should see "Super test comment 31"
+    And I should see "1" in the ".block_comments .comment-paging" "css_element"
+    And I should see "2" in the ".block_comments .comment-paging" "css_element"
+    And I should see "3" in the ".block_comments .comment-paging" "css_element"
+    And I should not see "4" in the ".block_comments .comment-paging" "css_element"
+    And I click on "2" "link" in the ".block_comments .comment-paging" "css_element"
+    And I should not see "Super test comment 01"
+    And I should see "Super test comment 02"
+    And I should see "Super test comment 16"
+    And I should not see "Super test comment 17"
+    And I should not see "Super test comment 31"
+    And I click on "3" "link" in the ".block_comments .comment-paging" "css_element"
+    And I should see "Super test comment 01"
+    And I should not see "Super test comment 02"
+    And I should not see "Super test comment 16"
+    And I should not see "Super test comment 17"
+    And I should not see "Super test comment 31"
+    And I click on "1" "link" in the ".block_comments .comment-paging" "css_element"
+    And I should not see "Super test comment 01"
+    And I should not see "Super test comment 02"
+    And I should not see "Super test comment 16"
+    And I should see "Super test comment 17"
+    And I should see "Super test comment 31"
index 094c668..491c342 100644 (file)
@@ -64,6 +64,9 @@ class behat_block_comments extends behat_base {
             $commentstextarea->setValue($comment);
 
             $this->find_link(get_string('savecomment'))->click();
+            // Delay after clicking so that additional comments will have unique time stamps.
+            // We delay 1 second which is all we need.
+            $this->getSession()->wait(1000, false);
 
         } else {
 
index c27067b..041bfe8 100644 (file)
@@ -57,6 +57,8 @@ class block_completionstatus extends block_base {
 
         // Create empty content.
         $this->content = new stdClass();
+        $this->content->text = '';
+        $this->content->footer = '';
 
         // Can edit settings?
         $can_edit = has_capability('moodle/course:update', $context);
@@ -67,13 +69,13 @@ class block_completionstatus extends block_base {
         // Don't display if completion isn't enabled!
         if (!completion_info::is_enabled_for_site()) {
             if ($can_edit) {
-                $this->content->text = get_string('completionnotenabledforsite', 'completion');
+                $this->content->text .= get_string('completionnotenabledforsite', 'completion');
             }
             return $this->content;
 
         } else if (!$info->is_enabled()) {
             if ($can_edit) {
-                $this->content->text = get_string('completionnotenabledforcourse', 'completion');
+                $this->content->text .= get_string('completionnotenabledforcourse', 'completion');
             }
             return $this->content;
         }
@@ -84,7 +86,7 @@ class block_completionstatus extends block_base {
         // Check if this course has any criteria.
         if (empty($completions)) {
             if ($can_edit) {
-                $this->content->text = get_string('nocriteriaset', 'completion');
+                $this->content->text .= get_string('nocriteriaset', 'completion');
             }
             return $this->content;
         }
@@ -230,11 +232,11 @@ class block_completionstatus extends block_base {
             $rows = array_merge($rows, $srows);
 
             $table->data = $rows;
-            $this->content->text = html_writer::table($table);
+            $this->content->text .= html_writer::table($table);
 
             // Display link to detailed view.
             $details = new moodle_url('/blocks/completionstatus/details.php', array('course' => $course->id));
-            $this->content->footer = html_writer::link($details, get_string('moredetails', 'completion'));
+            $this->content->footer .= html_writer::link($details, get_string('moredetails', 'completion'));
         } else {
             // If user is not enrolled, show error.
             $this->content->text = get_string('nottracked', 'completion');
index 2fd28ca..4aa815f 100644 (file)
@@ -40,7 +40,7 @@ Feature: Expand the courses nodes within the navigation block
     And I click on "Edit settings" "link" in the "Administration" "block"
     And I set the following fields to these values:
       | Allow guest access | Yes |
-    And I press "Save changes"
+    And I press "Save and display"
     And I set the following administration settings values:
       | Show all courses | 1 |
     And I log out
index e02b874..b483ee2 100644 (file)
Binary files a/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-debug.js and b/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-debug.js differ
index 9431ffc..463c78c 100644 (file)
Binary files a/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-min.js and b/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-min.js differ
index 18bccb6..37b4dd9 100644 (file)
Binary files a/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation.js and b/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation.js differ
index 52f2f08..9544816 100644 (file)
@@ -748,8 +748,8 @@ BRANCH.prototype = {
             url = M.cfg.wwwroot+'/course/index.php?categoryid=' + branch.get('key');
         }
         branch.addChild({
-            name : M.str.moodle.viewallcourses,
-            title : M.str.moodle.viewallcourses,
+            name : M.util.get_string('viewallcourses', 'moodle'),
+            title : M.util.get_string('viewallcourses', 'moodle'),
             link : url,
             haschildren : false,
             icon : {'pix':"i/navigationitem",'component':'moodle'}
index 1768980..1509125 100644 (file)
@@ -34,12 +34,12 @@ Feature: Latest news block displays the course latest news
     And I follow "Edit settings"
     And I set the following fields to these values:
       | News items to show | 2 |
-    And I press "Save changes"
+    And I press "Save and display"
     And I should not see "Discussion One" in the "Latest news" "block"
     And I should see "Discussion Two" in the "Latest news" "block"
     And I should see "Discussion Three" in the "Latest news" "block"
     And I follow "Edit settings"
     And I set the following fields to these values:
       | News items to show | 0 |
-    And I press "Save changes"
+    And I press "Save and display"
     And "Latest news" "block" should not exist
index a84e3d1..e28c75b 100644 (file)
@@ -56,26 +56,36 @@ class block_site_main_menu extends block_list {
         if (!$isediting) {
             $modinfo = get_fast_modinfo($course);
             if (!empty($modinfo->sections[0])) {
-                $options = array('overflowdiv'=>true);
                 foreach($modinfo->sections[0] as $cmid) {
                     $cm = $modinfo->cms[$cmid];
                     if (!$cm->uservisible) {
                         continue;
                     }
 
-                    $content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
-                    $instancename = $cm->get_formatted_name();
-
-                    if (!($url = $cm->url)) {
-                        $this->content->items[] = $content;
-                        $this->content->icons[] = '';
+                    if ($cm->indent > 0) {
+                        $indent = '<div class="mod-indent mod-indent-'.$cm->indent.'"></div>';
                     } else {
-                        $linkcss = $cm->visible ? '' : ' class="dimmed" ';
-                        //Accessibility: incidental image - should be empty Alt text
+                        $indent = '';
+                    }
+
+                    if (!empty($cm->url)) {
+                        $attrs = array();
+                        $attrs['title'] = $cm->modfullname;
+                        $attrs['class'] = $cm->extraclasses . ' activity-action';
+                        if ($cm->onclick) {
+                            $attrs['id'] = html_writer::random_id('onclick');
+                            $OUTPUT->add_action_handler(new component_action('click', $cm->onclick), $attrs['id']);
+                        }
+                        if (!$cm->visible) {
+                            $attrs['class'] .= ' dimmed';
+                        }
                         $icon = '<img src="' . $cm->get_icon_url() . '" class="icon" alt="" />';
-                        $this->content->items[] = '<a title="'.$cm->modplural.'" '.$linkcss.' '.$cm->extra.
-                                ' href="' . $url . '">' . $icon . $instancename . '</a>';
+                        $content = html_writer::link($cm->url, $icon . $cm->get_formatted_name(), $attrs);
+                    } else {
+                        $content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
                     }
+
+                    $this->content->items[] = $indent.html_writer::div($content, 'main-menu-content');
                 }
             }
             return $this->content;
@@ -112,7 +122,7 @@ class block_site_main_menu extends block_list {
                     continue;
                 }
                 if (!$ismoving) {
-                    $actions = course_get_cm_edit_actions($mod, -1);
+                    $actions = course_get_cm_edit_actions($mod, $mod->indent);
 
                     // Prepend list of actions with the 'move' action.
                     $actions = array('move' => new action_menu_link_primary(
@@ -137,19 +147,31 @@ class block_site_main_menu extends block_list {
                             '<img style="height:16px; width:80px; border:0px" src="'.$OUTPUT->pix_url('movehere') . '" alt="'.$strmovehere.'" /></a>';
                         $this->content->icons[] = '';
                     }
-                    $content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
-                    $instancename = $mod->get_formatted_name();
-                    $linkcss = $mod->visible ? '' : ' class="dimmed" ';
-
-                    if (!($url = $mod->url)) {
-                        $this->content->items[] = $content . $editbuttons;
-                        $this->content->icons[] = '';
+                    if ($mod->indent > 0) {
+                        $indent = '<div class="mod-indent mod-indent-'.$mod->indent.'"></div>';
+                    } else {
+                        $indent = '';
+                    }
+                    $url = $mod->url;
+                    if (!$url) {
+                        $content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
                     } else {
                         //Accessibility: incidental image - should be empty Alt text
+                        $attrs = array();
+                        $attrs['title'] = $mod->modfullname;
+                        $attrs['class'] = $mod->extraclasses . ' activity-action';
+                        if ($mod->onclick) {
+                            $attrs['id'] = html_writer::random_id('onclick');
+                            $OUTPUT->add_action_handler(new component_action('click', $mod->onclick), $attrs['id']);
+                        }
+                        if (!$mod->visible) {
+                            $attrs['class'] .= ' dimmed';
+                        }
+
                         $icon = '<img src="' . $mod->get_icon_url() . '" class="icon" alt="" />';
-                        $this->content->items[] = '<a title="' . $mod->modfullname . '" ' . $linkcss . ' ' . $mod->extra .
-                            ' href="' . $url . '">' . $icon . $instancename . '</a>' . $editbuttons;
+                        $content = html_writer::link($url, $icon . $mod->get_formatted_name(), $attrs);
                     }
+                    $this->content->items[] = $indent.html_writer::div($content . $editbuttons, 'main-menu-content');
                 }
             }
         }
index dd8f92f..0e15ab7 100644 (file)
@@ -5,3 +5,6 @@
 .block_site_main_menu li .buttons a img{ vertical-align: text-bottom;}
 .block_site_main_menu .footer { margin-top: 1em; }
 .block_site_main_menu .section_add_menus noscript div { display: inline;}
+.block_site_main_menu .mod-indent,
+.block_site_main_menu .main-menu-content { display: table-cell; }
+.block_site_main_menu .main-menu-content > .activity-action { display: block; }
index b51143a..b71303c 100644 (file)
@@ -32,3 +32,27 @@ Feature: Add and configure blocks throughout the site
     And I follow "Course 1"
     # The first block matching the pattern should be top-left block
     And I should see "Comments" in the "//*[@id='region-pre' or @id='block-region-side-pre']/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' block ')]" "xpath_element"
+
+  Scenario: Blocks on the my home page cannot have roles assigned to them
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | manager1 | Manager | 1 | manager1@asd.com |
+    And I log in as "manager1"
+    And I click on "My home" "link" in the "Navigation" "block"
+    When I press "Customise this page"
+    Then I should not see "Assign roles in Navigation block"
+
+  Scenario: Blocks on courses can have roles assigned to them
+    Given the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And the following "users" exist:
+      | username | firstname | lastname | email               |
+      | teacher1 | teacher   | 1        | teacher@example.com |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | C1     | editingteacher |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I follow "Turn editing on"
+    Then I should see "Assign roles in Search forums block"
index 2abfabc..5a2ef3b 100644 (file)
@@ -65,13 +65,13 @@ class cachestore_addinstance_form extends moodleform {
         }
 
         if (is_array($locks)) {
-            $form->addElement('select', 'lock', get_string('lockmethod', 'cache'), $locks);
-            $form->addHelpButton('lock', 'lockmethod', 'cache');
+            $form->addElement('select', 'lock', get_string('locking', 'cache'), $locks);
+            $form->addHelpButton('lock', 'locking', 'cache');
             $form->setType('lock', PARAM_ALPHANUMEXT);
         } else {
             $form->addElement('hidden', 'lock', '');
             $form->setType('lock', PARAM_ALPHANUMEXT);
-            $form->addElement('static', 'lock-value', get_string('lockmethod', 'cache'),
+            $form->addElement('static', 'lock-value', get_string('locking', 'cache'),
                     '<em>'.get_string('nativelocking', 'cache').'</em>');
         }
 
index 1ccb4bb..f6ceb1d 100644 (file)
@@ -53,7 +53,7 @@ class core_cache_renderer extends plugin_renderer_base {
             get_string('mappings', 'cache'),
             get_string('modes', 'cache'),
             get_string('supports', 'cache'),
-            get_string('lockingmeans', 'cache'),
+            get_string('locking', 'cache') . ' ' . $this->output->help_icon('locking', 'cache'),
             get_string('actions', 'cache'),
         );
         $table->colclasses = array(
index ac5917e..902693d 100644 (file)
@@ -142,8 +142,8 @@ class core_calendar_external extends external_api {
                                              "Time from which events should be returned",
                                              VALUE_DEFAULT, 0, NULL_ALLOWED),
                                     'timeend' => new external_value(PARAM_INT,
-                                             "Time to which the events should be returned",
-                                             VALUE_DEFAULT, time(), NULL_ALLOWED),
+                                             "Time to which the events should be returned. We treat 0 and null as no end",
+                                             VALUE_DEFAULT, 0, NULL_ALLOWED),
                                     'ignorehidden' => new external_value(PARAM_BOOL,
                                              "Ignore hidden events or not",
                                              VALUE_DEFAULT, true, NULL_ALLOWED),
@@ -215,6 +215,11 @@ class core_calendar_external extends external_api {
             $funcparam['courses'][] = $SITE->id;
         }
 
+        // We treat 0 and null as no end.
+        if (empty($params['options']['timeend'])) {
+            $params['options']['timeend'] = PHP_INT_MAX;
+        }
+
         $eventlist = calendar_get_events($params['options']['timestart'], $params['options']['timeend'], $funcparam['users'], $funcparam['groups'],
                 $funcparam['courses'], true, $params['options']['ignorehidden']);
         // WS expects arrays.
index b8582ab..4fb8764 100644 (file)
@@ -292,7 +292,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
         $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 
         // Check to see if we got all events.
-        $this->assertEquals(4, count($events['events']));
+        $this->assertEquals(5, count($events['events']));
         $this->assertEquals(0, count($events['warnings']));
         $options = array ('siteevents' => true, 'userevents' => true, 'timeend' => time() + 7*WEEKSECS);
         $events = core_calendar_external::get_calendar_events($paramevents, $options);
@@ -314,7 +314,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
         $this->assertEquals(4, count($events['events'])); // site, user, both course events.
         $this->assertEquals(1, count($events['warnings'])); // group.
 
-        $options = array ('siteevents' => true, 'userevents' => true);
+        $options = array ('siteevents' => true, 'userevents' => true, 'timeend' => time() + HOURSECS);
         $events = core_calendar_external::get_calendar_events($paramevents, $options);
         $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
         $this->assertEquals(3, count($events['events'])); // site, user, one course event.
index 72a666a..a5d31af 100644 (file)
@@ -1,6 +1,11 @@
 This files describes API changes in /calendar/* ,
 information provided here is intended especially for developers.
 
+=== 2.9 ===
+default values changes in code:
+* core_calendar_external::get_calendar_events_parameters() 'timeend' default option changed; now, by default,
+  all events are returned, not only the past ones.
+
 === 2.5 ===
 required changes in code:
 * calendar_add_icalendar_event() now requires a valid subscriptionid
index cea8c66..17e9736 100644 (file)
@@ -48,7 +48,14 @@ class comment {
     private $courseid;
     /** @var stdClass course module object, only be used to help find pluginname automatically */
     private $cm;
-    /** @var string The component that this comment is for. It is STRONGLY recommended to set this. */
+    /**
+     * The component that this comment is for.
+     *
+     * It is STRONGLY recommended to set this.
+     * Added as a database field in 2.9, old comments will have a null component.
+     *
+     * @var string
+     */
     private $component;
     /** @var string This is calculated by normalising the component */
     private $pluginname;
@@ -241,10 +248,11 @@ class comment {
         }
         // setup variables for non-js interface
         self::$nonjs = optional_param('nonjscomment', '', PARAM_ALPHANUM);
-        self::$comment_itemid  = optional_param('comment_itemid',  '', PARAM_INT);
+        self::$comment_itemid = optional_param('comment_itemid',  '', PARAM_INT);
+        self::$comment_component = optional_param('comment_component', '', PARAM_COMPONENT);
         self::$comment_context = optional_param('comment_context', '', PARAM_INT);
-        self::$comment_page    = optional_param('comment_page',    '', PARAM_INT);
-        self::$comment_area    = optional_param('comment_area',    '', PARAM_AREA);
+        self::$comment_page = optional_param('comment_page',    '', PARAM_INT);
+        self::$comment_area = optional_param('comment_area',    '', PARAM_AREA);
 
         $page->requires->strings_for_js(array(
                 'addcomment',
@@ -264,6 +272,7 @@ class comment {
      * invalidates permission checks.
      * A coding_error is now thrown if code attempts to change the component.
      *
+     * @throws coding_exception if you try to change the component after it has been set.
      * @param string $component
      */
     public function set_component($component) {
@@ -325,6 +334,7 @@ class comment {
             'nonjscomment'    => true,
             'comment_itemid'  => $this->itemid,
             'comment_context' => $this->context->id,
+            'comment_component' => $this->get_component(),
             'comment_area'    => $this->commentarea,
         ));
         $link->remove_params(array('comment_page'));
@@ -530,10 +540,19 @@ class comment {
         $perpage = (!empty($CFG->commentsperpage))?$CFG->commentsperpage:15;
         $start = $page * $perpage;
         $ufields = user_picture::fields('u');
+
+        list($componentwhere, $component) = $this->get_component_select_sql('c');
+        if ($component) {
+            $params['component'] = $component;
+        }
+
         $sql = "SELECT $ufields, c.id AS cid, c.content AS ccontent, c.format AS cformat, c.timecreated AS ctimecreated
                   FROM {comments} c
                   JOIN {user} u ON u.id = c.userid
-                 WHERE c.contextid = :contextid AND c.commentarea = :commentarea AND c.itemid = :itemid
+                 WHERE c.contextid = :contextid AND
+                       c.commentarea = :commentarea AND
+                       c.itemid = :itemid AND
+                       $componentwhere
               ORDER BY c.timecreated DESC";
         $params['contextid'] = $this->contextid;
         $params['commentarea'] = $this->commentarea;
@@ -573,6 +592,25 @@ class comment {
         return $comments;
     }
 
+    /**
+     * Returns an SQL fragment and param for selecting on component.
+     * @param string $alias
+     * @return array
+     */
+    protected function get_component_select_sql($alias = '') {
+        $component = $this->get_component();
+        if ($alias) {
+            $alias = $alias.'.';
+        }
+        if (empty($component)) {
+            $componentwhere = "{$alias}component IS NULL";
+            $component = null;
+        } else {
+            $componentwhere = "({$alias}component IS NULL OR {$alias}component = :component)";
+        }
+        return array($componentwhere, $component);
+    }
+
     /**
      * Returns the number of comments associated with the details of this object
      *
@@ -582,7 +620,18 @@ class comment {
     public function count() {
         global $DB;
         if ($this->totalcommentcount === null) {
-            $this->totalcommentcount = $DB->count_records('comments', array('itemid' => $this->itemid, 'commentarea' => $this->commentarea, 'contextid' => $this->context->id));
+            list($where, $component) = $this->get_component_select_sql();
+            $where .= ' AND itemid = :itemid AND commentarea = :commentarea AND contextid = :contextid';
+            $params = array(
+                'itemid' => $this->itemid,
+                'commentarea' => $this->commentarea,
+                'contextid' => $this->context->id,
+            );
+            if ($component) {
+                $params['component'] = $component;
+            }
+
+            $this->totalcommentcount = $DB->count_records_select('comments', $where, $params);
         }
         return $this->totalcommentcount;
     }
@@ -641,6 +690,7 @@ class comment {
         $newcmt->contextid    = $this->contextid;
         $newcmt->commentarea  = $this->commentarea;
         $newcmt->itemid       = $this->itemid;
+        $newcmt->component    = !empty($this->component) ? $this->component : null;
         $newcmt->content      = $content;
         $newcmt->format       = $format;
         $newcmt->userid       = $USER->id;
@@ -785,10 +835,11 @@ class comment {
             return '';
         }
 
-        $html = '';
         if (!(self::$comment_itemid == $this->itemid &&
             self::$comment_context == $this->context->id &&
-            self::$comment_area == $this->commentarea)) {
+            self::$comment_area == $this->commentarea &&
+            self::$comment_component == $this->component
+        )) {
             $page = 0;
         }
         $comments = $this->get_comments($page);
@@ -918,13 +969,24 @@ class comment {
     }
 
     /**
-     * Returns the component associated with the comment
+     * Returns the component associated with the comment.
+     *
      * @return string
      */
-    public function get_compontent() {
+    public function get_component() {
         return $this->component;
     }
 
+    /**
+     * Do not call! I am a deprecated method because of the typo in my name.
+     * @deprecated since 2.9
+     * @see comment::get_component()
+     * @return string
+     */
+    public function get_compontent() {
+        return $this->get_component();
+    }
+
     /**
      * Returns the context associated with the comment
      * @return stdClass
index 606d335..14a4965 100644 (file)
@@ -62,7 +62,7 @@ class comment_manager {
         $comments = array();
 
         $usernamefields = get_all_user_name_fields(true, 'u');
-        $sql = "SELECT c.id, c.contextid, c.itemid, c.commentarea, c.userid, c.content, $usernamefields, c.timecreated
+        $sql = "SELECT c.id, c.contextid, c.itemid, c.component, c.commentarea, c.userid, c.content, $usernamefields, c.timecreated
                   FROM {comments} c
                   JOIN {user} u
                        ON u.id=c.userid
index 89b6344..5125a88 100644 (file)
@@ -105,4 +105,20 @@ class behat_completion extends behat_base {
         return $steps;
     }
 
+    /**
+     * Toggles completion tracking for course being in the course page.
+     *
+     * @When /^completion tracking is "(?P<completion_status_string>Enabled|Disabled)" in current course$/
+     * @param string $completionstatus The status, enabled or disabled.
+     */
+    public function completion_is_toggled_in_course($completionstatus) {
+
+        $toggle = strtolower($completionstatus) == 'enabled' ? get_string('yes') : get_string('no');
+
+        return array(
+            new Given('I follow "'.get_string('editsettings').'"'),
+            new Given('I set the field "'.get_string('enablecompletion', 'completion').'" to "'.$toggle.'"'),
+            new Given('I press "'.get_string('savechangesanddisplay').'"')
+        );
+    }
 }
index 7c29e65..303d79f 100644 (file)
@@ -28,7 +28,7 @@ Feature: Allow students to manually mark an activity as complete
     And I click on "Edit settings" "link" in the "Administration" "block"
     And I set the following fields to these values:
       | Enable completion tracking | Yes |
-    And I press "Save changes"
+    And I press "Save and display"
     When I add a "Forum" to section "1" and I fill the form with:
       | Forum name | Test forum name |
       | Description | Test forum description |
index 584506a..69239a4 100644 (file)
@@ -30,7 +30,7 @@ Feature: Restrict sections availability through completion or grade conditions
     And I click on "Edit settings" "link" in the "Administration" "block"
     And I set the following fields to these values:
       | Enable completion tracking | Yes |
-    And I press "Save changes"
+    And I press "Save and display"
     And I add a "Label" to section "1" and I fill the form with:
       | Label text | Test label |
       | Completion tracking | Students can manually mark the activity as completed |
diff --git a/completion/tests/behat/teacher_manual_completion.feature b/completion/tests/behat/teacher_manual_completion.feature
new file mode 100644 (file)
index 0000000..d15691a
--- /dev/null
@@ -0,0 +1,45 @@
+@core @core_completion
+Feature: Allow teachers to manually mark users as complete when configured
+  In order for teachers to mark students as complete
+  As a teacher
+  I need to be able to use the completion report mark complete functionality
+
+  Scenario: Mark a student as complete using the completion report
+    Given the following "courses" exist:
+      | fullname          | shortname | category |
+      | Completion course | CC1       | 0        |
+    And the following "users" exist:
+      | username | firstname | lastname | email                |
+      | student1 | Student   | First    | student1@example.com |
+      | teacher1 | Teacher   | First    | teacher1@example.com |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | student1 | CC1    | student        |
+      | teacher1 | CC1    | editingteacher |
+    And I log in as "admin"
+    And I set the following administration settings values:
+      | Enable completion tracking | 1 |
+    And I am on homepage
+    And I follow "Completion course"
+    And completion tracking is "Enabled" in current course
+    And I follow "Course completion"
+    And I set the field "Teacher" to "1"
+    And I press "Save changes"
+    And I turn editing mode on
+    And I add the "Course completion status" block
+    And I log out
+    And I log in as "student1"
+    And I follow "Completion course"
+    And I should see "Status: Not yet started"
+    And I log out
+    When I log in as "teacher1"
+    And I follow "Completion course"
+    And I follow "View course report"
+    And I should see "Student First"
+    And I follow "Click to mark user complete"
+    And I trigger cron
+    And I am on homepage
+    And I log out
+    Then I log in as "student1"
+    And I follow "Completion course"
+    And I should see "Status: Complete"
index f13757b..b553923 100644 (file)
@@ -315,7 +315,7 @@ class helper {
         // Edit.
         if ($course->can_edit()) {
             $actions[] = array(
-                'url' => new \moodle_url('/course/edit.php', array('id' => $course->id)),
+                'url' => new \moodle_url('/course/edit.php', array('id' => $course->id, 'returnto' => 'catmanage')),
                 'icon' => new \pix_icon('t/edit', \get_string('edit')),
                 'attributes' => array('class' => 'action-edit')
             );
index 4d9542a..eb548f3 100644 (file)
@@ -18,17 +18,19 @@ M.core_completion.init = function(Y) {
 
         } else {
             var current = args.state.get('value');
-            var modulename = args.modulename.get('value');
+            var modulename = args.modulename.get('value'),
+                altstr,
+                titlestr;
             if (current == 1) {
-                var altstr = M.str.completion['completion-alt-manual-y'].replace('{$a}', modulename);
-                var titlestr = M.str.completion['completion-title-manual-y'].replace('{$a}', modulename);
+                altstr = M.util.get_string('completion-alt-manual-y', 'completion', modulename);
+                titlestr = M.util.get_string('completion-title-manual-y', 'completion', modulename);
                 args.state.set('value', 0);
                 args.image.set('src', M.util.image_url('i/completion-manual-y', 'moodle'));
                 args.image.set('alt', altstr);
                 args.image.set('title', titlestr);
             } else {
-                var altstr = M.str.completion['completion-alt-manual-n'].replace('{$a}', modulename);
-                var titlestr = M.str.completion['completion-title-manual-n'].replace('{$a}', modulename);
+                altstr = M.util.get_string('completion-alt-manual-n', 'completion', modulename);
+                titlestr = M.util.get_string('completion-title-manual-n', 'completion', modulename);
                 args.state.set('value', 1);
                 args.image.set('src', M.util.image_url('i/completion-manual-n', 'moodle'));
                 args.image.set('alt', altstr);
index f21ca88..bbe8992 100644 (file)
@@ -29,6 +29,35 @@ require_once('edit_form.php');
 $id = optional_param('id', 0, PARAM_INT); // Course id.
 $categoryid = optional_param('category', 0, PARAM_INT); // Course category - can be changed in edit form.
 $returnto = optional_param('returnto', 0, PARAM_ALPHANUM); // Generic navigation return page switch.
+$returnurl = optional_param('returnurl', '', PARAM_LOCALURL); // A return URL. returnto must also be set to 'url'.
+
+if ($returnto === 'url' && confirm_sesskey() && $returnurl) {
+    // If returnto is 'url' then $returnurl may be used as the destination to return to after saving or cancelling.
+    // Sesskey must be specified, and would be set by the form anyway.
+    $returnurl = new moodle_url($returnurl);
+} else {
+    if (!empty($id)) {
+        $returnurl = new moodle_url($CFG->wwwroot . '/course/view.php', array('id' => $id));
+    } else {
+        $returnurl = new moodle_url($CFG->wwwroot . '/course/');
+    }
+    if ($returnto !== 0) {
+        switch ($returnto) {
+            case 'category':
+                $returnurl = new moodle_url($CFG->wwwroot . '/course/index.php', array('categoryid' => $categoryid));
+                break;
+            case 'catmanage':
+                $returnurl = new moodle_url($CFG->wwwroot . '/course/management.php', array('categoryid' => $categoryid));
+                break;
+            case 'topcatmanage':
+                $returnurl = new moodle_url($CFG->wwwroot . '/course/management.php');
+                break;
+            case 'topcat':
+                $returnurl = new moodle_url($CFG->wwwroot . '/course/');
+                break;
+        }
+    }
+}
 
 $PAGE->set_pagelayout('admin');
 if ($id) {
@@ -36,6 +65,12 @@ if ($id) {
 } else {
     $pageparams = array('category' => $categoryid);
 }
+if ($returnto !== 0) {
+    $pageparams['returnto'] = $returnto;
+    if ($returnto === 'url' && $returnurl) {
+        $pageparams['returnurl'] = $returnurl;
+    }
+}
 $PAGE->set_url('/course/edit.php', $pageparams);
 
 // Basic access control checks.
@@ -98,31 +133,17 @@ if (!empty($course)) {
 }
 
 // First create the form.
-$editform = new course_edit_form(NULL, array('course'=>$course, 'category'=>$category, 'editoroptions'=>$editoroptions, 'returnto'=>$returnto));
+$args = array(
+    'course' => $course,
+    'category' => $category,
+    'editoroptions' => $editoroptions,
+    'returnto' => $returnto,
+    'returnurl' => $returnurl
+);
+$editform = new course_edit_form(null, $args);
 if ($editform->is_cancelled()) {
-        switch ($returnto) {
-            case 'category':
-                $url = new moodle_url($CFG->wwwroot.'/course/index.php', array('categoryid' => $categoryid));
-                break;
-            case 'catmanage':
-                $url = new moodle_url($CFG->wwwroot.'/course/management.php', array('categoryid' => $categoryid));
-                break;
-            case 'topcatmanage':
-                $url = new moodle_url($CFG->wwwroot.'/course/management.php');
-                break;
-            case 'topcat':
-                $url = new moodle_url($CFG->wwwroot.'/course/');
-                break;
-            default:
-                if (!empty($course->id)) {
-                    $url = new moodle_url($CFG->wwwroot.'/course/view.php', array('id'=>$course->id));
-                } else {
-                    $url = new moodle_url($CFG->wwwroot.'/course/');
-                }
-                break;
-        }
-        redirect($url);
-
+    // The form has been cancelled, take them back to what ever the return to is.
+    redirect($returnurl);
 } else if ($data = $editform->get_data()) {
     // Process data if submitted.
     if (empty($course->id)) {
@@ -136,14 +157,20 @@ if ($editform->is_cancelled()) {
             // Deal with course creators - enrol them internally with default role.
             enrol_try_internal_enrol($course->id, $USER->id, $CFG->creatornewroleid);
         }
-        if (!is_enrolled($context)) {
+
+        // The URL to take them to if they chose save and display.
+        $courseurl = new moodle_url('/course/view.php', array('id' => $course->id));
+
+        // If they choose to save and display, and they are not enrolled take them to the enrolments page instead.
+        if (!is_enrolled($context) && isset($data->saveanddisplay)) {
             // Redirect to manual enrolment page if possible.
             $instances = enrol_get_instances($course->id, true);
             foreach($instances as $instance) {
                 if ($plugin = enrol_get_plugin($instance->enrol)) {
                     if ($plugin->get_manual_enrol_link($instance)) {
                         // We know that the ajax enrol UI will have an option to enrol.
-                        redirect(new moodle_url('/enrol/users.php', array('id'=>$course->id)));
+                        $courseurl = new moodle_url('/enrol/users.php', array('id' => $course->id));
+                        break;
                     }
                 }
             }
@@ -151,10 +178,17 @@ if ($editform->is_cancelled()) {
     } else {
         // Save any changes to the files used in the editor.
         update_course($data, $editoroptions);
+        // Set the URL to take them too if they choose save and display.
+        $courseurl = new moodle_url('/course/view.php', array('id' => $course->id));
     }
 
-    // Redirect user to newly created/updated course.
-    redirect(new moodle_url('/course/view.php', array('id' => $course->id)));
+    if (isset($data->saveanddisplay)) {
+        // Redirect user to newly created/updated course.
+        redirect($courseurl);
+    } else {
+        // Save and return. Take them back to wherever.
+        redirect($returnurl);
+    }
 }
 
 // Print the form.
index 1de01c9..54359aa 100644 (file)
@@ -27,6 +27,7 @@ class course_edit_form extends moodleform {
         $category      = $this->_customdata['category'];
         $editoroptions = $this->_customdata['editoroptions'];
         $returnto = $this->_customdata['returnto'];
+        $returnurl = $this->_customdata['returnurl'];
 
         $systemcontext   = context_system::instance();
         $categorycontext = context_coursecat::instance($category->id);
@@ -51,6 +52,10 @@ class course_edit_form extends moodleform {
         $mform->setType('returnto', PARAM_ALPHANUM);
         $mform->setConstant('returnto', $returnto);
 
+        $mform->addElement('hidden', 'returnurl', null);
+        $mform->setType('returnurl', PARAM_LOCALURL);
+        $mform->setConstant('returnurl', $returnurl);
+
         $mform->addElement('text','fullname', get_string('fullnamecourse'),'maxlength="254" size="50"');
         $mform->addHelpButton('fullname', 'fullnamecourse');
         $mform->addRule('fullname', get_string('missingfullname'), 'required', null, 'client');
@@ -296,7 +301,15 @@ class course_edit_form extends moodleform {
             }
         }
 
-        $this->add_action_buttons();
+        // When two elements we need a group.
+        $buttonarray = array();
+        if ($returnto !== 0) {
+            $buttonarray[] = &$mform->createElement('submit', 'saveandreturn', get_string('savechangesandreturn'));
+        }
+        $buttonarray[] = &$mform->createElement('submit', 'saveanddisplay', get_string('savechangesanddisplay'));
+        $buttonarray[] = &$mform->createElement('cancel');
+        $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
+        $mform->closeHeaderBefore('buttonar');
 
         $mform->addElement('hidden', 'id', null);
         $mform->setType('id', PARAM_INT);
index 33b9f55..eaeaff7 100644 (file)
@@ -395,6 +395,7 @@ abstract class format_base {
      * @return null|moodle_url
      */
     public function get_view_url($section, $options = array()) {
+        global $CFG;
         $course = $this->get_course();
         $url = new moodle_url('/course/view.php', array('id' => $course->id));
 
@@ -405,7 +406,7 @@ abstract class format_base {
         } else {
             $sectionno = $section;
         }
-        if (!empty($options['navigation']) && $sectionno !== null) {
+        if (empty($CFG->linkcoursesections) && !empty($options['navigation']) && $sectionno !== null) {
             // by default assume that sections are never displayed on separate pages
             return null;
         }
index f7e91bb..cb0a7ed 100644 (file)
@@ -75,6 +75,7 @@ class format_topics extends format_base {
      * @return null|moodle_url
      */
     public function get_view_url($section, $options = array()) {
+        global $CFG;
         $course = $this->get_course();
         $url = new moodle_url('/course/view.php', array('id' => $course->id));
 
@@ -101,7 +102,7 @@ class format_topics extends format_base {
             if ($sectionno != 0 && $usercoursedisplay == COURSE_DISPLAY_MULTIPAGE) {
                 $url->param('section', $sectionno);
             } else {
-                if (!empty($options['navigation'])) {
+                if (empty($CFG->linkcoursesections) && !empty($options['navigation'])) {
                     return null;
                 }
                 $url->set_anchor('section-'.$sectionno);
index 73d97c2..66152d2 100644 (file)
@@ -82,6 +82,7 @@ class format_weeks extends format_base {
      * @return null|moodle_url
      */
     public function get_view_url($section, $options = array()) {
+        global $CFG;
         $course = $this->get_course();
         $url = new moodle_url('/course/view.php', array('id' => $course->id));
 
@@ -108,7 +109,7 @@ class format_weeks extends format_base {
             if ($sectionno != 0 && $usercoursedisplay == COURSE_DISPLAY_MULTIPAGE) {
                 $url->param('section', $sectionno);
             } else {
-                if (!empty($options['navigation'])) {
+                if (empty($CFG->linkcoursesections) && !empty($options['navigation'])) {
                     return null;
                 }
                 $url->set_anchor('section-'.$sectionno);
index 33014d6..6a3a845 100644 (file)
@@ -436,6 +436,14 @@ abstract class moodleform_mod extends moodleform {
         }
 
         if (!empty($CFG->enableavailability)) {
+            // Add special button to end of previous section if groups/groupings
+            // are enabled.
+            if ($this->_features->groups || $this->_features->groupings) {
+                $mform->addElement('static', 'restrictgroupbutton', '',
+                        html_writer::tag('button', get_string('restrictbygroup', 'availability'),
+                        array('id' => 'restrictbygroup', 'disabled' => 'disabled')));
+            }
+
             // Availability field. This is just a textarea; the user interface
             // interaction is all implemented in JavaScript.
             $mform->addElement('header', 'availabilityconditionsheader',
index 35dbee9..3380e36 100644 (file)
@@ -25,7 +25,7 @@ Feature: Toggle activities groups mode from the course page
     And I set the following fields to these values:
       | Group mode | No groups |
       | Force group mode | No |
-    When I press "Save changes"
+    When I press "Save and display"
     Then "No groups (Click to change)" "link" should exist
     And "//a/child::img[contains(@src, 'groupn')]" "xpath_element" should exist
     And I click on "No groups (Click to change)" "link" in the "Test forum name" activity
index aaacef4..a2d102c 100644 (file)
@@ -108,7 +108,7 @@ class behat_course extends behat_base {
             $steps[] = new Given('I set the following fields to these values:', $table);
         }
 
-        $steps[] = new Given('I press "' . get_string('savechanges') . '"');
+        $steps[] = new Given('I press "' . get_string('savechangesanddisplay') . '"');
 
         return $steps;
     }
index ceccb9e..4b41415 100644 (file)
@@ -36,14 +36,15 @@ Feature: We can change the visibility of categories in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
     And category in management listing should be visible "CAT1"
     And I toggle visibility of category "CAT1" in management listing
-    # AJAX updated.
+    And a new page should not have loaded since I started watching
     And category in management listing should be dimmed "CAT1"
     And I toggle visibility of category "CAT1" in management listing
-    # AJAX updated.
+    And a new page should not have loaded since I started watching
     And category in management listing should be visible "CAT1"
 
   # Tests hiding and then showing a subcategory.
@@ -91,20 +92,21 @@ Feature: We can change the visibility of categories in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
     And I should not see "Cat 2" in the "#category-listing ul.ml" "css_element"
     And category in management listing should be visible "CAT1"
     And I click to expand category "CAT1" in the management interface
-    # AJAX loads sub category.
+    And a new page should not have loaded since I started watching
     And category in management listing should be visible "CAT1"
     And category in management listing should be visible "CAT2"
     And I toggle visibility of category "CAT2" in management listing
-    # AJAX hides the subcategory.
+    And a new page should not have loaded since I started watching
     And category in management listing should be visible "CAT1"
     And category in management listing should be dimmed "CAT2"
     And I toggle visibility of category "CAT2" in management listing
-    # AJAX reveals the subcategory.
+    And a new page should not have loaded since I started watching
     And category in management listing should be visible "CAT1"
     And category in management listing should be visible "CAT2"
 
@@ -199,9 +201,11 @@ Feature: We can change the visibility of categories in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
     And I should see "Cat 2" in the "#category-listing ul.ml" "css_element"
@@ -218,13 +222,13 @@ Feature: We can change the visibility of categories in the management interface.
     And course in management listing should be visible "C2"
     And course in management listing should be visible "C3"
     And I toggle visibility of course "C2" in management listing
-    # AJAX action - no redirect.
+    And a new page should not have loaded since I started watching
     And I should see "Cat 3" in the "#category-listing ul.ml" "css_element"
     And course in management listing should be visible "C1"
     And course in management listing should be dimmed "C2"
     And course in management listing should be visible "C3"
     And I toggle visibility of category "CAT3" in management listing
-    # AJAX action - no redirect.
+    And a new page should not have loaded since I started watching
     And category in management listing should be visible "CAT1"
     And category in management listing should be visible "CAT2"
     And category in management listing should be dimmed "CAT3"
@@ -233,7 +237,7 @@ Feature: We can change the visibility of categories in the management interface.
     And course in management listing should be dimmed "C2"
     And course in management listing should be visible "C3"
     And I toggle visibility of category "CAT1" in management listing
-    # AJAX action - no redirect.
+    And a new page should not have loaded since I started watching
     And category in management listing should be dimmed "CAT1"
     And category in management listing should be visible "CAT2"
     And category in management listing should be dimmed "CAT3"
@@ -242,7 +246,7 @@ Feature: We can change the visibility of categories in the management interface.
     And course in management listing should be dimmed "C2"
     And course in management listing should be dimmed "C3"
     And I toggle visibility of category "CAT1" in management listing
-    # AJAX action - no redirect.
+    And a new page should not have loaded since I started watching
     And category in management listing should be visible "CAT1"
     And category in management listing should be visible "CAT2"
     And category in management listing should be dimmed "CAT3"
@@ -264,46 +268,50 @@ Feature: We can change the visibility of categories in the management interface.
 
       And I log in as "admin"
       And I go to the courses management page
+      And I start watching to see if a new page loads
       And I should see the "Course categories and courses" management page
       And I click on category "Cat 1" in the management interface
-      # Redirect
+      And a new page should have loaded since I started watching
+      And I start watching to see if a new page loads
       And I should see the "Course categories and courses" management page
       And I click on category "Cat 2" in the management interface
-      # Redirect
+      And a new page should have loaded since I started watching
+      And I start watching to see if a new page loads
       And I should see the "Course categories and courses" management page
       And I click on category "Cat 3" in the management interface
-      # Redirect
+      And a new page should have loaded since I started watching
+      And I start watching to see if a new page loads
       And I should see the "Course categories and courses" management page
       And category in management listing should be visible "CAT1"
       And category in management listing should be visible "CAT2"
       And category in management listing should be visible "CAT3"
       And course in management listing should be visible "C1"
       And I toggle visibility of category "CAT1" in management listing
-      # AJAX action - no redirect.
+      And a new page should not have loaded since I started watching
       And category in management listing should be dimmed "CAT1"
       And category in management listing should be dimmed "CAT2"
       And category in management listing should be dimmed "CAT3"
       And course in management listing should be dimmed "C1"
       And I toggle visibility of category "CAT1" in management listing
-      # AJAX action - no redirect.
+      And a new page should not have loaded since I started watching
       And category in management listing should be visible "CAT1"
       And category in management listing should be visible "CAT2"
       And category in management listing should be visible "CAT3"
       And course in management listing should be visible "C1"
       And I toggle visibility of course "C1" in management listing
-      # AJAX action - no redirect.
+      And a new page should not have loaded since I started watching
       And category in management listing should be visible "CAT1"
       And category in management listing should be visible "CAT2"
       And category in management listing should be visible "CAT3"
       And course in management listing should be dimmed "C1"
       And I toggle visibility of category "CAT1" in management listing
-      # AJAX action - no redirect.
+      And a new page should not have loaded since I started watching
       And category in management listing should be dimmed "CAT1"
       And category in management listing should be dimmed "CAT2"
       And category in management listing should be dimmed "CAT3"
       And course in management listing should be dimmed "C1"
       And I toggle visibility of category "CAT1" in management listing
-      # AJAX action - no redirect.
+      And a new page should not have loaded since I started watching
       And category in management listing should be visible "CAT1"
       And category in management listing should be visible "CAT2"
       And category in management listing should be visible "CAT3"
index 781186a..dc35898 100644 (file)
@@ -97,12 +97,15 @@ Feature: Test we can resort categories in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I click on "Master cat" category in the management category listing
-  # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I click on <sortby> action for "Master cat" in management category listing
-  # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see category listing <cat1> before <cat2>
     And I should see category listing <cat2> before <cat3>
@@ -186,22 +189,24 @@ Feature: Test we can resort categories in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see category listing "Cat 1" before "Cat 1a"
     And I should see category listing "Cat 1a" before "Cat 1b"
     And I should see category listing "Cat 1b" before "Cat 1c"
     And I should see category listing "Cat 1c" before "Cat 2"
     And I click to move category "CATA" down one
-    # AJAX request. No redirect.We should a 1, 1b, 1a, 1c, 2.
+    And a new page should not have loaded since I started watching
     And I should see category listing "Cat 1" before "Cat 1b"
     And I should see category listing "Cat 1b" before "Cat 1a"
     And I should see category listing "Cat 1a" before "Cat 1c"
     And I should see category listing "Cat 1c" before "Cat 2"
     And I click to move category "CATC" up one
-    # AJAX request. No redirect. We should a 1, 1b, 1c, 1a, 2.
+    And a new page should not have loaded since I started watching
     And I should see category listing "Cat 1" before "Cat 1b"
     And I should see category listing "Cat 1b" before "Cat 1c"
     And I should see category listing "Cat 1c" before "Cat 1a"
index 6871aae..78c85b3 100644 (file)
@@ -35,18 +35,21 @@ Feature: Course category management interface performs as expected
     And I should not see "Course categories" in the ".view-mode-selector .menu" "css_element"
     And I should not see "Courses" in the ".view-mode-selector .menu" "css_element"
     When I click on "Course categories" "link" in the ".view-mode-selector" "css_element"
+    And I start watching to see if a new page loads
     Then I should see "Course categories and courses" in the ".view-mode-selector .menu" "css_element"
     And I should see "Course categories" in the ".view-mode-selector .menu" "css_element"
     And I should see "Courses" in the ".view-mode-selector .menu" "css_element"
     And I click on "Course categories and courses" "link" in the ".view-mode-selector .menu" "css_element"
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Course categories" in the "#category-listing h3" "css_element"
     And I should see "Miscellaneous" in the "#course-listing h3" "css_element"
     And I should see "Cat 1" in the "#category-listing" "css_element"
     And I should see "No courses in this category" in the "#course-listing" "css_element"
     And I click on category "Cat 1" in the management interface
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Course categories" in the "#category-listing h3" "css_element"
     And I should see "Cat 1" in the "#course-listing h3" "css_element"
@@ -55,12 +58,13 @@ Feature: Course category management interface performs as expected
     When I click on "Course categories" "link" in the ".view-mode-selector" "css_element"
     Then I should see "Courses" in the ".view-mode-selector .menu" "css_element"
     And I click on "Courses" "link" in the ".view-mode-selector .menu" "css_element"
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Courses" management page
     And I should see "Cat 1" in the "#course-listing h3" "css_element"
     And I should see "Course 1" in the "#course-listing" "css_element"
     And I click on course "Course 1" in the management interface
-    # Redirect.
+    And a new page should have loaded since I started watching
     And I should see the "Courses" management page with a course selected
     And I should see "Cat 1" in the "#course-listing h3" "css_element"
     And I should see "Course 1" in the "#course-listing" "css_element"
@@ -164,6 +168,7 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2" in the "#course-category-listings ul.ml" "css_element"
@@ -174,7 +179,7 @@ Feature: Course category management interface performs as expected
     And I should not see "Cat 2-1" in the "#course-category-listings ul.ml" "css_element"
     And I should not see "Cat 2-1-1" in the "#course-category-listings ul.ml" "css_element"
     And I click to expand category "CAT1" in the management interface
-    # AJAX action - no redirect.
+    And a new page should not have loaded since I started watching
     And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 1-1" in the "#course-category-listings ul.ml" "css_element"
@@ -184,7 +189,7 @@ Feature: Course category management interface performs as expected
     And I should not see "Cat 2-1" in the "#course-category-listings ul.ml" "css_element"
     And I should not see "Cat 2-1-1" in the "#course-category-listings ul.ml" "css_element"
     And I click to expand category "CAT3" in the management interface
-    # AJAX action - no redirect.
+    And a new page should not have loaded since I started watching
     And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 1-1" in the "#course-category-listings ul.ml" "css_element"
@@ -194,7 +199,7 @@ Feature: Course category management interface performs as expected
     And I should not see "Cat 2-1" in the "#course-category-listings ul.ml" "css_element"
     And I should not see "Cat 2-1-1" in the "#course-category-listings ul.ml" "css_element"
     And I click to expand category "CAT2" in the management interface
-    # AJAX action - no redirect.
+    And a new page should not have loaded since I started watching
     And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 1-1" in the "#course-category-listings ul.ml" "css_element"
@@ -204,7 +209,7 @@ Feature: Course category management interface performs as expected
     And I should see "Cat 2-1" in the "#course-category-listings ul.ml" "css_element"
     And I should not see "Cat 2-1-1" in the "#course-category-listings ul.ml" "css_element"
     And I click to expand category "CAT7" in the management interface
-    # AJAX action - no redirect.
+    And a new page should not have loaded since I started watching
     And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 1-1" in the "#course-category-listings ul.ml" "css_element"
@@ -214,7 +219,7 @@ Feature: Course category management interface performs as expected
     And I should see "Cat 2-1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2-1-1" in the "#course-category-listings ul.ml" "css_element"
     And I click to expand category "CAT1" in the management interface
-    # AJAX action - no redirect.
+    And a new page should not have loaded since I started watching
     And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2" in the "#course-category-listings ul.ml" "css_element"
     And I should not see "Cat 1-1" in the "#course-category-listings ul.ml" "css_element"
@@ -224,7 +229,7 @@ Feature: Course category management interface performs as expected
     And I should see "Cat 2-1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2-1-1" in the "#course-category-listings ul.ml" "css_element"
     And I click to expand category "CAT1" in the management interface
-    # AJAX action - no redirect.
+    And a new page should not have loaded since I started watching
     And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 1-1" in the "#course-category-listings ul.ml" "css_element"
@@ -244,11 +249,12 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I set the field "menuselectsortby" to "All categories"
     And I set the field "menuresortcategoriesby" to <sortby>
     And I press "Sort"
-    # Redirect.
+    And a new page should have loaded since I started watching
     And I should see the "Course categories and courses" management page
     And I should see category listing <cat1> before <cat2>
     And I should see category listing <cat2> before <cat3>
@@ -271,12 +277,14 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I click on "Master cat" category in the management category listing
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I click on <sortby> action for "Master cat" in management category listing
-    # Redirect.
+    And a new page should have loaded since I started watching
     And I should see the "Course categories and courses" management page
     And I should see category listing <cat1> before <cat2>
     And I should see category listing <cat2> before <cat3>
@@ -301,9 +309,11 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I click on "Cat 1" "link"
-  # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I click on "Sort courses" "link"
     And I should see "Sort by Course full name ascending" in the ".course-listing-actions" "css_element"
@@ -315,7 +325,7 @@ Feature: Course category management interface performs as expected
     And I should see "Sort by Course time created ascending" in the ".course-listing-actions" "css_element"
     And I should see "Sort by Course time created descending" in the ".course-listing-actions" "css_element"
     And I click on <sortby> "link" in the ".course-listing-actions" "css_element"
-  # Redirect.
+    And a new page should have loaded since I started watching
     And I should see the "Course categories and courses" management page
     And I should see course listing <course1> before <course2>
     And I should see course listing <course2> before <course3>
@@ -353,13 +363,16 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I click on "Cat 1" "link"
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I click on "Sort courses" "link"
     And I click on "Sort by Course ID number ascending" "link" in the ".course-listing-actions" "css_element"
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see "Per page: 20" in the ".course-listing-actions" "css_element"
     And I should see course listing "Course 1" before "Course 2"
     And I should see course listing "Course 2" before "Course 3"
@@ -379,7 +392,8 @@ Feature: Course category management interface performs as expected
     And I should see "20" in the ".courses-per-page" "css_element"
     And I should see "All" in the ".courses-per-page" "css_element"
     And I click on "5" "link" in the ".courses-per-page" "css_element"
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Per page: 5" in the ".course-listing-actions" "css_element"
     And I should see course listing "Course 1" before "Course 2"
@@ -403,7 +417,8 @@ Feature: Course category management interface performs as expected
     And I should see "Next" in the "#course-listing .listing-pagination" "css_element"
     And I should see "Last" in the "#course-listing .listing-pagination" "css_element"
     And I click on "2" "link" in the "#course-listing .listing-pagination" "css_element"
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Per page: 5" in the ".course-listing-actions" "css_element"
     And I should see "Course 10" in the "#course-listing" "css_element"
@@ -427,7 +442,8 @@ Feature: Course category management interface performs as expected
     And I should see "Next" in the "#course-listing .listing-pagination" "css_element"
     And I should see "Last" in the "#course-listing .listing-pagination" "css_element"
     And I click on "Next" "link" in the "#course-listing .listing-pagination" "css_element"
-    # Redirect. Test next link.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Per page: 5" in the ".course-listing-actions" "css_element"
     And I should see "Course 11"
@@ -451,7 +467,8 @@ Feature: Course category management interface performs as expected
     And I should not see "Next" in the "#course-listing .listing-pagination" "css_element"
     And I should not see "Last" in the "#course-listing .listing-pagination" "css_element"
     And I click on "First" "link" in the "#course-listing .listing-pagination" "css_element"
-    # Redirect. Test first link.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Per page: 5" in the ".course-listing-actions" "css_element"
     And I should see course listing "Course 1" before "Course 2"
@@ -475,7 +492,8 @@ Feature: Course category management interface performs as expected
     And I should see "Next" in the "#course-listing .listing-pagination" "css_element"
     And I should see "Last" in the "#course-listing .listing-pagination" "css_element"
     And I click on "Last" "link" in the "#course-listing .listing-pagination" "css_element"
-    # Redirect. Test last link.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Per page: 5" in the ".course-listing-actions" "css_element"
     And I should see "Course 11" in the "#course-listing" "css_element"
@@ -499,7 +517,7 @@ Feature: Course category management interface performs as expected
     And I should not see "Next" in the "#course-listing .listing-pagination" "css_element"
     And I should not see "Last" in the "#course-listing .listing-pagination" "css_element"
     And I click on "Prev" "link" in the "#course-listing .listing-pagination" "css_element"
-    # Redirect. Test prev link.
+    And a new page should have loaded since I started watching
     And I should see the "Course categories and courses" management page
     And I should see "Per page: 5" in the ".course-listing-actions" "css_element"
     And I should see "Course 10" in the "#course-listing" "css_element"
@@ -601,13 +619,16 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I click on "Cat 1" "link"
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I click on "Sort courses" "link"
     And I click on "Sort by Course ID number ascending" "link" in the ".course-listing-actions" "css_element"
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Per page: 20" in the ".course-listing-actions" "css_element"
     And I should see course listing "Course 1" before "Course 2"
@@ -616,7 +637,8 @@ Feature: Course category management interface performs as expected
     And I should see "Showing courses 1 to 20 of 32 courses"
     And I click on "Per page: 20" "link" in the ".course-listing-actions" "css_element"
     And I click on "100" "link" in the ".courses-per-page" "css_element"
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Per page: 100" in the ".course-listing-actions" "css_element"
     And I should see course listing "Course 1" before "Course 2"
@@ -626,7 +648,8 @@ Feature: Course category management interface performs as expected
     And "#course-listing .listing-pagination" "css_element" should not exist
     And I click on "Per page: 100" "link" in the ".course-listing-actions" "css_element"
     And I click on "5" "link" in the ".courses-per-page" "css_element"
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Per page: 5" in the ".course-listing-actions" "css_element"
     And I should see course listing "Course 1" before "Course 2"
@@ -645,7 +668,8 @@ Feature: Course category management interface performs as expected
     And I should see "Next" in the "#course-listing .listing-pagination" "css_element"
     And I should see "Last" in the "#course-listing .listing-pagination" "css_element"
     And I click on "Last" "link" in the "#course-listing .listing-pagination" "css_element"
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Per page: 5" in the ".course-listing-actions" "css_element"
     And I should not see "Course 30"
@@ -663,7 +687,7 @@ Feature: Course category management interface performs as expected
     And I should not see "Next" in the "#course-listing .listing-pagination" "css_element"
     And I should not see "Last" in the "#course-listing .listing-pagination" "css_element"
     And I click on "4" "link" in the "#course-listing .listing-pagination" "css_element"
-    # Redirect.
+    And a new page should have loaded since I started watching
     And I should see the "Course categories and courses" management page
     And I should see "Per page: 5" in the ".course-listing-actions" "css_element"
     And I should not see "Course 15"
@@ -720,6 +744,7 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2" in the "#course-category-listings ul.ml" "css_element"
@@ -727,11 +752,11 @@ Feature: Course category management interface performs as expected
     And I should not see "Cat 1-2" in the "#course-category-listings ul.ml" "css_element"
     And I should not see "Cat 2-1" in the "#course-category-listings ul.ml" "css_element"
     And I click to expand category "CAT2" in the management interface
-    # AJAX action - no redirect.
+    And a new page should not have loaded since I started watching
     And I click to expand category "CAT7" in the management interface
-    # AJAX action - no redirect.
+    And a new page should not have loaded since I started watching
     And I click to expand category "CAT9" in the management interface
-    # AJAX action - no redirect.
+    And a new page should not have loaded since I started watching
     And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2" in the "#course-category-listings ul.ml" "css_element"
     And I should not see "Cat 1-1" in the "#course-category-listings ul.ml" "css_element"
@@ -742,7 +767,8 @@ Feature: Course category management interface performs as expected
     And I should not see "Cat 2-1-1-1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2-1-2-1" in the "#course-category-listings ul.ml" "css_element"
     And I click on "Cat 1" category in the management category listing
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2" in the "#course-category-listings ul.ml" "css_element"
@@ -754,7 +780,7 @@ Feature: Course category management interface performs as expected
     And I should not see "Cat 2-1-1-1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2-1-2-1" in the "#course-category-listings ul.ml" "css_element"
     And I click on "resortbyidnumber" action for "Cat 1" in management category listing
-    # Redirect.
+    And a new page should have loaded since I started watching
     And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2" in the "#course-category-listings ul.ml" "css_element"
@@ -778,6 +804,7 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Cat A (1)" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat B (2)" in the "#course-category-listings ul.ml" "css_element"
@@ -786,23 +813,25 @@ Feature: Course category management interface performs as expected
     And I should not see "Cat E (2-1-1)" in the "#course-category-listings ul.ml" "css_element"
     And I click to expand category "CAT1" in the management interface
     And I should see "Cat C (1-1)" in the "#course-category-listings ul.ml" "css_element"
-    # AJAX action - no redirect.
+    And a new page should not have loaded since I started watching
     And I click to expand category "CAT2" in the management interface
     And I should see "Cat D (2-1)" in the "#course-category-listings ul.ml" "css_element"
-    # AJAX action - no redirect.
+    And a new page should not have loaded since I started watching
     And I click to expand category "CAT4" in the management interface
     And I should see "Cat E (2-1-1)" in the "#course-category-listings ul.ml" "css_element"
-    # AJAX action - no redirect.
+    And a new page should not have loaded since I started watching
     And I click on "delete" action for "Cat B (2)" in management category listing
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see "Delete category: Cat B (2)"
     And I should see "Contents of Cat B (2)"
     And I press "Delete"
-    # Redirect
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see "Delete category: Cat B (2)"
     And I should see "Deleted course category Cat B (2)"
     And I press "Continue"
-    # Redirect.
+    And a new page should have loaded since I started watching
     And I should see the "Course categories and courses" management page
     And I should see "Cat A (1)" in the "#course-category-listings ul.ml" "css_element"
     And I should not see "Cat B (2)" in the "#course-category-listings ul.ml" "css_element"
index eedf984..6311d8b 100644 (file)
@@ -62,42 +62,46 @@ Feature: We can change the visibility of courses in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
     And I should see "Course 1" in the "#course-listing ul.ml" "css_element"
     And category in management listing should be visible "CAT1"
     And course in management listing should be visible "C1"
     And I toggle visibility of course "C1" in management listing
-    # AJAX updates the visibility
+    And a new page should not have loaded since I started watching
     And category in management listing should be visible "CAT1"
     And course in management listing should be dimmed "C1"
     And I toggle visibility of course "C1" in management listing
-    # AJAX updates the visibility
+    And a new page should not have loaded since I started watching
     And category in management listing should be visible "CAT1"
     And course in management listing should be visible "C1"
     And I toggle visibility of course "C1" in management listing
-    # AJAX updates the visibility
+    And a new page should not have loaded since I started watching
     And category in management listing should be visible "CAT1"
     And course in management listing should be dimmed "C1"
     And I toggle visibility of category "CAT1" in management listing
-    # AJAX updates the visibility
+    And a new page should not have loaded since I started watching
     And category in management listing should be dimmed "CAT1"
     And course in management listing should be dimmed "C1"
     And I toggle visibility of category "CAT1" in management listing
-    # AJAX updates the visibility
+    And a new page should not have loaded since I started watching
     And category in management listing should be visible "CAT1"
     And course in management listing should be dimmed "C1"
     And I toggle visibility of category "CAT1" in management listing
     And I toggle visibility of course "C1" in management listing
     And I click on "Course categories and courses" "link" in the ".view-mode-selector" "css_element"
     And I click on "Courses" "link"
-    # Redirect
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see "Course 1" in the "#course-listing ul.ml" "css_element"
     And I toggle visibility of course "C1" in management listing
-    # AJAX updates the visibility
+    And a new page should not have loaded since I started watching
     And course in management listing should be dimmed "C1"
     And I toggle visibility of course "C1" in management listing
     And course in management listing should be visible "C1"
+    And a new page should not have loaded since I started watching
index 668a50d..27a334a 100644 (file)
@@ -29,3 +29,21 @@ Feature: Managers can create courses
     And I follow "News forum"
     And "Add a new topic" "button" should not exist
     And I should see "Forced subscription" in the "Administration" "block"
+
+  Scenario: Create a course from the management interface and return to it
+    Given the following "courses" exist:
+      | fullname | shortname | idnumber |
+      | Course 1 | Course 1 | C1 |
+    And I log in as "admin"
+    And I go to the courses management page
+    And I should see the "Categories" management page
+    And I click on category "Miscellaneous" in the management interface
+    And I should see the "Course categories and courses" management page
+    And I click on "Create new course" "link" in the "#course-listing" "css_element"
+    When I set the following fields to these values:
+      | Course full name | Course 2 |
+      | Course short name | Course 2 |
+      | Course summary | Course 2 summary |
+    And I press "Save and return"
+    Then I should see the "Course categories and courses" management page
+    And I should see course listing "Course 1" before "Course 2"
index 4558c89..cee3ccd 100644 (file)
@@ -60,9 +60,11 @@ Feature: Test we can resort course in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Sort courses" in the ".course-listing-actions" "css_element"
     And I should not see "Sort by Course full name ascending" in the ".course-listing-actions" "css_element"
@@ -83,7 +85,8 @@ Feature: Test we can resort course in the management interface.
     And I should see "Sort by Course time created ascending" in the ".course-listing-actions" "css_element"
     And I should see "Sort by Course time created descending" in the ".course-listing-actions" "css_element"
     And I click on <sortby> "link" in the ".course-listing-actions" "css_element"
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see course listing <course1> before <course2>
     And I should see course listing <course2> before <course3>
@@ -148,23 +151,26 @@ Feature: Test we can resort course in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see "Course categories" in the "#category-listing h3" "css_element"
     And I should see "Cat 1" in the "#category-listing" "css_element"
     And I click on "Sort courses" "link"
     And I click on "Sort by Course ID number ascending" "link" in the ".course-listing-actions" "css_element"
-    # Redirect.
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see course listing "Course 1" before "Course 2"
     And I should see course listing "Course 2" before "Course 3"
     And I click to move course "C1" down one
-    # AJAX, no redirect.
+    And a new page should not have loaded since I started watching
     And I should see course listing "Course 2" before "Course 1"
     And I should see course listing "Course 1" before "Course 3"
     And I click to move course "C3" up one
-    # AJAX, no redirect.
+    And a new page should not have loaded since I started watching
     And I should see course listing "Course 2" before "Course 3"
     And I should see course listing "Course 3" before "Course 1"
\ No newline at end of file
index 2cff137..1c74098 100644 (file)
@@ -23,9 +23,8 @@ Feature: Test we can both create and delete a course.
       | Course short name | TCCAC |
       | Course ID number | TC3401 |
       | Course summary | This course has been created by automated tests. |
-    And I press "Save changes"
+    And I press "Save and return"
     # Redirect
-    And I go to the courses management page
     And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
     # Redirect
index 11e0f09..30b9b6a 100644 (file)
@@ -22,7 +22,7 @@ Feature: Edit course settings
       | Course full name | Edited course fullname |
       | Course short name | Edited course shortname |
       | Course summary | Edited course summary |
-    And I press "Save changes"
+    And I press "Save and display"
     And I follow "Edited course fullname"
     Then I should not see "Course 1"
     And I should not see "C1"
@@ -34,3 +34,23 @@ Feature: Edit course settings
     And the field "Course summary" matches value "Edited course summary"
     And I am on homepage
     And I should see "Edited course fullname"
+
+  Scenario: Edit course settings and return to the management interface
+    Given the following "categories" exist:
+      | name | category | idnumber |
+      | Cat 1 | 0 | CAT1 |
+    And the following "courses" exist:
+      | category | fullname | shortname | idnumber |
+      | CAT1 | Course 1 | Course 1 | C1 |
+    And I log in as "admin"
+    And I go to the courses management page
+    And I should see the "Categories" management page
+    And I click on category "Cat 1" in the management interface
+    And I should see the "Course categories and courses" management page
+    When I click on "edit" action for "Course 1" in management course listing
+    And I set the following fields to these values:
+      | Course full name | Edited course fullname |
+      | Course short name | Edited course shortname |
+      | Course summary | Edited course summary |
+    And I press "Save and return"
+    Then I should see the "Course categories and courses" management page
\ No newline at end of file
index 73fe95e..41975e0 100644 (file)
@@ -27,7 +27,7 @@ Feature: Force group mode in a course
     Given I set the following fields to these values:
       | Group mode | Separate groups |
       | Force group mode | Yes |
-    When I press "Save changes"
+    When I press "Save and display"
     Then "//a/child::img[contains(@alt, 'Separate groups (forced mode)')]" "xpath_element" should not exist
     And "//img[contains(@alt, 'Separate groups (forced mode)')]" "xpath_element" should not exist
 
@@ -36,7 +36,7 @@ Feature: Force group mode in a course
     Given I set the following fields to these values:
       | Group mode | Visible groups |
       | Force group mode | Yes |
-    And I press "Save changes"
+    And I press "Save and display"
     Then "//a/child::img[contains(@alt, 'Visible groups (forced mode)')]" "xpath_element" should not exist
     And "//img[contains(@alt, 'Visible groups (forced mode)')]" "xpath_element" should not exist
 
@@ -45,7 +45,7 @@ Feature: Force group mode in a course
     Given I set the following fields to these values:
       | Group mode | No groups |
       | Force group mode | Yes |
-    And I press "Save changes"
+    And I press "Save and display"
     Then "//a/child::img[contains(@alt, 'No groups (forced mode)')]" "xpath_element" should not exist
     And "//img[contains(@alt, 'No groups (forced mode)')]" "xpath_element" should not exist
 
index 68abd0f..f2cde5b 100644 (file)
@@ -36,7 +36,7 @@ Feature: Activities can be moved between sections
     Given I click on "Edit settings" "link" in the "Administration" "block"
     And I set the following fields to these values:
       | Course layout | Show one section per page |
-    And I press "Save changes"
+    And I press "Save and display"
     When I move "Test forum name" activity to section "2"
     Then I should see "Test forum name" in the "#section-2" "css_element"
     And I should not see "Test forum name" in the "#section-1" "css_element"
@@ -45,7 +45,7 @@ Feature: Activities can be moved between sections
     Given I click on "Edit settings" "link" in the "Administration" "block"
     And I set the following fields to these values:
       | Course layout | Show one section per page |
-    And I press "Save changes"
+    And I press "Save and display"
     And I add a "Forum" to section "1" and I fill the form with:
       | Forum name | Second forum name |
       | Description | Second forum description |
index b56a0b9..a4772fe 100644 (file)
@@ -31,7 +31,7 @@ Feature: Sections can be moved
     Given I click on "Edit settings" "link" in the "Administration" "block"
     And I set the following fields to these values:
       | Course layout | Show one section per page |
-    And I press "Save changes"
+    And I press "Save and display"
     And I add a "Forum" to section "1" and I fill the form with:
       | Forum name | Test forum name |
       | Description | Test forum description |
@@ -44,7 +44,7 @@ Feature: Sections can be moved
     Given I click on "Edit settings" "link" in the "Administration" "block"
     And I set the following fields to these values:
       | Course layout | Show one section per page |
-    And I press "Save changes"
+    And I press "Save and display"
     And I add a "Forum" to section "2" and I fill the form with:
       | Forum name | Test forum name |
       | Description | Test forum description |
index 082242c..f0195bd 100644 (file)
@@ -25,7 +25,7 @@ Feature: Rename roles within a course
     And I set the following fields to these values:
       | Your word for 'Non-editing teacher' | Tutor |
       | Your word for 'Student' | Learner |
-    And I press "Save changes"
+    And I press "Save and display"
     And I expand "Switch role to..." node
     Then I should see "Tutor"
     And I should see "Learner"
@@ -37,7 +37,7 @@ Feature: Rename roles within a course
     And I set the following fields to these values:
       | Your word for 'Non-editing teacher' | |
       | Your word for 'Student' | |
-    And I press "Save changes"
+    And I press "Save and display"
     And I expand "Switch role to..." node
     And I should see "Teacher"
     And I should see "Student"
index 53992db..234b8c8 100644 (file)
@@ -19,6 +19,9 @@
  * Toggles the manual completion flag for a particular activity or course completion
  * and the current user.
  *
+ * If by student params: course=2
+ * If by manager params: course=2&user=4&rolec=3&sesskey=ghfgsdf
+ *
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  * @package course
  */
@@ -31,6 +34,10 @@ $cmid = optional_param('id', 0, PARAM_INT);
 $courseid = optional_param('course', 0, PARAM_INT);
 $confirm = optional_param('confirm', 0, PARAM_BOOL);
 
+// Check if we are marking a user complete via the completion report.
+$user = optional_param('user', 0, PARAM_INT);
+$rolec = optional_param('rolec', 0, PARAM_INT);
+
 if (!$cmid && !$courseid) {
     print_error('invalidarguments');
 }
@@ -45,16 +52,14 @@ if ($courseid) {
     require_login($course);
 
     $completion = new completion_info($course);
+    $trackeduser = ($user ? $user : $USER->id);
+
     if (!$completion->is_enabled()) {
         throw new moodle_exception('completionnotenabled', 'completion');
-    } elseif (!$completion->is_tracked_user($USER->id)) {
+    } else if (!$completion->is_tracked_user($trackeduser)) {
         throw new moodle_exception('nottracked', 'completion');
     }
 
-    // Check if we are marking a user complete via the completion report
-    $user = optional_param('user', 0, PARAM_INT);
-    $rolec = optional_param('rolec', 0, PARAM_INT);
-
     if ($user && $rolec) {
         require_sesskey();
 
index 1b8488e..316477f 100644 (file)
@@ -27,7 +27,7 @@ Feature: Guest users can auto-enrol themself in courses where guest access is al
   Scenario: Allow guest access without password
     Given I set the following fields to these values:
       | Allow guest access | Yes |
-    And I press "Save changes"
+    And I press "Save and display"
     And I log out
     And I log in as "student1"
     And I follow "Course 1"
@@ -39,7 +39,7 @@ Feature: Guest users can auto-enrol themself in courses where guest access is al
     Given I set the following fields to these values:
       | Allow guest access | Yes |
       | Password | moodle_rules |
-    And I press "Save changes"
+    And I press "Save and display"
     And I log out
     And I log in as "student1"
     When I follow "Course 1"
index 46e6e1f..f4d1bee 100644 (file)
@@ -89,7 +89,7 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
             var recovergrades = null;
             if (this.get(UEP.DISABLEGRADEHISTORY) != true) {
                 recovergrades = create('<div class="'+CSS.ENROLMENTOPTION+' '+CSS.RECOVERGRADES+'"></div>')
-                    .append(create('<label class="'+CSS.RECOVERGRADESTITLE+'" for="'+CSS.RECOVERGRADES+'">'+M.str.enrol.recovergrades+'</label>'))
+                    .append(create('<label class="'+CSS.RECOVERGRADESTITLE+'" for="'+CSS.RECOVERGRADES+'">'+M.util.get_string('recovergrades', 'enrol')+'</label>'))
                     .append(create('<input type="checkbox" id="'+CSS.RECOVERGRADES+'" name="'+CSS.RECOVERGRADES+'"'+ this.get(UEP.RECOVERGRADESDEFAULT) +' />'))
             }
 
@@ -97,21 +97,21 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
                 .append(create('<div class="'+CSS.WRAP+'"></div>')
                     .append(create('<div class="'+CSS.HEADER+' header"></div>')
                         .append(create('<div class="'+CSS.CLOSE+'"></div>'))
-                        .append(create('<h2>'+M.str.enrol.enrolusers+'</h2>')))
+                        .append(create('<h2>'+M.util.get_string('enrolusers', 'enrol')+'</h2>')))
                     .append(create('<div class="'+CSS.CONTENT+'"></div>')
                         .append(create('<div class="'+CSS.SEARCHCONTROLS+'"></div>')
-                            .append(create('<div class="'+CSS.ENROLMENTOPTION+' '+CSS.ROLE+'"><label for="id_enrol_manual_assignable_roles">'+M.str.role.assignroles+'</label></div>')
-                                    .append(create('<select id="id_enrol_manual_assignable_roles"><option value="">'+M.str.enrol.none+'</option></select>'))
+                            .append(create('<div class="'+CSS.ENROLMENTOPTION+' '+CSS.ROLE+'"><label for="id_enrol_manual_assignable_roles">'+M.util.get_string('assignroles', 'role')+'</label></div>')
+                                    .append(create('<select id="id_enrol_manual_assignable_roles"><option value="">'+M.util.get_string('none', 'enrol')+'</option></select>'))
                             )
                             .append(create('<div class="'+CSS.ENTITYSELECTOR+'"></div>'))
                             .append(create('<div class="'+CSS.SEARCHOPTIONS+'"></div>')
-                                .append(create('<div class="'+CSS.COLLAPSIBLEHEADING+'"><img alt="" />'+M.str.enrol.enrolmentoptions+'</div>'))
+                                .append(create('<div class="'+CSS.COLLAPSIBLEHEADING+'"><img alt="" />'+M.util.get_string('enrolmentoptions', 'enrol')+'</div>'))
                                 .append(create('<div class="'+CSS.COLLAPSIBLEAREA+' '+CSS.HIDDEN+'"></div>')
                                     .append(recovergrades)
-                                    .append(create('<div class="'+CSS.ENROLMENTOPTION+' '+CSS.STARTDATE+'">'+M.str.moodle.startingfrom+'</div>')
+                                    .append(create('<div class="'+CSS.ENROLMENTOPTION+' '+CSS.STARTDATE+'">'+M.util.get_string('startingfrom', 'moodle')+'</div>')
                                         .append(create('<select></select>')))
-                                    .append(create('<div class="'+CSS.ENROLMENTOPTION+' '+CSS.DURATION+'">'+M.str.enrol.enrolperiod+'</div>')
-                                        .append(create('<select><option value="0" selected="selected">'+M.str.enrol.unlimitedduration+'</option></select>')))
+                                    .append(create('<div class="'+CSS.ENROLMENTOPTION+' '+CSS.DURATION+'">'+M.util.get_string('enrolperiod', 'enrol')+'</div>')
+                                        .append(create('<select><option value="0" selected="selected">'+M.util.get_string('unlimitedduration', 'enrol')+'</option></select>')))
                                 )
                             )
                         )
@@ -121,12 +121,12 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
                                 .setAttribute('src', M.util.image_url('i/loading', 'moodle')))
                             .setStyle('opacity', 0.5)))
                     .append(create('<div class="'+CSS.FOOTER+'"></div>')
-                        .append(create('<div class="'+CSS.SEARCH+'"><label for="enrolusersearch" class="accesshide">'+M.str.enrol.usersearch+'</label></div>')
+                        .append(create('<div class="'+CSS.SEARCH+'"><label for="enrolusersearch" class="accesshide">'+M.util.get_string('usersearch', 'enrol')+'</label></div>')
                             .append(create('<input type="text" id="enrolusersearch" value="" />'))
-                                .append(create('<input type="button" id="searchbtn" class="'+CSS.SEARCHBTN+'" value="'+M.str.enrol.usersearch+'" />'))
+                                .append(create('<input type="button" id="searchbtn" class="'+CSS.SEARCHBTN+'" value="'+M.util.get_string('usersearch', 'enrol')+'" />'))
                         )
                         .append(create('<div class="'+CSS.CLOSEBTN+'"></div>')
-                            .append(create('<input type="button" value="'+M.str.enrol.finishenrollingusers+'" />'))
+                            .append(create('<input type="button" value="'+M.util.get_string('finishenrollingusers', 'enrol')+'" />'))
                         )
                     )
                 )
@@ -152,9 +152,9 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
             if (this.get(UEP.COHORTSAVAILABLE)) {
                 this.get(UEP.BASE).one('.'+CSS.ENTITYSELECTOR)
                     .append(create('<input type="radio" id="id_enrol_manual_entity_users" name="enrol_manual_entity" value="users" checked="checked"/>'))
-                    .append(create('<label for="id_enrol_manual_entity_users">'+ M.str.enrol_manual.browseusers+'</label>'))
+                    .append(create('<label for="id_enrol_manual_entity_users">'+ M.util.get_string('browseusers', 'enrol_manual')+'</label>'))
                     .append(create('<input type="radio" id="id_enrol_manual_entity_cohorts" name="enrol_manual_entity" value="cohorts"/>'))
-                    .append(create('<label for="id_enrol_manual_entity_cohorts">'+M.str.enrol_manual.browsecohorts+'</label>'));
+                    .append(create('<label for="id_enrol_manual_entity_cohorts">'+M.util.get_string('browsecohorts', 'enrol_manual')+'</label>'));
                 this.get(UEP.BASE).one('#id_enrol_manual_entity_cohorts').on('change', this.search, this);
                 this.get(UEP.BASE).one('#id_enrol_manual_entity_users').on('change', this.search, this);
             } else {
@@ -380,7 +380,7 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
                 new M.core.exception(e);
             }
             if (!result.success) {
-                this.setContent = M.str.enrol.errajaxsearch;
+                this.setContent = M.util.get_string('errajaxsearch', 'enrol');
             }
             var users;
             if (!args.append) {
@@ -401,17 +401,17 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
                         .append(create('<div class="'+CSS.FULLNAME+'">'+user.fullname+'</div>'))
                         .append(create('<div class="'+CSS.EXTRAFIELDS+'">'+user.extrafields+'</div>')))
                     .append(create('<div class="'+CSS.OPTIONS+'"></div>')
-                        .append(create('<input type="button" class="'+CSS.ENROL+'" value="'+M.str.enrol.enrol+'" />')))
+                        .append(create('<input type="button" class="'+CSS.ENROL+'" value="'+M.util.get_string('enrol', 'enrol')+'" />')))
                 );
             }
             this.set(UEP.USERCOUNT, count);
             if (!args.append) {
-                var usersstr = (result.response.totalusers == '1')?M.str.enrol.ajaxoneuserfound:M.util.get_string('ajaxxusersfound','enrol', result.response.totalusers);
+                var usersstr = (result.response.totalusers == '1')?M.util.get_string('ajaxoneuserfound', 'enrol'):M.util.get_string('ajaxxusersfound','enrol', result.response.totalusers);
                 var content = create('<div class="'+CSS.SEARCHRESULTS+'"></div>')
                     .append(create('<div class="'+CSS.TOTALUSERS+'">'+usersstr+'</div>'))
                     .append(users);
                 if (result.response.totalusers > (this.get(UEP.PAGE)+1)*this.get(UEP.PERPAGE)) {
-                    var fetchmore = create('<div class="'+CSS.MORERESULTS+'"><a href="#">'+M.str.enrol.ajaxnext25+'</a></div>');
+                    var fetchmore = create('<div class="'+CSS.MORERESULTS+'"><a href="#">'+M.util.get_string('ajaxnext25', 'enrol')+'</a></div>');
                     fetchmore.on('click', this.search, this, true);
                     content.append(fetchmore)
                 }
@@ -433,7 +433,7 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
                 new M.core.exception(e);
             }
             if (!result.success) {
-                this.setContent = M.str.enrol.errajaxsearch;
+                this.setContent = M.util.get_string('errajaxsearch', 'enrol');
             }
             var cohorts;
             if (!args.append) {
@@ -456,13 +456,13 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
             }
             this.set(UEP.COHORTCOUNT, count);
             if (!args.append) {
-                //var usersstr = (result.response.totalusers == '1')?M.str.enrol.ajaxoneuserfound:M.util.get_string('ajaxxusersfound','enrol', result.response.totalusers);
+                //var usersstr = (result.response.totalusers == '1')?M.util.get_string('ajaxoneuserfound', 'enrol'):M.util.get_string('ajaxxusersfound','enrol', result.response.totalusers);
                 var cohortsstr = 'Found '+result.response.totalcohorts+' cohorts'; // TODO
                 var content = create('<div class="'+CSS.SEARCHRESULTS+'"></div>')
                     .append(create('<div class="'+CSS.TOTALCOHORTS+'">'+cohortsstr+'</div>'))
                     .append(cohorts);
                 if (result.response.totalcohorts > (this.get(UEP.PAGE)+1)*this.get(UEP.PERPAGE)) {
-                    var fetchmore = create('<div class="'+CSS.MORERESULTS+'"><a href="#">'+M.str.enrol.ajaxnext25+'</a></div>');
+                    var fetchmore = create('<div class="'+CSS.MORERESULTS+'"><a href="#">'+M.util.get_string('ajaxnext25', 'enrol')+'</a></div>');
                     fetchmore.on('click', this.search, this, true);
                     content.append(fetchmore)
                 }
index c463082..ecc3f54 100644 (file)
@@ -42,7 +42,11 @@ class enrol_meta_addinstance_form extends moodleform {
         $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)";
-        $sql = "SELECT c.id, c.fullname, c.shortname, c.visible $select FROM {course} c $join ORDER BY c.sortorder ASC";
+
+        $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 ORDER BY " . $sortorder;
         $rs = $DB->get_recordset_sql($sql, array('contextlevel' => CONTEXT_COURSE));
         foreach ($rs as $c) {
             if ($c->id == SITEID or $c->id == $course->id or isset($existing[$c->id])) {
index e554ddd..b47f526 100644 (file)
@@ -22,6 +22,8 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['coursesort'] = 'Sort course list';
+$string['coursesort_help'] = 'This determines whether the list of courses that can be linked are sorted by sort order (i.e. the order set in Site administration > Courses > Manage courses and categories) or alphabetically by course setting.';
 $string['linkedcourse'] = 'Link course';
 $string['meta:config'] = 'Configure meta enrol instances';
 $string['meta:selectaslinked'] = 'Select course as meta linked';
index 3cbc4e1..8fb9a7d 100644 (file)
@@ -41,5 +41,19 @@ if ($ADMIN->fulltree) {
             ENROL_EXT_REMOVED_SUSPENDNOROLES => get_string('extremovedsuspendnoroles', 'core_enrol'),
         );
         $settings->add(new admin_setting_configselect('enrol_meta/unenrolaction', get_string('extremovedaction', 'enrol'), get_string('extremovedaction_help', 'enrol'), ENROL_EXT_REMOVED_SUSPENDNOROLES, $options));
+
+        $sortoptions = array(
+            'sortorder' => new lang_string('sort_sortorder', 'admin'),
+            'fullname' => new lang_string('sort_fullname', 'admin'),
+            'shortname' => new lang_string('sort_shortname', 'admin'),
+            'idnumber' => new lang_string('sort_idnumber', 'admin'),
+        );
+        $settings->add(new admin_setting_configselect(
+            'enrol_meta/coursesort',
+            new lang_string('coursesort', 'enrol_meta'),
+            new lang_string('coursesort_help', 'enrol_meta'),
+            'sortorder',
+            $sortoptions
+        ));
     }
 }
index df71ec9..216b532 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2014111000;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2014112400;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2014110400;        // Requires this Moodle version
 $plugin->component = 'enrol_meta';      // Full name of the plugin (used for diagnostics)
 $plugin->cron      = 60*60;             // run&n