Merge branch 'MDL-44834-master' of git://github.com/FMCorz/moodle
authorSam Hemelryk <sam@moodle.com>
Tue, 1 Apr 2014 20:28:49 +0000 (09:28 +1300)
committerSam Hemelryk <sam@moodle.com>
Tue, 1 Apr 2014 20:28:49 +0000 (09:28 +1300)
Conflicts:
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js

272 files changed:
admin/settings/security.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
blog/tests/bloglib_test.php
cohort/tests/cohortlib_test.php
course/classes/editcategory_form.php
course/tests/behat/category_management.feature
course/tests/courselib_test.php
enrol/database/tests/sync_test.php
enrol/meta/tests/plugin_test.php
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
install/lang/ca/admin.php
lang/en/admin.php
lang/en/blog.php
lang/en/grades.php
lang/en/moodle.php
lang/en/notes.php
lang/en/webservice.php
lib/authlib.php
lib/behat/behat_base.php
lib/behat/behat_field_manager.php
lib/behat/classes/behat_selectors.php
lib/behat/form_field/behat_form_editor.php
lib/behat/form_field/behat_form_text.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/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/datalib.php
lib/db/install.xml
lib/db/upgrade.php
lib/deprecatedlib.php
lib/dml/pgsql_native_moodle_database.php
lib/editor/atto/adminlib.php
lib/editor/atto/lang/en/editor_atto.php
lib/editor/atto/pix/logo.svg
lib/editor/atto/plugins/accessibilitychecker/lang/en/atto_accessibilitychecker.php
lib/editor/atto/plugins/backcolor/lang/en/atto_backcolor.php
lib/editor/atto/plugins/collapse/pix/icon.svg
lib/editor/atto/plugins/equation/lang/en/atto_equation.php
lib/editor/atto/plugins/equation/lib.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/fontcolor/lang/en/atto_fontcolor.php
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/rtl/lang/en/atto_rtl.php [changed mode: 0755->0644]
lib/editor/atto/plugins/rtl/version.php [changed mode: 0755->0644]
lib/editor/atto/plugins/rtl/yui/src/button/build.json [changed mode: 0755->0644]
lib/editor/atto/plugins/rtl/yui/src/button/js/button.js [changed mode: 0755->0644]
lib/editor/atto/plugins/rtl/yui/src/button/meta/button.json [changed mode: 0755->0644]
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/lang/en/atto_undo.php [changed mode: 0755->0644]
lib/editor/atto/plugins/undo/version.php [changed mode: 0755->0644]
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/build.json [changed mode: 0755->0644]
lib/editor/atto/plugins/undo/yui/src/button/js/button.js [changed mode: 0755->0644]
lib/editor/atto/plugins/undo/yui/src/button/meta/button.json [changed mode: 0755->0644]
lib/editor/atto/settings.php [changed mode: 0755->0644]
lib/editor/atto/styles.css [changed mode: 0755->0644]
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js
lib/editor/atto/yui/src/editor/js/editor.js [changed mode: 0755->0644]
lib/editor/atto/yui/src/editor/js/selection.js
lib/outputrenderers.php
lib/tests/accesslib_test.php
lib/tests/adhoc_task_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/scheduled_task_test.php
lib/upgrade.txt
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/src/chooserdialogue/js/chooserdialogue.js
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/locallib.php
mod/assign/submission/file/classes/event/assessable_uploaded.php
mod/chat/classes/event/message_sent.php
mod/chat/classes/event/sessions_viewed.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/forum/classes/event/assessable_uploaded.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/sql.php
mod/lesson/classes/event/essay_assessed.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/mod_form.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/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/tests/behat/preview_page.feature
mod/wiki/tests/behat/wiki_formats.feature
mod/workshop/classes/event/assessments_reset.php
mod/workshop/toolbox.php
pix/e/accessibility_checker.svg
pix/e/screenreader_helper.svg
question/addquestion.php
question/editlib.php
question/qbank.js [deleted file]
question/renderer.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/tests/events_test.php
report/loglive/classes/event/report_viewed.php
report/loglive/tests/events_test.php
report/outline/classes/event/activity_viewed.php
report/outline/classes/event/outline_viewed.php
report/participation/classes/event/report_viewed.php
report/stats/classes/event/report_viewed.php
report/stats/classes/event/user_report_viewed.php
theme/base/style/core.css
theme/base/style/question.css
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/question.less
theme/bootstrapbase/style/moodle.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/lib.php
user/tests/userlib_test.php
version.php

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 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 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 e624efc..764794a 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" exists:
+      | 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 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 a2e053c..fa3b543 100644 (file)
@@ -40,5 +40,5 @@ $string['cliunknowoption'] = 'Opcions invàlides:
  {$a}
 L\'opció --help us orientarà.';
 $string['cliyesnoprompt'] = 'Escriu y (significa Sí) o n (significa No)';
-$string['environmentrequireinstall'] = 'es requereix instal·lar/habilitar';
+$string['environmentrequireinstall'] = 'cal instal·lar-lo i habilitar-lo';
 $string['environmentrequireversion'] = 'esteu executant la versió {$a->current} i es requereix la {$a->needed}';
index 8221742..e7afe89 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.';
@@ -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..bb24a80 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}';
@@ -1324,7 +1323,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 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 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 ec71838..8c60cec 100644 (file)
@@ -583,7 +583,11 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
      * @return void
      */
     protected function ensure_editors_are_loaded() {
-        debugging('Function behat_base::ensure_editors_are_loaded() is deprecated. It is no longer required.');
+        global $CFG;
+
+        if (empty($CFG->behat_usedeprecated)) {
+            debugging('Function behat_base::ensure_editors_are_loaded() is deprecated. It is no longer required.');
+        }
         return;
     }
 
index 7454da8..6bbc39e 100644 (file)
@@ -118,7 +118,7 @@ class behat_field_manager {
         if ($tagname == 'textarea') {
 
             // If there is an iframe with $id + _ifr there a TinyMCE editor loaded.
-            $xpath = '//iframe[@id="' . $fieldnode->getAttribute('id') . '_ifr"]';
+            $xpath = '//div[@id="' . $fieldnode->getAttribute('id') . 'editable"]';
             if ($session->getPage()->find('xpath', $xpath)) {
                 return 'editor';
             }
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 43d8377..e666b02 100644 (file)
@@ -54,16 +54,26 @@ class behat_form_editor extends behat_form_textarea {
             $js = '
 var editor = Y.one(document.getElementById("'.$editorid.'editable"));
 if (editor) {
-    editor.setHTML("' . $value . '").focus();
-} else {
-    editor = Y.one(document.getElementById("'.$editorid.'"));
-    editor.set("value", "' . $value . '");
-}';
-            $result = $this->session->executeScript($js);
+    editor.setHTML("' . $value . '");
+}
+editor = Y.one(document.getElementById("'.$editorid.'"));
+editor.set("value", "' . $value . '");
+';
+            $this->session->executeScript($js);
         } else {
             parent::set_value($value);
         }
     }
 
+    /**
+     * Matches the provided value against the current field value.
+     *
+     * @param string $expectedvalue
+     * @return bool The provided value matches the field value?
+     */
+    public function matches($expectedvalue) {
+        // A text editor may silently wrap the content in p tags (or not). Neither is an error.
+        return $this->text_matches($expectedvalue) || $this->text_matches('<p>' . $expectedvalue . '</p>');
+    }
 }
 
index 14291e1..dffdf86 100644 (file)
@@ -63,8 +63,7 @@ class behat_form_text extends behat_form_field {
      * @return bool The provided value matches the field value?
      */
     public function matches($expectedvalue) {
-        // A text editor may silently wrap the content in p tags (or not). Neither is an error.
-        return $this->text_matches($expectedvalue) || $this->text_matches('<p>' . $expectedvalue . '</p>');
+        return $this->text_matches($expectedvalue);
     }
 
 }
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..9178561 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;
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.');
         }
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) .
                     (($CFG->lastnotifyfailure != 0) ? '('.userdate($CFG->lastnotifyfailure).')' : '')."\n\n" .
                     $messages .
-                    "\n\n" . get_string('notifyloginfailuresmessageend', '', $CFG->wwwroot) . "\n\n";
+                    "\n\n".get_string('notifyloginfailuresmessageend', '',  $url->out(false).' ')."\n\n";
 
             // For each destination, send mail.
             mtrace('Emailing admins about '. $count .' failed login attempts');
