Merge branch 'install_master' of git://git.moodle.org/moodle-install
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Thu, 23 Jan 2014 11:38:06 +0000 (12:38 +0100)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Thu, 23 Jan 2014 11:38:06 +0000 (12:38 +0100)
303 files changed:
admin/tests/behat/upload_users.feature
admin/tool/behat/tests/behat/data_generators.feature
admin/tool/behat/upgrade.txt [new file with mode: 0644]
admin/tool/qeupgradehelper/locallib.php
admin/tool/uploadcourse/tests/behat/create.feature
admin/tool/uploadcourse/tests/behat/update.feature
backup/converter/moodle1/lib.php
backup/moodle2/restore_stepslib.php
badges/backpack_form.php
badges/backpackconnect.php
badges/lib/backpacklib.php
badges/tests/behat/add_badge.feature
badges/tests/behat/award_badge.feature
badges/upgrade.txt
blocks/activity_modules/tests/behat/block_activity_modules.feature
blocks/html/styles.css [deleted file]
blocks/private_files/edit.php
blocks/private_files/lang/en/block_private_files.php
blocks/site_main_menu/block_site_main_menu.php
blocks/site_main_menu/styles.css
blocks/social_activities/block_social_activities.php
blocks/social_activities/styles.css [new file with mode: 0644]
cohort/tests/behat/upload_cohort_users.feature
comment/locallib.php
completion/tests/behat/restrict_activity_by_date.feature
course/lib.php
course/tests/behat/course_controls.feature
files/tests/behat/course_files.feature
grade/report/grader/styles.css
group/autogroup.php
group/delete.php
group/group.php
group/grouping.php
group/index.php
group/tests/behat/groups_import.feature
lang/en/mnet.php
lang/en/moodle.php
lang/en/my.php
lang/en/question.php
lib/ajax/blocks.php
lib/ajax/getnavbranch.php
lib/badgeslib.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_entries_viewed.php
lib/classes/event/blog_entry_created.php
lib/classes/event/blog_entry_deleted.php
lib/classes/event/blog_entry_updated.php
lib/classes/event/cohort_created.php
lib/classes/event/cohort_deleted.php
lib/classes/event/cohort_member_added.php
lib/classes/event/cohort_member_removed.php
lib/classes/event/cohort_updated.php
lib/classes/event/comment_created.php
lib/classes/event/comment_deleted.php
lib/classes/event/comments_viewed.php
lib/classes/event/content_viewed.php
lib/classes/event/course_category_created.php
lib/classes/event/course_category_deleted.php
lib/classes/event/course_category_updated.php
lib/classes/event/course_completed.php
lib/classes/event/course_completion_updated.php
lib/classes/event/course_content_deleted.php
lib/classes/event/course_created.php
lib/classes/event/course_deleted.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_restored.php
lib/classes/event/course_section_updated.php
lib/classes/event/course_updated.php
lib/classes/event/email_failed.php [new file with mode: 0644]
lib/classes/event/group_created.php
lib/classes/event/group_deleted.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/mnet_access_control_created.php [new file with mode: 0644]
lib/classes/event/mnet_access_control_updated.php [new file with mode: 0644]
lib/classes/event/note_created.php
lib/classes/event/note_deleted.php
lib/classes/event/note_updated.php
lib/classes/event/notes_viewed.php
lib/classes/event/role_allow_assign_updated.php
lib/classes/event/role_allow_override_updated.php
lib/classes/event/role_allow_switch_updated.php
lib/classes/event/role_assigned.php
lib/classes/event/role_capabilities_updated.php
lib/classes/event/role_deleted.php
lib/classes/event/role_unassigned.php
lib/classes/event/user_created.php
lib/classes/event/user_deleted.php
lib/classes/event/user_enrolment_created.php
lib/classes/event/user_enrolment_deleted.php
lib/classes/event/user_enrolment_updated.php
lib/classes/event/user_list_viewed.php
lib/classes/event/user_loggedin.php
lib/classes/event/user_loggedinas.php
lib/classes/event/user_loggedout.php
lib/classes/event/user_profile_viewed.php
lib/classes/event/user_updated.php
lib/classes/event/webservice_function_called.php
lib/classes/event/webservice_login_failed.php
lib/classes/event/webservice_service_created.php
lib/classes/event/webservice_service_deleted.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/event/webservice_token_sent.php
lib/db/upgrade.php
lib/grade/grade_item.php
lib/grade/tests/grade_item_test.php
lib/moodlelib.php
lib/navigationlib.php
lib/tests/event_test.php
lib/tests/events_test.php
lib/tests/fixtures/event_fixtures.php
lib/tests/upgradelib_test.php
lib/upgrade.txt
lib/upgradelib.php
message/index.php
mnet/lib.php
mnet/tests/events_test.php [new file with mode: 0644]
mod/assign/classes/event/all_submissions_downloaded.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_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/module.js
mod/assign/tests/behat/file_submission.feature
mod/book/classes/event/chapter_created.php
mod/book/classes/event/chapter_deleted.php
mod/book/classes/event/chapter_updated.php
mod/book/classes/event/chapter_viewed.php
mod/book/classes/event/course_module_viewed.php
mod/book/tool/exportimscp/classes/event/book_exported.php
mod/book/tool/print/classes/event/book_printed.php
mod/book/tool/print/classes/event/chapter_printed.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/course_module_viewed.php
mod/choice/classes/event/report_viewed.php
mod/feedback/classes/event/course_module_viewed.php
mod/feedback/classes/event/response_deleted.php
mod/feedback/classes/event/response_submitted.php
mod/feedback/item/captcha/lib.php
mod/folder/classes/event/course_module_viewed.php
mod/folder/classes/event/folder_updated.php
mod/imscp/backup/moodle1/lib.php
mod/imscp/locallib.php
mod/lesson/classes/event/course_module_viewed.php
mod/lesson/classes/event/essay_assessed.php [new file with mode: 0644]
mod/lesson/classes/event/essay_attempt_viewed.php
mod/lesson/classes/event/highscore_added.php
mod/lesson/classes/event/highscores_viewed.php
mod/lesson/classes/event/lesson_ended.php
mod/lesson/classes/event/lesson_started.php
mod/lesson/essay.php
mod/lesson/lang/en/lesson.php
mod/lesson/lib.php
mod/lesson/tests/events_test.php
mod/lti/classes/event/course_module_viewed.php
mod/lti/classes/event/unknown_service_api_called.php
mod/page/classes/event/course_module_viewed.php
mod/quiz/attemptlib.php
mod/quiz/backup/moodle1/lib.php
mod/quiz/backup/moodle2/backup_quiz_stepslib.php
mod/quiz/backup/moodle2/restore_quiz_stepslib.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/db/install.xml
mod/quiz/db/upgrade.php
mod/quiz/editlib.php
mod/quiz/lib.php
mod/quiz/locallib.php
mod/quiz/report/reportlib.php
mod/quiz/styles.css
mod/quiz/tests/editlib_test.php
mod/quiz/upgrade.txt
mod/quiz/version.php
mod/resource/classes/event/course_module_instance_list_viewed.php [moved from blocks/private_files/edit_form.php with 53% similarity]
mod/resource/classes/event/course_module_viewed.php [new file with mode: 0644]
mod/resource/index.php
mod/resource/tests/events_test.php [new file with mode: 0644]
mod/resource/view.php
mod/scorm/classes/event/attempt_deleted.php
mod/scorm/classes/event/course_module_viewed.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/tracks_viewed.php
mod/scorm/classes/event/user_report_viewed.php
mod/scorm/tests/behat/add_scorm.feature
mod/scorm/tests/packages/readme_moodle.txt [new file with mode: 0644]
mod/scorm/tests/packages/singlesco_scorm12.zip [new file with mode: 0644]
mod/url/classes/event/course_module_viewed.php
mod/wiki/classes/event/course_module_viewed.php
mod/wiki/classes/event/page_created.php
mod/wiki/classes/event/page_deleted.php
mod/wiki/classes/event/page_diff_viewed.php
mod/wiki/classes/event/page_history_viewed.php
mod/wiki/classes/event/page_locks_deleted.php
mod/wiki/classes/event/page_map_viewed.php
mod/wiki/classes/event/page_updated.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/workshop/classes/event/course_module_viewed.php
my/index.php
my/lib.php
my/tests/behat/add_blocks.feature [new file with mode: 0644]
my/tests/behat/reset_page.feature [new file with mode: 0644]
phpunit.xml.dist
question/editlib.php
question/engine/questionattempt.php
question/engine/tests/helpers.php
question/engine/tests/questionusage_autosave_test.php
question/format/blackboard_six/formatpool.php
question/format/blackboard_six/formatqti.php
question/format/examview/format.php
question/format/gift/format.php
question/format/webct/format.php
question/format/xml/format.php
question/format/xml/tests/xmlformat_test.php
question/question.php
question/tests/behat/copy_questions.feature [new file with mode: 0644]
question/tests/behat/delete_questions.feature
question/tests/behat/edit_questions.feature
question/tests/behat/preview_question.feature
question/type/calculated/questiontype.php
question/type/edit_question_form.php
question/type/essay/backup/moodle1/lib.php
question/type/essay/backup/moodle2/backup_qtype_essay_plugin.class.php
question/type/essay/backup/moodle2/restore_qtype_essay_plugin.class.php
question/type/essay/db/install.xml
question/type/essay/db/upgrade.php
question/type/essay/edit_essay_form.php
question/type/essay/lang/en/qtype_essay.php
question/type/essay/question.php
question/type/essay/questiontype.php
question/type/essay/renderer.php
question/type/essay/tests/helper.php
question/type/essay/tests/question_test.php
question/type/essay/version.php
question/type/multianswer/edit_multianswer_form.php
report/loglive/index.php
report/loglive/lang/en/report_loglive.php
repository/recent/tests/behat/add_recent.feature
repository/tests/behat/cancel_add_file.feature
repository/tests/behat/create_shortcut.feature
repository/tests/behat/delete_files.feature
repository/tests/behat/overwrite_file.feature
repository/upload/tests/behat/upload_file.feature
theme/arialist/style/pagelayout.css
theme/base/style/blocks.css
theme/base/style/core.css
theme/base/style/filemanager.css
theme/base/style/message.css
theme/base/style/pagelayout.css
theme/binarius/style/pagelayout.css
theme/bootstrapbase/config.php
theme/bootstrapbase/less/moodle/buttons.less
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/filemanager.less
theme/bootstrapbase/less/moodle/message.less
theme/bootstrapbase/less/moodle/modules.less
theme/bootstrapbase/less/moodle/responsive.less
theme/bootstrapbase/style/moodle.css
theme/brick/style/pagelayout.css
theme/clean/config.php
theme/fusion/style/pagelayout.css
theme/magazine/style/layout.css
theme/nimble/style/pagelayout.css
theme/nonzero/style/pagelayout.css
theme/overlay/style/pagelayout.css
theme/sky_high/style/pagelayout.css
user/profile.php
user/tests/behat/add_blocks.feature [new file with mode: 0644]
user/tests/behat/reset_page.feature [new file with mode: 0644]
version.php

index b276f44..5cde0e8 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_admin @_only_local
+@core @core_admin @_file_upload
 Feature: Upload users
   In order to add users to the system
   As an admin
index 8f21a72..558c848 100644 (file)
@@ -1,4 +1,4 @@
-@tool @tool_behat @_only_local
+@tool @tool_behat
 Feature: Set up contextual data for tests
   In order to write tests quickly
   As a developer
