Merge branch 'master_MDL-43011' of git://github.com/danmarsden/moodle
authorMarina Glancy <marina@moodle.com>
Tue, 8 Apr 2014 00:53:49 +0000 (08:53 +0800)
committerMarina Glancy <marina@moodle.com>
Tue, 8 Apr 2014 00:53:49 +0000 (08:53 +0800)
425 files changed:
admin/index.php
admin/settings/security.php
admin/tool/log/lang/en/tool_log.php
admin/tool/task/classes/edit_scheduled_task_form.php
admin/tool/task/cli/schedule_task.php
admin/tool/task/lang/en/tool_task.php
admin/tool/task/renderer.php
admin/tool/task/scheduledtasks.php
auth/db/tests/db_test.php
auth/manual/auth.php
auth/manual/config.html
auth/manual/lang/en/auth_manual.php
auth/manual/tests/manual_test.php [new file with mode: 0644]
blog/tests/bloglib_test.php
cohort/tests/cohortlib_test.php
course/classes/editcategory_form.php
course/externallib.php
course/lib.php
course/recent.php
course/tests/behat/category_management.feature
course/tests/courselib_test.php
enrol/database/tests/sync_test.php
enrol/meta/tests/plugin_test.php
enrol/otherusers.php
filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-debug.js
filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js
filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker.js
filter/glossary/yui/src/autolinker/js/autolinker.js
filter/glossary/yui/src/autolinker/meta/autolinker.json
filter/mathjaxloader/filter.php [new file with mode: 0644]
filter/mathjaxloader/lang/en/filter_mathjaxloader.php [new file with mode: 0644]
filter/mathjaxloader/readme_moodle.txt [new file with mode: 0644]
filter/mathjaxloader/settings.php [new file with mode: 0644]
filter/mathjaxloader/styles.css [new file with mode: 0644]
filter/mathjaxloader/version.php [new file with mode: 0644]
filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader-debug.js [new file with mode: 0644]
filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader-min.js [new file with mode: 0644]
filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader.js [new file with mode: 0644]
filter/mathjaxloader/yui/src/loader/build.json [new file with mode: 0644]
filter/mathjaxloader/yui/src/loader/js/loader.js [new file with mode: 0644]
filter/mathjaxloader/yui/src/loader/meta/loader.json [new file with mode: 0644]
grade/edit/tree/grade.php
grade/report/grader/ajax_callbacks.php
grade/report/grader/index.php
grade/report/grader/lib.php
group/tests/lib_test.php
lang/en/admin.php
lang/en/blog.php
lang/en/grades.php
lang/en/moodle.php
lang/en/notes.php
lang/en/role.php
lang/en/webservice.php
lib/adminlib.php
lib/ajax/blocks.php
lib/authlib.php
lib/behat/classes/behat_selectors.php
lib/blocklib.php
lib/classes/event/assessable_submitted.php
lib/classes/event/assessable_uploaded.php
lib/classes/event/base.php
lib/classes/event/blog_association_created.php
lib/classes/event/blog_entry_created.php
lib/classes/event/blog_entry_updated.php
lib/classes/event/cohort_deleted.php
lib/classes/event/comment_created.php
lib/classes/event/comment_deleted.php
lib/classes/event/content_viewed.php
lib/classes/event/course_category_created.php
lib/classes/event/course_category_updated.php
lib/classes/event/course_module_completion_updated.php
lib/classes/event/course_module_created.php
lib/classes/event/course_module_deleted.php
lib/classes/event/course_module_instance_list_viewed.php
lib/classes/event/course_module_updated.php
lib/classes/event/course_module_viewed.php
lib/classes/event/course_reset_ended.php
lib/classes/event/course_reset_started.php
lib/classes/event/course_updated.php
lib/classes/event/email_failed.php
lib/classes/event/group_member_added.php
lib/classes/event/group_member_removed.php
lib/classes/event/group_updated.php
lib/classes/event/grouping_created.php
lib/classes/event/grouping_deleted.php
lib/classes/event/grouping_updated.php
lib/classes/event/message_contact_added.php
lib/classes/event/message_contact_blocked.php
lib/classes/event/message_contact_removed.php
lib/classes/event/message_contact_unblocked.php
lib/classes/event/message_read.php
lib/classes/event/message_sent.php
lib/classes/event/mnet_access_control_created.php
lib/classes/event/mnet_access_control_updated.php
lib/classes/event/role_capabilities_updated.php
lib/classes/event/user_deleted.php
lib/classes/event/user_enrolment_updated.php
lib/classes/event/user_graded.php [new file with mode: 0644]
lib/classes/event/user_loggedin.php
lib/classes/event/user_login_failed.php
lib/classes/event/webservice_function_called.php
lib/classes/event/webservice_login_failed.php
lib/classes/event/webservice_service_updated.php
lib/classes/event/webservice_service_user_added.php
lib/classes/event/webservice_service_user_removed.php
lib/classes/event/webservice_token_created.php
lib/classes/grades_external.php [new file with mode: 0644]
lib/classes/plugin_manager.php
lib/classes/task/manager.php
lib/classes/task/scheduled_task.php
lib/classes/task/send_failed_login_notifications_task.php
lib/classes/task/task_base.php
lib/classes/user.php
lib/datalib.php
lib/db/access.php
lib/db/install.xml
lib/db/services.php
lib/db/upgrade.php
lib/deprecatedlib.php
lib/dml/pgsql_native_moodle_database.php
lib/editor/atto/adminlib.php
lib/editor/atto/db/install.php [new file with mode: 0644]
lib/editor/atto/db/upgrade.php [new file with mode: 0644]
lib/editor/atto/lang/en/editor_atto.php
lib/editor/atto/lib.php
lib/editor/atto/plugins/accessibilitychecker/lang/en/atto_accessibilitychecker.php
lib/editor/atto/plugins/backcolor/lang/en/atto_backcolor.php
lib/editor/atto/plugins/backcolor/styles.css [new file with mode: 0644]
lib/editor/atto/plugins/backcolor/yui/build/moodle-atto_backcolor-button/moodle-atto_backcolor-button-debug.js
lib/editor/atto/plugins/backcolor/yui/build/moodle-atto_backcolor-button/moodle-atto_backcolor-button-min.js
lib/editor/atto/plugins/backcolor/yui/build/moodle-atto_backcolor-button/moodle-atto_backcolor-button.js
lib/editor/atto/plugins/backcolor/yui/src/button/js/button.js
lib/editor/atto/plugins/collapse/lang/en/atto_collapse.php
lib/editor/atto/plugins/collapse/yui/build/moodle-atto_collapse-button/moodle-atto_collapse-button-debug.js
lib/editor/atto/plugins/collapse/yui/build/moodle-atto_collapse-button/moodle-atto_collapse-button-min.js
lib/editor/atto/plugins/collapse/yui/build/moodle-atto_collapse-button/moodle-atto_collapse-button.js
lib/editor/atto/plugins/collapse/yui/src/button/js/button.js
lib/editor/atto/plugins/equation/lang/en/atto_equation.php
lib/editor/atto/plugins/equation/lib.php
lib/editor/atto/plugins/equation/settings.php
lib/editor/atto/plugins/equation/styles.css
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/equation/yui/src/button/meta/button.json
lib/editor/atto/plugins/fontcolor/lang/en/atto_fontcolor.php
lib/editor/atto/plugins/fontcolor/styles.css [new file with mode: 0644]
lib/editor/atto/plugins/fontcolor/yui/build/moodle-atto_fontcolor-button/moodle-atto_fontcolor-button-debug.js
lib/editor/atto/plugins/fontcolor/yui/build/moodle-atto_fontcolor-button/moodle-atto_fontcolor-button-min.js
lib/editor/atto/plugins/fontcolor/yui/build/moodle-atto_fontcolor-button/moodle-atto_fontcolor-button.js
lib/editor/atto/plugins/fontcolor/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/styles.css
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/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button-debug.js
lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button-min.js
lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button.js
lib/editor/atto/plugins/media/yui/src/button/js/button.js
lib/editor/atto/plugins/table/lang/en/atto_table.php
lib/editor/atto/plugins/table/lib.php
lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-debug.js
lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-min.js
lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button.js
lib/editor/atto/plugins/table/yui/src/button/js/button.js
lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button-debug.js
lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button-min.js
lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button.js
lib/editor/atto/plugins/undo/yui/src/button/js/button.js
lib/editor/atto/settings.php
lib/editor/atto/styles.css
lib/editor/atto/version.php
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js
lib/editor/atto/yui/build/moodle-editor_atto-menu/moodle-editor_atto-menu-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-menu/moodle-editor_atto-menu-min.js
lib/editor/atto/yui/build/moodle-editor_atto-menu/moodle-editor_atto-menu.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/editor-plugin-dialogue.js
lib/editor/atto/yui/src/editor/js/editor-plugin.js
lib/editor/atto/yui/src/editor/js/editor.js
lib/editor/atto/yui/src/editor/js/menu.js
lib/editor/atto/yui/src/editor/js/selection.js
lib/editor/atto/yui/src/editor/js/styling.js
lib/editor/tinymce/lang/en/editor_tinymce.php
lib/enrollib.php
lib/gradelib.php
lib/grouplib.php
lib/messagelib.php
lib/modinfolib.php
lib/moodlelib.php
lib/outputlib.php
lib/outputrenderers.php
lib/phpunit/tests/advanced_test.php
lib/testing/generator/data_generator.php
lib/tests/accesslib_test.php
lib/tests/adhoc_task_test.php
lib/tests/behat/behat_hooks.php
lib/tests/blocklib_test.php
lib/tests/event_user_graded_test.php [new file with mode: 0644]
lib/tests/events_test.php
lib/tests/fixtures/event_fixtures.php
lib/tests/fixtures/task_fixtures.php [new file with mode: 0644]
lib/tests/grades_externallib_test.php [new file with mode: 0644]
lib/tests/messagelib_test.php
lib/tests/modinfolib_test.php
lib/tests/scheduled_task_test.php
lib/tests/user_test.php
lib/upgrade.txt
lib/yui/build/moodle-core-blocks/moodle-core-blocks-debug.js
lib/yui/build/moodle-core-blocks/moodle-core-blocks-min.js
lib/yui/build/moodle-core-blocks/moodle-core-blocks.js
lib/yui/build/moodle-core-checknet/assets/checknet.txt [new file with mode: 0644]
lib/yui/build/moodle-core-checknet/moodle-core-checknet-debug.js [new file with mode: 0644]
lib/yui/build/moodle-core-checknet/moodle-core-checknet-min.js [new file with mode: 0644]
lib/yui/build/moodle-core-checknet/moodle-core-checknet.js [new file with mode: 0644]
lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-debug.js
lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-min.js
lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue.js
lib/yui/build/moodle-core-event/moodle-core-event-debug.js [new file with mode: 0644]
lib/yui/build/moodle-core-event/moodle-core-event-min.js [new file with mode: 0644]
lib/yui/build/moodle-core-event/moodle-core-event.js [new file with mode: 0644]
lib/yui/src/blocks/js/blocks.js
lib/yui/src/checknet/assets/checknet.txt [new file with mode: 0644]
lib/yui/src/checknet/build.json [new file with mode: 0644]
lib/yui/src/checknet/js/checknet.js [new file with mode: 0644]
lib/yui/src/checknet/meta/checknet.json [new file with mode: 0644]
lib/yui/src/chooserdialogue/js/chooserdialogue.js
lib/yui/src/event/build.json [new file with mode: 0644]
lib/yui/src/event/js/event.js [new file with mode: 0644]
lib/yui/src/event/meta/event.json [new file with mode: 0644]
message/lib.php
message/tests/events_test.php
mnet/lib.php
mnet/tests/events_test.php
mod/assign/classes/event/all_submissions_downloaded.php
mod/assign/classes/event/assessable_submitted.php
mod/assign/classes/event/extension_granted.php
mod/assign/classes/event/identities_revealed.php
mod/assign/classes/event/marker_updated.php
mod/assign/classes/event/statement_accepted.php
mod/assign/classes/event/submission_created.php
mod/assign/classes/event/submission_duplicated.php
mod/assign/classes/event/submission_graded.php
mod/assign/classes/event/submission_locked.php
mod/assign/classes/event/submission_status_updated.php
mod/assign/classes/event/submission_unlocked.php
mod/assign/classes/event/submission_updated.php
mod/assign/classes/event/workflow_state_updated.php
mod/assign/feedback/editpdf/classes/pdf.php
mod/assign/feedback/editpdf/locallib.php
mod/assign/feedback/offline/locallib.php
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/module.js
mod/assign/styles.css
mod/assign/submission/file/classes/event/assessable_uploaded.php
mod/assign/tests/behat/prevent_submission_changes.feature
mod/chat/chat_ajax.php
mod/chat/classes/event/message_sent.php
mod/chat/classes/event/sessions_viewed.php
mod/chat/lib.php
mod/choice/classes/event/answer_submitted.php
mod/choice/classes/event/answer_updated.php
mod/choice/classes/event/report_viewed.php
mod/data/classes/event/field_created.php
mod/data/classes/event/field_deleted.php
mod/data/classes/event/field_updated.php
mod/data/classes/event/record_created.php
mod/data/classes/event/record_deleted.php
mod/data/classes/event/record_updated.php
mod/data/classes/event/template_updated.php
mod/data/classes/event/template_viewed.php
mod/data/tests/events_test.php
mod/feedback/lib.php
mod/forum/classes/event/assessable_uploaded.php
mod/forum/externallib.php
mod/glossary/classes/event/category_created.php
mod/glossary/classes/event/category_deleted.php
mod/glossary/classes/event/category_updated.php
mod/glossary/classes/event/entry_approved.php
mod/glossary/classes/event/entry_created.php
mod/glossary/classes/event/entry_deleted.php
mod/glossary/classes/event/entry_disapproved.php
mod/glossary/classes/event/entry_updated.php
mod/glossary/classes/event/entry_viewed.php
mod/glossary/lib.php
mod/glossary/sql.php
mod/lesson/classes/event/essay_assessed.php
mod/lesson/format.php
mod/lti/lang/en/lti.php
mod/quiz/attemptlib.php
mod/quiz/classes/event/attempt_abandoned.php
mod/quiz/classes/event/attempt_becameoverdue.php
mod/quiz/classes/event/attempt_started.php
mod/quiz/classes/event/attempt_submitted.php
mod/quiz/editlib.php
mod/quiz/lib.php
mod/quiz/mod_form.php
mod/scorm/backup/moodle2/restore_scorm_stepslib.php
mod/scorm/classes/event/interactions_viewed.php
mod/scorm/classes/event/report_viewed.php
mod/scorm/classes/event/sco_launched.php
mod/scorm/classes/event/user_report_viewed.php
mod/scorm/datamodels/scorm_12.js.php
mod/scorm/datamodels/scormlib.php
mod/scorm/db/upgrade.php
mod/scorm/lang/en/scorm.php
mod/scorm/player.php
mod/scorm/settings.php
mod/scorm/version.php
mod/survey/classes/event/course_module_viewed.php
mod/survey/classes/event/report_downloaded.php
mod/survey/classes/event/response_submitted.php
mod/wiki/backup/moodle2/backup_wiki_stepslib.php
mod/wiki/backup/moodle2/restore_wiki_stepslib.php
mod/wiki/classes/event/comments_viewed.php
mod/wiki/classes/event/page_diff_viewed.php
mod/wiki/classes/event/page_map_viewed.php
mod/wiki/classes/event/page_version_deleted.php
mod/wiki/classes/event/page_version_restored.php
mod/wiki/classes/event/page_version_viewed.php
mod/wiki/classes/event/page_viewed.php
mod/wiki/lib.php
mod/wiki/pagelib.php
mod/wiki/renderer.php
mod/wiki/search.php
mod/wiki/tests/behat/wiki_search.feature [new file with mode: 0644]
mod/workshop/classes/event/assessments_reset.php
mod/workshop/lib.php
mod/workshop/submission.php
mod/workshop/toolbox.php
my/index.php
my/indexsys.php
question/addquestion.php
question/editlib.php
question/qbank.js [deleted file]
question/renderer.php
question/tests/behat/behat_question_base.php
question/type/edit_question_form.php
question/type/questiontypebase.php
question/type/shortanswer/questiontype.php
question/yui/build/moodle-question-chooser/moodle-question-chooser-debug.js [new file with mode: 0644]
question/yui/build/moodle-question-chooser/moodle-question-chooser-min.js [new file with mode: 0644]
question/yui/build/moodle-question-chooser/moodle-question-chooser.js [new file with mode: 0644]
question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager-debug.js [new file with mode: 0644]
question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager-min.js [new file with mode: 0644]
question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager.js [new file with mode: 0644]
question/yui/src/chooser/build.json [new file with mode: 0644]
question/yui/src/chooser/js/chooser.js [new file with mode: 0644]
question/yui/src/chooser/meta/chooser.json [new file with mode: 0644]
question/yui/src/qbankmanager/build.json [new file with mode: 0644]
question/yui/src/qbankmanager/js/qbankmanager.js [new file with mode: 0644]
question/yui/src/qbankmanager/meta/qbankmanager.json [new file with mode: 0644]
report/log/classes/event/report_viewed.php
report/log/classes/event/user_report_viewed.php
report/log/classes/renderable.php
report/log/classes/table_log.php
report/log/index.php
report/log/tests/events_test.php
report/loglive/classes/event/report_viewed.php
report/loglive/classes/renderable.php [new file with mode: 0644]
report/loglive/classes/renderer.php [new file with mode: 0644]
report/loglive/classes/renderer_ajax.php [new file with mode: 0644]
report/loglive/classes/table_log.php [new file with mode: 0644]
report/loglive/classes/table_log_ajax.php [new file with mode: 0644]
report/loglive/index.php
report/loglive/lang/en/report_loglive.php
report/loglive/lib.php
report/loglive/loglive_ajax.php [new file with mode: 0644]
report/loglive/settings.php
report/loglive/styles.css
report/loglive/tests/behat/loglive_report.feature [new file with mode: 0644]
report/loglive/tests/events_test.php
report/loglive/version.php
report/loglive/yui/build/moodle-report_loglive-fetchlogs/moodle-report_loglive-fetchlogs-debug.js [new file with mode: 0644]
report/loglive/yui/build/moodle-report_loglive-fetchlogs/moodle-report_loglive-fetchlogs-min.js [new file with mode: 0644]
report/loglive/yui/build/moodle-report_loglive-fetchlogs/moodle-report_loglive-fetchlogs.js [new file with mode: 0644]
report/loglive/yui/src/fetchlogs/build.json [new file with mode: 0644]
report/loglive/yui/src/fetchlogs/js/fetchlogs.js [new file with mode: 0644]
report/loglive/yui/src/fetchlogs/meta/fetchlogs.json [new file with mode: 0644]
report/outline/classes/event/activity_viewed.php
report/outline/classes/event/outline_viewed.php
report/participation/classes/event/report_viewed.php
report/performance/locallib.php
report/stats/classes/event/report_viewed.php
report/stats/classes/event/user_report_viewed.php
tag/edit.php
theme/base/config.php
theme/base/style/core.css
theme/base/style/pagelayout.css
theme/base/style/question.css
theme/bootstrapbase/less/bootstrap/reset.less
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/question.less
theme/bootstrapbase/readme_moodle.txt
theme/bootstrapbase/renderers/core_renderer.php
theme/bootstrapbase/style/editor.css
theme/bootstrapbase/style/moodle.css
theme/canvas/style/pagelayout.css
theme/clean/pix/screenshot.jpg
theme/font.php
theme/more/db/install.php [new file with mode: 0644]
theme/more/db/upgrade.php [new file with mode: 0644]
theme/more/lang/en/theme_more.php
theme/more/pix/background.jpg [new file with mode: 0644]
theme/more/pix/screenshot.jpg
theme/more/settings.php
theme/more/version.php
user/index.php
user/lib.php
user/profile.php
user/profilesys.php
user/tests/userlib_test.php
version.php