index 2faabe6..05e68a5 100644 (file)
@@ -75,8 +75,8 @@ abstract class task_base {
     }
 
     /**
-     * Get the last run time for this task.
-     * @return int
+     * Get the next run time for this task.
+     * @return int timestamp
      */
     public function get_next_run_time() {
         return $this->nextruntime;
index 9e7cd61..8c45183 100644 (file)
@@ -1832,43 +1832,6 @@ function get_logs_userday($userid, $courseid, $daystart) {
                                GROUP BY FLOOR((time - $daystart)/". HOURSECS .") ", $params);
 }
 
-/**
- * Returns an object with counts of failed login attempts
- *
- * Returns information about failed login attempts.  If the current user is
- * an admin, then two numbers are returned:  the number of attempts and the
- * number of accounts.  For non-admins, only the attempts on the given user
- * are shown.
- *
- * @global moodle_database $DB
- * @uses CONTEXT_SYSTEM
- * @param string $mode Either 'admin' or 'everybody'
- * @param string $username The username we are searching for
- * @param string $lastlogin The date from which we are searching
- * @return int
- */
-function count_login_failures($mode, $username, $lastlogin) {
-    global $DB;
-
-    $params = array('mode'=>$mode, 'username'=>$username, 'lastlogin'=>$lastlogin);
-    $select = "module='login' AND action='error' AND time > :lastlogin";
-
-    $count = new stdClass();
-
-    if (is_siteadmin()) {
-        if ($count->attempts = $DB->count_records_select('log', $select, $params)) {
-            $count->accounts = $DB->count_records_select('log', $select, $params, 'COUNT(DISTINCT info)');
-            return $count;
-        }
-    } else if ($mode == 'everybody') {
-        if ($count->attempts = $DB->count_records_select('log', "$select AND info = :username", $params)) {
-            return $count;
-        }
-    }
-    return NULL;
-}
-
-
 /// GENERAL HELPFUL THINGS  ///////////////////////////////////
 
 /**
index 5f18624..3627e91 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20140213" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20140324" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
         <FIELD NAME="credit" TYPE="number" LENGTH="15" NOTNULL="true" SEQUENCE="false" DECIMALS="5"/>
       </FIELDS>
       <KEYS>
-          <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
       </KEYS>
     </TABLE>
     <TABLE NAME="question_response_count" COMMENT="Count for each responses for each try at a question.">
         <FIELD NAME="rcount" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
       </FIELDS>
       <KEYS>
-          <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
-          <KEY NAME="analysisid" TYPE="foreign" FIELDS="analysisid" REFTABLE="question_response_analysis" REFFIELDS="id"/>
-        </KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+        <KEY NAME="analysisid" TYPE="foreign" FIELDS="analysisid" REFTABLE="question_response_analysis" REFFIELDS="id"/>
+      </KEYS>
     </TABLE>
     <TABLE NAME="mnet_application" COMMENT="Information about applications on remote hosts">
       <FIELDS>
         <FIELD NAME="dayofweek" TYPE="char" LENGTH="25" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="faildelay" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
         <FIELD NAME="customised" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Used on upgrades to prevent overwriting custom schedules."/>
+        <FIELD NAME="disabled" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="1 means do not run from cron"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
index 2ce903c..298cb26 100644 (file)
@@ -3085,6 +3085,7 @@ function xmldb_main_upgrade($oldversion) {
         $table->add_field('dayofweek', XMLDB_TYPE_CHAR, '25', null, XMLDB_NOTNULL, null, null);
         $table->add_field('faildelay', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
         $table->add_field('customised', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
+        $table->add_field('disabled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
 
         // Adding keys to table task_scheduled.
         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
@@ -3331,5 +3332,33 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2014032600.02);
     }
 
+    if ($oldversion < 2014032700.01) {
+
+        // Define field disabled to be added to task_scheduled.
+        $table = new xmldb_table('task_scheduled');
+        $field = new xmldb_field('disabled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'customised');
+
+        // Conditionally launch add field disabled.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2014032700.01);
+    }
+
+    if ($oldversion < 2014032700.02) {
+
+        // Update displayloginfailures setting.
+        if (empty($CFG->displayloginfailures)) {
+            set_config('displayloginfailures', 0);
+        } else {
+            set_config('displayloginfailures', 1);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2014032700.02);
+    }
+
     return true;
 }
index c4d29a9..852b0e7 100644 (file)
@@ -4335,3 +4335,43 @@ function can_use_html_editor() {
     debugging('can_use_html_editor has been deprecated please update your code to assume it returns true.', DEBUG_DEVELOPER);
     return true;
 }
+
+
+/**
+ * Returns an object with counts of failed login attempts
+ *
+ * Returns information about failed login attempts.  If the current user is
+ * an admin, then two numbers are returned:  the number of attempts and the
+ * number of accounts.  For non-admins, only the attempts on the given user
+ * are shown.
+ *
+ * @deprecate since Moodle 2.7, use {@link user_count_login_failures()} instead.
+ * @global moodle_database $DB
+ * @uses CONTEXT_SYSTEM
+ * @param string $mode Either 'admin' or 'everybody'
+ * @param string $username The username we are searching for
+ * @param string $lastlogin The date from which we are searching
+ * @return int
+ */
+function count_login_failures($mode, $username, $lastlogin) {
+    global $DB;
+
+    debugging('This method has been deprecated. Please use user_count_login_failures() instead.', DEBUG_DEVELOPER);
+
+    $params = array('mode'=>$mode, 'username'=>$username, 'lastlogin'=>$lastlogin);
+    $select = "module='login' AND action='error' AND time > :lastlogin";
+
+    $count = new stdClass();
+
+    if (is_siteadmin()) {
+        if ($count->attempts = $DB->count_records_select('log', $select, $params)) {
+            $count->accounts = $DB->count_records_select('log', $select, $params, 'COUNT(DISTINCT info)');
+            return $count;
+        }
+    } else if ($mode == 'everybody') {
+        if ($count->attempts = $DB->count_records_select('log', "$select AND info = :username", $params)) {
+            return $count;
+        }
+    }
+    return NULL;
+}
index 717e632..1b8150c 100644 (file)
@@ -136,6 +136,10 @@ class pgsql_native_moodle_database extends moodle_database {
             $connection = "user='$this->dbuser' password='$pass' dbname='$this->dbname'";
             if (strpos($this->dboptions['dbsocket'], '/') !== false) {
                 $connection = $connection." host='".$this->dboptions['dbsocket']."'";
+                if (!empty($this->dboptions['dbport'])) {
+                    // Somehow non-standard port is important for sockets - see MDL-44862.
+                    $connection = $connection." port ='".$this->dboptions['dbport']."'";
+                }
             }
         } else {
             $this->dboptions['dbsocket'] = '';
index 8c5a623..a1b9bd7 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
+/**
+ * Admin setting for toolbar.
+ *
+ * @package    editor_atto
+ * @copyright  2014 Frédéric Massart
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class editor_atto_toolbar_setting extends admin_setting_configtextarea {
+
+    /**
+     * Validate data.
+     *
+     * This ensures that:
+     * - Plugins are only used once,
+     * - Group names are unique,
+     * - Lines match: group = plugin[, plugin[, plugin ...]],
+     * - There are some groups and plugins defined,
+     * - The plugins used are installed.
+     *
+     * @param string $data
+     * @return mixed True on success, else error message.
+     */
+    public function validate($data) {
+        $result = parent::validate($data);
+        if ($result !== true) {
+            return $result;
+        }
+
+        $lines = explode("\n", $data);
+        $groups = array();
+        $plugins = array();
+
+        foreach ($lines as $line) {
+            if (!trim($line)) {
+                continue;
+            }
+
+            $matches = array();
+            if (!preg_match('/^\s*([a-z0-9]+)\s*=\s*([a-z0-9]+(\s*,\s*[a-z0-9]+)*)+\s*$/', $line, $matches)) {
+                $result = get_string('errorcannotparseline', 'editor_atto', $line);
+                break;
+            }
+
+            $group = $matches[1];
+            if (isset($groups[$group])) {
+                $result = get_string('errorgroupisusedtwice', 'editor_atto', $group);
+                break;
+            }
+            $groups[$group] = true;
+
+            $lineplugins = array_map('trim', explode(',', $matches[2]));
+            foreach ($lineplugins as $plugin) {
+                if (isset($plugins[$plugin])) {
+                    $result = get_string('errorpluginisusedtwice', 'editor_atto', $plugin);
+                    break 2;
+                } else if (!core_component::get_component_directory('atto_' . $plugin)) {
+                    $result = get_string('errorpluginnotfound', 'editor_atto', $plugin);
+                    break 2;
+                }
+                $plugins[$plugin] = true;
+            }
+        }
+
+        // We did not find any groups or plugins.
+        if (empty($groups) || empty($plugins)) {
+            $result = get_string('errornopluginsorgroupsfound', 'editor_atto');
+        }
+
+        return $result;
+    }
+
+}
 
 /**
  * Special class for Atto plugins administration.
@@ -32,7 +104,7 @@ defined('MOODLE_INTERNAL') || die();
  * @copyright 2014 Jerome Mouneyrac
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class atto_subplugins_settings extends admin_setting {
+class editor_atto_subplugins_setting extends admin_setting {
 
     /**
      * Constructor.
@@ -130,44 +202,6 @@ class atto_subplugins_settings extends admin_setting {
         $table->data  = array();
         $table->attributes['class'] = 'admintable generaltable';
 
-        $corepluginicons = array(
-            'accessibilitychecker' => $OUTPUT->pix_url('e/visual_blocks', 'core'),
-            'accessibilityhelper' => $OUTPUT->pix_url('e/visual_aid'),
-            'align' => array(
-                $OUTPUT->pix_url('e/align_left', 'core'),
-                $OUTPUT->pix_url('e/align_center', 'core'), $OUTPUT->pix_url('e/align_right', 'core')
-            ),
-            'backcolor' => $OUTPUT->pix_url('e/text_highlight', 'core'),
-            'bold' => $OUTPUT->pix_url('e/bold', 'core'),
-            'charmap' => $OUTPUT->pix_url('e/special_character', 'core'),
-            'clear' => $OUTPUT->pix_url('e/clear_formatting', 'core'),
-            'emoticon' => $OUTPUT->pix_url('e/emoticons', 'core'),
-            'equation' => $OUTPUT->pix_url('e/math', 'core'),
-            'fontcolor' => $OUTPUT->pix_url('e/text_color', 'core'),
-            'html' => $OUTPUT->pix_url('e/source_code', 'core'),
-            'image' => $OUTPUT->pix_url('e/insert_edit_image', 'core'),
-            'indent' => array(
-                $OUTPUT->pix_url('e/increase_indent', 'core'),
-                $OUTPUT->pix_url('e/decrease_indent', 'core'),
-            ),
-            'italic' => $OUTPUT->pix_url('e/italic', 'core'),
-            'link' => $OUTPUT->pix_url('e/insert_edit_link', 'core'),
-            'managefiles' => $OUTPUT->pix_url('e/manage_files', 'core'),
-            'media' => $OUTPUT->pix_url('e/insert_edit_video', 'core'),
-            'orderedlist' => $OUTPUT->pix_url('e/numbered_list', 'core'),
-            'rtl' => array($OUTPUT->pix_url('e/left_to_right', 'core'),
-                $OUTPUT->pix_url('e/right_to_left', 'core')),
-            'strike' => $OUTPUT->pix_url('e/strikethrough', 'core'),
-            'subscript' => $OUTPUT->pix_url('e/subscript', 'core'),
-            'superscript' => $OUTPUT->pix_url('e/superscript', 'core'),
-            'table' => $OUTPUT->pix_url('e/table', 'core'),
-            'title' => $OUTPUT->pix_url('e/styleprops', 'core'),
-            'underline' => $OUTPUT->pix_url('e/underline', 'core'),
-            'undo' => array($OUTPUT->pix_url('e/undo', 'core'), $OUTPUT->pix_url('e/redo', 'core')),
-            'unlink' => $OUTPUT->pix_url('e/remove_link', 'core'),
-            'unorderedlist' => $OUTPUT->pix_url('e/bullet_list', 'core')
-        );
-
         // Iterate through subplugins.
         foreach ($subplugins as $name => $dir) {
             $namestr = get_string('pluginname', 'atto_' . $name);
@@ -181,30 +215,14 @@ class atto_subplugins_settings extends admin_setting {
 
             $displayname = $namestr;
 
-            // Check if there is a pix folder in the atto plugin.
+            // Check if there is an icon in the atto plugin pix/ folder.
             if ($PAGE->theme->resolve_image_location('icon', 'atto_' . $name, false)) {
                 $icon = $OUTPUT->pix_icon('icon', '', 'atto_' . $name, array('class' => 'icon pluginicon'));
             } else {
-                // Attempt to find out the icons for core atto plugins.
-                if (array_key_exists($name, $corepluginicons)) {
-                    // It's a core plugin.
-                    $icons = array();
-                    if (!is_array($corepluginicons[$name])) {
-                        $icons[] = $corepluginicons[$name];
-                    } else {
-                        $icons = $corepluginicons[$name];
-                    }
-                    $icon = '';
-                    foreach ($icons as $anicon) {
-                        $icon .= html_writer::empty_tag('img', array('src' => $anicon,
-                            "class" => "pluginicon", "alt" => $displayname));
-                    }
-                } else {
-                    // No icon found.
-                    $icon = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'icon pluginicon noicon'));
-                }
+                // No icon found.
+                $icon = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'icon pluginicon noicon'));
             }
-            $displayname  = $icon . ' ' . $displayname;
+            $displayname = $icon . $displayname;
 
             // Add settings link.
             if (!$version) {
index 61ceb0f..410711d 100644 (file)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['errorcannotparseline'] = 'The line \'{$a}\' could not be parsed.';
+$string['errorgroupisusedtwice'] = 'The group \'{$a}\' is defined twice, group names must be unique.';
+$string['errornopluginsorgroupsfound'] = 'No plugins or groups found, please add some groups and plugins.';
+$string['errorpluginnotfound'] = 'The plugin \'{$a}\' cannot be used, it does not appear to be installed.';
+$string['errorpluginisusedtwice'] = 'The plugin \'{$a}\' is used twice, plugins can only be defined once.';
 $string['pluginname'] = 'Atto HTML editor';
 $string['subplugintype_atto'] = 'Atto plugin';
 $string['subplugintype_atto_plural'] = 'Atto plugins';
 $string['settings'] = 'Atto toolbar settings';
 $string['toolbarconfig'] = 'Toolbar config';
-$string['toolbarconfig_desc'] = 'The list of plugins and the order they are displayed can be configured here. The configuration consists of groups (one per line) followed by the ordered list of plugins for that group. The group is separated from the plugins with an equals sign and the plugins are separated with commas. The group must be one of the following: style, paragraph, links, insert, other.';
+$string['toolbarconfig_desc'] = 'The list of plugins and the order they are displayed can be configured here. The configuration consists of groups (one per line) followed by the ordered list of plugins for that group. The group is separated from the plugins with an equals sign and the plugins are separated with commas. The group names must be unique and should indicate what the buttons have in common. Button and group names should not be repeated and may only contain alphanumeric characters.';
index 8839120..2f2e945 100644 (file)
@@ -14,7 +14,7 @@
    id="svg2"
    version="1.1"
    inkscape:version="0.48.3.1 r9886"
-   sodipodi:docname="New document 1">
+   sodipodi:docname="New document 1" preserveAspectRatio="xMinYMid meet">
   <defs
      id="defs4" />
   <sodipodi:namedview
index 72e2dbb..28a90bb 100644 (file)
@@ -26,4 +26,4 @@ $string['pluginname'] = 'Accessibility checker';
 $string['nowarnings'] = 'Congratulations, no accessibility problems found!';
 $string['report'] = 'Accessibility report:';
 $string['imagesmissingalt'] = 'Images require alternative text. To fix this warning, add an alt attribute to your img tags. An empty alt attribute may be used, but only when the image is purely decorative and carries no information.';
-$string['needsmorecontrast'] = 'The colors of the foreground and background text do not have enough contrast. To fix this warning, change either foreground or background color of the text so that it is easier to read.';
+$string['needsmorecontrast'] = 'The colours of the foreground and background text do not have enough contrast. To fix this warning, change either foreground or background colour of the text so that it is easier to read.';
index 382ce62..0c7b763 100644 (file)
@@ -22,4 +22,4 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['pluginname'] = 'Background color';
+$string['pluginname'] = 'Background colour';
index 4ec6f93..f612be9 100644 (file)
@@ -14,7 +14,7 @@
    inkscape:version="0.48.3.1 r9886"
    width="16"
    height="16"
-   sodipodi:docname="icon.svg">
+   sodipodi:docname="icon.svg" preserveAspectRatio="xMinYMid meet">
   <metadata
      id="metadata8">
     <rdf:RDF>
index b0ec620..88398dd 100644 (file)
@@ -22,8 +22,7 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['editequation'] = 'Edit equation';
-$string['editequation_desc'] = 'Equations are written in <a target="_blank" href="http://en.wikibooks.org/wiki/TeX" title="Link to wikipedia">TeX.</a>';
+$string['editequation'] = 'Edit equation using <a href="{$a}" target="_blank">TeX</a>';
 $string['librarygroup1'] = 'Operators';
 $string['librarygroup1_desc'] = 'List of tex commands to list on the operators tab.';
 $string['librarygroup2'] = 'Arrows';
index 58ccf54..3e2816e 100644 (file)
@@ -34,7 +34,6 @@ function atto_equation_strings_for_js() {
     $PAGE->requires->strings_for_js(array('saveequation',
                                           'editequation',
                                           'preview',
-                                          'editequation_desc',
                                           'update',
                                           'librarygroup1',
                                           'librarygroup2',
@@ -82,5 +81,6 @@ function atto_equation_params_for_js($elementid, $options, $fpoptions) {
                 'elements' => get_config('atto_equation', 'librarygroup4'),
             ));
 
-    return array('texfilteractive' => $texfilteractive, 'contextid'=>$context->id, 'library'=>$library);
+    return array('texfilteractive' => $texfilteractive, 'contextid' => $context->id, 'library' => $library,
+        'texdocsurl' => get_docs_url('Using_TeX_Notation'));
 }
index 595c4cf..ad450d7 100644 (file)
@@ -10,7 +10,8 @@
 }
 
 .atto_equation_library button {
-    margin: 4px;
+    margin: 0.25%;
+    min-width: 12%;
 }
 
 #page-admin-setting-atto_equation_settings .form-defaultinfo {
index bc98c72..00abb9c 100644 (file)
Binary files a/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-debug.js and b/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-debug.js differ
index 53a447e..c043250 100644 (file)
Binary files a/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-min.js and b/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-min.js differ
index 540c061..76df89c 100644 (file)
Binary files a/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button.js and b/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button.js differ
index 6bbe8fd..a5af93d 100644 (file)
@@ -50,9 +50,8 @@ var COMPONENTNAME = 'atto_equation',
         FORM: '' +
             '<form class="atto_form">' +
                 '{{{library}}}' +
-                '<label for="{{elementid}}_{{CSS.EQUATION_TEXT}}">{{get_string "editequation" component}}</label>' +
+                '<label for="{{elementid}}_{{CSS.EQUATION_TEXT}}">{{{get_string "editequation" component texdocsurl}}}</label>' +
                 '<textarea class="fullwidth {{CSS.EQUATION_TEXT}}" id="{{elementid}}_{{CSS.EQUATION_TEXT}}" rows="8"></textarea><br/>' +
-                '<p>{{{get_string "editequation_desc" component}}}</p>' +
                 '<label for="{{elementid}}_{{CSS.EQUATION_PREVIEW}}">{{get_string "preview" component}}</label>' +
                 '<div class="fullwidth {{CSS.EQUATION_PREVIEW}}" id="{{elementid}}_{{CSS.EQUATION_PREVIEW}}"></div>' +
                 '<div class="mdl-align">' +
@@ -144,7 +143,8 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
 
         var dialogue = this.getDialogue({
             headerContent: M.util.get_string('pluginname', COMPONENTNAME),
-            focusAfterHide: true
+            focusAfterHide: true,
+            width: 600
         });
 
         var content = this._getDialogueContent();
@@ -315,6 +315,7 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
             elementid: this.get('host').get('elementid'),
             component: COMPONENTNAME,
             library: library,
+            texdocsurl: this.get('texdocsurl'),
             CSS: CSS
         }));
 
@@ -450,6 +451,16 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
          */
         library: {
             value: {}
+        },
+
+        /**
+         * The link to the Moodle Docs page about TeX.
+         *
+         * @attribute texdocsurl
+         * @type string
+         */
+        texdocsurl: {
+            value: null
         }
     }
 });