diff --git a/admin/tool/behat/upgrade.txt b/admin/tool/behat/upgrade.txt
new file mode 100644 (file)
index 0000000..e677b90
--- /dev/null
@@ -0,0 +1,6 @@
+This files describes API changes in the assign code.
+
+=== 2.7 ===
+
+* @_only_local tag used in .feature files replaced by @_file_upload tag
+* @_alerts tag used in .feature files replaced by @_alert tag
index f471b23..2c1d933 100644 (file)
@@ -638,10 +638,10 @@ function tool_qeupgradehelper_load_question($questionid, $quizid) {
     global $CFG, $DB;
 
     $question = $DB->get_record_sql('
-            SELECT q.*, qqi.grade AS maxmark
+            SELECT q.*, qqi.maxmark
             FROM {question} q
-            JOIN {quiz_question_instances} qqi ON qqi.question = q.id
-            WHERE q.id = :questionid AND qqi.quiz = :quizid',
+            JOIN {quiz_question_instances} qqi ON qqi.questionid = q.id
+            WHERE q.id = :questionid AND qqi.quizid = :quizid',
             array('questionid' => $questionid, 'quizid' => $quizid));
 
     if (tool_qeupgradehelper_is_upgraded()) {
index ce2c98b..c1bf56f 100644 (file)
@@ -1,4 +1,4 @@
-@tool @tool_uploadcourse @_only_local
+@tool @tool_uploadcourse @_file_upload
 Feature: An admin can create courses using a CSV file
   In order to create courses using a CSV file
   As an admin
index 457f07d..4ac4f4e 100644 (file)
@@ -1,4 +1,4 @@
-@tool @tool_uploadcourse @_only_local
+@tool @tool_uploadcourse @_file_upload
 Feature: An admin can update courses using a CSV file
   In order to update courses using a CSV file
   As an admin
index fb9b9e1..5b0ae4c 100644 (file)
@@ -862,6 +862,15 @@ class convert_path {
     /**
      * Constructor
      *
+     * The optional recipe array can have three keys, and for each key, the value is another array.
+     * - newfields    => array fieldname => defaultvalue indicates fields that have been added to the table,
+     *                                                   and so should be added to the XML.
+     * - dropfields   => array fieldname                 indicates fieldsthat have been dropped from the table,
+     *                                                   and so can be dropped from the XML.
+     * - renamefields => array oldname => newname        indicates fieldsthat have been renamed in the table,
+     *                                                   and so should be renamed in the XML.
+     * {@line moodle1_course_outline_handler} is a good example that uses all of these.
+     *
      * @param string $name name of the element
      * @param string $path path of the element
      * @param array $recipe basic description of the structure conversion
index 930a26a..cba5771 100644 (file)
@@ -2833,6 +2833,12 @@ class restore_activity_grades_structure_step extends restore_structure_step {
         }
         // no need to save any grade_letter mapping
     }
+
+    public function after_restore() {
+        // Fix grade item's sortorder after restore, as it might have duplicates.
+        $courseid = $this->get_task()->get_courseid();
+        grade_item::fix_duplicate_sortorder($courseid);
+    }
 }
 
 
index ad9d427..1941738 100644 (file)
@@ -45,7 +45,7 @@ class edit_backpack_form extends moodleform {
         $mform->addElement('html', html_writer::tag('span', '', array('class' => 'notconnected', 'id' => 'connection-error')));
         $mform->addElement('header', 'backpackheader', get_string('backpackconnection', 'badges'));
         $mform->addHelpButton('backpackheader', 'backpackconnection', 'badges');
-        $mform->addElement('static', 'url', get_string('url'), BADGE_BACKPACKURL);
+        $mform->addElement('static', 'url', get_string('url'), 'http://' . BADGE_BACKPACKURL);
         $status = html_writer::tag('span', get_string('notconnected', 'badges'),
             array('class' => 'notconnected', 'id' => 'connection-status'));
         $mform->addElement('static', 'status', get_string('status'), $status);
@@ -67,7 +67,7 @@ class edit_backpack_form extends moodleform {
         $mform->addElement('hidden', 'userid', $USER->id);
         $mform->setType('userid', PARAM_INT);
 
-        $mform->addElement('hidden', 'backpackurl', BADGE_BACKPACKURL);
+        $mform->addElement('hidden', 'backpackurl', 'http://' . BADGE_BACKPACKURL);
         $mform->setType('backpackurl', PARAM_URL);
 
     }
@@ -118,7 +118,7 @@ class edit_collections_form extends moodleform {
 
         $mform->addElement('header', 'backpackheader', get_string('backpackconnection', 'badges'));
         $mform->addHelpButton('backpackheader', 'backpackconnection', 'badges');
-        $mform->addElement('static', 'url', get_string('url'), BADGE_BACKPACKURL);
+        $mform->addElement('static', 'url', get_string('url'), 'http://' . BADGE_BACKPACKURL);
 
         $status = html_writer::tag('span', get_string('connected', 'badges'), array('class' => 'connected'));
         $mform->addElement('static', 'status', get_string('status'), $status);
index bbcc78f..e7616ec 100644 (file)
@@ -29,6 +29,7 @@ define('AJAX_SCRIPT', true);
 require_once(dirname(dirname(__FILE__)) . '/config.php');
 require_once($CFG->dirroot . '/badges/lib/backpacklib.php');
 require_once($CFG->libdir . '/filelib.php');
+require_once($CFG->libdir . '/badgeslib.php');
 
 require_sesskey();
 require_login();
@@ -86,7 +87,7 @@ if (!isset($data->status) || $data->status != 'okay') {
 
 // Make sure email matches a backpack.
 $check = new stdClass();
-$check->backpackurl = BADGE_BACKPACKURL;
+$check->backpackurl = 'http://' . BADGE_BACKPACKURL;
 $check->email = $data->email;
 
 $bp = new OpenBadgesBackpackHandler($check);
@@ -105,7 +106,7 @@ if (isset($request->status) && $request->status == 'missing') {
 $obj = new stdClass();
 $obj->userid = $USER->id;
 $obj->email = $data->email;
-$obj->backpackurl = BADGE_BACKPACKURL;
+$obj->backpackurl = 'http://' . BADGE_BACKPACKURL;
 $obj->backpackuid = $backpackuid;
 $obj->autosync = 0;
 $obj->password = '';
index 6e6b3da..4ecf084 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
-/*
- * URL of backpack. Currently only the Open Badges backpack
- * is supported.
- */
-define('BADGE_BACKPACKURL', 'http://backpack.openbadges.org');
-
 global $CFG;
 require_once($CFG->libdir . '/filelib.php');
 
index 64bf704..7371491 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_badges @_only_local
+@core @core_badges @_file_upload
 Feature: Add badges to the system
   In order to give badges to users for their achievements
   As an admin
index 458df34..faf198c 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_badges @_only_local
+@core @core_badges @_file_upload
 Feature: Award badges
   In order to award badges to users for their achievements
   As an admin
index bab46ee..8dd1f87 100644 (file)
@@ -13,3 +13,5 @@ information provided here is intended especially for developers.
   allows to indicate that a badge should be archived instead of fully deleted.
   If this parameter is set to FALSE, a badge will all its information, criteria,
   and awards will be removed from the database.
+* BADGE_BACKPACKURL constant has been moved from badges/lib/backpacklib.php to lib/badgeslib.php, and URI scheme
+  name ('http://') has been removed.
index f0ed3f5..50df25f 100644 (file)
@@ -1,4 +1,4 @@
-@block @block_activity_modules @_only_local
+@block @block_activity_modules
 Feature: Block activity modules
   In order to overview activity modules in a course
   As a manager
@@ -13,7 +13,7 @@ Feature: Block activity modules
     And I click on "//a[@title=\"Show\"]" "xpath_element" in the "Feedback" "table_row"
 
   Scenario: Add activities block on the frontpage
-    And the following "activities" exists:
+    Given the following "activities" exists:
       | activity   | name                        | intro                              | course               | idnumber    |
       | assign     | Frontpage assignment name   | Frontpage assignment description   | Acceptance test site | assign0     |
       | book       | Frontpage book name         | Frontpage book description         | Acceptance test site | book0       |
diff --git a/blocks/html/styles.css b/blocks/html/styles.css
deleted file mode 100644 (file)
index de90b14..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-.block.block_html .content {padding:0;}
-.block.block_html .content .no-overflow {padding:4px;}
\ No newline at end of file
index c3fa178..6dc243e 100644 (file)
@@ -16,7 +16,9 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Manage files in folder in private area - to be replaced by something better hopefully....
+ * Manage files in folder in private area.
+ *
+ * This page is not used and now redirects to the page to manage the private files.
  *
  * @package   block_private_files
  * @copyright 2010 Petr Skoda (http://skodak.org)
  */
 
 require('../../config.php');
-require_once("$CFG->dirroot/blocks/private_files/edit_form.php");
-require_once("$CFG->dirroot/repository/lib.php");
-
-require_login();
-if (isguestuser()) {
-    die();
-}
-//TODO: add capability check here!
-
-$context = context_user::instance($USER->id);
-$title = get_string('privatefiles', 'block_private_files');
-$struser = get_string('user');
-
-$PAGE->set_url('/blocks/private_files/edit.php');
-$PAGE->set_context($context);
-$PAGE->set_title($title);
-$PAGE->set_heading($title);
-$PAGE->set_pagelayout('mydashboard');
-$PAGE->set_pagetype('user-private-files');
-
-$data = new stdClass();
-$options = array('subdirs'=>1, 'maxbytes'=>$CFG->userquota, 'maxfiles'=>-1, 'accepted_types'=>'*');
-file_prepare_standard_filemanager($data, 'files', $options, $context, 'user', 'private', 0);
-
-$mform = new block_private_files_form(null, array('data'=>$data, 'options'=>$options));
-
-if ($mform->is_cancelled()) {
-    redirect(new moodle_url('/my/'));
-
-} else if ($formdata = $mform->get_data()) {
-    $formdata = file_postupdate_standard_filemanager($formdata, 'files', $options, $context, 'user', 'private', 0);
-    redirect(new moodle_url('/my/'));
-}
 
-echo $OUTPUT->header();
-echo $OUTPUT->box_start('generalbox');
-$mform->display();
-echo $OUTPUT->box_end();
-echo $OUTPUT->footer();
+redirect(new moodle_url('/user/files.php'));
index 11e1ba8..64321bb 100644 (file)
@@ -23,7 +23,6 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['managemyfiles'] = 'Manage my files';
 $string['pluginname'] = 'My private files';
 $string['privatefiles'] = 'Private files';
 $string['private_files:addinstance'] = 'Add a new private files block';
index 3aaa309..e829a25 100644 (file)
@@ -72,6 +72,8 @@ class block_site_main_menu extends block_list {
             $strmovefull = strip_tags(get_string('movefull', '', "'$USER->activitycopyname'"));
             $strcancel= get_string('cancel');
             $stractivityclipboard = $USER->activitycopyname;
+        } else {
+            $strmove = get_string('move');
         }
         $editbuttons = '';
 
@@ -90,18 +92,12 @@ class block_site_main_menu extends block_list {
                 if (!$ismoving) {
                     $actions = course_get_cm_edit_actions($mod, -1);
 
-                    // Add the action move.
-                    $modcontext = context_module::instance($mod->id);
-                    $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
-                    if ($hasmanageactivities) {
-                        $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
-                        $actions['move'] = new action_menu_link_primary(
-                            new moodle_url($baseurl, array('copy' => $mod->id)),
-                            new pix_icon('t/move', get_string('move'), 'moodle', array('class' => 'iconsmall', 'title' => '')),
-                            null,
-                            array('title' => get_string('move'))
-                        );
-                    }
+                    // Prepend list of actions with the 'move' action.
+                    $actions = array('move' => new action_menu_link_primary(
+                        new moodle_url('/course/mod.php', array('sesskey' => sesskey(), 'copy' => $mod->id)),
+                        new pix_icon('t/move', $strmove, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+                        $strmove
+                    )) + $actions;
 
                     $editbuttons = html_writer::tag('div',
                         $courserenderer->course_section_cm_edit_actions($actions, $mod, array('donotenhance' => true)),
index 271eca5..dd8f92f 100644 (file)
@@ -1,7 +1,7 @@
 .block_site_main_menu li { clear: both; }
 .block_site_main_menu li .column { width: 100%; }
-.block_site_main_menu li .buttons { float: right; margin-top: 3px;}
+.block_site_main_menu li .buttons { float: right; margin: 0; }
 .dir-rtl .block_site_main_menu li .buttons { float: left; }
-.block_site_main_menu li .buttons a img{ vertical-align: text-bottom; margin: 0 3px;}
+.block_site_main_menu li .buttons a img{ vertical-align: text-bottom;}
 .block_site_main_menu .footer { margin-top: 1em; }
 .block_site_main_menu .section_add_menus noscript div { display: inline;}
index 96f7a90..e7fce57 100644 (file)
@@ -74,6 +74,8 @@ class block_social_activities extends block_list {
             $strmovefull = strip_tags(get_string('movefull', '', "'$USER->activitycopyname'"));
             $strcancel= get_string('cancel');
             $stractivityclipboard = $USER->activitycopyname;
+        } else {
+            $strmove = get_string('move');
         }
         $editbuttons = '';
 
@@ -91,8 +93,18 @@ class block_social_activities extends block_list {
                 }
                 if (!$ismoving) {
                     $actions = course_get_cm_edit_actions($mod, -1);
-                    $editbuttons = '<br />'.
-                            $courserenderer->course_section_cm_edit_actions($actions, $mod);
+
+                    // Prepend list of actions with the 'move' action.
+                    $actions = array('move' => new action_menu_link_primary(
+                        new moodle_url('/course/mod.php', array('sesskey' => sesskey(), 'copy' => $mod->id)),
+                        new pix_icon('t/move', $strmove, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+                        $strmove
+                    )) + $actions;
+
+                    $editbuttons = html_writer::tag('div',
+                        $courserenderer->course_section_cm_edit_actions($actions, $mod, array('donotenhance' => true)),
+                        array('class' => 'buttons')
+                    );
                 } else {
                     $editbuttons = '';
                 }
diff --git a/blocks/social_activities/styles.css b/blocks/social_activities/styles.css
new file mode 100644 (file)
index 0000000..003155a
--- /dev/null
@@ -0,0 +1,5 @@
+.block_social_activities li { clear: both; }
+.block_social_activities li .column { width: 100%; }
+.block_social_activities li .buttons { float: right; margin: 0; }
+.dir-rtl .block_social_activities li .buttons { float: left; }
+.block_social_activities li .buttons a img{ vertical-align: text-bottom;}
index eb0ea17..2213329 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_cohort @_only_local
+@core @core_cohort @_file_upload
 Feature: Upload users to a cohort
   In order to quickly fill site-wide groups with users
   As an admin
index d56d954..606d335 100644 (file)
@@ -61,7 +61,8 @@ class comment_manager {
         }
         $comments = array();
 
-        $sql = "SELECT c.id, c.contextid, c.itemid, c.commentarea, c.userid, c.content, u.firstname, u.lastname, c.timecreated
+        $usernamefields = get_all_user_name_fields(true, 'u');
+        $sql = "SELECT c.id, c.contextid, c.itemid, c.commentarea, c.userid, c.content, $usernamefields, c.timecreated
                   FROM {comments} c
                   JOIN {user} u
                        ON u.id=c.userid
@@ -74,8 +75,9 @@ class comment_manager {
             $item->time = userdate($item->timecreated);
             $item->content = format_text($item->content, FORMAT_MOODLE, $formatoptions);
             // Unset fields not related to the comment
-            unset($item->firstname);
-            unset($item->lastname);
+            foreach (get_all_user_name_fields() as $namefield) {
+                unset($item->$namefield);
+            }
             unset($item->timecreated);
             // Record the comment
             $comments[] = $item;
index 8e700b1..52e7a26 100644 (file)
@@ -37,13 +37,13 @@ Feature: Restrict activity availability through date conditions
       | assignsubmission_file_enabled | 0 |
       | id_availablefrom_day | 31 |
       | id_availablefrom_month | 12 |
-      | id_availablefrom_year | 2050 |
+      | id_availablefrom_year | 2037 |
       | id_showavailability | 1 |
     And I press "Save and return to course"
     And I log out
     When I log in as "student1"
     And I follow "Course 1"
-    Then I should see "Available from 31 December 2050."
+    Then I should see "Available from 31 December 2037."
     And "Test assignment 1" activity should be hidden
     And I log out
 
index c1e9a14..aade33b 100644 (file)
@@ -1979,7 +1979,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
     }
 
     // Indent.
-    if ($hasmanageactivities) {
+    if ($hasmanageactivities && $indent >= 0) {
         $indentlimits = new stdClass();
         $indentlimits->min = 0;
         $indentlimits->max = 16;
index 0e58239..56101a1 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_course @_alerts
+@core @core_course @_alert
 Feature: Course activity controls works as expected
   In order to manage my course's activities
   As a teacher
index 314db1e..58e1fcd 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_files @_only_local
+@core @core_files
 Feature: Course files
   In order to add legacy files
   As a user
index 59906d7..a73da44 100644 (file)
@@ -179,6 +179,9 @@ table#user-grades .userpic {
 }
 table#user-grades .quickfeedback {
     border: 1px dashed #000;
+    width: auto;
+    margin: 0;
+    padding: 0;
     margin-left: 10px;
 }
 .dir-rtl table#user-grades .quickfeedback {
@@ -240,8 +243,9 @@ table#user-grades th.courseitem,
     border-style: solid;
     border-width: 0 1px 1px;
 }
-.path-grade-report-grader table td.topleft {
-    border-bottom: 0;
+.path-grade-report-grader .left_scroller table td.topleft {
+    background-color: #fff;
+    border-bottom-color: #cecece;
 }
 table#user-grades td.topleft {
     background-color: #fff;
@@ -287,6 +291,9 @@ table#user-grades td.topleft {
 }
 .path-grade-report-grader td input.text {
     border: 1px solid #666;
+    width: auto;
+    margin: 0;
+    padding: 0;
 }
 .path-grade-report-grader td input.submit {
     margin: 10px 10px 0px 10px;
@@ -508,6 +515,9 @@ table#user-grades td.controls,
 .path-grade-report-grader .grade_icons {
     margin-bottom: .3em;
 }
+.path-grade-report-grader tr.controls .grade_icons {
+    margin-bottom: 0;
+}
 .path-grade-report-grader .yui3-overlay {
     background-color: #FFEE69;
     border-color: #D4C237 #A6982B #A6982B;
index 64cdfd3..e570ccc 100644 (file)
@@ -50,6 +50,11 @@ $strgroups           = get_string('groups');
 $strparticipants     = get_string('participants');
 $strautocreategroups = get_string('autocreategroups', 'group');
 
+$PAGE->set_title($strgroups);
+$PAGE->set_heading($course->fullname. ': '.$strgroups);
+$PAGE->set_pagelayout('standard');
+navigation_node::override_active_url(new moodle_url('/group/index.php', array('id' => $courseid)));
+
 // Print the page and form
 $preview = '';
 $error = '';
@@ -233,9 +238,6 @@ $PAGE->navbar->add($strparticipants, new moodle_url('/user/index.php', array('id
 $PAGE->navbar->add($strgroups, new moodle_url('/group/index.php', array('id'=>$courseid)));
 $PAGE->navbar->add($strautocreategroups);
 
-/// Print header
-$PAGE->set_title($strgroups);
-$PAGE->set_heading($course->fullname. ': '.$strgroups);
 echo $OUTPUT->header();
 echo $OUTPUT->heading($strautocreategroups);
 
index b58bb0c..88430a5 100644 (file)
@@ -32,6 +32,7 @@ $groupids = required_param('groups', PARAM_SEQUENCE);
 $confirm = optional_param('confirm', 0, PARAM_BOOL);
 
 $PAGE->set_url('/group/delete.php', array('courseid'=>$courseid,'groups'=>$groupids));
+$PAGE->set_pagelayout('standard');
 
 // Make sure course is OK and user has access to manage groups
 if (!$course = $DB->get_record('course', array('id' => $courseid))) {
index b6aa753..3a61893 100644 (file)
@@ -74,6 +74,12 @@ require_login($course);
 $context = context_course::instance($course->id);
 require_capability('moodle/course:managegroups', $context);
 
+$strgroups = get_string('groups');
+$PAGE->set_title($strgroups);
+$PAGE->set_heading($course->fullname . ': '.$strgroups);
+$PAGE->set_pagelayout('standard');
+navigation_node::override_active_url(new moodle_url('/group/index.php', array('id' => $course->id)));
+
 $returnurl = $CFG->wwwroot.'/group/index.php?id='.$course->id.'&group='.$id;
 
 // Prepare the description editor: We do support files for group descriptions
@@ -123,8 +129,6 @@ $PAGE->navbar->add($strgroups, new moodle_url('/group/index.php', array('id'=>$c
 $PAGE->navbar->add($strheading);
 
 /// Print header
-$PAGE->set_title($strgroups);
-$PAGE->set_heading($course->fullname . ': '.$strgroups);
 echo $OUTPUT->header();
 echo '<div id="grouppicture">';
 if ($id) {
index bdfe58a..aa20ac2 100644 (file)
@@ -66,8 +66,13 @@ require_login($course);
 $context = context_course::instance($course->id);
 require_capability('moodle/course:managegroups', $context);
 
-$returnurl = $CFG->wwwroot.'/group/groupings.php?id='.$course->id;
+$strgroupings = get_string('groupings', 'group');
+$PAGE->set_title($strgroupings);
+$PAGE->set_heading($course->fullname. ': '.$strgroupings);
+$PAGE->set_pagelayout('standard');
+navigation_node::override_active_url(new moodle_url('/group/index.php', array('id' => $course->id)));
 
+$returnurl = $CFG->wwwroot.'/group/groupings.php?id='.$course->id;
 
 if ($id and $delete) {
     if (!empty($grouping->idnumber) && !has_capability('moodle/course:changeidnumber', $context)) {
@@ -126,9 +131,7 @@ if ($editform->is_cancelled()) {
 
 }
 
-$strgroupings    = get_string('groupings', 'group');
 $strparticipants = get_string('participants');
-
 if ($id) {
     $strheading = get_string('editgroupingsettings', 'group');
 } else {
@@ -140,8 +143,6 @@ $PAGE->navbar->add($strgroupings, new moodle_url('/group/groupings.php', array('
 $PAGE->navbar->add($strheading);
 
 /// Print header
-$PAGE->set_title($strgroupings);
-$PAGE->set_heading($course->fullname. ': '.$strgroupings);
 echo $OUTPUT->header();
 echo $OUTPUT->heading($strheading);
 $editform->display();
index 1511619..5e83918 100644 (file)
@@ -144,7 +144,7 @@ $strparticipants = get_string('participants');
 /// Print header
 $PAGE->set_title($strgroups);
 $PAGE->set_heading($course->fullname);
-$PAGE->set_pagelayout('admin');
+$PAGE->set_pagelayout('standard');
 echo $OUTPUT->header();
 
 // Add tabs
index ed021a6..fef297a 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_group @_only_local
+@core @core_group @_file_upload
 Feature: Importing of groups and groupings
   In order to import groups and grouping
   As a teacher
index 26966ec..e1d4908 100644 (file)
@@ -65,6 +65,8 @@ $string['error7023'] = 'The remote site has tried to decrypt your message with a
 $string['error7024'] = 'You send an unencrypted message to the remote site, but the remote site doesn\'t accept unencrypted communication from your site. This is very unexpected; you should probably file a bug if this occurs (giving as much information as possible about the application versions in question, etc.';
 $string['error7026'] = 'The key that your message was signed with differs from the key that the remote host has on file for your server. Further, the remote host attempted to fetch your current key and failed to do so. Please manually re-key with the remote host and try again.';
 $string['error709'] = 'The remote site failed to obtain a SSL key from you.';
+$string['eventaccesscontrolcreated'] = 'Access control created';
+$string['eventaccesscontrolupdated'] = 'Access control updated';
 $string['expired'] = 'This key expired on';
 $string['expires'] = 'Valid until';
 $string['expireyourkey'] = 'Delete this key';
index 9e3dcef..1a9e518 100644 (file)
@@ -731,6 +731,7 @@ $string['eventcourserestored'] = 'Course restored';
 $string['eventcourseupdated'] = 'Course updated';
 $string['eventcoursesectionupdated'] = ' Course section updated';
 $string['eventcoursemoduleinstancelistviewed'] = 'Course module instance list viewed';
+$string['eventemailfailed'] = 'Email failed to send';
 $string['eventusercreated'] = 'User created';
 $string['eventuserdeleted'] = 'User deleted';
 $string['eventuserlistviewed'] = 'User list viewed';
index 5fdcb5e..41a4688 100644 (file)
@@ -12,3 +12,5 @@ $string['defaultprofilepage'] = 'Default profile page';
 $string['addpage'] = 'Add page';
 $string['delpage'] = 'Delete page';
 $string['managepages'] = 'Manage pages';
+$string['resetpage'] = 'Reset page to default';
+$string['reseterror'] = 'There was an error resetting your page';
\ No newline at end of file
index d2923b1..15d32ad 100644 (file)
@@ -380,6 +380,7 @@ $string['questionbehavioursorder'] = 'Question behaviours order';
 $string['questionbehavioursorderexplained'] = 'Enter a comma separated list of behaviours in the order you want them to appear in dropdown menu';
 $string['questionidmismatch'] = 'Question ids mismatch';
 $string['questionname'] = 'Question name';
+$string['questionnamecopy'] = '{$a} (copy)';
 $string['questionpreviewdefaults'] = 'Question preview defaults';
 $string['questionpreviewdefaults_desc'] = 'These defaults are used when a user first previews a question in the question bank. Once a user has previewed a question, their personal preferences are stored as user preferences.';
 $string['questions'] = 'Questions';
index ab24525..7b2c820 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
@@ -34,16 +33,16 @@ $contextid = required_param('contextid', PARAM_INT);
 $subpage = optional_param('subpage', '', PARAM_ALPHANUMEXT);
 $cmid = optional_param('cmid', null, PARAM_INT);
 $action = optional_param('action', '', PARAM_ALPHA);
-// Params for blocks-move actions
-$bui_moveid = optional_param('bui_moveid', 0, PARAM_INT);
-$bui_newregion = optional_param('bui_newregion', '', PARAM_ALPHAEXT);
-$bui_beforeid = optional_param('bui_beforeid', 0, PARAM_INT);
+// Params for blocks-move actions.
+$buimoveid = optional_param('bui_moveid', 0, PARAM_INT);
+$buinewregion = optional_param('bui_newregion', '', PARAM_ALPHAEXT);
+$buibeforeid = optional_param('bui_beforeid', 0, PARAM_INT);
 
-// Setting pagetype and URL
+// Setting pagetype and URL.
 $PAGE->set_pagetype($pagetype);
 $PAGE->set_url('/lib/ajax/blocks.php', array('courseid' => $courseid, 'pagelayout' => $pagelayout, 'pagetype' => $pagetype));
 
-// Verifying login and session
+// Verifying login and session.
 $cm = null;
 if (!is_null($cmid)) {
     $cm = get_coursemodule_from_id(null, $cmid, $courseid, false, MUST_EXIST);
@@ -54,7 +53,7 @@ require_sesskey();
 // Set context from ID, so we don't have to guess it from other info.
 $PAGE->set_context(context::instance_by_id($contextid));
 
-// Setting layout to replicate blocks configuration for the page we edit
+// Setting layout to replicate blocks configuration for the page we edit.
 $PAGE->set_pagelayout($pagelayout);
 $PAGE->set_subpage($subpage);
 $pagetype = explode('-', $pagetype);
@@ -78,21 +77,27 @@ switch ($pagetype[0]) {
         break;
 }
 
-echo $OUTPUT->header(); // send headers
+// Send headers.
+echo $OUTPUT->header();
 
 switch ($action) {
     case 'move':
-        // Loading blocks and instances for the region
+        // Loading blocks and instances for the region.
         $PAGE->blocks->load_blocks();
-        $instances = $PAGE->blocks->get_blocks_for_region($bui_newregion);
+        $instances = $PAGE->blocks->get_blocks_for_region($buinewregion);
 
-        $bui_newweight = null;
-        if ($bui_beforeid == 0) {
-            // Moving to very bottom
-            $last = end($instances);
-            $bui_newweight = $last->instance->weight + 1;
+        $buinewweight = null;
+        if ($buibeforeid == 0) {
+            if (count($instances) === 0) {
+                // Moving the block into an empty region. Give it the default weight.
+                $buinewweight = 0;
+            } else {
+                // Moving to very bottom.
+                $last = end($instances);
+                $buinewweight = $last->instance->weight + 1;
+            }
         } else {
-            // Moving somewhere
+            // Moving somewhere.
             $lastweight = 0;
             $lastblock = 0;
             $first = reset($instances);
@@ -101,14 +106,13 @@ switch ($action) {
             }
 
             foreach ($instances as $instance) {
-                if ($instance->instance->id == $bui_beforeid) {
-                    // Location found, just calculate weight like in
-                    // block_manager->create_block_contents() and quit the loop.
-                    if ($lastblock == $bui_moveid) {
-                        // same block, same place - nothing to move
+                if ($instance->instance->id == $buibeforeid) {
+                    // Location found, just calculate weight like in block_manager->create_block_contents() and quit the loop.
+                    if ($lastblock == $buimoveid) {
+                        // Same block, same place - nothing to move.
                         break;
                     }
-                    $bui_newweight = ($lastweight + $instance->instance->weight) / 2;
+                    $buinewweight = ($lastweight + $instance->instance->weight) / 2;
                     break;
                 }
                 $lastweight = $instance->instance->weight;
@@ -116,10 +120,10 @@ switch ($action) {
             }
         }
 
-        // Move block if we need
-        if (isset($bui_newweight)) {
-            // Nasty hack
-            $_POST['bui_newweight'] = $bui_newweight;
+        // Move block if we need.
+        if (isset($buinewweight)) {
+            // Nasty hack.
+            $_POST['bui_newweight'] = $buinewweight;
             $PAGE->blocks->process_url_move();
         }
         break;
index 957d0a9..457f8d9 100644 (file)
@@ -112,16 +112,16 @@ try {
     $html = ob_get_contents();
     ob_end_clean();
 } catch (Exception $e) {
-    die('Error: '.$e->getMessage());
+    throw new coding_exception('Error: '.$e->getMessage());
 }
 
 // Check if the buffer contianed anything if it did ERROR!
 if (trim($html) !== '') {
-    die('Errors were encountered while producing the navigation branch'."\n\n\n".$html);
+    throw new coding_exception('Errors were encountered while producing the navigation branch'."\n\n\n".$html);
 }
 // Check that branch isn't empty... if it is ERROR!
-if (empty($branch) || $branch->nodetype !== navigation_node::NODETYPE_BRANCH) {
-    die('No further information available for this branch');
+if (empty($branch) || ($branch->nodetype !== navigation_node::NODETYPE_BRANCH && !$branch->isexpandable)) {
+    throw new coding_exception('No further information available for this branch');
 }
 
 // Prepare an XML converter for the branch
index 0d1f928..c24400e 100644 (file)
@@ -93,6 +93,11 @@ define('BADGE_MESSAGE_DAILY', 2);
 define('BADGE_MESSAGE_WEEKLY', 3);
 define('BADGE_MESSAGE_MONTHLY', 4);
 
+/*
+ * URL of backpack. Currently only the Open Badges backpack is supported.
+ */
+define('BADGE_BACKPACKURL', 'backpack.openbadges.org');
+
 /**
  * Class that represents badge.
  *
@@ -1177,7 +1182,7 @@ function badges_check_backpack_accessibility() {
     // Using fake assertion url to check whether backpack can access the web site.
     $fakeassertion = new moodle_url('/badges/assertion.php', array('b' => 'abcd1234567890'));
 
-    // Curl request to http://backpack.openbadges.org/baker.
+    // Curl request to backpack baker.
     $curl = new curl();
     $options = array(
         'FRESH_CONNECT' => true,
@@ -1185,7 +1190,7 @@ function badges_check_backpack_accessibility() {
         'HEADER' => 0,
         'CONNECTTIMEOUT' => 2,
     );
-    $location = 'http://backpack.openbadges.org/baker';
+    $location = 'http://' . BADGE_BACKPACKURL . '/baker';
     $out = $curl->get($location, array('assertion' => $fakeassertion->out(false)), $options);
 
     $data = json_decode($out);
@@ -1254,7 +1259,7 @@ function badges_setup_backpack_js() {
     if (!empty($CFG->badges_allowexternalbackpack)) {
         $PAGE->requires->string_for_js('error:backpackproblem', 'badges');
         $protocol = (strpos($CFG->wwwroot, 'https://') === 0) ? 'https://' : 'http://';
-        $PAGE->requires->js(new moodle_url($protocol . 'backpack.openbadges.org/issuer.js'), true);
+        $PAGE->requires->js(new moodle_url($protocol . BADGE_BACKPACKURL . '/issuer.js'), true);
         $PAGE->requires->js('/badges/backpack.js', true);
     }
 }
index a889e3e..8f49ae1 100644 (file)
@@ -49,7 +49,7 @@ abstract class assessable_submitted extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index 46aa825..efdc7ec 100644 (file)
@@ -56,7 +56,7 @@ abstract class assessable_uploaded extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index 6cbcbe2..d1cbe32 100644 (file)
@@ -40,7 +40,7 @@ defined('MOODLE_INTERNAL') || die();
  * @property-read string $objecttable name of database table where is object record stored
  * @property-read int $objectid optional id of the object
  * @property-read string $crud letter indicating event type
- * @property-read int $level log level (number between 1 and 100)
+ * @property-read int $edulevel log level (one of the constants LEVEL_)
  * @property-read int $contextid
  * @property-read int $contextlevel
  * @property-read int $contextinstanceid
@@ -101,7 +101,7 @@ abstract class base implements \IteratorAggregate {
 
     /** @var array list of event properties */
     private static $fields = array(
-        'eventname', 'component', 'action', 'target', 'objecttable', 'objectid', 'crud', 'level', 'contextid',
+        'eventname', 'component', 'action', 'target', 'objecttable', 'objectid', 'crud', 'edulevel', 'contextid',
         'contextlevel', 'contextinstanceid', 'userid', 'courseid', 'relateduserid', 'other',
         'timecreated');
 
@@ -148,7 +148,7 @@ abstract class base implements \IteratorAggregate {
      * @throws \coding_exception
      */
     public static final function create(array $data = null) {
-        global $PAGE, $USER, $CFG;
+        global $USER, $CFG;
 
         $data = (array)$data;
 
@@ -161,6 +161,14 @@ abstract class base implements \IteratorAggregate {
         // Set static event data specific for child class.
         $event->init();
 
+        if (isset($event->data['level'])) {
+            if (!isset($event->data['edulevel'])) {
+                debugging('level property is deprecated, use edulevel property instead', DEBUG_DEVELOPER);
+                $event->data['edulevel'] = $event->data['level'];
+            }
+            unset($event->data['level']);
+        }
+
         // Set automatic data.
         $event->data['timecreated'] = time();
 
@@ -205,7 +213,7 @@ abstract class base implements \IteratorAggregate {
         // Warn developers if they do something wrong.
         if ($CFG->debugdeveloper) {
             static $automatickeys = array('eventname', 'component', 'action', 'target', 'contextlevel', 'contextinstanceid', 'timecreated');
-            static $initkeys = array('crud', 'level', 'objecttable');
+            static $initkeys = array('crud', 'level', 'objecttable', 'edulevel');
 
             foreach ($data as $key => $ignored) {
                 if ($key === 'context') {
@@ -234,7 +242,7 @@ abstract class base implements \IteratorAggregate {
      *
      * Set all required data properties:
      *  1/ crud - letter [crud]
-     *  2/ level - using a constant self::LEVEL_*.
+     *  2/ edulevel - using a constant self::LEVEL_*.
      *  3/ objecttable - name of database table if objectid specified
      *
      * Optionally it can set:
@@ -362,7 +370,7 @@ abstract class base implements \IteratorAggregate {
     /**
      * Return standardised event data as array.
      *
-     * @return array
+     * @return array All elements are scalars except the 'other' field which is array.
      */
     public function get_data() {
         return $this->data;
@@ -371,7 +379,9 @@ abstract class base implements \IteratorAggregate {
     /**
      * Return auxiliary data that was stored in logs.
      *
-     * TODO MDL-41331: Properly define this method once logging is finalised.
+     * List of standard properties:
+     *  - origin: IP number, cli,cron
+     *  - realuserid: id of the user when logged-in-as
      *
      * @return array the format is standardised by logging API
      */
@@ -425,8 +435,8 @@ abstract class base implements \IteratorAggregate {
         if (empty($this->data['crud'])) {
             throw new \coding_exception('crud must be specified in init() method of each method');
         }
-        if (!isset($this->data['level'])) {
-            throw new \coding_exception('level must be specified in init() method of each method');
+        if (!isset($this->data['edulevel'])) {
+            throw new \coding_exception('edulevel must be specified in init() method of each method');
         }
         if (!empty($this->data['objectid']) and empty($this->data['objecttable'])) {
             throw new \coding_exception('objecttable must be specified in init() method if objectid present');
@@ -439,9 +449,9 @@ abstract class base implements \IteratorAggregate {
             if (!in_array($this->data['crud'], array('c', 'r', 'u', 'd'), true)) {
                 debugging("Invalid event crud value specified.", DEBUG_DEVELOPER);
             }
-            if (!in_array($this->data['level'], array(self::LEVEL_OTHER, self::LEVEL_TEACHING, self::LEVEL_PARTICIPATING))) {
+            if (!in_array($this->data['edulevel'], array(self::LEVEL_OTHER, self::LEVEL_TEACHING, self::LEVEL_PARTICIPATING))) {
                 // Bitwise combination of levels is not allowed at this stage.
-                debugging('Event property level must a constant value, see event_base::LEVEL_*', DEBUG_DEVELOPER);
+                debugging('Event property edulevel must a constant value, see event_base::LEVEL_*', DEBUG_DEVELOPER);
             }
             if (self::$fields !== array_keys($this->data)) {
                 debugging('Number of event data fields must not be changed in event classes', DEBUG_DEVELOPER);
@@ -601,6 +611,10 @@ abstract class base implements \IteratorAggregate {
      * @return mixed
      */
     public function __get($name) {
+        if ($name === 'level') {
+            debugging('level property is deprecated, use edulevel property instead', DEBUG_DEVELOPER);
+            return $this->data['edulevel'];
+        }
         if (array_key_exists($name, $this->data)) {
             return $this->data[$name];
         }
@@ -630,6 +644,10 @@ abstract class base implements \IteratorAggregate {
      * @return bool
      */
     public function __isset($name) {
+        if ($name === 'level') {
+            debugging('level property is deprecated, use edulevel property instead', DEBUG_DEVELOPER);
+            return isset($this->data['edulevel']);
+        }
         return isset($this->data[$name]);
     }
 
index 0e7da1c..f09d4d7 100644 (file)
@@ -51,7 +51,7 @@ class blog_association_created extends \core\event\base {
         $this->context = \context_system::instance();
         $this->data['objecttable'] = 'blog_association';
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index d0119fa..6da91bb 100644 (file)
@@ -50,7 +50,7 @@ class blog_entries_viewed extends base {
     protected function init() {
         $this->context = \context_system::instance();
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index 4e1518a..149d843 100644 (file)
@@ -47,7 +47,7 @@ class blog_entry_created extends \core\event\base {
         $this->context = \context_system::instance();
         $this->data['objecttable'] = 'post';
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index b7275d5..f341739 100644 (file)
@@ -46,7 +46,7 @@ class blog_entry_deleted extends \core\event\base {
         $this->context = \context_system::instance();
         $this->data['objecttable'] = 'post';
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index 777ac2e..2530511 100644 (file)
@@ -45,7 +45,7 @@ class blog_entry_updated extends base {
         $this->context = \context_system::instance();
         $this->data['objecttable'] = 'post';
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index 0db4d7b..cfe435d 100644 (file)
@@ -41,7 +41,7 @@ class cohort_created extends base {
      */
     protected function init() {
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'cohort';
     }
 
index b43b110..0844565 100644 (file)
@@ -41,7 +41,7 @@ class cohort_deleted extends base {
      */
     protected function init() {
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'cohort';
     }
 
index a7e52eb..8dc153d 100644 (file)
@@ -41,7 +41,7 @@ class cohort_member_added extends base {
      */
     protected function init() {
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'cohort';
     }
 
index b3f47bd..e42868a 100644 (file)
@@ -42,7 +42,7 @@ class cohort_member_removed extends base {
      */
     protected function init() {
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'cohort';
     }
 
index f9b6a07..7ee1226 100644 (file)
@@ -41,7 +41,7 @@ class cohort_updated extends base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'cohort';
     }
 
index ffd8874..09935b8 100644 (file)
@@ -50,7 +50,7 @@ abstract class comment_created extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
         $this->data['objecttable'] = 'comments';
     }
 
index fca1472..2c20348 100644 (file)
@@ -50,7 +50,7 @@ abstract class comment_deleted extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
         $this->data['objecttable'] = 'comments';
     }
 
index 05618f0..a048d01 100644 (file)
@@ -44,7 +44,7 @@ abstract class comments_viewed extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index 17f0328..10c2594 100644 (file)
@@ -61,7 +61,7 @@ abstract class content_viewed extends base {
         global $PAGE;
 
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->context = $PAGE->context;
     }
 
index 33a4ea1..b049a8a 100644 (file)
@@ -33,7 +33,7 @@ class course_category_created extends base {
     protected function init() {
         $this->data['objecttable'] = 'course_categories';
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index a856ef1..881d84b 100644 (file)
@@ -44,7 +44,7 @@ class course_category_deleted extends base {
     protected function init() {
         $this->data['objecttable'] = 'course_categories';
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index 978c9e4..1a0055f 100644 (file)
@@ -36,7 +36,7 @@ class course_category_updated extends base {
     protected function init() {
         $this->data['objecttable'] = 'course_categories';
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index 6e42b60..300bcdc 100644 (file)
@@ -33,7 +33,7 @@ class course_completed extends base {
     protected function init() {
         $this->data['objecttable'] = 'course_completions';
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index 36a3d80..9ffa4f6 100644 (file)
@@ -40,7 +40,7 @@ class course_completion_updated extends base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index d5b6624..05a59d1 100644 (file)
@@ -39,7 +39,7 @@ class course_content_deleted extends base {
     protected function init() {
         $this->data['objecttable'] = 'course';
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
     }
 
     /**
index 57eb640..93e6bab 100644 (file)
@@ -40,7 +40,7 @@ class course_created extends base {
     protected function init() {
         $this->data['objecttable'] = 'course';
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
     }
 
     /**
index 0580e7c..357b9cd 100644 (file)
@@ -41,7 +41,7 @@ class course_deleted extends base {
     protected function init() {
         $this->data['objecttable'] = 'course';
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
     }
 
     /**
index 7436e83..86fe91f 100644 (file)
@@ -33,7 +33,7 @@ class course_module_completion_updated extends base {
     protected function init() {
         $this->data['objecttable'] = 'course_modules_completion';
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index 2c481a8..bc4d75f 100644 (file)
@@ -50,7 +50,7 @@ class course_module_created extends base {
     protected function init() {
         $this->data['objecttable'] = 'course_modules';
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
     }
 
     /**
index d00a43a..4c58a3d 100644 (file)
@@ -49,7 +49,7 @@ class course_module_deleted extends base {
     protected function init() {
         $this->data['objecttable'] = 'course_modules';
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
     }
 
     /**
index de0e553..cf94035 100644 (file)
@@ -52,7 +52,7 @@ abstract class course_module_instance_list_viewed extends base{
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         if (strstr($this->component, 'mod_') === false) {
             throw new \coding_exception('The event name or namespace is invalid.');
         } else {
index 99e61f8..9c4e1cf 100644 (file)
@@ -50,7 +50,7 @@ class course_module_updated extends base {
     protected function init() {
         $this->data['objecttable'] = 'course_modules';
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
     }
 
     /**
index 7b45a25..43e1504 100644 (file)
@@ -45,7 +45,7 @@ abstract class course_module_viewed extends base {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index 5c8f280..1b8fdb8 100644 (file)
@@ -74,7 +74,7 @@ class course_reset_ended extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index f28d6de..8a2be3c 100644 (file)
@@ -74,7 +74,7 @@ class course_reset_started extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index 8e7af36..02a9dd2 100644 (file)
@@ -43,7 +43,7 @@ class course_restored extends base {
     protected function init() {
         $this->data['objecttable'] = 'course';
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
     }
 
     /**
index 60760e5..bec63f3 100644 (file)
@@ -49,7 +49,7 @@ class course_section_updated extends base {
     protected function init() {
         $this->data['objecttable'] = 'course_sections';
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
     }
 
     /**
index 47ab3af..1ca5407 100644 (file)
@@ -43,7 +43,7 @@ class course_updated extends base {
     protected function init() {
         $this->data['objecttable'] = 'course';
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
     }
 
     /**
diff --git a/lib/classes/event/email_failed.php b/lib/classes/event/email_failed.php
new file mode 100644 (file)
index 0000000..40affbd
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Email failed event.
+ *
+ * @package    core
+ * @copyright  2013 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class email_failed extends base {
+
+    /**
+     * Initialise the event data.
+     */
+    protected function init() {
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventemailfailed');
+    }
+
+    /**
+     * Returns non-localised description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'Failed to send an email from the user with the id ' . $this->userid . ' to the user with the id ' .
+            $this->relateduserid . ' due to the following error: \'' . $this->other['errorinfo'] . '\'';
+    }
+
+    /**
+     * Return legacy data for add_to_log().
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        return array(SITEID, 'library', 'mailer', qualified_me(), 'ERROR: ' . $this->other['errorinfo']);
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     */
+    protected function validate_data() {
+        if (!isset($this->other['subject'])) {
+            throw new \coding_exception('The subject needs to be set in $other');
+        }
+        if (!isset($this->other['message'])) {
+            throw new \coding_exception('The message needs to be set in $other');
+        }
+        if (!isset($this->other['errorinfo'])) {
+            throw new \coding_exception('The error info needs to be set in $other');
+        }
+    }
+}
index f6478ca..ff92010 100644 (file)
@@ -86,7 +86,7 @@ class group_created extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'groups';
     }
 
index 6f0e437..e1cfca4 100644 (file)
@@ -86,7 +86,7 @@ class group_deleted extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'groups';
     }
 
index 6855e15..2c1cf28 100644 (file)
@@ -98,7 +98,7 @@ class group_member_added extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'groups';
     }
 
index eac1ec2..cc5fd26 100644 (file)
@@ -89,7 +89,7 @@ class group_member_removed extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'groups';
     }
 
index 51c1a12..8631ca4 100644 (file)
@@ -86,7 +86,7 @@ class group_updated extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'groups';
     }
 
index 1d331cf..b5c815a 100644 (file)
@@ -93,7 +93,7 @@ class grouping_created extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'groupings';
     }
 
index 92ccb1e..0176cc9 100644 (file)
@@ -93,7 +93,7 @@ class grouping_deleted extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'groupings';
     }
 
index 8d35b49..23c8e85 100644 (file)
@@ -93,7 +93,7 @@ class grouping_updated extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'groupings';
     }
 
diff --git a/lib/classes/event/mnet_access_control_created.php b/lib/classes/event/mnet_access_control_created.php
new file mode 100644 (file)
index 0000000..7f5baf1
--- /dev/null
@@ -0,0 +1,83 @@
+<?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/>.
+
+/**
+ * Mnet access control created event class.
+ *
+ * @package    core
+ * @copyright  2013 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class mnet_access_control_created extends base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'mnet_sso_access_control';
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventaccesscontrolcreated', 'mnet');
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('admin/mnet/access_control.php');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @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 legacy data for add_to_log().
+     *
+     * @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($this->courseid, 'admin/mnet', 'add', 'admin/mnet/access_control.php', 'SSO ACL: ' .
+            $mnetaccesscontrol->accessctrl . ' user \'' . $mnetaccesscontrol->username . '\' from ' .
+            $mnethost->name);
+    }
+}
diff --git a/lib/classes/event/mnet_access_control_updated.php b/lib/classes/event/mnet_access_control_updated.php
new file mode 100644 (file)
index 0000000..3e4151e
--- /dev/null
@@ -0,0 +1,83 @@
+<?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/>.
+
+/**
+ * Mnet access control updated event class.
+ *
+ * @package    core
+ * @copyright  2013 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class mnet_access_control_updated extends base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'mnet_sso_access_control';
+        $this->data['crud'] = 'u';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventaccesscontrolupdated', 'mnet');
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('admin/mnet/access_control.php');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @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 legacy data for add_to_log().
+     *
+     * @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($this->courseid, 'admin/mnet', 'update', 'admin/mnet/access_control.php', 'SSO ACL: ' .
+            $mnetaccesscontrol->accessctrl . ' user \'' . $mnetaccesscontrol->username . '\' from ' .
+            $mnethost->name);
+    }
+}
index e3ce2ab..bcfe424 100644 (file)
@@ -49,7 +49,7 @@ class note_created extends \core\event\base {
     protected function init() {
         $this->data['objecttable'] = 'post';
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index 52f9182..bf15884 100644 (file)
@@ -49,7 +49,7 @@ class note_deleted extends \core\event\base {
     protected function init() {
         $this->data['objecttable'] = 'post';
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index fb94617..4df0526 100644 (file)
@@ -49,7 +49,7 @@ class note_updated extends \core\event\base {
     protected function init() {
         $this->data['objecttable'] = 'post';
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index a298106..8b4d525 100644 (file)
@@ -48,7 +48,7 @@ class notes_viewed extends \core\event\content_viewed {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index abf1f10..b5f2dd1 100644 (file)
@@ -32,7 +32,7 @@ class role_allow_assign_updated extends base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index 0d16517..9451cfa 100644 (file)
@@ -32,7 +32,7 @@ class role_allow_override_updated extends base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index 602f2af..7b96dde 100644 (file)
@@ -32,7 +32,7 @@ class role_allow_switch_updated extends base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index f22fd16..cad0a1b 100644 (file)
@@ -38,7 +38,7 @@ class role_assigned extends base {
     protected function init() {
         $this->data['objecttable'] = 'role';
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index d0f188b..9431cd3 100644 (file)
@@ -36,7 +36,7 @@ class role_capabilities_updated extends base {
     protected function init() {
         $this->data['objecttable'] = 'role';
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index ae150c8..de3e963 100644 (file)
@@ -41,7 +41,7 @@ class role_deleted extends base {
     protected function init() {
         $this->data['objecttable'] = 'role';
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index f3d0850..4521ab3 100644 (file)
@@ -38,7 +38,7 @@ class role_unassigned extends base {
     protected function init() {
         $this->data['objecttable'] = 'role';
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index 892efd5..ec91dc3 100644 (file)
@@ -40,7 +40,7 @@ class user_created extends base {
     protected function init() {
         $this->data['objecttable'] = 'user';
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index fdc68c8..1637822 100644 (file)
@@ -50,7 +50,7 @@ class user_deleted extends base {
     protected function init() {
         $this->data['objecttable'] = 'user';
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index c5e0d0e..23539ca 100644 (file)
@@ -46,7 +46,7 @@ class user_enrolment_created extends base {
     protected function init() {
         $this->data['objecttable'] = 'user_enrolments';
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index 4376a2b..e178aab 100644 (file)
@@ -47,7 +47,7 @@ class user_enrolment_deleted extends base {
     protected function init() {
         $this->data['objecttable'] = 'user_enrolments';
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index 69e71de..5ae4c03 100644 (file)
@@ -46,7 +46,7 @@ class user_enrolment_updated extends base {
     protected function init() {
         $this->data['objecttable'] = 'user_enrolments';
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index fce1f0d..315b376 100644 (file)
@@ -42,7 +42,7 @@ class user_list_viewed extends \core\event\base {
     protected function init() {
         $this->data['objecttable'] = 'course';
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index 0bb4e17..198a137 100644 (file)
@@ -95,7 +95,7 @@ class user_loggedin extends \core\event\base {
     protected function init() {
         $this->context = \context_system::instance();
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'user';
     }
 
index 136307d..bf88da9 100644 (file)
@@ -49,7 +49,7 @@ class user_loggedinas extends base {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'user';
     }
 
index d17f0bf..f332ae1 100644 (file)
@@ -47,7 +47,7 @@ class user_loggedout extends base {
         $this->context = \context_system::instance();
         $this->data['objecttable'] = 'user';
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index eefc9bf..919ecc7 100644 (file)
@@ -42,7 +42,7 @@ class user_profile_viewed extends base {
     protected function init() {
         $this->data['objecttable'] = 'user';
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index 005ee40..af7c04f 100644 (file)
@@ -40,7 +40,7 @@ class user_updated extends base {
     protected function init() {
         $this->data['objecttable'] = 'user';
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
     /**
index 1334784..7685abd 100644 (file)
@@ -79,7 +79,7 @@ class webservice_function_called extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->context = \context_system::instance();
     }
 
index 9ababb0..78560d3 100644 (file)
@@ -83,7 +83,7 @@ class webservice_login_failed extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->context = \context_system::instance();
     }
 
index 22fd29c..9aa7e24 100644 (file)
@@ -87,7 +87,7 @@ class webservice_service_created extends \core\event\base {
     protected function init() {
         $this->context = \context_system::instance();
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'external_services';
     }
 
index 4c497d9..7832e0a 100644 (file)
@@ -81,7 +81,7 @@ class webservice_service_deleted extends \core\event\base {
     protected function init() {
         $this->context = \context_system::instance();
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'external_services';
     }
 
index 0b82da7..9740329 100644 (file)
@@ -81,7 +81,7 @@ class webservice_service_updated extends \core\event\base {
     protected function init() {
         $this->context = \context_system::instance();
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'external_services';
     }
 
index 38309fb..c9c4d6d 100644 (file)
@@ -80,7 +80,7 @@ class webservice_service_user_added extends \core\event\base {
     protected function init() {
         $this->context = \context_system::instance();
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'external_services';
     }
 
index a0f7eec..c3770a8 100644 (file)
@@ -80,7 +80,7 @@ class webservice_service_user_removed extends \core\event\base {
     protected function init() {
         $this->context = \context_system::instance();
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'external_services';
     }
 
index d5799e0..cb0c653 100644 (file)
@@ -87,7 +87,7 @@ class webservice_token_created extends \core\event\base {
     protected function init() {
         $this->context = \context_system::instance();
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'external_tokens';
     }
 
index 51775bd..e674929 100644 (file)
@@ -69,7 +69,7 @@ class webservice_token_sent extends \core\event\base {
     protected function init() {
         $this->context = \context_system::instance();
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'external_tokens';
     }
 
index 9650b7f..4937628 100644 (file)
@@ -2919,5 +2919,13 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2014011000.01);
     }
 
+    if ($oldversion < 2014011701.00) {
+        // Fix gradebook sortorder duplicates.
+        upgrade_grade_item_fix_sortorder();
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2014011701.00);
+    }
+
     return true;
 }
index 18d1180..4a0aa6e 100644 (file)
@@ -1209,6 +1209,42 @@ class grade_item extends grade_object {
         $this->set_sortorder($sortorder + 1);
     }
 
+    /**
+     * Detect duplicate grade item's sortorder and re-sort them.
+     * Note: Duplicate sortorder will be introduced while duplicating activities or
+     * merging two courses.
+     *
+     * @param int $courseid id of the course for which grade_items sortorder need to be fixed.
+     */
+    public static function fix_duplicate_sortorder($courseid) {
+        global $DB;
+
+        $transaction = $DB->start_delegated_transaction();
+
+        $sql = "SELECT g1.id, g1.courseid, g1.sortorder
+                    FROM {grade_items} g1
+                    JOIN {grade_items} g2 ON g1.courseid = g2.courseid
+                WHERE g1.sortorder = g2.sortorder AND g1.id != g2.id AND g1.courseid = :courseid
+                ORDER BY g1.sortorder DESC, g1.id DESC";
+
+        // Get all duplicates in course highest sort order, and higest id first so that we can make space at the
+        // bottom higher end of the sort orders and work down by id.
+        $rs = $DB->get_recordset_sql($sql, array('courseid' => $courseid));
+
+        foreach($rs as $duplicate) {
+            $DB->execute("UPDATE {grade_items}
+                            SET sortorder = sortorder + 1
+                          WHERE courseid = :courseid AND
+                          (sortorder > :sortorder OR (sortorder = :sortorder2 AND id > :id))",
+                array('courseid' => $duplicate->courseid,
+                    'sortorder' => $duplicate->sortorder,
+                    'sortorder2' => $duplicate->sortorder,
+                    'id' => $duplicate->id));
+        }
+        $rs->close();
+        $transaction->allow_commit();
+    }
+
     /**
      * Returns the most descriptive field for this object.
      *
index 7b29077..34ba416 100644 (file)
@@ -65,6 +65,7 @@ class core_grade_item_testcase extends grade_base_testcase {
         $this->sub_test_grade_item_compute();
         $this->sub_test_update_final_grade();
         $this->sub_test_grade_item_can_control_visibility();
+        $this->sub_test_grade_item_fix_sortorder();
     }
 
     protected function sub_test_grade_item_construct() {
@@ -630,4 +631,106 @@ class core_grade_item_testcase extends grade_base_testcase {
         $grade_item = new grade_item($this->grade_items[11], false);
         $this->assertFalse($grade_item->can_control_visibility());
     }
+
+    /**
+     * Test the {@link grade_item::fix_duplicate_sortorder() function with
+     * faked duplicate sortorder data.
+     */
+    public function sub_test_grade_item_fix_sortorder() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        // Each set is used for filling the db with fake data and will be representing the result of query:
+        // "SELECT sortorder from {grade_items} WHERE courseid=? ORDER BY id".
+        $testsets = array(
+            // Items that need no action.
+            array(1,2,3),
+            array(5,6,7),
+            array(7,6,1,3,2,5),
+            // Items with sortorder duplicates
+            array(1,2,2,3,3,4,5),
+            // Only one sortorder duplicate.
+            array(1,1),
+            array(3,3),
+            // Non-sequential sortorders with one or multiple duplicates.
+            array(3,3,7,5,6,6,9,10,8,3),
+            array(7,7,3),
+            array(3,4,5,3,5,4,7,1)
+        );
+        $origsequence = array();
+
+        // Generate the data and remember the initial sequence or items.
+        foreach ($testsets as $testset) {
+            $course = $this->getDataGenerator()->create_course();
+            foreach ($testset as $sortorder) {
+                $this->insert_fake_grade_item_sortorder($course->id, $sortorder);
+            }
+            $DB->get_records('grade_items');
+            $origsequence[$course->id] = $DB->get_fieldset_sql("SELECT id FROM {grade_items} ".
+                "WHERE courseid = ? ORDER BY sortorder, id", array($course->id));
+        }
+
+        $duplicatedetectionsql = "SELECT courseid, sortorder
+                                    FROM {grade_items}
+                                WHERE courseid = :courseid
+                                GROUP BY courseid, sortorder
+                                  HAVING COUNT(id) > 1";
+
+        // Do the work.
+        foreach ($origsequence as $courseid => $ignore) {
+            grade_item::fix_duplicate_sortorder($courseid);
+            // Verify that no duplicates are left in the database.
+            $dupes = $DB->record_exists_sql($duplicatedetectionsql, array('courseid' => $courseid));
+            $this->assertFalse($dupes);
+        }
+
+        // Verify that sequences are exactly the same as they were before upgrade script.
+        $idx = 0;
+        foreach ($origsequence as $courseid => $sequence) {
+            if (count(($testsets[$idx])) == count(array_unique($testsets[$idx]))) {
+                // If there were no duplicates for this course verify that sortorders are not modified.
+                $newsortorders = $DB->get_fieldset_sql("SELECT sortorder from {grade_items} WHERE courseid=? ORDER BY id", array($courseid));
+                $this->assertEquals($testsets[$idx], $newsortorders);
+            }
+            $newsequence = $DB->get_fieldset_sql("SELECT id FROM {grade_items} ".
+                "WHERE courseid = ? ORDER BY sortorder, id", array($courseid));
+            $this->assertEquals($sequence, $newsequence,
+                    "Sequences do not match for test set $idx : ".join(',', $testsets[$idx]));
+            $idx++;
+        }
+    }
+
+    /**
+     * Populate some fake grade items into the database with specified
+     * sortorder and course id.
+     *
+     * NOTE: This function doesn't make much attempt to respect the
+     * gradebook internals, its simply used to fake some data for
+     * testing the upgradelib function. Please don't use it for other
+     * purposes.
+     *
+     * @param int $courseid id of course
+     * @param int $sortorder numeric sorting order of item
+     * @return stdClass grade item object from the database.
+     */
+    private function insert_fake_grade_item_sortorder($courseid, $sortorder) {
+        global $DB, $CFG;
+        require_once($CFG->libdir.'/gradelib.php');
+
+        $item = new stdClass();
+        $item->courseid = $courseid;
+        $item->sortorder = $sortorder;
+        $item->gradetype = GRADE_TYPE_VALUE;
+        $item->grademin = 30;
+        $item->grademax = 110;
+        $item->itemnumber = 1;
+        $item->iteminfo = '';
+        $item->timecreated = time();
+        $item->timemodified = time();
+
+        $item->id = $DB->insert_record('grade_items', $item);
+
+        return $DB->get_record('grade_items', array('id' => $item->id));
+    }
 }
index 073ecbc..312ac4e 100644 (file)
@@ -3628,7 +3628,7 @@ function fullname($user, $override=false) {
     // The special characters are Japanese brackets that are common enough to make allowances for them (not covered by :punct:).
     $patterns[] = '/[[:punct:]「」]*EMPTY[[:punct:]「」]*/u';
     // This regular expression is to remove any double spaces in the display name.
-    $patterns[] = '/\s{2,}/';
+    $patterns[] = '/\s{2,}/u';
     foreach ($patterns as $pattern) {
         $displayname = preg_replace($pattern, ' ', $displayname);
     }
@@ -5830,7 +5830,18 @@ function email_to_user($user, $from, $subject, $messagetext, $messagehtml = '',
         }
         return true;
     } else {
-        add_to_log(SITEID, 'library', 'mailer', qualified_me(), 'ERROR: '. $mail->ErrorInfo);
+        // Trigger event for failing to send email.
+        $event = \core\event\email_failed::create(array(
+            'context' => context_system::instance(),
+            'userid' => $from->id,
+            'relateduserid' => $user->id,
+            'other' => array(
+                'subject' => $subject,
+                'message' => $messagetext,
+                'errorinfo' => $mail->ErrorInfo
+            )
+        ));
+        $event->trigger();
         if (CLI_SCRIPT) {
             mtrace('Error: lib/moodlelib.php email_to_user(): '.$mail->ErrorInfo);
         }
index 4ae18b2..06081c0 100644 (file)
@@ -2748,6 +2748,9 @@ class global_navigation_for_ajax extends global_navigation {
         $this->rootnodes['site']    = $this->add_course($SITE);
         $this->rootnodes['mycourses'] = $this->add(get_string('mycourses'), new moodle_url('/my'), self::TYPE_ROOTNODE, null, 'mycourses');
         $this->rootnodes['courses'] = $this->add(get_string('courses'), null, self::TYPE_ROOTNODE, null, 'courses');
+        // The courses branch is always displayed, and is always expandable (although may be empty).
+        // This mimicks what is done during {@link global_navigation::initialise()}.
+        $this->rootnodes['courses']->isexpandable = true;
 
         // Branchtype will be one of navigation_node::TYPE_*
         switch ($this->branchtype) {
index 84b2bcd..a85276d 100644 (file)
@@ -52,7 +52,7 @@ class core_event_testcase extends advanced_testcase {
         $this->assertSame('unittest', $event->target);
         $this->assertSame(5, $event->objectid);
         $this->assertSame('u', $event->crud);
-        $this->assertSame(\core\event\base::LEVEL_PARTICIPATING, $event->level);
+        $this->assertSame(\core\event\base::LEVEL_PARTICIPATING, $event->edulevel);
 
         $this->assertEquals($system, $event->get_context());
         $this->assertSame($system->id, $event->contextid);
@@ -451,6 +451,23 @@ class core_event_testcase extends advanced_testcase {
             \core_tests\event\unittest_observer::$info);
     }
 
+    public function test_deprecated() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        $event = \core_tests\event\deprecated_event1::create();
+        $this->assertDebuggingCalled('level property is deprecated, use edulevel property instead');
+
+        $this->assertSame($event::LEVEL_TEACHING, $event->level);
+        $this->assertDebuggingCalled('level property is deprecated, use edulevel property instead');
+
+        $this->assertTrue(isset($event->level));
+        $this->assertDebuggingCalled('level property is deprecated, use edulevel property instead');
+
+        $this->assertSame($event::LEVEL_TEACHING, $event->edulevel);
+    }
+
     public function test_legacy() {
         global $DB;
 
index ee5797c..e298bcc 100644 (file)
@@ -140,4 +140,35 @@ class core_events_testcase extends advanced_testcase {
         $expected = array(SITEID, 'category', 'show', 'editcategory.php?id=' . $category2->id, $category2->id);
         $this->assertEventLegacyLogData($expected, $event);
     }
+
+    /**
+     * Test the email failed event.
+     *
+     * It's not possible to use the moodle API to simulate the failure of sending
+     * an email, so here we simply create the event and trigger it.
+     */
+    public function test_email_failed() {
+        // Trigger event for failing to send email.
+        $event = \core\event\email_failed::create(array(
+            'context' => context_system::instance(),
+            'userid' => 1,
+            'relateduserid' => 2,
+            'other' => array(
+                'subject' => 'This is a subject',
+                'message' => 'This is a message',
+                'errorinfo' => 'The email failed to send!'
+            )
+        ));
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        $this->assertInstanceOf('\core\event\email_failed', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+        $expected = array(SITEID, 'library', 'mailer', qualified_me(), 'ERROR: The email failed to send!');
+        $this->assertEventLegacyLogData($expected, $event);
+    }
 }
index 9cc8252..a516184 100644 (file)
@@ -41,7 +41,7 @@ class unittest_executed extends \core\event\base {
 
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     public function get_url() {
@@ -116,14 +116,14 @@ class unittest_observer {
 class bad_event1 extends \core\event\base {
     protected function init() {
         //$this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 }
 
 class bad_event2 extends \core\event\base {
     protected function init() {
         $this->data['crud'] = 'u';
-        //$this->data['level'] = 10;
+        //$this->data['edulevel'] = 10;
     }
 }
 
@@ -131,14 +131,14 @@ class bad_event2b extends \core\event\base {
     protected function init() {
         $this->data['crud'] = 'u';
         // Invalid level value.
-        $this->data['level'] = -1;
+        $this->data['edulevel'] = -1;
     }
 }
 
 class bad_event3 extends \core\event\base {
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         unset($this->data['courseid']);
     }
 }
@@ -146,7 +146,7 @@ class bad_event3 extends \core\event\base {
 class bad_event4 extends \core\event\base {
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['xxx'] = 1;
     }
 }
@@ -154,14 +154,14 @@ class bad_event4 extends \core\event\base {
 class bad_event5 extends \core\event\base {
     protected function init() {
         $this->data['crud'] = 'x';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 }
 
 class bad_event6 extends \core\event\base {
     protected function init() {
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'xxx_xxx_xx';
     }
 }
@@ -169,7 +169,7 @@ class bad_event6 extends \core\event\base {
 class bad_event7 extends \core\event\base {
     protected function init() {
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = null;
     }
 }
@@ -177,7 +177,7 @@ class bad_event7 extends \core\event\base {
 class bad_event8 extends \core\event\base {
     protected function init() {
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'user';
     }
 }
@@ -185,14 +185,14 @@ class bad_event8 extends \core\event\base {
 class problematic_event1 extends \core\event\base {
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 }
 
 class problematic_event2 extends \core\event\base {
     protected function init() {
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->context = \context_system::instance();
     }
 }
@@ -200,7 +200,7 @@ class problematic_event2 extends \core\event\base {
 class problematic_event3 extends \core\event\base {
     protected function init() {
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->context = \context_system::instance();
     }
 
@@ -211,11 +211,19 @@ class problematic_event3 extends \core\event\base {
     }
 }
 
+class deprecated_event1 extends \core\event\base {
+    protected function init() {
+        $this->data['crud'] = 'c';
+        $this->data['level'] = self::LEVEL_TEACHING; // Tests edulevel hint.
+        $this->context = \context_system::instance();
+    }
+}
+
 class noname_event extends \core\event\base {
 
     protected function init() {
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->context = \context_system::instance();
     }
 }
@@ -237,7 +245,7 @@ class content_viewed extends \core\event\content_viewed {
 class course_module_viewed extends \core\event\course_module_viewed {
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'feedback';
     }
 }
index 7e9be49..0ff743f 100644 (file)
@@ -42,4 +42,111 @@ class core_upgradelib_testcase extends advanced_testcase {
         // if there aren't any old files in the codebase.
         $this->assertFalse(upgrade_stale_php_files_present());
     }
+
+    /**
+     * Test the {@link upgrade_grade_item_fix_sortorder() function with
+     * faked duplicate sortorder data.
+     */
+    public function test_upgrade_grade_item_fix_sortorder() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        // The purpose of this test is to make sure that after upgrade script
+        // there is no duplicates in the field grade_items.sortorder (for each course)
+        // and the result of query "SELECT id FROM grade_items WHERE courseid=? ORDER BY sortorder, id" does not change.
+        $sequencesql = 'SELECT id FROM {grade_items} WHERE courseid=? ORDER BY sortorder, id';
+
+        // Each set is used for filling the db with fake data and will be representing the result of query:
+        // "SELECT sortorder from {grade_items} WHERE courseid=? ORDER BY id".
+        $testsets = array(
+            // Items that need no action.
+            array(1,2,3),
+            array(5,6,7),
+            array(7,6,1,3,2,5),
+            // Items with sortorder duplicates
+            array(1,2,2,3,3,4,5),
+            // Only one sortorder duplicate.
+            array(1,1),
+            array(3,3),
+            // Non-sequential sortorders with one or multiple duplicates.
+            array(3,3,7,5,6,6,9,10,8,3),
+            array(7,7,3),
+            array(3,4,5,3,5,4,7,1)
+        );
+        $origsequences = array();
+
+        // Generate the data and remember the initial sequence or items.
+        foreach ($testsets as $testset) {
+            $course = $this->getDataGenerator()->create_course();
+            foreach ($testset as $sortorder) {
+                $this->insert_fake_grade_item_sortorder($course->id, $sortorder);
+            }
+            $DB->get_records('grade_items');
+            $origsequences[$course->id] = $DB->get_fieldset_sql($sequencesql, array($course->id));
+        }
+
+        $duplicatedetectionsql = "SELECT courseid, sortorder
+                                    FROM {grade_items}
+                                GROUP BY courseid, sortorder
+                                  HAVING COUNT(id) > 1";
+
+        // Verify there are duplicates before we start the fix.
+        $dupes = $DB->record_exists_sql($duplicatedetectionsql);
+        $this->assertTrue($dupes);
+
+        // Do the work.
+        upgrade_grade_item_fix_sortorder();
+
+        // Verify that no duplicates are left in the database.
+        $dupes = $DB->record_exists_sql($duplicatedetectionsql);
+        $this->assertFalse($dupes);
+
+        // Verify that sequences are exactly the same as they were before upgrade script.
+        $idx = 0;
+        foreach ($origsequences as $courseid => $origsequence) {
+            if (count(($testsets[$idx])) == count(array_unique($testsets[$idx]))) {
+                // If there were no duplicates for this course verify that sortorders are not modified.
+                $newsortorders = $DB->get_fieldset_sql("SELECT sortorder from {grade_items} WHERE courseid=? ORDER BY id", array($courseid));
+                $this->assertEquals($testsets[$idx], $newsortorders);
+            }
+            $newsequence = $DB->get_fieldset_sql($sequencesql, array($courseid));
+            $this->assertEquals($origsequence, $newsequence,
+                    "Sequences do not match for test set $idx : ".join(',', $testsets[$idx]));
+            $idx++;
+        }
+    }
+
+    /**
+     * Populate some fake grade items into the database with specified
+     * sortorder and course id.
+     *
+     * NOTE: This function doesn't make much attempt to respect the
+     * gradebook internals, its simply used to fake some data for
+     * testing the upgradelib function. Please don't use it for other
+     * purposes.
+     *
+     * @param int $courseid id of course
+     * @param int $sortorder numeric sorting order of item
+     * @return stdClass grade item object from the database.
+     */
+    private function insert_fake_grade_item_sortorder($courseid, $sortorder) {
+        global $DB, $CFG;
+        require_once($CFG->libdir.'/gradelib.php');
+
+        $item = new stdClass();
+        $item->courseid = $courseid;
+        $item->sortorder = $sortorder;
+        $item->gradetype = GRADE_TYPE_VALUE;
+        $item->grademin = 30;
+        $item->grademax = 110;
+        $item->itemnumber = 1;
+        $item->iteminfo = '';
+        $item->timecreated = time();
+        $item->timemodified = time();
+
+        $item->id = $DB->insert_record('grade_items', $item);
+
+        return $DB->get_record('grade_items', array('id' => $item->id));
+    }
 }
index 15a06ca..bd1a652 100644 (file)
@@ -6,6 +6,7 @@ information provided here is intended especially for developers.
 * $core_renderer->block_move_target() changed to support more verbose move-block-here descriptions.
 
 DEPRECATIONS:
+* Update init methods in all event classes - "level" property was renamed to "edulevel", the level property is now deprecated.
 * Abstract class \core\event\course_module_instances_list_viewed is deprecated now, use \core\event\instances_list_viewed instead.
 * mod_book\event\instances_list_viewed has been deprecated. Please use mod_book\event\course_module_instance_list_viewed instead.
 * mod_chat\event\instances_list_viewed has been deprecated. Please use mod_chat\event\course_module_instance_list_viewed instead.
index 0ec6e24..24563fd 100644 (file)
@@ -2049,3 +2049,42 @@ function upgrade_rename_old_backup_files_using_shortname() {
         @rename($dir . '/' . $file, $dir . '/' . $newname);
     }
 }
+
+/**
+ * Detect duplicate grade item sortorders and resort the
+ * items to remove them.
+ */
+function upgrade_grade_item_fix_sortorder() {
+    global $DB;
+
+    // The simple way to fix these sortorder duplicates would be simply to resort each
+    // affected course. But in order to reduce the impact of this upgrade step we're trying
+    // to do it more efficiently by doing a series of update statements rather than updating
+    // every single grade item in affected courses.
+
+    $transaction = $DB->start_delegated_transaction();
+
+    $sql = "SELECT g1.id, g1.courseid, g1.sortorder
+              FROM {grade_items} g1
+              JOIN {grade_items} g2 ON g1.courseid = g2.courseid
+             WHERE g1.sortorder = g2.sortorder AND g1.id != g2.id
+             ORDER BY g1.courseid ASC, g1.sortorder DESC, g1.id DESC";
+
+    // Get all duplicates in course order, highest sort order, and higest id first so that we can make space at the
+    // bottom higher end of the sort orders and work down by id.
+    $rs = $DB->get_recordset_sql($sql);
+
+    foreach($rs as $duplicate) {
+        $DB->execute("UPDATE {grade_items}
+                         SET sortorder = sortorder + 1
+                       WHERE courseid = :courseid AND
+                       (sortorder > :sortorder OR (sortorder = :sortorder2 AND id > :id))",
+            array('courseid' => $duplicate->courseid,
+                'sortorder' => $duplicate->sortorder,
+                'sortorder2' => $duplicate->sortorder,
+                'id' => $duplicate->id));
+    }
+    $rs->close();
+
+    $transaction->allow_commit();
+}
index d4c6d4d..c04e6d5 100644 (file)
@@ -114,10 +114,6 @@ $user2realuser = !empty($user2) && core_user::is_real_user($user2->id);
 $showactionlinks = $showactionlinks && $user2realuser;
 $systemcontext = context_system::instance();
 
-if (!empty($user2) && $user1->id == $user2->id) {
-    print_error('invaliduserid');
-}
-
 // Is the user involved in the conversation?
 // Do they have the ability to read other user's conversations?
 if (!message_current_user_is_involved($user1, $user2) && !has_capability('moodle/site:readallmessages', $systemcontext)) {
index aa233c5..62c8bef 100644 (file)
@@ -440,20 +440,36 @@ function mnet_update_sso_access_control($username, $mnet_host_id, $accessctrl) {
 
     $mnethost = $DB->get_record('mnet_host', array('id'=>$mnet_host_id));
     if ($aclrecord = $DB->get_record('mnet_sso_access_control', array('username'=>$username, 'mnet_host_id'=>$mnet_host_id))) {
-        // update
+        // Update.
         $aclrecord->accessctrl = $accessctrl;
         $DB->update_record('mnet_sso_access_control', $aclrecord);
-        add_to_log(SITEID, 'admin/mnet', 'update', 'admin/mnet/access_control.php',
-                "SSO ACL: $accessctrl user '$username' from {$mnethost->name}");
+
+        // Trigger access control updated event.
+        $params = array(
+            'objectid' => $aclrecord->id,
+            'courseid' => SITEID,
+            'context' => context_system::instance()
+        );
+        $event = \core\event\mnet_access_control_updated::create($params);
+        $event->add_record_snapshot('mnet_host', $mnethost);
+        $event->trigger();
     } else {
-        // insert
+        // Insert.
         $aclrecord = new stdClass();
         $aclrecord->username = $username;
         $aclrecord->accessctrl = $accessctrl;
         $aclrecord->mnet_host_id = $mnet_host_id;
-        $id = $DB->insert_record('mnet_sso_access_control', $aclrecord);
-        add_to_log(SITEID, 'admin/mnet', 'add', 'admin/mnet/access_control.php',
-                "SSO ACL: $accessctrl user '$username' from {$mnethost->name}");
+        $aclrecord->id = $DB->insert_record('mnet_sso_access_control', $aclrecord);
+
+        // Trigger access control created event.
+        $params = array(
+            'objectid' => $aclrecord->id,
+            'courseid' => SITEID,
+            'context' => context_system::instance()
+        );
+        $event = \core\event\mnet_access_control_created::create($params);
+        $event->add_record_snapshot('mnet_host', $mnethost);
+        $event->trigger();
     }
     return true;
 }
diff --git a/mnet/tests/events_test.php b/mnet/tests/events_test.php
new file mode 100644 (file)
index 0000000..0724d57
--- /dev/null
@@ -0,0 +1,98 @@
+<?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/>.
+
+/**
+ * Events tests.
+ *
+ * @package   core_mnet
+ * @category  test
+ * @copyright 2013 Mark Nelson <markn@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/mnet/lib.php');
+
+class mnet_events_testcase extends advanced_testcase {
+
+    /** @var stdClass the mnet host we are using to test */
+    protected $mnethost;
+
+    /**
+     * Test set up.
+     *
+     * This is executed before running any test in this file.
+     */
+    public function setUp() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        // Add a mnet host.
+        $this->mnethost = new stdClass();
+        $this->mnethost->name = 'A mnet host';
+        $this->mnethost->public_key = 'A random public key!';
+        $this->mnethost->id = $DB->insert_record('mnet_host', $this->mnethost);
+    }
+
+    /**
+     * Test the mnet access control created event.
+     */
+    public function test_mnet_access_control_created() {
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        mnet_update_sso_access_control('username', $this->mnethost->id, 'enabled');
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\mnet_access_control_created', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+        $expected = array(SITEID, 'admin/mnet', 'add', 'admin/mnet/access_control.php',
+            'SSO ACL: enabled user \'username\' from ' . $this->mnethost->name);
+        $this->assertEventLegacyLogData($expected, $event);
+    }
+
+    /**
+     * Test the mnet access control updated event.
+     */
+    public function test_mnet_access_control_updated() {
+        global $DB;
+
+        // Create a mnet access control.
+        $mnetaccesscontrol = new stdClass();
+        $mnetaccesscontrol->username = 'username';
+        $mnetaccesscontrol->mnet_host_id = $this->mnethost->id;
+        $mnetaccesscontrol->accessctrl = 'enabled';
+        $mnetaccesscontrol->id = $DB->insert_record('mnet_sso_access_control', $mnetaccesscontrol);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        mnet_update_sso_access_control('username', $this->mnethost->id, 'enabled');
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\mnet_access_control_updated', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+        $expected = array(SITEID, 'admin/mnet', 'update', 'admin/mnet/access_control.php',
+            'SSO ACL: enabled user \'username\' from ' . $this->mnethost->name);
+        $this->assertEventLegacyLogData($expected, $event);
+    }
+}
index 6fa46d4..b33f689 100644 (file)
@@ -95,7 +95,7 @@ class all_submissions_downloaded extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
         $this->data['objecttable'] = 'assign';
     }
 
index 5a59f11..287f847 100644 (file)
@@ -85,7 +85,7 @@ class extension_granted extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
         $this->data['objecttable'] = 'assign';
     }
 
index ca4a033..3dc67b1 100644 (file)
@@ -85,7 +85,7 @@ class identities_revealed extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
         $this->data['objecttable'] = 'assign';
     }
 
index 616005d..b59ecd1 100644 (file)
@@ -91,7 +91,7 @@ class marker_updated extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
         $this->data['objecttable'] = 'assign';
     }
 
index 8465c14..6381682 100644 (file)
@@ -85,7 +85,7 @@ class statement_accepted extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
         $this->data['objecttable'] = 'assign_submission';
     }
 
index ebccc0f..a5bf9c0 100644 (file)
@@ -85,7 +85,7 @@ class submission_duplicated extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
         $this->data['objecttable'] = 'assign_submission';
     }
 
index bd9e138..cac5fac 100644 (file)
@@ -85,7 +85,7 @@ class submission_graded extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
         $this->data['objecttable'] = 'assign_grades';
     }
 
index 09e86c0..082a1a4 100644 (file)
@@ -85,7 +85,7 @@ class submission_locked extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
         $this->data['objecttable'] = 'assign';
     }
 
index 32be18b..e0d21c9 100644 (file)
@@ -91,7 +91,7 @@ class submission_status_updated extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
         $this->data['objecttable'] = 'assign_submission';
     }
 
index fe3feae..b50bd36 100644 (file)
@@ -85,7 +85,7 @@ class submission_unlocked extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
         $this->data['objecttable'] = 'assign';
     }
 
index 1d053c7..f9e6e47 100644 (file)
@@ -85,7 +85,7 @@ class submission_updated extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
         $this->data['objecttable'] = 'assign_submission';
     }
 
index 6ccdf0d..a52b93a 100644 (file)
@@ -91,7 +91,7 @@ class workflow_state_updated extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
         $this->data['objecttable'] = 'assign';
     }
 
index 2981464..ba35ef0 100644 (file)
@@ -65,37 +65,39 @@ M.mod_assign.init_grading_table = function(Y) {
         }
 
         var batchform = Y.one('form.gradingbatchoperationsform');
-        batchform.on('submit', function(e) {
-            checkboxes = Y.all('td.c0 input');
-            var selectedusers = [];
-            checkboxes.each(function(node) {
-                if (node.get('checked')) {
-                    selectedusers[selectedusers.length] = node.get('value');
-                }
-            });
+        if (batchform) {
+            batchform.on('submit', function(e) {
+                checkboxes = Y.all('td.c0 input');
+                var selectedusers = [];
+                checkboxes.each(function(node) {
+                    if (node.get('checked')) {
+                        selectedusers[selectedusers.length] = node.get('value');
+                    }
+                });
 
-            operation = Y.one('#id_operation');
-            usersinput = Y.one('input.selectedusers');
-            usersinput.set('value', selectedusers.join(','));
-            if (selectedusers.length == 0) {
-                alert(M.str.assign.nousersselected);
-                e.preventDefault();
-            } else {
-                action = operation.get('value');
-                prefix = 'plugingradingbatchoperation_';
-                if (action.indexOf(prefix) == 0) {
-                    pluginaction = action.substr(prefix.length);
-                    plugin = pluginaction.split('_')[0];
-                    action = pluginaction.substr(plugin.length + 1);
-                    confirmmessage = eval('M.str.assignfeedback_' + plugin + '.batchoperationconfirm' + action);
-                } else {
-                    confirmmessage = eval('M.str.assign.batchoperationconfirm' + operation.get('value'));
-                }
-                if (!confirm(confirmmessage)) {
+                operation = Y.one('#id_operation');
+                usersinput = Y.one('input.selectedusers');
+                usersinput.set('value', selectedusers.join(','));
+                if (selectedusers.length == 0) {
+                    alert(M.str.assign.nousersselected);
                     e.preventDefault();
+                } else {
+                    action = operation.get('value');
+                    prefix = 'plugingradingbatchoperation_';
+                    if (action.indexOf(prefix) == 0) {
+                        pluginaction = action.substr(prefix.length);
+                        plugin = pluginaction.split('_')[0];
+                        action = pluginaction.substr(plugin.length + 1);
+                        confirmmessage = eval('M.str.assignfeedback_' + plugin + '.batchoperationconfirm' + action);
+                    } else {
+                        confirmmessage = eval('M.str.assign.batchoperationconfirm' + operation.get('value'));
+                    }
+                    if (!confirm(confirmmessage)) {
+                        e.preventDefault();
+                    }
                 }
-            }
-        });
+            });
+        }
 
         Y.use('node-menunav', function(Y) {
             var menus = Y.all('.gradingtable .actionmenu');
index 97d2502..0befe33 100644 (file)
@@ -1,4 +1,4 @@
-@mod @mod_assign @_only_local
+@mod @mod_assign @_file_upload
 Feature: In an assignment, students can upload files for assessment
   In order to complete my assignments providing files
   As a student
index a107bd8..c89e479 100644 (file)
@@ -81,7 +81,7 @@ class chapter_created extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
         $this->data['objecttable'] = 'book_chapters';
     }
 
index 43595e1..ff9d9af 100644 (file)
@@ -84,7 +84,7 @@ class chapter_deleted extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
         $this->data['objecttable'] = 'book_chapters';
     }
 
index 559da1f..c889de0 100644 (file)
@@ -81,7 +81,7 @@ class chapter_updated extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
         $this->data['objecttable'] = 'book_chapters';
     }
 
index 35010b8..a1f8703 100644 (file)
@@ -78,7 +78,7 @@ class chapter_viewed extends \core\event\content_viewed {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
         $this->data['objecttable'] = 'book_chapters';
     }
 
index 20848d8..daa2e37 100644 (file)
@@ -41,7 +41,7 @@ class course_module_viewed extends \core\event\course_module_viewed {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
         $this->data['objecttable'] = 'book';
     }
 }
index 556ca97..2d094de 100644 (file)
@@ -78,7 +78,7 @@ class book_exported extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'book';
     }
 
index 642881f..0917406 100644 (file)
@@ -78,7 +78,7 @@ class book_printed extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
         $this->data['objecttable'] = 'book';
     }
 
index 8310424..ea4d2ec 100644 (file)
@@ -78,7 +78,7 @@ class chapter_printed extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
         $this->data['objecttable'] = 'book';
     }
 
index b881203..fdb1d5e 100644 (file)
@@ -79,7 +79,7 @@ class message_sent extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
         $this->data['objecttable'] = 'chat_messages';
     }
 
index 766ac9a..71be7c0 100644 (file)
@@ -85,7 +85,7 @@ class sessions_viewed extends \core\event\content_viewed {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['objecttable'] = 'chat';
     }
 
index 999a969..bc7256f 100644 (file)
@@ -92,7 +92,7 @@ class answer_submitted extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
         $this->data['objecttable'] = 'choice_answers';
     }
 
index eb0a276..2df2afb 100644 (file)
@@ -92,7 +92,7 @@ class answer_updated extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
         $this->data['objecttable'] = 'choice_answers';
     }
 
index 7aab11f..db30241 100644 (file)
@@ -39,7 +39,7 @@ class course_module_viewed extends \core\event\course_module_viewed {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
         $this->data['objecttable'] = 'choice';
     }
 }
index acf1394..c7b6225 100644 (file)
@@ -45,7 +45,7 @@ class report_viewed extends \core\event\content_viewed {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
         $this->data['objecttable'] = 'choice';
     }
 
index bbe47c0..9e21da0 100644 (file)
@@ -46,7 +46,7 @@ class course_module_viewed extends \core\event\course_module_viewed {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
         $this->data['objecttable'] = 'feedback';
     }
 
index be00b5e..cbd086f 100644 (file)
@@ -50,7 +50,7 @@ class response_deleted extends \core\event\base {
     protected function init() {
         $this->data['objecttable'] = 'feedback_completed';
         $this->data['crud'] = 'd';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index 600fd27..5225388 100644 (file)
@@ -53,7 +53,7 @@ class response_submitted extends \core\event\base {
         require_once($CFG->dirroot.'/mod/feedback/lib.php');
         $this->data['objecttable'] = 'feedback_completed';
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index f790a65..b676f6b 100644 (file)
@@ -231,9 +231,17 @@ class feedback_item_captcha extends feedback_item_base {
         <a href="javascript:Recaptcha.switch_type(\'image\')">' . $strgetanimagecaptcha . '</a>
         </div>
         </div>';
+
+        // Check if we are using SSL.
+        if (strpos($CFG->wwwroot, 'https://') === 0) {
+            $ssl = true;
+        } else {
+            $ssl = false;
+        }
+
         //we have to rename the challengefield
         if (!empty($CFG->recaptchaprivatekey) AND !empty($CFG->recaptchapublickey)) {
-            $captchahtml = recaptcha_get_html($CFG->recaptchapublickey, null);
+            $captchahtml = recaptcha_get_html($CFG->recaptchapublickey, null, $ssl);
             echo $html.$captchahtml;
         }
     }
index 6c502a5..39a0e59 100644 (file)
@@ -33,7 +33,7 @@ class course_module_viewed extends \core\event\course_module_viewed {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
         $this->data['objecttable'] = 'folder';
     }
 }
index f813d06..e972d86 100644 (file)
@@ -33,7 +33,7 @@ class folder_updated extends \core\event\base {
      */
     protected function init() {
         $this->data['crud'] = 'u';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
         $this->data['objecttable'] = 'folder';
     }
 
index 1977312..2a74c31 100644 (file)
@@ -83,7 +83,7 @@ class moodle1_mod_imscp_handler extends moodle1_resource_successor_handler {
         $this->fileman->migrate_directory('moddata/resource/'.$data['id']);
 
         // parse manifest
-        $structure = $this->parse_structure($this->converter->get_tempdir_path().'/moddata/resource/'.$data['id'].'/imsmanifest.xml');
+        $structure = $this->parse_structure($this->converter->get_tempdir_path().'/moddata/resource/'.$data['id'].'/imsmanifest.xml', $imscp, $contextid);
         $imscp['structure'] = is_array($structure) ? serialize($structure) : null;
 
         // write imscp.xml
@@ -113,7 +113,7 @@ class moodle1_mod_imscp_handler extends moodle1_resource_successor_handler {
      *
      * @param string $manifestfilepath the full path to the manifest file to parse
      */
-    protected function parse_structure($manifestfilepath) {
+    protected function parse_structure($manifestfilepath, $imscp, $context) {
         global $CFG;
 
         if (!file_exists($manifestfilepath)) {
@@ -127,6 +127,6 @@ class moodle1_mod_imscp_handler extends moodle1_resource_successor_handler {
         }
 
         require_once($CFG->dirroot.'/mod/imscp/locallib.php');
-        return imscp_parse_manifestfile($manifestfilecontents);
+        return imscp_parse_manifestfile($manifestfilecontents, $imscp, $context);
     }
 }
index d51ecf7..b3dbafb 100644 (file)
@@ -95,7 +95,7 @@ function imscp_parse_structure($imscp, $context) {
         return null;
     }
 
-    return imscp_parse_manifestfile($manifestfile->get_content());
+    return imscp_parse_manifestfile($manifestfile->get_content(), $imscp, $context);
 }
 
 /**
@@ -103,7 +103,7 @@ function imscp_parse_structure($imscp, $context) {
  * @param string $manifestfilecontents the contents of the manifest file
  * @return array
  */
-function imscp_parse_manifestfile($manifestfilecontents) {
+function imscp_parse_manifestfile($manifestfilecontents, $imscp, $context) {
     $doc = new DOMDocument();
     if (!$doc->loadXML($manifestfilecontents, LIBXML_NONET)) {
         return null;
@@ -162,6 +162,9 @@ function imscp_parse_manifestfile($manifestfilecontents) {
             foreach ($fileresources as $file) {
                 $href = $file->getAttribute('href');
             }
+            if (pathinfo($href, PATHINFO_EXTENSION) == 'xml') {
+                $href = imscp_recursive_href($href, $imscp, $context);
+            }
             if (empty($href)) {
                 continue;
             }
@@ -189,6 +192,43 @@ function imscp_parse_manifestfile($manifestfilecontents) {
     return $items;
 }
 
+function imscp_recursive_href($manifestfilename, $imscp, $context) {
+    $fs = get_file_storage();
+
+    $dirname = dirname($manifestfilename);
+    $filename = basename($manifestfilename);
+
+    if ($dirname !== '/') {
+        $dirname = "/$dirname/";
+    }
+
+    if (!$manifestfile = $fs->get_file($context->id, 'mod_imscp', 'content', $imscp->revision, $dirname, $filename)) {
+        return null;
+    }
+    $doc = new DOMDocument();
+    if (!$doc->loadXML($manifestfile->get_content(), LIBXML_NONET)) {
+        return null;
+    }
+    $xmlresources = $doc->getElementsByTagName('resource');
+    foreach ($xmlresources as $res) {
+        if (!$href = $res->attributes->getNamedItem('href')) {
+            $fileresources = $res->getElementsByTagName('file');
+            foreach ($fileresources as $file) {
+                $href = $file->getAttribute('href');
+                if (pathinfo($href, PATHINFO_EXTENSION) == 'xml') {
+                    $href = imscp_recursive_href($href, $imscp, $context);
+                }
+
+                if (pathinfo($href, PATHINFO_EXTENSION) == 'htm' || pathinfo($href, PATHINFO_EXTENSION) == 'html') {
+                    return $href;
+                }
+            }
+        }
+    }
+
+    return $href;
+}
+
 function imscp_recursive_item($xmlitem, $level, $resources) {
     $identifierref = '';
     if ($identifierref = $xmlitem->attributes->getNamedItem('identifierref')) {
index 3625533..6920dfa 100644 (file)
@@ -34,6 +34,6 @@ class course_module_viewed extends \core\event\course_module_viewed {
     protected function init() {
         $this->data['objecttable'] = 'lesson';
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 }
diff --git a/mod/lesson/classes/event/essay_assessed.php b/mod/lesson/classes/event/essay_assessed.php
new file mode 100644 (file)
index 0000000..ce488e0
--- /dev/null
@@ -0,0 +1,112 @@
+<?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/>.
+
+/**
+ * mod_lesson essay assessed event.
+ *
+ * @package    mod_lesson
+ * @copyright  2014 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_lesson\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * mod_lesson essay assessed event class.
+ *
+ * @property-read array $other {
+ *     Extra information about the event.
+ *
+ *     @type int lessonid The ID of the lesson.
+ *     @type int attemptid The ID for the attempt.
+ * }
+ *
+ * @package    mod_lesson
+ * @copyright  2014 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class essay_assessed extends \core\event\base {
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['crud'] = 'u';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+        $this->data['objecttable'] = 'lesson_grades';
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'User ' . $this->userid . ' has marked the essay ' . $this->other['attemptid']
+                . ' and recorded a mark ' . $this->objectid . ' in the lesson ' . $this->other['lessonid'] . '.';
+    }
+
+    /**
+     * Return legacy data for add_to_log().
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        $lesson = $this->get_record_snapshot('lesson', $this->other['lessonid']);
+        return array($this->courseid, 'lesson', 'update grade', 'essay.php?id=' .
+                $this->context->instanceid, $lesson->name, $this->context->instanceid);
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventessayassessed', 'mod_lesson');
+    }
+
+    /**
+     * Get URL related to the action
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/mod/lesson/essay.php', array('id' => $this->context->instanceid));
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        if (!isset($this->relateduserid)) {
+            throw new \coding_exception('relateduserid is a mandatory property.');
+        }
+        if (!isset($this->other['lessonid'])) {
+            throw new \coding_exception('lessonid is a mandatory property.');
+        }
+        if (!isset($this->other['attemptid'])) {
+            throw new \coding_exception('attemptid is a mandatory property.');
+        }
+    }
+}
index 7e066c7..eec32b3 100644 (file)
@@ -34,7 +34,7 @@ class essay_attempt_viewed extends \core\event\base {
     protected function init() {
         $this->data['objecttable'] = 'lesson_attempts';
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_TEACHING;
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
     }
 
     /**
index b2204db..645d113 100644 (file)
@@ -34,7 +34,7 @@ class highscore_added extends \core\event\base {
     protected function init() {
         $this->data['objecttable'] = 'lesson_high_scores';
         $this->data['crud'] = 'c';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index 549e451..cd0aff2 100644 (file)
@@ -34,7 +34,7 @@ class highscores_viewed extends \core\event\base {
     protected function init() {
         $this->data['objecttable'] = 'lesson';
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index 76963d2..a3cef8c 100644 (file)
@@ -34,7 +34,7 @@ class lesson_ended extends \core\event\base {
     protected function init() {
         $this->data['objecttable'] = 'lesson';
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index de37727..afd3b05 100644 (file)
@@ -34,7 +34,7 @@ class lesson_started extends \core\event\base {
     protected function init() {
         $this->data['objecttable'] = 'lesson';
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 
     /**
index 123a166..44e972b 100644 (file)
@@ -34,7 +34,8 @@ $mode = optional_param('mode', 'display', PARAM_ALPHA);
 
 $cm = get_coursemodule_from_id('lesson', $id, 0, false, MUST_EXIST);
 $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
-$lesson = new lesson($DB->get_record('lesson', array('id' => $cm->instance), '*', MUST_EXIST));
+$dblesson = $DB->get_record('lesson', array('id' => $cm->instance), '*', MUST_EXIST);
+$lesson = new lesson($dblesson);
 
 require_login($course, false, $cm);
 $context = context_module::instance($cm->id);
@@ -129,8 +130,20 @@ switch ($mode) {
             $updategrade->id = $grade->id;
             $updategrade->grade = $gradeinfo->grade;
             $DB->update_record('lesson_grades', $updategrade);
-            // Log it
-            add_to_log($course->id, 'lesson', 'update grade', "essay.php?id=$cm->id", $lesson->name, $cm->id);
+
+            $params = array(
+                'context' => $context,
+                'objectid' => $grade->id,
+                'courseid' => $course->id,
+                'relateduserid' => $attempt->userid,
+                'other' => array(
+                    'lessonid' => $lesson->id,
+                    'attemptid' => $attemptid
+                )
+            );
+            $event = \mod_lesson\event\essay_assessed::create($params);
+            $event->add_record_snapshot('lesson', $dblesson);
+            $event->trigger();
 
             $lesson->add_message(get_string('changessaved'), 'notifysuccess');
 
@@ -266,23 +279,12 @@ switch ($mode) {
                 $ufields = user_picture::fields('u');
                 list($sort, $sortparams) = users_order_by_sql('u');
                 $params = array_merge($params, $sortparams);
-                if (!empty($cm->groupingid)) {
-                    $params["groupingid"] = $cm->groupingid;
-                    $sql = "SELECT DISTINCT $ufields
-                            FROM {lesson_attempts} a
-                                INNER JOIN {user} u ON u.id = a.userid
-                                INNER JOIN {groups_members} gm ON gm.userid = u.id
-                                INNER JOIN {groupings_groups} gg ON gm.groupid = gg.groupid AND gg.groupingid = :groupingid
-                            WHERE a.lessonid = :lessonid
-                            ORDER BY $sort";
-                } else {
-                    $sql = "SELECT DISTINCT $ufields
-                            FROM {user} u,
-                                 {lesson_attempts} a
-                            WHERE a.lessonid = :lessonid and
-                                  u.id = a.userid
-                            ORDER BY $sort";
-                }
+                $sql = "SELECT DISTINCT $ufields
+                        FROM {user} u,
+                             {lesson_attempts} a
+                        WHERE a.lessonid = :lessonid and
+                              u.id = a.userid
+                        ORDER BY $sort";
                 if (!$users = $DB->get_records_sql($sql, $params)) {
                     $mode = 'none'; // not displaying anything
                     $lesson->add_message(get_string('noonehasanswered', 'lesson'));
index 98e6f0c..971381d 100644 (file)
@@ -171,6 +171,7 @@ $string['essayemailmessage2'] = '<p>Essay prompt:<blockquote>{$a->question}</blo
 $string['essayemailsubject'] = 'Your grade for {$a} question';
 $string['essays'] = 'Essays';
 $string['essayscore'] = 'Essay score';
+$string['eventessayassessed'] = 'Essay assessed';
 $string['eventessayattemptviewed'] = 'Essay attempt viewed';
 $string['eventhighscoreadded'] = 'Highscore added';
 $string['eventhighscoresviewed'] = 'Highscores viewed';
index 7972481..10ff1d6 100644 (file)
@@ -885,7 +885,11 @@ function lesson_pluginfile($course, $cm, $context, $filearea, $args, $forcedownl
         $fullpath = "/$context->id/mod_lesson/$filearea/$pageid/".implode('/', $args);
 
     } else if ($filearea === 'mediafile') {
-        array_shift($args); // ignore itemid - caching only
+        if (count($args) > 1) {
+            // Remove the itemid when it appears to be part of the arguments. If there is only one argument
+            // then it is surely the file name. The itemid is sometimes used to prevent browser caching.
+            array_shift($args);
+        }
         $fullpath = "/$context->id/mod_lesson/$filearea/0/".implode('/', $args);
 
     } else {
index 55ff921..e0b202c 100644 (file)
@@ -194,4 +194,39 @@ class mod_lesson_events_testcase extends advanced_testcase {
             $this->lesson->properties()->id, $this->lesson->properties()->cmid);
         $this->assertEventLegacyLogData($expected, $event);
     }
+
+    /**
+     * Test the essay assessed event.
+     *
+     * There is no external API for assessing an essay, so the unit test will simply
+     * create and trigger the event and ensure the legacy log data is returned as expected.
+     */
+    public function test_essay_assessed() {
+        // Create an essay assessed event
+        $gradeid = 5;
+        $attemptid = 7;
+        $event = \mod_lesson\event\essay_assessed::create(array(
+            'objectid' => $gradeid,
+            'relateduserid' => 3,
+            'context' => context_module::instance($this->lesson->properties()->cmid),
+            'courseid' => $this->course->id,
+            'other' => array(
+                'lessonid' => $this->lesson->id,
+                'attemptid' => $attemptid
+            )
+        ));
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\mod_lesson\event\essay_assessed', $event);
+        $this->assertEquals(context_module::instance($this->lesson->properties()->cmid), $event->get_context());
+        $expected = array($this->course->id, 'lesson', 'update grade', 'essay.php?id=' . $this->lesson->properties()->cmid,
+                $this->lesson->name, $this->lesson->properties()->cmid);
+        $this->assertEventLegacyLogData($expected, $event);
+    }
 }
index 8d8ed19..a1b0b7b 100644 (file)
@@ -34,6 +34,6 @@ class course_module_viewed extends \core\event\course_module_viewed {
     protected function init() {
         $this->data['objecttable'] = 'lti';
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
     }
 }
index 9e6dff7..7510f5d 100644 (file)
@@ -62,7 +62,7 @@ class unknown_service_api_called extends \core\event\base {
     protected function init() {
         $this->data['objecttable'] = 'lti';
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_OTHER;
+        $this->data['edulevel'] = self::LEVEL_OTHER;
         $this->data['context'] = \context_system::instance();
     }
 
index 60bf95c..52740a5 100644 (file)
@@ -39,7 +39,7 @@ class course_module_viewed extends \core\event\course_module_viewed {
      */
     protected function init() {
         $this->data['crud'] = 'r';
-        $this->data['level'] = self::LEVEL_PARTICIPATING;
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
         $this->data['objecttable'] = 'page';
     }
 }
index 30c05e6..863064a 100644 (file)
@@ -136,8 +136,8 @@ class quiz {
             throw new moodle_quiz_exception($this, 'noquestions', $this->edit_url());
         }
         $this->questions = question_preload_questions($this->questionids,
-                'qqi.grade AS maxmark, qqi.id AS instance',
-                '{quiz_question_instances} qqi ON qqi.quiz = :quizid AND q.id = qqi.question',
+                'qqi.maxmark, qqi.id AS instance',
+                '{quiz_question_instances} qqi ON qqi.quizid = :quizid AND q.id = qqi.questionid',
                 array('quizid' => $this->quiz->id));
     }
 
index 09efca0..02bb986 100644 (file)
@@ -55,24 +55,31 @@ class moodle1_mod_quiz_handler extends moodle1_mod_handler {
                 'quiz', '/MOODLE_BACKUP/COURSE/MODULES/MOD/QUIZ',
                 array(
                     'newfields' => array(
-                        'showuserpicture'   => 0,
+                        'showuserpicture'       => 0,
                         'questiondecimalpoints' => -1,
-                        'introformat'   => 0,
-                        'showblocks'    => 0,
-                    )
+                        'introformat'           => 0,
+                        'showblocks'            => 0,
+                    ),
                 )
             ),
             new convert_path('quiz_question_instances',
                     '/MOODLE_BACKUP/COURSE/MODULES/MOD/QUIZ/QUESTION_INSTANCES'),
             new convert_path('quiz_question_instance',
-                    '/MOODLE_BACKUP/COURSE/MODULES/MOD/QUIZ/QUESTION_INSTANCES/QUESTION_INSTANCE'),
+                    '/MOODLE_BACKUP/COURSE/MODULES/MOD/QUIZ/QUESTION_INSTANCES/QUESTION_INSTANCE',
+                array(
+                    'renamefields' => array(
+                        'question' => 'questionid',
+                        'grade'    => 'maxmark',
+                    ),
+                )
+            ),
             new convert_path('quiz_feedbacks',
                     '/MOODLE_BACKUP/COURSE/MODULES/MOD/QUIZ/FEEDBACKS'),
             new convert_path('quiz_feedback',
                     '/MOODLE_BACKUP/COURSE/MODULES/MOD/QUIZ/FEEDBACKS/FEEDBACK',
                 array(
                     'newfields' => array(
-                        'feedbacktextformat' => 0
+                        'feedbacktextformat' => FORMAT_HTML,
                     )
                 )
             )
index 63a897a..e229eb6 100644 (file)
@@ -57,7 +57,7 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
         $qinstances = new backup_nested_element('question_instances');
 
         $qinstance = new backup_nested_element('question_instance', array('id'), array(
-            'question', 'grade'));
+            'questionid', 'maxmark'));
 
         $feedbacks = new backup_nested_element('feedbacks');
 
@@ -108,7 +108,7 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
         $quiz->set_source_table('quiz', array('id' => backup::VAR_ACTIVITYID));
 
         $qinstance->set_source_table('quiz_question_instances',
-                array('quiz' => backup::VAR_PARENTID));
+                array('quizid' => backup::VAR_PARENTID));
 
         $feedback->set_source_table('quiz_feedback',
                 array('quizid' => backup::VAR_PARENTID));
@@ -137,7 +137,7 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
         $attempt->set_source_alias('attempt', 'attemptnum');
 
         // Define id annotations.
-        $qinstance->annotate_ids('question', 'question');
+        $qinstance->annotate_ids('question', 'questionid');
         $override->annotate_ids('user', 'userid');
         $override->annotate_ids('group', 'groupid');
         $grade->annotate_ids('user', 'userid');