Merge branch 'MDL-41622_b' of git://github.com/rwijaya/moodle
authorMarina Glancy <marina@moodle.com>
Tue, 17 Sep 2013 06:43:12 +0000 (16:43 +1000)
committerMarina Glancy <marina@moodle.com>
Tue, 17 Sep 2013 06:43:12 +0000 (16:43 +1000)
116 files changed:
admin/tool/uploaduser/user_form.php
auth/ldap/auth.php
auth/ldap/ntlmsso_magic.php
backup/util/progress/core_backup_progress.class.php
backup/util/progress/tests/progress_test.php
badges/criteria/award_criteria_activity.php
config-dist.php
course/dnduploadlib.php
course/lib.php
course/modlib.php
course/tests/courselib_test.php
enrol/self/lib.php
error/index.php
grade/import/csv/index.php
grade/import/grade_import_form.php
grade/report/grader/lib.php
lang/en/admin.php
lang/en/grades.php
lang/en/moodle.php
lib/ajax/ajaxlib.php
lib/authlib.php
lib/badgeslib.php
lib/classes/event/course_module_created.php [new file with mode: 0644]
lib/classes/event/course_module_deleted.php [new file with mode: 0644]
lib/classes/event/course_module_updated.php [new file with mode: 0644]
lib/classes/user.php [new file with mode: 0644]
lib/classes/useragent.php
lib/cronlib.php
lib/db/install.xml
lib/db/upgrade.php
lib/deprecatedlib.php
lib/editor/tinymce/lib.php
lib/editor/tinymce/plugins/spellchecker/lib.php
lib/excellib.class.php
lib/filelib.php
lib/filestorage/stored_file.php
lib/medialib.php
lib/messagelib.php
lib/moodlelib.php
lib/outputlib.php
lib/tests/user_test.php [new file with mode: 0644]
lib/tests/useragent_test.php
lib/upgrade.txt
message/index.php
message/lib.php
message/output/email/message_output_email.php
message/output/lib.php
message/upgrade.txt
mod/assign/assignmentplugin.php
mod/assign/db/services.php
mod/assign/externallib.php
mod/assign/feedback/comments/backup/moodle2/restore_assignfeedback_comments_subplugin.class.php
mod/assign/feedback/comments/locallib.php
mod/assign/feedback/comments/settings.php
mod/assign/feedback/file/backup/moodle2/restore_assignfeedback_file_subplugin.class.php
mod/assign/feedback/file/locallib.php
mod/assign/feedback/file/settings.php
mod/assign/gradingtable.php
mod/assign/index.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/mod_form.php
mod/assign/renderable.php
mod/assign/renderer.php
mod/assign/submission/file/backup/moodle2/restore_assignsubmission_file_subplugin.class.php
mod/assign/submission/file/locallib.php
mod/assign/submission/onlinetext/backup/moodle2/restore_assignsubmission_onlinetext_subplugin.class.php
mod/assign/submission/onlinetext/locallib.php
mod/assign/submissionplugin.php
mod/assign/tests/base_test.php
mod/assign/tests/externallib_test.php
mod/assign/tests/locallib_test.php
mod/assign/upgrade.txt
mod/assign/version.php
mod/book/locallib.php
mod/quiz/locallib.php
mod/quiz/report/statistics/tests/fixtures/quizzes.csv [new file with mode: 0644]
mod/quiz/report/statistics/tests/stats_from_steps_walkthrough_test.php
mod/quiz/styles.css
mod/quiz/tests/attempt_walkthrough_from_csv_test.php
mod/quiz/tests/attempt_walkthrough_test.php
mod/quiz/tests/fixtures/questions01.csv [new file with mode: 0644]
mod/quiz/tests/fixtures/quizzes.csv [new file with mode: 0644]
mod/quiz/tests/fixtures/results01.csv [new file with mode: 0644]
mod/quiz/tests/fixtures/steps01.csv [new file with mode: 0644]
mod/resource/locallib.php
mod/scorm/backup/moodle2/backup_scorm_stepslib.php
mod/scorm/datamodels/aicclib.php
mod/scorm/datamodels/scormlib.php
mod/scorm/db/install.xml
mod/scorm/db/subplugins.php [changed mode: 0644->0755]
mod/scorm/db/upgrade.php
mod/scorm/index.php
mod/scorm/lib.php
mod/scorm/loadSCO.php
mod/scorm/locallib.php
mod/scorm/player.php
mod/scorm/report.php
mod/scorm/report/basic/report.php
mod/scorm/report/graphs/report.php
mod/scorm/report/interactions/report.php
mod/scorm/report/objectives/report.php
mod/scorm/report/userreport.php
mod/scorm/report/userreporttracks.php
mod/scorm/version.php
mod/scorm/view.js
mod/scorm/view.php
question/engine/lib.php
question/engine/questionusage.php
question/engine/tests/helpers.php
repository/repository_ajax.php
theme/bootstrapbase/config.php
theme/formal_white/style/course.css
user/edit.php
user/editlib.php
version.php

index ce32ea2..26876ce 100644 (file)
@@ -250,7 +250,7 @@ class admin_uploaduser_form2 extends moodleform {
             $mform->setType('htmleditor', PARAM_INT);
         }
 