index c55415c..4cc7f20 100644 (file)
@@ -22,4 +22,4 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['pluginname'] = 'Font color';
+$string['pluginname'] = 'Font colour';
index cd8eb4d..43e7011 100644 (file)
@@ -35,11 +35,11 @@ $string['alignment_texttop'] = 'Text top';
 $string['alignment_top'] = 'Top';
 $string['browserepositories'] = 'Browse repositories...';
 $string['createimage'] = 'Insert image';
-$string['enteralt'] = 'Enter alternative text';
+$string['enteralt'] = 'Describe this image for someone who cannot see it';
 $string['enterurl'] = 'Enter URL';
 $string['height'] = 'Height';
-$string['presentation'] = 'Decorative only';
+$string['presentation'] = 'Description not necessary';
 $string['pluginname'] = 'Image';
-$string['presentationoraltrequired'] = 'Images must be marked as decorative only, or have suitable alternative text.';
+$string['presentationoraltrequired'] = 'Images must have a description, except if the description is marked as not necessary.';
 $string['preview'] = 'Preview';
 $string['width'] = 'Width';
index 1a650fa..81c3a45 100644 (file)
@@ -48,7 +48,6 @@ function atto_image_strings_for_js() {
         'enteralt',
         'height',
         'presentation',
-        'preview',
         'presentationoraltrequired',
         'width',
     );
