Merge branch 'MDL-34437-master' of git://github.com/ankitagarwal/moodle
authorSam Hemelryk <sam@moodle.com>
Tue, 9 Dec 2014 02:58:46 +0000 (15:58 +1300)
committerSam Hemelryk <sam@moodle.com>
Tue, 9 Dec 2014 02:58:46 +0000 (15:58 +1300)
437 files changed:
.csslintrc [new file with mode: 0644]
admin/mnet/peer_forms.php
admin/mnet/peers.php
admin/mnet/testclient.php
admin/roles/module.js
admin/settings/appearance.php
admin/settings/plugins.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/messageinbound/classes/edit_handler_form.php
admin/tool/messageinbound/classes/manager.php
admin/tool/messageinbound/classes/message/inbound/invalid_recipient_handler.php
admin/tool/messageinbound/classes/task/cleanup_task.php
admin/tool/messageinbound/classes/task/pickup_task.php
admin/tool/messageinbound/db/messageinbound_handlers.php
admin/tool/messageinbound/db/messages.php
admin/tool/monitor/tests/eventobservers_test.php
admin/tool/replace/cli/replace.php [new file with mode: 0644]
admin/tool/replace/lang/en/tool_replace.php
admin/tool/spamcleaner/module.js
admin/tool/xmldb/styles_bootstrapbase.css [new file with mode: 0644]
auth/db/auth.php
auth/db/config.html
auth/db/lang/en/auth_db.php
auth/db/tests/db_test.php
auth/db/upgrade.txt [new file with mode: 0644]
auth/email/auth.php
auth/ldap/auth.php
auth/ldap/tests/plugin_test.php
auth/manual/auth.php
auth/mnet/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/backup_stepslib.php
backup/moodle2/restore_stepslib.php
backup/moodle2/tests/moodle2_test.php
backup/util/dbops/backup_controller_dbops.class.php
backup/util/factories/backup_factory.class.php
backup/util/helper/backup_cron_helper.class.php
backup/util/helper/backup_general_helper.class.php
backup/util/helper/backup_helper.class.php
backup/util/helper/restore_prechecks_helper.class.php
backup/util/ui/backup_moodleform.class.php
backup/util/ui/backup_ui.class.php
backup/util/ui/backup_ui_setting.class.php
backup/util/ui/backup_ui_stage.class.php
backup/util/ui/base_moodleform.class.php
backup/util/ui/base_ui.class.php
backup/util/ui/base_ui_stage.class.php
backup/util/ui/import_extensions.php
backup/util/ui/renderer.php
backup/util/ui/restore_moodleform.class.php
backup/util/ui/restore_ui.class.php
backup/util/ui/restore_ui_components.php
backup/util/ui/restore_ui_stage.class.php
backup/util/ui/tests/behat/behat_backup.php
backup/util/ui/tests/behat/restore_moodle2_courses.feature
backup/util/ui/tests/ui_test.php
badges/tests/badgeslib_test.php
badges/tests/behat/award_badge.feature
blocks/activity_modules/tests/behat/block_activity_modules.feature
blocks/comments/tests/behat/add_comment.feature
blocks/comments/tests/behat/behat_block_comments.php
blocks/community/styles.css
blocks/completionstatus/block_completionstatus.php
blocks/edit_form.php
blocks/navigation/styles.css
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/classes/definition.php
cache/classes/factory.php
cache/classes/helper.php
cache/classes/loaders.php
cache/disabledlib.php
cache/forms.php
cache/renderer.php
cache/stores/memcache/lang/en/cachestore_memcache.php
cache/stores/memcached/lang/en/cachestore_memcached.php
cache/stores/mongodb/lang/en/cachestore_mongodb.php
cache/upgrade.txt
calendar/externallib.php
calendar/tests/externallib_test.php
calendar/upgrade.txt
comment/comment.js
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]
composer.json
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/lib.php
course/moodleform_mod.php
course/request.php
course/style.css [deleted file]
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/category_role_assignment.feature [new file with mode: 0644]
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/tests/courserequest_test.php
course/togglecompletion.php
enrol/cohort/edit_form.php
enrol/cohort/lib.php
enrol/guest/tests/behat/guest_access.feature
enrol/manual/lib.php
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/edit_form.php
enrol/self/lang/en/enrol_self.php
enrol/self/lib.php
enrol/self/locallib.php
enrol/self/version.php
enrol/yui/otherusersmanager/otherusersmanager.js
enrol/yui/rolemanager/rolemanager.js
grade/edit/letter/index.php
grade/grading/form/guide/js/guideeditor.js
grade/grading/form/rubric/js/rubriceditor.js
grade/report/grader/module.js
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-debug.js
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-min.js
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable.js
grade/report/grader/yui/src/gradereporttable/js/floatingheaders.js
grade/report/singleview/classes/local/screen/grade.php
grade/report/singleview/classes/local/screen/screen.php
grade/report/singleview/classes/local/screen/select.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
grade/tests/behat/grade_override_letter.feature [new file with mode: 0644]
group/autogroup_form.php
group/group_form.php
group/index.php
group/lib.php
install/lang/nl/install.php
install/lang/pt/install.php
install/lang/ta/admin.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/accesslib.php
lib/behat/classes/behat_selectors.php
lib/behat/classes/util.php
lib/blocklib.php
lib/classes/session/manager.php
lib/classes/task/file_temp_cleanup_task.php
lib/classes/update/checker.php
lib/coursecatlib.php
lib/db/install.xml
lib/db/upgrade.php
lib/deprecatedlib.php
lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-debug.js
lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-min.js
lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button.js
lib/editor/atto/plugins/equation/yui/src/button/js/button.js
lib/editor/atto/plugins/image/lang/en/atto_image.php
lib/editor/atto/plugins/image/lib.php
lib/editor/atto/plugins/image/version.php
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button.js
lib/editor/atto/plugins/image/yui/src/button/js/button.js
lib/editor/atto/upgrade.txt [new file with mode: 0644]
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js
lib/editor/atto/yui/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/atto/yui/src/editor/js/selection.js
lib/editor/tinymce/plugins/managefiles/module.js
lib/filelib.php
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/navigationlib.php
lib/phpunit/bootstrap.php
lib/phpunit/classes/util.php
lib/phpunit/tests/advanced_test.php
lib/questionlib.php
lib/testing/classes/util.php
lib/testing/generator/data_generator.php
lib/testing/tests/generator_test.php
lib/tests/accesslib_test.php
lib/tests/behat/behat_data_generators.php
lib/tests/behat/behat_general.php
lib/tests/behat/behat_hooks.php
lib/tests/behat/behat_permissions.php
lib/tests/blocklib_test.php
lib/tests/messagelib_test.php
lib/tests/moodlelib_test.php
lib/tests/scheduled_task_test.php
lib/tests/session_manager_test.php
lib/tests/weblib_test.php
lib/thirdpartylibs.xml
lib/upgrade.txt
lib/weblib.php
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/confirm.php
login/index.php
login/lib.php
login/set_password_form.php
login/signup.php
message/module.js
message/output/email/message_output_email.php
mnet/peer.php
mnet/xmlrpc/client.php
mod/assign/feedback/editpdf/lang/en/assignfeedback_editpdf.php
mod/assign/feedback/editpdf/testgs.php
mod/assign/locallib.php
mod/assign/tests/behat/group_submission.feature
mod/assign/tests/locallib_test.php
mod/book/tool/print/index.php
mod/book/tool/print/print.css
mod/chat/chatd.php
mod/chat/gui_ajax/module.js
mod/feedback/backup/moodle1/lib.php
mod/forum/classes/message/inbound/reply_handler.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/subscribe.php
mod/forum/subscribers.php
mod/forum/tests/behat/completion_condition_number_discussions.feature
mod/forum/tests/behat/discussion_subscriptions.feature
mod/forum/tests/behat/forum_subscriptions_management.feature [new file with mode: 0644]
mod/forum/tests/mail_test.php
mod/forum/tests/maildigest_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/continue.php
mod/lesson/format.php
mod/lesson/tests/behat/lesson_navigation.feature
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/resource/lib.php
mod/scorm/module.js
mod/scorm/styles.css
mod/scorm/view.js
mod/survey/survey.js
mod/wiki/tests/behat/wiki_comments.feature
pix/t/viewdetails.png [new file with mode: 0644]
pix/t/viewdetails.svg [new file with mode: 0644]
question/engine/tests/helpers.php
question/format.php
question/format/gift/tests/behat/import_export.feature
question/format/xhtml/format.php
question/format/xhtml/xhtml.css
question/format/xml/tests/behat/import_export.feature
question/tests/behat/copy_questions.feature
question/tests/behat/delete_questions.feature
question/tests/behat/edit_questions.feature
question/tests/behat/preview_question.feature
question/tests/behat/question_categories.feature
question/tests/behat/sort_questions.feature
question/upgrade.txt
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/equella/callback.php
repository/filepicker.js
tag/tag.js
theme/base/style/core.css
theme/base/style/course.css
theme/base/style/dock.css
theme/base/style/grade.css
theme/base/style/user.css
theme/bootstrapbase/less/moodle/dock.less
theme/bootstrapbase/less/moodle/user.less
theme/bootstrapbase/style/moodle.css
theme/canvas/config.php
theme/canvas/style/question.css [deleted file]
user/edit_form.php
user/lib.php
user/selector/module.js
user/tests/userlib_test.php
user/view.php
version.php
webservice/renderer.php