-        $mform->addElement('text', 'city', get_string('city'), 'maxlength="100" size="25"');
+        $mform->addElement('text', 'city', get_string('city'), 'maxlength="120" size="25"');
         $mform->setType('city', PARAM_TEXT);
         if (empty($CFG->defaultcity)) {
             $mform->setDefault('city', $templateuser->city);
@@ -288,14 +288,14 @@ class admin_uploaduser_form2 extends moodleform {
         $mform->setType('url', PARAM_URL);
         $mform->setAdvanced('url');
 
-        $mform->addElement('text', 'idnumber', get_string('idnumber'), 'maxlength="64" size="25"');
+        $mform->addElement('text', 'idnumber', get_string('idnumber'), 'maxlength="255" size="25"');
         $mform->setType('idnumber', PARAM_NOTAGS);
 
-        $mform->addElement('text', 'institution', get_string('institution'), 'maxlength="40" size="25"');
+        $mform->addElement('text', 'institution', get_string('institution'), 'maxlength="255" size="25"');
         $mform->setType('institution', PARAM_TEXT);
         $mform->setDefault('institution', $templateuser->institution);
 
-        $mform->addElement('text', 'department', get_string('department'), 'maxlength="30" size="25"');
+        $mform->addElement('text', 'department', get_string('department'), 'maxlength="255" size="25"');
         $mform->setType('department', PARAM_TEXT);
         $mform->setDefault('department', $templateuser->department);
 
@@ -307,7 +307,7 @@ class admin_uploaduser_form2 extends moodleform {
         $mform->setType('phone2', PARAM_NOTAGS);
         $mform->setAdvanced('phone2');
 
-        $mform->addElement('text', 'address', get_string('address'), 'maxlength="70" size="25"');
+        $mform->addElement('text', 'address', get_string('address'), 'maxlength="255" size="25"');
         $mform->setType('address', PARAM_TEXT);
         $mform->setAdvanced('address');
 
index 1bad153..a435caa 100644 (file)
@@ -1645,7 +1645,7 @@ class auth_plugin_ldap extends auth_plugin_base {
             // Now start the whole NTLM machinery.
             if($this->config->ntlmsso_ie_fastpath == AUTH_NTLM_FASTPATH_YESATTEMPT ||
                 $this->config->ntlmsso_ie_fastpath == AUTH_NTLM_FASTPATH_YESFORM) {
-                if (core_useragent::check_ie_version()) {
+                if (core_useragent::is_ie()) {
                     $sesskey = sesskey();
                     redirect($CFG->wwwroot.'/auth/ldap/ntlmsso_magic.php?sesskey='.$sesskey);
                 } else if ($this->config->ntlmsso_ie_fastpath == AUTH_NTLM_FASTPATH_YESFORM) {
index 2ac49a3..37228c1 100644 (file)
@@ -28,7 +28,7 @@ $file = $CFG->dirroot.'/pix/spacer.gif';
 
 if ($authplugin->ntlmsso_magic($sesskey) && file_exists($file)) {
     if (!empty($authplugin->config->ntlmsso_ie_fastpath)) {
-        if (core_useragent::check_ie_version()) {
+        if (core_useragent::is_ie()) {
             // $PAGE->https_required() up above takes care of what $CFG->httpswwwroot should be.
             redirect($CFG->httpswwwroot.'/auth/ldap/ntlmsso_finish.php');
         }
index 2c092c9..513b18f 100644 (file)
@@ -87,9 +87,9 @@ abstract class core_backup_progress {
      */
     public function start_progress($description, $max = self::INDETERMINATE,
             $parentcount = 1) {
-        if ($max != self::INDETERMINATE && $max <= 0) {
+        if ($max != self::INDETERMINATE && $max < 0) {
             throw new coding_exception(
-                    'start_progress() max value cannot be zero or negative');
+                    'start_progress() max value cannot be negative');
         }
         if ($parentcount < 1) {
             throw new coding_exception(
index 3c673f7..28f9f03 100644 (file)
@@ -213,6 +213,28 @@ class backup_progress_testcase extends basic_testcase {
         set_time_limit(0);
     }
 
+    /**
+     * To avoid causing problems, progress needs to work for sections that have
+     * zero entries.
+     */
+    public function test_zero() {
+        $progress = new core_backup_mock_progress();
+        $progress->start_progress('parent', 100);
+        $progress->progress(1);
+        $this->assert_min_max(0.01, 0.01, $progress);
+        $progress->start_progress('child', 0);
+
+        // For 'zero' progress, the progress section as immediately complete
+        // within the parent count, so it moves up to 2%.
+        $this->assert_min_max(0.02, 0.02, $progress);
+        $progress->progress(0);
+        $this->assert_min_max(0.02, 0.02, $progress);
+        $progress->end_progress();
+        $this->assert_min_max(0.02, 0.02, $progress);
+
+        set_time_limit(0);
+    }
+
     /**
      * Tests for any exceptions due to invalid calls.
      */
@@ -245,12 +267,12 @@ class backup_progress_testcase extends basic_testcase {
             $this->assertEquals(1, preg_match('~must be 1~', $e->getMessage()));
         }
 
-        // Check invalid start (0).
+        // Check invalid start (-2).
         try {
-            $progress->start_progress('hello', 0);
+            $progress->start_progress('hello', -2);
             $this->fail();
         } catch (coding_exception $e) {
-            $this->assertEquals(1, preg_match('~cannot be zero or negative~', $e->getMessage()));
+            $this->assertEquals(1, preg_match('~cannot be negative~', $e->getMessage()));
         }
 
         // Indeterminate when value expected.
index 3a14e6b..ff9b92e 100644 (file)
@@ -120,7 +120,7 @@ class award_criteria_activity extends award_criteria {
         $course = $DB->get_record('course', array('id' => $this->courseid));
         $info = new completion_info($course);
         $mods = $info->get_activities();
-        $mids = array_map(create_function('$o', 'return $o->id;'), $mods);
+        $mids = array_keys($mods);
 
         if ($this->id !== 0) {
             $existing = array_keys($this->params);
index 0b2a9b1..1865166 100644 (file)
@@ -448,6 +448,14 @@ $CFG->admin = 'admin';
 // config.php file
 //      $CFG->preventexecpath = true;
 //
+// Use the following flag to set userid for noreply user. If not set then moodle will
+// create dummy user and use -ve value as user id.
+//      $CFG->noreplyuserid = -10;
+//
+// As of version 2.6 Moodle supports admin to set support user. If not set, all mails
+// will be sent to supportemail.
+//      $CFG->supportuserid = -20;
+//
 //=========================================================================
 // 7. SETTINGS FOR DEVELOPMENT SERVERS - not intended for production use!!!
 //=========================================================================
index 38a323b..8126449 100644 (file)
@@ -703,18 +703,19 @@ class dndupload_ajax_processor {
         $mod->groupmodelink = $this->cm->groupmodelink;
         $mod->groupmode = $this->cm->groupmode;
 
-        // Trigger mod_created event with information about this module.
-        $eventdata = new stdClass();
-        $eventdata->modulename = $mod->modname;
-        $eventdata->name       = $mod->name;
-        $eventdata->cmid       = $mod->id;
-        $eventdata->courseid   = $this->course->id;
-        $eventdata->userid     = $USER->id;
-        events_trigger('mod_created', $eventdata);
-
-        add_to_log($this->course->id, "course", "add mod",
-                   "../mod/{$mod->modname}/view.php?id=$mod->id",
-                   "{$mod->modname} $instanceid");
+        // Trigger course module created event.
+        $event = \core\event\course_module_created::create(array(
+            'courseid' => $this->course->id,
+            'context'  => context_module::instance($mod->id),
+            'objectid' => $mod->id,
+            'other'    => array(
+                'modulename' => $mod->modname,
+                'name'       => $mod->name,
+                'instanceid' => $instanceid
+            )
+        ));
+        $event->trigger();
+
         add_to_log($this->course->id, $mod->modname, "add",
                    "view.php?id=$mod->id",
                    "$instanceid", $mod->id);
index 50494d5..19988e2 100644 (file)
@@ -1619,7 +1619,7 @@ function set_coursemodule_visible($id, $visible) {
  * @since 2.5
  */
 function course_delete_module($cmid) {
-    global $CFG, $DB, $USER;
+    global $CFG, $DB;
 
     require_once($CFG->libdir.'/gradelib.php');
     require_once($CFG->dirroot.'/blog/lib.php');
@@ -1702,18 +1702,18 @@ function course_delete_module($cmid) {
             "Cannot delete the module $modulename (instance) from section.");
     }
 
-    // Trigger a mod_deleted event with information about this module.
-    $eventdata = new stdClass();
-    $eventdata->modulename = $modulename;
-    $eventdata->cmid       = $cm->id;
-    $eventdata->courseid   = $cm->course;
-    $eventdata->userid     = $USER->id;
-    events_trigger('mod_deleted', $eventdata);
-
-    add_to_log($cm->course, 'course', "delete mod",
-               "view.php?id=$cm->course",
-               "$modulename $cm->instance", $cm->id);
-
+    // Trigger event for course module delete action.
+    $event = \core\event\course_module_deleted::create(array(
+        'courseid' => $cm->course,
+        'context'  => $modcontext,
+        'objectid' => $cm->id,
+        'other'    => array(
+            'modulename' => $modulename,
+            'instanceid'   => $cm->instance,
+        )
+    ));
+    $event->add_record_snapshot('course_modules', $cm);
+    $event->trigger();
     rebuild_course_cache($cm->course, true);
 }
 
@@ -2186,7 +2186,7 @@ function move_courses($courseids, $categoryid) {
     $i = 1;
 
     list($where, $params) = $DB->get_in_or_equal($courseids);
-    $dbcourses = $DB->get_records_select('course', 'id ' . $where, $params);
+    $dbcourses = $DB->get_records_select('course', 'id ' . $where, $params, '', 'id, category, shortname, fullname');
     foreach ($dbcourses as $dbcourse) {
         $course = new stdClass();
         $course->id = $dbcourse->id;
@@ -2200,28 +2200,19 @@ function move_courses($courseids, $categoryid) {
 
         $DB->update_record('course', $course);
 
-        // Store the context.
+        // Update context, so it can be passed to event.
         $context = context_course::instance($course->id);
-
-        // Update the course object we are passing to the event.
-        $dbcourse->category = $course->category;
-        $dbcourse->sortorder = $course->sortorder;
-        if (isset($course->visible)) {
-            $dbcourse->visible = $course->visible;
-        }
+        $context->update_moved($newparent);
 
         // Trigger a course updated event.
         $event = \core\event\course_updated::create(array(
             'objectid' => $course->id,
-            'context' => $context,
+            'context' => context_course::instance($course->id),
             'other' => array('shortname' => $dbcourse->shortname,
                              'fullname' => $dbcourse->fullname)
         ));
-        $event->add_record_snapshot('course', $dbcourse);
         $event->set_legacy_logdata(array($course->id, 'course', 'move', 'edit.php?id=' . $course->id, $course->id));
         $event->trigger();
-
-        $context->update_moved($newparent);
     }
     fix_course_sortorder();
     cache_helper::purge_by_event('changesincourse');
@@ -2474,7 +2465,6 @@ function create_course($data, $editoroptions = NULL) {
         'other' => array('shortname' => $course->shortname,
                          'fullname' => $course->fullname)
     ));
-    $event->add_record_snapshot('course', $course);
     $event->trigger();
 
     return $course;
@@ -2558,8 +2548,8 @@ function update_course($data, $editoroptions = NULL) {
         $newparent = context_coursecat::instance($course->category);
         $context->update_moved($newparent);
     }
-
-    if ($movecat || (isset($data->sortorder) && $oldcourse->sortorder != $data->sortorder)) {
+    $fixcoursesortorder = $movecat || (isset($data->sortorder) && ($oldcourse->sortorder != $data->sortorder));
+    if ($fixcoursesortorder) {
         fix_course_sortorder();
     }
 
@@ -2581,11 +2571,11 @@ function update_course($data, $editoroptions = NULL) {
     // Trigger a course updated event.
     $event = \core\event\course_updated::create(array(
         'objectid' => $course->id,
-        'context' => $context,
+        'context' => context_course::instance($course->id),
         'other' => array('shortname' => $course->shortname,
                          'fullname' => $course->fullname)
     ));
-    $event->add_record_snapshot('course', $course);
+
     $event->set_legacy_logdata(array($course->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id));
     $event->trigger();
 
index 88eefe5..efbf6a1 100644 (file)
@@ -140,16 +140,24 @@ function add_moduleinfo($moduleinfo, $course, $mform = null) {
         condition_info::update_cm_from_form((object)array('id'=>$moduleinfo->coursemodule), $moduleinfo, false);
     }
 
-    $eventname = 'mod_created';
+    // Trigger event based on the action we did.
+    $event = \core\event\course_module_created::create(array(
+         'courseid' => $course->id,
+         'context'  => $modcontext,
+         'objectid' => $moduleinfo->coursemodule,
+         'other'    => array(
+             'modulename' => $moduleinfo->modulename,
+             'name'       => $moduleinfo->name,
+             'instanceid' => $moduleinfo->instance
+         )
+    ));
+    $event->trigger();
 
-    add_to_log($course->id, "course", "add mod",
-               "../mod/$moduleinfo->modulename/view.php?id=$moduleinfo->coursemodule",
-               "$moduleinfo->modulename $moduleinfo->instance");
     add_to_log($course->id, $moduleinfo->modulename, "add",
                "view.php?id=$moduleinfo->coursemodule",
                "$moduleinfo->instance", $moduleinfo->coursemodule);
 
-    $moduleinfo = edit_module_post_actions($moduleinfo, $course, 'mod_created');
+    $moduleinfo = edit_module_post_actions($moduleinfo, $course);
 
     return $moduleinfo;
 }
@@ -157,28 +165,19 @@ function add_moduleinfo($moduleinfo, $course, $mform = null) {
 
 /**
  * Common create/update module module actions that need to be processed as soon as a module is created/updaded.
- * For example: trigger event, create grade parent category, add outcomes, rebuild caches, regrade, save plagiarism settings...
+ * For example:create grade parent category, add outcomes, rebuild caches, regrade, save plagiarism settings...
+ * Please note this api does not trigger events as of MOODLE 2.6. Please trigger events before calling this api.
  *
  * @param object $moduleinfo the module info
  * @param object $course the course of the module
- * @param string $eventname the event name to trigger
  *
- * return object moduleinfo update with grading management info
+ * @return object moduleinfo update with grading management info
  */
-function edit_module_post_actions($moduleinfo, $course, $eventname) {
-    global $USER, $CFG;
+function edit_module_post_actions($moduleinfo, $course) {
+    global $CFG;
 
     $modcontext = context_module::instance($moduleinfo->coursemodule);
 
-    // Trigger mod_created/mod_updated event with information about this module.
-    $eventdata = new stdClass();
-    $eventdata->modulename = $moduleinfo->modulename;
-    $eventdata->name       = $moduleinfo->name;
-    $eventdata->cmid       = $moduleinfo->coursemodule;
-    $eventdata->courseid   = $course->id;
-    $eventdata->userid     = $USER->id;
-    events_trigger($eventname, $eventdata);
-
     // Sync idnumber with grade_item.
     if ($grade_item = grade_item::fetch(array('itemtype'=>'mod', 'itemmodule'=>$moduleinfo->modulename,
                  'iteminstance'=>$moduleinfo->instance, 'itemnumber'=>0, 'courseid'=>$course->id))) {
@@ -490,14 +489,24 @@ function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
         $completion->reset_all_state($cm);
     }
 
-    add_to_log($course->id, "course", "update mod",
-               "../mod/$moduleinfo->modulename/view.php?id=$moduleinfo->coursemodule",
-               "$moduleinfo->modulename $moduleinfo->instance");
+    // Trigger event based on the action we did.
+    $event = \core\event\course_module_updated::create(array(
+        'courseid' => $course->id,
+        'context'  => $modcontext,
+        'objectid' => $moduleinfo->coursemodule,
+        'other'    => array(
+            'modulename' => $moduleinfo->modulename,
+            'name'       => $moduleinfo->name,
+            'instanceid' => $moduleinfo->instance
+        )
+    ));
+    $event->trigger();
+
     add_to_log($course->id, $moduleinfo->modulename, "update",
                "view.php?id=$moduleinfo->coursemodule",
                "$moduleinfo->instance", $moduleinfo->coursemodule);
 
-    $moduleinfo = edit_module_post_actions($moduleinfo, $course, 'mod_updated');
+    $moduleinfo = edit_module_post_actions($moduleinfo, $course);
 
     return array($cm, $moduleinfo);
 }
index a890de2..f4ac8aa 100644 (file)
@@ -317,6 +317,7 @@ class core_course_courselib_testcase extends advanced_testcase {
         // Test specific to the module.
         $modulerunasserts = $modulename.'_create_run_asserts';
         $this->$modulerunasserts($moduleinfo, $dbmodinstance);
+        return $moduleinfo;
     }
 
     /**
@@ -569,6 +570,7 @@ class core_course_courselib_testcase extends advanced_testcase {
         // Test specific to the module.
         $modulerunasserts = $modulename.'_update_run_asserts';
         $this->$modulerunasserts($moduleinfo, $dbmodinstance);
+        return $moduleinfo;
    }
 
 
@@ -1390,6 +1392,8 @@ class core_course_courselib_testcase extends advanced_testcase {
      * Test that triggering a course_created event works as expected.
      */
     public function test_course_created_event() {
+        global $DB;
+
         $this->resetAfterTest();
 
         // Catch the events.
@@ -1397,6 +1401,8 @@ class core_course_courselib_testcase extends advanced_testcase {
 
         // Create the course.
         $course = $this->getDataGenerator()->create_course();
+        // Get course from DB for comparison.
+        $course = $DB->get_record('course', array('id' => $course->id));
 
         // Capture the event.
         $events = $sink->get_events();
@@ -1407,7 +1413,7 @@ class core_course_courselib_testcase extends advanced_testcase {
         $this->assertInstanceOf('\core\event\course_created', $event);
         $this->assertEquals('course', $event->objecttable);
         $this->assertEquals($course->id, $event->objectid);
-        $this->assertEquals(context_course::instance($course->id)->id, $event->contextid);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
         $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
         $this->assertEquals('course_created', $event->get_legacy_eventname());
         $this->assertEventLegacyData($course, $event);
@@ -1432,69 +1438,60 @@ class core_course_courselib_testcase extends advanced_testcase {
         // Create a hidden category we are going to move this course to.
         $categoryhidden = $this->getDataGenerator()->create_category(array('visible' => 0));
 
-        // Catch the update events.
+        // Update course and catch course_updated event.
         $sink = $this->redirectEvents();
-
-        // Keep track of the old sortorder.
-        $sortorder = $course->sortorder;
-
-        // Call update_course which will trigger a course_updated event.
         update_course($course);
-
-        // Return the updated course information from the DB.
-        $updatedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
-
-        // Now move the course to the category, this will also trigger an event.
-        move_courses(array($course->id), $category->id);
-
-        // Return the moved course information from the DB.
-        $movedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
-
-        // Now move the course to the hidden category, this will also trigger an event.
-        move_courses(array($course->id), $categoryhidden->id);
-
-        // Return the moved course information from the DB.
-        $movedcoursehidden = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
-
-        // Now we want to set the sortorder back to what it was before fix_course_sortorder() was called. The reason for
-        // this is because update_course() and move_courses() call fix_course_sortorder() which alters the sort order in
-        // the DB, but it does not set the value of the sortorder for the course object passed to the event.
-        $updatedcourse->sortorder = $sortorder;
-        $movedcourse->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - 1;
-        $movedcoursehidden->sortorder = $categoryhidden->sortorder + MAX_COURSES_IN_CATEGORY - 1;
-
-        // Capture the events.
         $events = $sink->get_events();
         $sink->close();
 
-        // Validate the events.
-        $event = $events[0];
+        // Get updated course information from the DB.
+        $updatedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
+        // Validate event.
+        $event = array_shift($events);
         $this->assertInstanceOf('\core\event\course_updated', $event);
         $this->assertEquals('course', $event->objecttable);
         $this->assertEquals($updatedcourse->id, $event->objectid);
-        $this->assertEquals(context_course::instance($updatedcourse->id)->id, $event->contextid);
-        $this->assertEquals($updatedcourse, $event->get_record_snapshot('course', $updatedcourse->id));
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEquals($updatedcourse, $event->get_record_snapshot('course', $event->objectid));
         $this->assertEquals('course_updated', $event->get_legacy_eventname());
         $this->assertEventLegacyData($updatedcourse, $event);
         $expectedlog = array($updatedcourse->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id);
         $this->assertEventLegacyLogData($expectedlog, $event);
 
-        $event = $events[1];
+        // Move course and catch course_updated event.
+        $sink = $this->redirectEvents();
+        move_courses(array($course->id), $category->id);
+        $events = $sink->get_events();
+        $sink->close();
+
+        // Return the moved course information from the DB.
+        $movedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
+        // Validate event.
+        $event = array_shift($events);
         $this->assertInstanceOf('\core\event\course_updated', $event);
         $this->assertEquals('course', $event->objecttable);
         $this->assertEquals($movedcourse->id, $event->objectid);
-        $this->assertEquals(context_course::instance($movedcourse->id)->id, $event->contextid);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
         $this->assertEquals($movedcourse, $event->get_record_snapshot('course', $movedcourse->id));
         $this->assertEquals('course_updated', $event->get_legacy_eventname());
         $this->assertEventLegacyData($movedcourse, $event);
         $expectedlog = array($movedcourse->id, 'course', 'move', 'edit.php?id=' . $movedcourse->id, $movedcourse->id);
         $this->assertEventLegacyLogData($expectedlog, $event);
 
-        $event = $events[2];
+        // Move course to hidden category and catch course_updated event.
+        $sink = $this->redirectEvents();
+        move_courses(array($course->id), $categoryhidden->id);
+        $events = $sink->get_events();
+        $sink->close();
+
+        // Return the moved course information from the DB.
+        $movedcoursehidden = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
+        // Validate event.
+        $event = array_shift($events);
         $this->assertInstanceOf('\core\event\course_updated', $event);
         $this->assertEquals('course', $event->objecttable);
         $this->assertEquals($movedcoursehidden->id, $event->objectid);
-        $this->assertEquals(context_course::instance($movedcoursehidden->id)->id, $event->contextid);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
         $this->assertEquals($movedcoursehidden, $event->get_record_snapshot('course', $movedcoursehidden->id));
         $this->assertEquals('course_updated', $event->get_legacy_eventname());
         $this->assertEventLegacyData($movedcoursehidden, $event);
@@ -1892,4 +1889,298 @@ class core_course_courselib_testcase extends advanced_testcase {
         $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
         $this->assertEquals(2, count($cms));
     }
+
+    /**
+     * Tests for event related to course module creation.
+     */
+    public function test_course_module_created_event() {
+        global $USER, $DB;
+        $this->resetAfterTest();
+
+        // Create an assign module.
+        $sink = $this->redirectEvents();
+        $modinfo = $this->create_specific_module_test('assign');
+        $events = $sink->get_events();
+        $event = array_pop($events);
+        $sink->close();
+
+        $cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST);
+        $mod = $DB->get_record('assign', array('id' => $modinfo->instance), '*', MUST_EXIST);
+
+        // Validate event data.
+        $this->assertInstanceOf('\core\event\course_module_created', $event);
+        $this->assertEquals($cm->id, $event->objectid);
+        $this->assertEquals($USER->id, $event->userid);
+        $this->assertEquals('course_modules', $event->objecttable);
+        $url = new moodle_url('/mod/assign/view.php', array('id' => $mod->id));
+        $this->assertEquals($url, $event->get_url());
+
+        // Test legacy data.
+        $this->assertSame('mod_created', $event->get_legacy_eventname());
+        $eventdata = new stdClass();
+        $eventdata->modulename = 'assign';
+        $eventdata->name       = $mod->name;
+        $eventdata->cmid       = $cm->id;
+        $eventdata->courseid   = $cm->course;
+        $eventdata->userid     = $USER->id;
+        $this->assertEventLegacyData($eventdata, $event);
+
+        $arr = array($cm->course, "course", "add mod", "../mod/assign/view.php?id=$mod->id", "assign $cm->instance");
+        $this->assertEventLegacyLogData($arr, $event);
+
+    }
+
+    /**
+     * Tests for event validations related to course module creation.
+     */
+    public function test_course_module_created_event_exceptions() {
+
+        $this->resetAfterTest();
+
+        // Generate data.
+        $modinfo = $this->create_specific_module_test('assign');
+        $context = context_module::instance($modinfo->coursemodule);
+
+        // Test not setting instanceid.
+        try {
+            $event = \core\event\course_module_created::create(array(
+                'courseid' => $modinfo->course,
+                'context'  => $context,
+                'objectid' => $modinfo->coursemodule,
+                'other'    => array(
+                    'modulename' => 'assign',
+                    'name'       => 'My assignment',
+                )
+            ));
+            $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
+                    other['instanceid']");
+        } catch (coding_exception $e) {
+            $this->assertContains("Field other['instanceid'] cannot be empty", $e->getMessage());
+        }
+
+        // Test not setting modulename.
+        try {
+            $event = \core\event\course_module_created::create(array(
+                'courseid' => $modinfo->course,
+                'context'  => $context,
+                'objectid' => $modinfo->coursemodule,
+                'other'    => array(
+                    'instanceid' => $modinfo->instance,
+                    'name'       => 'My assignment',
+                )
+            ));
+            $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
+                    other['modulename']");
+        } catch (coding_exception $e) {
+            $this->assertContains("Field other['modulename'] cannot be empty", $e->getMessage());
+        }
+
+        // Test not setting name.
+
+        try {
+            $event = \core\event\course_module_created::create(array(
+                'courseid' => $modinfo->course,
+                'context'  => $context,
+                'objectid' => $modinfo->coursemodule,
+                'other'    => array(
+                    'modulename' => 'assign',
+                    'instanceid' => $modinfo->instance,
+                )
+            ));
+            $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
+                    other['name']");
+        } catch (coding_exception $e) {
+            $this->assertContains("Field other['name'] cannot be empty", $e->getMessage());
+        }
+
+    }
+
+    /**
+     * Tests for event related to course module updates.
+     */
+    public function test_course_module_updated_event() {
+        global $USER, $DB;
+        $this->resetAfterTest();
+
+        // Update a forum module.
+        $sink = $this->redirectEvents();
+        $modinfo = $this->update_specific_module_test('forum');
+        $events = $sink->get_events();
+        $event = array_pop($events);
+        $sink->close();
+
+        $cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST);
+        $mod = $DB->get_record('forum', array('id' => $cm->instance), '*', MUST_EXIST);
+
+        // Validate event data.
+        $this->assertInstanceOf('\core\event\course_module_updated', $event);
+        $this->assertEquals($cm->id, $event->objectid);
+        $this->assertEquals($USER->id, $event->userid);
+        $this->assertEquals('course_modules', $event->objecttable);
+        $url = new moodle_url('/mod/forum/view.php', array('id' => $mod->id));
+        $this->assertEquals($url, $event->get_url());
+
+        // Test legacy data.
+        $this->assertSame('mod_updated', $event->get_legacy_eventname());
+        $eventdata = new stdClass();
+        $eventdata->modulename = 'forum';
+        $eventdata->name       = $mod->name;
+        $eventdata->cmid       = $cm->id;
+        $eventdata->courseid   = $cm->course;
+        $eventdata->userid     = $USER->id;
+        $this->assertEventLegacyData($eventdata, $event);
+
+        $arr = array($cm->course, "course", "update mod", "../mod/forum/view.php?id=$mod->id", "forum $cm->instance");
+        $this->assertEventLegacyLogData($arr, $event);
+
+    }
+
+    /**
+     * Tests for event validations related to course module update.
+     */
+    public function test_course_module_updated_event_exceptions() {
+
+        $this->resetAfterTest();
+
+        // Generate data.
+        $modinfo = $this->create_specific_module_test('assign');
+        $context = context_module::instance($modinfo->coursemodule);
+
+        // Test not setting instanceid.
+        try {
+            $event = \core\event\course_module_updated::create(array(
+                'courseid' => $modinfo->course,
+                'context'  => $context,
+                'objectid' => $modinfo->coursemodule,
+                'other'    => array(
+                    'modulename' => 'assign',
+                    'name'       => 'My assignment',
+                )
+            ));
+            $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
+                    other['instanceid']");
+        } catch (coding_exception $e) {
+            $this->assertContains("Field other['instanceid'] cannot be empty", $e->getMessage());
+        }
+
+        // Test not setting modulename.
+        try {
+            $event = \core\event\course_module_updated::create(array(
+                'courseid' => $modinfo->course,
+                'context'  => $context,
+                'objectid' => $modinfo->coursemodule,
+                'other'    => array(
+                    'instanceid' => $modinfo->instance,
+                    'name'       => 'My assignment',
+                )
+            ));
+            $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
+                    other['modulename']");
+        } catch (coding_exception $e) {
+            $this->assertContains("Field other['modulename'] cannot be empty", $e->getMessage());
+        }
+
+        // Test not setting name.
+
+        try {
+            $event = \core\event\course_module_updated::create(array(
+                'courseid' => $modinfo->course,
+                'context'  => $context,
+                'objectid' => $modinfo->coursemodule,
+                'other'    => array(
+                    'modulename' => 'assign',
+                    'instanceid' => $modinfo->instance,
+                )
+            ));
+            $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
+                    other['name']");
+        } catch (coding_exception $e) {
+            $this->assertContains("Field other['name'] cannot be empty", $e->getMessage());
+        }
+
+    }
+
+    /**
+     * Tests for event related to course module delete.
+     */
+    public function test_course_module_deleted_event() {
+        global $USER, $DB;
+        $this->resetAfterTest();
+
+        // Create and delete a module.
+        $sink = $this->redirectEvents();
+        $modinfo = $this->create_specific_module_test('forum');
+        $cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST);
+        course_delete_module($modinfo->coursemodule);
+        $events = $sink->get_events();
+        $event = array_pop($events); // delete module event.;
+        $sink->close();
+
+        // Validate event data.
+        $this->assertInstanceOf('\core\event\course_module_deleted', $event);
+        $this->assertEquals($cm->id, $event->objectid);
+        $this->assertEquals($USER->id, $event->userid);
+        $this->assertEquals('course_modules', $event->objecttable);
+        $this->assertEquals(null, $event->get_url());
+        $this->assertEquals($cm, $event->get_record_snapshot('course_modules', $cm->id));
+
+        // Test legacy data.
+        $this->assertSame('mod_deleted', $event->get_legacy_eventname());
+        $eventdata = new stdClass();
+        $eventdata->modulename = 'forum';
+        $eventdata->cmid       = $cm->id;
+        $eventdata->courseid   = $cm->course;
+        $eventdata->userid     = $USER->id;
+        $this->assertEventLegacyData($eventdata, $event);
+
+        $arr = array($cm->course, 'course', "delete mod", "view.php?id=$cm->course", "forum $cm->instance", $cm->id);
+        $this->assertEventLegacyLogData($arr, $event);
+
+    }
+
+    /**
+     * Tests for event validations related to course module deletion.
+     */
+    public function test_course_module_deleted_event_exceptions() {
+
+        $this->resetAfterTest();
+
+        // Generate data.
+        $modinfo = $this->create_specific_module_test('assign');
+        $context = context_module::instance($modinfo->coursemodule);
+
+        // Test not setting instanceid.
+        try {
+            $event = \core\event\course_module_deleted::create(array(
+                'courseid' => $modinfo->course,
+                'context'  => $context,
+                'objectid' => $modinfo->coursemodule,
+                'other'    => array(
+                    'modulename' => 'assign',
+                    'name'       => 'My assignment',
+                )
+            ));
+            $this->fail("Event validation should not allow \\core\\event\\course_module_deleted to be triggered without
+                    other['instanceid']");
+        } catch (coding_exception $e) {
+            $this->assertContains("Field other['instanceid'] cannot be empty", $e->getMessage());
+        }
+
+        // Test not setting modulename.
+        try {
+            $event = \core\event\course_module_deleted::create(array(
+                'courseid' => $modinfo->course,
+                'context'  => $context,
+                'objectid' => $modinfo->coursemodule,
+                'other'    => array(
+                    'instanceid' => $modinfo->instance,
+                    'name'       => 'My assignment',
+                )
+            ));
+            $this->fail("Event validation should not allow \\core\\event\\course_module_deleted to be triggered without
+                    other['modulename']");
+        } catch (coding_exception $e) {
+            $this->assertContains("Field other['modulename'] cannot be empty", $e->getMessage());
+        }
+    }
 }
index 9b0fa48..22d2f74 100644 (file)
@@ -433,7 +433,7 @@ class enrol_self_plugin extends enrol_plugin {
         if ($rusers) {
             $contact = reset($rusers);
         } else {
-            $contact = generate_email_supportuser();
+            $contact = core_user::get_support_user();
         }
 
         // Directly emailing welcome message rather than using messaging.
index aac240a..54f0b53 100644 (file)
@@ -3,32 +3,19 @@
     require('../config.php');
     require_once($CFG->libdir.'/eventslib.php');
 
-    if ($form = data_submitted()) { // form submitted, do not check referer (original page unknown)!
-
-    /// Only deal with real users
+    // Form submitted, do not check referer (original page unknown).
+    if ($form = data_submitted()) {
+        // Only deal with real users.
         if (!isloggedin()) {
             redirect($CFG->wwwroot);
         }
 
-    /// Work out who to send the message to
-        if (!$admin = get_admin() ) {
-            print_error('cannotfindadmin', 'debug');
-        }
-
-        $supportuser = new stdClass();
-        $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $admin->email;
-        $supportuser->firstname = $CFG->supportname ? $CFG->supportname : $admin->firstname;
-        $supportuser->lastname = $CFG->supportname ? '' : $admin->lastname;
-        // emailstop could be hard coded "false" to ensure error reports are sent
-        // but then admin's would have to alter their messaging preferences to temporarily stop them
-        $supportuser->emailstop = $admin->emailstop;
-        $supportuser->maildisplay = true;
-
-    /// Send the message and redirect
+        // Send the message and redirect.
         $eventdata = new stdClass();
-        $eventdata->modulename        = 'moodle';
+        $eventdata->component        = 'moodle';
+        $eventdata->name             = 'errors';
         $eventdata->userfrom          = $USER;
-        $eventdata->userto            = $supportuser;
+        $eventdata->userto            = core_user::get_support_user();
         $eventdata->subject           = 'Error: '. $form->referer .' -> '. $form->requested;
         $eventdata->fullmessage       = $form->text;
         $eventdata->fullmessageformat = FORMAT_PLAIN;
index f09a79a..8cdc537 100644 (file)
@@ -63,7 +63,8 @@ if ($id) {
 
             $displaystring = null;
             if (!empty($grade_item->itemmodule)) {
-                $displaystring = get_string('modulename', $grade_item->itemmodule).': '.$grade_item->get_name();
+                $displaystring = get_string('modulename', $grade_item->itemmodule).get_string('labelsep', 'langconfig')
+                        .$grade_item->get_name();
             } else {
                 $displaystring = $grade_item->get_name();
             }
index 73ff680..eb427c9 100644 (file)
@@ -88,17 +88,22 @@ class grade_import_mapping_form extends moodleform {
         }
         $mform->addElement('select', 'mapfrom', get_string('mapfrom', 'grades'), $mapfromoptions);
 
-        $maptooptions = array('userid'=>'userid', 'username'=>'username', 'useridnumber'=>'useridnumber', 'useremail'=>'useremail', '0'=>'ignore');
+        $maptooptions = array(
+            'userid'       => get_string('userid', 'grades'),
+            'username'     => get_string('username'),
+            'useridnumber' => get_string('idnumber'),
+            'useremail'    => get_string('email'),
+            '0'            => get_string('ignore', 'grades')
+        );
         $mform->addElement('select', 'mapto', get_string('mapto', 'grades'), $maptooptions);
 
         $mform->addElement('header', 'general', get_string('mappings', 'grades'));
 
-        // add a comment option
-
-        $comments = array();
+        // Add a feedback option.
+        $feedbacks = array();
         if ($gradeitems = $this->_customdata['gradeitems']) {
             foreach ($gradeitems as $itemid => $itemname) {
-                $comments['feedback_'.$itemid] = 'comments for '.$itemname;
+                $feedbacks['feedback_'.$itemid] = get_string('feedbackforgradeitems', 'grades', $itemname);
             }
         }
 
@@ -106,11 +111,16 @@ class grade_import_mapping_form extends moodleform {
             $i = 0; // index
             foreach ($header as $h) {
                 $h = trim($h);
-                // this is what each header maps to
-                $mform->addElement('selectgroups', 'mapping_'.$i, s($h),
-                    array('others'=>array('0'=>'ignore', 'new'=>'new gradeitem'),
-                    'gradeitems'=>$gradeitems,
-                    'comments'=>$comments));
+                // This is what each header maps to.
+                $headermapsto = array(
+                    get_string('others', 'grades')     => array(
+                        '0'   => get_string('ignore', 'grades'),
+                        'new' => get_string('newitem', 'grades')
+                    ),
+                    get_string('gradeitems', 'grades') => $gradeitems,
+                    get_string('feedbacks', 'grades')  => $feedbacks
+                );
+                $mform->addElement('selectgroups', 'mapping_'.$i, s($h), $headermapsto);
                 $i++;
             }
         }
index d4804fc..a6dcf42 100644 (file)
@@ -1585,14 +1585,8 @@ class grade_report_grader extends grade_report {
      */
     public function is_fixed_students() {
         global $CFG;
-        return $CFG->grade_report_fixedstudents &&
-            (core_useragent::check_ie_version('7.0') ||
-             core_useragent::check_firefox_version('2.0') ||
-             core_useragent::check_gecko_version('2006010100') ||
-             core_useragent::check_camino_version('1.0') ||
-             core_useragent::check_opera_version('6.0') ||
-             core_useragent::check_chrome_version('6') ||
-             core_useragent::check_safari_version('300'));
+
+        return $CFG->grade_report_fixedstudents;
     }
 
     /**
index ef68ec9..235a81c 100644 (file)
@@ -571,8 +571,6 @@ $string['guestroleid'] = 'Role for guest';
 $string['guestroleid_help'] = 'This role is automatically assigned to the guest user. It is also temporarily assigned to not enrolled users that enter the course via guest enrolment plugin.';
 $string['helpadminseesall'] = 'Do admins see all calendar events or just those that apply to themselves?';
 $string['helpcalendarcustomexport'] = 'Enable custom date range export option in calendar exports. Calendar exports must be enabled before this is effective.';
-$string['helpcalendarsettings'] = 'Configure various calendar and date/time-related aspects of Moodle';
-$string['helpcalendartype'] = 'This is the calendar type that will be used throughout your site.';
 $string['helpexportlookahead'] = 'How many days in the future does the calendar look for events during export for the custom export option?';
 $string['helpexportlookback'] = 'How many days in the past does the calendar look for events during export for the custom export option?';
 $string['helpforcetimezone'] = 'You can allow users to individually select their timezone, or force a timezone for everyone.';
index 99ab859..005f649 100644 (file)
@@ -202,6 +202,8 @@ $string['feedback'] = 'Feedback';
 $string['feedback_help'] = 'This box enables any comments about the grade to be added.';
 $string['feedbackadd'] = 'Add feedback';
 $string['feedbackedit'] = 'Edit feedback';
+$string['feedbackforgradeitems'] = 'Feedback for {$a}';
+$string['feedbacks'] = 'Feedbacks';
 $string['feedbacksaved'] = 'Feedback saved';
 $string['feedbackview'] = 'View feedback';
 $string['finalgrade'] = 'Final grade';
@@ -335,6 +337,7 @@ $string['chooseaction'] = 'Choose an action ...';
 $string['choosecategory'] = 'Select category';
 $string['identifier'] = 'Identify user by';
 $string['idnumbers'] = 'ID numbers';
+$string['ignore'] = 'Ignore';
 $string['import'] = 'Import';
 $string['importcsv'] = 'Import CSV';
 $string['importcustom'] = 'Import as custom outcomes (only this course)';
@@ -446,6 +449,7 @@ $string['numberofgrades'] = 'Number of grades';
 $string['onascaleof'] = 'on a scale of {$a->grademin} to {$a->grademax}';
 $string['operations'] = 'Operations';
 $string['options'] = 'Options';
+$string['others'] = 'Others';
 $string['outcome'] = 'Outcome';
 $string['outcome_help'] = 'This setting determines the outcome which this grade item will represent in the gradebook.';
 $string['outcomeassigntocourse'] = 'Assign another outcome to this course';
@@ -656,6 +660,7 @@ $string['usenoscale'] = 'Use no scale';
 $string['usepercent'] = 'Use percent';
 $string['user'] = 'User';
 $string['usergrade'] = 'User {$a->fullname} ({$a->useridnumber}) on item {$a->gradeidnumber}';
+$string['userid'] = 'User ID';
 $string['userpreferences'] = 'User preferences';
 $string['userenrolmentsuspended'] = 'User enrolment suspended';
 $string['useweighted'] = 'Use weighted';
index 488667d..5ea320f 100644 (file)
@@ -665,6 +665,9 @@ $string['eventcoursecategorydeleted'] = 'Category deleted';
 $string['eventcoursecontentdeleted'] = 'Course content deleted';
 $string['eventcoursecreated'] = 'Course created';
 $string['eventcoursedeleted'] = 'Course deleted';
+$string['eventcoursemodulecreated'] = 'Course module created';
+$string['eventcoursemoduledeleted'] = 'Course module deleted';
+$string['eventcoursemoduleupdated'] = 'Course module updated';
 $string['eventcourserestored'] = 'Course restored';
 $string['eventcourseupdated'] = 'Course updated';
 $string['eventcoursesectionupdated'] = ' Course section updated';
index daa3126..c57e6c0 100644 (file)
@@ -61,19 +61,6 @@ function ajaxenabled(array $browsers = null) {
         }
     }
 
-    $ie = core_useragent::check_browser_version('MSIE', 6.0);
-    $ff = core_useragent::check_browser_version('Gecko', 20051106);
-    $op = core_useragent::check_browser_version('Opera', 9.0);
-    $sa = core_useragent::check_browser_version('Safari', 412);
-    $ch = core_useragent::check_browser_version('Chrome', 6);
-
-    if (!$ie && !$ff && !$op && !$sa && !$ch) {
-        /** @see http://en.wikipedia.org/wiki/User_agent */
-        // Gecko build 20051107 is what is in Firefox 1.5.
-        // We still have issues with AJAX in other browsers.
-        return false;
-    }
-
     if (!empty($CFG->enableajax)) {
         return true;
     } else {
index c24c5be..f8fac6e 100644 (file)
@@ -707,7 +707,7 @@ function login_lock_account($user) {
         }
 
         $site = get_site();
-        $supportuser = generate_email_supportuser();
+        $supportuser = core_user::get_support_user();
 
         $data = new stdClass();
         $data->firstname = $user->firstname;
index 598a88e..1a300b9 100644 (file)
@@ -1136,13 +1136,20 @@ function profile_display_badges($userid, $courseid = 0) {
     global $CFG, $PAGE, $USER, $SITE;
     require_once($CFG->dirroot . '/badges/renderer.php');
 
-    if ($USER->id == $userid || has_capability('moodle/badges:viewotherbadges', context_user::instance($USER->id))) {
+    // Determine context.
+    if (isloggedin()) {
+        $context = context_user::instance($USER->id);
+    } else {
+        $context = context_system::instance();
+    }
+
+    if ($USER->id == $userid || has_capability('moodle/badges:viewotherbadges', $context)) {
         $records = badges_get_user_badges($userid, $courseid, null, null, null, true);
         $renderer = new core_badges_renderer($PAGE, '');
 
         // Print local badges.
         if ($records) {
-            $left = get_string('localbadgesp', 'badges', $SITE->fullname);
+            $left = get_string('localbadgesp', 'badges', format_string($SITE->fullname));
             $right = $renderer->print_badges_list($records, $userid, true);
             echo html_writer::tag('dt', $left);
             echo html_writer::tag('dd', $right);
diff --git a/lib/classes/event/course_module_created.php b/lib/classes/event/course_module_created.php
new file mode 100644 (file)
index 0000000..442c02e
--- /dev/null
@@ -0,0 +1,126 @@
+<?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/>.
+
+/**
+ * Event to be triggered when a new course module is created.
+ *
+ * @package    core
+ * @copyright  2013 Ankit Agarwal
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
+ */
+
+namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class course_module_created
+ *
+ * Class for event to be triggered when a new course module is created.
+ *
+ * @package    core
+ * @copyright  2013 Ankit Agarwal
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
+ */
+class course_module_created extends base {
+
+    /**
+     * Set basic properties for the event.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'course_modules';
+        $this->data['crud'] = 'c';
+        $this->data['level'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventcoursemodulecreated', 'core');
+    }
+
+    /**
+     * Returns non-localised event description with id's for admin use only.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'The '. $this->other['modulename'] . ' module ' . $this->other['name']. ' was created by user with id '.
+               $this->userid;
+    }
+
+    /**
+     * Returns relevant URL.
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/mod/' . $this->other['modulename'] . '/view.php', array('id' => $this->other['instanceid']));
+    }
+
+    /**
+     * Legacy event name.
+     *
+     * @return string legacy event name
+     */
+    public static function get_legacy_eventname() {
+        return 'mod_created';
+    }
+
+    /**
+     * Legacy event data.
+     *
+     * @return \stdClass
+     */
+    protected function get_legacy_eventdata() {
+        $eventdata = new \stdClass();
+        $eventdata->modulename = $this->other['modulename'];
+        $eventdata->name       = $this->other['name'];
+        $eventdata->cmid       = $this->objectid;
+        $eventdata->courseid   = $this->courseid;
+        $eventdata->userid     = $this->userid;
+        return $eventdata;
+    }
+
+    /**
+     * replace add_to_log() statement.
+     *
+     * @return array of parameters to be passed to legacy add_to_log() function.
+     */
+    protected function get_legacy_logdata() {
+        return array ($this->courseid, "course", "add mod", "../mod/" . $this->other['modulename'] . "/view.php?id=" .
+                $this->other['instanceid'], $this->other['modulename'] . " " . $this->other['instanceid']);
+    }
+
+    /**
+     * custom validations
+     *
+     * Throw \coding_exception notice in case of any problems.
+     */
+    protected function validate_data() {
+        if (!isset($this->other['modulename'])) {
+            throw new \coding_exception("Field other['modulename'] cannot be empty");
+        }
+        if (!isset($this->other['instanceid'])) {
+            throw new \coding_exception("Field other['instanceid'] cannot be empty");
+        }
+        if (!isset($this->other['name'])) {
+            throw new \coding_exception("Field other['name'] cannot be empty");
+        }
+    }
+}
+
diff --git a/lib/classes/event/course_module_deleted.php b/lib/classes/event/course_module_deleted.php
new file mode 100644 (file)
index 0000000..67a4b02
--- /dev/null
@@ -0,0 +1,114 @@
+<?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/>.
+
+/**
+ * Event to be triggered when a new course module is deleted.
+ *
+ * @package    core
+ * @copyright  2013 Ankit Agarwal
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
+ */
+
+namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class course_module_deleted
+ *
+ * Class for event to be triggered when a course module is deleted.
+ *
+ * @package    core
+ * @copyright  2013 Ankit Agarwal
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
+ */
+class course_module_deleted extends base {
+
+    /**
+     * Set basic properties for the event.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'course_modules';
+        $this->data['crud'] = 'd';
+        $this->data['level'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventcoursemoduledeleted', 'core');
+    }
+
+    /**
+     * Returns non-localised event description with id's for admin use only.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'The module ' . $this->other['modulename'] . ' with instance id ' . $this->other['instanceid']. ' was deleted by
+                user with id ' . $this->userid;
+    }
+
+    /**
+     * Legacy event name.
+     *
+     * @return string legacy event name
+     */
+    public static function get_legacy_eventname() {
+        return 'mod_deleted';
+    }
+
+    /**
+     * Legacy event data.
+     *
+     * @return \stdClass
+     */
+    protected function get_legacy_eventdata() {
+        $eventdata = new \stdClass();
+        $eventdata->modulename = $this->other['modulename'];
+        $eventdata->cmid       = $this->objectid;
+        $eventdata->courseid   = $this->courseid;
+        $eventdata->userid     = $this->userid;
+        return $eventdata;
+    }
+
+    /**
+     * replace add_to_log() statement.
+     *
+     * @return array of parameters to be passed to legacy add_to_log() function.
+     */
+    protected function get_legacy_logdata() {
+        return array ($this->courseid, "course", "delete mod", "view.php?id=$this->courseid",
+                $this->other['modulename'] . " " . $this->other['instanceid'], $this->objectid);
+    }
+
+    /**
+     * custom validations
+     *
+     * Throw \coding_exception notice in case of any problems.
+     */
+    protected function validate_data() {
+        if (!isset($this->other['modulename'])) {
+            throw new \coding_exception("Field other['modulename'] cannot be empty");
+        }
+        if (!isset($this->other['instanceid'])) {
+            throw new \coding_exception("Field other['instanceid'] cannot be empty");
+        }
+    }
+}
+
diff --git a/lib/classes/event/course_module_updated.php b/lib/classes/event/course_module_updated.php
new file mode 100644 (file)
index 0000000..e6a7b19
--- /dev/null
@@ -0,0 +1,126 @@
+<?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/>.
+
+/**
+ * Event to be triggered when a new course module is updated.
+ *
+ * @package    core
+ * @copyright  2013 Ankit Agarwal
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
+ */
+
+namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class course_module_updated
+ *
+ * Class for event to be triggered when a course module is updated.
+ *
+ * @package    core
+ * @copyright  2013 Ankit Agarwal
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
+ */
+class course_module_updated extends base {
+
+    /**
+     * Set basic properties for the event.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'course_modules';
+        $this->data['crud'] = 'u';
+        $this->data['level'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventcoursemoduleupdated', 'core');
+    }
+
+    /**
+     * Returns non-localised event description with id's for admin use only.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'The ' . $this->other['modulename'] . ' module ' . $this->other['name']. ' was updated by user with id '.
+               $this->userid;
+    }
+
+    /**
+     * Returns relevant URL.
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/mod/' . $this->other['modulename'] . '/view.php', array('id' => $this->other['instanceid']));
+    }
+
+    /**
+     * Legacy event name.
+     *
+     * @return string legacy event name
+     */
+    public static function get_legacy_eventname() {
+        return 'mod_updated';
+    }
+
+    /**
+     * Legacy event data.
+     *
+     * @return \stdClass
+     */
+    protected function get_legacy_eventdata() {
+        $eventdata = new \stdClass();
+        $eventdata->modulename = $this->other['modulename'];
+        $eventdata->name       = $this->other['name'];
+        $eventdata->cmid       = $this->objectid;
+        $eventdata->courseid   = $this->courseid;
+        $eventdata->userid     = $this->userid;
+        return $eventdata;
+    }
+
+    /**
+     * replace add_to_log() statement.
+     *
+     * @return array of parameters to be passed to legacy add_to_log() function.
+     */
+    protected function get_legacy_logdata() {
+        return array ($this->courseid, "course", "update mod", "../mod/" . $this->other['modulename'] . "/view.php?id=" .
+                $this->other['instanceid'], $this->other['modulename'] . " " . $this->other['instanceid']);
+    }
+
+    /**
+     * custom validations
+     *
+     * Throw \coding_exception notice in case of any problems.
+     */
+    protected function validate_data() {
+        if (!isset($this->other['modulename'])) {
+            throw new \coding_exception("Field other['modulename'] cannot be empty");
+        }
+        if (!isset($this->other['instanceid'])) {
+            throw new \coding_exception("Field other['instanceid'] cannot be empty");
+        }
+        if (!isset($this->other['name'])) {
+            throw new \coding_exception("Field other['name'] cannot be empty");
+        }
+    }
+}
+
diff --git a/lib/classes/user.php b/lib/classes/user.php
new file mode 100644 (file)
index 0000000..4032884
--- /dev/null
@@ -0,0 +1,211 @@
+<?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/>.
+
+/**
+ * User class
+ *
+ * @package    core
+ * @copyright  2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * User class to access user details.
+ *
+ * @todo       move api's from user/lib.php and depreciate old ones.
+ * @package    core
+ * @copyright  2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_user {
+    /**
+     * No reply user id.
+     */
+    const NOREPLY_USER = -10;
+
+    /**
+     * Suppport user id.
+     */
+    const SUPPORT_USER = -20;
+
+    /** @var stdClass keep record of noreply user */
+    public static $noreplyuser = false;
+
+    /** @var stdClass keep record of support user */
+    public static $supportuser = false;
+
+    /**
+     * Return user object from db or create noreply or support user,
+     * if userid matches corse_user::NOREPLY_USER or corse_user::SUPPORT_USER
+     * respectively. If userid is not found, then return false.
+     *
+     * @param int $userid user id
+     * @param string $fields A comma separated list of user fields to be returned, support and noreply user
+     *                       will not be filtered by this.
+     * @param int $strictness IGNORE_MISSING means compatible mode, false returned if user not found, debug message if more found;
+     *                        IGNORE_MULTIPLE means return first user, ignore multiple user records found(not recommended);
+     *                        MUST_EXIST means throw an exception if no user record or multiple records found.
+     * @return stdClass|bool user record if found, else false.
+     * @throws dml_exception if user record not found and respective $strictness is set.
+     */
+    public static function get_user($userid, $fields = '*', $strictness = IGNORE_MISSING) {
+        global $DB;
+
+        // If noreply user then create fake record and return.
+        switch ($userid) {
+            case self::NOREPLY_USER:
+                return self::get_noreply_user($strictness);
+                break;
+            case self::SUPPORT_USER:
+                return self::get_support_user($strictness);
+                break;
+            default:
+                return $DB->get_record('user', array('id' => $userid), $fields, $strictness);
+        }
+    }
+
+    /**
+     * Helper function to return dummy noreply user record.
+     *
+     * @return stdClass
+     */
+    protected static function get_dummy_user_record() {
+        global $CFG;
+
+        $dummyuser = new stdClass();
+        $dummyuser->id = self::NOREPLY_USER;
+        $dummyuser->email = $CFG->noreplyaddress;
+        $dummyuser->firstname = get_string('noreplyname');
+        $dummyuser->username = 'noreply';
+        $dummyuser->lastname = '';
+        $dummyuser->confirmed = 1;
+        $dummyuser->suspended = 0;
+        $dummyuser->deleted = 0;
+        $dummyuser->picture = 0;
+        $dummyuser->auth = 'manual';
+        $dummyuser->firstnamephonetic = '';
+        $dummyuser->lastnamephonetic = '';
+        $dummyuser->middlename = '';
+        $dummyuser->alternatename = '';
+        $dummyuser->imagealt = '';
+        return $dummyuser;
+    }
+
+    /**
+     * Return noreply user record, this is currently used in messaging
+     * system only for sending messages from noreply email.
+     * It will return record of $CFG->noreplyuserid if set else return dummy
+     * user object with hard-coded $user->emailstop = 1 so noreply can be sent to user.
+     *
+     * @return stdClass user record.
+     */
+    public static function get_noreply_user() {
+        global $CFG;
+
+        if (!empty(self::$noreplyuser)) {
+            return self::$noreplyuser;
+        }
+
+        // If noreply user is set then use it, else create one.
+        if (!empty($CFG->noreplyuserid)) {
+            self::$noreplyuser = self::get_user($CFG->noreplyuserid);
+        }
+
+        if (empty(self::$noreplyuser)) {
+            self::$noreplyuser = self::get_dummy_user_record();
+        }
+        self::$noreplyuser->emailstop = 1; // Force msg stop for this user.
+        return self::$noreplyuser;
+    }
+
+    /**
+     * Return support user record, this is currently used in messaging
+     * system only for sending messages to support email.
+     * $CFG->supportuserid is set then returns user record
+     * $CFG->supportemail is set then return dummy record with $CFG->supportemail
+     * else return admin user record with hard-coded $user->emailstop = 0, so user
+     * gets support message.
+     *
+     * @return stdClass user record.
+     */
+    public static function get_support_user() {
+        global $CFG;
+
+        if (!empty(self::$supportuser)) {
+            return self::$supportuser;
+        }
+
+        // If custom support user is set then use it, else if supportemail is set then use it, else use noreply.
+        if (!empty($CFG->supportuserid)) {
+            self::$supportuser = self::get_user($CFG->supportuserid, '*', MUST_EXIST);
+        }
+
+        // Try sending it to support email if support user is not set.
+        if (empty(self::$supportuser) && !empty($CFG->supportemail)) {
+            self::$supportuser = self::get_dummy_user_record();
+            self::$supportuser->id = self::SUPPORT_USER;
+            self::$supportuser->email = $CFG->supportemail;
+            self::$supportuser->firstname = $CFG->supportname ? $CFG->supportname : $supportuser->firstname;
+            self::$supportuser->username = 'support';
+            self::$supportuser->maildisplay = true;
+        }
+
+        // Send support msg to admin user if nothing is set above.
+        if (empty(self::$supportuser)) {
+            self::$supportuser = get_admin();
+        }
+
+        // Unset emailstop to make sure support message is sent.
+        self::$supportuser->emailstop = 0;
+        return self::$supportuser;
+    }
+
+    /**
+     * Reset self::$noreplyuser and self::$supportuser.
+     * This is only used by phpunit, and there is no other use case for this function.
+     * Please don't use it outside phpunit.
+     */
+    public static function reset_internal_users() {
+        if (PHPUNIT_TEST) {
+            self::$noreplyuser = false;
+            self::$supportuser = false;
+        } else {
+            debugging('reset_internal_users() should not be used outside phpunit.', DEBUG_DEVELOPER);
+        }
+    }
+
+    /**
+     * Return true is user id is greater than self::NOREPLY_USER and
+     * alternetely check db.
+     *
+     * @param int $userid user id.
+     * @param bool $checkdb if true userid will be checked in db. By default it's false, and
+     *                      userid is compared with NOREPLY_USER for performance.
+     * @return bool true is real user else false.
+     */
+    public static function is_real_user($userid, $checkdb = false) {
+        if ($userid < 0) {
+            return false;
+        }
+        if ($checkdb) {
+            return $DB->record_exists('user', array('id' => $userid));
+        } else {
+            return true;
+        }
+    }
+}
index e8c1906..971d138 100644 (file)
@@ -367,6 +367,15 @@ class core_useragent {
         return false;
     }
 
+    /**
+     * Checks the user agent is Firefox (of any version).
+     *
+     * @return bool true if firefox
+     */
+    public static function is_firefox() {
+        return self::check_firefox_version();
+    }
+
     /**
      * Checks the user agent is Firefox based and that the version is equal to or greater than that specified.
      *
@@ -393,6 +402,15 @@ class core_useragent {
         return false;
     }
 
+    /**
+     * Checks the user agent is Gecko based (of any version).
+     *
+     * @return bool true if Gecko based.
+     */
+    public static function is_gecko() {
+        return self::check_gecko_version();
+    }
+
     /**
      * Checks the user agent is Gecko based and that the version is equal to or greater than that specified.
      *
@@ -447,6 +465,15 @@ class core_useragent {
         return false;
     }
 
+    /**
+     * Checks the user agent is IE (of any version).
+     *
+     * @return bool true if internet exporeer
+     */
+    public static function is_ie() {
+        return self::check_ie_version();
+    }
+
     /**
      * Checks the user agent is IE and that the version is equal to or greater than that specified.
      *
@@ -484,6 +511,15 @@ class core_useragent {
         return ($browser >= $version);
     }
 
+    /**
+     * Checks the user agent is Opera (of any version).
+     *
+     * @return bool true if opera
+     */
+    public static function is_opera() {
+        return self::check_opera_version();
+    }
+
     /**
      * Checks the user agent is Opera and that the version is equal to or greater than that specified.
      *
@@ -517,6 +553,15 @@ class core_useragent {
         return false;
     }
 
+    /**
+     * Checks the user agent is webkit based
+     *
+     * @return bool true if webkit
+     */
+    public static function is_webkit() {
+        return self::check_webkit_version();
+    }
+
     /**
      * Checks the user agent is Webkit based and that the version is equal to or greater than that specified.
      *
@@ -543,6 +588,15 @@ class core_useragent {
         return false;
     }
 
+    /**
+     * Checks the user agent is Safari
+     *
+     * @return bool true if safari
+     */
+    public static function is_safari() {
+        return self::check_safari_version();
+    }
+
     /**
      * Checks the user agent is Safari based and that the version is equal to or greater than that specified.
      *
@@ -594,6 +648,15 @@ class core_useragent {
         return false;
     }
 
+    /**
+     * Checks the user agent is Chrome
+     *
+     * @return bool true if chrome
+     */
+    public static function is_chrome() {
+        return self::check_chrome_version();
+    }
+
     /**
      * Checks the user agent is Chrome based and that the version is equal to or greater than that specified.
      *
@@ -620,6 +683,15 @@ class core_useragent {
         return false;
     }
 
+    /**
+     * Checks the user agent is webkit android based.
+     *
+     * @return bool true if webkit based and on Android
+     */
+    public static function is_webkit_android() {
+        return self::check_webkit_android_version();
+    }
+
     /**
      * Checks the user agent is Webkit based and on Android and that the version is equal to or greater than that specified.
      *
@@ -646,6 +718,15 @@ class core_useragent {
         return false;
     }
 
+    /**
+     * Checks the user agent is Safari on iOS
+     *
+     * @return bool true if Safari on iOS
+     */
+    public static function is_safari_ios() {
+        return self::check_safari_ios_version();
+    }
+
     /**
      * Checks the user agent is Safari on iOS and that the version is equal to or greater than that specified.
      *
@@ -694,7 +775,7 @@ class core_useragent {
      */
     public static function get_browser_version_classes() {
         $classes = array();
-        if (self::check_ie_version('0')) {
+        if (self::is_ie()) {
             $classes[] = 'ie';
             for ($i = 12; $i >= 6; $i--) {
                 if (self::check_ie_version($i)) {
@@ -702,19 +783,19 @@ class core_useragent {
                     break;
                 }
             }
-        } else if (self::check_firefox_version() || self::check_gecko_version() || self::check_camino_version()) {
+        } else if (self::is_firefox() || self::is_gecko() || self::check_camino_version()) {
             $classes[] = 'gecko';
             if (preg_match('/rv\:([1-2])\.([0-9])/', self::get_user_agent_string(), $matches)) {
                 $classes[] = "gecko{$matches[1]}{$matches[2]}";
             }
-        } else if (self::check_webkit_version()) {
+        } else if (self::is_webkit()) {
             $classes[] = 'safari';
-            if (self::check_safari_ios_version()) {
+            if (self::is_safari_ios()) {
                 $classes[] = 'ios';
-            } else if (self::check_webkit_android_version()) {
+            } else if (self::is_webkit_android()) {
                 $classes[] = 'android';
             }
-        } else if (self::check_opera_version()) {
+        } else if (self::is_opera()) {
             $classes[] = 'opera';
         }
         return $classes;
@@ -732,13 +813,13 @@ class core_useragent {
             if ($instance->useragent === false) {
                 // Can't be sure, just say no.
                 $instance->supportssvg = false;
-            } else if (self::check_ie_version() and !self::check_ie_version('9')) {
+            } else if (self::is_ie() and !self::check_ie_version('9')) {
                 // IE < 9 doesn't support SVG. Say no.
                 $instance->supportssvg = false;
             } else if (preg_match('#Android +[0-2]\.#', $instance->useragent)) {
                 // Android < 3 doesn't support SVG. Say no.
                 $instance->supportssvg = false;
-            } else if (self::check_opera_version()) {
+            } else if (self::is_opera()) {
                 // Opera 12 still does not support SVG well enough. Say no.
                 $instance->supportssvg = false;
             } else {
index 117f155..b15105a 100644 (file)
@@ -758,7 +758,7 @@ function notify_login_failures() {
         mtrace('Emailing admins about '. $count .' failed login attempts');
         foreach ($recip as $admin) {
             //emailing the admins directly rather than putting these through the messaging system
-            email_to_user($admin, generate_email_supportuser(), $subject, $body);
+            email_to_user($admin, core_user::get_support_user(), $subject, $body);
         }
     }
 
index 8095fae..d79981f 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20130905" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20130913" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
         <FIELD NAME="msn" TYPE="char" LENGTH="50" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="phone1" TYPE="char" LENGTH="20" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="phone2" TYPE="char" LENGTH="20" NOTNULL="true" SEQUENCE="false"/>
-        <FIELD NAME="institution" TYPE="char" LENGTH="40" NOTNULL="true" SEQUENCE="false"/>
-        <FIELD NAME="department" TYPE="char" LENGTH="30" NOTNULL="true" SEQUENCE="false"/>
-        <FIELD NAME="address" TYPE="char" LENGTH="70" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="institution" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="department" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="address" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="city" TYPE="char" LENGTH="120" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="country" TYPE="char" LENGTH="2" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="lang" TYPE="char" LENGTH="30" NOTNULL="true" DEFAULT="en" SEQUENCE="false"/>
       </KEYS>
     </TABLE>
   </TABLES>
-</XMLDB>
+</XMLDB>
\ No newline at end of file
index 1fad3d4..543d1b3 100644 (file)
@@ -2424,5 +2424,31 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2013091000.03);
     }
 
+    if ($oldversion < 2013091300.01) {
+
+        $table = new xmldb_table('user');
+
+        // Changing precision of field institution on table user to (255).
+        $field = new xmldb_field('institution', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, 'phone2');
+
+        // Launch change of precision for field institution.
+        $dbman->change_field_precision($table, $field);
+
+        // Changing precision of field department on table user to (255).
+        $field = new xmldb_field('department', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, 'institution');
+
+        // Launch change of precision for field department.
+        $dbman->change_field_precision($table, $field);
+
+        // Changing precision of field address on table user to (255).
+        $field = new xmldb_field('address', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, 'department');
+
+        // Launch change of precision for field address.
+        $dbman->change_field_precision($table, $field);
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2013091300.01);
+    }
+
     return true;
 }
index c5b6501..3cc26ed 100644 (file)
@@ -4532,4 +4532,16 @@ function get_user_device_type() {
 function get_browser_version_classes() {
     debugging('get_browser_version_classes has been deprecated, please update your code to use core_useragent instead.', DEBUG_DEVELOPER);
     return core_useragent::get_browser_version_classes();
+}
+
+/**
+ * Generate a fake user for emails based on support settings
+ *
+ * @deprecated since Moodle 2.6
+ * @see core_user::get_support_user()
+ * @return stdClass user info
+ */
+function generate_email_supportuser() {
+    debugging('generate_email_supportuser is deprecated, please use core_user::get_support_user');
+    return core_user::get_support_user();
 }
\ No newline at end of file
index 195710d..f0e6a19 100644 (file)
@@ -34,29 +34,8 @@ class tinymce_texteditor extends texteditor {
      * @return bool
      */
     public function supported_by_browser() {
-        if (core_useragent::check_ie_version(6)) {
-            return true;
-        }
-        if (core_useragent::check_gecko_version(20030516)) {
-            return true;
-        }
-        if (core_useragent::check_safari_version(412)) {
-            return true;
-        }
-        if (core_useragent::check_chrome_version(6)) {
-            return true;
-        }
-        if (core_useragent::check_opera_version(9)) {
-            return true;
-        }
-        if (core_useragent::check_safari_ios_version(534)) {
-            return true;
-        }
-        if (core_useragent::check_webkit_version(534)) {
-            return true;
-        }
-
-        return false;
+        // We don't support any browsers which it doesn't support.
+        return true;
     }
 
     /**
index a3c8540..330bbe4 100644 (file)
@@ -63,7 +63,7 @@ class tinymce_spellchecker extends editor_tinymce_plugin {
 
     protected function is_legacy_browser() {
         // IE8 and IE9 are the only supported browsers that do not have spellchecker.
-        if (core_useragent::check_ie_version() and !core_useragent::check_ie_version(10)) {
+        if (core_useragent::is_ie() and !core_useragent::check_ie_version(10)) {
             return true;
         }
         // The rest of browsers supports spellchecking or is horribly outdated and we do not care...
index 77341cc..ea3cbad 100644 (file)
@@ -120,7 +120,7 @@ class MoodleExcelWorkbook {
             header('Pragma: no-cache');
         }
 
-        if (core_useragent::check_ie_version()) {
+        if (core_useragent::is_ie()) {
             $filename = rawurlencode($filename);
         } else {
             $filename = s($filename);
index 2260e4a..cf82585 100644 (file)
@@ -1959,6 +1959,43 @@ function send_header_404() {
     }
 }
 
+/**
+ * The readfile function can fail when files are larger than 2GB (even on 64-bit
+ * platforms). This wrapper uses readfile for small files and custom code for
+ * large ones.
+ *
+ * @param string $path Path to file
+ * @param int $filesize Size of file (if left out, will get it automatically)
+ * @return int|bool Size read (will always be $filesize) or false if failed
+ */
+function readfile_allow_large($path, $filesize = -1) {
+    // Automatically get size if not specified.
+    if ($filesize === -1) {
+        $filesize = filesize($path);
+    }
+    if ($filesize <= 2147483647) {
+        // If the file is up to 2^31 - 1, send it normally using readfile.
+        return readfile($path);
+    } else {
+        // For large files, read and output in 64KB chunks.
+        $handle = fopen($path, 'r');
+        if ($handle === false) {
+            return false;
+        }
+        $left = $filesize;
+        while ($left > 0) {
+            $size = min($left, 65536);
+            $buffer = fread($handle, $size);
+            if ($buffer === false) {
+                return false;
+            }
+            echo $buffer;
+            $left -= $size;
+        }
+        return $filesize;
+    }
+}
+
 /**
  * Enhanced readfile() with optional acceleration.
  * @param string|stored_file $file
@@ -2081,7 +2118,7 @@ function readfile_accel($file, $mimetype, $accelerate) {
     if (is_object($file)) {
         $file->readfile();
     } else {
-        readfile($file);
+        readfile_allow_large($file, $filesize);
     }
 }
 
@@ -2126,7 +2163,7 @@ function readstring_accel($string, $mimetype, $accelerate) {
 function send_temp_file($path, $filename, $pathisstring=false) {
     global $CFG;
 
-    if (core_useragent::check_firefox_version('1.5')) {
+    if (core_useragent::is_firefox()) {
         // only FF is known to correctly save to disk before opening...
         $mimetype = mimeinfo('type', $filename);
     } else {
@@ -2146,7 +2183,7 @@ function send_temp_file($path, $filename, $pathisstring=false) {
     }
 
     // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
-    if (core_useragent::check_ie_version()) {
+    if (core_useragent::is_ie()) {
         $filename = urlencode($filename);
     }
 
@@ -2222,12 +2259,12 @@ function send_file($path, $filename, $lifetime = 'default' , $filter=0, $pathiss
     // Use given MIME type if specified, otherwise guess it using mimeinfo.
     // IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O
     // only Firefox saves all files locally before opening when content-disposition: attachment stated
-    $isFF         = core_useragent::check_firefox_version('1.5'); // only FF > 1.5 properly tested
+    $isFF         = core_useragent::is_firefox(); // only FF properly tested
     $mimetype     = ($forcedownload and !$isFF) ? 'application/x-forcedownload' :
                          ($mimetype ? $mimetype : mimeinfo('type', $filename));
 
     // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
-    if (core_useragent::check_ie_version()) {
+    if (core_useragent::is_ie()) {
         $filename = rawurlencode($filename);
     }
 
@@ -2386,12 +2423,12 @@ function send_stored_file($stored_file, $lifetime=86400 , $filter=0, $forcedownl
     // IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O
     // only Firefox saves all files locally before opening when content-disposition: attachment stated
     $filename     = is_null($filename) ? $stored_file->get_filename() : $filename;
-    $isFF         = core_useragent::check_firefox_version('1.5'); // only FF > 1.5 properly tested
+    $isFF         = core_useragent::is_firefox(); // only FF properly tested
     $mimetype     = ($forcedownload and !$isFF) ? 'application/x-forcedownload' :
                          ($stored_file->get_mimetype() ? $stored_file->get_mimetype() : mimeinfo('type', $filename));
 
     // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
-    if (core_useragent::check_ie_version()) {
+    if (core_useragent::is_ie()) {
         $filename = rawurlencode($filename);
     }
 
index d56aa52..f1e6c24 100644 (file)
@@ -409,7 +409,7 @@ class stored_file {
                 throw new file_exception('storedfilecannotread', '', $path);
             }
         }
-        readfile($path);
+        readfile_allow_large($path, $this->get_filesize());
     }
 
     /**
index 2de7fc6..e001185 100644 (file)
@@ -983,7 +983,7 @@ class core_media_player_html5video extends core_media_player {
     public function embed($urls, $name, $width, $height, $options) {
         // Special handling to make videos play on Android devices pre 2.3.
         // Note: I tested and 2.3.3 (in emulator) works without, is 533.1 webkit.
-        $oldandroid = core_useragent::check_webkit_android_version() &&
+        $oldandroid = core_useragent::is_webkit_android() &&
                 !core_useragent::check_webkit_android_version('533.1');
 
         // Build array of source tags.
@@ -1067,12 +1067,12 @@ OET;
                 // versions or manual plugins.
                 if ($ext === 'ogv' || $ext === 'webm') {
                     // Formats .ogv and .webm are not supported in IE or Safari.
-                    if (core_useragent::check_ie_version() || core_useragent::check_safari_version()) {
+                    if (core_useragent::is_ie() || core_useragent::is_safari()) {
                         continue;
                     }
                 } else {
                     // Formats .m4v and .mp4 are not supported in Firefox or Opera.
-                    if (core_useragent::check_firefox_version() || core_useragent::check_opera_version()) {
+                    if (core_useragent::is_firefox() || core_useragent::is_opera()) {
                         continue;
                     }
                 }
@@ -1136,18 +1136,18 @@ OET;
             if (in_array($ext, $extensions)) {
                 if ($ext === 'ogg' || $ext === 'oga') {
                     // Formats .ogg and .oga are not supported in IE or Safari.
-                    if (core_useragent::check_ie_version() || core_useragent::check_safari_version()) {
+                    if (core_useragent::is_ie() || core_useragent::is_safari()) {
                         continue;
                     }
                 } else {
                     // Formats .aac, .mp3, and .m4a are not supported in Firefox or Opera.
-                    if (core_useragent::check_firefox_version() || core_useragent::check_opera_version()) {
+                    if (core_useragent::is_firefox() || core_useragent::is_opera()) {
                         continue;
                     }
                 }
                 // Old Android versions (pre 2.3.3) 'support' audio tag but no codecs.
-                if (core_useragent::check_webkit_android_version() &&
-                        !core_useragent::check_webkit_android_version('533.1')) {
+                if (core_useragent::is_webkit_android() &&
+                        !core_useragent::is_webkit_android('533.1')) {
                     continue;
                 }
 
index d4cd8d0..426c593 100644 (file)
@@ -61,13 +61,21 @@ function message_send($eventdata) {
     $DB->transactions_forbidden();
 
     if (is_number($eventdata->userto)) {
-        $eventdata->userto = $DB->get_record('user', array('id' => $eventdata->userto));
+        $eventdata->userto = core_user::get_user($eventdata->userto);
     }
     if (is_int($eventdata->userfrom)) {
-        $eventdata->userfrom = $DB->get_record('user', array('id' => $eventdata->userfrom));
+        $eventdata->userfrom = core_user::get_user($eventdata->userfrom);
     }
+
+    $usertoisrealuser = (core_user::is_real_user($eventdata->userto->id) != false);
+    // If recipient is internal user (noreply user), and emailstop is set then don't send any msg.
+    if (!$usertoisrealuser && !empty($eventdata->userto->emailstop)) {
+        debugging('Attempt to send msg to internal (noreply) user', DEBUG_NORMAL);
+        return false;
+    }
+
     if (!isset($eventdata->userto->auth) or !isset($eventdata->userto->suspended) or !isset($eventdata->userto->deleted)) {
-        $eventdata->userto = $DB->get_record('user', array('id' => $eventdata->userto->id));
+        $eventdata->userto = core_user::get_user($eventdata->userto->id);
     }
 
     //after how long inactive should the user be considered logged off?
@@ -150,6 +158,11 @@ function message_send($eventdata) {
     $preferencebase = $eventdata->component.'_'.$eventdata->name;
     // Fill in the array of processors to be used based on default and user preferences
     foreach ($processors as $processor) {
+        // Skip adding processors for internal user, if processor doesn't support sending message to internal user.
+        if (!$usertoisrealuser && !$processor->object->can_send_to_any_users()) {
+            continue;
+        }
+
         // First find out permissions
         $defaultpreference = $processor->name.'_provider_'.$preferencebase.'_permitted';
         if (isset($defaultpreferences->{$defaultpreference})) {
index d599f05..92091ea 100644 (file)
@@ -5653,7 +5653,7 @@ function email_to_user($user, $from, $subject, $messagetext, $messagehtml = '',
     $temprecipients = array();
     $tempreplyto = array();
 
-    $supportuser = generate_email_supportuser();
+    $supportuser = core_user::get_support_user();
 
     // Make up an email address for handling bounces.
     if (!empty($CFG->handlebounces)) {
@@ -5802,30 +5802,6 @@ function generate_email_signoff() {
     return $signoff;
 }
 
-/**
- * Generate a fake user for emails based on support settings
- *
- * @return stdClass user info
- */
-function generate_email_supportuser() {
-    global $CFG;
-
-    static $supportuser;
-
-    if (!empty($supportuser)) {
-        return $supportuser;
-    }
-
-    $supportuser = new stdClass();
-    $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $CFG->noreplyaddress;
-    $supportuser->firstname = $CFG->supportname ? $CFG->supportname : get_string('noreplyname');
-    $supportuser->lastname = '';
-    $supportuser->maildisplay = true;
-
-    return $supportuser;
-}
-
-
 /**
  * Sets specified user's password and send the new password to the user via email.
  *
@@ -5843,7 +5819,7 @@ function setnew_password_and_mail($user, $fasthash = false) {
 
     $site  = get_site();
 
-    $supportuser = generate_email_supportuser();
+    $supportuser = core_user::get_support_user();
 
     $newpassword = generate_password();
 
@@ -5877,7 +5853,7 @@ function reset_password_and_mail($user) {
     global $CFG;
 
     $site  = get_site();
-    $supportuser = generate_email_supportuser();
+    $supportuser = core_user::get_support_user();
 
     $userauth = get_auth_plugin($user->auth);
     if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
@@ -5920,7 +5896,7 @@ function send_confirmation_email($user) {
     global $CFG;
 
     $site = get_site();
-    $supportuser = generate_email_supportuser();
+    $supportuser = core_user::get_support_user();
 
     $data = new stdClass();
     $data->firstname = fullname($user);
@@ -5951,7 +5927,7 @@ function send_password_change_confirmation_email($user) {
     global $CFG;
 
     $site = get_site();
-    $supportuser = generate_email_supportuser();
+    $supportuser = core_user::get_support_user();
 
     $data = new stdClass();
     $data->firstname = $user->firstname;
@@ -5978,7 +5954,7 @@ function send_password_change_info($user) {
     global $CFG;
 
     $site = get_site();
-    $supportuser = generate_email_supportuser();
+    $supportuser = core_user::get_support_user();
     $systemcontext = context_system::instance();
 
     $data = new stdClass();
index e1051dd..231e0a2 100644 (file)
@@ -656,7 +656,7 @@ class theme_config {
 
         if ($rev > -1) {
             $url = new moodle_url("$CFG->httpswwwroot/theme/styles.php");
-            $separate = (core_useragent::check_ie_version('5') && !core_useragent::check_ie_version('10'));
+            $separate = (core_useragent::is_ie() && !core_useragent::check_ie_version('10'));
             if (!empty($CFG->slasharguments)) {
                 $slashargs = '';
                 if (!$svg) {
@@ -734,7 +734,7 @@ class theme_config {
                 // We do this because all modern browsers support SVG and this param will one day be removed.
                 $baseurl->param('svg', '0');
             }
-            if (core_useragent::check_ie_version('5')) {
+            if (core_useragent::is_ie()) {
                 // lalala, IE does not allow more than 31 linked CSS files from main document
                 $urls[] = new moodle_url($baseurl, array('theme'=>$this->name, 'type'=>'ie', 'subtype'=>'plugins'));
                 foreach ($css['parents'] as $parent=>$sheets) {
diff --git a/lib/tests/user_test.php b/lib/tests/user_test.php
new file mode 100644 (file)
index 0000000..ea379e8
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Tests core_user class.
+ *
+ * @package    core
+ * @copyright  2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Test core_user class.
+ *
+ * @package    core
+ * @copyright  2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_user_testcase extends advanced_testcase {
+
+    public function test_get_user() {
+        global $CFG;
+
+        $this->resetAfterTest(true);
+
+        // Create user and try fetach it with api.
+        $user = $this->getDataGenerator()->create_user();
+        $this->assertEquals($user, core_user::get_user($user->id, '*', MUST_EXIST));
+
+        // Test noreply user.
+        $CFG->noreplyuserid = null;
+        $noreplyuser = core_user::get_noreply_user();
+        $this->assertEquals(1, $noreplyuser->emailstop);
+        $this->assertFalse(core_user::is_real_user($noreplyuser->id));
+        $this->assertEquals($CFG->noreplyaddress, $noreplyuser->email);
+        $this->assertEquals(get_string('noreplyname'), $noreplyuser->firstname);
+
+        // Set user as noreply user and make sure noreply propery is set.
+        core_user::reset_internal_users();
+        $CFG->noreplyuserid = $user->id;
+        $noreplyuser = core_user::get_noreply_user();
+        $this->assertEquals(1, $noreplyuser->emailstop);
+        $this->assertTrue(core_user::is_real_user($noreplyuser->id));
+
+        // Test support user.
+        core_user::reset_internal_users();
+        $CFG->supportemail = null;
+        $CFG->noreplyuserid = null;
+        $supportuser = core_user::get_support_user();
+        $adminuser = get_admin();
+        $this->assertEquals($adminuser, $supportuser);
+        $this->assertTrue(core_user::is_real_user($supportuser->id));
+
+        // When supportemail is set.
+        core_user::reset_internal_users();
+        $CFG->supportemail = 'test@support.moodle.test';
+        $supportuser = core_user::get_support_user();
+        $this->assertEquals(core_user::SUPPORT_USER, $supportuser->id);
+        $this->assertFalse(core_user::is_real_user($supportuser->id));
+
+        // Set user as support user and make sure noreply propery is set.
+        core_user::reset_internal_users();
+        $CFG->supportuserid = $user->id;
+        $supportuser = core_user::get_support_user();
+        $this->assertEquals($user, $supportuser);
+        $this->assertTrue(core_user::is_real_user($supportuser->id));
+    }
+}
index 21e0ef7..7d74dd9 100644 (file)
@@ -163,53 +163,69 @@ class core_useragent_testcase extends basic_testcase {
      */
     public function test_check_browser_version() {
         core_useragent::instance(true, $this->user_agents['Safari']['412']['Mac OS X']);
+        $this->assertTrue(core_useragent::is_safari());
         $this->assertTrue(core_useragent::check_safari_version());
+        $this->assertTrue(core_useragent::is_webkit());
         $this->assertTrue(core_useragent::check_webkit_version());
         $this->assertTrue(core_useragent::check_safari_version('312'));
         $this->assertFalse(core_useragent::check_safari_version('500'));
+        $this->assertFalse(core_useragent::is_chrome());
         $this->assertFalse(core_useragent::check_chrome_version());
+        $this->assertFalse(core_useragent::is_safari_ios());
         $this->assertFalse(core_useragent::check_safari_ios_version());
 
         core_useragent::instance(true, $this->user_agents['Safari iOS']['528']['iPhone']);
+        $this->assertTrue(core_useragent::is_safari_ios());
         $this->assertTrue(core_useragent::check_safari_ios_version());
+        $this->assertTrue(core_useragent::is_webkit());
         $this->assertTrue(core_useragent::check_webkit_version());
         $this->assertTrue(core_useragent::check_safari_ios_version('527'));
         $this->assertFalse(core_useragent::check_safari_ios_version(590));
         $this->assertFalse(core_useragent::check_safari_version('312'));
         $this->assertFalse(core_useragent::check_safari_version('500'));
+        $this->assertFalse(core_useragent::is_chrome());
         $this->assertFalse(core_useragent::check_chrome_version());
 
         core_useragent::instance(true, $this->user_agents['WebKit Android']['530']['Nexus']);
+        $this->assertTrue(core_useragent::is_webkit());
         $this->assertTrue(core_useragent::check_webkit_version());
         $this->assertTrue(core_useragent::check_webkit_android_version('527'));
         $this->assertFalse(core_useragent::check_webkit_android_version(590));
+        $this->assertFalse(core_useragent::is_safari());
         $this->assertFalse(core_useragent::check_safari_version());
+        $this->assertFalse(core_useragent::is_chrome());
         $this->assertFalse(core_useragent::check_chrome_version());
 
         core_useragent::instance(true, $this->user_agents['Chrome']['8']['Mac OS X']);
+        $this->assertTrue(core_useragent::is_chrome());
         $this->assertTrue(core_useragent::check_chrome_version());
+        $this->assertTrue(core_useragent::is_webkit());
         $this->assertTrue(core_useragent::check_webkit_version());
         $this->assertTrue(core_useragent::check_chrome_version(8));
         $this->assertFalse(core_useragent::check_chrome_version(10));
         $this->assertFalse(core_useragent::check_safari_version('1'));
 
         core_useragent::instance(true, $this->user_agents['Opera']['9.0']['Windows XP']);
+        $this->assertTrue(core_useragent::is_opera());
         $this->assertTrue(core_useragent::check_opera_version());
         $this->assertTrue(core_useragent::check_opera_version('8.0'));
         $this->assertFalse(core_useragent::check_opera_version('10.0'));
 
         core_useragent::instance(true, $this->user_agents['MSIE']['6.0']['Windows XP SP2']);
+        $this->assertTrue(core_useragent::is_ie());
         $this->assertTrue(core_useragent::check_ie_version());
         $this->assertTrue(core_useragent::check_ie_version('5.0'));
         $this->assertFalse(core_useragent::check_ie_version('7.0'));
 
         core_useragent::instance(true, $this->user_agents['MSIE']['5.0']['Windows 98']);
+        $this->assertFalse(core_useragent::is_ie());
         $this->assertFalse(core_useragent::check_ie_version());
         $this->assertTrue(core_useragent::check_ie_version(0));
         $this->assertTrue(core_useragent::check_ie_version('5.0'));
         $this->assertFalse(core_useragent::check_ie_version('7.0'));
 
         core_useragent::instance(true, $this->user_agents['MSIE']['9.0']['Windows 7']);
+        $this->assertTrue(core_useragent::is_ie());
         $this->assertTrue(core_useragent::check_ie_version());
         $this->assertTrue(core_useragent::check_ie_version(0));
         $this->assertTrue(core_useragent::check_ie_version('5.0'));
@@ -217,6 +233,7 @@ class core_useragent_testcase extends basic_testcase {
         $this->assertFalse(core_useragent::check_ie_version('10'));
 
         core_useragent::instance(true, $this->user_agents['MSIE']['9.0i']['Windows 7']);
+        $this->assertTrue(core_useragent::is_ie());
         $this->assertTrue(core_useragent::check_ie_version());
         $this->assertTrue(core_useragent::check_ie_version(0));
         $this->assertTrue(core_useragent::check_ie_version('5.0'));
@@ -224,6 +241,7 @@ class core_useragent_testcase extends basic_testcase {
         $this->assertFalse(core_useragent::check_ie_version('10'));
 
         core_useragent::instance(true, $this->user_agents['MSIE']['10.0']['Windows 8']);
+        $this->assertTrue(core_useragent::is_ie());
         $this->assertTrue(core_useragent::check_ie_version());
         $this->assertTrue(core_useragent::check_ie_version(0));
         $this->assertTrue(core_useragent::check_ie_version('5.0'));
@@ -232,6 +250,7 @@ class core_useragent_testcase extends basic_testcase {
         $this->assertFalse(core_useragent::check_ie_version('11'));
 
         core_useragent::instance(true, $this->user_agents['MSIE']['10.0i']['Windows 8']);
+        $this->assertTrue(core_useragent::is_ie());
         $this->assertTrue(core_useragent::check_ie_version());
         $this->assertTrue(core_useragent::check_ie_version(0));
         $this->assertTrue(core_useragent::check_ie_version('5.0'));
@@ -240,6 +259,7 @@ class core_useragent_testcase extends basic_testcase {
         $this->assertFalse(core_useragent::check_ie_version('11'));
 
         core_useragent::instance(true, $this->user_agents['Firefox']['2.0']['Windows XP']);
+        $this->assertTrue(core_useragent::is_firefox());
         $this->assertTrue(core_useragent::check_firefox_version());
         $this->assertTrue(core_useragent::check_firefox_version('1.5'));
         $this->assertFalse(core_useragent::check_firefox_version('3.0'));
@@ -249,6 +269,7 @@ class core_useragent_testcase extends basic_testcase {
         $this->assertTrue(core_useragent::check_gecko_version(2006010100));
 
         core_useragent::instance(true, $this->user_agents['Firefox']['1.0.6']['Windows XP']);
+        $this->assertTrue(core_useragent::is_firefox());
         $this->assertTrue(core_useragent::check_firefox_version());
         $this->assertTrue(core_useragent::check_gecko_version('1'));
         $this->assertFalse(core_useragent::check_gecko_version(20030516));
@@ -259,6 +280,7 @@ class core_useragent_testcase extends basic_testcase {
         $this->assertFalse(core_useragent::check_gecko_version('2'));
 
         core_useragent::instance(true, $this->user_agents['Firefox']['2.0']['Windows XP']);
+        $this->assertTrue(core_useragent::is_firefox());
         $this->assertTrue(core_useragent::check_firefox_version());
         $this->assertTrue(core_useragent::check_firefox_version('1.5'));
         $this->assertTrue(core_useragent::check_gecko_version('1'));
@@ -269,6 +291,7 @@ class core_useragent_testcase extends basic_testcase {
         $this->assertFalse(core_useragent::check_firefox_version('3.0'));
 
         core_useragent::instance(true, $this->user_agents['Firefox']['3.6']['Linux']);
+        $this->assertTrue(core_useragent::is_firefox());
         $this->assertTrue(core_useragent::check_firefox_version());
         $this->assertTrue(core_useragent::check_firefox_version('1.5'));
         $this->assertTrue(core_useragent::check_firefox_version('3.0'));
@@ -281,6 +304,7 @@ class core_useragent_testcase extends basic_testcase {
         $this->assertFalse(core_useragent::check_firefox_version('10'));
 
         core_useragent::instance(true, $this->user_agents['Firefox']['3.6']['Linux']);
+        $this->assertTrue(core_useragent::is_firefox());
         $this->assertTrue(core_useragent::check_firefox_version());
         $this->assertTrue(core_useragent::check_firefox_version('1.5'));
         $this->assertTrue(core_useragent::check_firefox_version('3.0'));
@@ -295,6 +319,7 @@ class core_useragent_testcase extends basic_testcase {
         $this->assertFalse(core_useragent::check_gecko_version('4'));
 
         core_useragent::instance(true, $this->user_agents['Firefox']['15.0a2']['Windows']);
+        $this->assertTrue(core_useragent::is_firefox());
         $this->assertTrue(core_useragent::check_firefox_version());
         $this->assertTrue(core_useragent::check_firefox_version('1.5'));
         $this->assertTrue(core_useragent::check_firefox_version('3.0'));
@@ -311,6 +336,7 @@ class core_useragent_testcase extends basic_testcase {
         $this->assertFalse(core_useragent::check_gecko_version('18'));
 
         core_useragent::instance(true, $this->user_agents['Firefox']['18.0']['Mac OS X']);
+        $this->assertTrue(core_useragent::is_firefox());
         $this->assertTrue(core_useragent::check_firefox_version());
         $this->assertTrue(core_useragent::check_firefox_version('1.5'));
         $this->assertTrue(core_useragent::check_firefox_version('3.0'));
@@ -335,6 +361,7 @@ class core_useragent_testcase extends basic_testcase {
         $this->assertTrue(core_useragent::check_gecko_version(2006010100));
         $this->assertFalse(core_useragent::check_gecko_version('3.6'));
         $this->assertFalse(core_useragent::check_gecko_version('4.0'));
+        $this->assertFalse(core_useragent::is_firefox());
         $this->assertFalse(core_useragent::check_firefox_version());
 
         core_useragent::instance(true, $this->user_agents['SeaMonkey']['2.1']['Linux']);
@@ -344,6 +371,7 @@ class core_useragent_testcase extends basic_testcase {
         $this->assertTrue(core_useragent::check_gecko_version(20030516));
         $this->assertTrue(core_useragent::check_gecko_version(20051106));
         $this->assertTrue(core_useragent::check_gecko_version(2006010100));
+        $this->assertTrue(core_useragent::is_firefox());
         $this->assertTrue(core_useragent::check_firefox_version());
         $this->assertTrue(core_useragent::check_firefox_version(4.0));
         $this->assertFalse(core_useragent::check_firefox_version(5));
@@ -416,4 +444,4 @@ class core_useragent_testcase extends basic_testcase {
 
         core_useragent::instance(true);
     }
-}
\ No newline at end of file
+}
index 550f271..ae66726 100644 (file)
@@ -32,6 +32,10 @@ information provided here is intended especially for developers.
   get_fast_modinfo(). Purging all caches and every core upgrade purges course modinfo cache as well.
   If function get_fast_modinfo() is called for multiple courses make sure to include field cacherev in course
   object.
+* Internal (noreply and support) user support has been added for sending/receiving message.
+  Use core_user::get_noreply_user() and core_user::get_support_user() to get noreply and support user's respectively.
+  Real users can be used as noreply/support users by setting $CFG->noreplyuserid and $CFG->supportuserid
+* New function readfile_allow_large() in filelib.php for use when very large files may need sending to user.
 
 DEPRECATIONS:
 Various previously deprecated functions have now been altered to throw DEBUG_DEVELOPER debugging notices
@@ -104,6 +108,7 @@ Misc:
     * js_minify()                           -> core_minify::js_files()
     * css_minify_css()                      -> core_minify::css_files()
     * course_modinfo::build_section_cache() -> (no replacement)
+    * generate_email_supportuser()          -> core_user::get_support_user()
 
 User-agent related functions:
     * check_browser_operating_system()      -> core_useragent::check_browser_operating_system()
@@ -136,6 +141,7 @@ Event triggering and event handlers:
       * groups_groupings_groups_removed -> (no replacement)
       * groups_groups_deleted           -> \core\event\group_deleted
       * groups_groupings_deleted        -> \core\event\grouping_deleted
+    * edit_module_post_actions() does not trigger events any more.
 
 === 2.5.1 ===
 
index 16c46fb..e13c486 100644 (file)
@@ -103,13 +103,15 @@ unset($user1id);
 
 $user2 = null;
 if (!empty($user2id)) {
-    $user2 = $DB->get_record("user", array("id" => $user2id));
+    $user2 = core_user::get_user($user2id);
     if (!$user2) {
         print_error('invaliduserid');
     }
 }
 unset($user2id);
 
+$user2realuser = !empty($user2) && core_user::is_real_user($user2->id);
+$showactionlinks = $showactionlinks && $user2realuser;
 $systemcontext = context_system::instance();
 
 if (!empty($user2) && $user1->id == $user2->id) {
@@ -159,7 +161,7 @@ if ($unblockcontact and confirm_sesskey()) {
 
 //was a message sent? Do NOT allow someone looking at someone else's messages to send them.
 $messageerror = null;
-if ($currentuser && !empty($user2) && has_capability('moodle/site:sendmessage', $systemcontext)) {
+if ($currentuser && $user2realuser && has_capability('moodle/site:sendmessage', $systemcontext)) {
     // Check that the user is not blocking us!!
     if ($contact = $DB->get_record('message_contacts', array('userid' => $user2->id, 'contactid' => $user1->id))) {
         if ($contact->blocked and !has_capability('moodle/site:readallmessages', $systemcontext)) {
@@ -199,7 +201,7 @@ if ($currentuser && !empty($user2) && has_capability('moodle/site:sendmessage',
 }
 
 $strmessages = get_string('messages', 'message');
-if (!empty($user2)) {
+if ($user2realuser) {
     $user2fullname = fullname($user2);
 
     $PAGE->set_title("$strmessages: $user2fullname");
@@ -219,7 +221,7 @@ $countunreadtotal = 0; //count of unread messages from all users
 
 //we're dealing with unread messages early so the contact list will accurately reflect what is read/unread
 $viewingnewmessages = false;
-if (!empty($user2)) {
+if ($user2realuser) {
     //are there any unread messages from $user2
     $countunread = message_count_unread_messages($user1, $user2);
     if ($countunread>0) {
@@ -247,7 +249,7 @@ list($onlinecontacts, $offlinecontacts, $strangers) = message_get_contacts($user
 message_print_contact_selector($countunreadtotal, $viewing, $user1, $user2, $blockedusers, $onlinecontacts, $offlinecontacts, $strangers, $showactionlinks, $page);
 
 echo html_writer::start_tag('div', array('class' => 'messagearea mdl-align'));
-    if (!empty($user2)) {
+    if ($user2realuser) {
 
         echo html_writer::start_tag('div', array('class' => 'mdl-left messagehistory'));
 
index 288db3e..f0d8816 100644 (file)
@@ -1198,7 +1198,7 @@ function message_print_search_results($frm, $showicontext=false, $currentuser=nu
 
                 // Load user-to record.
                 if ($message->useridto !== $USER->id) {
-                    $userto = $DB->get_record('user', array('id' => $message->useridto));
+                    $userto = core_user::get_user($message->useridto);
                     $tocontact = (array_key_exists($message->useridto, $contacts) and
                                     ($contacts[$message->useridto]->blocked == 0) );
                     $toblocked = (array_key_exists($message->useridto, $contacts) and
@@ -1211,7 +1211,7 @@ function message_print_search_results($frm, $showicontext=false, $currentuser=nu
 
                 // Load user-from record.
                 if ($message->useridfrom !== $USER->id) {
-                    $userfrom = $DB->get_record('user', array('id' => $message->useridfrom));
+                    $userfrom = core_user::get_user($message->useridfrom);
                     $fromcontact = (array_key_exists($message->useridfrom, $contacts) and
                                     ($contacts[$message->useridfrom]->blocked == 0) );
                     $fromblocked = (array_key_exists($message->useridfrom, $contacts) and
@@ -1294,10 +1294,16 @@ function message_print_search_results($frm, $showicontext=false, $currentuser=nu
 function message_print_user ($user=false, $iscontact=false, $isblocked=false, $includeicontext=false) {
     global $USER, $OUTPUT;
 
+    $userpictureparams = array('size' => 20, 'courseid' => SITEID);
+
     if ($user === false) {
-        echo $OUTPUT->user_picture($USER, array('size' => 20, 'courseid' => SITEID));
+        echo $OUTPUT->user_picture($USER, $userpictureparams);
+    } else if (core_user::is_real_user($user->id)) { // If not real user, then don't show any links.
+        $userpictureparams['link'] = false;
+        echo $OUTPUT->user_picture($USER, $userpictureparams);
+        echo fullname($user);
     } else {
-        echo $OUTPUT->user_picture($user, array('size' => 20, 'courseid' => SITEID));
+        echo $OUTPUT->user_picture($user, $userpictureparams);
 
         $link = new moodle_url("/message/index.php?id=$user->id");
         echo $OUTPUT->action_link($link, fullname($user), null, array('title' =>
index 58d4b69..1873d1d 100644 (file)
@@ -127,4 +127,13 @@ class message_output_email extends message_output {
     function load_data(&$preferences, $userid){
         $preferences->email_email = get_user_preferences( 'message_processor_email_email', '', $userid);
     }
+
+    /**
+     * Returns true as message can be sent to internal support user.
+     *
+     * @return bool
+     */
+    public function can_send_to_any_users() {
+        return true;
+    }
 }
index dd480e5..2694333 100644 (file)
@@ -92,6 +92,16 @@ abstract class message_output {
     public function get_default_messaging_settings() {
         return MESSAGE_PERMITTED;
     }
+
+    /**
+     * Returns true if message can be sent to fake/internal user as well.
+     * If message_output support message to be sent to fake user, then it should return true, like email.
+     *
+     * @return bool
+     */
+    public function can_send_to_any_users() {
+        return false;
+    }
 }
 
 
index a45939c..715c9e2 100644 (file)
@@ -1,6 +1,10 @@
 This files describes API changes in /message/ messaging system,
 information provided here is intended especially for developers.
 
+=== 2.6 ===
+* Message processor extending message_output, should return true in can_send_to_any_users()
+  if it supports message sending to internal (noreply/support) users.
+
 === 2.2 ===
 
 required changes:
index cda69f1..ea6a36a 100644 (file)
@@ -627,5 +627,13 @@ abstract class assign_plugin {
         return true;
     }
 
-
+    /**
+     * If this plugin can participate in a webservice (save_submission or save_grade),
+     * return a list of external_params to be included in the definition of that webservice.
+     *
+     * @return external_description|null
+     */
+    public function get_external_parameters() {
+        return null;
+    }
 }
index 1fe8cd2..b2d12fc 100644 (file)
@@ -63,5 +63,70 @@ $functions = array(
                 'classpath' => 'mod/assign/externallib.php',
                 'description' => 'Returns the blind marking mappings for assignments',
                 'type' => 'read'
+        ),
+
+        'mod_assign_revert_submissions_to_draft' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'revert_submissions_to_draft',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Reverts the list of submissions to draft status',
+                'type' => 'write'
+        ),
+
+        'mod_assign_lock_submissions' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'lock_submissions',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Prevent students from making changes to a list of submissions',
+                'type' => 'write'
+        ),
+
+        'mod_assign_unlock_submissions' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'unlock_submissions',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Allow students to make changes to a list of submissions',
+                'type' => 'write'
+        ),
+
+        'mod_assign_save_submission' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'save_submission',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Update the current students submission',
+                'type' => 'write'
+        ),
+
+        'mod_assign_submit_for_grading' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'submit_for_grading',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Submit the current students assignment for grading',
+                'type' => 'write'
+        ),
+
+        'mod_assign_save_grade' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'save_grade',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Save a grade update for a single student.',
+                'type' => 'write'
+        ),
+
+        'mod_assign_save_user_extensions' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'save_user_extensions',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Save a list of assignment extensions',
+                'type' => 'write'
+        ),
+
+        'mod_assign_reveal_identities' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'reveal_identities',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Reveal the identities for a blind marking assignment',
+                'type' => 'write'
         )
+
 );
index 2b03f07..8aa0a35 100644 (file)
@@ -29,9 +29,43 @@ require_once("$CFG->libdir/externallib.php");
 
 /**
  * Assign functions
+ * @copyright 2012 Paul Charsley
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class mod_assign_external extends external_api {
 
+    /**
+     * Generate a warning in a standard structure for a known failure.
+     *
+     * @param int $assignmentid - The assignment
+     * @param string $warningcode - The key for the warning message
+     * @param string $detail - A description of the error
+     * @return array - Warning structure containing item, itemid, warningcode, message
+     */
+    private static function generate_warning($assignmentid, $warningcode, $detail) {
+        $warningmessages = array(
+            'couldnotlock'=>'Could not lock the submission for this user.',
+            'couldnotunlock'=>'Could not unlock the submission for this user.',
+            'couldnotsubmitforgrading'=>'Could not submit assignment for grading.',
+            'couldnotrevealidentities'=>'Could not reveal identities.',
+            'couldnotgrantextensions'=>'Could not grant submission date extensions.',
+            'couldnotrevert'=>'Could not revert submission to draft.',
+            'invalidparameters'=>'Invalid parameters.',
+            'couldnotsavesubmission'=>'Could not save submission.',
+            'couldnotsavegrade'=>'Could not save grade.'
+        );
+
+        $message = $warningmessages[$warningcode];
+        if (empty($message)) {
+            $message = 'Unknown warning type.';
+        }
+
+        return array('item'=>$detail,
+                     'itemid'=>$assignmentid,
+                     'warningcode'=>$warningcode,
+                     'message'=>$message);
+    }
+
     /**
      * Describes the parameters for get_grades
      * @return external_external_function_parameters
@@ -53,7 +87,7 @@ class mod_assign_external extends external_api {
 
     /**
      * Returns grade information from assign_grades for the requested assignment ids
-     * @param array of ints $assignmentids
+     * @param int[] $assignmentids
      * @param int $since only return records with timemodified >= since
      * @return array of grade records for each requested assignment
      * @since  Moodle 2.4
@@ -102,7 +136,7 @@ class mod_assign_external extends external_api {
                                 WHERE mxg.assignment ' . $inorequalsql2 . ' GROUP BY mxg.userid';
 
             $sql = "SELECT ag.id,ag.assignment,ag.userid,ag.timecreated,ag.timemodified,".
-                   "ag.grader,ag.grade ".
+                   "ag.grader,ag.grade,ag.attemptnumber ".
                    "FROM {assign_grades} ag ".
                    "JOIN ( " . $grademaxattempt . " ) gmx ON ag.userid = gmx.userid".
                    " WHERE ag.assignment ".$inorequalsql.
@@ -122,6 +156,7 @@ class mod_assign_external extends external_api {
                 $grade['timecreated'] = $rd->timecreated;
                 $grade['timemodified'] = $rd->timemodified;
                 $grade['grader'] = $rd->grader;
+                $grade['attemptnumber'] = $rd->attemptnumber;
                 $grade['grade'] = (string)$rd->grade;
 
                 if (is_null($currentassignmentid) || ($rd->assignment != $currentassignmentid )) {
@@ -170,6 +205,7 @@ class mod_assign_external extends external_api {
                         array(
                             'id'            => new external_value(PARAM_INT, 'grade id'),
                             'userid'        => new external_value(PARAM_INT, 'student id'),
+                            'attemptnumber' => new external_value(PARAM_INT, 'attempt number'),
                             'timecreated'   => new external_value(PARAM_INT, 'grade creation time'),
                             'timemodified'  => new external_value(PARAM_INT, 'grade last modified time'),
                             'grader'        => new external_value(PARAM_INT, 'grader'),
@@ -473,7 +509,7 @@ class mod_assign_external extends external_api {
     /**
      * Returns submissions for the requested assignment ids
      *
-     * @param array of ints $assignmentids
+     * @param int[] $assignmentids
      * @param string $status only return submissions with this status
      * @param int $since only return submissions with timemodified >= since
      * @param int $before only return submissions with timemodified <= before
@@ -528,7 +564,7 @@ class mod_assign_external extends external_api {
                                      WHERE mxs.assignment = :assignid1 GROUP BY mxs.userid';
 
             $sql = "SELECT mas.id, mas.assignment,mas.userid,".
-                   "mas.timecreated,mas.timemodified,mas.status,mas.groupid ".
+                   "mas.timecreated,mas.timemodified,mas.status,mas.groupid,mas.attemptnumber ".
                    "FROM {assign_submission} mas ".
                    "JOIN ( " . $submissionmaxattempt . " ) smx ON mas.userid = smx.userid ".
                    "WHERE mas.assignment = :assignid2 AND mas.attemptnumber = smx.maxattempt";
@@ -557,6 +593,7 @@ class mod_assign_external extends external_api {
                         'timecreated' => $submissionrecord->timecreated,
                         'timemodified' => $submissionrecord->timemodified,
                         'status' => $submissionrecord->status,
+                        'attemptnumber' => $submissionrecord->attemptnumber,
                         'groupid' => $submissionrecord->groupid
                     );
                     foreach ($submissionplugins as $submissionplugin) {
@@ -638,6 +675,7 @@ class mod_assign_external extends external_api {
                         array(
                             'id' => new external_value(PARAM_INT, 'submission id'),
                             'userid' => new external_value(PARAM_INT, 'student id'),
+                            'attemptnumber' => new external_value(PARAM_INT, 'attempt number'),
                             'timecreated' => new external_value(PARAM_INT, 'submission creation time'),
                             'timemodified' => new external_value(PARAM_INT, 'submission last modified time'),
                             'status' => new external_value(PARAM_TEXT, 'submission status'),
@@ -716,7 +754,7 @@ class mod_assign_external extends external_api {
 
     /**
      * Returns user flag information from assign_user_flags for the requested assignment ids
-     * @param array of ints $assignmentids
+     * @param int[] $assignmentids
      * @return array of user flag records for each requested assignment
      * @since  Moodle 2.6
      */
@@ -870,7 +908,7 @@ class mod_assign_external extends external_api {
 
     /**
      * Returns user mapping information from assign_user_mapping for the requested assignment ids
-     * @param array of ints $assignmentids
+     * @param int[] $assignmentids
      * @return array of user mapping records for each requested assignment
      * @since  Moodle 2.6
      */
@@ -995,4 +1033,620 @@ class mod_assign_external extends external_api {
         );
     }
 
+    /**
+     * Describes the parameters for lock_submissions
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function lock_submissions_parameters() {
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
+                'userids' => new external_multiple_structure(
+                    new external_value(PARAM_INT, 'user id'),
+                    '1 or more user ids',
+                    VALUE_REQUIRED),
+            )
+        );
+    }
+
+    /**
+     * Locks (prevent updates to) submissions in this assignment.
+     *
+     * @param int $assignmentid The id of the assignment
+     * @param array $userids Array of user ids to lock
+     * @return array of warnings for each submission that could not be locked.
+     * @since Moodle 2.6
+     */
+    public static function lock_submissions($assignmentid, $userids) {
+        global $CFG;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::lock_submissions_parameters(),
+                        array('assignmentid' => $assignmentid,
+                              'userids' => $userids));
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $warnings = array();
+        foreach ($userids as $userid) {
+            if (!$assignment->lock_submission($userid)) {
+                $detail = 'User id: ' . $userid . ', Assignment id: ' . $assignmentid;
+                $warnings[] = self::generate_warning($assignmentid,
+                                                     'couldnotlock',
+                                                     $detail);
+            }
+        }
+
+        return $warnings;
+    }
+
+    /**
+     * Describes the return value for lock_submissions
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function lock_submissions_returns() {
+        return new external_multiple_structure(
+           new external_warnings()
+        );
+    }
+
+    /**
+     * Describes the parameters for revert_submissions_to_draft
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function revert_submissions_to_draft_parameters() {
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
+                'userids' => new external_multiple_structure(
+                    new external_value(PARAM_INT, 'user id'),
+                    '1 or more user ids',
+                    VALUE_REQUIRED),
+            )
+        );
+    }
+
+    /**
+     * Reverts a list of user submissions to draft for a single assignment.
+     *
+     * @param int $assignmentid The id of the assignment
+     * @param array $userids Array of user ids to revert
+     * @return array of warnings for each submission that could not be reverted.
+     * @since Moodle 2.6
+     */
+    public static function revert_submissions_to_draft($assignmentid, $userids) {
+        global $CFG;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::revert_submissions_to_draft_parameters(),
+                        array('assignmentid' => $assignmentid,
+                              'userids' => $userids));
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $warnings = array();
+        foreach ($userids as $userid) {
+            if (!$assignment->revert_to_draft($userid)) {
+                $detail = 'User id: ' . $userid . ', Assignment id: ' . $assignmentid;
+                $warnings[] = self::generate_warning($assignmentid,
+                                                     'couldnotrevert',
+                                                     $detail);
+            }
+        }
+
+        return $warnings;
+    }
+
+    /**
+     * Describes the return value for revert_submissions_to_draft
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function revert_submissions_to_draft_returns() {
+        return new external_multiple_structure(
+           new external_warnings()
+        );
+    }
+
+    /**
+     * Describes the parameters for unlock_submissions
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function unlock_submissions_parameters() {
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
+                'userids' => new external_multiple_structure(
+                    new external_value(PARAM_INT, 'user id'),
+                    '1 or more user ids',
+                    VALUE_REQUIRED),
+            )
+        );
+    }
+
+    /**
+     * Locks (prevent updates to) submissions in this assignment.
+     *
+     * @param int $assignmentid The id of the assignment
+     * @param array $userids Array of user ids to lock
+     * @return array of warnings for each submission that could not be locked.
+     * @since Moodle 2.6
+     */
+    public static function unlock_submissions($assignmentid, $userids) {
+        global $CFG;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::unlock_submissions_parameters(),
+                        array('assignmentid' => $assignmentid,
+                              'userids' => $userids));
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $warnings = array();
+        foreach ($userids as $userid) {
+            if (!$assignment->unlock_submission($userid)) {
+                $detail = 'User id: ' . $userid . ', Assignment id: ' . $assignmentid;
+                $warnings[] = self::generate_warning($assignmentid,
+                                                     'couldnotunlock',
+                                                     $detail);
+            }
+        }
+
+        return $warnings;
+    }
+
+    /**
+     * Describes the return value for unlock_submissions
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function unlock_submissions_returns() {
+        return new external_multiple_structure(
+           new external_warnings()
+        );
+    }
+
+    /**
+     * Describes the parameters for submit_for_grading
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function submit_for_grading_parameters() {
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
+                'acceptsubmissionstatement' => new external_value(PARAM_BOOL, 'Accept the assignment submission statement')
+            )
+        );
+    }
+
+    /**
+     * Submit the logged in users assignment for grading.
+     *
+     * @param int $assignmentid The id of the assignment
+     * @return array of warnings to indicate any errors.
+     * @since Moodle 2.6
+     */
+    public static function submit_for_grading($assignmentid, $acceptsubmissionstatement) {
+        global $CFG, $USER;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::submit_for_grading_parameters(),
+                                            array('assignmentid' => $assignmentid,
+                                                  'acceptsubmissionstatement' => $acceptsubmissionstatement));
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $warnings = array();
+        $data = new stdClass();
+        $data->submissionstatement = $acceptsubmissionstatement;
+
+        if (!$assignment->submit_for_grading($data)) {
+            $detail = 'User id: ' . $USER->id . ', Assignment id: ' . $assignmentid;
+            $warnings[] = self::generate_warning($assignmentid,
+                                                 'couldnotsubmitforgrading',
+                                                 $detail);
+        }
+
+        return $warnings;
+    }
+
+    /**
+     * Describes the return value for submit_for_grading
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function submit_for_grading_returns() {
+        return new external_multiple_structure(
+           new external_warnings()
+        );
+    }
+
+    /**
+     * Describes the parameters for save_user_extensions
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function save_user_extensions_parameters() {
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
+                'userids' => new external_multiple_structure(
+                    new external_value(PARAM_INT, 'user id'),
+                    '1 or more user ids',
+                    VALUE_REQUIRED),
+                'dates' => new external_multiple_structure(
+                    new external_value(PARAM_INT, 'dates'),
+                    '1 or more extension dates (timestamp)',
+                    VALUE_REQUIRED),
+            )
+        );
+    }
+
+    /**
+     * Grant extension dates to students for an assignment.
+     *
+     * @param int $assignmentid The id of the assignment
+     * @param array $userids Array of user ids to grant extensions to
+     * @param array $dates Array of extension dates
+     * @return array of warnings for each extension date that could not be granted
+     * @since Moodle 2.6
+     */
+    public static function save_user_extensions($assignmentid, $userids, $dates) {
+        global $CFG;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::save_user_extensions_parameters(),
+                        array('assignmentid' => $assignmentid,
+                              'userids' => $userids,
+                              'dates' => $dates));
+
+        if (count($userids) != count($dates)) {
+            $detail = 'Length of userids and dates parameters differ.';
+            $warnings[] = self::generate_warning($assignmentid,
+                                                 'invalidparameters',
+                                                 $detail);
+
+            return $warnings;
+        }
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $warnings = array();
+        foreach ($userids as $idx => $userid) {
+            $duedate = $dates[$idx];
+            if (!$assignment->save_user_extension($userid, $duedate)) {
+                $detail = 'User id: ' . $userid . ', Assignment id: ' . $assignmentid . ', Extension date: ' . $duedate;
+                $warnings[] = self::generate_warning($assignmentid,
+                                                     'couldnotgrantextensions',
+                                                     $detail);
+            }
+        }
+
+        return $warnings;
+    }
+
+    /**
+     * Describes the return value for save_user_extensions
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function save_user_extensions_returns() {
+        return new external_multiple_structure(
+           new external_warnings()
+        );
+    }
+
+    /**
+     * Describes the parameters for reveal_identities
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function reveal_identities_parameters() {
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on')
+            )
+        );
+    }
+
+    /**
+     * Reveal the identities of anonymous students to markers for a single assignment.
+     *
+     * @param int $assignmentid The id of the assignment
+     * @return array of warnings to indicate any errors.
+     * @since Moodle 2.6
+     */
+    public static function reveal_identities($assignmentid) {
+        global $CFG, $USER;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::reveal_identities_parameters(),
+                                            array('assignmentid' => $assignmentid));
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $warnings = array();
+        if (!$assignment->reveal_identities()) {
+            $detail = 'User id: ' . $USER->id . ', Assignment id: ' . $assignmentid;
+            $warnings[] = self::generate_warning($assignmentid,
+                                                 'couldnotrevealidentities',
+                                                 $detail);
+        }
+
+        return $warnings;
+    }
+
+    /**
+     * Describes the return value for reveal_identities
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function reveal_identities_returns() {
+        return new external_multiple_structure(
+           new external_warnings()
+        );
+    }
+
+    /**
+     * Describes the parameters for save_submission
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function save_submission_parameters() {
+        global $CFG;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+        $instance = new assign(null, null, null);
+        $pluginsubmissionparams = array();
+
+        foreach ($instance->get_submission_plugins() as $plugin) {
+            $pluginparams = $plugin->get_external_parameters();
+            if (!empty($pluginparams)) {
+                $pluginsubmissionparams = array_merge($pluginsubmissionparams, $pluginparams);
+            }
+        }
+
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
+                'plugindata' => new external_single_structure(
+                    $pluginsubmissionparams
+                )
+            )
+        );
+    }
+
+    /**
+     * Save a student submission for a single assignment
+     *
+     * @param int $assignmentid The id of the assignment
+     * @param array $plugindata - The submitted data for plugins
+     * @return array of warnings to indicate any errors
+     * @since Moodle 2.6
+     */
+    public static function save_submission($assignmentid, $plugindata) {
+        global $CFG, $USER;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::save_submission_parameters(),
+                                            array('assignmentid' => $assignmentid,
+                                                  'plugindata' => $plugindata));
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $notices = array();
+
+        $submissiondata = (object)$plugindata;
+
+        $assignment->save_submission($submissiondata, $notices);
+
+        $warnings = array();
+        foreach ($notices as $notice) {
+            $warnings[] = self::generate_warning($assignmentid,
+                                                 'couldnotsavesubmission',
+                                                 $notice);
+        }
+
+        return $warnings;
+    }
+
+    /**
+     * Describes the return value for save_submission
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function save_submission_returns() {
+        return new external_multiple_structure(
+           new external_warnings()
+        );
+    }
+
+    /**
+     * Describes the parameters for save_grade
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function save_grade_parameters() {
+        global $CFG;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+        $instance = new assign(null, null, null);
+        $pluginfeedbackparams = array();
+
+        foreach ($instance->get_feedback_plugins() as $plugin) {
+            $pluginparams = $plugin->get_external_parameters();
+            if (!empty($pluginparams)) {
+                $pluginfeedbackparams = array_merge($pluginfeedbackparams, $pluginparams);
+            }
+        }
+
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
+                'userid' => new external_value(PARAM_INT, 'The student id to operate on'),
+                'grade' => new external_value(PARAM_FLOAT, 'The new grade for this user'),
+                'attemptnumber' => new external_value(PARAM_INT, 'The attempt number (-1 means latest attempt)'),
+                'addattempt' => new external_value(PARAM_BOOL, 'Allow another attempt if the attempt reopen method is manual'),
+                'workflowstate' => new external_value(PARAM_ALPHA, 'The next marking workflow state'),
+                'applytoall' => new external_value(PARAM_BOOL, 'If true, this grade will be applied ' .
+                                                               'to all members ' .
+                                                               'of the group (for group assignments).'),
+                'plugindata' => new external_single_structure(
+                    $pluginfeedbackparams
+                )
+            )
+        );
+    }
+
+    /**
+     * Save a student grade for a single assignment.
+     *
+     * @param int $assignmentid The id of the assignment
+     * @param int $userid The id of the user
+     * @param float $grade The grade
+     * @param int $attemptnumber The attempt number
+     * @param bool $addattempt Allow another attempt
+     * @param string $workflowstate New workflow state
+     * @param bool $applytoall Apply the grade to all members of the group
+     * @param array $plugindata Custom data used by plugins
+     * @return null
+     * @since Moodle 2.6
+     */
+    public static function save_grade($assignmentid,
+                                      $userid,
+                                      $grade,
+                                      $attemptnumber,
+                                      $addattempt,
+                                      $workflowstate,
+                                      $applytoall,
+                                      $plugindata) {
+        global $CFG, $USER;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::save_grade_parameters(),
+                                            array('assignmentid' => $assignmentid,
+                                                  'userid' => $userid,
+                                                  'grade' => $grade,
+                                                  'attemptnumber' => $attemptnumber,
+                                                  'workflowstate' => $workflowstate,
+                                                  'addattempt' => $addattempt,
+                                                  'applytoall' => $applytoall,
+                                                  'plugindata' => $plugindata));
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $gradedata = (object)$plugindata;
+
+        $gradedata->addattempt = $addattempt;
+        $gradedata->attemptnumber = $attemptnumber;
+        $gradedata->workflowstate = $workflowstate;
+        $gradedata->applytoall = $applytoall;
+        $gradedata->grade = $grade;
+
+        $assignment->save_grade($userid, $gradedata);
+
+        return null;
+    }
+
+    /**
+     * Describes the return value for save_grade
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function save_grade_returns() {
+        return null;
+    }
+
+    /**
+     * Describes the parameters for copy_previous_attempt
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function copy_previous_attempt_parameters() {
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
+            )
+        );
+    }
+
+    /**
+     * Copy a students previous attempt to a new attempt.
+     *
+     * @param int $assignmentid
+     * @return array of warnings to indicate any errors.
+     * @since Moodle 2.6
+     */
+    public static function copy_previous_attempt($assignmentid) {
+        global $CFG, $USER;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::copy_previous_attempt_parameters(),
+                                            array('assignmentid' => $assignmentid));
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $notices = array();
+
+        $assignment->copy_previous_attempt($submissiondata, $notices);
+
+        $warnings = array();
+        foreach ($notices as $notice) {
+            $warnings[] = self::generate_warning($assignmentid,
+                                                 'couldnotcopyprevioussubmission',
+                                                 $notice);
+        }
+
+        return $warnings;
+    }
+
+    /**
+     * Describes the return value for save_submission
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function copy_previous_attempt_returns() {
+        return new external_multiple_structure(
+           new external_warnings()
+        );
+    }
 }
index 1efd24e..9b95aed 100644 (file)
@@ -15,7 +15,9 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Restore subplugin class that provides the necessary information needed to restore
+ * Restore subplugin class.
+ *
+ * Provides the necessary information needed to restore
  * one assign_submission subplugin.
  *
  * @package   assignfeedback_comments
@@ -26,7 +28,9 @@
 defined('MOODLE_INTERNAL') || die();
 
 /**
- * Restore subplugin class that provides the necessary information needed to restore
+ * Restore subplugin class.
+ *
+ * Provides the necessary information needed to restore
  * one assignfeedback subplugin.
  *
  * @package   assignfeedback_comments
index 9ed302c..2d6e92d 100644 (file)
@@ -390,4 +390,16 @@ class assign_feedback_comments extends assign_feedback_plugin {
         return $this->view($grade) == '';
     }
 
+    /**
+     * Return a description of external params suitable for uploading an feedback comment from a webservice.
+     *
+     * @return external_description|null
+     */
+    public function get_external_parameters() {
+        $editorparams = array('text' => new external_value(PARAM_TEXT, 'The text for this feedback.'),
+                              'format' => new external_value(PARAM_INT, 'The format for this feedback'));
+        $editorstructure = new external_single_structure($editorparams);
+        return array('assignfeedbackcomments_editor' => $editorstructure);
+    }
+
 }
index f85cd5a..f8d45ae 100644 (file)
@@ -17,7 +17,7 @@
 /**
  * This file defines the admin settings for this plugin
  *
- * @package   assignsubmission_comments
+ * @package   assignfeedback_comments
  * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
index df258d3..a408afd 100644 (file)
@@ -24,7 +24,9 @@
 defined('MOODLE_INTERNAL') || die();
 
 /**
- * Restore subplugin class that provides the necessary information needed
+ * Restore subplugin class.
+ *
+ * Provides the necessary information needed
  * to restore one assign_feedback subplugin.
  *
  * @package   assignfeedback_file
index 50c0e25..7b64583 100644 (file)
@@ -36,7 +36,7 @@ define('ASSIGNFEEDBACK_FILE_MAXFILEUNZIPTIME', 120);
 /**
  * Library class for file feedback plugin extending feedback plugin base class.
  *
- * @package   asignfeedback_file
+ * @package   assignfeedback_file
  * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -609,4 +609,19 @@ class assign_feedback_file extends assign_feedback_plugin {
     public function get_grading_actions() {
         return array('uploadzip'=>get_string('uploadzip', 'assignfeedback_file'));
     }
+
+    /**
+     * Return a description of external params suitable for uploading a feedback file from a webservice.
+     *
+     * @return external_description|null
+     */
+    public function get_external_parameters() {
+        return array(
+            'files_filemanager' => new external_value(
+                PARAM_INT,
+                'The id of a draft area containing files for this feedback.'
+            )
+        );
+    }
+
 }
index 0719b0b..ead9c88 100644 (file)
@@ -17,7 +17,7 @@
 /**
  * This file defines the admin settings for this plugin
  *
- * @package   assignsubmission_onlinetext
+ * @package   assignfeedback_file
  * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
index 4b795fe..d7c7e27 100644 (file)
@@ -71,6 +71,7 @@ class assign_grading_table extends table_sql implements renderable {
      * @param string $filter The current filter
      * @param int $rowoffset For showing a subsequent page of results
      * @param bool $quickgrading Is this table wrapped in a quickgrading form?
+     * @param string $downloadfilename
      */
     public function __construct(assign $assignment,
                                 $perpage,
@@ -435,6 +436,7 @@ class assign_grading_table extends table_sql implements renderable {
     /**
      * Add a column with an ID that uniquely identifies this user in this assignment.
      *
+     * @param stdClass $row
      * @return string
      */
     public function col_recordid(stdClass $row) {
@@ -621,8 +623,8 @@ class assign_grading_table extends table_sql implements renderable {
      * Use a static cache to try and reduce DB calls.
      *
      * @param int $userid The user id for this submission
-     * @param int $groupid The groupid (returned)
-     * @param mixed $submission The stdClass submission or false (returned)
+     * @param int $group The groupid (returned)
+     * @param stdClass|false $submission The stdClass submission or false (returned)
      * @param int $attemptnumber Return a specific attempt number (-1 for latest)
      */
     protected function get_group_and_submission($userid, &$group, &$submission, $attemptnumber) {
index f072009..bc6d798 100644 (file)
@@ -40,6 +40,7 @@ $PAGE->navbar->add($strplural);
 $PAGE->set_title($strplural);
 $PAGE->set_heading($course->fullname);
 echo $OUTPUT->header();
+echo $OUTPUT->heading(format_string($strplural));
 
 $context = context_course::instance($course->id);
 
index 49a13f1..99bd425 100644 (file)
@@ -16,6 +16,7 @@
 
 /**
  * This file contains the moodle hooks for the assign module.
+ *
  * It delegates most functions to the assignment class.
  *
  * @package   mod_assign
@@ -59,8 +60,9 @@ function assign_delete_instance($id) {
  * This function is used by the reset_course_userdata function in moodlelib.
  * This function will remove all assignment submissions and feedbacks in the database
  * and clean up any related data.
- * @param $data the data submitted from the reset course.
- * @return array status array
+ *
+ * @param stdClass $data the data submitted from the reset course.
+ * @return array
  */
 function assign_reset_userdata($data) {
     global $CFG, $DB;
@@ -109,7 +111,7 @@ function assign_reset_gradebook($courseid, $type='') {
 /**
  * Implementation of the function for printing the form elements that control
  * whether the course reset functionality affects the assignment.
- * @param $mform form passed by reference
+ * @param moodleform $mform form passed by reference
  */
 function assign_reset_course_form_definition(&$mform) {
     $mform->addElement('header', 'assignheader', get_string('modulenameplural', 'assign'));
@@ -131,7 +133,7 @@ function assign_reset_course_form_defaults($course) {
  *
  * This is done by calling the update_instance() method of the assignment type class
  * @param stdClass $data
- * @param $form
+ * @param stdClass $form - unused
  * @return object
  */
 function assign_update_instance(stdClass $data, $form) {
@@ -280,11 +282,11 @@ function assign_get_coursemodule_info($coursemodule) {
  * @param stdClass $currentcontext Current context of block
  */
 function assign_page_type_list($pagetype, $parentcontext, $currentcontext) {
-    $module_pagetype = array(
+    $modulepagetype = array(
         'mod-assign-*' => get_string('page-mod-assign-x', 'assign'),
         'mod-assign-view' => get_string('page-mod-assign-view', 'assign'),
     );
-    return $module_pagetype;
+    return $modulepagetype;
 }
 
 /**
@@ -656,10 +658,10 @@ function assign_get_recent_mod_activity(&$activities,
     }
 
     $groupmode       = groups_get_activity_groupmode($cm, $course);
-    $cm_context      = context_module::instance($cm->id);
-    $grader          = has_capability('moodle/grade:viewall', $cm_context);
-    $accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context);
-    $viewfullnames   = has_capability('moodle/site:viewfullnames', $cm_context);
+    $cmcontext      = context_module::instance($cm->id);
+    $grader          = has_capability('moodle/grade:viewall', $cmcontext);
+    $accessallgroups = has_capability('moodle/site:accessallgroups', $cmcontext);
+    $viewfullnames   = has_capability('moodle/site:viewfullnames', $cmcontext);
 
     if (is_null($modinfo->get_groups())) {
         // Load all my groups and cache it in modinfo.
index 5941b34..62f0678 100644 (file)
@@ -389,7 +389,7 @@ class assign {
                 $nextpageparams['action'] = 'editsubmission';
             }
         } else if ($action == 'lock') {
-            $this->process_lock();
+            $this->process_lock_submission();
             $action = 'redirect';
             $nextpageparams['action'] = 'grading';
         } else if ($action == 'addattempt') {
@@ -401,7 +401,7 @@ class assign {
             $action = 'redirect';
             $nextpageparams['action'] = 'grading';
         } else if ($action == 'unlock') {
-            $this->process_unlock();
+            $this->process_unlock_submission();
             $action = 'redirect';
             $nextpageparams['action'] = 'grading';
         } else if ($action == 'setbatchmarkingworkflowstate') {
@@ -674,7 +674,7 @@ class assign {
      * Actual implementation of the reset course functionality, delete all the
      * assignment submissions for course $data->courseid.
      *
-     * @param $data the data submitted from the reset course.
+     * @param stdClass $data the data submitted from the reset course.
      * @return array status array
      */
     public function reset_userdata($data) {
@@ -1260,12 +1260,21 @@ class assign {
      */
     public function list_participants($currentgroup, $idsonly) {
         if ($idsonly) {
-            return get_enrolled_users($this->context, 'mod/assign:submit', $currentgroup, 'u.id', null, null, null,
+            $users = get_enrolled_users($this->context, 'mod/assign:submit', $currentgroup, 'u.id', null, null, null,
                     $this->show_only_active_users());
         } else {
-            return get_enrolled_users($this->context, 'mod/assign:submit', $currentgroup, 'u.*', null, null, null,
+            $users = get_enrolled_users($this->context, 'mod/assign:submit', $currentgroup, 'u.*', null, null, null,
                     $this->show_only_active_users());
         }
+
+        $cm = $this->get_course_module();
+        foreach ($users as $userid => $user) {
+            if (!groups_course_module_visible($cm, $userid)) {
+                unset($users[$userid]);
+            }
+        }
+
+        return $users;
     }
 
     /**
@@ -1297,7 +1306,7 @@ class assign {
      * @return int number of matching users
      */
     public function count_participants($currentgroup) {
-        return count_enrolled_users($this->context, 'mod/assign:submit', $currentgroup, true);
+        return count($this->list_participants($currentgroup, true));
     }
 
     /**
@@ -2003,10 +2012,10 @@ class assign {
                     $submitted = get_string('submissionstatus_', 'assign');
                 }
             }
-            $grading_info = grade_get_grades($course->id, 'mod', 'assign', $cm->instance, $USER->id);
-            if (isset($grading_info->items[0]->grades[$USER->id]) &&
-                    !$grading_info->items[0]->grades[$USER->id]->hidden ) {
-                $grade = $grading_info->items[0]->grades[$USER->id]->str_grade;
+            $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $cm->instance, $USER->id);
+            if (isset($gradinginfo->items[0]->grades[$USER->id]) &&
+                    !$gradinginfo->items[0]->grades[$USER->id]->hidden ) {
+                $grade = $gradinginfo->items[0]->grades[$USER->id]->str_grade;
             } else {
                 $grade = '-';
             }
@@ -2142,6 +2151,7 @@ class assign {
     /**
      * Rewrite plugin file urls so they resolve correctly in an exported zip.
      *
+     * @param string $text - The replacement text
      * @param stdClass $user - The user record
      * @param assign_plugin $plugin - The assignment plugin
      */
@@ -2231,6 +2241,7 @@ class assign {
     /**
      * Display a continue page.
      *
+     * @param string $message - The message to display
      * @return string
      */
     protected function view_savegrading_result($message) {
@@ -3237,6 +3248,7 @@ class assign {
     /**
      * Allows the plugin to show a batch grading operation page.
      *
+     * @param moodleform $mform
      * @return none
      */
     protected function view_plugin_grading_batch_operation($mform) {
@@ -3314,9 +3326,9 @@ class assign {
 
             foreach ($userlist as $userid) {
                 if ($data->operation == 'lock') {
-                    $this->process_lock($userid);
+                    $this->process_lock_submission($userid);
                 } else if ($data->operation == 'unlock') {
-                    $this->process_unlock($userid);
+                    $this->process_unlock_submission($userid);
                 } else if ($data->operation == 'reverttodraft') {
                     $this->process_revert_to_draft($userid);
                 } else if ($data->operation == 'addattempt') {
@@ -3453,7 +3465,7 @@ class assign {
     /**
      * Ask the user to confirm they want to submit their work for grading.
      *
-     * @param $mform moodleform - null unless form validation has failed
+     * @param moodleform $mform - null unless form validation has failed
      * @return string
      */
     protected function check_submit_for_grading($mform) {
@@ -3651,7 +3663,8 @@ class assign {
                 // Only show the grade if it is not hidden in gradebook.
                 if (!empty($gradebookgrade->grade) && ($cangrade || !$gradebookgrade->hidden)) {
                     if ($controller = $gradingmanager->get_active_controller()) {
-                        $controller->set_grade_range(make_grades_menu($this->get_instance()->grade), $this->get_instance()->grade > 0);
+                        $menu = make_grades_menu($this->get_instance()->grade);
+                        $controller->set_grade_range($menu, $this->get_instance()->grade > 0);
                         $gradefordisplay = $controller->render_grade($PAGE,
                                                                      $grade->id,
                                                                      $gradingitem,
@@ -4241,7 +4254,6 @@ class assign {
      * @param string $modulename
      * @param stdClass $coursemodule
      * @param string $assignmentname
-     * @param stdClass $info
      */
     protected static function format_notification_message_html($messagetype,
                                                              $info,
@@ -4281,6 +4293,8 @@ class assign {
      * @param stdClass $course
      * @param string $modulename
      * @param string $assignmentname
+     * @param bool $blindmarking
+     * @param int $uniqueidforuser
      * @return void
      */
     public static function send_assignment_notification($userfrom,
@@ -4462,18 +4476,85 @@ class assign {
         }
     }
 
+    /**
+     * Submit a submission for grading.
+     *
+     * @return bool Return false if the submission was not submitted.
+     */
+    public function submit_for_grading($data) {
+        global $USER;
+
+        // Need submit permission to submit an assignment.
+        require_capability('mod/assign:submit', $this->context);
+
+        $instance = $this->get_instance();
+
+        if ($instance->teamsubmission) {
+            $submission = $this->get_group_submission($USER->id, 0, true);
+        } else {
+            $submission = $this->get_user_submission($USER->id, true);
+        }
+
+        if (!$this->submissions_open($USER->id)) {
+            return false;
+        }
+
+        if ($instance->requiresubmissionstatement && !$data->submissionstatement) {
+            return false;
+        }
+
+        if ($submission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
+            // Give each submission plugin a chance to process the submission.
+            $plugins = $this->get_submission_plugins();
+            foreach ($plugins as $plugin) {
+                if ($plugin->is_enabled() && $plugin->is_visible()) {
+                    $plugin->submit_for_grading($submission);
+                }
+            }
+
+            $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
+            $this->update_submission($submission, $USER->id, true, $instance->teamsubmission);
+            $completion = new completion_info($this->get_course());
+            if ($completion->is_enabled($this->get_course_module()) && $instance->completionsubmit) {
+                $completion->update_state($this->get_course_module(), COMPLETION_COMPLETE, $USER->id);
+            }
+
+            if (!empty($data->submissionstatement)) {
+                $logmessage = get_string('submissionstatementacceptedlog',
+                                         'mod_assign',
+                                         fullname($USER));
+                $this->add_to_log('submission statement accepted', $logmessage);
+            }
+            $logdata = $this->add_to_log('submit for grading', $this->format_submission_for_log($submission), '', true);
+            $this->notify_graders($submission);
+            $this->notify_student_submission_receipt($submission);
+
+            // Trigger assessable_submitted event on submission.
+            $params = array(
+                'context' => context_module::instance($this->get_course_module()->id),
+                'objectid' => $submission->id,
+                'other' => array(
+                    'submission_editable' => false
+                )
+            );
+            $event = \mod_assign\event\assessable_submitted::create($params);
+            $event->set_legacy_logdata($logdata);
+            $event->trigger();
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Assignment submission is processed before grading.
      *
-     * @param $mform If validation failed when submitting this form - this is the moodleform.
+     * @param moodleform|null $mform If validation failed when submitting this form - this is the moodleform.
      *               It can be null.
      * @return bool Return false if the validation fails. This affects which page is displayed next.
      */
     protected function process_submit_for_grading($mform) {
         global $USER, $CFG;
 
-        // Need submit permission to submit an assignment.
-        require_capability('mod/assign:submit', $this->context);
         require_once($CFG->dirroot . '/mod/assign/submissionconfirmform.php');
         require_sesskey();
 
@@ -4503,57 +4584,7 @@ class assign {
             if ($mform->get_data() == false) {
                 return false;
             }
-            if ($instance->teamsubmission) {
-                $submission = $this->get_group_submission($USER->id, 0, true);
-            } else {
-                $submission = $this->get_user_submission($USER->id, true);
-            }
-
-            if ($submission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
-                // Give each submission plugin a chance to process the submission.
-                $plugins = $this->get_submission_plugins();
-                foreach ($plugins as $plugin) {
-                    if ($plugin->is_enabled() && $plugin->is_visible()) {
-                        $plugin->submit_for_grading($submission);
-                    }
-                }
-
-                $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
-                $this->update_submission($submission, $USER->id, true, $instance->teamsubmission);
-                $completion = new completion_info($this->get_course());
-                if ($completion->is_enabled($this->get_course_module()) && $instance->completionsubmit) {
-                    $completion->update_state($this->get_course_module(), COMPLETION_COMPLETE, $USER->id);
-                }
-
-                if (isset($data->submissionstatement)) {
-                    $logmessage = get_string('submissionstatementacceptedlog',
-                                             'mod_assign',
-                                             fullname($USER));
-                    $addtolog = $this->add_to_log('submission statement accepted', $logmessage, '', true);
-                    $params = array(
-                        'context' => $this->context,
-                        'objectid' => $submission->id
-                    );
-                    $event = \mod_assign\event\statement_accepted::create($params);
-                    $event->set_legacy_logdata($addtolog);
-                    $event->trigger();
-                }
-                $logdata = $this->add_to_log('submit for grading', $this->format_submission_for_log($submission), '', true);
-                $this->notify_graders($submission);
-                $this->notify_student_submission_receipt($submission);
-
-                // Trigger assessable_submitted event on submission.
-                $params = array(
-                    'context' => context_module::instance($this->get_course_module()->id),
-                    'objectid' => $submission->id,
-                    'other' => array(
-                        'submission_editable' => false
-                    )
-                );
-                $event = \mod_assign\event\assessable_submitted::create($params);
-                $event->set_legacy_logdata($logdata);
-                $event->trigger();
-            }
+            return $this->submit_for_grading($data);
         }
         return true;
     }
@@ -4565,9 +4596,30 @@ class assign {
      * @param mixed $extensionduedate Either an integer date or null
      * @return boolean
      */
-    protected function save_user_extension($userid, $extensionduedate) {
+    public function save_user_extension($userid, $extensionduedate) {
         global $DB;
 
+        // Need submit permission to submit an assignment.
+        require_capability('mod/assign:grantextension', $this->context);
+
+        if (!is_enrolled($this->get_course_context(), $userid)) {
+            return false;
+        }
+        if (!has_capability('mod/assign:submit', $this->context, $userid)) {
+            return false;
+        }
+
+        if ($this->get_instance()->duedate && $extensionduedate) {
+            if ($this->get_instance()->duedate > $extensionduedate) {
+                return false;
+            }
+        }
+        if ($this->get_instance()->allowsubmissionsfromdate && $extensionduedate) {
+            if ($this->get_instance()->allowsubmissionsfromdate > $extensionduedate) {
+                return false;
+            }
+        }
+
         $flags = $this->get_user_flags($userid, true);
         $flags->extensionduedate = $extensionduedate;
 
@@ -4599,9 +4651,6 @@ class assign {
         // Include extension form.
         require_once($CFG->dirroot . '/mod/assign/extensionform.php');
 
-        // Need submit permission to submit an assignment.
-        require_capability('mod/assign:grantextension', $this->context);
-
         $batchusers = optional_param('selectedusers', '', PARAM_SEQUENCE);
         $userid = 0;
         if (!$batchusers) {
@@ -4834,11 +4883,12 @@ class assign {
      *
      * @return void
      */
-    protected function process_reveal_identities() {
-        global $DB, $CFG;
+    public function reveal_identities() {
+        global $DB;
 
         require_capability('mod/assign:revealidentities', $this->context);
-        if (!confirm_sesskey()) {
+
+        if ($this->get_instance()->revealidentities || empty($this->get_instance()->blindmarking)) {
             return false;
         }
 
@@ -4882,6 +4932,20 @@ class assign {
         $event->trigger();
     }
 
+    /**
+     * Reveal student identities to markers (and the gradebook).
+     *
+     * @return void
+     */
+    protected function process_reveal_identities() {
+
+        if (!confirm_sesskey()) {
+            return false;
+        }
+
+        return $this->reveal_identities();
+    }
+
 
     /**
      * Save grading options.
@@ -5009,15 +5073,30 @@ class assign {
     }
 
     /**
-     * Copy the current assignment submission from the last submitted attempt.
+     * Require a valid sess key and then call copy_previous_attempt.
      *
      * @param  array $notices Any error messages that should be shown
      *                        to the user at the top of the edit submission form.
      * @return bool
      */
     protected function process_copy_previous_attempt(&$notices) {
+        require_sesskey();
+
+        return copy_previous_attempt($notices);
+    }
+
+    /**
+     * Copy the current assignment submission from the last submitted attempt.
+     *
+     * @param  array $notices Any error messages that should be shown
+     *                        to the user at the top of the edit submission form.
+     * @return bool
+     */
+    public function copy_previous_attempt(&$notices) {
         global $USER, $CFG;
 
+        require_capability('mod/assign:submit', $this->context);
+
         $instance = $this->get_instance();
         if ($instance->teamsubmission) {
             $submission = $this->get_group_submission($USER->id, 0, true);
@@ -5128,121 +5207,137 @@ class assign {
     }
 
     /**
-     * Save assignment submission.
+     * Save assignment submission for the current user.
      *
-     * @param  moodleform $mform
+     * @param  stdClass $data
      * @param  array $notices Any error messages that should be shown
-     *                        to the user at the top of the edit submission form.
+     *                        to the user.
      * @return bool
      */
-    protected function process_save_submission(&$mform, &$notices) {
-        global $USER, $CFG;
-
-        // Include submission form.
-        require_once($CFG->dirroot . '/mod/assign/submission_form.php');
+    public function save_submission(stdClass $data, & $notices) {
+        global $CFG, $USER;
 
-        // Need submit permission to submit an assignment.
         require_capability('mod/assign:submit', $this->context);
-        require_sesskey();
-        if (!$this->submissions_open()) {
-            $notices[] = get_string('duedatereached', 'assign');
-            return false;
-        }
         $instance = $this->get_instance();
 
-        $data = new stdClass();
-        $mform = new mod_assign_submission_form(null, array($this, $data));
-        if ($mform->is_cancelled()) {
-            return true;
+        if ($instance->teamsubmission) {
+            $submission = $this->get_group_submission($USER->id, 0, true);
+        } else {
+            $submission = $this->get_user_submission($USER->id, true);
+        }
+        if ($instance->submissiondrafts) {
+            $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
+        } else {
+            $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
         }
-        if ($data = $mform->get_data()) {
-            if ($instance->teamsubmission) {
-                $submission = $this->get_group_submission($USER->id, 0, true);
-            } else {
-                $submission = $this->get_user_submission($USER->id, true);
-            }
-            if ($instance->submissiondrafts) {
-                $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
-            } else {
-                $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
-            }
 
-            $flags = $this->get_user_flags($USER->id, false);
+        $flags = $this->get_user_flags($USER->id, false);
 
-            // Get the flags to check if it is locked.
-            if ($flags && $flags->locked) {
-                print_error('submissionslocked', 'assign');
-                return true;
-            }
+        // Get the flags to check if it is locked.
+        if ($flags && $flags->locked) {
+            print_error('submissionslocked', 'assign');
+            return true;
+        }
 
-            $pluginerror = false;
-            foreach ($this->submissionplugins as $plugin) {
-                if ($plugin->is_enabled() && $plugin->is_visible()) {
-                    if (!$plugin->save($submission, $data)) {
-                        $notices[] = $plugin->get_error();
-                        $pluginerror = true;
-                    }
+        $pluginerror = false;
+        foreach ($this->submissionplugins as $plugin) {
+            if ($plugin->is_enabled() && $plugin->is_visible()) {
+                if (!$plugin->save($submission, $data)) {
+                    $notices[] = $plugin->get_error();
+                    $pluginerror = true;
                 }
             }
-            $allempty = $this->submission_empty($submission);
-            if ($pluginerror || $allempty) {
-                if ($allempty) {
-                    $notices[] = get_string('submissionempty', 'mod_assign');
-                }
-                return false;
+        }
+        $allempty = $this->submission_empty($submission);
+        if ($pluginerror || $allempty) {
+            if ($allempty) {
+                $notices[] = get_string('submissionempty', 'mod_assign');
             }
+            return false;
+        }
 
-            $this->update_submission($submission, $USER->id, true, $instance->teamsubmission);
-
-            // Logging.
-            if (isset($data->submissionstatement)) {
-                $logmessage = get_string('submissionstatementacceptedlog',
-                                         'mod_assign',
-                                         fullname($USER));
-                $addtolog = $this->add_to_log('submission statement accepted', $logmessage, '', true);
-                $params = array(
-                    'context' => $this->context,
-                    'objectid' => $submission->id
-                );
-                $event = \mod_assign\event\statement_accepted::create($params);
-                $event->set_legacy_logdata($addtolog);
-                $event->trigger();
-            }
+        $this->update_submission($submission, $USER->id, true, $instance->teamsubmission);
 
-            $addtolog = $this->add_to_log('submit', $this->format_submission_for_log($submission), '', true);
+        // Logging.
+        if (isset($data->submissionstatement)) {
+            $logmessage = get_string('submissionstatementacceptedlog',
+                                     'mod_assign',
+                                     fullname($USER));
+            $this->add_to_log('submission statement accepted', $logmessage);
+            $addtolog = $this->add_to_log('submission statement accepted', $logmessage, '', true);
             $params = array(
                 'context' => $this->context,
                 'objectid' => $submission->id
             );
-            $event = \mod_assign\event\submission_updated::create($params);
+            $event = \mod_assign\event\statement_accepted::create($params);
             $event->set_legacy_logdata($addtolog);
             $event->trigger();
+        }
+        $addtolog = $this->add_to_log('submit', $this->format_submission_for_log($submission), '', true);
+        $params = array(
+            'context' => $this->context,
+            'objectid' => $submission->id
+        );
+        $event = \mod_assign\event\submission_updated::create($params);
+        $event->set_legacy_logdata($addtolog);
+        $event->trigger();
 
-            $complete = COMPLETION_INCOMPLETE;
-            if ($submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
-                $complete = COMPLETION_COMPLETE;
-            }
-            $completion = new completion_info($this->get_course());
-            if ($completion->is_enabled($this->get_course_module()) && $instance->completionsubmit) {
-                $completion->update_state($this->get_course_module(), $complete, $USER->id);
-            }
+        $complete = COMPLETION_INCOMPLETE;
+        if ($submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
+            $complete = COMPLETION_COMPLETE;
+        }
+        $completion = new completion_info($this->get_course());
+        if ($completion->is_enabled($this->get_course_module()) && $instance->completionsubmit) {
+            $completion->update_state($this->get_course_module(), $complete, $USER->id);
+        }
 
-            if (!$instance->submissiondrafts) {
-                $this->notify_student_submission_receipt($submission);
-                $this->notify_graders($submission);
-                // Trigger assessable_submitted event on submission.
-                $params = array(
-                    'context' => context_module::instance($this->get_course_module()->id),
-                    'objectid' => $submission->id,
-                    'other' => array(
-                        'submission_editable' => true
-                    )
-                );
-                $event = \mod_assign\event\assessable_submitted::create($params);
-                $event->trigger();
-            }
+        if (!$instance->submissiondrafts) {
+            $this->notify_student_submission_receipt($submission);
+            $this->notify_graders($submission);
+            // Trigger assessable_submitted event on submission.
+            $params = array(
+                'context' => context_module::instance($this->get_course_module()->id),
+                'objectid' => $submission->id,
+                'other' => array(
+                    'submission_editable' => true
+                )
+            );
+            $event = \mod_assign\event\assessable_submitted::create($params);
+            $event->trigger();
+        }
+        return true;
+    }
+
+    /**
+     * Save assignment submission.
+     *
+     * @param  moodleform $mform
+     * @param  array $notices Any error messages that should be shown
+     *                        to the user at the top of the edit submission form.
+     * @return bool
+     */
+    protected function process_save_submission(&$mform, &$notices) {
+        global $CFG;
+
+        // Include submission form.
+        require_once($CFG->dirroot . '/mod/assign/submission_form.php');
+
+        // Need submit permission to submit an assignment.
+        require_sesskey();
+        if (!$this->submissions_open()) {
+            $notices[] = get_string('duedatereached', 'assign');
+            return false;
+        }
+        $instance = $this->get_instance();
+
+        $data = new stdClass();
+        $mform = new mod_assign_submission_form(null, array($this, $data));
+        if ($mform->is_cancelled()) {
             return true;
         }
+        if ($data = $mform->get_data()) {
+            return $this->save_submission($data, $notices);
+        }
         return false;
     }
 
@@ -5286,6 +5381,7 @@ class assign {
      * This is specific to the assignment, marker and student.
      *
      * @param int $userid - The student userid
+     * @param stdClass|false $grade - The grade record
      * @param bool $gradingdisabled
      * @return mixed gradingform_instance|null $gradinginstance
      */
@@ -5677,21 +5773,15 @@ class assign {
 
     /**
      * Revert to draft.
-     * Uses url parameter userid
      *
      * @param int $userid
-     * @return void
+     * @return boolean
      */
-    protected function process_revert_to_draft($userid = 0) {
+    public function revert_to_draft($userid) {
         global $DB, $USER;
 
         // Need grade permission.
         require_capability('mod/assign:grade', $this->context);
-        require_sesskey();
-
-        if (!$userid) {
-            $userid = required_param('userid', PARAM_INT);
-        }
 
         if ($this->get_instance()->teamsubmission) {
             $submission = $this->get_group_submission($userid, 0, false);
@@ -5700,7 +5790,7 @@ class assign {
         }
 
         if (!$submission) {
-            return;
+            return false;
         }
         $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
         $this->update_submission($submission, $userid, true, $this->get_instance()->teamsubmission);
@@ -5739,39 +5829,51 @@ class assign {
         $event = \mod_assign\event\submission_status_updated::create($params);
         $event->set_legacy_logdata($addtolog);
         $event->trigger();
+        return true;
     }
 
     /**
-     * Lock the process.
-     * Uses url parameter userid
+     * Revert to draft.
+     * Uses url parameter userid if userid not supplied as a parameter.
      *
      * @param int $userid
-     * @return void
+     * @return boolean
      */
-    protected function process_lock($userid = 0) {
-        global $USER, $DB;
-
-        // Need grade permission.
-        require_capability('mod/assign:grade', $this->context);
+    protected function process_revert_to_draft($userid = 0) {
         require_sesskey();
 
         if (!$userid) {
             $userid = required_param('userid', PARAM_INT);
         }
 
+        return $this->revert_to_draft($userid);
+    }
+
+    /**
+     * Prevent student updates to this submission
+     *
+     * @param int $userid
+     * @return bool
+     */
+    public function lock_submission($userid) {
+        global $USER, $DB;
+        // Need grade permission.
+        require_capability('mod/assign:grade', $this->context);
+
         // Give each submission plugin a chance to process the locking.
         $plugins = $this->get_submission_plugins();
         $submission = $this->get_user_submission($userid, false);
-        foreach ($plugins as $plugin) {
-            if ($plugin->is_enabled() && $plugin->is_visible()) {
-                $plugin->lock($submission);
-            }
-        }
 
         $flags = $this->get_user_flags($userid, true);
         $flags->locked = 1;
         $this->update_user_flags($flags);
 
+        foreach ($plugins as $plugin) {
+            if ($plugin->is_enabled() && $plugin->is_visible()) {
+                $plugin->lock($submission, $flags);
+            }
+        }
+
         $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
 
         $logmessage = get_string('locksubmissionforstudent',
@@ -5786,6 +5888,7 @@ class assign {
         $event = \mod_assign\event\submission_locked::create($params);
         $event->set_legacy_logdata($addtolog);
         $event->trigger();
+        return true;
     }
 
 
@@ -5896,34 +5999,49 @@ class assign {
 
 
     /**
-     * Unlock the process.
+     * Prevent student updates to this submission.
+     * Uses url parameter userid.
      *
      * @param int $userid
      * @return void
      */
-    protected function process_unlock($userid = 0) {
-        global $USER, $DB;
+    protected function process_lock_submission($userid = 0) {
 
-        // Need grade permission.
-        require_capability('mod/assign:grade', $this->context);
         require_sesskey();
 
         if (!$userid) {
             $userid = required_param('userid', PARAM_INT);
         }
+
+        return $this->lock_submission($userid);
+    }
+
+    /**
+     * Unlock the student submission.
+     *
+     * @param int $userid
+     * @return bool
+     */
+    public function unlock_submission($userid) {
+        global $USER, $DB;
+
+        // Need grade permission.
+        require_capability('mod/assign:grade', $this->context);
+
         // Give each submission plugin a chance to process the unlocking.
         $plugins = $this->get_submission_plugins();
         $submission = $this->get_user_submission($userid, false);
-        foreach ($plugins as $plugin) {
-            if ($plugin->is_enabled() && $plugin->is_visible()) {
-                $plugin->unlock($submission);
-            }
-        }
 
         $flags = $this->get_user_flags($userid, true);
         $flags->locked = 0;
         $this->update_user_flags($flags);
 
+        foreach ($plugins as $plugin) {
+            if ($plugin->is_enabled() && $plugin->is_visible()) {
+                $plugin->unlock($submission, $flags);
+            }
+        }
+
         $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
 
         $logmessage = get_string('unlocksubmissionforstudent',
@@ -5938,6 +6056,25 @@ class assign {
         $event = \mod_assign\event\submission_unlocked::create($params);
         $event->set_legacy_logdata($addtolog);
         $event->trigger();
+        return true;
+    }
+
+    /**
+     * Unlock the student submission.
+     * Uses url parameter userid.
+     *
+     * @param int $userid
+     * @return bool
+     */
+    protected function process_unlock_submission($userid = 0) {
+
+        require_sesskey();
+
+        if (!$userid) {
+            $userid = required_param('userid', PARAM_INT);
+        }
+
+        return $this->unlock_submission($userid);
     }
 
     /**
@@ -5945,7 +6082,7 @@ class assign {
      *
      * @param stdClass $formdata - the data from the form
      * @param int $userid - the user to apply the grade to
-     * @param int attemptnumber - The attempt number to apply the grade to.
+     * @param int $attemptnumber - The attempt number to apply the grade to.
      * @return void
      */
     protected function apply_grade_to_user($formdata, $userid, $attemptnumber) {
@@ -6051,6 +6188,92 @@ class assign {
     }
 
 
+    /**
+     * Save grade update.
+     *
+     * @param int $userid
+     * @param  stdClass $data
+     * @return bool - was the grade saved
+     */
+    public function save_grade($userid, $data) {
+
+        // Need grade permission.
+        require_capability('mod/assign:grade', $this->context);
+
+        $instance = $this->get_instance();
+        $submission = null;
+        if ($instance->teamsubmission) {
+            $submission = $this->get_group_submission($userid, 0, false, $data->attemptnumber);
+        } else {
+            $submission = $this->get_user_submission($userid, false, $data->attemptnumber);
+        }
+        if ($instance->teamsubmission && $data->applytoall) {
+            $groupid = 0;
+            if ($this->get_submission_group($userid)) {
+                $group = $this->get_submission_group($userid);
+                if ($group) {
+                    $groupid = $group->id;
+                }
+            }
+            $members = $this->get_submission_group_members($groupid, true);
+            foreach ($members as $member) {
+                // User may exist in multple groups (which should put them in the default group).
+                $this->apply_grade_to_user($data, $member->id, $data->attemptnumber);
+                $this->process_outcomes($member->id, $data);
+            }
+        } else {
+            $this->apply_grade_to_user($data, $userid, $data->attemptnumber);
+
+            $this->process_outcomes($userid, $data);
+        }
+        $maxattemptsreached = !empty($submission) &&
+                              $submission->attemptnumber >= ($instance->maxattempts - 1) &&
+                              $instance->maxattempts != ASSIGN_UNLIMITED_ATTEMPTS;
+        $shouldreopen = false;
+        if ($instance->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS) {
+            // Check the gradetopass from the gradebook.
+            $gradinginfo = grade_get_grades($this->get_course()->id,
+                                            'mod',
+                                            'assign',
+                                            $instance->id,
+                                            $userid);
+
+            // What do we do if the grade has not been added to the gradebook (e.g. blind marking)?
+            $gradingitem = null;
+            $gradebookgrade = null;
+            if (isset($gradinginfo->items[0])) {
+                $gradingitem = $gradinginfo->items[0];
+                $gradebookgrade = $gradingitem->grades[$userid];
+            }
+
+            if ($gradebookgrade) {
+                // TODO: This code should call grade_grade->is_passed().
+                $shouldreopen = true;
+                if (is_null($gradebookgrade->grade)) {
+                    $shouldreopen = false;
+                }
+                if (empty($gradingitem->gradepass) || $gradingitem->gradepass == $gradingitem->grademin) {
+                    $shouldreopen = false;
+                }
+                if ($gradebookgrade->grade >= $gradingitem->gradepass) {
+                    $shouldreopen = false;
+                }
+            }
+        }
+        if ($instance->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL &&
+                !empty($data->addattempt)) {
+            $shouldreopen = true;
+        }
+        // Never reopen if we are editing a previous attempt.
+        if ($data->attemptnumber != -1) {
+            $shouldreopen = false;
+        }
+        if ($shouldreopen && !$maxattemptsreached) {
+            $this->add_attempt($userid);
+        }
+        return true;
+    }
+
     /**
      * Save grade.
      *
@@ -6062,8 +6285,6 @@ class assign {
         // Include grade form.
         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
 
-        // Need submit permission to submit an assignment.
-        require_capability('mod/assign:grade', $this->context);
         require_sesskey();
 
         $instance = $this->get_instance();
@@ -6102,80 +6323,10 @@ class assign {
                                            array('class'=>'gradeform'));
 
         if ($formdata = $mform->get_data()) {
-            $submission = null;
-            if ($instance->teamsubmission) {
-                $submission = $this->get_group_submission($userid, 0, false, $attemptnumber);
-            } else {
-                $submission = $this->get_user_submission($userid, false, $attemptnumber);
-            }
-            if ($instance->teamsubmission && $formdata->applytoall) {
-                $groupid = 0;
-                if ($this->get_submission_group($userid)) {
-                    $group = $this->get_submission_group($userid);
-                    if ($group) {
-                        $groupid = $group->id;
-                    }
-                }
-                $members = $this->get_submission_group_members($groupid, true);
-                foreach ($members as $member) {
-                    // User may exist in multple groups (which should put them in the default group).
-                    $this->apply_grade_to_user($formdata, $member->id, $attemptnumber);
-                    $this->process_outcomes($member->id, $formdata);
-                }
-            } else {
-                $this->apply_grade_to_user($formdata, $userid, $attemptnumber);
-
-                $this->process_outcomes($userid, $formdata);
-            }
-            $maxattemptsreached = !empty($submission) &&
-                                  $submission->attemptnumber >= ($instance->maxattempts - 1) &&
-                                  $instance->maxattempts != ASSIGN_UNLIMITED_ATTEMPTS;
-            $shouldreopen = false;
-            if ($instance->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS) {
-                // Check the gradetopass from the gradebook.
-                $gradinginfo = grade_get_grades($this->get_course()->id,
-                                                'mod',
-                                                'assign',
-                                                $instance->id,
-                                                $userid);
-
-                // What do we do if the grade has not been added to the gradebook (e.g. blind marking)?
-                $gradingitem = null;
-                $gradebookgrade = null;
-                if (isset($gradinginfo->items[0])) {
-                    $gradingitem = $gradinginfo->items[0];
-                    $gradebookgrade = $gradingitem->grades[$userid];
-                }
-
-                if ($gradebookgrade) {
-                    // TODO: This code should call grade_grade->is_passed().
-                    $shouldreopen = true;
-                    if (is_null($gradebookgrade->grade)) {
-                        $shouldreopen = false;
-                    }
-                    if (empty($gradingitem->gradepass) || $gradingitem->gradepass == $gradingitem->grademin) {
-                        $shouldreopen = false;
-                    }
-                    if ($gradebookgrade->grade >= $gradingitem->gradepass) {
-                        $shouldreopen = false;
-                    }
-                }
-            }
-            if ($instance->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL &&
-                    !empty($formdata->addattempt)) {
-                $shouldreopen = true;
-            }
-            // Never reopen if we are editing a previous attempt.
-            if ($attemptnumber != -1) {
-                $shouldreopen = false;
-            }
-            if ($shouldreopen && !$maxattemptsreached) {
-                $this->process_add_attempt($userid);
-            }
+            return $this->save_grade($userid, $formdata);
         } else {
             return false;
         }
-        return true;
     }
 
     /**
@@ -6288,15 +6439,26 @@ class assign {
     }
 
     /**
-     * Add a new attempt for a user.
+     * Check for a sess key and then call add_attempt.
      *
      * @param int $userid int The user to add the attempt for
      * @return bool - true if successful.
      */
     protected function process_add_attempt($userid) {
-        require_capability('mod/assign:grade', $this->context);
         require_sesskey();
 
+        return $this->add_attempt($userid);
+    }
+
+    /**
+     * Add a new attempt for a user.
+     *
+     * @param int $userid int The user to add the attempt for
+     * @return bool - true if successful.
+     */
+    protected function add_attempt($userid) {
+        require_capability('mod/assign:grade', $this->context);
+
         if ($this->get_instance()->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_NONE) {
             return false;
         }
@@ -6457,6 +6619,7 @@ class assign {
     /**
      * Lookup this user id and return the unique id for this assignment.
      *
+     * @param int $assignid The assignment id
      * @param int $userid The userid to lookup
      * @return int The unique id
      */
index edfe6eb..1cd183c 100644 (file)
@@ -265,6 +265,11 @@ class mod_assign_mod_form extends moodleform_mod {
         $assignment->plugin_data_preprocessing($defaultvalues);
     }
 
+    /**
+     * Add any custom completion rules to the form.
+     *
+     * @return array Contains the names of the added form elements
+     */
     public function add_completion_rules() {
         $mform =& $this->_form;
 
@@ -272,6 +277,12 @@ class mod_assign_mod_form extends moodleform_mod {
         return array('completionsubmit');
     }
 
+    /**
+     * Determines if completion is enabled for this module.
+     *
+     * @param array $data
+     * @return bool
+     */
     public function completion_rule_enabled($data) {
         return !empty($data['completionsubmit']);
     }
index 5e40398..6b03323 100644 (file)
@@ -42,6 +42,7 @@ class assign_submit_for_grading_page implements renderable {
      * Constructor
      * @param string $notifications - Any mesages to display
      * @param int $coursemoduleid
+     * @param moodleform $confirmform
      */
     public function __construct($notifications, $coursemoduleid, $confirmform) {
         $this->notifications = $notifications;
@@ -69,6 +70,7 @@ class assign_gradingmessage implements renderable {
      * Constructor
      * @param string $heading This is the heading to display
      * @param string $message This is the message to display
+     * @param int $coursemoduleid
      */
     public function __construct($heading, $message, $coursemoduleid) {
         $this->heading = $heading;
@@ -404,6 +406,7 @@ class assign_submission_status implements renderable {
      * @param int $extensionduedate - Any extension to the due date granted for this user
      * @param context $context - Any extension to the due date granted for this user
      * @param bool $blindmarking - Should we hide student identities from graders?
+     * @param string $gradingcontrollerpreview
      * @param string $attemptreopenmethod - The method of reopening student attempts.
      * @param int $maxattempts - How many attempts can a student make?
      */
@@ -493,14 +496,14 @@ class assign_attempt_history implements renderable {
     /**
      * Constructor
      *
-     * @param $submissions
-     * @param $grades
-     * @param $submissionplugins
-     * @param $feedbackplugins
-     * @param $coursemoduleid
-     * @param $returnaction
-     * @param $returnparams
-     * @param $cangrade
+     * @param array $submissions
+     * @param array $grades
+     * @param array $submissionplugins
+     * @param array $feedbackplugins
+     * @param int $coursemoduleid
+     * @param string $returnaction
+     * @param array $returnparams
+     * @param bool $cangrade
      */
     public function __construct($submissions,
                                 $grades,
@@ -648,8 +651,8 @@ class assign_course_index_summary implements renderable {
     /**
      * constructor
      *
-     * @param $usesections boolean - True if this course format uses sections
-     * @param $courseformatname string - The id of this course format
+     * @param boolean $usesections - True if this course format uses sections
+     * @param string $courseformatname - The id of this course format
      */
     public function __construct($usesections, $courseformatname) {
         $this->usesections = $usesections;
index f3a0306..f9058c8 100644 (file)
@@ -221,14 +221,14 @@ class mod_assign_renderer extends plugin_renderer_base {
         }
 
         $this->page->set_title(get_string('pluginname', 'assign'));
-        $this->page->set_heading($header->assign->name);
+        $this->page->set_heading($this->page->course->fullname);
 
         $o .= $this->output->header();
+        $heading = format_string($header->assign->name, false, array('context' => $header->context));
+        $o .= $this->output->heading($heading);
         if ($header->preface) {
             $o .= $header->preface;
         }
-        $heading = format_string($header->assign->name, false, array('context' => $header->context));
-        $o .= $this->output->heading($heading);
 
         if ($header->showintro) {
             $o .= $this->output->box_start('generalbox boxaligncenter', 'intro');
index 8f575e5..2447270 100644 (file)
@@ -23,7 +23,9 @@
  */
 
 /**
- * restore subplugin class that provides the necessary information
+ * Restore subplugin class.
+ *
+ * Provides the necessary information
  * needed to restore one assign_submission subplugin.
  *
  * @package assignsubmission_file
index e9d72e4..a2c2c8b 100644 (file)
@@ -249,6 +249,7 @@ class assign_submission_file extends assign_submission_plugin {
      * Produce a list of files suitable for export that represent this feedback or submission
      *
      * @param stdClass $submission The submission
+     * @param stdClass $user The user record - unused
      * @return array - return an array of files indexed by filename
      */
     public function get_files(stdClass $submission, stdClass $user) {
@@ -477,4 +478,18 @@ class assign_submission_file extends assign_submission_plugin {
         }
         return true;
     }
+
+    /**
+     * Return a description of external params suitable for uploading a file submission from a webservice.
+     *
+     * @return external_description|null
+     */
+    public function get_external_parameters() {
+        return array(
+            'files_filemanager' => new external_value(
+                PARAM_INT,
+                'The id of a draft area containing files for this submission.'
+            )
+        );
+    }
 }
index bfa2342..ac97586 100644 (file)
@@ -23,7 +23,9 @@
  */
 
 /**
- * restore subplugin class that provides the necessary information needed to restore
+ * Restore subplugin class.
+ *
+ * Provides the necessary information needed to restore
  * one assign_submission subplugin.
  *
  * @package assignsubmission_onlinetext
index 6f44e4e..87fcc19 100644 (file)
@@ -495,6 +495,20 @@ class assign_submission_onlinetext extends assign_submission_plugin {
         }
         return true;
     }
+
+    /**
+     * Return a description of external params suitable for uploading an onlinetext submission from a webservice.
+     *
+     * @return external_description|null
+     */
+    public function get_external_parameters() {
+        $editorparams = array('text' => new external_value(PARAM_TEXT, 'The text for this submission.'),
+                              'format' => new external_value(PARAM_INT, 'The format for this submission'),
+                              'itemid' => new external_value(PARAM_INT, 'The draft area id for files attached to the submission'));
+        $editorstructure = new external_single_structure($editorparams);
+        return array('onlinetext_editor' => $editorstructure);
+    }
+
 }
 
 
index 5b48a38..70388cb 100644 (file)
@@ -79,28 +79,32 @@ abstract class assign_submission_plugin extends assign_plugin {
     /**
      * Copy the plugin specific submission data to a new submission record.
      *
+     * @param stdClass $oldsubmission - Old submission record
+     * @param stdClass $submission - New submission record
      * @return bool
      */
     public function copy_submission( stdClass $oldsubmission, stdClass $submission) {
         return true;
     }
 
-    /*
+    /**
      * Carry out any extra processing required when the work is locked.
      *
-     * @param stdClass $submission - assign_submission data
+     * @param stdClass|false $submission - assign_submission data if any
+     * @param stdClass $flags - User flags record
      * @return void
      */
-    public function lock(stdClass $submission) {
+    public function lock($submission, stdClass $flags) {
     }
 
     /**
      * Carry out any extra processing required when the work is unlocked.
      *
-     * @param stdClass $submission - assign_submission data
+     * @param stdClass|false $submission - assign_submission data if any
+     * @param stdClass $flags - User flags record
      * @return void
      */
-    public function unlock(stdClass $submission) {
+    public function unlock($submission, stdClass $flags) {
     }
 
     /**
index cae9553..9155540 100644 (file)
@@ -223,10 +223,6 @@ class mod_assign_base_testcase extends advanced_testcase {
  */
 class testable_assign extends assign {
 
-    public function testable_process_reveal_identities() {
-        return parent::process_reveal_identities();
-    }
-
     public function testable_show_intro() {
         return parent::show_intro();
     }
@@ -259,10 +255,6 @@ class testable_assign extends assign {
         return parent::process_add_attempt($userid);
     }
 
-    public function testable_process_lock($userid = 0) {
-        return parent::process_lock($userid);
-    }
-
     public function testable_process_save_quick_grades($postdata) {
         // Ugly hack to get something into the method.
         global $_POST;
@@ -270,18 +262,6 @@ class testable_assign extends assign {
         return parent::process_save_quick_grades();
     }
 
-    public function testable_process_unlock($userid = 0) {
-        return parent::process_unlock($userid);
-    }
-
-    public function testable_process_copy_previous_attempt(&$notices) {
-        return parent::process_copy_previous_attempt($notices);
-    }
-
-    public function testable_process_revert_to_draft($userid = 0) {
-        return parent::process_revert_to_draft($userid);
-    }
-
     public function testable_process_set_batch_marking_allocation($selectedusers, $markerid) {
         // Ugly hack to get something into the method.
         global $_POST;
index 88a4aa2..eebc750 100644 (file)
@@ -41,7 +41,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase {
     /**
      * Test get_grades
      */
-    public function test_get_grades () {
+    public function test_get_grades() {
         global $DB, $USER;
 
         $this->resetAfterTest(true);
@@ -118,7 +118,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase {
     /**
      * Test get_assignments
      */
-    public function test_get_assignments () {
+    public function test_get_assignments() {
         global $DB, $USER;
 
         $this->resetAfterTest(true);
@@ -218,7 +218,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase {
     /**
      * Test get_submissions
      */
-    public function test_get_submissions () {
+    public function test_get_submissions() {
         global $DB, $USER;
 
         $this->resetAfterTest(true);
@@ -307,7 +307,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase {
     /**
      * Test get_user_flags
      */
-    public function test_get_user_flags () {
+    public function test_get_user_flags() {
         global $DB, $USER;
 
         $this->resetAfterTest(true);
@@ -378,7 +378,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase {
     /**
      * Test get_user_mappings
      */
-    public function test_get_user_mappings () {
+    public function test_get_user_mappings() {
         global $DB, $USER;
 
         $this->resetAfterTest(true);
@@ -436,4 +436,611 @@ class mod_assign_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals($student->id, $mapping['userid']);
     }
 
+    /**
+     * Test lock_submissions
+     */
+    public function test_lock_submissions() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['assignsubmission_onlinetext_enabled'] = 1;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $student2 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+        $this->getDataGenerator()->enrol_user($student2->id,
+                                              $course->id,
+                                              $studentrole->id);
+        $teacher = self::getDataGenerator()->create_user();
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->getDataGenerator()->enrol_user($teacher->id,
+                                              $course->id,
+                                              $teacherrole->id);
+
+        // Create a student1 with an online text submission.
+        // Simulate a submission.
+        $this->setUser($student1);
+        $submission = $assign->get_user_submission($student1->id, true);
+        $data = new stdClass();
+        $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
+                                         'text'=>'Submission text',
+                                         'format'=>FORMAT_MOODLE);
+        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
+        $plugin->save($submission, $data);
+
+        // Ready to test.
+        $this->setUser($teacher);
+        $students = array($student1->id, $student2->id);
+        $result = mod_assign_external::lock_submissions($instance->id, $students);
+
+        // Check for 0 warnings.
+        $this->assertEquals(0, count($result));
+
+        $this->setUser($student2);
+        $submission = $assign->get_user_submission($student2->id, true);
+        $data = new stdClass();
+        $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
+                                         'text'=>'Submission text',
+                                         'format'=>FORMAT_MOODLE);
+        $notices = array();
+        $this->setExpectedException('moodle_exception');
+        $assign->save_submission($data, $notices);
+    }
+
+    /**
+     * Test unlock_submissions
+     */
+    public function test_unlock_submissions() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['assignsubmission_onlinetext_enabled'] = 1;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $student2 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+        $this->getDataGenerator()->enrol_user($student2->id,
+                                              $course->id,
+                                              $studentrole->id);
+        $teacher = self::getDataGenerator()->create_user();
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->getDataGenerator()->enrol_user($teacher->id,
+                                              $course->id,
+                                              $teacherrole->id);
+
+        // Create a student1 with an online text submission.
+        // Simulate a submission.
+        $this->setUser($student1);
+        $submission = $assign->get_user_submission($student1->id, true);
+        $data = new stdClass();
+        $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
+                                         'text'=>'Submission text',
+                                         'format'=>FORMAT_MOODLE);
+        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
+        $plugin->save($submission, $data);
+
+        // Ready to test.
+        $this->setUser($teacher);
+        $students = array($student1->id, $student2->id);
+        $result = mod_assign_external::lock_submissions($instance->id, $students);
+
+        // Check for 0 warnings.
+        $this->assertEquals(0, count($result));
+
+        $result = mod_assign_external::unlock_submissions($instance->id, $students);
+
+        // Check for 0 warnings.
+        $this->assertEquals(0, count($result));
+
+        $this->setUser($student2);
+        $submission = $assign->get_user_submission($student2->id, true);
+        $data = new stdClass();
+        $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
+                                         'text'=>'Submission text',
+                                         'format'=>FORMAT_MOODLE);
+        $notices = array();
+        $assign->save_submission($data, $notices);
+    }
+
+    /**
+     * Test submit_for_grading
+     */
+    public function test_submit_for_grading() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        set_config('submissionreceipts', 0, 'assign');
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['assignsubmission_onlinetext_enabled'] = 1;
+        $params['submissiondrafts'] = 1;
+        $params['sendnotifications'] = 0;
+        $params['requiresubmissionstatement'] = 1;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+
+        // Create a student1 with an online text submission.
+        // Simulate a submission.
+        $this->setUser($student1);
+        $submission = $assign->get_user_submission($student1->id, true);
+        $data = new stdClass();
+        $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
+                                         'text'=>'Submission text',
+                                         'format'=>FORMAT_MOODLE);
+        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
+        $plugin->save($submission, $data);
+
+        $result = mod_assign_external::submit_for_grading($instance->id, false);
+
+        // Should be 1 fail because the submission statement was not aceptted.
+        $this->assertEquals(1, count($result));
+
+        $result = mod_assign_external::submit_for_grading($instance->id, true);
+
+        // Check for 0 warnings.
+        $this->assertEquals(0, count($result));
+
+        $submission = $assign->get_user_submission($student1->id, false);
+
+        $this->assertEquals(ASSIGN_SUBMISSION_STATUS_SUBMITTED, $submission->status);
+    }
+
+    /**
+     * Test save_user_extensions
+     */
+    public function test_save_user_extensions() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        $teacher = self::getDataGenerator()->create_user();
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->getDataGenerator()->enrol_user($teacher->id,
+                                              $course->id,
+                                              $teacherrole->id);
+        $this->setUser($teacher);
+
+        $now = time();
+        $yesterday = $now - 24*60*60;
+        $tomorrow = $now + 24*60*60;
+        set_config('submissionreceipts', 0, 'assign');
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['submissiondrafts'] = 1;
+        $params['sendnotifications'] = 0;
+        $params['duedate'] = $yesterday;
+        $params['cutoffdate'] = $now - 10;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+
+        $this->setUser($student1);
+        $result = mod_assign_external::submit_for_grading($instance->id, true);
+
+        // Check for 0 warnings.
+        $this->assertEquals(1, count($result));
+
+        $this->setUser($teacher);
+        $result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($now, $tomorrow));
+        $this->assertEquals(1, count($result));
+
+        $this->setUser($teacher);
+        $result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($yesterday - 10));
+        $this->assertEquals(1, count($result));
+
+        $this->setUser($teacher);
+        $result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($tomorrow));
+        $this->assertEquals(0, count($result));
+
+        $this->setUser($student1);
+        $result = mod_assign_external::submit_for_grading($instance->id, true);
+        $this->assertEquals(0, count($result));
+
+        $this->setUser($student1);
+        $result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($now, $tomorrow));
+
+    }
+
+    /**
+     * Test reveal_identities
+     */
+    public function test_reveal_identities() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        $teacher = self::getDataGenerator()->create_user();
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->getDataGenerator()->enrol_user($teacher->id,
+                                              $course->id,
+                                              $teacherrole->id);
+        $this->setUser($teacher);
+
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['submissiondrafts'] = 1;
+        $params['sendnotifications'] = 0;
+        $params['blindmarking'] = 1;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+
+        $this->setUser($student1);
+        $this->setExpectedException('required_capability_exception');
+        $result = mod_assign_external::reveal_identities($instance->id);
+        $this->assertEquals(1, count($result));
+        $this->assertEquals(true, $assign->is_blind_marking());
+
+        $this->setUser($teacher);
+        $result = mod_assign_external::reveal_identities($instance->id);
+        $this->assertEquals(0, count($result));
+        $this->assertEquals(false, $assign->is_blind_marking());
+
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['submissiondrafts'] = 1;
+        $params['sendnotifications'] = 0;
+        $params['blindmarking'] = 0;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+        $result = mod_assign_external::reveal_identities($instance->id);
+        $this->assertEquals(1, count($result));
+        $this->assertEquals(false, $assign->is_blind_marking());
+
+    }
+
+    /**
+     * Test revert_submissions_to_draft
+     */
+    public function test_revert_submissions_to_draft() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        set_config('submissionreceipts', 0, 'assign');
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['sendnotifications'] = 0;
+        $params['submissiondrafts'] = 1;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $student2 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+        $this->getDataGenerator()->enrol_user($student2->id,
+                                              $course->id,
+                                              $studentrole->id);
+        $teacher = self::getDataGenerator()->create_user();
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->getDataGenerator()->enrol_user($teacher->id,
+                                              $course->id,
+                                              $teacherrole->id);
+
+        // Create a student1 with an online text submission.
+        // Simulate a submission.
+        $this->setUser($student1);
+        $result = mod_assign_external::submit_for_grading($instance->id, true);
+        $this->assertEquals(0, count($result));
+
+        // Ready to test.
+        $this->setUser($teacher);
+        $students = array($student1->id, $student2->id);
+        $result = mod_assign_external::revert_submissions_to_draft($instance->id, array($student1->id));
+
+        // Check for 0 warnings.
+        $this->assertEquals(0, count($result));
+
+    }
+
+    /**
+     * Test save_submission
+     */
+    public function test_save_submission() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        $teacher = self::getDataGenerator()->create_user();
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->getDataGenerator()->enrol_user($teacher->id,
+                                              $course->id,
+                                              $teacherrole->id);
+        $this->setUser($teacher);
+
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['assignsubmission_onlinetext_enabled'] = 1;
+        $params['assignsubmission_file_enabled'] = 1;
+        $params['assignsubmission_file_maxfiles'] = 5;
+        $params['assignsubmission_file_maxsizebytes'] = 1024*1024;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $student2 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+        $this->getDataGenerator()->enrol_user($student2->id,
+                                              $course->id,
+                                              $studentrole->id);
+        // Create a student1 with an online text submission.
+        // Simulate a submission.
+        $this->setUser($student1);
+
+        // Create a file in a draft area.
+        $draftidfile = file_get_unused_draft_itemid();
+
+        $usercontext = context_user::instance($student1->id);
+        $filerecord = array(
+            'contextid' => $usercontext->id,
+            'component' => 'user',
+            'filearea'  => 'draft',
+            'itemid'    => $draftidfile,
+            'filepath'  => '/',
+            'filename'  => 'testtext.txt',
+        );
+
+        $fs = get_file_storage();
+        $fs->create_file_from_string($filerecord, 'text contents');
+
+        // Create another file in a different draft area.
+        $draftidonlinetext = file_get_unused_draft_itemid();
+
+        $filerecord = array(
+            'contextid' => $usercontext->id,
+            'component' => 'user',
+            'filearea'  => 'draft',
+            'itemid'    => $draftidonlinetext,
+            'filepath'  => '/',
+            'filename'  => 'shouldbeanimage.txt',
+        );
+
+        $fs->create_file_from_string($filerecord, 'image contents (not really)');
+
+        // Now try a submission.
+        $submissionpluginparams = array();
+        $submissionpluginparams['files_filemanager'] = $draftidfile;
+        $onlinetexteditorparams = array('text'=>'Yeeha!',
+                                        'format'=>1,
+                                        'itemid'=>$draftidonlinetext);
+        $submissionpluginparams['onlinetext_editor'] = $onlinetexteditorparams;
+        $result = mod_assign_external::save_submission($instance->id, $submissionpluginparams);
+
+        $this->assertEquals(0, count($result));
+
+    }
+
+    /**
+     * Test save_grade
+     */
+    public function test_save_grade() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        $teacher = self::getDataGenerator()->create_user();
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->getDataGenerator()->enrol_user($teacher->id,
+                                              $course->id,
+                                              $teacherrole->id);
+        $this->setUser($teacher);
+
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['assignfeedback_file_enabled'] = 1;
+        $params['assignfeedback_comments_enabled'] = 1;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $student2 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+        $this->getDataGenerator()->enrol_user($student2->id,
+                                              $course->id,
+                                              $studentrole->id);
+        // Simulate a grade.
+        $this->setUser($teacher);
+
+        // Create a file in a draft area.
+        $draftidfile = file_get_unused_draft_itemid();
+
+        $usercontext = context_user::instance($teacher->id);
+        $filerecord = array(
+            'contextid' => $usercontext->id,
+            'component' => 'user',
+            'filearea'  => 'draft',
+            'itemid'    => $draftidfile,
+            'filepath'  => '/',
+            'filename'  => 'testtext.txt',
+        );
+
+        $fs = get_file_storage();
+        $fs->create_file_from_string($filerecord, 'text contents');
+
+        // Now try a grade.
+        $feedbackpluginparams = array();
+        $feedbackpluginparams['files_filemanager'] = $draftidfile;
+        $feedbackeditorparams = array('text'=>'Yeeha!',
+                                        'format'=>1);
+        $feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
+        $result = mod_assign_external::save_grade($instance->id,
+                                                  $student1->id,
+                                                  50.0,
+                                                  -1,
+                                                  true,
+                                                  'released',
+                                                  false,
+                                                  $feedbackpluginparams);
+
+        // No warnings.
+        $this->assertEquals(0, count($result));
+
+        $result = mod_assign_external::get_grades(array($instance->id));
+
+        $this->assertEquals($result['assignments'][0]['grades'][0]['grade'], '50.0');
+    }
+
+    /**
+     * Test copy_previous_attempt
+     */
+    public function test_copy_previous_attempt() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        $teacher = self::getDataGenerator()->create_user();
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->getDataGenerator()->enrol_user($teacher->id,
+                                              $course->id,
+                                              $teacherrole->id);
+        $this->setUser($teacher);
+
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['assignsubmission_onlinetext_enabled'] = 1;
+        $params['assignsubmission_file_enabled'] = 0;
+        $params['assignfeedback_file_enabled'] = 0;
+        $params['attemptreopenmethod'] = 'manual';
+        $params['maxattempts'] = 5;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+        // Now try a submission.
+        $this->setUser($student1);
+        $draftidonlinetext = file_get_unused_draft_itemid();
+        $submissionpluginparams = array();
+        $onlinetexteditorparams = array('text'=>'Yeeha!',
+                                        'format'=>1,
+                                        'itemid'=>$draftidonlinetext);
+        $submissionpluginparams['onlinetext_editor'] = $onlinetexteditorparams;
+        $submissionpluginparams['files_filemanager'] = file_get_unused_draft_itemid();
+        $result = mod_assign_external::save_submission($instance->id, $submissionpluginparams);
+
+        $this->setUser($teacher);
+        // Add a grade and reopen the attempt.
+        // Now try a grade.
+        $feedbackpluginparams = array();
+        $feedbackpluginparams['files_filemanager'] = file_get_unused_draft_itemid();
+        $feedbackeditorparams = array('text'=>'Yeeha!',
+                                        'format'=>1);
+        $feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
+        $result = mod_assign_external::save_grade($instance->id,
+                                                  $student1->id,
+                                                  50.0,
+                                                  -1,
+                                                  true,
+                                                  'released',
+                                                  false,
+                                                  $feedbackpluginparams);
+
+        $this->setUser($student1);
+        // Now copy the previous attempt.
+        $result = mod_assign_external::copy_previous_attempt($instance->id);
+        // No warnings.
+        $this->assertEquals(0, count($result));
+
+        $this->setUser($teacher);
+        $result = mod_assign_external::get_submissions(array($instance->id));
+
+        // Check we are now on the second attempt.
+        $this->assertEquals($result['assignments'][0]['submissions'][0]['attemptnumber'], 1);
+        // Check the plugins data is not empty.
+        $this->assertNotEmpty($result['assignments'][0]['submissions'][0]['plugins']);
+
+    }
 }
index 3ed48e8..ed10b06 100644 (file)
@@ -83,26 +83,31 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase {
 
         // Test students cannot reveal identities.
         $nopermission = false;
+        $this->students[0]->ignoresesskey = true;
         $this->setUser($this->students[0]);
         $this->setExpectedException('required_capability_exception');
-        $assign->testable_process_reveal_identities();
+        $assign->reveal_identities();
+        $this->students[0]->ignoresesskey = false;
 
         // Test teachers cannot reveal identities.
         $nopermission = false;
+        $this->teachers[0]->ignoresesskey = true;
         $this->setUser($this->teachers[0]);
         $this->setExpectedException('required_capability_exception');
-        $assign->testable_process_reveal_identities();
+        $assign->reveal_identities();
+        $this->teachers[0]->ignoresesskey = false;
 
         // Test sesskey is required.
         $this->setUser($this->editingteachers[0]);
         $this->setExpectedException('moodle_exception');
-        $assign->testable_process_reveal_identities();
+        $assign->reveal_identities();
 
         // Test editingteacher can reveal identities if sesskey is ignored.
         $this->editingteachers[0]->ignoresesskey = true;
         $this->setUser($this->editingteachers[0]);
-        $assign->testable_process_reveal_identities();
+        $assign->reveal_identities();
         $this->assertEquals(false, $assign->is_blind_marking());
+        $this->editingteachers[0]->ignoresesskey = false;
 
         // Test student names are visible.
         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
@@ -699,6 +704,28 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase {
         $this->assertCount(4, $assign->testable_get_graders($this->students[0]->id));
     }
 
+    public function test_group_members_only() {
+        global $CFG;
+
+        $this->setAdminUser();
+        $this->create_extra_users();
+        $CFG->enablegroupmembersonly = true;
+        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $this->course->id));
+        groups_assign_grouping($grouping->id, $this->groups[0]->id);
+
+        // Force create an assignment with SEPARATEGROUPS.
+        $instance = $this->getDataGenerator()->create_module('assign', array('course'=>$this->course->id),
+            array('groupmembersonly' => SEPARATEGROUPS, 'groupingid' => $grouping->id));
+
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+        $assign = new testable_assign($context, $cm, $this->course);
+
+        $this->setUser($this->teachers[0]);
+        $this->assertCount(5, $assign->list_participants(0, true));
+
+    }
+
     public function test_get_uniqueid_for_user() {
         $this->setUser($this->editingteachers[0]);
         $assign = $this->create_instance();
@@ -1016,7 +1043,7 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase {
         $assign = $this->create_instance();
         $sink = $this->redirectEvents();
 
-        $assign->testable_process_lock($this->students[0]->id);
+        $assign->lock_submission($this->students[0]->id);
 
         $events = $sink->get_events();
         $this->assertCount(1, $events);
@@ -1049,7 +1076,7 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase {
         $assign = $this->create_instance(array('blindmarking'=>1));
         $sink = $this->redirectEvents();
 
-        $assign->testable_process_reveal_identities();
+        $assign->reveal_identities();
 
         $events = $sink->get_events();
         $this->assertCount(1, $events);
@@ -1083,7 +1110,7 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase {
         $assign->testable_update_submission($submission, $this->students[0]->id, true, false);
 
         $sink = $this->redirectEvents();
-        $assign->testable_process_revert_to_draft($this->students[0]->id);
+        $assign->revert_to_draft($this->students[0]->id);
 
         $events = $sink->get_events();
         $this->assertCount(1, $events);
@@ -1191,7 +1218,7 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase {
 
         $sink = $this->redirectEvents();
         $notices = null;
-        $assign->testable_process_copy_previous_attempt($notices);
+        $assign->copy_previous_attempt($notices);
 
         $events = $sink->get_events();
         $this->assertCount(1, $events);
@@ -1221,7 +1248,7 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase {
         $assign = $this->create_instance();
         $sink = $this->redirectEvents();
 
-        $assign->testable_process_unlock($this->students[0]->id);
+        $assign->unlock_submission($this->students[0]->id);
 
         $events = $sink->get_events();
         $this->assertCount(1, $events);
index 1f513fe..44c2f89 100644 (file)
@@ -3,6 +3,11 @@ This files describes API changes in the assign code.
 === 2.6 ===
 * To see submission/grades of inactive users, user should have moodle/course:viewsuspendedusers capability.
 * count_* functions will return only active participants.
+* assign_submission_plugin->lock and unlock methods have an additional parameter for user flags. A user will not
+  always have a submission record when the submission is locked/unlocked.
+* Submission and feedback plugins can now participate in webservices. The plugin must implement get_external_parameters()
+  to describe the parameters it is expecting from the mod_assign_save_grade or mod_assign_save_submission functions. The
+  plugin will then handle the webservice via it's normal save() method with the extra data supplied in the $data argument.
 
 === 2.5 ===
 
index 7b06f0d..e2cb6d0 100644 (file)
@@ -25,7 +25,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 $module->component = 'mod_assign'; // Full name of the plugin (used for diagnostics).
-$module->version  = 2013070902;    // The current module version (Date: YYYYMMDDXX).
+$module->version  = 2013080800;    // The current module version (Date: YYYYMMDDXX).
 $module->requires = 2013050100;    // Requires this Moodle version.
 $module->cron     = 60;
 
index bc52dc7..0bc9061 100644 (file)
@@ -151,7 +151,7 @@ function book_get_chapter_title($chid, $chapters, $book, $context) {
 }
 
 /**
- * Add the book TOC sticky block to the 1st region available
+ * Add the book TOC sticky block to the default region
  *
  * @param array $chapters
  * @param stdClass $chapter
@@ -169,9 +169,8 @@ function book_add_fake_block($chapters, $chapter, $book, $cm, $edit) {
     $bc->attributes['class'] = 'block block_book_toc';
     $bc->content = $toc;
 
-    $regions = $PAGE->blocks->get_regions();
-    $firstregion = reset($regions);
-    $PAGE->blocks->add_fake_block($bc, $firstregion);
+    $defaultregion = $PAGE->blocks->get_default_region();
+    $PAGE->blocks->add_fake_block($bc, $defaultregion);
 }
 
 /**
index 51b34d7..61313bf 100644 (file)
@@ -86,12 +86,17 @@ define('QUIZ_SHOWIMAGE_LARGE', 2);
  *         if $attemptnumber > 1 and $quiz->attemptonlast is true.
  * @param int $timenow the time the attempt was started at.
  * @param bool $ispreview whether this new attempt is a preview.
+ * @param int $userid  the id of the user attempting this quiz.
  *
  * @return object the newly created attempt object.
  */
-function quiz_create_attempt(quiz $quizobj, $attemptnumber, $lastattempt, $timenow, $ispreview = false) {
+function quiz_create_attempt(quiz $quizobj, $attemptnumber, $lastattempt, $timenow, $ispreview = false, $userid = null) {
     global $USER;
 
+    if ($userid === null) {
+        $userid = $USER->id;
+    }
+
     $quiz = $quizobj->get_quiz();
     if ($quiz->sumgrades < 0.000005 && $quiz->grade > 0.000005) {
         throw new moodle_exception('cannotstartgradesmismatch', 'quiz',
@@ -103,7 +108,7 @@ function quiz_create_attempt(quiz $quizobj, $attemptnumber, $lastattempt, $timen
         // We are not building on last attempt so create a new attempt.
         $attempt = new stdClass();
         $attempt->quiz = $quiz->id;
-        $attempt->userid = $USER->id;
+        $attempt->userid = $userid;
         $attempt->preview = 0;
         $attempt->layout = quiz_clean_layout($quiz->questions, true);
         if ($quiz->shufflequestions) {
diff --git a/mod/quiz/report/statistics/tests/fixtures/quizzes.csv b/mod/quiz/report/statistics/tests/fixtures/quizzes.csv
new file mode 100644 (file)
index 0000000..1a3aaa8
--- /dev/null
@@ -0,0 +1,2 @@
+testnumber,preferredbehaviour
+00,deferredfeedback
index 5fdc453..50169d6 100644 (file)
@@ -79,7 +79,7 @@ class quiz_report_statistics_from_steps extends mod_quiz_attempt_walkthrough_fro
      *                                                                                  "stepsXX.csv" and "resultsXX.csv".
      * @dataProvider get_data_for_walkthrough
      */
-    public function test_walkthrough_from_csv($csvdata) {
+    public function test_walkthrough_from_csv($quizsettings, $csvdata) {
 
         // CSV data files for these tests were generated using :
         // https://github.com/jamiepratt/moodle-quiz-tools/tree/master/responsegenerator
@@ -87,7 +87,7 @@ class quiz_report_statistics_from_steps extends mod_quiz_attempt_walkthrough_fro
         $this->resetAfterTest(true);
         question_bank::get_qtype('random')->clear_caches_before_testing();
 
-        $this->create_quiz($csvdata['questions']);
+        $this->create_quiz($quizsettings, $csvdata['questions']);
 
         $attemptids = $this->walkthrough_attempts($csvdata['steps']);
 
index 428203c..589a927 100644 (file)
@@ -571,15 +571,15 @@ table#categoryquestions {
     border-left: thin solid #777;
     line-height: 1.3em;
     border-radius: 0.6em;
-    border-radius-bottomleft: 0;
-    border-radius-topleft: 0;
+    border-bottom-left-radius: 0;
+    border-top-left-radius: 0;
     width: 88%;
     padding: 0.15em 0 0.3em;
     background-color: #d6d6d6;
 }
 #page-mod-quiz-edit div.quizpage .pagecontent .pagestatus {
-    border-radius-bottomright: 0.3em;
-    border-radius-topright: 0.3em;
+    border-bottom-right-radius: 0.3em;
+    border-top-right-radius: 0.3em;
     margin: 0.3em;
     padding: 0.1em 0.1em 0.1em 0.3em;
     background-color: #eee;
@@ -647,8 +647,8 @@ table#categoryquestions {
     float: left;
     position: relative;
     border-radius: 0.3em;
-    border-radius-bottomleft: 0;
-    border-radius-topleft: 0;
+    border-bottom-left-radius: 0;
+    border-top-left-radius: 0;
     line-height: 1.2em;
     padding: 0.1em;
     background-color: #F9F9F9;
@@