Merge branch 'MDL-31936-master-workshop-reset' of git://github.com/mudrd8mz/moodle
authorMarina Glancy <marina@moodle.com>
Tue, 7 Oct 2014 02:08:48 +0000 (10:08 +0800)
committerMarina Glancy <marina@moodle.com>
Tue, 7 Oct 2014 02:08:48 +0000 (10:08 +0800)
course/reset_form.php
mod/workshop/lang/en/workshop.php
mod/workshop/lib.php
mod/workshop/locallib.php
mod/workshop/tests/generator/lib.php
mod/workshop/tests/generator_test.php
mod/workshop/tests/locallib_test.php

index 622909b..52ba8fa 100644 (file)
@@ -1,10 +1,37 @@
 <?php
-if (!defined('MOODLE_INTERNAL')) {
-    die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
-}
+// 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/>.
+
+/**
+ * Provides the course_reset_form class.
+ *
+ * @package     core
+ * @copyright   2007 Petr Skoda
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
 
 require_once $CFG->libdir.'/formslib.php';
 
+/**
+ * Defines the course reset settings form.
+ *
+ * @copyright   2007 Petr Skoda
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
 class course_reset_form extends moodleform {
     function definition (){
         global $CFG, $COURSE, $DB;
index 2a9bd34..3baa293 100644 (file)
@@ -224,6 +224,12 @@ $string['reassess'] = 'Re-assess';
 $string['receivedgrades'] = 'Grades received';
 $string['recentassessments'] = 'Workshop assessments:';
 $string['recentsubmissions'] = 'Workshop submissions:';
+$string['resetassessments'] = 'Delete all assessments';
+$string['resetassessments_help'] = 'You can choose to delete just allocated assessments without affecting submissions. If submissions are to be deleted, their assessments will be deleted implicitly and this option is ignored. Note this also includes assessments of example submissions.';
+$string['resetsubmissions'] = 'Delete all submissions';
+$string['resetsubmissions_help'] = 'All the submissions and their assessments will be deleted. This does not affect example submissions.';
+$string['resetphase'] = 'Switch to the setup phase';
+$string['resetphase_help'] = 'If enabled, all workshops will be put into the initial setup phase.';
 $string['saveandclose'] = 'Save and close';
 $string['saveandcontinue'] = 'Save and continue editing';
 $string['saveandpreview'] = 'Save and preview';
index 0c5724e..faa01f0 100644 (file)
@@ -1703,3 +1703,80 @@ function workshop_calendar_update(stdClass $workshop, $cmid) {
         $oldevent->delete();
     }
 }
+
+////////////////////////////////////////////////////////////////////////////////
+// Course reset API                                                           //
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Extends the course reset form with workshop specific settings.
+ *
+ * @param MoodleQuickForm $mform
+ */
+function workshop_reset_course_form_definition($mform) {
+
+    $mform->addElement('header', 'workshopheader', get_string('modulenameplural', 'mod_workshop'));
+
+    $mform->addElement('advcheckbox', 'reset_workshop_submissions', get_string('resetsubmissions', 'mod_workshop'));
+    $mform->addHelpButton('reset_workshop_submissions', 'resetsubmissions', 'mod_workshop');
+
+    $mform->addElement('advcheckbox', 'reset_workshop_assessments', get_string('resetassessments', 'mod_workshop'));
+    $mform->addHelpButton('reset_workshop_assessments', 'resetassessments', 'mod_workshop');
+    $mform->disabledIf('reset_workshop_assessments', 'reset_workshop_submissions', 'checked');
+
+    $mform->addElement('advcheckbox', 'reset_workshop_phase', get_string('resetphase', 'mod_workshop'));
+    $mform->addHelpButton('reset_workshop_phase', 'resetphase', 'mod_workshop');
+}
+
+/**
+ * Provides default values for the workshop settings in the course reset form.
+ *
+ * @param stdClass $course The course to be reset.
+ */
+function workshop_reset_course_form_defaults(stdClass $course) {
+
+    $defaults = array(
+        'reset_workshop_submissions'    => 1,
+        'reset_workshop_assessments'    => 1,
+        'reset_workshop_phase'          => 1,
+    );
+
+    return $defaults;
+}
+
+/**
+ * Performs the reset of all workshop instances in the course.
+ *
+ * @param stdClass $data The actual course reset settings.
+ * @return array List of results, each being array[(string)component, (string)item, (string)error]
+ */
+function workshop_reset_userdata(stdClass $data) {
+    global $CFG, $DB;
+
+    if (empty($data->reset_workshop_submissions)
+            and empty($data->reset_workshop_assessments)
+            and empty($data->reset_workshop_phase) ) {
+        // Nothing to do here.
+        return array();
+    }
+
+    $workshoprecords = $DB->get_records('workshop', array('course' => $data->courseid));
+
+    if (empty($workshoprecords)) {
+        // What a boring course - no workshops here!
+        return array();
+    }
+
+    require_once($CFG->dirroot . '/mod/workshop/locallib.php');
+
+    $course = $DB->get_record('course', array('id' => $data->courseid), '*', MUST_EXIST);
+    $status = array();
+
+    foreach ($workshoprecords as $workshoprecord) {
+        $cm = get_coursemodule_from_instance('workshop', $workshoprecord->id, $course->id, false, MUST_EXIST);
+        $workshop = new workshop($workshoprecord, $cm, $course);
+        $status = array_merge($status, $workshop->reset_userdata($data));
+    }
+
+    return $status;
+}
index c1e5ed6..55fee5a 100644 (file)
@@ -1057,8 +1057,14 @@ class workshop {
      */
     public function delete_submission(stdclass $submission) {
         global $DB;
+
         $assessments = $DB->get_records('workshop_assessments', array('submissionid' => $submission->id), '', 'id');
         $this->delete_assessment(array_keys($assessments));
+
+        $fs = get_file_storage();
+        $fs->delete_area_files($this->context->id, 'mod_workshop', 'submission_content', $submission->id);
+        $fs->delete_area_files($this->context->id, 'mod_workshop', 'submission_attachment', $submission->id);
+
         $DB->delete_records('workshop_submissions', array('id' => $submission->id));
     }
 
@@ -1262,21 +1268,39 @@ class workshop {
     }
 
     /**
-     * Delete assessment record or records
+     * Delete assessment record or records.
      *
-     * @param mixed $id int|array assessment id or array of assessments ids
-     * @return bool false if $id not a valid parameter, true otherwise
+     * Removes associated records from the workshop_grades table, too.
+     *
+     * @param int|array $id assessment id or array of assessments ids
+     * @todo Give grading strategy plugins a chance to clean up their data, too.
+     * @return bool true
      */
     public function delete_assessment($id) {
         global $DB;
 
-        // todo remove all given grades from workshop_grades;
+        if (empty($id)) {
+            return true;
+        }
+
+        $fs = get_file_storage();
 
         if (is_array($id)) {
-            return $DB->delete_records_list('workshop_assessments', 'id', $id);
+            $DB->delete_records_list('workshop_grades', 'assessmentid', $id);
+            foreach ($id as $itemid) {
+                $fs->delete_area_files($this->context->id, 'mod_workshop', 'overallfeedback_content', $itemid);
+                $fs->delete_area_files($this->context->id, 'mod_workshop', 'overallfeedback_attachment', $itemid);
+            }
+            $DB->delete_records_list('workshop_assessments', 'id', $id);
+
         } else {
-            return $DB->delete_records('workshop_assessments', array('id' => $id));
+            $DB->delete_records('workshop_grades', array('assessmentid' => $id));
+            $fs->delete_area_files($this->context->id, 'mod_workshop', 'overallfeedback_content', $id);
+            $fs->delete_area_files($this->context->id, 'mod_workshop', 'overallfeedback_attachment', $id);
+            $DB->delete_records('workshop_assessments', array('id' => $id));
         }
+
+        return true;
     }
 
     /**
@@ -2365,6 +2389,69 @@ class workshop {
         );
     }
 
+    /**
+     * Performs the reset of this workshop instance.
+     *
+     * @param stdClass $data The actual course reset settings.
+     * @return array List of results, each being array[(string)component, (string)item, (string)error]
+     */
+    public function reset_userdata(stdClass $data) {
+
+        $componentstr = get_string('pluginname', 'workshop').': '.format_string($this->name);
+        $status = array();
+
+        if (!empty($data->reset_workshop_assessments) or !empty($data->reset_workshop_submissions)) {
+            // Reset all data related to assessments, including assessments of
+            // example submissions.
+            $result = $this->reset_userdata_assessments($data);
+            if ($result === true) {
+                $status[] = array(
+                    'component' => $componentstr,
+                    'item' => get_string('resetassessments', 'mod_workshop'),
+                    'error' => false,
+                );
+            } else {
+                $status[] = array(
+                    'component' => $componentstr,
+                    'item' => get_string('resetassessments', 'mod_workshop'),
+                    'error' => $result,
+                );
+            }
+        }
+
+        if (!empty($data->reset_workshop_submissions)) {
+            // Reset all remaining data related to submissions.
+            $result = $this->reset_userdata_submissions($data);
+            if ($result === true) {
+                $status[] = array(
+                    'component' => $componentstr,
+                    'item' => get_string('resetsubmissions', 'mod_workshop'),
+                    'error' => false,
+                );
+            } else {
+                $status[] = array(
+                    'component' => $componentstr,
+                    'item' => get_string('resetsubmissions', 'mod_workshop'),
+                    'error' => $result,
+                );
+            }
+        }
+
+        if (!empty($data->reset_workshop_phase)) {
+            // Do not use the {@link workshop::switch_phase()} here, we do not
+            // want to trigger events.
+            $this->reset_phase();
+            $status[] = array(
+                'component' => $componentstr,
+                'item' => get_string('resetsubmissions', 'mod_workshop'),
+                'error' => false,
+            );
+        }
+
+        return $status;
+    }
+
+
     ////////////////////////////////////////////////////////////////////////////////
     // Internal methods (implementation details)                                  //
     ////////////////////////////////////////////////////////////////////////////////
@@ -2635,6 +2722,59 @@ class workshop {
 
         return substr($fullurl->out(), strlen($baseurl));
     }
+
+    /**
+     * Removes all user data related to assessments (including allocations).
+     *
+     * This includes assessments of example submissions as long as they are not
+     * referential assessments.
+     *
+     * @param stdClass $data The actual course reset settings.
+     * @return bool|string True on success, error message otherwise.
+     */
+    protected function reset_userdata_assessments(stdClass $data) {
+        global $DB;
+
+        $sql = "SELECT a.id
+                  FROM {workshop_assessments} a
+                  JOIN {workshop_submissions} s ON (a.submissionid = s.id)
+                 WHERE s.workshopid = :workshopid
+                       AND (s.example = 0 OR (s.example = 1 AND a.weight = 0))";
+
+        $assessments = $DB->get_records_sql($sql, array('workshopid' => $this->id));
+        $this->delete_assessment(array_keys($assessments));
+
+        $DB->delete_records('workshop_aggregations', array('workshopid' => $this->id));
+
+        return true;
+    }
+
+    /**
+     * Removes all user data related to participants' submissions.
+     *
+     * @param stdClass $data The actual course reset settings.
+     * @return bool|string True on success, error message otherwise.
+     */
+    protected function reset_userdata_submissions(stdClass $data) {
+        global $DB;
+
+        $submissions = $this->get_submissions();
+        foreach ($submissions as $submission) {
+            $this->delete_submission($submission);
+        }
+
+        return true;
+    }
+
+    /**
+     * Hard set the workshop phase to the setup one.
+     */
+    protected function reset_phase() {
+        global $DB;
+
+        $DB->set_field('workshop', 'phase', self::PHASE_SETUP, array('id' => $this->id));
+        $this->phase = self::PHASE_SETUP;
+    }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -3452,10 +3592,10 @@ class workshop_assessment extends workshop_assessment_base implements renderable
             return null;
         }
 