index 9531011..eeadc9d 100644 (file)
@@ -1,4 +1,9 @@
 .atto_image_preview {
     max-width: 150px;
     max-height: 150px;
+    margin-bottom: 1em;
+}
+
+.editor_atto_content img {
+    cursor: pointer;
 }
index 011f06a..5c5e4bb 100644 (file)
Binary files a/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js and b/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js differ
index 3aadb25..1154666 100644 (file)
Binary files a/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js and b/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js differ
index 011f06a..5c5e4bb 100644 (file)
Binary files a/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button.js and b/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button.js differ
index 131ffc5..7f0aba3 100644 (file)
@@ -137,7 +137,6 @@ var COMPONENTNAME = 'atto_image',
                 '<br/>' +
 
                 // Add the image preview.
-                '<label for="{{elementid}}_{{CSS.IMAGEPREVIEW}}">{{get_string "preview" component}}</label>' +
                 '<div class="mdl-align">' +
                 '<img src="#" class="{{CSS.IMAGEPREVIEW}}" id="{{elementid}}_{{CSS.IMAGEPREVIEW}}" alt="" style="display: none;"/>' +
                 '<br/>' +
@@ -191,6 +190,22 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
             tags: 'img',
             tagMatchRequiresAll: false
         });
+        this.editor.delegate('dblclick', this._handleDoubleClick, 'img', this);
+    },
+
+    /**
+     * Handle a double click on an image.
+     *
+     * @method _handleDoubleClick
+     * @param {EventFacade} e
+     * @private
+     */
+    _handleDoubleClick: function(e) {
+        var image = e.target;
+
+        var selection = this.get('host').getSelectionFromNode(image);
+        this.get('host').setSelection(selection);
+        this._displayDialogue();
     },
 
     /**
@@ -227,11 +242,12 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
      */
     _getDialogueContent: function() {
         var template = Y.Handlebars.compile(TEMPLATE),
+            canShowFilepicker = this.get('host').canShowFilepicker('image'),
             content = Y.Node.create(template({
                 elementid: this.get('host').get('elementid'),
                 CSS: CSS,
                 component: COMPONENTNAME,
-                showFilepicker: this.get('host').canShowFilepicker('image'),
+                showFilepicker: canShowFilepicker,
                 alignments: ALIGNMENTS
             }));
 
@@ -242,9 +258,12 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
 
         this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this);
         this._form.one('.' + CSS.INPUTSUBMIT).on('click', this._setImage, this);
-        this._form.one('.' + CSS.IMAGEBROWSER).on('click', function() {
-                this.get('host').showFilepicker('image', this._filepickerCallback, this);
-        }, this);
+
+        if (canShowFilepicker) {
+            this._form.one('.' + CSS.IMAGEBROWSER).on('click', function() {
+                    this.get('host').showFilepicker('image', this._filepickerCallback, this);
+            }, this);
+        }
 
         return content;
     },