index d31eeac..4947c5e 100644 (file)
@@ -237,6 +237,13 @@ if (!core_tables_exist()) {
 // Check version of Moodle code on disk compared with database
 // and upgrade if possible.
 
+if (!$cache) {
+    // Do not try to do anything fancy in non-cached mode,
+    // this prevents themes from fetching data from non-existent tables.
+    $PAGE->set_pagelayout('maintenance');
+    $PAGE->set_popup_notification_allowed(false);
+}
+
 $stradministration = get_string('administration');
 $PAGE->set_context(context_system::instance());
 
@@ -267,9 +274,6 @@ if (!$cache and $version > $CFG->version) {  // upgrade
     // We then purge the regular caches.
     purge_all_caches();
 
-    $PAGE->set_pagelayout('maintenance');
-    $PAGE->set_popup_notification_allowed(false);
-
     /** @var core_admin_renderer $output */
     $output = $PAGE->get_renderer('core', 'admin');
 
@@ -347,8 +351,6 @@ if (!$cache and $version > $CFG->version) {  // upgrade
         // Always verify plugin dependencies!
         $failed = array();
         if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) {
-            $PAGE->set_pagelayout('maintenance');
-            $PAGE->set_popup_notification_allowed(false);
             $reloadurl = new moodle_url('/admin/index.php', array('confirmupgrade' => 1, 'confirmrelease' => 1, 'cache' => 0));
             echo $output->unsatisfied_dependencies_page($version, $failed, $reloadurl);
             die();
@@ -382,8 +384,6 @@ if (!$cache and moodle_needs_upgrading()) {
         if (!$confirmplugins) {
             $strplugincheck = get_string('plugincheck');
 
-            $PAGE->set_pagelayout('maintenance');
-            $PAGE->set_popup_notification_allowed(false);
             $PAGE->navbar->add($strplugincheck);
             $PAGE->set_title($strplugincheck);
             $PAGE->set_heading($strplugincheck);
@@ -421,8 +421,6 @@ if (!$cache and moodle_needs_upgrading()) {
         // Make sure plugin dependencies are always checked.
         $failed = array();
         if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) {
-            $PAGE->set_pagelayout('maintenance');
-            $PAGE->set_popup_notification_allowed(false);
             $reloadurl = new moodle_url('/admin/index.php', array('cache' => 0));
             echo $output->unsatisfied_dependencies_page($version, $failed, $reloadurl);
             die();
index 62712e3..a75b6e1 100644 (file)
@@ -108,10 +108,8 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
 
     // "notifications" settingpage
     $temp = new admin_settingpage('notifications', new lang_string('notifications', 'admin'));
-    $temp->add(new admin_setting_configselect('displayloginfailures', new lang_string('displayloginfailures', 'admin'), new lang_string('configdisplayloginfailures', 'admin'), '', array('' => new lang_string('nobody'),
-                                                                                                                                                                                'admin' => new lang_string('administrators'),
-                                                                                                                                                                                'teacher' => new lang_string('administratorsandteachers'),
-                                                                                                                                                                                'everybody' => new lang_string('everybody'))));
+    $temp->add(new admin_setting_configcheckbox('displayloginfailures', new lang_string('displayloginfailures', 'admin'),
+            new lang_string('configdisplayloginfailures', 'admin'), 0));
     $temp->add(new admin_setting_users_with_capability('notifyloginfailures', new lang_string('notifyloginfailures', 'admin'), new lang_string('confignotifyloginfailures', 'admin'), array(), 'moodle/site:config'));
     $options = array();
     for ($i = 1; $i <= 100; $i++) {
index ef20f85..62f4a48 100644 (file)
@@ -28,3 +28,5 @@ $string['logging'] = 'Logging';
 $string['managelogging'] = 'Manage log stores';
 $string['reportssupported'] = 'Reports supported';
 $string['pluginname'] = 'Log store manager';
+$string['subplugintype_logstore'] = 'Log store';
+$string['subplugintype_logstore_plural'] = 'Log stores';
index 43ff051..7b593ca 100644 (file)
@@ -34,42 +34,44 @@ require_once($CFG->libdir.'/formslib.php');
  */
 class tool_task_edit_scheduled_task_form extends moodleform {
     public function definition() {
-        global $CFG;
-
         $mform = $this->_form;
+        /** @var \core\task\scheduled_task $task */
         $task = $this->_customdata;
 
-        $never = get_string('never');
-        $none = get_string('none');
-        $lastrun = $task->get_last_run_time() ? userdate($task->get_last_run_time()) : $never;
-        $nextrun = $task->get_next_run_time() ? userdate($task->get_next_run_time()) : $none;
+        $lastrun = $task->get_last_run_time() ? userdate($task->get_last_run_time()) : get_string('never');
+        $nextrun = $task->get_next_run_time();
+        if ($task->get_disabled()) {
+            $nextrun = get_string('disabled', 'tool_task');
+        } else if ($nextrun > time()) {
+            $nextrun = userdate($nextrun);
+        } else {
+            $nextrun = get_string('asap', 'tool_task');
+        }
         $mform->addElement('static', 'lastrun', get_string('lastruntime', 'tool_task'), $lastrun);
         $mform->addElement('static', 'nextrun', get_string('nextruntime', 'tool_task'), $nextrun);
 
         $mform->addElement('text', 'minute', get_string('taskscheduleminute', 'tool_task'));
         $mform->setType('minute', PARAM_RAW);
         $mform->addHelpButton('minute', 'taskscheduleminute', 'tool_task');
-        $mform->setDefault('minute', $task->get_minute());
 
         $mform->addElement('text', 'hour', get_string('taskschedulehour', 'tool_task'));
         $mform->setType('hour', PARAM_RAW);
         $mform->addHelpButton('hour', 'taskschedulehour', 'tool_task');
-        $mform->setDefault('hour', $task->get_hour());
 
         $mform->addElement('text', 'day', get_string('taskscheduleday', 'tool_task'));
         $mform->setType('day', PARAM_RAW);
         $mform->addHelpButton('day', 'taskscheduleday', 'tool_task');
-        $mform->setDefault('day', $task->get_day());
 
         $mform->addElement('text', 'month', get_string('taskschedulemonth', 'tool_task'));
         $mform->setType('month', PARAM_RAW);
         $mform->addHelpButton('month', 'taskschedulemonth', 'tool_task');
-        $mform->setDefault('month', $task->get_month());
 
         $mform->addElement('text', 'dayofweek', get_string('taskscheduledayofweek', 'tool_task'));
         $mform->setType('dayofweek', PARAM_RAW);
         $mform->addHelpButton('dayofweek', 'taskscheduledayofweek', 'tool_task');
-        $mform->setDefault('dayofweek', $task->get_day_of_week());
+
+        $mform->addElement('advcheckbox', 'disabled', get_string('disabled', 'tool_task'));
+        $mform->addHelpButton('disabled', 'disabled', 'tool_task');
 
         $mform->addElement('advcheckbox', 'resettodefaults', get_string('resettasktodefaults', 'tool_task'));
         $mform->addHelpButton('resettodefaults', 'resettasktodefaults', 'tool_task');
@@ -79,12 +81,16 @@ class tool_task_edit_scheduled_task_form extends moodleform {
         $mform->disabledIf('day', 'resettodefaults', 'checked');
         $mform->disabledIf('dayofweek', 'resettodefaults', 'checked');
         $mform->disabledIf('month', 'resettodefaults', 'checked');
+        $mform->disabledIf('disabled', 'resettodefaults', 'checked');
 
         $mform->addElement('hidden', 'task', get_class($task));
         $mform->setType('task', PARAM_RAW);
         $mform->addElement('hidden', 'action', 'edit');
         $mform->setType('action', PARAM_ALPHANUMEXT);
         $this->add_action_buttons(true, get_string('savechanges'));
+
+        // Do not use defaults for existing values, the set_data() is the correct way.
+        $this->set_data(\core\task\manager::record_from_scheduled_task($task));
     }
 }
 
index 0b56ac5..0660eff 100644 (file)
@@ -70,7 +70,15 @@ if ($options['list']) {
             . $task->get_day_of_week() . ' '
             . $task->get_month() . ' '
             . $task->get_day_of_week();
-        $nextrun = $task->get_next_run_time() ? userdate($task->get_next_run_time(), $shorttime) : 'asap';
+        $nextrun = $task->get_next_run_time();
+
+        if ($task->get_disabled()) {
+            $nextrun = get_string('disabled', 'tool_task');
+        } else if ($nextrun > time()) {
+            $nextrun = userdate($nextrun);
+        } else {
+            $nextrun = get_string('asap', 'tool_task');
+        }
 
         echo str_pad($class, 50, ' ') . ' ' . str_pad($schedule, 17, ' ') . ' ' . $nextrun . "\n";
     }
index a4f5e72..493fa1d 100644 (file)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['asap'] = 'ASAP';
 $string['blocking'] = 'Blocking';
 $string['component'] = 'Component';
 $string['corecomponent'] = 'Core';
 $string['default'] = 'Default';
+$string['disabled'] = 'Disabled';
+$string['disabled_help'] = 'Disabled scheduled tasks are not executed from cron, however they can still be executed manually via the CLI tool.';
 $string['edittaskschedule'] = 'Edit task schedule: {$a}';
 $string['faildelay'] = 'Fail delay';
 $string['lastruntime'] = 'Last run';
index 4a51116..f32c9e0 100644 (file)
@@ -57,11 +57,19 @@ class tool_task_renderer extends plugin_renderer_base {
         $yes = get_string('yes');
         $no = get_string('no');
         $never = get_string('never');
-        $now = get_string('now');
+        $asap = get_string('asap', 'tool_task');
+        $disabled = get_string('disabled', 'tool_task');
         foreach ($tasks as $task) {
             $customised = $task->is_customised() ? $no : $yes;
             $lastrun = $task->get_last_run_time() ? userdate($task->get_last_run_time()) : $never;
-            $nextrun = $task->get_next_run_time() ? userdate($task->get_next_run_time()) : $now;
+            $nextrun = $task->get_next_run_time();
+            if ($task->get_disabled()) {
+                $nextrun = $disabled;
+            } else if ($nextrun > time()) {
+                $nextrun = userdate($nextrun);
+            } else {
+                $nextrun = $asap;
+            }
             $configureurl = new moodle_url('/admin/tool/task/scheduledtasks.php', array('action'=>'edit', 'task' => get_class($task)));
             $editlink = $this->action_icon($configureurl, new pix_icon('t/edit', get_string('edittaskschedule', 'tool_task', $task->get_name())));
 
@@ -95,6 +103,9 @@ class tool_task_renderer extends plugin_renderer_base {
                         new html_table_cell($task->get_fail_delay()),
                         new html_table_cell($customised)));
 
+            if ($task->get_disabled()) {
+                $row->attributes['class'] = 'disabled';
+            }
             $data[] = $row;
         }
         $table->data = $data;
index 2779615..2b38300 100644 (file)
@@ -73,6 +73,7 @@ if ($mform && $mform->is_cancelled()) {
             $task->set_month($defaulttask->get_month());
             $task->set_day_of_week($defaulttask->get_day_of_week());
             $task->set_day($defaulttask->get_day());
+            $task->set_disabled($defaulttask->get_disabled());
             $task->set_customised(false);
         } else {
             $task->set_minute($data->minute);
@@ -80,6 +81,7 @@ if ($mform && $mform->is_cancelled()) {
             $task->set_month($data->month);
             $task->set_day_of_week($data->dayofweek);
             $task->set_day($data->day);
+            $task->set_disabled($data->disabled);
             $task->set_customised(true);
         }
 
index 9fccdd2..64f34f3 100644 (file)
@@ -87,7 +87,11 @@ class auth_db_testcase extends advanced_testcase {
                 set_config('sybasequoting', '0', 'auth/db');
                 if (!empty($CFG->dboptions['dbsocket']) and ($CFG->dbhost === 'localhost' or $CFG->dbhost === '127.0.0.1')) {
                     if (strpos($CFG->dboptions['dbsocket'], '/') !== false) {
-                        set_config('host', $CFG->dboptions['dbsocket'], 'auth/db');
+                        $socket = $CFG->dboptions['dbsocket'];
+                        if (!empty($CFG->dboptions['dbport'])) {
+                            $socket .= ':' . $CFG->dboptions['dbport'];
+                        }
+                        set_config('host', $socket, 'auth/db');
                     } else {
                         set_config('host', '', 'auth/db');
                     }
index 1c7ff9e..2803711 100644 (file)
@@ -37,12 +37,17 @@ require_once($CFG->libdir.'/authlib.php');
  */
 class auth_plugin_manual extends auth_plugin_base {
 
+    /**
+     * The name of the component. Used by the configuration.
+     */
+    const COMPONENT_NAME = 'auth_manual';
+
     /**
      * Constructor.
      */
     function auth_plugin_manual() {
         $this->authtype = 'manual';
-        $this->config = get_config('auth/manual');
+        $this->config = get_config(self::COMPONENT_NAME);
     }
 
     /**
@@ -81,6 +86,7 @@ class auth_plugin_manual extends auth_plugin_base {
      */
     function user_update_password($user, $newpassword) {
         $user = get_complete_user_data('id', $user->id);
+        set_user_preference('auth_manual_passwordupdatetime', time(), $user->id);
         // This will also update the stored hash to the latest algorithm
         // if the existing hash is using an out-of-date algorithm (or the
         // legacy md5 algorithm).
@@ -153,13 +159,56 @@ class auth_plugin_manual extends auth_plugin_base {
         include 'config.html';
     }
 
+    /**
+     * Return number of days to user password expires.
+     *
+     * If user password does not expire, it should return 0 or a positive value.
+     * If user password is already expired, it should return negative value.
+     *
+     * @param mixed $username username (with system magic quotes)
+     * @return integer
+     */
+    public function password_expire($username) {
+        $result = 0;
+
+        if (!empty($this->config->expirationtime)) {
+            $user = core_user::get_user_by_username($username, 'id,timecreated');
+            $lastpasswordupdatetime = get_user_preferences('auth_manual_passwordupdatetime', $user->timecreated, $user->id);
+            $expiretime = $lastpasswordupdatetime + $this->config->expirationtime * DAYSECS;
+            $now = time();
+            $result = ($expiretime - $now) / DAYSECS;
+            if ($expiretime > $now) {
+                $result = ceil($result);
+            } else {
+                $result = floor($result);
+            }
+        }
+
+        return $result;
+    }
+
     /**
      * Processes and stores configuration data for this authentication plugin.
      *
-     * @param array $config
+     * @param stdClass $config
      * @return void
      */
     function process_config($config) {
+        // Set to defaults if undefined.
+        if (!isset($config->expiration)) {
+            $config->expiration = '';
+        }
+        if (!isset($config->expiration_warning)) {
+            $config->expiration_warning = '';
+        }
+        if (!isset($config->expirationtime)) {
+            $config->expirationtime = '';
+        }
+
+        // Save settings.
+        set_config('expiration', $config->expiration, self::COMPONENT_NAME);
+        set_config('expiration_warning', $config->expiration_warning, self::COMPONENT_NAME);
+        set_config('expirationtime', $config->expirationtime, self::COMPONENT_NAME);
         return true;
     }
 
index 8c03ce0..f622ab1 100644 (file)
@@ -1,5 +1,78 @@
-<table cellspacing="0" cellpadding="5" border="0">
 <?php
-print_auth_lock_options($this->authtype, $user_fields, get_string('auth_fieldlocks_help', 'auth'), false, false);
+    // Set to defaults if undefined.
+    if (!isset($config->expiration)) {
+        $config->expiration = '';
+    }
+    if (!isset($config->expiration_warning)) {
+        $config->expiration_warning = '';
+    }
+    if (!isset($config->expirationtime)) {
+        $config->expirationtime = '';
+    }
+    $expirationoptions = array(
+        new lang_string('no'),
+        new lang_string('yes'),
+    );
+    $expirationtimeoptions = array(
+        '30' => new lang_string('numdays', '', 30),
+        '60' => new lang_string('numdays', '', 60),
+        '90' => new lang_string('numdays', '', 90),
+        '120' => new lang_string('numdays', '', 120),
+        '150' => new lang_string('numdays', '', 150),
+        '180' => new lang_string('numdays', '', 180),
+        '365' => new lang_string('numdays', '', 365),
+    );
+    $expirationwarningoptions = array(
+        '0' => new lang_string('never'),
+        '1' => new lang_string('numdays', '', 1),
+        '2' => new lang_string('numdays', '', 2),
+        '3' => new lang_string('numdays', '', 3),
+        '4' => new lang_string('numdays', '', 4),
+        '5' => new lang_string('numdays', '', 5),
+        '6' => new lang_string('numdays', '', 6),
+        '7' => new lang_string('numdays', '', 7),
+        '10' => new lang_string('numdays', '', 10),
+        '14' => new lang_string('numdays', '', 14),
+    );
 ?>
+<table cellspacing="0" cellpadding="5" border="0">
+    <tr>
+        <td colspan="3">
+            <h3><?php print_string('passwdexpire_settings', 'auth_manual') ?></h3>
+        </td>
+    </tr>
+    <tr>
+        <td align="right">
+            <label for="menuexpiration">
+                <?php print_string('expiration', 'auth_manual') ?>
+            </label>
+        </td>
+        <td>
+            <?php echo html_writer::select($expirationoptions, 'expiration', $config->expiration, false) ?>
+        </td>
+        <td><?php print_string('expiration_desc', 'auth_manual') ?></td>
+    </tr>
+    <tr>
+        <td align="right">
+            <label for="menuexpirationtime">
+                <?php print_string('passwdexpiretime', 'auth_manual') ?>
+            </label>
+        </td>
+        <td>
+            <?php echo html_writer::select($expirationtimeoptions, 'expirationtime', $config->expirationtime, false) ?>
+        </td>
+        <td><?php print_string('passwdexpiretime_desc', 'auth_manual') ?></td>
+    </tr>
+    <tr>
+        <td align="right">
+            <label for="menuexpiration_warning">
+                <?php print_string('expiration_warning', 'auth_manual') ?>
+            </label>
+        </td>
+        <td>
+            <?php echo html_writer::select($expirationwarningoptions, 'expiration_warning', $config->expiration_warning, false) ?>
+        </td>
+        <td><?php print_string('expiration_warning_desc', 'auth_manual') ?></td>
+    </tr>
+    <?php print_auth_lock_options($this->authtype, $user_fields, get_string('auth_fieldlocks_help', 'auth'), false, false) ?>
 </table>
index 53d8909..a919797 100644 (file)
  */
 
 $string['auth_manualdescription'] = 'This method removes any way for users to create their own accounts.  All accounts must be manually created by the admin user.';
+$string['expiration'] = 'Enable password expiry';
+$string['expiration_desc'] = 'Allow passwords to expire after a specified time.';
+$string['expiration_warning'] = 'Notification threshold';
+$string['expiration_warning_desc'] = 'Number of days before password expiry that a notification is issued.';
+$string['passwdexpiretime'] = 'Password duration';
+$string['passwdexpiretime_desc'] = 'Length of time for which a password is valid.';
 $string['pluginname'] = 'Manual accounts';
+$string['passwdexpire_settings'] = 'Password expiry settings';
diff --git a/auth/manual/tests/manual_test.php b/auth/manual/tests/manual_test.php
new file mode 100644 (file)
index 0000000..087721a
--- /dev/null
@@ -0,0 +1,108 @@
+<?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/>.
+
+/**
+ * Manual authentication tests.
+ *
+ * @package    auth_manual
+ * @category   test
+ * @copyright  2014 Gilles-Philippe Leblanc <gilles-philippe.leblanc@umontreal.ca>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot.'/auth/manual/auth.php');
+
+/**
+ * Manual authentication tests class.
+ *
+ * @package    auth_manual
+ * @category   test
+ * @copyright  2014 Gilles-Philippe Leblanc <gilles-philippe.leblanc@umontreal.ca>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class auth_manual_testcase extends advanced_testcase {
+
+    /** @var auth_plugin_manual Keeps the authentication plugin. */
+    protected $authplugin;
+
+    /** @var stdClass Keeps authentication plugin config */
+    protected $config;
+
+    /**
+     * Setup test data.
+     */
+    protected function setUp() {
+        $this->resetAfterTest(true);
+        $this->authplugin = new auth_plugin_manual();
+        $this->config = new stdClass();
+        $this->config->expiration = '1';
+        $this->config->expiration_warning = '2';
+        $this->config->expirationtime = '30';
+        $this->authplugin->process_config($this->config);
+        $this->authplugin->config = get_config(auth_plugin_manual::COMPONENT_NAME);
+    }
+
+    /**
+     * Test user_update_password method.
+     */
+    public function test_user_update_password() {
+        $user = $this->getDataGenerator()->create_user();
+        $expectedtime = time();
+        $passwordisupdated = $this->authplugin->user_update_password($user, 'MyNewPassword*');
+
+        // Assert that the actual time should be equal or a little greater than the expected time.
+        $this->assertGreaterThanOrEqual($expectedtime, get_user_preferences('auth_manual_passwordupdatetime', 0, $user->id));
+
+        // Assert that the password was successfully updated.
+        $this->assertTrue($passwordisupdated);
+    }
+
+    /**
+     * Test test_password_expire method.
+     */
+    public function test_password_expire() {
+        $userrecord = array();
+        $expirationtime = 31 * DAYSECS;
+        $userrecord['timecreated'] = time() - $expirationtime;
+        $user1 = $this->getDataGenerator()->create_user($userrecord);
+        $user2 = $this->getDataGenerator()->create_user();
+
+        // The user 1 was created 31 days ago and has not changed his password yet, so the password has expirated.
+        $this->assertLessThanOrEqual(-1, $this->authplugin->password_expire($user1->username));
+
+        // The user 2 just came to be created and has not changed his password yet, so the password has not expirated.
+        $this->assertEquals(30, $this->authplugin->password_expire($user2->username));
+
+        $this->authplugin->user_update_password($user1, 'MyNewPassword*');
+
+        // The user 1 just updated his password so the password has not expirated.
+        $this->assertEquals(30, $this->authplugin->password_expire($user1->username));
+    }
+
+    /**
+     * Test test_process_config method.
+     */
+    public function test_process_config() {
+        $this->assertTrue($this->authplugin->process_config($this->config));
+        $config = get_config(auth_plugin_manual::COMPONENT_NAME);
+        $this->assertEquals($this->config->expiration, $config->expiration);
+        $this->assertEquals($this->config->expiration_warning, $config->expiration_warning);
+        $this->assertEquals($this->config->expirationtime, $config->expirationtime);
+    }
+}
index b4af764..87c78bb 100644 (file)
@@ -174,6 +174,8 @@ class core_bloglib_testcase extends advanced_testcase {
 
         // Validate event data.
         $this->assertInstanceOf('\core\event\blog_entry_created', $event);
+        $url = new moodle_url('/blog/index.php', array('entryid' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
         $this->assertEquals($sitecontext->id, $event->contextid);
         $this->assertEquals($blog->id, $event->objectid);
         $this->assertEquals($USER->id, $event->userid);
@@ -207,6 +209,8 @@ class core_bloglib_testcase extends advanced_testcase {
 
         // Validate event data.
         $this->assertInstanceOf('\core\event\blog_entry_updated', $event);
+        $url = new moodle_url('/blog/index.php', array('entryid' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
         $this->assertEquals($sitecontext->id, $event->contextid);
         $this->assertEquals($blog->id, $event->objectid);
         $this->assertEquals($USER->id, $event->userid);
@@ -240,6 +244,7 @@ class core_bloglib_testcase extends advanced_testcase {
 
         // Validate event data.
         $this->assertInstanceOf('\core\event\blog_entry_deleted', $event);
+        $this->assertEquals(null, $event->get_url());
         $this->assertEquals($sitecontext->id, $event->contextid);
         $this->assertEquals($blog->id, $event->objectid);
         $this->assertEquals($USER->id, $event->userid);
@@ -278,6 +283,8 @@ class core_bloglib_testcase extends advanced_testcase {
         // Validate event data.
         $this->assertInstanceOf('\core\event\blog_association_created', $event);
         $this->assertEquals($sitecontext->id, $event->contextid);
+        $url = new moodle_url('/blog/index.php', array('entryid' => $event->other['blogid']));
+        $this->assertEquals($url, $event->get_url());
         $this->assertEquals($blog->id, $event->other['blogid']);
         $this->assertEquals($this->courseid, $event->other['associateid']);
         $this->assertEquals('course', $event->other['associatetype']);
index 6392b84..eadcc56 100644 (file)
@@ -104,6 +104,8 @@ class core_cohort_cohortlib_testcase extends advanced_testcase {
         $this->assertEquals('cohort', $event->objecttable);
         $this->assertEquals($id, $event->objectid);
         $this->assertEquals($cohort->contextid, $event->contextid);
+        $url = new moodle_url('/cohort/index.php', array('contextid' => $event->contextid));
+        $this->assertEquals($url, $event->get_url());
         $this->assertEquals($cohort, $event->get_record_snapshot('cohort', $id));
         $this->assertEventLegacyData($cohort, $event);
         $this->assertEventContextNotUsed($event);
@@ -175,6 +177,8 @@ class core_cohort_cohortlib_testcase extends advanced_testcase {
         $this->assertEquals('cohort', $event->objecttable);
         $this->assertEquals($updatedcohort->id, $event->objectid);
         $this->assertEquals($updatedcohort->contextid, $event->contextid);
+        $url = new moodle_url('/cohort/edit.php', array('id' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
         $this->assertEquals($cohort, $event->get_record_snapshot('cohort', $id));
         $this->assertEventLegacyData($cohort, $event);
         $this->assertEventContextNotUsed($event);
@@ -213,6 +217,8 @@ class core_cohort_cohortlib_testcase extends advanced_testcase {
         $this->assertInstanceOf('\core\event\cohort_deleted', $event);
         $this->assertEquals('cohort', $event->objecttable);
         $this->assertEquals($cohort->id, $event->objectid);
+        $url = new moodle_url('/cohort/index.php', array('contextid' => $event->contextid));
+        $this->assertEquals($url, $event->get_url());
         $this->assertEquals($cohort, $event->get_record_snapshot('cohort', $cohort->id));
         $this->assertEventLegacyData($cohort, $event);
         $this->assertEventContextNotUsed($event);
@@ -272,6 +278,8 @@ class core_cohort_cohortlib_testcase extends advanced_testcase {
         $this->assertEquals($cohort->id, $event->objectid);
         $this->assertEquals($user->id, $event->relateduserid);
         $this->assertEquals($USER->id, $event->userid);
+        $url = new moodle_url('/cohort/assign.php', array('id' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
         $this->assertEventLegacyData((object) array('cohortid' => $cohort->id, 'userid' => $user->id), $event);
         $this->assertEventContextNotUsed($event);
     }
@@ -316,6 +324,8 @@ class core_cohort_cohortlib_testcase extends advanced_testcase {
         $this->assertEquals($cohort->id, $event->objectid);
         $this->assertEquals($user->id, $event->relateduserid);
         $this->assertEquals($USER->id, $event->userid);
+        $url = new moodle_url('/cohort/assign.php', array('id' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
         $this->assertEventLegacyData((object) array('cohortid' => $cohort->id, 'userid' => $user->id), $event);
         $this->assertEventContextNotUsed($event);
     }
index a28ea9b..c866f6c 100644 (file)
@@ -125,10 +125,10 @@ class core_course_editcategory_form extends moodleform {
         if (!empty($data['idnumber'])) {
             if ($existing = $DB->get_record('course_categories', array('idnumber' => $data['idnumber']))) {
                 if (!$data['id'] || $existing->id != $data['id']) {
-                    $errors['idnumber']= get_string('categoryidnumbertaken');
+                    $errors['idnumber'] = get_string('categoryidnumbertaken', 'error');
                 }
             }
         }
         return $errors;
     }
-}
\ No newline at end of file
+}
index f8b2fa4..6c10da1 100644 (file)
@@ -140,6 +140,7 @@ class core_course_external extends external_api {
                         //common info (for people being able to see the module or availability dates)
                         $module['id'] = $cm->id;
                         $module['name'] = format_string($cm->name, true);
+                        $module['instance'] = $cm->instance;
                         $module['modname'] = $cm->modname;
                         $module['modplural'] = $cm->modplural;
                         $module['modicon'] = $cm->get_icon_url()->out(false);
index 6d54403..6726f03 100644 (file)
@@ -2597,14 +2597,14 @@ function update_course($data, $editoroptions = NULL) {
 
     // Check we don't have a duplicate shortname.
     if (!empty($data->shortname) && $oldcourse->shortname != $data->shortname) {
-        if ($DB->record_exists('course', array('shortname' => $data->shortname))) {
+        if ($DB->record_exists_sql('SELECT id from {course} WHERE shortname = ? AND id <> ?', array($data->shortname, $data->id))) {
             throw new moodle_exception('shortnametaken', '', '', $data->shortname);
         }
     }
 
     // Check we don't have a duplicate idnumber.
     if (!empty($data->idnumber) && $oldcourse->idnumber != $data->idnumber) {
-        if ($DB->record_exists('course', array('idnumber' => $data->idnumber))) {
+        if ($DB->record_exists_sql('SELECT id from {course} WHERE idnumber = ? AND id <> ?', array($data->idnumber, $data->id))) {
             throw new moodle_exception('courseidnumbertaken', '', '', $data->idnumber);
         }
     }
index 192ccc2..11e3d5c 100644 (file)
@@ -119,9 +119,6 @@ if ($param->modid === 'all') {
     $sections = array($sectionnum => $sections[$sectionnum]);
 }
 
-
-$modinfo->get_groups(); // load all my groups and cache it in modinfo
-
 $activities = array();
 $index = 0;
 
index e624efc..63e00d4 100644 (file)
@@ -332,3 +332,17 @@ Feature: Test category management actions
     And the "movecategoriesto" "select" should be disabled
     And the "resortcategoriesby" "select" should be disabled
     And the "resortcoursesby" "select" should be disabled
+
+  Scenario: Test that is not possible to create a course category with a duplicate idnumber
+    Given the following "categories" exist:
+      | name | category | idnumber |
+      | Cat 1 | 0 | CAT1 |
+    And I log in as "admin"
+    And I expand "Site administration" node
+    And I expand "Courses" node
+    And I follow "Add a category"
+    And I set the following fields to these values:
+      | Category name | Test duplicate |
+      | Category ID number | CAT1 |
+    When I press "Create category"
+    Then I should see "ID number is already used for another category"
index 45c731c..0334214 100644 (file)
@@ -1496,6 +1496,8 @@ class core_course_courselib_testcase extends advanced_testcase {
         $this->assertEquals('course', $event->objecttable);
         $this->assertEquals($updatedcourse->id, $event->objectid);
         $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $url = new moodle_url('/course/edit.php', array('id' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
         $this->assertEquals($updatedcourse, $event->get_record_snapshot('course', $event->objectid));
         $this->assertEquals('course_updated', $event->get_legacy_eventname());
         $this->assertEventLegacyData($updatedcourse, $event);
@@ -1666,6 +1668,7 @@ class core_course_courselib_testcase extends advanced_testcase {
         $this->assertEquals($category->id, $event->objectid);
         $this->assertEquals($categorycontext->id, $event->contextid);
         $this->assertEquals('course_category_deleted', $event->get_legacy_eventname());
+        $this->assertEquals(null, $event->get_url());
         $this->assertEventLegacyData($category, $event);
         $expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category->name . '(ID ' . $category->id . ')');
         $this->assertEventLegacyLogData($expectedlog, $event);
@@ -1762,6 +1765,8 @@ class core_course_courselib_testcase extends advanced_testcase {
             'operation' => $rc->get_operation(),
             'samesite' => $rc->is_samesite()
         );
+        $url = new moodle_url('/course/view.php', array('id' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
         $this->assertEventLegacyData($legacydata, $event);
         $this->assertEventContextNotUsed($event);
 
@@ -1813,6 +1818,8 @@ class core_course_courselib_testcase extends advanced_testcase {
         $this->assertEquals($coursecontext->id, $event->contextid);
         $expecteddesc = 'Course ' . $event->courseid . ' section ' . $event->other['sectionnum'] . ' updated by user ' . $event->userid;
         $this->assertEquals($expecteddesc, $event->get_description());
+        $url = new moodle_url('/course/editsection.php', array('id' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
         $this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid));
         $id = $section->id;
         $sectionnum = $section->section;
index 9e73cc9..30fdc44 100644 (file)
@@ -89,7 +89,11 @@ class enrol_database_testcase extends advanced_testcase {
                 set_config('dbsybasequoting', '0', 'enrol_database');
                 if (!empty($CFG->dboptions['dbsocket']) and ($CFG->dbhost === 'localhost' or $CFG->dbhost === '127.0.0.1')) {
                     if (strpos($CFG->dboptions['dbsocket'], '/') !== false) {
-                      set_config('dbhost', $CFG->dboptions['dbsocket'], 'enrol_database');
+                        $socket = $CFG->dboptions['dbsocket'];
+                        if (!empty($CFG->dboptions['dbport'])) {
+                            $socket .= ':' . $CFG->dboptions['dbport'];
+                        }
+                        set_config('dbhost', $socket, 'enrol_database');
                     } else {
                       set_config('dbhost', '', 'enrol_database');
                     }
index a23c148..3a9e038 100644 (file)
@@ -547,6 +547,8 @@ class enrol_meta_plugin_testcase extends advanced_testcase {
         $expectedlegacyeventdata = $dbuserenrolled;
         $expectedlegacyeventdata->enrol = 'meta';
         $expectedlegacyeventdata->courseid = $course2->id;
+        $url = new \moodle_url('/enrol/editenrolment.php', array('ue' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
         $this->assertEventLegacyData($expectedlegacyeventdata, $event);
         $this->assertEventContextNotUsed($event);
     }
index 063d5b4..50c2bcb 100644 (file)
@@ -35,7 +35,7 @@ $course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
 $context = context_course::instance($course->id, MUST_EXIST);
 
 require_login($course);
-require_capability('moodle/role:assign', $context);
+require_capability('moodle/course:reviewotherusers', $context);
 
 if ($course->id == SITEID) {
     redirect("$CFG->wwwroot/");
index 0a18679..92b7fe5 100644 (file)
Binary files a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-debug.js and b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-debug.js differ
index 5d6f83b..a895bd0 100644 (file)
Binary files a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js and b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js differ
index 0a18679..92b7fe5 100644 (file)
Binary files a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker.js and b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker.js differ
index 3217f4a..8879603 100644 (file)
@@ -70,6 +70,8 @@ Y.extend(AUTOLINKER, Y.Base, {
                     alertpanel = new M.core.alert({title:data.entries[key].concept,
                         message:definition, modal:false, yesLabel: M.util.get_string('ok', 'moodle')});
                     alertpanel.show();
+                    Y.fire(M.core.event.FILTER_CONTENT_UPDATED, {nodes: (new Y.NodeList(alertpanel.get('boundingBox')))});
+
                     Y.Node.one('#id_yuialertconfirm-' + alertpanel.get('COUNT')).focus();
                 }
 
index ac4042e..065cb2e 100644 (file)
@@ -7,6 +7,7 @@
         "json-parse",
         "event-delegate",
         "overlay",
+        "moodle-core-event",
         "moodle-core-notification-alert"
     ]
   }
diff --git a/filter/mathjaxloader/filter.php b/filter/mathjaxloader/filter.php
new file mode 100644 (file)
index 0000000..a22e5bf
--- /dev/null
@@ -0,0 +1,171 @@
+<?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/>.
+
+/**
+ * This filter provides automatic support for MathJax
+ *
+ * @package    filter_mathjaxloader
+ * @copyright  2013 Damyon Wiese (damyon@moodle.com)
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Mathjax filtering
+ */
+class filter_mathjaxloader extends moodle_text_filter {
+
+    /*
+     * Perform a mapping of the moodle language code to the equivalent for MathJax.
+     *
+     * @param string $moodlelangcode - The moodle language code - e.g. en_pirate
+     * @return string The MathJax language code.
+     */
+    public function map_language_code($moodlelangcode) {
+        $mathjaxlangcodes = array('br',
+                                  'cdo',
+                                  'cs',
+                                  'da',
+                                  'de',
+                                  'en',
+                                  'eo',
+                                  'es',
+                                  'fa',
+                                  'fi',
+                                  'fr',
+                                  'gl',
+                                  'he',
+                                  'ia',
+                                  'it',
+                                  'ja',
+                                  'ko',
+                                  'lb',
+                                  'mk',
+                                  'nl',
+                                  'oc',
+                                  'pl',
+                                  'pt',
+                                  'pt-br',
+                                  'ru',
+                                  'sl',
+                                  'sv',
+                                  'tr',
+                                  'uk',
+                                  'zh-hans');
+        $exceptions = array('cz' => 'cs');
+
+        // First see if this is an exception.
+        if (isset($exceptions[$moodlelangcode])) {
+            $moodlelangcode = $exceptions[$moodlelangcode];
+        }
+
+        // Now look for an exact lang string match.
+        if (in_array($moodlelangcode, $mathjaxlangcodes)) {
+            return $moodlelangcode;
+        }
+
+        // Now try shortening the moodle lang string.
+        $moodlelangcode = preg_replace('/-.*/', '', $moodlelangcode);
+        // Look for a match on the shortened string.
+        if (in_array($moodlelangcode, $mathjaxlangcodes)) {
+            return $moodlelangcode;
+        }
+        // All failed - use english.
+        return 'en';
+    }
+
+    /*
+     * Add the javascript to enable mathjax processing on this page.
+     *
+     * @param moodle_page $page The current page.
+     * @param context $context The current context.
+     */
+    public function setup($page, $context) {
+        global $CFG;
+        // This only requires execution once per request.
+        static $jsinitialised = false;
+
+        if (empty($jsinitialised)) {
+            if (strpos($CFG->httpswwwroot, 'https:') === 0) {
+                $url = get_config('filter_mathjaxloader', 'httpsurl');
+            } else {
+                $url = get_config('filter_mathjaxloader', 'httpurl');
+            }
+            $lang = $this->map_language_code(current_language());
+            $url = new moodle_url($url, array('delayStartupUntil' => 'configured'));
+
+            $moduleconfig = array(
+                'name' => 'mathjax',
+                'fullpath' => $url
+            );
+
+            $page->requires->js_module($moduleconfig);
+
+            $config = get_config('filter_mathjaxloader', 'mathjaxconfig');
+
+            $params = array('mathjaxconfig' => $config, 'lang' => $lang);
+
+            $page->requires->yui_module('moodle-filter_mathjaxloader-loader', 'M.filter_mathjaxloader.configure', array($params));
+
+            $jsinitialised = true;
+        }
+    }
+
+    /*
+     * This function wraps the filtered text in a span, that mathjaxloader is configured to process.
+     *
+     * @param string $text The text to filter.
+     * @param array $options The filter options.
+     */
+    public function filter($text, array $options = array()) {
+        global $PAGE;
+
+        $legacy = get_config('filter_mathjaxloader', 'texfiltercompatibility');
+        $extradelimiters = explode(',', get_config('filter_mathjaxloader', 'additionaldelimiters'));
+        if ($legacy) {
+            // This replaces any of the tex filter maths delimiters with the default for inline maths in MathJAX "\( blah \)".
+            // E.g. "<tex.*> blah </tex>".
+            $text = preg_replace('|<(/?) *tex( [^>]*)?>|u', '[\1tex]', $text);
+            // E.g. "[tex.*] blah [/tex]".
+            $text = str_replace('[tex]', '\\(', $text);
+            $text = str_replace('[/tex]', '\\)', $text);
+            // E.g. "$$ blah $$".
+            $text = preg_replace('|\$\$[\S\s]\$\$|u', '\\(\1\\)', $text);
+            // E.g. "\[ blah \]".
+            $text = str_replace('\\[', '\\(', $text);
+            $text = str_replace('\\]', '\\)', $text);
+        }
+
+        $hasinline = strpos($text, '\\(') !== false && strpos($text, '\\)') !== false;
+        $hasdisplay = (strpos($text, '$$') !== false) ||
+                      (strpos($text, '\\[') !== false && strpos($text, '\\]') !== false);
+
+        $hasextra = false;
+
+        foreach ($extradelimiters as $extra) {
+            if ($extra && strpos($text, $extra) !== false) {
+                $hasextra = true;
+                break;
+            }
+        }
+        if ($hasinline || $hasdisplay || $hasextra) {
+            $PAGE->requires->yui_module('moodle-filter_mathjaxloader-loader', 'M.filter_mathjaxloader.typeset');
+            return '<span class="nolink"><span class="filter_mathjaxloader_equation">' . $text . '</span></span>';
+        }
+        return $text;
+    }
+}
diff --git a/filter/mathjaxloader/lang/en/filter_mathjaxloader.php b/filter/mathjaxloader/lang/en/filter_mathjaxloader.php
new file mode 100644 (file)
index 0000000..8ba3583
--- /dev/null
@@ -0,0 +1,45 @@
+<?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/>.
+
+/**
+ * Strings for component 'filter_mathjaxloader', language 'en'.
+ *
+ * @package    filter_mathjaxloader
+ * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['filtername'] = 'MathJax';
+$string['additionaldelimiters'] = 'Additional equation delimiters';
+$string['additionaldelimiters_help'] = 'MathJax filter parses text for equations contained within delimiter characters.
+
+The list of recognised delimiter characters can be added to here (e.g. AsciiMath uses `). Delimiters can contain multiple characters and multiple delimiters can be separated with commas.';
+$string['httpurl'] = 'HTTP MathJax URL';
+$string['httpurl_help'] = 'Full URL to MathJax library. Used when the page is loaded via http.';
+$string['httpsurl'] = 'HTTPS MathJax URL';
+$string['httpsurl_help'] = 'Full URL to MathJax library. Used when the page is loaded via https (secure). ';
+$string['texfiltercompatibility'] = 'Tex filter compatibility';
+$string['texfiltercompatibility_help'] = 'The MathJax filter can be used as a replacement for the Tex filter.
+
+To support all the delimiters supported by the Tex filter MathJax will be configured to display all equations "inline" with the tex.';
+$string['localinstall'] = 'Local MathJax installation';
+$string['localinstall_help'] = 'The default MathJAX configuration uses the CDN version of MathJAX, but MathJAX can be installed locally if required.
+
+Some reasons this might be useful are to save on bandwidth - or because of local proxy restrictions.
+
+To use a local installation of MathJAX, first download the full MathJax library from http://www.mathjax.org/. Then install it on a web server. Finally update the MathJax filter settings httpurl and/or httpsurl to point to the local MathJax.js url.';
+$string['mathjaxsettings'] = 'MathJax configuration';
+$string['mathjaxsettings_desc'] = 'The default MathJAX configuration should be appropriate for most users, but MathJax is highly configurable and any of the standard MathJax configuration options can be added here.';
diff --git a/filter/mathjaxloader/readme_moodle.txt b/filter/mathjaxloader/readme_moodle.txt
new file mode 100644 (file)
index 0000000..a46c895
--- /dev/null
@@ -0,0 +1,15 @@
+Description of MathJAX library integration in Moodle
+=========================================================================================
+
+License: Apache 2.0
+Source: http://www.mathjax.org
+
+Moodle maintainer: Damyon Wiese
+
+=========================================================================================
+This library is not shipped with Moodle, but this filter is provided, which can be used to
+correctly load MathJax into a page from the CDN. Alternatively you can download the entire
+library and install it locally, then use this filter to load that local version.
+
+The only changes required to this filter to handle different MathJax versions is to update
+the default CDN urls in settings.php - and update the list of language mappings - in filter.php.
diff --git a/filter/mathjaxloader/settings.php b/filter/mathjaxloader/settings.php
new file mode 100644 (file)
index 0000000..deeb975
--- /dev/null
@@ -0,0 +1,86 @@
+<?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/>.
+
+/**
+ * MathJAX filter settings
+ *
+ * @package    filter_mathjaxloader
+ * @copyright  2014 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+if ($ADMIN->fulltree) {
+    $item = new admin_setting_heading('filter_mathjaxloader/localinstall',
+                                      new lang_string('localinstall', 'filter_mathjaxloader'),
+                                      new lang_string('localinstall_help', 'filter_mathjaxloader'));
+    $settings->add($item);
+
+    $item = new admin_setting_configtext('filter_mathjaxloader/httpurl',
+                                         new lang_string('httpurl', 'filter_mathjaxloader'),
+                                         new lang_string('httpurl_help', 'filter_mathjaxloader'),
+                                         'http://cdn.mathjax.org/mathjax/2.3-latest/MathJax.js',
+                                         PARAM_RAW);
+    $settings->add($item);
+
+    $item = new admin_setting_configtext('filter_mathjaxloader/httpsurl',
+                                         new lang_string('httpsurl', 'filter_mathjaxloader'),
+                                         new lang_string('httpsurl_help', 'filter_mathjaxloader'),
+                                         'https://c328740.ssl.cf1.rackcdn.com/mathjax/2.3-latest/MathJax.js',
+                                         PARAM_RAW);
+    $settings->add($item);
+
+    $item = new admin_setting_configcheckbox('filter_mathjaxloader/texfiltercompatibility',
+                                             new lang_string('texfiltercompatibility', 'filter_mathjaxloader'),
+                                             new lang_string('texfiltercompatibility_help', 'filter_mathjaxloader'),
+                                             0);
+    $settings->add($item);
+
+    $default = '
+MathJax.Hub.Config({
+    config: ["MMLorHTML.js", "Safe.js"],
+    jax: ["input/TeX","input/MathML","output/HTML-CSS","output/NativeMML"],
+    extensions: ["tex2jax.js","mml2jax.js","MathMenu.js","MathZoom.js"],
+    TeX: {
+        extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js"]
+    },
+    menuSettings: {
+        zoom: "Double-Click",
+        mpContext: true,
+        mpMouse: true
+    },
+    errorSettings: { message: ["!"] },
+    skipStartupTypeset: true,
+    messageStyle: "none"
+});
+';
+
+    $item = new admin_setting_configtextarea('filter_mathjaxloader/mathjaxconfig',
+                                             new lang_string('mathjaxsettings','filter_mathjaxloader'),
+                                             new lang_string('mathjaxsettings_desc', 'filter_mathjaxloader'),
+                                             $default);
+
+    $settings->add($item);
+
+    $item = new admin_setting_configtext('filter_mathjaxloader/additionaldelimiters',
+                                         new lang_string('additionaldelimiters', 'filter_mathjaxloader'),
+                                         new lang_string('additionaldelimiters_help', 'filter_mathjaxloader'),
+                                         '',
+                                         PARAM_RAW);
+    $settings->add($item);
+
+}
diff --git a/filter/mathjaxloader/styles.css b/filter/mathjaxloader/styles.css
new file mode 100644 (file)
index 0000000..f095292
--- /dev/null
@@ -0,0 +1,3 @@
+.jsenabled #MathJax_ZoomFrame {
+    position: absolute;
+}
diff --git a/filter/mathjaxloader/version.php b/filter/mathjaxloader/version.php
new file mode 100644 (file)
index 0000000..666c4b8
--- /dev/null
@@ -0,0 +1,29 @@
+<?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/>.
+
+/**
+ * MathJax filter version information
+ *
+ * @package    filter_mathjaxloader
+ * @copyright  2014 Damyon Wiese (damyon@moodle.com)
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version  = 2013110500;
+$plugin->requires = 2013110500;  // Requires this Moodle version
+$plugin->component= 'filter_mathjaxloader';
diff --git a/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader-debug.js b/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader-debug.js
new file mode 100644 (file)
index 0000000..15f0c75
Binary files /dev/null and b/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader-debug.js differ
diff --git a/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader-min.js b/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader-min.js
new file mode 100644 (file)
index 0000000..a5cd7f1
Binary files /dev/null and b/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader-min.js differ
diff --git a/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader.js b/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader.js
new file mode 100644 (file)
index 0000000..15f0c75
Binary files /dev/null and b/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader.js differ
diff --git a/filter/mathjaxloader/yui/src/loader/build.json b/filter/mathjaxloader/yui/src/loader/build.json
new file mode 100644 (file)
index 0000000..b262702
--- /dev/null
@@ -0,0 +1,10 @@
+{
+    "name": "moodle-filter_mathjaxloader-loader",
+    "builds": {
+        "moodle-filter_mathjaxloader-loader": {
+            "jsfiles": [
+                "loader.js"
+            ]
+        }
+    }
+}
diff --git a/filter/mathjaxloader/yui/src/loader/js/loader.js b/filter/mathjaxloader/yui/src/loader/js/loader.js
new file mode 100644 (file)
index 0000000..fb7fe1b
--- /dev/null
@@ -0,0 +1,115 @@
+// 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/>.
+
+/**
+ * Mathjax JS Loader.
+ *
+ * @package    filter_mathjaxloader
+ * @copyright  2014 Damyon Wiese  <damyon@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+M.filter_mathjaxloader = M.filter_mathjaxloader || {
+
+    /**
+     * The users current language - this can't be set until MathJax is loaded - so we need to store it.
+     * @property _lang
+     * @type String
+     * @default ''
+     * @private
+     */
+    _lang: '',
+
+    /**
+     * Boolean used to prevent configuring MathJax twice.
+     * @property _configured
+     * @type Boolean
+     * @default ''
+     * @private
+     */
+    _configured: false,
+
+    /**
+     * Called by the filter when it is active on any page.
+     * This does not load MathJAX yet - it addes the configuration to the head incase it gets loaded later.
+     * It also subscribes to the filter-content-updated event so MathJax can respond to content loaded by Ajax.
+     *
+     * @method typeset
+     * @param {Object} params List of configuration params containing mathjaxconfig (text) and lang
+     */
+    configure: function(params) {
+
+        // Add a js configuration object to the head.
+        // See "http://docs.mathjax.org/en/latest/dynamic.html#ajax-mathjax"
+        var script = document.createElement("script");
+        script.type = "text/x-mathjax-config";
+        script[(window.opera ? "innerHTML" : "text")] = params.mathjaxconfig;
+        document.getElementsByTagName("head")[0].appendChild(script);
+
+        // Save the lang config until MathJax is actually loaded.
+        this._lang = params.lang;
+
+        // Listen for events triggered when new text is added to a page that needs
+        // processing by a filter.
+        Y.on(M.core.event.FILTER_CONTENT_UPDATED, this.contentUpdated, this);
+    },
+
+    /**
+     * Set the correct language for the MathJax menus. Only do this once.
+     *
+     * @method setLocale
+     * @private
+     */
+    _setLocale: function() {
+        if (!this._configured) {
+            MathJax.Localization.setLocale(this._lang);
+            MathJax.Hub.Configured();
+            this._configured = true;
+        }
+    },
+
+    /**
+     * Called by the filter when an equation is found while rendering the page.
+     *
+     * @method typeset
+     */
+    typeset: function() {
+        if (!this._configured) {
+            var self = this;
+            Y.use('mathjax', function() {
+                self._setLocale();
+                Y.all('.filter_mathjaxloader_equation').each(function(node) {
+                    MathJax.Hub.Queue(["Typeset", MathJax.Hub, node.getDOMNode()]);
+                });
+            });
+        }
+    },
+
+    /**
+     * Handle content updated events - typeset the new content.
+     * @method contentUpdated
+     * @param Y.Event - Custom event with "nodes" indicating the root of the updated nodes.
+     */
+    contentUpdated: function(event) {
+        var self = this;
+        Y.use('mathjax', function() {
+            self._setLocale();
+            event.nodes.each(function (node) {
+                node.all('.filter_mathjaxloader_equation').each(function(node) {
+                    MathJax.Hub.Queue(["Typeset", MathJax.Hub, node.getDOMNode()]);
+                });
+            });
+        });
+    }
+};
diff --git a/filter/mathjaxloader/yui/src/loader/meta/loader.json b/filter/mathjaxloader/yui/src/loader/meta/loader.json
new file mode 100644 (file)
index 0000000..eaca422
--- /dev/null
@@ -0,0 +1,7 @@
+{
+    "moodle-filter_mathjaxloader-loader": {
+        "requires": [
+            "moodle-core-event"
+        ]
+    }
+}
index 3545a64..62536f6 100644 (file)
@@ -197,17 +197,6 @@ if ($mform->is_cancelled()) {
         $data->feedbackformat = $old_grade_grade->feedbackformat;
     }
 
-    // Only log a grade override if they actually changed the student grade.
-    if ($data->finalgrade != $old_grade_grade->finalgrade) {
-        $url = '/report/grader/index.php?id=' . $course->id;
-
-        $user = $DB->get_record('user', array('id'=>$data->userid), '*', MUST_EXIST);
-        $fullname = fullname($user);
-
-        $info = "{$grade_item->itemname}: $fullname";
-        add_to_log($course->id, 'grade', 'update', $url, $info);
-    }
-
     // update final grade or feedback
     // when we set override grade the first time, it happens here
     $grade_item->update_final_grade($data->userid, $data->finalgrade, 'editgrade', $data->feedback, $data->feedbackformat);
@@ -221,19 +210,6 @@ if ($mform->is_cancelled()) {
             $data->overridden = 0; // checkbox unticked
         }
         $grade_grade->set_overridden($data->overridden);
-
-        if ($data->overridden == 0 && $data->overridden != $old_grade_grade->overridden) {
-            // Log removing an override.
-            // The addition of an override is logged above.
-            // One or the other will happen but never both.
-            $url = '/report/grader/index.php?id=' . $course->id;
-
-            $user = $DB->get_record('user', array('id'=>$data->userid), '*', MUST_EXIST);
-            $fullname = fullname($user);
-
-            $info = "{$grade_item->itemname}: $fullname";
-            add_to_log($course->id, 'grade', 'update', $url, $info);
-        }
     }
 
     if (has_capability('moodle/grade:manage', $context) or has_capability('moodle/grade:hide', $context)) {
@@ -289,6 +265,14 @@ if ($mform->is_cancelled()) {
         $grade_item->force_regrading();
     }
 
+    $grade_grade = new grade_grade(array('userid'=>$data->userid, 'itemid'=>$grade_item->id), true);
+    if ($old_grade_grade->finalgrade != $grade_grade->finalgrade
+        or empty($old_grade_grade->overridden) != empty($grade_grade->overridden)
+    ) {
+        $grade_grade->grade_item = $grade_item;
+        \core\event\user_graded::create_from_grade($grade_grade)->trigger();
+    }
+
     redirect($returnurl);
 }
 
index b61f579..d49e265 100644 (file)
@@ -118,16 +118,10 @@ switch ($action) {
                 echo json_encode($json_object);
                 die();
             } else {
-                $url = '/report/grader/index.php?id=' . $course->id;
-
-                $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
-                $fullname = fullname($user);
-
-                $info = "{$grade_item->itemname}: $fullname";
-                add_to_log($course->id, 'grade', 'update', $url, $info);
-
                 $json_object->gradevalue = $finalvalue;
 
+                $old_grade_grade = new grade_grade(array('userid' => $userid, 'itemid' => $grade_item->id), true);
+
                 if ($grade_item->update_final_grade($userid, $finalgrade, 'gradebook', $feedback, FORMAT_MOODLE)) {
                     $json_object->result = 'success';
                     $json_object->message = false;
@@ -138,6 +132,14 @@ switch ($action) {
                     die();
                 }
 
+                $grade_grade = new grade_grade(array('userid' => $userid, 'itemid' => $grade_item->id), true);
+                if ($old_grade_grade->finalgrade != $grade_grade->finalgrade
+                    or empty($old_grade_grade->overridden) != empty($grade_grade->overridden)
+                ) {
+                    $grade_grade->load_grade_item();
+                    \core\event\user_graded::create_from_grade($grade_grade)->trigger();
+                }
+
                 // Get row data
                 $sql = "SELECT gg.id, gi.id AS itemid, gi.scaleid AS scale, gg.userid AS userid, finalgrade, gg.overridden AS overridden "
                      . "FROM {grade_grades} gg, {grade_items} gi WHERE "
index b3d812a..c73e1f0 100644 (file)
@@ -179,7 +179,7 @@ $reporthtml = $report->get_grade_table($displayaverages);
 
 // print submit button
 if ($USER->gradeediting[$course->id] && ($report->get_pref('showquickfeedback') || $report->get_pref('quickgrading'))) {
-    echo '<form action="index.php" enctype="application/x-www-form-urlencoded" method="post">'; // Enforce compatibility with our max_input_vars hack.
+    echo '<form action="index.php" enctype="application/x-www-form-urlencoded" method="post" id="gradereport_grader">'; // Enforce compatibility with our max_input_vars hack.
     echo '<div>';
     echo '<input type="hidden" value="'.s($courseid).'" name="id" />';
     echo '<input type="hidden" value="'.sesskey().'" name="sesskey" />';
index f4a357c..c91394a 100644 (file)
@@ -301,14 +301,19 @@ class grade_report_grader extends grade_report {
                         }
                     }
 
-                    $url = '/report/grader/index.php?id=' . $this->course->id;
-                    $fullname = fullname($this->users[$userid]);
-
-                    $info = "{$gradeitem->itemname}: $fullname";
-                    add_to_log($this->course->id, 'grade', 'update', $url, $info);
+                    $oldgradegrade = new grade_grade(array('userid' => $userid, 'itemid' => $gradeitem->id), true);
 
                     $gradeitem->update_final_grade($userid, $finalgrade, 'gradebook', $feedback, FORMAT_MOODLE);
 
+                    $gradegrade = new grade_grade(array('userid' => $userid, 'itemid' => $gradeitem->id), true);
+
+                    if ($oldgradegrade->finalgrade != $gradegrade->finalgrade
+                        or empty($oldgradegrade->overridden) != empty($gradegrade->overridden)
+                    ) {
+                        $gradegrade->grade_item = $gradeitem;
+                        \core\event\user_graded::create_from_grade($gradegrade)->trigger();
+                    }
+
                     // We can update feedback without reloading the grade item as it doesn't affect grade calculations
                     if ($datatype === 'feedback') {
                         $this->grades[$userid][$itemid]->feedback = $feedback;
@@ -1056,6 +1061,15 @@ class grade_report_grader extends grade_report {
         $PAGE->requires->js_init_call('M.gradereport_grader.init_report', $jsarguments, false, $module);
         $PAGE->requires->strings_for_js(array('addfeedback', 'feedback', 'grade'), 'grades');
         $PAGE->requires->strings_for_js(array('ajaxchoosescale', 'ajaxclicktoclose', 'ajaxerror', 'ajaxfailedupdate', 'ajaxfieldchanged'), 'gradereport_grader');
+        if (!$this->get_pref('enableajax') && $USER->gradeediting[$this->courseid]) {
+            $PAGE->requires->yui_module('moodle-core-formchangechecker',
+                    'M.core_formchangechecker.init',
+                    array(array(
+                        'formid' => 'gradereport_grader'
+                    ))
+            );
+            $PAGE->requires->string_for_js('changesmadereallygoaway', 'moodle');
+        }
 
         $rows = $this->get_right_range_row($rows);
         if ($displayaverages) {
index d845868..4f7dd52 100644 (file)
@@ -62,6 +62,8 @@ class core_group_lib_testcase extends advanced_testcase {
         $this->assertEquals($user->id, $event->relateduserid);
         $this->assertEquals(context_course::instance($course->id), $event->get_context());
         $this->assertEquals($group->id, $event->objectid);
+        $url = new moodle_url('/group/members.php', array('group' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
     }
 
     public function test_member_removed_event() {
@@ -89,6 +91,8 @@ class core_group_lib_testcase extends advanced_testcase {
         $this->assertEquals($user->id, $event->relateduserid);
         $this->assertEquals(context_course::instance($course->id), $event->get_context());
         $this->assertEquals($group->id, $event->objectid);
+        $url = new moodle_url('/group/members.php', array('group' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
     }
 
     public function test_group_created_event() {
@@ -108,6 +112,8 @@ class core_group_lib_testcase extends advanced_testcase {
         $this->assertSame('groups_group_created', $event->get_legacy_eventname());
         $this->assertEquals(context_course::instance($course->id), $event->get_context());
         $this->assertEquals($group->id, $event->objectid);
+        $url = new moodle_url('/group/index.php', array('id' => $event->courseid));
+        $this->assertEquals($url, $event->get_url());
     }
 
     public function test_grouping_created_event() {
@@ -129,6 +135,8 @@ class core_group_lib_testcase extends advanced_testcase {
 
         $this->assertEquals(context_course::instance($course->id), $event->get_context());
         $this->assertEquals($group->id, $event->objectid);
+        $url = new moodle_url('/group/groupings.php', array('id' => $event->courseid));
+        $this->assertEquals($url, $event->get_url());
     }
 
     public function test_group_updated_event() {
@@ -158,6 +166,8 @@ class core_group_lib_testcase extends advanced_testcase {
         $this->assertSame('groups_group_updated', $event->get_legacy_eventname());
         $this->assertEquals(context_course::instance($course->id), $event->get_context());
         $this->assertEquals($group->id, $event->objectid);
+        $url = new moodle_url('/group/group.php', array('id' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
     }
 
     public function test_grouping_updated_event() {
@@ -196,6 +206,8 @@ class core_group_lib_testcase extends advanced_testcase {
 
         $this->assertEquals(context_course::instance($course->id), $event->get_context());
         $this->assertEquals($grouping->id, $event->objectid);
+        $url = new moodle_url('/group/grouping.php', array('id' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
     }
 
     public function test_group_deleted_event() {
@@ -215,6 +227,8 @@ class core_group_lib_testcase extends advanced_testcase {
         $this->assertSame('groups_group_deleted', $event->get_legacy_eventname());
         $this->assertEquals(context_course::instance($course->id), $event->get_context());
         $this->assertEquals($group->id, $event->objectid);
+        $url = new moodle_url('/group/index.php', array('id' => $event->courseid));
+        $this->assertEquals($url, $event->get_url());
     }
 
     public function test_grouping_deleted_event() {
@@ -234,6 +248,8 @@ class core_group_lib_testcase extends advanced_testcase {
         $this->assertSame('groups_grouping_deleted', $event->get_legacy_eventname());
         $this->assertEquals(context_course::instance($course->id), $event->get_context());
         $this->assertEquals($group->id, $event->objectid);
+        $url = new moodle_url('/group/groupings.php', array('id' => $event->courseid));
+        $this->assertEquals($url, $event->get_url());
     }
 
     public function test_groups_delete_group_members() {
index 8221742..94e2c61 100644 (file)
@@ -190,7 +190,7 @@ $string['configdenyemailaddresses'] = 'To deny email addresses from particular d
 $string['configenableblogs'] = 'This switch provides all site users with their own blog.';
 $string['configenabledevicedetection'] = 'Enables detection of mobiles, smartphones, tablets or default devices (desktop PCs, laptops, etc) for the application of themes and other features.';
 $string['configdisableuserimages'] = 'Disable the ability for users to change user profile images.';
-$string['configdisplayloginfailures'] = 'This will display information to selected users about previous failed logins.';
+$string['configdisplayloginfailures'] = 'This will display information to users about previous failed logins.';
 $string['configdndallowtextandlinks'] = 'Enable or disable the dragging and dropping of text and links onto a course page, alongside the dragging and dropping of files. Note that the dragging of text into Firefox or between different browsers is unreliable and may result in no data being uploaded, or corrupted text being uploaded.';
 $string['configdoclang'] = 'This language will be used in links for the documentation pages.';
 $string['configdocroot'] = 'Defines the path to the Moodle Docs for providing context-specific documentation via \'Moodle Docs for this page\' links in the footer of each page. If the field is left blank, links will not be displayed.';
@@ -270,7 +270,7 @@ $string['configmypagelocked'] = 'This setting prevents the default page from bei
 $string['confignavcourselimit'] = 'Limits the number of courses shown to the user when they are either not logged in or are not enrolled in any courses.';
 $string['confignavshowallcourses'] = 'This setting determines whether users who are enrolled in courses can see Courses (listing all courses) in the navigation, in addition to My Courses (listing courses in which they are enrolled).';
 $string['confignavshowcategories'] = 'Show course categories in the navigation bar and navigation blocks. This does not occur with courses the user is currently enrolled in, they will still be listed under mycourses without categories.';
-$string['confignotifyloginfailures'] = 'If login failures have been recorded, email notifications can be sent out.  Who should see these notifications?';
+$string['confignotifyloginfailures'] = 'Send login failure notification messages to these selected users. This requires an internal logstore (eg Standard Logstore) to be enabled.';
 $string['confignotifyloginthreshold'] = 'If notifications about failed logins are active, how many failed login attempts by one user or one IP address is it worth notifying about?';
 $string['confignotloggedinroleid'] = 'Users who are not logged in to the site will be treated as if they have this role granted to them at the site context.  Guest is almost always what you want here, but you might want to create roles that are less or more restrictive.  Things like creating posts still require the user to log in properly.';
 $string['configopentogoogle'] = 'If you enable this setting, then Google will be allowed to enter your site as a Guest.  In addition, people coming in to your site via a Google search will automatically be logged in as a Guest.  Note that this only provides transparent access to courses that already allow guest access.';
@@ -428,7 +428,7 @@ $string['devicedetectregexvalue'] = 'Return value';
 $string['devicetype'] = 'Device type';
 $string['disableuserimages'] = 'Disable user profile images';
 $string['displayerrorswarning'] = 'Enabling the PHP setting <em>display_errors</em> is not recommended on production sites because some error messages may reveal sensitive information about your server.';
-$string['displayloginfailures'] = 'Display login failures to';
+$string['displayloginfailures'] = 'Display login failures';
 $string['dndallowtextandlinks'] = 'Drag and drop upload of text/links';
 $string['doclang'] = 'Language for docs';
 $string['docroot'] = 'Moodle Docs document root';
index a7d5d29..0904968 100644 (file)
@@ -87,7 +87,7 @@ $string['entryerrornotyours'] = 'This entry is not yours';
 $string['entrysaved'] = 'Your entry has been saved';
 $string['entrytitle'] = 'Entry title';
 $string['eventblogentriesviewed'] = 'Blog entries viewed';
-$string['eventblogassociationcreated'] = 'Blog association created';
+$string['eventblogassociationadded'] = 'Blog association created';
 $string['evententryadded'] = 'Blog entry added';
 $string['evententrydeleted'] = 'Blog entry deleted';
 $string['evententryupdated'] = 'Blog entry updated';
index 005f649..fc20033 100644 (file)
@@ -185,6 +185,7 @@ $string['errorupdatinggradecategoryaggregateoutcomes'] = 'Error updating the "In
 $string['errorupdatinggradecategoryaggregatesubcats'] = 'Error updating the "Aggregate including subcategories" setting of grade category ID {$a->id}';
 $string['errorupdatinggradecategoryaggregation'] = 'Error updating the aggregation type of grade category ID {$a->id}';
 $string['errorupdatinggradeitemaggregationcoef'] = 'Error updating the aggregation coefficient (weight or extra credit) of grade item ID {$a->id}';
+$string['eventusergraded'] = 'User grade edited in gradebook';
 $string['excluded'] = 'Excluded';
 $string['excluded_help'] = 'If ticked, the grade will not be included in any aggregation.';
 $string['expand'] = 'Expand category';
index 2e01aab..469ff00 100644 (file)
@@ -762,7 +762,6 @@ $string['explanation'] = 'Explanation';
 $string['extendenrol'] = 'Extend enrolment (individual)';
 $string['extendperiod'] = 'Extended period';
 $string['failedloginattempts'] = '{$a->attempts} failed logins since your last login';
-$string['failedloginattemptsall'] = '{$a->attempts} failed logins for {$a->accounts} accounts';
 $string['feedback'] = 'Feedback';
 $string['file'] = 'File';
 $string['fileexists'] = 'There is already a file called {$a}';
@@ -1210,6 +1209,8 @@ $string['nameforlink'] = 'What do you want to call this link?';
 $string['nameforpage'] = 'Name';
 $string['navigation'] = 'Navigation';
 $string['needed'] = 'Needed';
+$string['networkdropped'] = 'We have detected that your Internet connection is unreliable or has been interrupted.<br />
+Please be aware that changes may not be saved properly until your connection improves.';
 $string['never'] = 'Never';
 $string['neverdeletelogs'] = 'Never delete logs';
 $string['new'] = 'New';
@@ -1324,7 +1325,7 @@ $string['notice'] = 'Notice';
 $string['noticenewerbackup'] = 'This backup file has been created with Moodle {$a->backuprelease} ({$a->backupversion}) and it\'s newer than your currently installed Moodle {$a->serverrelease} ({$a->serverversion}). This could cause some inconsistencies because backwards compatibility of backup files cannot be guaranteed.';
 $string['notifications'] = 'Notifications';
 $string['notifyloginfailuresmessage'] = '{$a->time}, IP: {$a->ip}, User: {$a->info}';
-$string['notifyloginfailuresmessageend'] = 'You can view these logs at {$a}/report/log/index.php?id=1&amp;chooselog=1&amp;modid=site_errors.';
+$string['notifyloginfailuresmessageend'] = 'You can view these logs at {$a}';
 $string['notifyloginfailuresmessagestart'] = 'Here is a list of failed login attempts at {$a} since you were last notified';
 $string['notifyloginfailuressubject'] = '{$a} :: Failed logins notification';
 $string['notincluded'] = 'Not included';
index 84b8489..48aab57 100644 (file)
@@ -37,7 +37,7 @@ $string['deletenotes'] = 'Delete all notes';
 $string['editnote'] = 'Edit note';
 $string['enablenotes'] = 'Enable notes';
 $string['eventnotecreated'] = 'Note created';
-$string['eventnoteupdate'] = 'Note updated';
+$string['eventnoteupdated'] = 'Note updated';
 $string['eventnotedeleted'] = 'Note deleted';
 $string['eventnotesviewed'] = 'Notes viewed';
 $string['groupaddnewnote'] = 'Add a common note';
index b3cbdec..6033ac9 100644 (file)
@@ -144,6 +144,7 @@ $string['course:movesections'] = 'Move sections';
 $string['course:publish'] = 'Publish a course into hub';
 $string['course:request'] = 'Request new courses';
 $string['course:reset'] = 'Reset course';
+$string['course:reviewotherusers'] = 'Review other users';
 $string['course:sectionvisibility'] = 'Control section visibility';
 $string['course:setcurrentsection'] = 'Set current section';
 $string['course:update'] = 'Update course settings';
index 568111e..00a61ed 100644 (file)
@@ -85,6 +85,7 @@ $string['erroroptionalparamarray'] = 'The web service description parameter name
 $string['event_webservice_function_called'] = 'Web service function called';
 $string['event_webservice_login_failed'] = 'Web service login failed';
 $string['event_webservice_service_created'] = 'Web service service created';
+$string['event_webservice_service_deleted'] = 'Web service service deleted';
 $string['event_webservice_service_updated'] = 'Web service service updated';
 $string['event_webservice_service_user_added'] = 'Web service service user added';
 $string['event_webservice_service_user_removed'] = 'Web service service user removed';
index 9ce6ad2..ad0bb9f 100644 (file)
@@ -8142,6 +8142,11 @@ class admin_setting_configcolourpicker extends admin_setting {
      */
     protected $previewconfig = null;
 
+    /**
+     * Use default when empty.
+     */
+    protected $usedefaultwhenempty = true;
+
     /**
      *
      * @param string $name
@@ -8150,8 +8155,10 @@ class admin_setting_configcolourpicker extends admin_setting {
      * @param string $defaultsetting
      * @param array $previewconfig Array('selector'=>'.some .css .selector','style'=>'backgroundColor');
      */
-    public function __construct($name, $visiblename, $description, $defaultsetting, array $previewconfig=null) {
+    public function __construct($name, $visiblename, $description, $defaultsetting, array $previewconfig = null,
+            $usedefaultwhenempty = true) {
         $this->previewconfig = $previewconfig;
+        $this->usedefaultwhenempty = $usedefaultwhenempty;
         parent::__construct($name, $visiblename, $description, $defaultsetting);
     }
 
@@ -8242,7 +8249,11 @@ class admin_setting_configcolourpicker extends admin_setting {
         } else if (($data == 'transparent') || ($data == 'currentColor') || ($data == 'inherit')) {
             return $data;
         } else if (empty($data)) {
-            return $this->defaultsetting;
+            if ($this->usedefaultwhenempty){
+                return $this->defaultsetting;
+            } else {
+                return '';
+            }
         } else {
             return false;
         }
index 7b2c820..545b82f 100644 (file)
@@ -56,17 +56,14 @@ $PAGE->set_context(context::instance_by_id($contextid));
 // Setting layout to replicate blocks configuration for the page we edit.
 $PAGE->set_pagelayout($pagelayout);
 $PAGE->set_subpage($subpage);
+$PAGE->blocks->add_custom_regions_for_pagetype($pagetype);
 $pagetype = explode('-', $pagetype);
 switch ($pagetype[0]) {
     case 'my':
-        // My Home page needs to have 'content' block region set up.
         $PAGE->set_blocks_editing_capability('moodle/my:manageblocks');
-        $PAGE->blocks->add_region('content');
         break;
     case 'user':
         if ($pagelayout == 'mydashboard') {
-            // User profile pages also need the 'content' block region set up.
-            $PAGE->blocks->add_region('content');
             // If it's not the current user's profile, we need a different capability.
             if ($PAGE->context->contextlevel == CONTEXT_USER && $PAGE->context->instanceid != $USER->id) {
                 $PAGE->set_blocks_editing_capability('moodle/user:manageblocks');
index 639d2f0..92ac007 100644 (file)
@@ -647,6 +647,12 @@ function login_attempt_failed($user) {
         return;
     }
 
+    $count = get_user_preferences('login_failed_count', 0, $user);
+    $last = get_user_preferences('login_failed_last', 0, $user);
+    $sincescuccess = get_user_preferences('login_failed_count_since_success', $count, $user);
+    $sincescuccess = $sincescuccess + 1;
+    set_user_preference('login_failed_count_since_success', $sincescuccess, $user);
+
     if (empty($CFG->lockoutthreshold)) {
         // No threshold means no lockout.
         // Always unlock here, there might be some race conditions or leftovers when switching threshold.
@@ -654,9 +660,6 @@ function login_attempt_failed($user) {
         return;
     }
 
-    $count = get_user_preferences('login_failed_count', 0, $user);
-    $last = get_user_preferences('login_failed_last', 0, $user);
-
     if (!empty($CFG->lockoutwindow) and time() - $last > $CFG->lockoutwindow) {
         $count = 0;
     }
index d9d9246..cedf70e 100644 (file)
@@ -83,7 +83,12 @@ class behat_selectors {
      */
     protected static $moodleselectors = array(
         'dialogue' => <<<XPATH
-//div[contains(concat(' ', normalize-space(@class), ' '), ' moodle-dialogue ')]/descendant::h1[normalize-space(.) = %locator%]/ancestor::div[contains(concat(' ', normalize-space(@class), ' '), ' moodle-dialogue ')] | //div[contains(concat(' ', normalize-space(@class), ' '), ' yui-dialog ')]/descendant::div[@class='hd'][normalize-space(.) = %locator%]/parent::div
+//div[contains(concat(' ', normalize-space(@class), ' '), ' moodle-dialogue ') and
+    normalize-space(descendant::div[
+        contains(concat(' ', normalize-space(@class), ' '), ' moodle-dialogue-hd ')
+        ]) = %locator%] |
+//div[contains(concat(' ', normalize-space(@class), ' '), ' yui-dialog ') and
+    normalize-space(descendant::div[@class='hd']) = %locator%]
 XPATH
         , 'block' => <<<XPATH
 //div[contains(concat(' ', normalize-space(@class), ' '), concat(' ', %locator%, ' '))] | //div[contains(concat(' ', normalize-space(@class), ' '), ' block ')]/descendant::h2[normalize-space(.) = %locator%]/ancestor::div[contains(concat(' ', normalize-space(@class), ' '), ' block ')]
index 07f31a5..fbf37ac 100644 (file)
@@ -394,12 +394,30 @@ class block_manager {
     /**
      * Add a region to a page
      *
-     * @param string $region add a named region where blocks may appear on the
-     * current page. This is an internal name, like 'side-pre', not a string to
-     * display in the UI.
+     * @param string $region add a named region where blocks may appear on the current page.
+     *      This is an internal name, like 'side-pre', not a string to display in the UI.
+     * @param bool $custom True if this is a custom block region, being added by the page rather than the theme layout.
      */
-    public function add_region($region) {
+    public function add_region($region, $custom = true) {
+        global $SESSION;
         $this->check_not_yet_loaded();
+        if ($custom) {
+            if (array_key_exists($region, $this->regions)) {
+                // This here is EXACTLY why we should not be adding block regions into a page. It should
+                // ALWAYS be done in a theme layout.
+                debugging('A custom region conflicts with a block region in the theme.', DEBUG_DEVELOPER);
+            }
+            // We need to register this custom region against the page type being used.
+            // This allows us to check, when performing block actions, that unrecognised regions can be worked with.
+            $type = $this->page->pagetype;
+            if (!isset($SESSION->custom_block_regions)) {
+                $SESSION->custom_block_regions = array($type => array($region));
+            } else if (!isset($SESSION->custom_block_regions[$type])) {
+                $SESSION->custom_block_regions[$type] = array($region);
+            } else if (!in_array($region, $SESSION->custom_block_regions[$type])) {
+                $SESSION->custom_block_regions[$type][] = $region;
+            }
+        }
         $this->regions[$region] = 1;
     }
 
@@ -409,9 +427,23 @@ class block_manager {
      *
      * @param array $regions this utility method calls add_region for each array element.
      */
-    public function add_regions($regions) {
+    public function add_regions($regions, $custom = true) {
         foreach ($regions as $region) {
-            $this->add_region($region);
+            $this->add_region($region, $custom);
+        }
+    }
+
+    /**
+     * Finds custom block regions associated with a page type and registers them with this block manager.
+     *
+     * @param string $pagetype
+     */
+    public function add_custom_regions_for_pagetype($pagetype) {
+        global $SESSION;
+        if (isset($SESSION->custom_block_regions[$pagetype])) {
+            foreach ($SESSION->custom_block_regions[$pagetype] as $customregion) {
+                $this->add_region($customregion, false);
+            }
         }
     }
 
@@ -746,7 +778,7 @@ class block_manager {
      * @param string $subpagepattern optional. Passed to @see add_block()
      */
     public function add_blocks($blocks, $pagetypepattern = NULL, $subpagepattern = NULL, $showinsubcontexts=false, $weight=0) {
-        $this->add_regions(array_keys($blocks));
+        $this->add_regions(array_keys($blocks), false);
         foreach ($blocks as $region => $regionblocks) {
             $weight = 0;
             foreach ($regionblocks as $blockname) {
index 8b632e3..6b4bf6e 100644 (file)
@@ -59,6 +59,7 @@ abstract class assessable_submitted extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if ($this->contextlevel != CONTEXT_MODULE) {
             throw new \coding_exception('Context passed must be module context.');
         }
index b284f99..972195c 100644 (file)
@@ -66,6 +66,7 @@ abstract class assessable_uploaded extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if ($this->contextlevel != CONTEXT_MODULE) {
             throw new \coding_exception('Context passed must be module context.');
         } else if (!isset($this->other['pathnamehashes']) || !is_array($this->other['pathnamehashes'])) {
index 58329d9..8d9cdf6 100644 (file)
@@ -345,6 +345,7 @@ abstract class base implements \IteratorAggregate {
             return false;
         }
 
+        $event->init(); // Init method of events could be setting custom properties.
         $event->restored = true;
         $event->triggered = true;
         $event->dispatched = true;
@@ -679,9 +680,17 @@ abstract class base implements \IteratorAggregate {
             throw new \coding_exception('It is not possible to add snapshots after triggering of events');
         }
 
+        // Special case for course module, allow instance of cm_info to be passed instead of stdClass.
+        if ($tablename === 'course_modules' && $record instanceof \cm_info) {
+            $record = $record->get_course_module_record();
+        }
+
         // NOTE: this might use some kind of MUC cache,
         //       hopefully we will not run out of memory here...
         if ($CFG->debugdeveloper) {
+            if (!($record instanceof \stdClass)) {
+                debugging('Argument $record must be an instance of stdClass.', DEBUG_DEVELOPER);
+            }
             if (!$DB->get_manager()->table_exists($tablename)) {
                 debugging("Invalid table name '$tablename' specified, database table does not exist.", DEBUG_DEVELOPER);
             } else {
index 0fa689b..28c03c1 100644 (file)
@@ -78,7 +78,7 @@ class blog_association_created extends \core\event\base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('/blog/index.php', array('entryid' => $this->objectid, 'userid' => $this->userid));
+        return new \moodle_url('/blog/index.php', array('entryid' => $this->other['blogid']));
     }
 
     /**
@@ -103,6 +103,7 @@ class blog_association_created extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (empty($this->other['associatetype']) || ($this->other['associatetype'] !== 'course'
                 && $this->other['associatetype'] !== 'coursemodule')) {
             throw new \coding_exception('Invalid associatetype in event blog_association_created.');
index 4df80ac..1b3f6b1 100644 (file)
@@ -94,7 +94,7 @@ class blog_entry_created extends \core\event\base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('/blog/index.php', array('entryid' => $this->objectid, 'userid' => $this->userid));
+        return new \moodle_url('/blog/index.php', array('entryid' => $this->objectid));
     }
 
     /**
index 1ef826f..ff9686b 100644 (file)
@@ -92,7 +92,7 @@ class blog_entry_updated extends base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('/blog/index.php', array('entryid' => $this->objectid, 'userid' => $this->userid));
+        return new \moodle_url('/blog/index.php', array('entryid' => $this->objectid));
     }
 
     /**
index 0844565..ccc2ccd 100644 (file)
@@ -51,7 +51,7 @@ class cohort_deleted extends base {
      * @return string
      */
     public static function get_name() {
-        return get_string('event_core_deleted', 'core_cohort');
+        return get_string('event_cohort_deleted', 'core_cohort');
     }
 
     /**
index 420c4b8..2e18f69 100644 (file)
@@ -94,6 +94,7 @@ abstract class comment_created extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->other['itemid'])) {
             throw new \coding_exception('The itemid needs to be set in $other');
         }
index 57084fb..be04218 100644 (file)
@@ -94,6 +94,7 @@ abstract class comment_deleted extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->other['itemid'])) {
             throw new \coding_exception('The itemid needs to be set in $other');
         }
index d470e37..797ad37 100644 (file)
@@ -113,11 +113,10 @@ abstract class content_viewed extends base {
      * @return void
      */
     protected function validate_data() {
-        if (debugging('', DEBUG_DEVELOPER)) {
-            // Make sure this class is never used without a content identifier.
-            if (empty($this->other['content'])) {
-                throw new \coding_exception('content_viewed event must define content identifier.');
-            }
+        parent::validate_data();
+        // Make sure this class is never used without a content identifier.
+        if (empty($this->other['content'])) {
+            throw new \coding_exception('content_viewed event must define content identifier.');
         }
     }
 }
index b049a8a..c131f27 100644 (file)
@@ -45,6 +45,15 @@ class course_category_created extends base {
         return get_string('eventcoursecategorycreated');
     }
 
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/course/management.php', array('categoryid' => $this->objectid));
+    }
+
     /**
      * Returns non-localised description of what happened.
      *
index 1a0055f..2cb7739 100644 (file)
@@ -48,6 +48,15 @@ class course_category_updated extends base {
         return get_string('eventcoursecategoryupdated');
     }
 
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/course/editcategory.php', array('id' => $this->objectid));
+    }
+
     /**
      * Returns non-localised description of what happened.
      *
index 53897cb..6233bfb 100644 (file)
@@ -87,6 +87,7 @@ class course_module_completion_updated extends base {
      * @throws \coding_exception in case of a problem.
      */
     protected function validate_data() {
+        parent::validate_data();
         // Make sure the context level is set to module.
         if ($this->contextlevel !== CONTEXT_MODULE) {
             throw new \coding_exception('Context passed must be module context.');
index e4b7b3d..474caee 100644 (file)
@@ -120,6 +120,7 @@ class course_module_created extends base {
      * Throw \coding_exception notice in case of any problems.
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->other['modulename'])) {
             throw new \coding_exception("Field other['modulename'] cannot be empty");
         }
index 4c58a3d..82d01d1 100644 (file)
@@ -110,6 +110,7 @@ class course_module_deleted extends base {
      * Throw \coding_exception notice in case of any problems.
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->other['modulename'])) {
             throw new \coding_exception("Field other['modulename'] cannot be empty");
         }
index f33bde5..87c222a 100644 (file)
@@ -42,8 +42,8 @@ defined('MOODLE_INTERNAL') || die();
  */
 abstract class course_module_instance_list_viewed extends base{
 
-    /** @var string private var to store mod name */
-    private $modname;
+    /** @var string protected var to store mod name */
+    protected $modname;
 
     /**
      * Init method.
@@ -105,6 +105,7 @@ abstract class course_module_instance_list_viewed extends base{
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if ($this->contextlevel != CONTEXT_COURSE) {
             throw new \coding_exception('Context passed must be course context.');
         }
index 9ac5594..6ae6752 100644 (file)
@@ -120,6 +120,7 @@ class course_module_updated extends base {
      * Throw \coding_exception notice in case of any problems.
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->other['modulename'])) {
             throw new \coding_exception("Field other['modulename'] cannot be empty");
         }
index a6461ab..9f9b654 100644 (file)
@@ -92,6 +92,7 @@ abstract class course_module_viewed extends base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         // Make sure this class is never used without proper object details.
         if (empty($this->objectid) || empty($this->objecttable)) {
             throw new \coding_exception('course_module_viewed event must define objectid and object table.');
index 1b8fdb8..02aa67c 100644 (file)
@@ -84,6 +84,7 @@ class course_reset_ended extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->other['reset_options'])) {
            throw new \coding_exception('The key reset_options must be set in $other.');
         }
index 8a2be3c..91531ef 100644 (file)
@@ -84,6 +84,7 @@ class course_reset_started extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->other['reset_options'])) {
            throw new \coding_exception('The key reset_options must be set in $other.');
         }
index 1ca5407..a7d8bc3 100644 (file)
@@ -70,7 +70,7 @@ class course_updated extends base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('/course/view.php', array('id' => $this->objectid));
+        return new \moodle_url('/course/edit.php', array('id' => $this->objectid));
     }
 
     /**
index 40affbd..d0220da 100644 (file)
@@ -69,6 +69,7 @@ class email_failed extends base {
      * @throws \coding_exception
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->other['subject'])) {
             throw new \coding_exception('The subject needs to be set in $other');
         }
index 2c1cf28..a248f91 100644 (file)
@@ -53,7 +53,7 @@ class group_member_added extends \core\event\base {
     /**
      * Legacy event data if get_legacy_eventname() is not empty.
      *
-     * @return stdClass
+     * @return \stdClass
      */
     protected function get_legacy_eventdata() {
         $eventdata = new \stdClass();
@@ -88,7 +88,7 @@ class group_member_added extends \core\event\base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('/group/index.php', array('id' => $this->courseid));
+        return new \moodle_url('/group/members.php', array('group' => $this->objectid));
     }
 
     /**
@@ -112,6 +112,7 @@ class group_member_added extends \core\event\base {
         if (!isset($this->other['component']) || !isset($this->other['itemid'])) {
             throw new \coding_exception('The component and itemid need to be set in $other, even if empty.');
         }
+        parent::validate_data();
     }
 
 }
index cc5fd26..8557359 100644 (file)
@@ -46,7 +46,7 @@ class group_member_removed extends \core\event\base {
     /**
      * Legacy event data if get_legacy_eventname() is not empty.
      *
-     * @return stdClass
+     * @return \stdClass
      */
     protected function get_legacy_eventdata() {
         $eventdata = new \stdClass();
@@ -79,7 +79,7 @@ class group_member_removed extends \core\event\base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('/group/index.php', array('id' => $this->courseid));
+        return new \moodle_url('/group/members.php', array('group' => $this->objectid));
     }
 
     /**
index 8631ca4..4b03799 100644 (file)
@@ -76,7 +76,7 @@ class group_updated extends \core\event\base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('/group/index.php', array('id' => $this->courseid));
+        return new \moodle_url('/group/group.php', array('id' => $this->objectid));
     }
 
     /**
index 2f79a6a..b4ecb1a 100644 (file)
@@ -76,7 +76,7 @@ class grouping_created extends \core\event\base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('/group/groupings/index.php', array('id' => $this->courseid));
+        return new \moodle_url('/group/groupings.php', array('id' => $this->courseid));
     }
 
     /**
index 0176cc9..3603be9 100644 (file)
@@ -83,7 +83,7 @@ class grouping_deleted extends \core\event\base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('/group/groupings/index.php', array('id' => $this->courseid));
+        return new \moodle_url('/group/groupings.php', array('id' => $this->courseid));
     }
 
     /**
index 5e81352..4e351dd 100644 (file)
@@ -76,7 +76,7 @@ class grouping_updated extends \core\event\base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('/group/groupings/index.php', array('id' => $this->courseid));
+        return new \moodle_url('/group/grouping.php', array('id' => $this->objectid));
     }
 
     /**
index 7281048..6ea7dd8 100644 (file)
@@ -51,7 +51,7 @@ class message_contact_added extends base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('message/index.php', array('user1' => $this->relateduserid, 'user2' => $this->userid));
+        return new \moodle_url('/message/index.php', array('user1' => $this->userid, 'user2' => $this->relateduserid));
     }
 
     /**
index 7ea6bd4..3d79fbd 100644 (file)
@@ -51,7 +51,7 @@ class message_contact_blocked extends base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('message/index.php', array('user1' => $this->relateduserid, 'user2' => $this->userid));
+        return new \moodle_url('/message/index.php', array('user1' => $this->userid, 'user2' => $this->relateduserid));
     }
 
     /**
index 40761f6..78c0d04 100644 (file)
@@ -51,7 +51,7 @@ class message_contact_removed extends base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('message/index.php', array('user1' => $this->relateduserid, 'user2' => $this->userid));
+        return new \moodle_url('/message/index.php', array('user1' => $this->userid, 'user2' => $this->relateduserid));
     }
 
     /**
index cca7464..2475511 100644 (file)
@@ -51,7 +51,7 @@ class message_contact_unblocked extends base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('message/index.php', array('user1' => $this->relateduserid, 'user2' => $this->userid));
+        return new \moodle_url('/message/index.php', array('user1' => $this->userid, 'user2' => $this->relateduserid));
     }
 
     /**
index 6ba652e..664f2f2 100644 (file)
@@ -57,7 +57,7 @@ class message_read extends base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('message/index.php', array('user1' => $this->relateduserid, 'user2' => $this->userid));
+        return new \moodle_url('/message/index.php', array('user1' => $this->userid, 'user2' => $this->relateduserid));
     }
 
     /**
index 453f74a..3aad156 100644 (file)
@@ -56,7 +56,7 @@ class message_sent extends base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('message/index.php', array('user1' => $this->relateduserid, 'user2' => $this->userid));
+        return new \moodle_url('/message/index.php', array('user1' => $this->userid, 'user2' => $this->relateduserid));
     }
 
     /**
index 3bf5979..cf9cb13 100644 (file)
@@ -51,7 +51,7 @@ class mnet_access_control_created extends base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('admin/mnet/access_control.php');
+        return new \moodle_url('/admin/mnet/access_control.php');
     }
 
     /**
@@ -60,11 +60,8 @@ class mnet_access_control_created extends base {
      * @return string
      */
     public function get_description() {
-        $mnetaccesscontrol = $this->get_record_snapshot('mnet_sso_access_control', $this->objectid);
-        $mnethost = $this->get_record_snapshot('mnet_host', $mnetaccesscontrol->mnet_host_id);
-
-        return 'Access control created for the user with the username \'' . $mnetaccesscontrol->username . '\' belonging
-            to the mnet host \'' . $mnethost->name . '\'';
+        return 'Access control created for the user with the username \'' . $this->other['username'] . '\' belonging
+            to the mnet host \'' . $this->other['hostname'] . '\'';
     }
 
     /**
@@ -73,11 +70,29 @@ class mnet_access_control_created extends base {
      * @return array
      */
     protected function get_legacy_logdata() {
-        $mnetaccesscontrol = $this->get_record_snapshot('mnet_sso_access_control', $this->objectid);
-        $mnethost = $this->get_record_snapshot('mnet_host', $mnetaccesscontrol->mnet_host_id);
+        return array(SITEID, 'admin/mnet', 'add', 'admin/mnet/access_control.php', 'SSO ACL: ' . $this->other['accessctrl'] .
+            ' user \'' . $this->other['username'] . '\' from ' . $this->other['hostname']);
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['username'])) {
+            throw new \coding_exception('The \'username\' must be set in other.');
+        }
+
+        if (!isset($this->other['hostname'])) {
+            throw new \coding_exception('The \'hostname\' must be set in other.');
+        }
 
-        return array(SITEID, 'admin/mnet', 'add', 'admin/mnet/access_control.php', 'SSO ACL: ' .
-            $mnetaccesscontrol->accessctrl . ' user \'' . $mnetaccesscontrol->username . '\' from ' .
-            $mnethost->name);
+        if (!isset($this->other['accessctrl'])) {
+            throw new \coding_exception('The \'accessctrl\' must be set in other.');
+        }
     }
 }
index 88e309d..79ac0ec 100644 (file)
@@ -51,7 +51,7 @@ class mnet_access_control_updated extends base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('admin/mnet/access_control.php');
+        return new \moodle_url('/admin/mnet/access_control.php');
     }
 
     /**
@@ -60,11 +60,8 @@ class mnet_access_control_updated extends base {
      * @return string
      */
     public function get_description() {
-        $mnetaccesscontrol = $this->get_record_snapshot('mnet_sso_access_control', $this->objectid);
-        $mnethost = $this->get_record_snapshot('mnet_host', $mnetaccesscontrol->mnet_host_id);
-
-        return 'Access control created for the user with the username \'' . $mnetaccesscontrol->username . '\' belonging
-            to the mnet host \'' . $mnethost->name . '\'';
+        return 'Access control created for the user with the username \'' . $this->other['username'] . '\' belonging
+            to the mnet host \'' . $this->other['hostname'] . '\'';
     }
 
     /**
@@ -73,11 +70,29 @@ class mnet_access_control_updated extends base {
      * @return array
      */
     protected function get_legacy_logdata() {
-        $mnetaccesscontrol = $this->get_record_snapshot('mnet_sso_access_control', $this->objectid);
-        $mnethost = $this->get_record_snapshot('mnet_host', $mnetaccesscontrol->mnet_host_id);
+        return array(SITEID, 'admin/mnet', 'update', 'admin/mnet/access_control.php', 'SSO ACL: ' . $this->other['accessctrl'] .
+            ' user \'' . $this->other['username'] . '\' from ' . $this->other['hostname']);
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['username'])) {
+            throw new \coding_exception('The \'username\' must be set in other.');
+        }
+
+        if (!isset($this->other['hostname'])) {
+            throw new \coding_exception('The \'hostname\' must be set in other.');
+        }
 
-        return array(SITEID, 'admin/mnet', 'update', 'admin/mnet/access_control.php', 'SSO ACL: ' .
-            $mnetaccesscontrol->accessctrl . ' user \'' . $mnetaccesscontrol->username . '\' from ' .
-            $mnethost->name);
+        if (!isset($this->other['accessctrl'])) {
+            throw new \coding_exception('The \'accessctrl\' must be set in other.');
+        }
     }
 }
index 9431cd3..9347479 100644 (file)
@@ -64,7 +64,7 @@ class role_capabilities_updated extends base {
      */
     public function get_url() {
         if ($this->contextlevel === CONTEXT_SYSTEM) {
-            return new \moodle_url('admin/roles/define.php', array('action' => 'view', 'roleid' => $this->objectid));
+            return new \moodle_url('/admin/roles/define.php', array('action' => 'view', 'roleid' => $this->objectid));
         } else {
             return new \moodle_url('/admin/roles/override.php', array('contextid' => $this->contextid,
                 'roleid' => $this->objectid));
index 1637822..37d9edb 100644 (file)
@@ -31,11 +31,11 @@ defined('MOODLE_INTERNAL') || die();
  * @property-read array $other {
  *      Extra information about event.
  *
- *      @type string username name of user.
- *      @type string email user email.
- *      @type string idnumber user idnumber.
- *      @type string picture user picture.
- *      @type int mnethostid mnet host id.
+ *      - string username: name of user.
+ *      - string email: user email.
+ *      - string idnumber: user idnumber.
+ *      - string picture: user picture.
+ *      - int mnethostid: mnet host id.
  * }
  *
  * @package    core
@@ -68,8 +68,7 @@ class user_deleted extends base {
      * @return string
      */
     public function get_description() {
-        $user = $this->get_record_snapshot('user', $this->data['objectid']);
-        return 'User profile deleted for userid ' . $user->id;
+        return 'User profile deleted for the user with the id ' . $this->objectid;
     }
 
     /**
@@ -87,12 +86,13 @@ class user_deleted extends base {
      * @return \stdClass user data.
      */
     protected function get_legacy_eventdata() {
-        $user = $this->get_record_snapshot('user', $this->data['objectid']);
+        $user = $this->get_record_snapshot('user', $this->objectid);
         $user->deleted = 0;
-        $user->username = $this->data['other']['username'];
-        $user->email = $this->data['other']['email'];
-        $user->idnumber = $this->data['other']['idnumber'];
-        $user->picture = $this->data['other']['picture'];
+        $user->username = $this->other['username'];
+        $user->email = $this->other['email'];
+        $user->idnumber = $this->other['idnumber'];
+        $user->picture = $this->other['picture'];
+
         return $user;
     }
 
@@ -102,8 +102,8 @@ class user_deleted extends base {
      * @return array
      */
     protected function get_legacy_logdata() {
-        $user = $this->get_record_snapshot('user', $this->data['objectid']);
-        return array(SITEID, 'user', 'delete', "view.php?id=".$user->id, $user->firstname.' '.$user->lastname);
+        $user = $this->get_record_snapshot('user', $this->objectid);
+        return array(SITEID, 'user', 'delete', 'view.php?id=' . $user->id, $user->firstname . ' ' . $user->lastname);
     }
 
     /**
@@ -113,29 +113,26 @@ class user_deleted extends base {
      * @return void
      */
     protected function validate_data() {
-        global $CFG;
+        parent::validate_data();
 
-        if ($CFG->debugdeveloper) {
-            parent::validate_data();
-            if (!isset($this->other['username'])) {
-                throw new \coding_exception('username must be set in $other.');
-            }
+        if (!isset($this->other['username'])) {
+            throw new \coding_exception('username must be set in $other.');
+        }
 
-            if (!isset($this->other['email'])) {
-                throw new \coding_exception('email must be set in $other.');
-            }
+        if (!isset($this->other['email'])) {
+            throw new \coding_exception('email must be set in $other.');
+        }
 
-            if (!isset($this->other['idnumber'])) {
-                throw new \coding_exception('idnumber must be set in $other.');
-            }
+        if (!isset($this->other['idnumber'])) {
+            throw new \coding_exception('idnumber must be set in $other.');
+        }
 
-            if (!isset($this->other['picture'])) {
-                throw new \coding_exception('picture must be set in $other.');
-            }
+        if (!isset($this->other['picture'])) {
+            throw new \coding_exception('picture must be set in $other.');
+        }
 
-            if (!isset($this->other['mnethostid'])) {
-                throw new \coding_exception('mnethostid must be set in $other.');
-            }
+        if (!isset($this->other['mnethostid'])) {
+            throw new \coding_exception('mnethostid must be set in $other.');
         }
     }
 }
index 5ae4c03..5bfad22 100644 (file)
@@ -73,7 +73,7 @@ class user_enrolment_updated extends base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('/enrol/users.php', array('id' => $this->courseid));
+        return new \moodle_url('/enrol/editenrolment.php', array('ue' => $this->objectid));
     }
 
     /**
diff --git a/lib/classes/event/user_graded.php b/lib/classes/event/user_graded.php
new file mode 100644 (file)
index 0000000..ae77611
--- /dev/null
@@ -0,0 +1,135 @@
+<?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/>.
+
+/**
+ * Grade edited event.
+ *
+ * @package    core_grades
+ * @copyright  2014 Petr Skoda
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Event triggered after teacher edits manual grade or
+ * overrides activity/aggregated grade.
+ *
+ * Note: use grade_grades_history table if you need to know
+ *       the history of grades.
+ *
+ * @property-read array $other Extra information about the event.
+ *     -int itemid: grade item id.
+ *     -bool overridden: Is this grade override?
+ *     -float finalgrade: the final grade value.
+ *
+ * @package    core_grades
+ * @copyright  2013 Petr Skoda
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class user_graded extends \core\event\base {
+    /** @var \grade_grade $grade */
+    protected $grade;
+
+    /**
+     * Utility method to create new event.
+     *
+     * @param \grade_grade $grade
+     * @return user_graded
+     */
+    public static function create_from_grade(\grade_grade $grade) {
+        $event = self::create(array(
+            'context'       => \context_course::instance($grade->grade_item->courseid),
+            'objectid'      => $grade->id,
+            'relateduserid' => $grade->userid,
+            'other'         => array(
+                'itemid'     => $grade->itemid,
+                'overridden' => !empty($grade->overridden),
+                'finalgrade' => $grade->finalgrade),
+        ));
+        $event->grade = $grade;
+        return $event;
+    }
+
+    /**
+     * Get grade object.
+     *
+     * @return \grade_grade
+     */
+    public function get_grade() {
+        if ($this->is_restored()) {
+            throw new \coding_exception('get_grade() is intended for event observers only');
+        }
+        return $this->grade;
+    }
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['crud'] = 'u';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+        $this->data['objecttable'] = 'grade_grades';
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventusergraded', 'core_grades');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "User {$this->userid} edited grade of user {$this->objectid} for grade item " . $this->other['itemid'];
+    }
+
+    /**
+     * Get URL related to the action
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/grade/edit/tree/grade.php', array(
+            'courseid' => $this->courseid,
+            'itemid'   => $this->other['itemid'],
+            'userid'   => $this->relateduserid,
+        ));
+    }
+
+    /**
+     * Return legacy log info.
+     *
+     * @return null|array of parameters to be passed to legacy add_to_log() function.
+     */
+    public function get_legacy_logdata() {
+        $user = $this->get_record_snapshot('user', $this->relateduserid);
+        $fullname = fullname($user);
+        $info = $this->grade->grade_item->itemname . ': ' . $fullname;
+        $url = '/report/grader/index.php?id=' . $this->courseid;
+
+        return array($this->courseid, 'grade', 'update', $url, $info);
+    }
+}
index 198a137..ec94f35 100644 (file)
@@ -106,11 +106,11 @@ class user_loggedin extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->data['objectid'])) {
             throw new \coding_exception("objectid has to be specified.");
         } else if (!isset($this->data['other']['username'])) {
             throw new \coding_exception("other['username'] has to be specified.");
         }
     }
-
 }
index 6533a10..7ea66cf 100644 (file)
@@ -99,6 +99,7 @@ class user_login_failed extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->other['reason'])) {
             throw new \coding_exception("other['reason'] has to be specified.");
         } else if (!isset($this->other['username'])) {
index 7685abd..a2f3e8c 100644 (file)
@@ -99,6 +99,7 @@ class webservice_function_called extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->other['function'])) {
            throw new \coding_exception('The key \'function\' needs to be set in $other.');
         }
index 78560d3..7d82a95 100644 (file)
@@ -113,6 +113,7 @@ class webservice_login_failed extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->other['reason'])) {
            throw new \coding_exception('The key \'reason\' needs to be set in $other.');
         } else if (!isset($this->other['method'])) {
index 9740329..121f7a3 100644 (file)
@@ -70,7 +70,7 @@ class webservice_service_updated extends \core\event\base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('/admin/settings.php', array('section' => 'externalservices'));
+        return new \moodle_url('/admin/webservice/service.php', array('id' => $this->objectid));
     }
 
     /**
index c9c4d6d..4bf7c94 100644 (file)
@@ -90,6 +90,7 @@ class webservice_service_user_added extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->relateduserid)) {
             throw new \coding_exception('The relateduserid must be set.');
         }
index c3770a8..7114b47 100644 (file)
@@ -90,6 +90,7 @@ class webservice_service_user_removed extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->relateduserid)) {
             throw new \coding_exception('The relateduserid must be set.');
         }
index cb0c653..db241df 100644 (file)
@@ -98,6 +98,7 @@ class webservice_token_created extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->relateduserid)) {
            throw new \coding_exception('The property \'relateduserid\' must be set.');
         }
diff --git a/lib/classes/grades_external.php b/lib/classes/grades_external.php
new file mode 100644 (file)
index 0000000..5fcee40
--- /dev/null
@@ -0,0 +1,511 @@
+<?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/>.
+
+/**
+ * Core grades external functions
+ *
+ * @package    core_grades
+ * @copyright  2012 Andrew Davis
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.7
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once("$CFG->libdir/externallib.php");
+require_once("$CFG->libdir/gradelib.php");
+
+/**
+ * core grades functions
+ */
+class core_grades_external extends external_api {
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 2.7
+     */
+    public static function get_grades_parameters() {
+        return new external_function_parameters(
+            array(
+                'courseid' => new external_value(PARAM_INT, 'id of course'),
+                'component' => new external_value(
+                    PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz', VALUE_DEFAULT, ''),
+                'activityid' => new external_value(PARAM_INT, 'The activity ID', VALUE_DEFAULT, null),
+                'userids' => new external_multiple_structure(
+                    new external_value(PARAM_INT, 'user ID'),
+                    'An array of user IDs, leave empty to just retrieve grade item information', VALUE_DEFAULT, array()
+                )
+            )
+        );
+    }
+
+    /**
+     * Retrieve grade items and, optionally, student grades
+     *
+     * @param  int $courseid        Course id
+     * @param  string $component    Component name
+     * @param  int $activityid      Activity id
+     * @param  array  $userids      Array of user ids
+     * @return array                Array of grades
+     * @since Moodle 2.7
+     */
+    public static function get_grades($courseid, $component = null, $activityid = null, $userids = array()) {
+        global $CFG, $USER, $DB;
+
+        $params = self::validate_parameters(self::get_grades_parameters(),
+            array('courseid' => $courseid, 'component' => $component, 'activityid' => $activityid, 'userids' => $userids));
+
+        $coursecontext = context_course::instance($params['courseid']);
+
+        try {
+            self::validate_context($coursecontext);
+        } catch (Exception $e) {
+            $exceptionparam = new stdClass();
+            $exceptionparam->message = $e->getMessage();
+            $exceptionparam->courseid = $params['courseid'];
+            throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
+        }
+
+        $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST);
+
+        $access = false;
+        if (has_capability('moodle/grade:viewall', $coursecontext)) {
+            // Can view all user's grades in this course.
+            $access = true;
+
+        } else if ($course->showgrades && count($params['userids']) == 1) {
+            // Course showgrades == students/parents can access grades.
+
+            if ($params['userids'][0] == $USER->id and has_capability('moodle/grade:view', $coursecontext)) {
+                // Student can view their own grades in this course.
+                $access = true;
+
+            } else if (has_capability('moodle/grade:viewall', context_user::instance($params['userids'][0]))) {
+                // User can view the grades of this user. Parent most probably.
+                $access = true;
+            }
+        }
+
+        if (!$access) {
+            throw new moodle_exception('nopermissiontoviewgrades', 'error');
+        }
+
+        $itemtype = null;
+        $itemmodule = null;
+        if (!empty($params['component'])) {
+            list($itemtype, $itemmodule) = normalize_component($params['component']);
+        }
+
+        $cm = null;
+        if (!empty($itemmodule) && !empty($activityid)) {
+            if (! $cm = get_coursemodule_from_id($itemmodule, $activityid)) {
+                throw new moodle_exception('invalidcoursemodule');
+            }
+        }
+
+        $cminstanceid = null;
+        if (!empty($cm)) {
+            $cminstanceid = $cm->instance;
+        }
+        $grades = grade_get_grades($params['courseid'], $itemtype, $itemmodule, $cminstanceid, $params['userids']);
+
+        $acitivityinstances = null;
+        if (empty($cm)) {
+            // If we're dealing with multiple activites load all the module info.
+            $modinfo = get_fast_modinfo($params['courseid']);
+            $acitivityinstances = $modinfo->get_instances();
+        }
+
+        foreach ($grades->items as $gradeitem) {
+            if (!empty($cm)) {
+                // If they only requested one activity we will already have the cm.
+                $modulecm = $cm;
+            } else if (!empty($gradeitem->itemmodule)) {
+                $modulecm = $acitivityinstances[$gradeitem->itemmodule][$gradeitem->iteminstance];
+            } else {
+                // Course grade item.
+                continue;
+            }
+
+            // Make student feedback ready for output.
+            foreach ($gradeitem->grades as $studentgrade) {
+                if (!empty($studentgrade->feedback)) {
+                    list($studentgrade->feedback, $categoryinfo->feedbackformat) =
+                        external_format_text($studentgrade->feedback, $studentgrade->feedbackformat,
+                        $modulecm->id, $params['component'], 'feedback', null);
+                }
+            }
+        }
+
+        // Convert from objects to arrays so all web service clients are supported.
+        // While we're doing that we also remove grades the current user can't see due to hiding.
+        $gradesarray = array();
+        $canviewhidden = has_capability('moodle/grade:viewhidden', context_course::instance($params['courseid']));
+
+        $gradesarray['items'] = array();
+        foreach ($grades->items as $gradeitem) {
+            // Switch the stdClass instance for a grade item instance so we can call is_hidden() and use the ID.
+            $gradeiteminstance = self::get_grade_item(
+                $course->id, $gradeitem->itemtype, $gradeitem->itemmodule, $gradeitem->iteminstance, 0);
+            if (!$canviewhidden && $gradeiteminstance->is_hidden()) {
+                continue;
+            }
+            $gradeitemarray = (array)$gradeitem;
+            $gradeitemarray['grades'] = array();
+
+            if (!empty($gradeitem->grades)) {
+                foreach ($gradeitem->grades as $studentid => $studentgrade) {
+                    $gradegradeinstance = grade_grade::fetch(
+                        array(
+                            'userid' => $studentid,
+                            'itemid' => $gradeiteminstance->id
+                        )
+                    );
+                    if (!$canviewhidden && $gradegradeinstance->is_hidden()) {
+                        continue;
+                    }
+                    $gradeitemarray['grades'][$studentid] = (array)$studentgrade;
+                    // Add the student ID as some WS clients can't access the array key.
+                    $gradeitemarray['grades'][$studentid]['userid'] = $studentid;
+                }
+            }
+
+            // If they requested grades for multiple activities load the cm object now.
+            $modulecm = $cm;
+            if (empty($modulecm) && !empty($gradeiteminstance->itemmodule)) {
+                $modulecm = $acitivityinstances[$gradeiteminstance->itemmodule][$gradeiteminstance->iteminstance];
+            }
+            if ($gradeiteminstance->itemtype == 'course') {
+                $gradesarray['items']['course'] = $gradeitemarray;
+                $gradesarray['items']['course']['activityid'] = 'course';
+            } else {
+                $gradesarray['items'][$modulecm->id] = $gradeitemarray;
+                // Add the activity ID as some WS clients can't access the array key.
+                $gradesarray['items'][$modulecm->id]['activityid'] = $modulecm->id;
+            }
+        }
+
+        $gradesarray['outcomes'] = array();
+        foreach ($grades->outcomes as $outcome) {
+            $modulecm = $cm;
+            if (empty($modulecm)) {
+                $modulecm = $acitivityinstances[$outcome->itemmodule][$outcome->iteminstance];
+            }
+            $gradesarray['outcomes'][$modulecm->id] = (array)$outcome;
+            $gradesarray['outcomes'][$modulecm->id]['activityid'] = $modulecm->id;
+
+            $gradesarray['outcomes'][$modulecm->id]['grades'] = array();
+            if (!empty($outcome->grades)) {
+                foreach ($outcome->grades as $studentid => $studentgrade) {
+                    if (!$canviewhidden) {
+                        // Need to load the grade_grade object to check visibility.
+                        $gradeiteminstance = self::get_grade_item(
+                            $course->id, $outcome->itemtype, $outcome->itemmodule, $outcome->iteminstance, $outcome->itemnumber);
+                        $gradegradeinstance = grade_grade::fetch(
+                            array(
+                                'userid' => $studentid,
+                                'itemid' => $gradeiteminstance->id
+                            )
+                        );
+                        // The grade grade may be legitimately missing if the student has no grade.
+                        if (!empty($gradegradeinstance ) && $gradegradeinstance->is_hidden()) {
+                            continue;
+                        }
+                    }
+                    $gradesarray['outcomes'][$modulecm->id]['grades'][$studentid] = (array)$studentgrade;
+
+                    // Add the student ID into the grade structure as some WS clients can't access the key.
+                    $gradesarray['outcomes'][$modulecm->id]['grades'][$studentid]['userid'] = $studentid;
+                }
+            }
+        }
+
+        return $gradesarray;
+    }
+
+    /**
+     * Get a grade item
+     * @param  int $courseid        Course id
+     * @param  string $itemtype     Item type
+     * @param  string $itemmodule   Item module
+     * @param  int $iteminstance    Item instance
+     * @param  int $itemnumber      Item number
+     * @return grade_item           A grade_item instance
+     */
+    private static function get_grade_item($courseid, $itemtype, $itemmodule = null, $iteminstance = null, $itemnumber = null) {
+        $gradeiteminstance = null;
+        if ($itemtype == 'course') {
+            $gradeiteminstance = grade_item::fetch(array('courseid' => $courseid, 'itemtype' => $itemtype));
+        } else {
+            $gradeiteminstance = grade_item::fetch(
+                array('courseid' => $courseid, 'itemtype' => $itemtype,
+                    'itemmodule' => $itemmodule, 'iteminstance' => $iteminstance, 'itemnumber' => $itemnumber));
+        }
+        return $gradeiteminstance;
+    }
+
+    /**
+     * Returns description of method result value
+     *
+     * @return external_description
+     * @since Moodle 2.7
+     */
+    public static function get_grades_returns() {
+        return new external_single_structure(
+            array(
+                'items'  => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'activityid' => new external_value(
+                                PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'),
+                            'itemnumber'  => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'),
+                            'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'),
+                            'name' => new external_value(PARAM_RAW, 'The module name'),
+                            'grademin' => new external_value(PARAM_FLOAT, 'Minimum grade'),
+                            'grademax' => new external_value(PARAM_FLOAT, 'Maximum grade'),
+                            'gradepass' => new external_value(PARAM_FLOAT, 'The passing grade threshold'),
+                            'locked' => new external_value(PARAM_BOOL, 'Is the grade item locked?'),
+                            'hidden' => new external_value(PARAM_BOOL, 'Is the grade item hidden?'),
+                            'grades' => new external_multiple_structure(
+                                new external_single_structure(
+                                    array(
+                                        'userid' => new external_value(
+                                            PARAM_INT, 'Student ID'),
+                                        'grade' => new external_value(
+                                            PARAM_FLOAT, 'Student grade'),
+                                        'locked' => new external_value(
+                                            PARAM_BOOL, 'Is the student\'s grade locked?'),
+                                        'hidden' => new external_value(
+                                            PARAM_BOOL, 'Is the student\'s grade hidden?'),
+                                        'overridden' => new external_value(
+                                            PARAM_BOOL, 'Is the student\'s grade overridden?'),
+                                        'feedback' => new external_value(
+                                            PARAM_RAW, 'Feedback from the grader'),
+                                        'feedbackformat' => new external_value(
+                                            PARAM_INT, 'The format of the feedback'),
+                                        'usermodified' => new external_value(
+                                            PARAM_INT, 'The ID of the last user to modify this student grade'),
+                                        'datesubmitted' => new external_value(
+                                            PARAM_INT, 'A timestamp indicating when the student submitted the activity'),
+                                        'dategraded' => new external_value(
+                                            PARAM_INT, 'A timestamp indicating when the assignment was grades'),
+                                        'str_grade' => new external_value(
+                                            PARAM_RAW, 'A string representation of the grade'),
+                                        'str_long_grade' => new external_value(
+                                            PARAM_RAW, 'A nicely formatted string representation of the grade'),
+                                        'str_feedback' => new external_value(
+                                            PARAM_TEXT, 'A string representation of the feedback from the grader'),
+                                    )
+                                )
+                            ),
+                        )
+                    )
+                ),
+                'outcomes'  => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'activityid' => new external_value(
+                                PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'),
+                            'itemnumber'  => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'),
+                            'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'),
+                            'name' => new external_value(PARAM_RAW, 'The module name'),
+                            'locked' => new external_value(PARAM_BOOL, 'Is the grade item locked?'),
+                            'hidden' => new external_value(PARAM_BOOL, 'Is the grade item hidden?'),
+                            'grades' => new external_multiple_structure(
+                                new external_single_structure(
+                                    array(
+                                        'userid' => new external_value(
+                                            PARAM_INT, 'Student ID'),
+                                        'grade' => new external_value(
+                                            PARAM_FLOAT, 'Student grade'),
+                                        'locked' => new external_value(
+                                            PARAM_BOOL, 'Is the student\'s grade locked?'),
+                                        'hidden' => new external_value(
+                                            PARAM_BOOL, 'Is the student\'s grade hidden?'),
+                                        'feedback' => new external_value(
+                                            PARAM_RAW, 'Feedback from the grader'),
+                                        'feedbackformat' => new external_value(
+                                            PARAM_INT, 'The feedback format'),
+                                        'usermodified' => new external_value(
+                                            PARAM_INT, 'The ID of the last user to modify this student grade'),
+                                        'str_grade' => new external_value(
+                                            PARAM_RAW, 'A string representation of the grade'),
+                                        'str_feedback' => new external_value(
+                                            PARAM_TEXT, 'A string representation of the feedback from the grader'),
+                                    )
+                                )
+                            ),
+                        )
+                    ), 'An array of outcomes associated with the grade items', VALUE_OPTIONAL
+                )
+            )
+        );
+
+    }
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 2.7
+     */
+    public static function update_grades_parameters() {
+        return new external_function_parameters(
+            array(
+                'source' => new external_value(PARAM_TEXT, 'The source of the grade update'),
+                'courseid' => new external_value(PARAM_INT, 'id of course'),
+                'component' => new external_value(PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz'),
+                'activityid' => new external_value(PARAM_INT, 'The activity ID'),
+                'itemnumber' => new external_value(
+                    PARAM_INT, 'grade item ID number for modules that have multiple grades. Typically this is 0.'),
+                'grades' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'studentid' => new external_value(PARAM_INT, 'Student ID'),
+                            'grade' => new external_value(PARAM_FLOAT, 'Student grade'),
+                            'str_feedback' => new external_value(
+                                PARAM_TEXT, 'A string representation of the feedback from the grader', VALUE_OPTIONAL),
+                        )
+                ), 'Any student grades to alter', VALUE_OPTIONAL),
+                'itemdetails' => new external_single_structure(
+                    array(
+                        'itemname' => new external_value(
+                            PARAM_ALPHANUMEXT, 'The grade item name', VALUE_OPTIONAL),
+                        'idnumber' => new external_value(
+                            PARAM_INT, 'Arbitrary ID provided by the module responsible for the grade item', VALUE_OPTIONAL),
+                        'gradetype' => new external_value(
+                            PARAM_INT, 'The type of grade (0 = none, 1 = value, 2 = scale, 3 = text)', VALUE_OPTIONAL),
+                        'grademax' => new external_value(
+                            PARAM_FLOAT, 'Maximum grade allowed', VALUE_OPTIONAL),
+                        'grademin' => new external_value(
+                            PARAM_FLOAT, 'Minimum grade allowed', VALUE_OPTIONAL),
+                        'scaleid' => new external_value(
+                            PARAM_INT, 'The ID of the custom scale being is used', VALUE_OPTIONAL),
+                        'multfactor' => new external_value(
+                            PARAM_FLOAT, 'Multiply all grades by this number', VALUE_OPTIONAL),
+                        'plusfactor' => new external_value(
+                            PARAM_FLOAT, 'Add this to all grades', VALUE_OPTIONAL),
+                        'deleted' => new external_value(
+                            PARAM_BOOL, 'True if the grade item should be deleted', VALUE_OPTIONAL),
+                        'hidden' => new external_value(
+                            PARAM_BOOL, 'True if the grade item is hidden', VALUE_OPTIONAL),
+                    ), 'Any grade item settings to alter', VALUE_OPTIONAL
+                )
+            )
+        );
+    }
+
+    /**
+     * Update a grade item and, optionally, student grades
+     *
+     * @param  string $source       The source of the grade update
+     * @param  int $courseid        The course id
+     * @param  string $component    Component name
+     * @param  int $activityid      The activity id
+     * @param  int $itemnumber      The item number
+     * @param  array  $grades      Array of grades
+     * @param  array  $itemdetails Array of item details
+     * @return int                  A status flag
+     * @since Moodle 2.7
+     */
+    public static function update_grades($source, $courseid, $component, $activityid,
+        $itemnumber, $grades = array(), $itemdetails = array()) {
+        global $CFG;
+
+        $params = self::validate_parameters(
+            self::update_grades_parameters(),
+            array(
+                'source' => $source,
+                'courseid' => $courseid,
+                'component' => $component,
+                'activityid' => $activityid,
+                'itemnumber' => $itemnumber,
+                'grades' => $grades,
+                'itemdetails' => $itemdetails
+            )
+        );
+
+        list($itemtype, $itemmodule) = normalize_component($params['component']);
+
+        if (! $cm = get_coursemodule_from_id($itemmodule, $activityid)) {
+            throw new moodle_exception('invalidcoursemodule');
+        }
+        $iteminstance = $cm->instance;
+
+        $coursecontext = context_course::instance($params['courseid']);
+
+        try {
+            self::validate_context($coursecontext);
+        } catch (Exception $e) {
+            $exceptionparam = new stdClass();
+            $exceptionparam->message = $e->getMessage();
+            $exceptionparam->courseid = $params['courseid'];
+            throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
+        }
+
+        $hidinggrades = false;
+        $editinggradeitem = false;
+        $editinggrades = false;
+
+        $gradestructure = array();
+        foreach ($grades as $grade) {
+            $editinggrades = true;
+            $gradestructure[ $grade['studentid'] ] = array('userid' => $grade['studentid'], 'rawgrade' => $grade['grade']);
+        }
+        if (!empty($params['itemdetails'])) {
+            if (isset($params['itemdetails']['hidden'])) {
+                $hidinggrades = true;
+            } else {
+                $editinggradeitem = true;
+            }
+        }
+
+        if ($editinggradeitem && !has_capability('moodle/grade:manage', $coursecontext)) {
+            throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null,
+                'moodle/grade:manage required to edit grade information');
+        }
+        if ($hidinggrades && !has_capability('moodle/grade:hide', $coursecontext) &&
+            !has_capability('moodle/grade:hide', $coursecontext)) {
+            throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null,
+                'moodle/grade:hide required to hide grade items');
+        }
+        if ($editinggrades && !has_capability('moodle/grade:edit', $coursecontext)) {
+            throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null,
+                'moodle/grade:edit required to edit grades');
+        }
+
+        return grade_update($params['source'], $params['courseid'], $itemtype,
+            $itemmodule, $iteminstance, $itemnumber, $gradestructure, $params['itemdetails']);
+    }
+
+    /**
+     * Returns description of method result value
+     *
+     * @return external_description
+     * @since Moodle 2.7
+     */
+    public static function update_grades_returns() {
+        return new external_single_structure(
+            array (
+                'result' => new external_value(
+                    PARAM_INT,
+                    'A value like ' . GRADE_UPDATE_OK . ' => OK, ' . GRADE_UPDATE_FAILED . ' => FAILED
+                    as defined in lib/grade/constants.php')
+            )
+        );
+    }
+}
index d06464b..580265a 100644 (file)
@@ -1012,7 +1012,7 @@ class core_plugin_manager {
 
             'filter' => array(
                 'activitynames', 'algebra', 'censor', 'emailprotect',
-                'emoticon', 'mediaplugin', 'multilang', 'tex', 'tidy',
+                'emoticon', 'mathjaxloader', 'mediaplugin', 'multilang', 'tex', 'tidy',
                 'urltolink', 'data', 'glossary'
             ),
 
index ea573c9..be60ee2 100644 (file)
@@ -198,6 +198,7 @@ class manager {
         $record->day = $task->get_day();
         $record->dayofweek = $task->get_day_of_week();
         $record->month = $task->get_month();
+        $record->disabled = $task->get_disabled();
 
         return $record;
     }
@@ -306,6 +307,9 @@ class manager {
         if (isset($record->faildelay)) {
             $task->set_fail_delay($record->faildelay);
         }
+        if (isset($record->disabled)) {
+            $task->set_disabled($record->disabled);
+        }
 
         return $task;
     }
@@ -446,7 +450,9 @@ class manager {
             throw new \moodle_exception('locktimeout');
         }
 
-        $where = '(lastruntime IS NULL OR lastruntime < :timestart1) AND (nextruntime IS NULL OR nextruntime < :timestart2)';
+        $where = "(lastruntime IS NULL OR lastruntime < :timestart1)
+                  AND (nextruntime IS NULL OR nextruntime < :timestart2)
+                  AND disabled = 0";
         $params = array('timestart1' => $timestart, 'timestart2' => $timestart);
         $records = $DB->get_records_select('task_scheduled', $where, $params);
 
index 1c36b07..f7fb050 100644 (file)
@@ -52,6 +52,9 @@ abstract class scheduled_task extends task_base {
     /** @var boolean $customised - Has this task been changed from it's default schedule? */
     private $customised = false;
 
+    /** @var int $disabled - Is this task disabled in cron? */
+    private $disabled = false;
+
     /**
      * Get the last run time for this scheduled task.
      * @return int
@@ -164,6 +167,22 @@ abstract class scheduled_task extends task_base {
         return $this->dayofweek;
     }
 
+    /**
+     * Setter for $disabled.
+     * @param bool $disabled
+     */
+    public function set_disabled($disabled) {
+        $this->disabled = (bool)$disabled;
+    }
+
+    /**
+     * Getter for $disabled.
+     * @return bool
+     */
+    public function get_disabled() {
+        return $this->disabled;
+    }
+
     /**
      * Take a cron field definition and return an array of valid numbers with the range min-max.
      *
index 1c71b6f..7791fcf 100644 (file)
@@ -42,7 +42,7 @@ class send_failed_login_notifications_task extends scheduled_task {
      * Throw exceptions on errors (the job will be retried).
      */
     public function execute() {
-        global $CFG, $DB, $OUTPUT;
+        global $CFG, $DB;
 
         if (empty($CFG->notifyloginfailures)) {
             return;
@@ -66,13 +66,23 @@ class send_failed_login_notifications_task extends scheduled_task {
 
         // Get all the IPs with more than notifyloginthreshold failures since lastnotifyfailure
         // and insert them into the cache_flags temp table.
+        $logmang = get_log_manager();
+        $readers = $logmang->get_readers('\core\log\sql_internal_reader');
+        $reader = reset($readers);
+        $readername = key($readers);
+        if (empty($reader) || empty($readername)) {
+            // No readers, no processing.
+            return true;
+        }
+        $logtable = $reader->get_internal_log_table_name();
+
         $sql = "SELECT ip, COUNT(*)
-                  FROM {log}
-                 WHERE module = 'login' AND action = 'error'
-                       AND time > ?
-              GROUP BY ip
-                HAVING COUNT(*) >= ?";
-        $params = array($CFG->lastnotifyfailure, $CFG->notifyloginthreshold);
+                  FROM {" . $logtable . "}
+                 WHERE eventname = ?
+                       AND timecreated > ?
+               GROUP BY ip
+                 HAVING COUNT(*) >= ?";
+        $params = array('\core\event\user_login_failed', $CFG->lastnotifyfailure, $CFG->notifyloginthreshold);
         $rs = $DB->get_recordset_sql($sql, $params);
         foreach ($rs as $iprec) {
             if (!empty($iprec->ip)) {
@@ -83,17 +93,17 @@ class send_failed_login_notifications_task extends scheduled_task {
 
         // Get all the INFOs with more than notifyloginthreshold failures since lastnotifyfailure
         // and insert them into the cache_flags temp table.
-        $sql = "SELECT info, count(*)
-                  FROM {log}
-                 WHERE module = 'login' AND action = 'error'
-                       AND time > ?
-              GROUP BY info
+        $sql = "SELECT userid, count(*)
+                  FROM {" . $logtable . "}
+                 WHERE eventname = ?
+                       AND timecreated > ?
+              GROUP BY userid
                 HAVING count(*) >= ?";
-        $params = array($CFG->lastnotifyfailure, $CFG->notifyloginthreshold);
+        $params = array('\core\event\user_login_failed', $CFG->lastnotifyfailure, $CFG->notifyloginthreshold);
         $rs = $DB->get_recordset_sql($sql, $params);
         foreach ($rs as $inforec) {
             if (!empty($inforec->info)) {
-                set_cache_flag('login_failure_by_info', $inforec->info, '1', 0);
+                set_cache_flag('login_failure_by_id', $inforec->userid, '1', 0);
             }
         }
         $rs->close();
@@ -101,23 +111,23 @@ class send_failed_login_notifications_task extends scheduled_task {
         // Now, select all the login error logged records belonging to the ips and infos
         // since lastnotifyfailure, that we have stored in the cache_flags table.
         $sql = "SELECT * FROM (
-            SELECT l.*, u.firstname, u.lastname
-                  FROM {log} l
-                  JOIN {cache_flags} cf ON l.ip = cf.name
-             LEFT JOIN {user} u         ON l.userid = u.id
-                 WHERE l.module = 'login' AND l.action = 'error'
-                       AND l.time > ?
-                       AND cf.flagtype = 'login_failure_by_ip'
-            UNION ALL
-                SELECT l.*, u.firstname, u.lastname
-                  FROM {log} l
-                  JOIN {cache_flags} cf ON l.info = cf.name
-             LEFT JOIN {user} u         ON l.userid = u.id
-                 WHERE l.module = 'login' AND l.action = 'error'
-                       AND l.time > ?
-                       AND cf.flagtype = 'login_failure_by_info') t
-            ORDER BY t.time DESC";
-        $params = array($CFG->lastnotifyfailure, $CFG->lastnotifyfailure);
+                        SELECT l.*, u.username
+                          FROM {" . $logtable . "} l
+                          JOIN {cache_flags} cf ON l.ip = cf.name
+                     LEFT JOIN {user} u         ON l.userid = u.id
+                         WHERE l.eventname = ?
+                               AND l.timecreated > ?
+                               AND cf.flagtype = 'login_failure_by_ip'
+                    UNION ALL
+                        SELECT l.*, u.username
+                          FROM {" . $logtable . "} l
+                          JOIN {cache_flags} cf ON l.userid = " . $DB->sql_cast_char2int('cf.name') . "
+                     LEFT JOIN {user} u         ON l.userid = u.id
+                         WHERE l.eventname = ?
+                               AND l.timecreated > ?
+                               AND cf.flagtype = 'login_failure_by_info') t
+             ORDER BY t.timecreated DESC";
+        $params = array('\core\event\user_login_failed', $CFG->lastnotifyfailure, '\core\event\user_login_failed', $CFG->lastnotifyfailure);
 
         // Init some variables.
         $count = 0;
@@ -125,8 +135,17 @@ class send_failed_login_notifications_task extends scheduled_task {
         // Iterate over the logs recordset.
         $rs = $DB->get_recordset_sql($sql, $params);
         foreach ($rs as $log) {
-            $log->time = userdate($log->time);
-            $messages .= get_string('notifyloginfailuresmessage', '', $log) . "\n";
+            $a = new \stdClass();
+            $a->time = userdate($log->timecreated);
+            if (empty($log->username)) {
+                // Entries with no valid username. We get attempted username from the event's other field.
+                $other = unserialize($log->other);
+                $a->info = empty($other['username']) ? '' : $other['username'];
+            } else {
+                $a->info = $log->username;
+            }
+            $a->ip = $log->ip;
+            $messages .= get_string('notifyloginfailuresmessage', '', $a)."\n";
             $count++;
         }
         $rs->close();
@@ -136,10 +155,12 @@ class send_failed_login_notifications_task extends scheduled_task {
             $site = get_site();
             $subject = get_string('notifyloginfailuressubject', '', format_string($site->fullname));
             // Calculate the complete body of notification (start + messages + end).
+            $params = array('id' => 0, 'modid' => 'site_errors', 'chooselog' => '1', 'logreader' => $readername);
+            $url = new \moodle_url('/report/log/index.php', $params);
             $body = get_string('notifyloginfailuresmessagestart', '', $CFG->wwwroot) .