-        $content = format_text($this->feedbackauthor, $this->feedbackauthorformat,
-            array('overflowdiv' => true, 'context' => $this->workshop->context));
-        $content = file_rewrite_pluginfile_urls($content, 'pluginfile.php', $this->workshop->context->id,
+        $content = file_rewrite_pluginfile_urls($this->feedbackauthor, 'pluginfile.php', $this->workshop->context->id,
             'mod_workshop', 'overallfeedback_content', $this->id);
+        $content = format_text($content, $this->feedbackauthorformat,
+            array('overflowdiv' => true, 'context' => $this->workshop->context));
 
         return $content;
     }
index 7f37135..3774b8c 100644 (file)
@@ -97,4 +97,63 @@ class mod_workshop_generator extends testing_module_generator {
 
         return parent::create_instance($record, (array)$options);
     }
+
+    /**
+     * Generates a submission authored by the given user.
+     *
+     * @param int $workshopid Workshop instance id.
+     * @param int $authorid Author user id.
+     * @param stdClass|array $options Optional explicit properties.
+     * @return int The new submission id.
+     */
+    public function create_submission($workshopid, $authorid, $options = null) {
+        global $DB;
+
+        $timenow = time();
+        $options = (array)$options;
+
+        $record = $options + array(
+            'workshopid' => $workshopid,
+            'example' => 0,
+            'authorid' => $authorid,
+            'timecreated' => $timenow,
+            'timemodified' => $timenow,
+            'title' => 'Generated submission',
+            'content' => 'Generated content',
+            'contentformat' => FORMAT_MARKDOWN,
+            'contenttrust' => 0,
+        );
+
+        $id = $DB->insert_record('workshop_submissions', $record);
+
+        return $id;
+    }
+
+    /**
+     * Generates an allocation of the given submission for peer-assessment by the given user
+     *
+     * @param int $submissionid Submission id.
+     * @param int $reviewerid Reviewer's user id.
+     * @param stdClass|array $options Optional explicit properties.
+     * @return int The new assessment id.
+     */
+    public function create_assessment($submissionid, $reviewerid, $options = null) {
+        global $DB;
+
+        $timenow = time();
+        $options = (array)$options;
+
+        $record = $options + array(
+            'submissionid' => $submissionid,
+            'reviewerid' => $reviewerid,
+            'weight' => 1,
+            'timecreated' => $timenow,
+            'timemodified' => $timenow,
+            'grade' => null,
+        );
+
+        $id = $DB->insert_record('workshop_assessments', $record);
+
+        return $id;
+    }
 }