index dba9b7c..d1a2d28 100644 (file)
Binary files a/lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button-debug.js and b/lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button-debug.js differ
index 55b6e5e..48f7ef8 100644 (file)
Binary files a/lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button-min.js and b/lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button-min.js differ
index dba9b7c..d1a2d28 100644 (file)
Binary files a/lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button.js and b/lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button.js differ
index 0738fb2..44a2a8f 100644 (file)
@@ -67,10 +67,12 @@ Y.namespace('M.atto_media').Button = Y.Base.create('button', Y.M.editor_atto.Edi
     _content: null,
 
     initializer: function() {
-        this.addButton({
-            icon: 'e/insert_edit_video',
-            callback: this._displayDialogue
-        });
+        if (this.get('host').canShowFilepicker('media')) {
+            this.addButton({
+                icon: 'e/insert_edit_video',
+                callback: this._displayDialogue
+            });
+        }
     },
 
     /**
old mode 100755 (executable)
new mode 100644 (file)
index 388e14e..64190e5 100644 (file)
@@ -22,7 +22,6 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['accessibilityhint'] = '<h3>Accessible tables recommendation</h3><p>An accessible HTML table should have :<ul><li>a caption describing the content of the table</li><li>row and/or column headers</li><li>no empty table headers</li><li>no merged table cells</li></ul>.</p>';
 $string['createtable'] = 'Create table';
 $string['pluginname'] = 'Table';
 $string['numberofcolumns'] = 'Number of columns';
@@ -33,8 +32,8 @@ $string['columns'] = 'Columns';
 $string['rows'] = 'Rows';
 $string['both'] = 'Both';
 $string['edittable'] = 'Edit table';
-$string['addrowafter'] = 'Add row after';
-$string['addcolumnafter'] = 'Add column after';
+$string['addrowafter'] = 'Insert row after current cell';
+$string['addcolumnafter'] = 'Insert column after current cell';
 $string['moverowup'] = 'Move row up';
 $string['moverowdown'] = 'Move row down';
 $string['movecolumnleft'] = 'Move column left';
index cca2098..500421a 100644 (file)
@@ -31,7 +31,6 @@ function atto_table_strings_for_js() {
     global $PAGE;
 
     $PAGE->requires->strings_for_js(array('createtable',
-                                          'accessibilityhint',
                                           'headers',
                                           'caption',
                                           'columns',
index 9cf781f..8e9c9a5 100644 (file)
Binary files a/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-debug.js and b/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-debug.js differ
index da3625d..5fd06f0 100644 (file)
Binary files a/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-min.js and b/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-min.js differ
index 9cf781f..8e9c9a5 100644 (file)
Binary files a/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button.js and b/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button.js differ
index f0e52b7..5cc18aa 100644 (file)
@@ -35,7 +35,7 @@ var COMPONENT = 'atto_table',
     TEMPLATE = '' +
         '<form class="atto_form">' +
             '<label for="{{elementid}}_atto_table_caption">{{get_string "caption" component}}</label>' +
-            '<textarea class="caption" id="{{elementid}}_atto_table_caption" rows="4" class="fullwidth" required></textarea>' +
+            '<input class="caption fullwidth" id="{{elementid}}_atto_table_caption" required />' +
             '<br/>' +
             '<label for="{{elementid}}_atto_table_headers" class="sameline">{{get_string "headers" component}}</label>' +
             '<select class="headers" id="{{elementid}}_atto_table_headers">' +
@@ -54,8 +54,7 @@ var COMPONENT = 'atto_table',
                 '<br/>' +
                 '<button class="submit" type="submit">{{get_string "createtable" component}}</button>' +
             '</div>' +
-        '</form>' +
-        '<hr/>{{{get_string "accessibilityhint" component}}}',
+        '</form>',
     CONTEXTMENUTEMPLATE = '' +
         '<ul>' +
             '<li><a href="#" data-change="addcolumnafter">{{get_string "addcolumnafter" component}}</a></li>' +
@@ -295,7 +294,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
 
         // Show the context menu, and align to the current position.
         this._contextMenu.show();
-        this._contextMenu.align(e.tableCell, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
+        this._contextMenu.align(this.buttons.table, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
 
         // If there are any anchors in the bounding box, focus on the first.
         if (boundingBox.one('a')) {
old mode 100755 (executable)
new mode 100644 (file)
index 50a76a4..81ceac4 100644 (file)
Binary files a/lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button-debug.js and b/lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button-debug.js differ
index 0c1a4ef..c93d216 100644 (file)
Binary files a/lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button-min.js and b/lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button-min.js differ
index 50a76a4..f13bced 100644 (file)
Binary files a/lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button.js and b/lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button.js differ
old mode 100755 (executable)
new mode 100644 (file)
index d95b725..026e4a2
@@ -23,6 +23,8 @@
  * @module moodle-atto_undo-button
  */
 
+var LOGNAME = 'moodle-atto_undo-button';
+
 /**
  * Atto text editor undo plugin.
  *
@@ -84,36 +86,170 @@ Y.namespace('M.atto_undo').Button = Y.Base.create('button', Y.M.editor_atto.Edit
             keys: 89
         });
 
-        this.get('host').on('atto:selectionchanged', this._changeListener, this);
+        // Enable the undo once everything has loaded.
+        this.get('host').on('pluginsloaded', function() {
+            // Adds the current value to the stack.
+            this._addToUndo(this._getHTML());
+            this.get('host').on('atto:selectionchanged', this._changeListener, this);
+        }, this);
+
+        this._updateButtonsStates();
     },
 
     /**
-     * Handle a click on undo
+     * Adds an element to the redo stack.
      *
-     * @method _undoHandler
+     * @method _addToRedo
      * @private
+     * @param {String} html The HTML content to save.
      */
-    _undoHandler: function() {
-        var html = this.editor.getHTML();
-
+    _addToRedo: function(html) {
         this._redoStack.push(html);
-        var last = this._undoStack.pop();
-        if (last === html) {
-            last = this._undoStack.pop();
+    },
+
+    /**
+     * Adds an element to the undo stack.
+     *
+     * @method _addToUndo
+     * @private
+     * @param {String} html The HTML content to save.
+     * @param {Boolean} [clearRedo=false] Whether or not we should clear the redo stack.
+     */
+    _addToUndo: function(html, clearRedo) {
+        var last = this._undoStack[this._undoStack.length - 1];
+
+        if (typeof clearRedo === 'undefined') {
+            clearRedo = false;
+        }
+
+        if (typeof last === 'undefined') {
+            Y.log('Oops, nothing was in the undo stack! There should always be something in there.', 'warn', LOGNAME);
         }
-        if (last) {
-            this.editor.setHTML(last);
-            // Put it back in the undo stack so a new event wont clear the redo stack.
-            this._undoStack.push(last);
-            this.highlightButtons('redo');
+
+        if (last !== html) {
+            this._undoStack.push(html);
+            if (clearRedo) {
+                this._redoStack = [];
+            }
         }
 
+        while (this._undoStack.length > this._maxUndos) {
+            this._undoStack.shift();
+        }
+    },
+
+    /**
+     * Get the editor HTML.
+     *
+     * @method _getHTML
+     * @private
+     * @return {String} The HTML.
+     */
+    _getHTML: function() {
+        return this.get('host').getCleanHTML();
+    },
+
+    /**
+     * Get an element on the redo stack.
+     *
+     * @method _getRedo
+     * @private
+     * @return {String} The HTML to restore, or undefined.
+     */
+    _getRedo: function() {
+        return this._redoStack.pop();
+    },
+
+    /**
+     * Get an element on the undo stack.
+     *
+     * @method _getUndo
+     * @private
+     * @param {String} current The current HTML.
+     * @return {String} The HTML to restore.
+     */
+    _getUndo: function(current) {
+        if (this._undoStack.length === 1) {
+            return this._undoStack[0];
+        }
+
+        last = this._undoStack.pop();
+        if (last === current) {
+            // Oops, the latest undo step is the current content, we should unstack once more.
+            // There is no need to do that in a loop as the same stack should never contain duplicates.
+            last = this._undoStack.pop();
+        }
+
+        // We always need to keep the first element of the stack.
         if (this._undoStack.length === 0) {
-            // If there are no undos left, unhighlight the undo button.
-            this.unHighlightButtons('undo');
+            this._addToUndo(last);
+        }
+
+        return last;
+    },
+
+    /**
+     * Restore a value from a stack.
+     *
+     * @method _restoreValue
+     * @private
+     * @param {String} html The HTML to restore in the editor.
+     */
+    _restoreValue: function(html) {
+        this.editor.setHTML(html);
+        // We always add the restored value to the stack, otherwise an event could think that
+        // the content has changed and clear the redo stack.
+        this._addToUndo(html);
+    },
+
+    /**
+     * Update the states of the buttons.
+     *
+     * @method _updateButtonsStates
+     * @private
+     */
+    _updateButtonsStates: function() {
+        if (this._undoStack.length > 1) {
+            this.enableButtons('undo');
+        } else {
+            this.disableButtons('undo');
+        }
+
+        if (this._redoStack.length > 0) {
+            this.enableButtons('redo');
+        } else {
+            this.disableButtons('redo');
         }
     },
 
+    /**
+     * Handle a click on undo
+     *
+     * @method _undoHandler
+     * @param {Event} The click event
+     * @private
+     */
+    _undoHandler: function(e) {
+        e.preventDefault();
+        var html = this._getHTML(),
+            undo = this._getUndo(html);
+
+        // Edge case, but that could happen. We do nothing when the content equals the undo step.
+        if (html === undo) {
+            this._updateButtonsStates();
+            return;
+        }
+
+        // Restore the value.
+        this._restoreValue(undo);
+
+        // Add to the redo stack.
+        this._addToRedo(html);
+
+        // Update the button states.
+        this._updateButtonsStates();
+    },
+
     /**
      * Handle a click on redo
      *
@@ -123,12 +259,19 @@ Y.namespace('M.atto_undo').Button = Y.Base.create('button', Y.M.editor_atto.Edit
      */
     _redoHandler: function(e) {
         e.preventDefault();
-        var html = this.editor.getHTML();
+        var html = this._getHTML(),
+            redo = this._getRedo();
+
+        // Edge case, but that could happen. We do nothing when the content equals the redo step.
+        if (html === redo) {
+            this._updateButtonsStates();
+            return;
+        }
+        // Restore the value.
+        this._restoreValue(redo);
 
-        this._undoStack.push(html);
-        var last = this._redoStack.pop();
-        this.editor.setHTML(last);
-        this._undoStack.push(last);
+        // Update the button states.
+        this._updateButtonsStates();
     },
 
     /**
@@ -142,35 +285,15 @@ Y.namespace('M.atto_undo').Button = Y.Base.create('button', Y.M.editor_atto.Edit
         if (e.event.type.indexOf('key') !== -1) {
             // These are the 4 arrow keys.
             if ((e.event.keyCode !== 39) &&
-                (e.event.keyCode !== 37) &&
-                (e.event.keyCode !== 40) &&
-                (e.event.keyCode !== 38)) {
+                    (e.event.keyCode !== 37) &&
+                    (e.event.keyCode !== 40) &&
+                    (e.event.keyCode !== 38)) {
                 // Skip this event type. We only want focus/mouse/arrow events.
                 return;
             }
         }
 
-        if (typeof this._undoStack === 'undefined') {
-            this._undoStack = [];
-        }
-
-        var last = this._undoStack[this._undoStack.length-1];
-        var html = this.editor.getHTML();
-        if (last !== html) {
-            this._undoStack.push(this.editor.getHTML());
-            this._redoStack = [];
-            this.unHighlightButtons('redo');
-        }
-
-        while (this._undoStack.length > this._maxUndos) {
-            this._undoStack.shift();
-        }
-
-        // Show in the buttons if undo/redo is possible.
-        if (this._undoStack.length) {
-           this.highlightButtons('undo');
-        } else {
-           this.unHighlightButtons('undo');
-        }
+        this._addToUndo(this._getHTML(), true);
+        this._updateButtonsStates();
     }
 });
old mode 100755 (executable)
new mode 100644 (file)
index 34c0613..3ab4753
@@ -29,7 +29,7 @@ $ADMIN->add('editorsettings', new admin_category('editoratto', $editor->displayn
 $settings = new admin_settingpage('editorsettingsatto', new lang_string('settings', 'editor_atto'));
 if ($ADMIN->fulltree) {
     require_once(__DIR__ . '/adminlib.php');
-    $settings->add(new atto_subplugins_settings());
+    $settings->add(new editor_atto_subplugins_setting());
     $name = new lang_string('toolbarconfig', 'editor_atto');
     $desc = new lang_string('toolbarconfig_desc', 'editor_atto');
     $default = 'collapse = collapse
@@ -44,10 +44,7 @@ insert = equation, charmap, table, clear
 undo = undo
 accessibility = accessibilitychecker, accessibilityhelper
 other = html';
-    $setting = new admin_setting_configtextarea('editor_atto/toolbar',
-                                                    $name,
-                                                    $desc,
-                                                    $default);
+    $setting = new editor_atto_toolbar_setting('editor_atto/toolbar', $name, $desc, $default);
 
     $settings->add($setting);
 }
old mode 100755 (executable)
new mode 100644 (file)
index a754ce5..9fc447a
@@ -137,7 +137,7 @@ div.editor_atto_toolbar div.atto_group {
 }
 .atto_form label {
     display: block;
-    margin: 5px;
+    margin: 0 0 5px 0;
 }
 
 /* RTL Rules */
index e723981..95a41cd 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js differ
index 7010e14..6bad585 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js differ
index 278c68f..f64e1e5 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js differ
old mode 100755 (executable)
new mode 100644 (file)
index 1f918a6..383f825 100644 (file)
@@ -204,7 +204,7 @@ EditorSelection.prototype = {
 
         var editor = this.editor,
             stopFn = function(node) {
-                editor.contains(node);
+                return editor.contains(node);
             };
 
         selectednodes.each(function(node){
index 9f4d8e4..cee68f5 100644 (file)
@@ -666,16 +666,16 @@ class core_renderer extends renderer_base {
             unset($SESSION->justloggedin);
             if (!empty($CFG->displayloginfailures)) {
                 if (!isguestuser()) {
-                    if ($count = count_login_failures($CFG->displayloginfailures, $USER->username, $USER->lastlogin)) {
+                    // Include this file only when required.
+                    require_once($CFG->dirroot . '/user/lib.php');
+                    if ($count = user_count_login_failures($USER)) {
                         $loggedinas .= '&nbsp;<div class="loginfailures">';
-                        if (empty($count->accounts)) {
-                            $loggedinas .= get_string('failedloginattempts', '', $count);
-                        } else {
-                            $loggedinas .= get_string('failedloginattemptsall', '', $count);
-                        }
+                        $a = new stdClass();
+                        $a->attempts = $count;
+                        $loggedinas .= get_string('failedloginattempts', '', $a);
                         if (file_exists("$CFG->dirroot/report/log/index.php") and has_capability('report/log:view', context_system::instance())) {
-                            $loggedinas .= ' (<a href="'.$CFG->wwwroot.'/report/log/index.php'.
-                                                 '?chooselog=1&amp;id=1&amp;modid=site_errors">'.get_string('logs').'</a>)';
+                            $loggedinas .= html_writer::link(new moodle_url('/report/log/index.php', array('chooselog' => 1,
+                                    'id' => 0 , 'modid' => 'site_errors')), '(' . get_string('logs') . ')');
                         }
                         $loggedinas .= '</div>';
                     }
index ac7cc06..a8856e7 100644 (file)
@@ -416,9 +416,10 @@ class core_accesslib_testcase extends advanced_testcase {
         $event = array_pop($events);
 
         $this->assertInstanceOf('\core\event\role_capabilities_updated', $event);
-        $expectedurl = new moodle_url('admin/roles/define.php', array('action' => 'view', 'roleid' => $student->id));
+        $expectedurl = new moodle_url('/admin/roles/define.php', array('action' => 'view', 'roleid' => $student->id));
         $this->assertEquals($expectedurl, $event->get_url());
         $this->assertEventLegacyLogData($expectedlegacylog, $event);
+        $this->assertEventContextNotUsed($event);
     }
 
     /**
index ebe592f..174e54c 100644 (file)
@@ -15,7 +15,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * This file contains the unittests for the css optimiser in csslib.php
+ * This file contains the unittests for adhock tasks.
  *
  * @package   core
  * @category  phpunit
@@ -24,6 +24,7 @@
  */
 
 defined('MOODLE_INTERNAL') || die();
+require_once(__DIR__ . '/fixtures/task_fixtures.php');
 
 
 /**
@@ -34,14 +35,12 @@ defined('MOODLE_INTERNAL') || die();
  * @copyright 2013 Damyon Wiese
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class adhoc_task_testcase extends advanced_testcase {
+class core_adhoc_task_testcase extends advanced_testcase {
 
     public function test_get_next_adhoc_task() {
-        global $DB;
-
         $this->resetAfterTest(true);
         // Create an adhoc task.
-        $task = new testable_adhoc_task();
+        $task = new \core\task\adhoc_test_task();
 
         // Queue it.
         $task = \core\task\manager::queue_adhoc_task($task);
@@ -69,8 +68,3 @@ class adhoc_task_testcase extends advanced_testcase {
         $this->assertNull($task);
     }
 }
-
-class testable_adhoc_task extends \core\task\adhoc_task {
-    public function execute() {
-    }
-}
diff --git a/lib/tests/event_user_graded_test.php b/lib/tests/event_user_graded_test.php
new file mode 100644 (file)
index 0000000..293b00e
--- /dev/null
@@ -0,0 +1,89 @@
+<?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/>.
+
+/**
+ * Tests for base course module viewed event.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2014 Petr Skoda
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class core_event_user_graded_testcase
+ *
+ * Tests for event \core\event\user_graded
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2014 Petr Skoda
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_event_user_graded_testcase extends advanced_testcase {
+    /**
+     * Test the event.
+     */
+    public function test_event() {
+        global $CFG;
+        require_once("$CFG->libdir/gradelib.php");
+
+        $this->resetAfterTest();
+
+        $course = $this->getDataGenerator()->create_course();
+        $user = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($user->id, $course->id);
+
+        $grade_category = grade_category::fetch_course_category($course->id);
+        $grade_category->load_grade_item();
+        $grade_item = $grade_category->grade_item;
+
+        $grade_item->update_final_grade($user->id, 10, 'gradebook');
+
+        $grade_grade = new grade_grade(array('userid' => $user->id, 'itemid' => $grade_item->id), true);
+        $grade_grade->grade_item = $grade_item;
+
+        $event = \core\event\user_graded::create_from_grade($grade_grade);
+
+        $this->assertEventLegacyLogData(
+            array($course->id, 'grade', 'update', '/report/grader/index.php?id=' . $course->id, $grade_item->itemname . ': ' . fullname($user)),
+            $event
+        );
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertSame($event->objecttable, 'grade_grades');
+        $this->assertEquals($event->objectid, $grade_grade->id);
+        $this->assertEquals($event->other['itemid'], $grade_item->id);
+        $this->assertTrue($event->other['overridden']);
+        $this->assertEquals(10, $event->other['finalgrade']);
+
+        // Trigger the events.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $result = $sink->get_events();
+        $sink->close();
+
+        $this->assertCount(1, $result);
+
+        $event = reset($result);
+        $this->assertEventContextNotUsed($event);
+
+        $grade = $event->get_grade();
+        $this->assertInstanceOf('grade_grade', $grade);
+        $this->assertEquals($grade_grade->id, $grade->id);
+    }
+}
index cd54a1c..130404d 100644 (file)
@@ -49,6 +49,8 @@ class core_events_testcase extends advanced_testcase {
         // Check that the event data is valid.
         $this->assertInstanceOf('\core\event\course_category_created', $event);
         $this->assertEquals(context_coursecat::instance($category->id), $event->get_context());
+        $url = new moodle_url('/course/management.php', array('categoryid' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
         $expected = array(SITEID, 'category', 'add', 'editcategory.php?id=' . $category->id, $category->id);
         $this->assertEventLegacyLogData($expected, $event);
         $this->assertEventContextNotUsed($event);
@@ -74,6 +76,8 @@ class core_events_testcase extends advanced_testcase {
         // Check that the event data is valid.
         $this->assertInstanceOf('\core\event\course_category_updated', $event);
         $this->assertEquals(context_coursecat::instance($category->id), $event->get_context());
+        $url = new moodle_url('/course/editcategory.php', array('id' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
         $expected = array(SITEID, 'category', 'update', 'editcategory.php?id=' . $category->id, $category->id);
         $this->assertEventLegacyLogData($expected, $event);
 
index 9d8e516..885a3f0 100644 (file)
@@ -205,6 +205,7 @@ class problematic_event3 extends \core\event\base {
     }
 
     protected function validate_data() {
+        parent::validate_data();
         if (empty($this->data['other'])) {
             debugging('other is missing');
         }
diff --git a/lib/tests/fixtures/task_fixtures.php b/lib/tests/fixtures/task_fixtures.php
new file mode 100644 (file)
index 0000000..6ec619f
--- /dev/null
@@ -0,0 +1,59 @@
+<?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/>.
+
+/**
+ * Fixtures for task tests.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2014 Petr Skoda
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\task;
+defined('MOODLE_INTERNAL') || die();
+
+class adhoc_test_task extends \core\task\adhoc_task {
+    public function execute() {
+    }
+}
+
+class scheduled_test_task extends \core\task\scheduled_task {
+    public function get_name() {
+        return "Test task";
+    }
+
+    public function execute() {
+    }
+}
+
+class scheduled_test2_task extends \core\task\scheduled_task {
+    public function get_name() {
+        return "Test task 2";
+    }
+
+    public function execute() {
+    }
+}
+
+class scheduled_test3_task extends \core\task\scheduled_task {
+    public function get_name() {
+        return "Test task 3";
+    }
+
+    public function execute() {
+    }
+}
index 648fb40..e60b99a 100644 (file)
@@ -15,7 +15,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * This file contains the unittests for the css optimiser in csslib.php
+ * This file contains the unittests for scheduled tasks.
  *
  * @package   core
  * @category  phpunit
@@ -24,7 +24,7 @@
  */
 
 defined('MOODLE_INTERNAL') || die();
-
+require_once(__DIR__ . '/fixtures/task_fixtures.php');
 
 /**
  * Test class for scheduled task.
@@ -34,13 +34,13 @@ defined('MOODLE_INTERNAL') || die();
  * @copyright 2013 Damyon Wiese
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class scheduled_task_testcase extends advanced_testcase {
+class core_scheduled_task_testcase extends advanced_testcase {
 
     /**
      * Test the cron scheduling method
      */
     public function test_eval_cron_field() {
-        $testclass = new testable_scheduled_task();
+        $testclass = new \core\task\scheduled_test_task();
 
         $this->assertEquals(20, count($testclass->eval_cron_field('*/3', 0, 59)));
         $this->assertEquals(31, count($testclass->eval_cron_field('1,*/2', 0, 59)));
@@ -52,7 +52,7 @@ class scheduled_task_testcase extends advanced_testcase {
 
     public function test_get_next_scheduled_time() {
         // Test job run at 1 am.
-        $testclass = new testable_scheduled_task();
+        $testclass = new \core\task\scheduled_test_task();
 
         // All fields default to '*'.
         $testclass->set_hour('1');
@@ -68,8 +68,13 @@ class scheduled_task_testcase extends advanced_testcase {
 
         $this->assertEquals($oneam, $nexttime, 'Next scheduled time is 1am.');
 
+        // Disabled flag does not affect next time.
+        $testclass->set_disabled(true);
+        $nexttime = $testclass->get_next_scheduled_time();
+        $this->assertEquals($oneam, $nexttime, 'Next scheduled time is 1am.');
+
         // Now test for job run every 10 minutes.
-        $testclass = new testable_scheduled_task();
+        $testclass = new \core\task\scheduled_test_task();
 
         // All fields default to '*'.
         $testclass->set_minute('*/10');
@@ -80,6 +85,11 @@ class scheduled_task_testcase extends advanced_testcase {
         $nexttenminutes = mktime(date('H'), $minutes, 0);
 
         $this->assertEquals($nexttenminutes, $nexttime, 'Next scheduled time is in 10 minutes.');
+
+        // Disabled flag does not affect next time.
+        $testclass->set_disabled(true);
+        $nexttime = $testclass->get_next_scheduled_time();
+        $this->assertEquals($nexttenminutes, $nexttime, 'Next scheduled time is in 10 minutes.');
     }
 
     public function test_timezones() {
@@ -103,7 +113,7 @@ class scheduled_task_testcase extends advanced_testcase {
         // GMT-04:30.
         $CFG->timezone = 'America/Caracas';
 
-        $testclass = new testable_scheduled_task();
+        $testclass = new \core\task\scheduled_test_task();
 
         // Scheduled tasks should always use servertime - so this is 03:30 GMT.
         $testclass->set_hour('1');
@@ -142,23 +152,28 @@ class scheduled_task_testcase extends advanced_testcase {
         $record->day = '*';
         $record->month = '*';
         $record->component = 'test_scheduled_task';
-        $record->classname = '\\testable_scheduled_task';
+        $record->classname = '\core\task\scheduled_test_task';
 
         $DB->insert_record('task_scheduled', $record);
         // And another one to test failures.
-        $record->classname = '\\testable_scheduled_task2';
+        $record->classname = '\core\task\scheduled_test2_task';
+        $DB->insert_record('task_scheduled', $record);
+        // And disabled test.
+        $record->classname = '\core\task\scheduled_test3_task';
+        $record->disabled = 1;
         $DB->insert_record('task_scheduled', $record);
+
         $now = time();
 
         // Should get handed the first task.
         $task = \core\task\manager::get_next_scheduled_task($now);
-        $this->assertNotNull($task);
+        $this->assertInstanceOf('\core\task\scheduled_test_task', $task);
         $task->execute();
 
         \core\task\manager::scheduled_task_complete($task);
         // Should get handed the second task.
         $task = \core\task\manager::get_next_scheduled_task($now);
-        $this->assertNotNull($task);
+        $this->assertInstanceOf('\core\task\scheduled_test2_task', $task);
         $task->execute();
 
         \core\task\manager::scheduled_task_failed($task);
@@ -168,7 +183,7 @@ class scheduled_task_testcase extends advanced_testcase {
 
         // Should get the second task (retry after delay).
         $task = \core\task\manager::get_next_scheduled_task($now + 120);
-        $this->assertNotNull($task);
+        $this->assertInstanceOf('\core\task\scheduled_test2_task', $task);
         $task->execute();
 
         \core\task\manager::scheduled_task_complete($task);
@@ -178,22 +193,3 @@ class scheduled_task_testcase extends advanced_testcase {
         $this->assertNull($task);
     }
 }
-
-class testable_scheduled_task extends \core\task\scheduled_task {
-    public function get_name() {
-        return "Test task";
-    }
-
-    public function execute() {
-    }
-}
-
-class testable_scheduled_task2 extends \core\task\scheduled_task {
-    public function get_name() {
-        return "Test task 2";
-    }
-
-    public function execute() {
-    }
-}
-
index 385ad28..2b1b032 100644 (file)
@@ -25,6 +25,7 @@ DEPRECATIONS:
 * Various cm_info methods have been deprecated in favour of their read-only properties (get_url(), get_content(), get_extra_classes(),
   get_on_click(), get_custom_data(), get_after_link, get_after_edit_icons)
 * The ajaxenabled function has been deprecated and always returns true. All code should be fully functional in Javascript.
+* count_login_failures() has been deprecated, use user_count_login_failures() instead. Refer MDL-42891 for details.
 
 YUI:
   * The lightbox attribute for moodle-core-notification-dialogue has been
index c88ac91..6f63084 100644 (file)
Binary files a/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-debug.js and b/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-debug.js differ
index b1de034..53287d9 100644 (file)
Binary files a/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-min.js and b/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-min.js differ
index c88ac91..6f63084 100644 (file)
Binary files a/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue.js and b/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue.js differ
index 700e140..66307fa 100644 (file)
@@ -45,6 +45,9 @@ Y.extend(CHOOSERDIALOGUE, Y.Base, {
             return;
         }
 
+        // Ensure that we're showing the JS version of the chooser.
+        Y.one(Y.config.doc.body).addClass('jschooser');
+
         // Set Default options
         var paramkey,
             params = {
index a4d2bcb..e7f1a5e 100644 (file)
@@ -58,6 +58,8 @@ class core_message_events_testcase extends advanced_testcase {
         $expected = array(SITEID, 'message', 'add contact', 'index.php?user1=' . $user->id .
             '&amp;user2=2', $user->id);
         $this->assertEventLegacyLogData($expected, $event);
+        $url = new moodle_url('/message/index.php', array('user1' => $event->userid, 'user2' => $event->relateduserid));
+        $this->assertEquals($url, $event->get_url());
     }
 
     /**
@@ -85,6 +87,8 @@ class core_message_events_testcase extends advanced_testcase {
         $expected = array(SITEID, 'message', 'remove contact', 'index.php?user1=' . $user->id .
             '&amp;user2=2', $user->id);
         $this->assertEventLegacyLogData($expected, $event);
+        $url = new moodle_url('/message/index.php', array('user1' => $event->userid, 'user2' => $event->relateduserid));
+        $this->assertEquals($url, $event->get_url());
     }
 
     /**
@@ -111,6 +115,8 @@ class core_message_events_testcase extends advanced_testcase {
         $this->assertEquals(context_user::instance(2), $event->get_context());
         $expected = array(SITEID, 'message', 'block contact', 'index.php?user1=' . $user->id . '&amp;user2=2', $user->id);
         $this->assertEventLegacyLogData($expected, $event);
+        $url = new moodle_url('/message/index.php', array('user1' => $event->userid, 'user2' => $event->relateduserid));
+        $this->assertEquals($url, $event->get_url());
     }
 
     /**
@@ -137,6 +143,8 @@ class core_message_events_testcase extends advanced_testcase {
         $this->assertEquals(context_user::instance(2), $event->get_context());
         $expected = array(SITEID, 'message', 'unblock contact', 'index.php?user1=' . $user->id . '&amp;user2=2', $user->id);
         $this->assertEventLegacyLogData($expected, $event);
+        $url = new moodle_url('/message/index.php', array('user1' => $event->userid, 'user2' => $event->relateduserid));
+        $this->assertEquals($url, $event->get_url());
     }
 
     /**
@@ -166,6 +174,8 @@ class core_message_events_testcase extends advanced_testcase {
         $this->assertEquals(context_system::instance(), $event->get_context());
         $expected = array(SITEID, 'message', 'write', 'index.php?user=1&id=2&history=1#m3', 1);
         $this->assertEventLegacyLogData($expected, $event);
+        $url = new moodle_url('/message/index.php', array('user1' => $event->userid, 'user2' => $event->relateduserid));
+        $this->assertEquals($url, $event->get_url());
     }
 
     /**
@@ -191,5 +201,7 @@ class core_message_events_testcase extends advanced_testcase {
         // Check that the event data is valid.
         $this->assertInstanceOf('\core\event\message_read', $event);
         $this->assertEquals(context_user::instance(2), $event->get_context());
+        $url = new moodle_url('/message/index.php', array('user1' => $event->userid, 'user2' => $event->relateduserid));
+        $this->assertEquals($url, $event->get_url());
     }
 }
index 4e1ad1d..df89cbc 100644 (file)
@@ -447,7 +447,12 @@ function mnet_update_sso_access_control($username, $mnet_host_id, $accessctrl) {
         // Trigger access control updated event.
         $params = array(
             'objectid' => $aclrecord->id,
-            'context' => context_system::instance()
+            'context' => context_system::instance(),
+            'other' => array(
+                'username' => $username,
+                'hostname' => $mnethost->name,
+                'accessctrl' => $accessctrl
+            )
         );
         $event = \core\event\mnet_access_control_updated::create($params);
         $event->add_record_snapshot('mnet_host', $mnethost);
@@ -463,7 +468,12 @@ function mnet_update_sso_access_control($username, $mnet_host_id, $accessctrl) {
         // Trigger access control created event.
         $params = array(
             'objectid' => $aclrecord->id,
-            'context' => context_system::instance()
+            'context' => context_system::instance(),
+            'other' => array(
+                'username' => $username,
+                'hostname' => $mnethost->name,
+                'accessctrl' => $accessctrl
+            )
         );
         $event = \core\event\mnet_access_control_created::create($params);
         $event->add_record_snapshot('mnet_host', $mnethost);
index 0724d57..451a1c6 100644 (file)
@@ -67,6 +67,9 @@ class mnet_events_testcase extends advanced_testcase {
         $expected = array(SITEID, 'admin/mnet', 'add', 'admin/mnet/access_control.php',
             'SSO ACL: enabled user \'username\' from ' . $this->mnethost->name);
         $this->assertEventLegacyLogData($expected, $event);
+        $this->assertEventContextNotUsed($event);
+        $url = new \moodle_url('/admin/mnet/access_control.php');
+        $this->assertEquals($url, $event->get_url());
     }
 
     /**
@@ -94,5 +97,8 @@ class mnet_events_testcase extends advanced_testcase {
         $expected = array(SITEID, 'admin/mnet', 'update', 'admin/mnet/access_control.php',
             'SSO ACL: enabled user \'username\' from ' . $this->mnethost->name);
         $this->assertEventLegacyLogData($expected, $event);
+        $this->assertEventContextNotUsed($event);
+        $url = new \moodle_url('/admin/mnet/access_control.php');
+        $this->assertEquals($url, $event->get_url());
     }
 }
index 6ceb783..3e180e3 100644 (file)
@@ -81,7 +81,7 @@ class all_submissions_downloaded extends \core\event\base {
     /**
      * Sets the legacy event log data.
      *
-     * @param stdClass $legacylogdata legacy log data.
+     * @param \stdClass $legacylogdata legacy log data.
      * @return void
      */
     public function set_legacy_logdata($legacylogdata) {
index 2f519f0..258aa9e 100644 (file)
@@ -60,7 +60,7 @@ class assessable_submitted extends \core\event\assessable_submitted {
     /**
      * Legacy event data if get_legacy_eventname() is not empty.
      *
-     * @return stdClass
+     * @return \stdClass
      */
     protected function get_legacy_eventdata() {
         $eventdata = new \stdClass();
@@ -112,7 +112,7 @@ class assessable_submitted extends \core\event\assessable_submitted {
     /**
      * Sets the legacy event log data.
      *
-     * @param stdClass $legacylogdata legacy log data.
+     * @param \stdClass $legacylogdata legacy log data.
      * @return void
      */
     public function set_legacy_logdata($legacylogdata) {
index 07eae94..a7c20ec 100644 (file)
@@ -92,7 +92,7 @@ class extension_granted extends \core\event\base {
     /**
      * Sets the legacy event log data.
      *
-     * @param stdClass $legacylogdata legacy log data.
+     * @param \stdClass $legacylogdata legacy log data.
      * @return void
      */
     public function set_legacy_logdata($legacylogdata) {
@@ -106,6 +106,7 @@ class extension_granted extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->relateduserid)) {
             throw new \coding_exception('relateduserid is a mandatory property.');
         }
index f6b726c..1bc307a 100644 (file)
@@ -92,7 +92,7 @@ class identities_revealed extends \core\event\base {
     /**
      * Sets the legacy event log data.
      *
-     * @param stdClass $legacylogdata legacy log data.
+     * @param \stdClass $legacylogdata legacy log data.
      * @return void
      */
     public function set_legacy_logdata($legacylogdata) {
index 20b20f2..ba36181 100644 (file)
@@ -98,7 +98,7 @@ class marker_updated extends \core\event\base {
     /**
      * Sets the legacy event log data.
      *
-     * @param stdClass $legacylogdata legacy log data.
+     * @param \stdClass $legacylogdata legacy log data.
      * @return void
      */
     public function set_legacy_logdata($legacylogdata) {
@@ -112,6 +112,7 @@ class marker_updated extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->other['markerid'])) {
             throw new \coding_exception('markerid must be set in $other.');
         } else if (!isset($this->relateduserid)) {
index 4169ea6..ea92ec3 100644 (file)
@@ -92,7 +92,7 @@ class statement_accepted extends \core\event\base {
     /**
      * Sets the legacy event log data.
      *
-     * @param stdClass $legacylogdata legacy log data.
+     * @param \stdClass $legacylogdata legacy log data.
      * @return void
      */
     public function set_legacy_logdata($legacylogdata) {
index 2c1057b..22df013 100644 (file)
@@ -74,6 +74,7 @@ abstract class submission_created extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->other['submissionid'])) {
             throw new \coding_exception('Other must contain the key submissionid.');
         }
index b52b984..e25a3c4 100644 (file)
@@ -92,7 +92,7 @@ class submission_duplicated extends \core\event\base {
     /**
      * Sets the legacy event log data.
      *
-     * @param stdClass $legacylogdata legacy log data.
+     * @param \stdClass $legacylogdata legacy log data.
      * @return void
      */
     public function set_legacy_logdata($legacylogdata) {
index bc3f474..ff82bd3 100644 (file)
@@ -92,7 +92,7 @@ class submission_graded extends \core\event\base {
     /**
      * Sets the legacy event log data.
      *
-     * @param stdClass $legacylogdata legacy log data.
+     * @param \stdClass $legacylogdata legacy log data.
      * @return void
      */
     public function set_legacy_logdata($legacylogdata) {
@@ -106,6 +106,7 @@ class submission_graded extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->relateduserid)) {
             throw new \coding_exception('relateduserid is a mandatory property.');
         }
index 7d94453..5327faf 100644 (file)
@@ -92,7 +92,7 @@ class submission_locked extends \core\event\base {
     /**
      * Sets the legacy event log data.
      *
-     * @param stdClass $legacylogdata legacy log data.
+     * @param \stdClass $legacylogdata legacy log data.
      * @return void
      */
     public function set_legacy_logdata($legacylogdata) {
@@ -106,6 +106,7 @@ class submission_locked extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->relateduserid)) {
             throw new \coding_exception('relateduserid is a mandatory property.');
         }
index cbf526f..e998ef2 100644 (file)
@@ -98,7 +98,7 @@ class submission_status_updated extends \core\event\base {
     /**
      * Sets the legacy event log data.
      *
-     * @param stdClass $legacylogdata legacy log data.
+     * @param \stdClass $legacylogdata legacy log data.
      * @return void
      */
     public function set_legacy_logdata($legacylogdata) {
@@ -112,6 +112,7 @@ class submission_status_updated extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->other['newstatus'])) {
             throw new \coding_exception('newstatus must be set in $other.');
         }
index e5594b1..5e97042 100644 (file)
@@ -92,7 +92,7 @@ class submission_unlocked extends \core\event\base {
     /**
      * Sets the legacy event log data.
      *
-     * @param stdClass $legacylogdata legacy log data.
+     * @param \stdClass $legacylogdata legacy log data.
      * @return void
      */
     public function set_legacy_logdata($legacylogdata) {
@@ -106,6 +106,7 @@ class submission_unlocked extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->relateduserid)) {
             throw new \coding_exception('relateduserid is a mandatory property.');
         }
index 6ec4951..69120ab 100644 (file)
@@ -74,6 +74,7 @@ abstract class submission_updated extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->other['submissionid'])) {
             throw new \coding_exception('Other must contain the key submissionid.');
         }
index 1ef9037..683b140 100644 (file)
@@ -98,7 +98,7 @@ class workflow_state_updated extends \core\event\base {
     /**
      * Sets the legacy event log data.
      *
-     * @param stdClass $legacylogdata legacy log data.
+     * @param \stdClass $legacylogdata legacy log data.
      * @return void
      */
     public function set_legacy_logdata($legacylogdata) {
@@ -112,6 +112,7 @@ class workflow_state_updated extends \core\event\base {
      * @return void
      */
     protected function validate_data() {
+        parent::validate_data();
         if (!isset($this->other['newstate'])) {
             throw new \coding_exception('newstate must be set in $other.');
         } else if (!isset($this->relateduserid)) {
index c3c5f00..87bec4c 100644 (file)
@@ -546,13 +546,13 @@ class pdf extends \FPDI {
             return $ret;
         }
 
-        $testfile = $CFG->dirroot.'/mod/assign/feedback/editpdf/tests/fixtures/testgs.pdf';
-        if (!file_exists($testfile)) {
-            $ret->status = self::GSPATH_NOTESTFILE;
+        if (!$generateimage) {
             return $ret;
         }
 
-        if (!$generateimage) {
+        $testfile = $CFG->dirroot.'/mod/assign/feedback/editpdf/tests/fixtures/testgs.pdf';
+        if (!file_exists($testfile)) {
+            $ret->status = self::GSPATH_NOTESTFILE;
             return $ret;
         }
 
index 1df66f3..719e29e 100644 (file)
@@ -37,6 +37,9 @@ use \assignfeedback_editpdf\page_editor;
  */
 class assign_feedback_editpdf extends assign_feedback_plugin {
 
+    /** @var boolean|null $enabledcache Cached lookup of the is_enabled function */
+    private $enabledcache = null;
+
     /**
      * Get the name of the file feedback plugin
      * @return string
@@ -265,11 +268,11 @@ class assign_feedback_editpdf extends assign_feedback_plugin {
      * @return bool
      */
     public function is_enabled() {
-        $testpath = assignfeedback_editpdf\pdf::test_gs_path();
-        if ($testpath->status == assignfeedback_editpdf\pdf::GSPATH_OK) {
-            return true;
+        if ($this->enabledcache === null) {
+            $testpath = assignfeedback_editpdf\pdf::test_gs_path(false);
+            $this->enabledcache = ($testpath->status == assignfeedback_editpdf\pdf::GSPATH_OK);
         }
-        return false;
+        return $this->enabledcache;
     }
     /**
      * Automatically hide the setting for the editpdf feedback plugin.
index 143d08d..c47d036 100644 (file)
@@ -36,6 +36,9 @@ require_once($CFG->dirroot.'/grade/grading/lib.php');
  */
 class assign_feedback_offline extends assign_feedback_plugin {
 
+    /** @var boolean|null $enabledcache Cached lookup of the is_enabled function */
+    private $enabledcache = null;
+
     /**
      * Get the name of the file feedback plugin
      * @return string
@@ -373,14 +376,18 @@ class assign_feedback_offline extends assign_feedback_plugin {
      * @return bool
      */
     public function is_enabled() {
-        $gradingmanager = get_grading_manager($this->assignment->get_context(), 'mod_assign', 'submissions');</