diff --git a/.csslintrc b/.csslintrc
new file mode 100644 (file)
index 0000000..5b01fd1
--- /dev/null
@@ -0,0 +1,3 @@
+--errors=errors,duplicate-properties
+--warnings=known-properties,display-property-grouping,empty-rules,important
+--exclude-list=vendor/,lib/editor/tinymce/,lib/yuilib/,theme/bootstrapbase/style/
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 bee9753..7a1551a 100644 (file)
@@ -77,6 +77,10 @@ if ($hassiteconfig) {
     $temp->add(new admin_setting_configcheckbox('loginpageautofocus', new lang_string('loginpageautofocus', 'admin'), new lang_string('loginpageautofocus_help', 'admin'), 0));
     $temp->add(new admin_setting_configselect('guestloginbutton', new lang_string('guestloginbutton', 'auth'),
                                               new lang_string('showguestlogin', 'auth'), '1', array('0'=>new lang_string('hide'), '1'=>new lang_string('show'))));
+    $options = array(0 => get_string('no'), 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 10 => 10, 20 => 20, 50 => 50);
+    $temp->add(new admin_setting_configselect('limitconcurrentlogins',
+        new lang_string('limitconcurrentlogins', 'core_auth'),
+        new lang_string('limitconcurrentlogins_desc', 'core_auth'), 0, $options));
     $temp->add(new admin_setting_configtext('alternateloginurl', new lang_string('alternateloginurl', 'auth'),
                                             new lang_string('alternatelogin', 'auth', htmlspecialchars(get_login_url())), ''));
     $temp->add(new admin_setting_configtext('forgottenpasswordurl', new lang_string('forgottenpasswordurl', 'auth'),
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 a3c36ac..a0bea75 100644 (file)
@@ -33,6 +33,10 @@ require_once($CFG->libdir . '/formslib.php');
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class tool_messageinbound_edit_handler_form extends moodleform {
+
+    /**
+     * The form definition
+     */
     public function definition() {
         $mform = $this->_form;
 
index e9f0108..dcb4e7b 100644 (file)
@@ -60,7 +60,7 @@ class manager {
     const MESSAGE_DELETED = '\deleted';
 
     /**
-     * @var Horde_Imap_Client_Socket A reference to the IMAP client.
+     * @var \Horde_Imap_Client_Socket A reference to the IMAP client.
      */
     protected $client = null;
 
@@ -70,7 +70,7 @@ class manager {
     protected $addressmanager = null;
 
     /**
-     * @var stdClass The data for the current message being processed.
+     * @var \stdClass The data for the current message being processed.
      */
     protected $currentmessagedata = null;
 
@@ -126,6 +126,7 @@ class manager {
      * Get the current mailbox information.
      *
      * @return \Horde_Imap_Client_Mailbox
+     * @throws \core\message\inbound\processing_failed_exception if the mailbox could not be opened.
      */
     protected function get_mailbox() {
         // Get the current mailbox.
@@ -140,6 +141,8 @@ class manager {
 
     /**
      * Execute the main Inbound Message pickup task.
+     *
+     * @return bool
      */
     public function pickup_messages() {
         if (!$this->get_imap_client()) {
@@ -176,8 +179,9 @@ class manager {
     /**
      * Process a message received and validated by the Inbound Message processor.
      *
-     * @param stdClass $maildata The data retrieved from the database for the current record.
+     * @param \stdClass $maildata The data retrieved from the database for the current record.
      * @return bool Whether the message was successfully processed.
+     * @throws \core\message\inbound\processing_failed_exception if the message cannot be found.
      */
     public function process_existing_message(\stdClass $maildata) {
         // Grab the new IMAP client.
@@ -273,9 +277,9 @@ class manager {
     /**
      * Process a message and pass it through the Inbound Message handling systems.
      *
-     * @param Horde_Imap_Client_Data_Fetch $message The message to process
+     * @param \Horde_Imap_Client_Data_Fetch $message The message to process
      * @param bool $viewreadmessages Whether to also look at messages which have been marked as read
-     * @param bool $skipsenderverification Whether to skip the sender verificiation stage
+     * @param bool $skipsenderverification Whether to skip the sender verification stage
      */
     public function process_message(
             \Horde_Imap_Client_Data_Fetch $message,
@@ -437,9 +441,9 @@ class manager {
     /**
      * Process a message to retrieve it's header data without body and attachemnts.
      *
-     * @param Horde_Imap_Client_Data_Envelope $envelope The Envelope of the message
-     * @param Horde_Imap_Client_Data_Fetch $messagedata The structure and part of the message body
-     * @param string|Horde_Imap_Client_Ids $messageid The Hore message Uid
+     * @param \Horde_Imap_Client_Data_Envelope $envelope The Envelope of the message
+     * @param \Horde_Imap_Client_Data_Fetch $basemessagedata The structure and part of the message body
+     * @param string|\Horde_Imap_Client_Ids $messageid The Hore message Uid
      * @return \stdClass The current value of the messagedata
      */
     private function process_message_data(
@@ -496,7 +500,6 @@ class manager {
     /**
      * Process a message again to add body and attachment data.
      *
-     * @param Horde_Imap_Client_Data_Envelope $envelope The Envelope of the message
      * @param Horde_Imap_Client_Data_Fetch $basemessagedata The structure and part of the message body
      * @param string|Horde_Imap_Client_Ids $messageid The Hore message Uid
      * @return \stdClass The current value of the messagedata
@@ -578,9 +581,9 @@ class manager {
     /**
      * Process the messagedata and part data to extract the content of this part.
      *
-     * @param $messagedata The structure and part of the message body
-     * @param $partdata The part data
-     * @param $part The part ID
+     * @param \Horde_Imap_Client_Data_Fetch $messagedata The structure and part of the message body
+     * @param \Horde_Mime_Part $partdata The part data
+     * @param string $part The part ID
      * @return string
      */
     private function process_message_part_body($messagedata, $partdata, $part) {
@@ -608,10 +611,12 @@ class manager {
     /**
      * Process a message again to add body and attachment data.
      *
-     * @param $messagedata The structure and part of the message body
-     * @param $partdata The part data
-     * @param $filename The filename of the attachment
+     * @param \Horde_Imap_Client_Data_Fetch $messagedata The structure and part of the message body
+     * @param \Horde_Mime_Part $partdata The part data
+     * @param string $part The part ID.
+     * @param string $filename The filename of the attachment
      * @return \stdClass
+     * @throws \core\message\inbound\processing_failed_exception If the attachment can't be saved to disk.
      */
     private function process_message_part_attachment($messagedata, $partdata, $part, $filename) {
         global $CFG;
@@ -660,8 +665,8 @@ class manager {
     /**
      * Check whether the key provided is valid.
      *
-     * @param $status The Message to process
-     * @param $messageid The Hore message Uid
+     * @param bool $status
+     * @param mixed $messageid The Hore message Uid
      * @return bool
      */
     private function passes_key_validation($status, $messageid) {
@@ -682,7 +687,7 @@ class manager {
     /**
      * Add the specified flag to the message.
      *
-     * @param $messageid
+     * @param mixed $messageid
      * @param string $flag The flag to add
      */
     private function add_flag_to_message($messageid, $flag) {
@@ -699,7 +704,7 @@ class manager {
     /**
      * Remove the specified flag from the message.
      *
-     * @param $messageid
+     * @param mixed $messageid
      * @param string $flag The flag to remove
      */
     private function remove_flag_from_message($messageid, $flag) {
@@ -716,7 +721,7 @@ class manager {
     /**
      * Check whether the message has the specified flag
      *
-     * @param $messageid
+     * @param mixed $messageid
      * @param string $flag The flag to check
      * @return bool
      */
@@ -739,6 +744,8 @@ class manager {
     /**
      * Send the message to the appropriate handler.
      *
+     * @return bool
+     * @throws \core\message\inbound\processing_failed_exception if anything goes wrong.
      */
     private function send_to_handler() {
         try {
@@ -785,7 +792,9 @@ class manager {
      * stored. The message includes a verification link and reply-to address which is handled by the
      * invalid_recipient_handler.
      *
-     * @param $recipient The message recipient
+     * @param \Horde_Imap_Client_Ids $messageids
+     * @param string $recipient The message recipient
+     * @return bool
      */
     private function handle_verification_failure(
             \Horde_Imap_Client_Ids $messageids,
@@ -889,8 +898,9 @@ class manager {
     /**
      * Inform the identified sender that message processing was successful.
      *
-     * @param stdClass $messagedata The data for the current message being processed.
+     * @param \stdClass $messagedata The data for the current message being processed.
      * @param mixed $handlerresult The result returned by the handler.
+     * @return bool
      */
     private function inform_user_of_success(\stdClass $messagedata, $handlerresult) {
         global $USER;
@@ -948,7 +958,7 @@ class manager {
     /**
      * Return a formatted subject line for replies.
      *
-     * @param $subject string The subject string
+     * @param string $subject The subject string
      * @return string The formatted reply subject
      */
     private function get_reply_subject($subject) {
index 7ff3a69..df51bf0 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * A Handler to re-process messages which previously failed sender
- * verification.
+ * A Handler to re-process messages which previously failed sender verification.
  *
- * @package    task_messageinbound
+ * @package    tool_messageinbound
+ * @category   message
  * @copyright  2014 Andrew Nicols
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -30,14 +30,12 @@ defined('MOODLE_INTERNAL') || die();
 require_once($CFG->dirroot . '/repository/lib.php');
 
 /**
- * A Handler to re-process messages which previously failed sender
- * verification.
+ * A Handler to re-process messages which previously failed sender verification.
  *
  * This may happen if the user did not use their registerd e-mail address,
  * the verification hash used had expired, or if some erroneous content was
  * introduced into the content hash.
  *
- * @package    task
  * @copyright  2014 Andrew Nicols
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -72,9 +70,10 @@ class invalid_recipient_handler extends \core\message\inbound\handler {
     /**
      * Process a message received and validated by the Inbound Message processor.
      *
-     * @param $messagedata The Inbound Message record
-     * @param $messagedata The message data packet.
+     * @param \stdClass $record The Inbound Message record
+     * @param \stdClass $data The message data packet.
      * @return bool Whether the message was successfully processed.
+     * @throws \core\message\inbound\processing_failed_exception when the message can not be found.
      */
     public function process_message(\stdClass $record, \stdClass $data) {
         global $DB;
index e507597..e8b1c7a 100644 (file)
@@ -18,6 +18,7 @@
  * A scheduled task to handle cleanup of old, unconfirmed e-mails.
  *
  * @package    tool_messageinbound
+ * @category   task
  * @copyright  2014 Andrew Nicols
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
index d10fb8c..1342b6f 100644 (file)
@@ -18,6 +18,7 @@
  * A scheduled task to handle Inbound Message e-mail pickup.
  *
  * @package    tool_messageinbound
+ * @category   task
  * @copyright  2014 Andrew Nicols
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
index 50cb34a..1ec8ed2 100644 (file)
@@ -17,8 +17,7 @@
 /**
  * Handlers for tool_messageinbound.
  *
- * @package    task
- * @category   tool_messageinbound
+ * @package    tool_messageinbound
  * @copyright  2014 Andrew Nicols
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
index c425c48..38d35ca 100644 (file)
@@ -17,8 +17,7 @@
 /**
  * Message Providers for task_messageinbound.
  *
- * @package    task
- * @category   messageinbound
+ * @package    tool_messageinbound
  * @copyright  2014 Andrew Nicols
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
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");
     }
 
     /**
diff --git a/admin/tool/replace/cli/replace.php b/admin/tool/replace/cli/replace.php
new file mode 100644 (file)
index 0000000..1f9e74b
--- /dev/null
@@ -0,0 +1,94 @@
+<?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/>.
+
+/**
+ * Search and replace strings throughout all texts in the whole database.
+ *
+ * @package    tool_replace
+ * @copyright  1999 onwards Martin Dougiamas (http://dougiamas.com)
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+
+require(__DIR__.'/../../../../config.php');
+require_once($CFG->libdir.'/clilib.php');
+require_once($CFG->libdir.'/adminlib.php');
+
+$help =
+    "Search and replace text throughout the whole database.
+
+Options:
+--search=STRING       String to search for.
+--replace=STRING      String to replace with.
+--shorten             Shorten result if necessary.
+--non-interactive     Perform the replacement without confirming.
+-h, --help            Print out this help.
+
+Example:
+\$ sudo -u www-data /usr/bin/php admin/tool/replace/cli/replace.php --search=//oldsitehost --replace=//newsitehost
+";
+
+list($options, $unrecognized) = cli_get_params(
+    array(
+        'search'  => null,
+        'replace' => null,
+        'shorten' => false,
+        'non-interactive' => false,
+        'help'    => false,
+    ),
+    array(
+        'h' => 'help',
+    )
+);
+
+if ($options['help'] || $options['search'] === null || $options['replace'] === null) {
+    echo $help;
+    exit(0);
+}
+
+if (!$DB->replace_all_text_supported()) {
+    cli_error(get_string('notimplemented', 'tool_replace'));
+}
+
+if (empty($options['shorten']) && core_text::strlen($options['search']) < core_text::strlen($options['replace'])) {
+    cli_error(get_string('cannotfit', 'tool_replace'));
+}
+
+try {
+    $search = validate_param($options['search'], PARAM_RAW);
+    $replace = validate_param($options['replace'], PARAM_RAW);
+} catch (invalid_parameter_exception $e) {
+    cli_error(get_string('invalidcharacter', 'tool_replace'));
+}
+
+if (!$options['non-interactive']) {
+    echo get_string('excludedtables', 'tool_replace') . "\n\n";
+    echo get_string('notsupported', 'tool_replace') . "\n\n";
+    $prompt = get_string('cliyesnoprompt', 'admin');
+    $input = cli_input($prompt, '', array(get_string('clianswerno', 'admin'), get_string('cliansweryes', 'admin')));
+    if ($input == get_string('clianswerno', 'admin')) {
+        exit(1);
+    }
+}
+
+if (!db_replace($search, $replace)) {
+    cli_heading(get_string('error'));
+    exit(1);
+}
+
+cli_heading(get_string('success'));
+exit(0);
index 919ed67..481fd06 100644 (file)
@@ -27,11 +27,12 @@ $string['cannotfit'] = 'The replacement is longer than original and shortening i
 $string['disclaimer'] = 'I understand the risks of this operation';
 $string['doit'] = 'Yes, do it!';
 $string['excludedtables'] = 'Several tables are not updated as part of the text replacement. This include configuration, log, events, and session tables.';
-$string['pageheader'] = 'Search and replace text throughout the whole database';
+$string['invalidcharacter'] = 'Invalid characters were found in the search or replacement text.';
 $string['notifyfinished'] = '...finished';
 $string['notifyrebuilding'] = 'Rebuilding course cache...';
 $string['notimplemented'] = 'Sorry, this feature is not implemented in your database driver.';
-$string['notsupported'] ='This script is not supported, always make complete backup before proceeding!<br />This operation can not be reverted!';
+$string['notsupported'] = 'This script should be considered experimental and the changes it makes can not be reverted. Please make a complete backup for running this script!';
+$string['pageheader'] = 'Search and replace text throughout the whole database';
 $string['pluginname'] = 'DB search and replace';
 $string['replacewith'] = 'Replace with this string';
 $string['replacewithhelp'] = 'usually new server URL';
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 faccb6d..3feed3e 100644 (file)
@@ -105,29 +105,35 @@ class auth_plugin_db extends auth_plugin_base {
 
             $authdb = $this->db_init();
 
-            if ($this->config->passtype === 'md5') {   // Re-format password accordingly.
-                $extpassword = md5($extpassword);
-            } else if ($this->config->passtype === 'sha1') {
-                $extpassword = sha1($extpassword);
-            }
-
-            $rs = $authdb->Execute("SELECT *
+            $rs = $authdb->Execute("SELECT {$this->config->fieldpass} AS userpass
                                       FROM {$this->config->table}
-                                     WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'
-                                           AND {$this->config->fieldpass} = '".$this->ext_addslashes($extpassword)."'");
+                                     WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'");
             if (!$rs) {
                 $authdb->Close();
                 debugging(get_string('auth_dbcantconnect','auth_db'));
                 return false;
             }
 
-            if (!$rs->EOF) {
-                $rs->Close();
+            if ($rs->EOF) {
                 $authdb->Close();
-                return true;
+                return false;
+            }
+
+            $fields = array_change_key_case($rs->fields, CASE_LOWER);
+            $fromdb = $fields['userpass'];
+            $rs->Close();
+            $authdb->Close();
+
+            if ($this->config->passtype === 'plaintext') {
+                return ($fromdb == $extpassword);
+            } else if ($this->config->passtype === 'md5') {
+                return (strtolower($fromdb) == md5($extpassword));
+            } else if ($this->config->passtype === 'sha1') {
+                return (strtolower($fromdb) == sha1($extpassword));
+            } else if ($this->config->passtype === 'saltedcrypt') {
+                require_once($CFG->libdir.'/password_compat/lib/password.php');
+                return password_verify($extpassword, $fromdb);
             } else {
-                $rs->Close();
-                $authdb->Close();
                 return false;
             }
 
index 9366967..331ad04 100644 (file)
         $passtype["plaintext"] = get_string("plaintext", "auth");
         $passtype["md5"]       = get_string("md5", "auth");
         $passtype["sha1"]      = get_string("sha1", "auth");
+        $passtype["saltedcrypt"]      = get_string("auth_dbsaltedcrypt", "auth_db");
         $passtype["internal"]  = get_string("internal", "auth");
         echo html_writer::select($passtype, "passtype", $config->passtype, false);
 
index 678f11b..ac3e645 100644 (file)
@@ -49,6 +49,7 @@ $string['auth_dbpasstype'] = '<p>Specify the format that the password field is u
 $string['auth_dbpasstype_key'] = 'Password format';
 $string['auth_dbreviveduser'] = 'Revived user {$a->name} id {$a->id}';
 $string['auth_dbrevivedusererror'] = 'Error reviving user {$a}';
+$string['auth_dbsaltedcrypt'] = 'Crypt one-way string hashing';
 $string['auth_dbsetupsql'] = 'SQL setup command';
 $string['auth_dbsetupsqlhelp'] = 'SQL command for special database setup, often used to setup communication encoding - example for MySQL and PostgreSQL: <em>SET NAMES \'utf8\'</em>';
 $string['auth_dbsuspenduser'] = 'Suspended user {$a->name} id {$a->id}';
index 3931ea9..e619df6 100644 (file)
@@ -306,6 +306,13 @@ class auth_db_testcase extends advanced_testcase {
         $DB->update_record('auth_db_users', $user3);
         $this->assertTrue($auth->user_login('u3', 'heslo'));
 
+        require_once($CFG->libdir.'/password_compat/lib/password.php');
+        set_config('passtype', 'saltedcrypt', 'auth/db');
+        $auth->config->passtype = 'saltedcrypt';
+        $user3->pass = password_hash('heslo', PASSWORD_BCRYPT, array('salt' => 'best_salt_ever_moodle_rocks_dont_tell'));
+        $DB->update_record('auth_db_users', $user3);
+        $this->assertTrue($auth->user_login('u3', 'heslo'));
+
         set_config('passtype', 'internal', 'auth/db');
         $auth->config->passtype = 'internal';
         create_user_record('u3', 'heslo', 'db');
diff --git a/auth/db/upgrade.txt b/auth/db/upgrade.txt
new file mode 100644 (file)
index 0000000..45258e7
--- /dev/null
@@ -0,0 +1,10 @@
+This files describes API changes in /auth/db/*,
+information provided here is intended especially for developers.
+
+=== 2.9 ===
+
+Some alterations have been made to the handling of case sensitity handling of passwords
+and password hashes which previously varied depending on database configuration:
+
+* Plain text password matching is now always case sensitive
+* sha1/md5 hash comparisons are now enforced case insensitive (as underlying they are hexidecimal values)
index 2c36eab..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);
 
@@ -147,9 +150,6 @@ class auth_plugin_email extends auth_plugin_base {
 
             } else if ($user->secret == $confirmsecret) {   // They have provided the secret key to get in
                 $DB->set_field("user", "confirmed", 1, array("id"=>$user->id));
-                if ($user->firstaccess == 0) {
-                    $DB->set_field("user", "firstaccess", time(), array("id"=>$user->id));
-                }
                 return AUTH_CONFIRM_OK;
             }
         } else {
index b77c305..1da1b66 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);
 
@@ -615,9 +618,6 @@ class auth_plugin_ldap extends auth_plugin_base {
                     return AUTH_CONFIRM_FAIL;
                 }
                 $user->confirmed = 1;
-                if ($user->firstaccess == 0) {
-                    $user->firstaccess = time();
-                }
                 user_update_user($user, false);
                 return AUTH_CONFIRM_OK;
             }
@@ -742,7 +742,7 @@ class auth_plugin_ldap extends auth_plugin_base {
                     } while ($entry = ldap_next_entry($ldapconnection, $entry));
                 }
                 unset($ldap_result); // Free mem.
-            } while ($ldap_pagedresults && !empty($ldap_cookie));
+            } while ($ldap_pagedresults && $ldap_cookie !== null && $ldap_cookie != '');
         }
 
         // If LDAP paged results were used, the current connection must be completely
index 7f0855e..ebb6f31 100644 (file)
@@ -386,8 +386,9 @@ class auth_ldap_plugin_testcase extends advanced_testcase {
         $auth = get_auth_plugin('ldap');
 
         $sink = $this->redirectEvents();
+        $mailsink = $this->redirectEmails();
         $auth->user_signup((object)$user, false);
-        $this->assertDebuggingCalled('Not sending email due to $CFG->noemailever config setting');
+        $this->assertEquals(1, $mailsink->count());
         $events = $sink->get_events();
         $sink->close();
 
index 0b014bb..938aae8 100644 (file)
@@ -233,9 +233,6 @@ class auth_plugin_manual extends auth_plugin_base {
                 return AUTH_CONFIRM_ALREADY;
             } else {
                 $DB->set_field("user", "confirmed", 1, array("id"=>$user->id));
-                if ($user->firstaccess == 0) {
-                    $DB->set_field("user", "firstaccess", time(), array("id"=>$user->id));
-                }
                 return AUTH_CONFIRM_OK;
             }
         } else  {
index ef3d1fc..f061539 100644 (file)
@@ -289,7 +289,7 @@ class auth_plugin_mnet extends auth_plugin_base {
             } See MDL-21327   for why this is commented out
             */
             $remoteuser->mnethostid = $remotehost->id;
-            $remoteuser->firstaccess = time(); // First time user in this server, grab it here
+            $remoteuser->firstaccess = 0;
             $remoteuser->confirmed = 1;
 
             $remoteuser->id = $DB->insert_record('user', $remoteuser);
@@ -359,9 +359,6 @@ class auth_plugin_mnet extends auth_plugin_base {
         }
 
         $localuser->mnethostid = $remotepeer->id;
-        if (empty($localuser->firstaccess)) { // Now firstaccess, grab it here
-            $localuser->firstaccess = time();
-        }
         user_update_user($localuser, false);
 
         if (!$firsttime) {
index 5308808..a2acc9f 100644 (file)
@@ -1,6 +1,12 @@
 This files describes API changes in /auth/* - plugins,
 information provided here is intended especially for developers.
 
+=== 2.9 ===
+
+* 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 84cf4de..de35cc3 100644 (file)
@@ -503,6 +503,14 @@ class backup_course_structure_step extends backup_structure_step {
  */
 class backup_enrolments_structure_step extends backup_structure_step {
 
+    /**
+     * Skip enrolments on the front page.
+     * @return bool
+     */
+    protected function execute_condition() {
+        return ($this->get_courseid() != SITEID);
+    }
+
     protected function define_structure() {
 
         // To know if we are including users
@@ -921,7 +929,12 @@ class backup_gradebook_structure_step extends backup_structure_step {
      * the module gradeitems have been already included in backup
      */
     protected function execute_condition() {
-        return backup_plan_dbops::require_gradebook_backup($this->get_courseid(), $this->get_backupid());
+        $courseid = $this->get_courseid();
+        if ($courseid == SITEID) {
+            return false;
+        }
+
+        return backup_plan_dbops::require_gradebook_backup($courseid, $this->get_backupid());
     }
 
     protected function define_structure() {
@@ -1035,7 +1048,12 @@ class backup_grade_history_structure_step extends backup_structure_step {
      * because we do not want to save the history of items which are not backed up. At least for now.
      */
     protected function execute_condition() {
-        return backup_plan_dbops::require_gradebook_backup($this->get_courseid(), $this->get_backupid());
+        $courseid = $this->get_courseid();
+        if ($courseid == SITEID) {
+            return false;
+        }
+
+        return backup_plan_dbops::require_gradebook_backup($courseid, $this->get_backupid());
     }
 
     protected function define_structure() {
@@ -1088,6 +1106,14 @@ class backup_grade_history_structure_step extends backup_structure_step {
  */
 class backup_userscompletion_structure_step extends backup_structure_step {
 
+    /**
+     * Skip completion on the front page.
+     * @return bool
+     */
+    protected function execute_condition() {
+        return ($this->get_courseid() != SITEID);
+    }
+
     protected function define_structure() {
 
         // Define each element separated
@@ -1617,6 +1643,7 @@ class backup_main_structure_step extends backup_structure_step {
         $info['original_site_identifier_hash'] = md5(get_site_identifier());
         $info['original_course_id'] = $this->get_courseid();
         $originalcourseinfo = backup_controller_dbops::backup_get_original_course_info($this->get_courseid());
+        $info['original_course_format'] = $originalcourseinfo->format;
         $info['original_course_fullname']  = $originalcourseinfo->fullname;
         $info['original_course_shortname'] = $originalcourseinfo->shortname;
         $info['original_course_startdate'] = $originalcourseinfo->startdate;
@@ -1634,7 +1661,7 @@ class backup_main_structure_step extends backup_structure_step {
         $information = new backup_nested_element('information', null, array(
             'name', 'moodle_version', 'moodle_release', 'backup_version',
             'backup_release', 'backup_date', 'mnet_remoteusers', 'include_files', 'include_file_references_to_external_content', 'original_wwwroot',
-            'original_site_identifier_hash', 'original_course_id',
+            'original_site_identifier_hash', 'original_course_id', 'original_course_format',
             'original_course_fullname', 'original_course_shortname', 'original_course_startdate',
             'original_course_contextid', 'original_system_contextid'));
 
@@ -2102,6 +2129,12 @@ class backup_activity_grading_structure_step extends backup_structure_step {
      * Include the grading.xml only if the module supports advanced grading
      */
     protected function execute_condition() {
+
+        // No grades on the front page.
+        if ($this->get_courseid() == SITEID) {
+            return false;
+        }
+
         return plugin_supports('mod', $this->get_task()->get_modulename(), FEATURE_ADVANCED_GRADING, false);
     }
 
@@ -2175,6 +2208,14 @@ class backup_activity_grading_structure_step extends backup_structure_step {
  */
 class backup_activity_grades_structure_step extends backup_structure_step {
 
+    /**
+     * No grades on the front page.
+     * @return bool
+     */
+    protected function execute_condition() {
+        return ($this->get_courseid() != SITEID);
+    }
+
     protected function define_structure() {
 
         // To know if we are including userinfo
@@ -2259,6 +2300,14 @@ class backup_activity_grades_structure_step extends backup_structure_step {
  */
 class backup_activity_grade_history_structure_step extends backup_structure_step {
 
+    /**
+     * No grades on the front page.
+     * @return bool
+     */
+    protected function execute_condition() {
+        return ($this->get_courseid() != SITEID);
+    }
+
     protected function define_structure() {
 
         // Settings to use.
@@ -2306,6 +2355,12 @@ class backup_activity_grade_history_structure_step extends backup_structure_step
 class backup_course_completion_structure_step extends backup_structure_step {
 
     protected function execute_condition() {
+
+        // No completion on front page.
+        if ($this->get_courseid() == SITEID) {
+            return false;
+        }
+
         // Check that all activities have been included
         if ($this->task->is_excluding_activities()) {
             return false;
index fc4fe15..7df7a2e 100644 (file)
@@ -89,6 +89,10 @@ class restore_gradebook_structure_step extends restore_structure_step {
      protected function execute_condition() {
         global $CFG, $DB;
 
+        if ($this->get_courseid() == SITEID) {
+            return false;
+        }
+
         // No gradebook info found, don't execute
         $fullpath = $this->task->get_taskbasepath();
         $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
@@ -464,6 +468,10 @@ class restore_grade_history_structure_step extends restore_structure_step {
      protected function execute_condition() {
         global $CFG, $DB;
 
+        if ($this->get_courseid() == SITEID) {
+            return false;
+        }
+
         // No gradebook info found, don't execute.
         $fullpath = $this->task->get_taskbasepath();
         $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
@@ -1814,9 +1822,15 @@ class restore_ras_and_caps_structure_step extends restore_structure_step {
  * If no instances yet add default enrol methods the same way as when creating new course in UI.
  */
 class restore_default_enrolments_step extends restore_execution_step {
+
     public function define_execution() {
         global $DB;
 
+        // No enrolments in front page.
+        if ($this->get_courseid() == SITEID) {
+            return;
+        }
+
         $course = $DB->get_record('course', array('id'=>$this->get_courseid()), '*', MUST_EXIST);
 
         if ($DB->record_exists('enrol', array('courseid'=>$this->get_courseid(), 'enrol'=>'manual'))) {
@@ -1853,6 +1867,10 @@ class restore_enrolments_structure_step extends restore_structure_step {
      */
     protected function execute_condition() {
 
+        if ($this->get_courseid() == SITEID) {
+            return false;
+        }
+
         // Check it is included in the backup
         $fullpath = $this->task->get_taskbasepath();
         $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
@@ -2444,6 +2462,11 @@ class restore_course_completion_structure_step extends restore_structure_step {
             return false;
         }
 
+        // No course completion on the front page.
+        if ($this->get_courseid() == SITEID) {
+            return false;
+        }
+
         // Check it is included in the backup
         $fullpath = $this->task->get_taskbasepath();
         $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
@@ -2791,6 +2814,10 @@ class restore_activity_grading_structure_step extends restore_structure_step {
      */
      protected function execute_condition() {
 
+        if ($this->get_courseid() == SITEID) {
+            return false;
+        }
+
         $fullpath = $this->task->get_taskbasepath();
         $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
         if (!file_exists($fullpath)) {
@@ -2925,6 +2952,14 @@ class restore_activity_grading_structure_step extends restore_structure_step {
  */
 class restore_activity_grades_structure_step extends restore_structure_step {
 
+    /**
+     * No grades in front page.
+     * @return bool
+     */
+    protected function execute_condition() {
+        return ($this->get_courseid() != SITEID);
+    }
+
     protected function define_structure() {
 
         $paths = array();
@@ -3061,6 +3096,11 @@ class restore_activity_grade_history_structure_step extends restore_structure_st
      * This step is executed only if the grade history file is present.
      */
      protected function execute_condition() {
+
+        if ($this->get_courseid() == SITEID) {
+            return false;
+        }
+
         $fullpath = $this->task->get_taskbasepath();
         $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
         if (!file_exists($fullpath)) {
@@ -3147,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;
             }
         }
@@ -3447,6 +3528,11 @@ class restore_userscompletion_structure_step extends restore_structure_step {
              return false;
          }
 
+        // No completion on the front page.
+        if ($this->get_courseid() == SITEID) {
+            return false;
+        }
+
          // No user completion info found, don't execute
         $fullpath = $this->task->get_taskbasepath();
         $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
index 960c9ec..4842d4a 100644 (file)
@@ -391,6 +391,81 @@ class core_backup_moodle2_testcase extends advanced_testcase {
                 'assign', 'allowsubmissionsfromdate', array('id' => $newassign->instance)));
     }
 
+    /**
+     * Test front page backup/restore and duplicate activities
+     * @return void
+     */
+    public function test_restore_frontpage() {
+        global $DB, $CFG, $USER;
+
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+        $generator = $this->getDataGenerator();
+
+        $frontpage = $DB->get_record('course', array('id' => SITEID));
+        $forum = $generator->create_module('forum', array('course' => $frontpage->id));
+
+        // Activities can be duplicated.
+        $this->duplicate($frontpage, $forum->cmid);
+
+        $modinfo = get_fast_modinfo($frontpage);
+        $this->assertEquals(2, count($modinfo->get_instances_of('forum')));
+
+        // Front page backup.
+        $frontpagebc = new backup_controller(backup::TYPE_1COURSE, $frontpage->id,
+                backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT,
+                $USER->id);
+        $frontpagebackupid = $frontpagebc->get_backupid();
+        $frontpagebc->execute_plan();
+        $frontpagebc->destroy();
+
+        $course = $generator->create_course();
+        $newcourseid = restore_dbops::create_new_course(
+                $course->fullname . ' 2', $course->shortname . '_2', $course->category);
+
+        // Other course backup.
+        $bc = new backup_controller(backup::TYPE_1COURSE, $course->id,
+                backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT,
+                $USER->id);
+        $otherbackupid = $bc->get_backupid();
+        $bc->execute_plan();
+        $bc->destroy();
+
+        // We can only restore a front page over the front page.
+        $rc = new restore_controller($frontpagebackupid, $course->id,
+                backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id,
+                backup::TARGET_CURRENT_ADDING);
+        $this->assertFalse($rc->execute_precheck());
+        $rc->destroy();
+
+        $rc = new restore_controller($frontpagebackupid, $newcourseid,
+                backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id,
+                backup::TARGET_NEW_COURSE);
+        $this->assertFalse($rc->execute_precheck());
+        $rc->destroy();
+
+        $rc = new restore_controller($frontpagebackupid, $frontpage->id,
+                backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id,
+                backup::TARGET_CURRENT_ADDING);
+        $this->assertTrue($rc->execute_precheck());
+        $rc->execute_plan();
+        $rc->destroy();
+
+        // We can't restore a non-front page course on the front page course.
+        $rc = new restore_controller($otherbackupid, $frontpage->id,
+                backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id,
+                backup::TARGET_CURRENT_ADDING);
+        $this->assertFalse($rc->execute_precheck());
+        $rc->destroy();
+
+        $rc = new restore_controller($otherbackupid, $newcourseid,
+                backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id,
+                backup::TARGET_NEW_COURSE);
+        $this->assertTrue($rc->execute_precheck());
+        $rc->execute_plan();
+        $rc->destroy();
+    }
+
     /**
      * Backs a course up and restores it.
      *
index 41ff9d3..e7456ce 100644 (file)
@@ -524,7 +524,7 @@ abstract class backup_controller_dbops extends backup_dbops {
      */
     public static function backup_get_original_course_info($courseid) {
         global $DB;
-        return $DB->get_record('course', array('id' => $courseid), 'fullname, shortname, startdate');
+        return $DB->get_record('course', array('id' => $courseid), 'fullname, shortname, startdate, format');
     }
 
     /**
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 434059b..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());
+                    }
                 }
             }
 
@@ -556,9 +574,6 @@ abstract class backup_cron_automated_helper {
             return true;
         }
 
-        $backupword = str_replace(' ', '_', core_text::strtolower(get_string('backupfilename')));
-        $backupword = trim(clean_filename($backupword), '_');
-
         if (!file_exists($dir) || !is_dir($dir) || !is_writable($dir)) {
             $dir = null;
         }
@@ -573,9 +588,6 @@ abstract class backup_cron_automated_helper {
             $files = array();
             // Store all the matching files into timemodified => stored_file array.
             foreach ($fs->get_area_files($context->id, $component, $filearea, $itemid) as $file) {
-                if (strpos($file->get_filename(), $backupword) !== 0) {
-                    continue;
-                }
                 $files[$file->get_timemodified()] = $file;
             }
             if (count($files) <= $keep) {
@@ -595,8 +607,8 @@ abstract class backup_cron_automated_helper {
         if (!empty($dir) && ($storage == 1 || $storage == 2)) {
             // Calculate backup filename regex, ignoring the date/time/info parts that can be
             // variable, depending of languages, formats and automated backup settings.
-            $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' . $course->id . '-';
-            $regex = '#^'.preg_quote($filename, '#').'.*\.mbz$#';
+            $filename = backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' . $course->id . '-';
+            $regex = '#' . preg_quote($filename, '#') . '.*\.mbz$#';
 
             // Store all the matching files into filename => timemodified array.
             $files = array();
index d9ad80d..b0425a3 100644 (file)
@@ -155,6 +155,11 @@ abstract class backup_general_helper extends backup_helper {
         } else {
             $info->include_file_references_to_external_content = 0;
         }
+        // Introduced in Moodle 2.9.
+        $info->original_course_format = '';
+        if (!empty($infoarr['original_course_format'])) {
+            $info->original_course_format = $infoarr['original_course_format'];
+        }
         // include_files is a new setting in 2.6.
         if (isset($infoarr['include_files'])) {
             $info->include_files = $infoarr['include_files'];
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 a80a80b..803cd77 100644 (file)
@@ -105,10 +105,24 @@ abstract class restore_prechecks_helper {
             $warnings[] = get_string('noticenewerbackup','',$message);
         }
 
-        // Error if restoring over frontpage
-        // TODO: Review the whole restore process in order to transform this into one warning (see 1.9)
-        if ($controller->get_courseid() == SITEID) {
-            $errors[] = get_string('errorrestorefrontpage', 'backup');
+        // The original_course_format var was introduced in Moodle 2.9.
+        $originalcourseformat = null;
+        if (!empty($controller->get_info()->original_course_format)) {
+            $originalcourseformat = $controller->get_info()->original_course_format;
+        }
+
+        // We can't restore other course's backups on the front page.
+        if ($controller->get_courseid() == SITEID &&
+                $originalcourseformat != 'site' &&
+                $controller->get_type() == backup::TYPE_1COURSE) {
+            $errors[] = get_string('errorrestorefrontpagebackup', 'backup');
+        }
+
+        // We can't restore front pages over other courses.
+        if ($controller->get_courseid() != SITEID &&
+                $originalcourseformat == 'site' &&
+                $controller->get_type() == backup::TYPE_1COURSE) {
+            $errors[] = get_string('errorrestorefrontpagebackup', 'backup');
         }
 
         // If restoring to different site and restoring users and backup has mnet users warn/error
index 3b66dc5..f586d40 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
@@ -20,7 +19,7 @@
  * as well as the individual forms that relate to the different stages the user
  * interface can exist within.
  *
- * @package   moodlecore
+ * @package   core_backup
  * @copyright 2010 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -34,6 +33,7 @@ defined('MOODLE_INTERNAL') || die();
  * grown african swallows all of whom have been carring coconuts for several days.
  * EWWWWW!!!!!!!!!!!!!!!!!!!!!!!!
  *
+ * @package core_backup
  * @copyright 2010 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -41,6 +41,8 @@ abstract class backup_moodleform extends base_moodleform {
     /**
      * Creates the form
      *
+     * Overridden for type hinting on the first arg.
+     *
      * @param backup_ui_stage $uistage
      * @param moodle_url|string $action
      * @param mixed $customdata
@@ -49,38 +51,71 @@ abstract class backup_moodleform extends base_moodleform {
      * @param array $attributes
      * @param bool $editable
      */
-    public function __construct(backup_ui_stage $uistage, $action = null, $customdata = null, $method = 'post', $target = '', $attributes = null, $editable = true) {
+    public function __construct(backup_ui_stage $uistage, $action = null, $customdata = null, $method = 'post',
+                                $target = '', $attributes = null, $editable = true) {
         parent::__construct($uistage, $action, $customdata, $method, $target, $attributes, $editable);
     }
 }
+
 /**
  * Initial backup user interface stage moodleform.
  *
  * Nothing to override we only need it defined so that moodleform doesn't get confused
  * between stages.
+ *
+ * @package   core_backup
+ * @copyright 2010 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class backup_initial_form extends backup_moodleform {}
+
 /**
  * Schema backup user interface stage moodleform.
  *
  * Nothing to override we only need it defined so that moodleform doesn't get confused
  * between stages.
+ *
+ * @package   core_backup
+ * @copyright 2010 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class backup_schema_form extends backup_moodleform {}
+
 /**
  * Confirmation backup user interface stage moodleform.
  *
  * Nothing to override we only need it defined so that moodleform doesn't get confused
  * between stages.
+ *
+ * @package   core_backup
+ * @copyright 2010 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class backup_confirmation_form extends backup_moodleform {
 
+    /**
+     * Adds the last elements, rules, settings etc to the form after data has been set.
+     *
+     * We override this to add a rule and type to the filename setting.
+     *
+     * @throws coding_exception
+     */
     public function definition_after_data() {
         parent::definition_after_data();
         $this->_form->addRule('setting_root_filename', get_string('errorfilenamerequired', 'backup'), 'required');
         $this->_form->setType('setting_root_filename', PARAM_FILE);
     }
 
+    /**
+     * Validates the form.
+     *
+     * Relies on the parent::validation for the bulk of the work.
+     *
+     * @param array $data
+     * @param array $files
+     * @return array
+     * @throws coding_exception
+     */
     public function validation($data, $files) {
         $errors = parent::validation($data, $files);
 
@@ -94,5 +129,4 @@ class backup_confirmation_form extends backup_moodleform {
 
         return $errors;
     }
-
 }
index d906d7c..02b71db 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
@@ -18,7 +17,7 @@
 /**
  * This file contains the backup user interface class
  *
- * @package   moodlecore
+ * @package   core_backup
  * @copyright 2010 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 /**
  * This is the backup user interface class
  *
- * The backup user interface class manages the user interface and backup for
- * Moodle.
+ * The backup user interface class manages the user interface and backup for Moodle.
  *
+ * @package core_backup
  * @copyright 2010 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class backup_ui extends base_ui {
     /**
      * The stages of the backup user interface.
+     * The initial stage of the backup - settings are here.
      */
     const STAGE_INITIAL = 1;
+
+    /**
+     * The stages of the backup user interface.
+     * The schema stage of the backup - here you choose the bits you include.
+     */
     const STAGE_SCHEMA = 2;
+
+    /**
+     * The stages of the backup user interface.
+     * The confirmation stage of the backup.
+     */
     const STAGE_CONFIRMATION = 4;
+
+    /**
+     * The stages of the backup user interface.
+     * The final stage of the backup - where it is being processed.
+     */
     const STAGE_FINAL = 8;
+
+    /**
+     * The stages of the backup user interface.
+     * The backup is now complete.
+     */
     const STAGE_COMPLETE = 16;
 
     /**
@@ -52,10 +72,11 @@ class backup_ui extends base_ui {
      * Intialises what ever stage is requested. If none are requested we check
      * params for 'stage' and default to initial
      *
-     * @param int|null $stage The desired stage to intialise or null for the default
+     * @param int $stage The desired stage to intialise or null for the default
+     * @param array $params
      * @return backup_ui_stage_initial|backup_ui_stage_schema|backup_ui_stage_confirmation|backup_ui_stage_final
      */
-    protected function initialise_stage($stage = null, array $params=null) {
+    protected function initialise_stage($stage = null, array $params = null) {
         if ($stage == null) {
             $stage = optional_param('stage', self::STAGE_INITIAL, PARAM_INT);
         }
@@ -81,6 +102,7 @@ class backup_ui extends base_ui {
         }
         return $stage;
     }
+
     /**
      * Returns the backup id
      * @return string
@@ -88,6 +110,7 @@ class backup_ui extends base_ui {
     public function get_uniqueid() {
         return $this->get_backupid();
     }
+
     /**
      * Gets the backup id from the controller
      * @return string
@@ -95,8 +118,10 @@ class backup_ui extends base_ui {
     public function get_backupid() {
         return $this->controller->get_backupid();
     }
+
     /**
      * Executes the backup plan
+     * @throws backup_ui_exception when the steps are wrong.
      * @return bool
      */
     public function execute() {
@@ -112,16 +137,18 @@ class backup_ui extends base_ui {
         $this->stage = new backup_ui_stage_complete($this, $this->stage->get_params(), $this->controller->get_results());
         return true;
     }
+
     /**
      * Loads the backup controller if we are tracking one
+     * @param string $backupid
      * @return backup_controller|false
      */
-    final public static function load_controller($backupid=false) {
-        // Get the backup id optional param
+    final public static function load_controller($backupid = false) {
+        // Get the backup id optional param.
         if ($backupid) {
             try {
                 // Try to load the controller with it.
-                // If it fails at this point it is likely because this is the first load
+                // If it fails at this point it is likely because this is the first load.
                 $controller = backup_controller::load_controller($backupid);
                 return $controller;
             } catch (Exception $e) {
@@ -143,24 +170,24 @@ class backup_ui extends base_ui {
         $items = array();
         while ($stage > 0) {
             $classes = array('backup_stage');
-            if (floor($stage/2) == $currentstage) {
+            if (floor($stage / 2) == $currentstage) {
                 $classes[] = 'backup_stage_next';
             } else if ($stage == $currentstage) {
                 $classes[] = 'backup_stage_current';
             } else if ($stage < $currentstage) {
                 $classes[] = 'backup_stage_complete';
             }
-            $item = array('text' => strlen(decbin($stage)).'. '.get_string('currentstage'.$stage, 'backup'),'class' => join(' ', $classes));
-            if ($stage < $currentstage && $currentstage < self::STAGE_COMPLETE && (!self::$skipcurrentstage || ($stage*2) != $currentstage)) {
+            $item = array('text' => strlen(decbin($stage)).'. '.get_string('currentstage'.$stage, 'backup'), 'class' => join(' ', $classes));
+            if ($stage < $currentstage && $currentstage < self::STAGE_COMPLETE && (!self::$skipcurrentstage || ($stage * 2) != $currentstage)) {
                 $params = $this->stage->get_params();
                 if (empty($params)) {
                     $params = array();
                 }
-                $params = array_merge($params, array('backup'=>$this->get_backupid(), 'stage'=>$stage));
+                $params = array_merge($params, array('backup' => $this->get_backupid(), 'stage' => $stage));
                 $item['link'] = new moodle_url($PAGE->url, $params);
             }
             array_unshift($items, $item);
-            $stage = floor($stage/2);
+            $stage = floor($stage / 2);
         }
         return $items;
     }
@@ -181,13 +208,18 @@ class backup_ui extends base_ui {
     /**
      * If called with default arg the current stage gets skipped.
      * @static
+     * @param bool $setting Set to true (default) if you want to skip this stage, false otherwise.
      */
-    public static function skip_current_stage($setting=true) {
+    public static function skip_current_stage($setting = true) {
         self::$skipcurrentstage = $setting;
     }
 }
 
 /**
  * Backup user interface exception. Modelled off the backup_exception class
+ *
+ * @package   core_backup
+ * @copyright 2010 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class backup_ui_exception extends base_ui_exception {}
index 1712a73..37d6e75 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
@@ -19,7 +18,7 @@
  * This file contains the setting user interface classes that all backup/restore
  * settings use to represent the UI they have.
  *
- * @package   moodlecore
+ * @package   core_backup
  * @copyright 2010 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -28,6 +27,7 @@
  * Abstract class used to represent the user interface that a setting has.
  *
  * @todo extend as required for restore
+ * @package core_backup
  * @copyright 2010 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -66,6 +66,7 @@ class base_setting_ui {
      * @var base_setting|backup_setting
      */
     protected $setting;
+
     /**
      * Constructors are sooooo cool
      * @param base_setting $setting
@@ -78,7 +79,7 @@ class base_setting_ui {
      * Destroy all circular references. It helps PHP 5.2 a lot!
      */
     public function destroy() {
-        // No need to destroy anything recursively here, direct reset
+        // No need to destroy anything recursively here, direct reset.
         $this->setting = null;
     }
 
@@ -89,6 +90,7 @@ class base_setting_ui {
     public function get_name() {
         return self::NAME_PREFIX.$this->name;
     }
+
     /**
      * Gets the name of this item including its prefix
      * @return string
@@ -96,6 +98,7 @@ class base_setting_ui {
     public function get_label() {
         return $this->label;
     }
+
     /**
      * Gets the type of this element
      * @return int
@@ -103,6 +106,7 @@ class base_setting_ui {
     public function get_type() {
         return $this->type;
     }
+
     /**
      * Gets the HTML attributes for this item
      * @return array
@@ -110,6 +114,7 @@ class base_setting_ui {
     public function get_attributes() {
         return $this->attributes;
     }
+
     /**
      * Gets the value of this setting
      * @return mixed
@@ -117,6 +122,7 @@ class base_setting_ui {
     public function get_value() {
         return $this->setting->get_value();
     }
+
     /**
      * Gets the value to display in a static quickforms element
      * @return mixed
@@ -135,7 +141,9 @@ class base_setting_ui {
     }
 
     /**
-     * Sets the label
+     * Sets the label.
+     *
+     * @throws base_setting_ui_exception when the label is not valid.
      * @param string $label
      */
     public function set_label($label) {
@@ -145,11 +153,12 @@ class base_setting_ui {
         }
         $this->label = $label;
     }
+
     /**
      * Disables the UI for this element
      */
     public function disable() {
-       $this->attributes['disabled'] = 'disabled';
+        $this->attributes['disabled'] = 'disabled';
     }
 
     /**
@@ -177,6 +186,7 @@ class base_setting_ui {
 /**
  * Abstract class to represent the user interface backup settings have
  *
+ * @package core_backup
  * @copyright 2010 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -191,13 +201,13 @@ abstract class backup_setting_ui extends base_setting_ui {
      * JAC... Just Another Constructor
      *
      * @param backup_setting $setting
-     * @param string|null $label The label to display with the setting ui
-     * @param array|null $attributes Array of HTML attributes to apply to the element
-     * @param array|null $options Array of options to apply to the setting ui object
+     * @param string $label The label to display with the setting ui
+     * @param array $attributes Array of HTML attributes to apply to the element
+     * @param array $options Array of options to apply to the setting ui object
      */
     public function __construct(backup_setting $setting, $label = null, array $attributes = null, array $options = null) {
         parent::__construct($setting);
-        // Improve the inputs name by appending the level to the name
+        // Improve the inputs name by appending the level to the name.
         switch ($setting->get_level()) {
             case backup_setting::ROOT_LEVEL :
                 $this->name = 'root_'.$setting->get_name();
@@ -220,21 +230,20 @@ abstract class backup_setting_ui extends base_setting_ui {
             $this->options = $options;
         }
     }
+
     /**
      * Creates a new backup setting ui based on the setting it is given
      *
-     * Throws an exception if an invalid type is provided.
-     *
+     * @throws backup_setting_ui_exception if the setting type is not supported,
      * @param backup_setting $setting
      * @param int $type The backup_setting UI type. One of backup_setting::UI_*;
      * @param string $label The label to display with the setting ui
      * @param array $attributes Array of HTML attributes to apply to the element
      * @param array $options Array of options to apply to the setting ui object
-     *
      * @return backup_setting_ui_text|backup_setting_ui_checkbox|backup_setting_ui_select|backup_setting_ui_radio
      */
-    final public static function make(backup_setting $setting, $type, $label, array $attributes = null, array $options=null) {
-        // Base the decision we make on the type that was sent
+    final public static function make(backup_setting $setting, $type, $label, array $attributes = null, array $options = null) {
+        // Base the decision we make on the type that was sent.
         switch ($type) {
             case backup_setting::UI_HTML_CHECKBOX :
                 return new backup_setting_ui_checkbox($setting, $label, null, (array)$attributes, (array)$options);
@@ -248,11 +257,16 @@ abstract class backup_setting_ui extends base_setting_ui {
                 throw new backup_setting_ui_exception('setting_invalid_ui_type');
         }
     }
+
     /**
      * Get element properties that can be used to make a quickform element
+     *
+     * @param base_task $task
+     * @param renderer_base $output
      * @return array
      */
-    abstract public function get_element_properties(base_task $task=null, renderer_base $output=null);
+    abstract public function get_element_properties(base_task $task = null, renderer_base $output = null);
+
     /**
      * Applies config options to a given properties array and then returns it
      * @param array $properties
@@ -264,16 +278,17 @@ abstract class backup_setting_ui extends base_setting_ui {
         }
         return $properties;
     }
+
     /**
      * Gets the label for this item
-     * @param backup_task|null $task Optional, if provided and the setting is an include
+     * @param base_task $task Optional, if provided and the setting is an include
      *          $task is used to set the setting label
      * @return string
      */
-    public function get_label(base_task $task=null) {
-        // If a task has been provided and the label is not already set meaniningfully
+    public function get_label(base_task $task = null) {
+        // If a task has been provided and the label is not already set meaningfully
         // we will attempt to improve it.
-        if (!is_null($task) && $this->label == $this->setting->get_name() && strpos($this->setting->get_name(), '_include')!==false) {
+        if (!is_null($task) && $this->label == $this->setting->get_name() && strpos($this->setting->get_name(), '_include') !== false) {
             if ($this->setting->get_level() == backup_setting::SECTION_LEVEL) {
                 $this->label = get_string('includesection', 'backup', $task->get_name());
             } else if ($this->setting->get_level() == backup_setting::ACTIVITY_LEVEL) {
@@ -282,6 +297,7 @@ abstract class backup_setting_ui extends base_setting_ui {
         }
         return $this->label;
     }
+
     /**
      * Returns true if the setting is changeable.
      *
@@ -296,24 +312,23 @@ abstract class backup_setting_ui extends base_setting_ui {
      */
     public function is_changeable() {
         if ($this->setting->get_status() === backup_setting::NOT_LOCKED) {
-            // Its not locked so its chanegable
+            // Its not locked so its chanegable.
             return true;
         } else if ($this->setting->get_status() !== backup_setting::LOCKED_BY_HIERARCHY) {
-            // Its not changeable because its locked by permission or config
+            // Its not changeable because its locked by permission or config.
             return false;
         } else if ($this->setting->has_dependencies_on_settings()) {
             foreach ($this->setting->get_settings_depended_on() as $dependency) {
                 if ($dependency->is_locked() && $dependency->get_setting()->get_level() !== $this->setting->get_level()) {
-                    // Its not changeable because one or more dependancies arn't
-                    // changeable.
-                   return false;
+                    // Its not changeable because one or more dependancies arn't changeable.
+                    return false;
                 }
             }
             // Its changeable because all dependencies are changeable.
             return true;
         }
         // We should never get here but if we do return false to be safe.
-        // The setting would need to be locked by hierarchy and not have any deps
+        // The setting would need to be locked by hierarchy and not have any deps.
         return false;
     }
 
@@ -322,6 +337,7 @@ abstract class backup_setting_ui extends base_setting_ui {
 /**
  * A text input user interface element for backup settings
  *
+ * @package core_backup
  * @copyright 2010 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -330,20 +346,26 @@ class backup_setting_ui_text extends backup_setting_ui {
      * @var int
      */
     protected $type = backup_setting::UI_HTML_TEXTFIELD;
+
     /**
      * Returns an array of properties suitable for generating a quickforms element
-     * @param backup_task|null $task
+     * @param base_task $task
+     * @param renderer_base $output
      * @return array (element, name, label, attributes)
      */
-    public function get_element_properties(base_task $task=null, renderer_base $output=null) {
-        // name, label, text, attributes
+    public function get_element_properties(base_task $task = null, renderer_base $output = null) {
         $icon = $this->get_icon();
         $label = $this->get_label($task);
         if (!empty($icon)) {
             $label .= $output->render($icon);
         }
-        // name, label, attributes
-        return $this->apply_options(array('element'=>'text','name'=>self::NAME_PREFIX.$this->name, 'label'=>$label, 'attributes'=>$this->attributes));
+        // Name, label, attributes.
+        return $this->apply_options(array(
+            'element' => 'text',
+            'name' => self::NAME_PREFIX.$this->name,
+            'label' => $label,
+            'attributes' => $this->attributes)
+        );
     }
 
 }
@@ -351,50 +373,64 @@ class backup_setting_ui_text extends backup_setting_ui {
 /**
  * A checkbox user interface element for backup settings (default)
  *
+ * @package core_backup
  * @copyright 2010 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class backup_setting_ui_checkbox extends backup_setting_ui {
+
     /**
      * @var int
      */
     protected $type = backup_setting::UI_HTML_CHECKBOX;
+
     /**
      * @var bool
      */
     protected $changeable = true;
+
     /**
      * The text to show next to the checkbox
      * @var string
      */
     protected $text;
+
     /**
      * Overridden constructor so we can take text argument
+     *
      * @param backup_setting $setting
      * @param string $label
      * @param string $text
      * @param array $attributes
      * @param array $options
      */
-    public function __construct(backup_setting $setting, $label = null, $text=null, array $attributes = array(), array $options = array()) {
+    public function __construct(backup_setting $setting, $label = null, $text = null, array $attributes = array(), array $options = array()) {
         parent::__construct($setting, $label, $attributes, $options);
         $this->text = $text;
     }
+
     /**
      * Returns an array of properties suitable for generating a quickforms element
-     * @param backup_task|null $task
+     * @param base_task $task
+     * @param renderer_base $output
      * @return array (element, name, label, text, attributes);
      */
-    public function get_element_properties(base_task $task=null, renderer_base $output=null) {
-        // name, label, text, attributes
-
+    public function get_element_properties(base_task $task = null, renderer_base $output = null) {
+        // Name, label, text, attributes.
         $icon = $this->get_icon();
         $label = $this->get_label($task);
         if (!empty($icon)) {
             $label .= $output->render($icon);
         }
-        return $this->apply_options(array('element'=>'checkbox','name'=>self::NAME_PREFIX.$this->name, 'label'=>$label, 'text'=>$this->text, 'attributes'=>$this->attributes));
+        return $this->apply_options(array(
+            'element' => 'checkbox',
+            'name' => self::NAME_PREFIX.$this->name,
+            'label' => $label,
+            'text' => $this->text,
+            'attributes' => $this->attributes
+        ));
     }
+
     /**
      * Sets the text for the element
      * @param string $text
@@ -402,6 +438,7 @@ class backup_setting_ui_checkbox extends backup_setting_ui {
     public function set_text($text) {
         $this->text = $text;
     }
+
     /**
      * Gets the static value for the element
      * @global core_renderer $OUTPUT
@@ -409,7 +446,7 @@ class backup_setting_ui_checkbox extends backup_setting_ui {
      */
     public function get_static_value() {
         global $OUTPUT;
-        // Checkboxes are always yes or no
+        // Checkboxes are always yes or no.
         if ($this->get_value()) {
             return $OUTPUT->pix_icon('i/valid', get_string('yes'));
         } else {
@@ -422,7 +459,7 @@ class backup_setting_ui_checkbox extends backup_setting_ui {
      * @return bool
      */
     public function is_changeable() {
-        if ($this->changeable===false) {
+        if ($this->changeable === false) {
             return false;
         } else {
             return parent::is_changeable();
@@ -442,6 +479,7 @@ class backup_setting_ui_checkbox extends backup_setting_ui {
 /**
  * Radio button user interface element for backup settings
  *
+ * @package core_backup
  * @copyright 2010 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -450,17 +488,21 @@ class backup_setting_ui_radio extends backup_setting_ui {
      * @var int
      */
     protected $type = backup_setting::UI_HTML_RADIOBUTTON;
+
     /**
      * The string shown next to the input
      * @var string
      */
     protected $text;
+
     /**
      * The value for the radio input
      * @var string
      */
     protected $value;
+
     /**
+     * Constructor
      *
      * @param backup_setting $setting
      * @param string $label
@@ -469,25 +511,33 @@ class backup_setting_ui_radio extends backup_setting_ui {
      * @param array $attributes
      * @param array $options
      */
-    public function __construct(backup_setting $setting, $label = null, $text=null, $value=null, array $attributes = array(), array $options = array()) {
+    public function __construct(backup_setting $setting, $label = null, $text = null, $value = null, array $attributes = array(), array $options = array()) {
         parent::__construct($setting, $label, $attributes, $options);
         $this->text = $text;
         $this->value = (string)$value;
     }
+
     /**
      * Returns an array of properties suitable for generating a quickforms element
-     * @param backup_task|null $task
+     * @param base_task $task
+     * @param renderer_base $output
      * @return array (element, name, label, text, value, attributes)
      */
-    public function get_element_properties(base_task $task=null, renderer_base $output=null) {
-        // name, label, text, attributes
+    public function get_element_properties(base_task $task = null, renderer_base $output = null) {
         $icon = $this->get_icon();
         $label = $this->get_label($task);
         if (!empty($icon)) {
             $label .= $output->render($icon);
         }
-        // name, label, text, value, attributes
-        return $this->apply_options(array('element'=>'radio','name'=>self::NAME_PREFIX.$this->name, 'label'=>$label, 'text'=>$this->text, 'value'=>$this->value, 'attributes'=>$this->attributes));
+        // Name, label, text, value, attributes.
+        return $this->apply_options(array(
+            'element' => 'radio',
+            'name' => self::NAME_PREFIX.$this->name,
+            'label' => $label,
+            'text' => $this->text,
+            'value' => $this->value,
+            'attributes' => $this->attributes
+        ));
     }
     /**
      * Sets the text next to this input
@@ -514,6 +564,7 @@ class backup_setting_ui_radio extends backup_setting_ui {
 /**
  * A select box, drop down user interface for backup settings
  *
+ * @package core_backup
  * @copyright 2010 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -522,12 +573,15 @@ class backup_setting_ui_select extends backup_setting_ui {
      * @var int
      */
     protected $type = backup_setting::UI_HTML_DROPDOWN;
+
     /**
      * An array of options to display in the select
      * @var array
      */
     protected $values;
+
     /**
+     * Constructor
      *
      * @param backup_setting $setting
      * @param string $label
@@ -535,32 +589,41 @@ class backup_setting_ui_select extends backup_setting_ui {
      * @param array $attributes
      * @param array $options
      */
-    public function __construct(backup_setting $setting, $label = null, $values=null, array $attributes = array(), array $options = array()) {
+    public function __construct(backup_setting $setting, $label = null, $values = null, array $attributes = array(), array $options = array()) {
         parent::__construct($setting, $label, $attributes, $options);
         $this->values = $values;
     }
+
     /**
      * Returns an array of properties suitable for generating a quickforms element
-     * @param backup_task|null $task
+     * @param base_task $task
+     * @param renderer_base $output
      * @return array (element, name, label, options, attributes)
      */
-    public function get_element_properties(base_task $task = null, renderer_base $output=null) {
-        // name, label, text, attributes
+    public function get_element_properties(base_task $task = null, renderer_base $output = null) {
         $icon = $this->get_icon();
         $label = $this->get_label($task);
         if (!empty($icon)) {
             $label .= $output->render($icon);
         }
-        // name, label, options, attributes
-        return $this->apply_options(array('element'=>'select','name'=>self::NAME_PREFIX.$this->name, 'label'=>$label, 'options'=>$this->values, 'attributes'=>$this->attributes));
+        // Name, label, options, attributes.
+        return $this->apply_options(array(
+            'element' => 'select',
+            'name' => self::NAME_PREFIX.$this->name,
+            'label' => $label,
+            'options' => $this->values,
+            'attributes' => $this->attributes
+        ));
     }
+
     /**
      * Sets the options for the select box
-     * @param array $values Associative array of value=>text options
+     * @param array $values Associative array of value => text options
      */
     public function set_values(array $values) {
         $this->values = $values;
     }
+
     /**
      * Gets the static value for this select element
      * @return string
@@ -568,6 +631,7 @@ class backup_setting_ui_select extends backup_setting_ui {
     public function get_static_value() {
         return $this->values[$this->get_value()];
     }
+
     /**
      * Returns true if the setting is changeable, false otherwise
      *
@@ -582,8 +646,22 @@ class backup_setting_ui_select extends backup_setting_ui {
     }
 }
 
+/**
+ * A date selector user interface widget for backup settings.
+ *
+ * @package core_backup
+ * @copyright 2010 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
 class backup_setting_ui_dateselector extends backup_setting_ui_text {
-    public function get_element_properties(base_task $task = null, renderer_base $output=null) {
+
+    /**
+     * Returns an array of properties suitable for generating a quickforms element
+     * @param base_task $task
+     * @param renderer_base $output
+     * @return array (element, name, label, options, attributes)
+     */
+    public function get_element_properties(base_task $task = null, renderer_base $output = null) {
         if (!array_key_exists('optional', $this->attributes)) {
             $this->attributes['optional'] = false;
         }
@@ -591,6 +669,11 @@ class backup_setting_ui_dateselector extends backup_setting_ui_text {
         $properties['element'] = 'date_selector';
         return $properties;
     }
+
+    /**
+     * Gets the static value for this select element
+     * @return string
+     */
     public function get_static_value() {
         $value = $this->get_value();
         if (!empty($value)) {
@@ -600,5 +683,20 @@ class backup_setting_ui_dateselector extends backup_setting_ui_text {
     }
 }
 
+/**
+ * Base setting UI exception class.
+ *
+ * @package core_backup
+ * @copyright 2010 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
 class base_setting_ui_exception extends base_setting_exception {}
+
+/**
+ * Backup setting UI exception class.
+ *
+ * @package core_backup
+ * @copyright 2010 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
 class backup_setting_ui_exception extends base_setting_ui_exception {};
index cde79ff..ec1b3b1 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
@@ -20,9 +19,9 @@
  *
  * This file contains the classes required to manage the stages that make up the
  * backup user interface.
- * These will be primarily operated a {@see backup_ui} instance.
+ * These will be primarily operated a {@link backup_ui} instance.
  *
- * @package   moodlecore
+ * @package   core_backup
  * @copyright 2010 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
  *  - process : To process the stage
  *  - initialise_stage_form : To get a backup_moodleform instance for the stage
  *
+ * @package   core_backup
  * @copyright 2010 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 abstract class backup_ui_stage extends base_ui_stage {
 
+    /**
+     * Constructor.
+     *
+     * @param backup_ui $ui
+     * @param array $params
+     */
     public function __construct(backup_ui $ui, array $params = null) {
-       parent::__construct($ui, $params);
+        parent::__construct($ui, $params);
     }
+
     /**
      * The backup id from the backup controller
      * @return string
@@ -57,6 +64,7 @@ abstract class backup_ui_stage extends base_ui_stage {
  *
  * In this stage the user is required to set the root level settings.
  *
+ * @package   core_backup
  * @copyright 2010 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -71,15 +79,16 @@ class backup_ui_stage_initial extends backup_ui_stage {
     /**
      * Initial backup stage constructor
      * @param backup_ui $ui
+     * @param array $params
      */
-    public function __construct(backup_ui $ui, array $params=null) {
+    public function __construct(backup_ui $ui, array $params = null) {
         $this->stage = backup_ui::STAGE_INITIAL;
         parent::__construct($ui, $params);
     }
 
     /**
      * Processes the initial backup stage
-     * @param backup_moodleform $form
+     * @param base_moodleform $m
      * @return int The number of changes
      */
     public function process(base_moodleform $m = null) {
@@ -98,9 +107,9 @@ class backup_ui_stage_initial extends backup_ui_stage {
             $tasks = $this->ui->get_tasks();
             $changes = 0;
             foreach ($tasks as &$task) {
-                // We are only interesting in the backup root task for this stage
+                // We are only interesting in the backup root task for this stage.
                 if ($task instanceof backup_root_task) {
-                    // Get all settings into a var so we can iterate by reference
+                    // Get all settings into a var so we can iterate by reference.
                     $settings = $task->get_settings();
                     foreach ($settings as &$setting) {
                         $name = $setting->get_ui_name();
@@ -114,7 +123,7 @@ class backup_ui_stage_initial extends backup_ui_stage {
                     }
                 }
             }
-            // Return the number of changes the user made
+            // Return the number of changes the user made.
             return $changes;
         } else {
             return false;
@@ -166,24 +175,24 @@ class backup_ui_stage_initial extends backup_ui_stage {
         global $PAGE;
         if ($this->stageform === null) {
             $form = new backup_initial_form($this, $PAGE->url);
-            // Store as a variable so we can iterate by reference
+            // Store as a variable so we can iterate by reference.
             $tasks = $this->ui->get_tasks();
-            // Iterate all tasks by reference
+            // Iterate all tasks by reference.
             $add_settings = array();
             $dependencies = array();
             foreach ($tasks as &$task) {
-                // For the initial stage we are only interested in the root settings
+                // For the initial stage we are only interested in the root settings.
                 if ($task instanceof backup_root_task) {
                     $form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
                     $settings = $task->get_settings();
-                    // First add all settings except the filename setting
+                    // First add all settings except the filename setting.
                     foreach ($settings as &$setting) {
                         if ($setting->get_name() == 'filename') {
                             continue;
                         }
                         $add_settings[] = array($setting, $task);
                     }
-                    // Then add all dependencies
+                    // Then add all dependencies.
                     foreach ($settings as &$setting) {
                         if ($setting->get_name() == 'filename') {
                             continue;
@@ -200,7 +209,7 @@ class backup_ui_stage_initial extends backup_ui_stage {
             }
             $this->stageform = $form;
         }
-        // Return the form
+        // Return the form.
         return $this->stageform;
     }
 }
@@ -211,10 +220,12 @@ class backup_ui_stage_initial extends backup_ui_stage {
  * During the schema stage the user is required to set the settings that relate
  * to the area that they are backing up as well as its children.
  *
+ * @package   core_backup
  * @copyright 2010 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class backup_ui_stage_schema extends backup_ui_stage {
+
     /**
      * @var int Maximum number of settings to add to form at once
      */
@@ -222,38 +233,40 @@ class backup_ui_stage_schema ex