index ef39f38..d0961fb 100644 (file)
@@ -52,4 +52,57 @@ class mod_workshop_generator_testcase extends advanced_testcase {
         $this->assertEquals(2, count($records));
         $this->assertEquals('Another workshop', $records[$workshop->id]->name);
     }
+
+    public function test_create_submission() {
+        global $DB;
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        $course = $this->getDataGenerator()->create_course();
+        $workshop = $this->getDataGenerator()->create_module('workshop', array('course' => $course));
+        $user = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($user->id, $course->id);
+        $workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
+
+        $id = $workshopgenerator->create_submission($workshop->id, $user->id, array(
+            'title' => 'My custom title',
+        ));
+
+        $submissions = $DB->get_records('workshop_submissions', array('workshopid' => $workshop->id));
+        $this->assertEquals(1, count($submissions));
+        $this->assertTrue(isset($submissions[$id]));
+        $this->assertEquals($submissions[$id]->authorid, $user->id);
+        $this->assertSame('My custom title', $submissions[$id]->title);
+    }
+
+    public function test_create_assessment() {
+        global $DB;
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        $course = $this->getDataGenerator()->create_course();
+        $workshop = $this->getDataGenerator()->create_module('workshop', array('course' => $course));
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
+        $workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
+
+        $submissionid1 = $workshopgenerator->create_submission($workshop->id, $user1->id);
+        $submissionid2 = $workshopgenerator->create_submission($workshop->id, $user2->id);
+
+        $assessmentid1 = $workshopgenerator->create_assessment($submissionid1, $user2->id, array(
+            'weight' => 3,
+            'grade' => 95.00000,
+        ));
+        $assessmentid2 = $workshopgenerator->create_assessment($submissionid2, $user1->id);
+
+        $assessments = $DB->get_records('workshop_assessments');
+        $this->assertTrue(isset($assessments[$assessmentid1]));
+        $this->assertTrue(isset($assessments[$assessmentid2]));
+        $this->assertEquals(3, $assessments[$assessmentid1]->weight);
+        $this->assertEquals(95.00000, $assessments[$assessmentid1]->grade);
+        $this->assertEquals(1, $assessments[$assessmentid2]->weight);
+        $this->assertNull($assessments[$assessmentid2]->grade);
+    }
 }
index 698b761..70900ec 100644 (file)
@@ -529,4 +529,97 @@ class mod_workshop_internal_api_testcase extends advanced_testcase {
         $users = $workshoprestricted->get_potential_authors(false, $group2->id);
         $this->assertEquals(array($student2->id), array_keys($users));
     }
+
+    /**
+     * Test the workshop reset feature.
+     */
+    public function test_reset_phase() {
+        $this->resetAfterTest(true);
+
+        $this->workshop->switch_phase(workshop::PHASE_CLOSED);
+        $this->assertEquals(workshop::PHASE_CLOSED, $this->workshop->phase);
+
+        $settings = (object)array(
+            'reset_workshop_phase' => 0,
+        );
+        $status = $this->workshop->reset_userdata($settings);
+        $this->assertEquals(workshop::PHASE_CLOSED, $this->workshop->phase);
+
+        $settings = (object)array(
+            'reset_workshop_phase' => 1,
+        );
+        $status = $this->workshop->reset_userdata($settings);
+        $this->assertEquals(workshop::PHASE_SETUP, $this->workshop->phase);
+        foreach ($status as $result) {
+            $this->assertFalse($result['error']);
+        }
+    }
+
+    /**
+     * Test deleting assessments related data on workshop reset.
+     */
+    public function test_reset_userdata_assessments() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        $student1 = $this->getDataGenerator()->create_user();
+        $student2 = $this->getDataGenerator()->create_user();
+
+        $this->getDataGenerator()->enrol_user($student1->id, $this->workshop->course->id);
+        $this->getDataGenerator()->enrol_user($student2->id, $this->workshop->course->id);
+
+        $workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
+
+        $subid1 = $workshopgenerator->create_submission($this->workshop->id, $student1->id);
+        $subid2 = $workshopgenerator->create_submission($this->workshop->id, $student2->id);
+
+        $asid1 = $workshopgenerator->create_assessment($subid1, $student2->id);
+        $asid2 = $workshopgenerator->create_assessment($subid2, $student1->id);
+
+        $settings = (object)array(
+            'reset_workshop_assessments' => 1,
+        );
+        $status = $this->workshop->reset_userdata($settings);
+
+        foreach ($status as $result) {
+            $this->assertFalse($result['error']);
+        }
+
+        $this->assertEquals(2, $DB->count_records('workshop_submissions', array('workshopid' => $this->workshop->id)));
+        $this->assertEquals(0, $DB->count_records('workshop_assessments'));
+    }
+
+    /**
+     * Test deleting submissions related data on workshop reset.
+     */
+    public function test_reset_userdata_submissions() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        $student1 = $this->getDataGenerator()->create_user();
+        $student2 = $this->getDataGenerator()->create_user();
+
+        $this->getDataGenerator()->enrol_user($student1->id, $this->workshop->course->id);
+        $this->getDataGenerator()->enrol_user($student2->id, $this->workshop->course->id);
+
+        $workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
+
+        $subid1 = $workshopgenerator->create_submission($this->workshop->id, $student1->id);
+        $subid2 = $workshopgenerator->create_submission($this->workshop->id, $student2->id);
+
+        $asid1 = $workshopgenerator->create_assessment($subid1, $student2->id);
+        $asid2 = $workshopgenerator->create_assessment($subid2, $student1->id);
+
+        $settings = (object)array(
+            'reset_workshop_submissions' => 1,
+        );
+        $status = $this->workshop->reset_userdata($settings);
+
+        foreach ($status as $result) {
+            $this->assertFalse($result['error']);
+        }
+
+        $this->assertEquals(0, $DB->count_records('workshop_submissions', array('workshopid' => $this->workshop->id)));
+        $this->assertEquals(0, $DB->count_records('workshop_assessments'));
+    }
 }