]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-19717 First drafts of allocation support
authorDavid Mudrak <david.mudrak@gmail.com>
Mon, 4 Jan 2010 17:46:05 +0000 (17:46 +0000)
committerDavid Mudrak <david.mudrak@gmail.com>
Mon, 4 Jan 2010 17:46:05 +0000 (17:46 +0000)
14 files changed:
mod/workshop/allocation.php [new file with mode: 0644]
mod/workshop/allocation/lib.php [new file with mode: 0644]
mod/workshop/allocation/manual/allocator.php [new file with mode: 0644]
mod/workshop/allocation/manual/ui.css [new file with mode: 0644]
mod/workshop/allocation/random/allocator.php [new file with mode: 0644]
mod/workshop/assessment.php
mod/workshop/editform.php
mod/workshop/lang/en_utf8/workshop.php
mod/workshop/lib.php
mod/workshop/locallib.php
mod/workshop/simpletest/testlib.php
mod/workshop/simpletest/testworkshopapi.php [new file with mode: 0644]
mod/workshop/submission.php
mod/workshop/view.php

diff --git a/mod/workshop/allocation.php b/mod/workshop/allocation.php
new file mode 100644 (file)
index 0000000..8f61587
--- /dev/null
@@ -0,0 +1,87 @@
+<?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/>.
+/**
+ * At this page, teachers allocate submissions to students for a review
+ *
+ * The allocation logic itself is delegated to allocators - subplugins in ./allocation 
+ * folder.
+ *
+ * @package   mod-workshop
+ * @copyright 2009 David Mudrak <david.mudrak@gmail.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(dirname(dirname(dirname(__FILE__))).'/config.php');
+require_once(dirname(__FILE__).'/locallib.php');
+require_once(dirname(__FILE__).'/allocation/lib.php');
+
+$cmid   = required_param('cmid', PARAM_INT);                    // course module
+$method = optional_param('method', 'manual', PARAM_ALPHA);      // method to use
+
+$PAGE->set_url('mod/workshop/allocation.php', array('cmid' => $cmid));
+
+if (!$cm = get_coursemodule_from_id('workshop', $cmid)) {
+    print_error('invalidcoursemodule');
+}
+if (!$course = $DB->get_record('course', array('id' => $cm->course))) {
+    print_error('coursemisconf');
+}
+if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
+    print_error('err_invalidworkshopid', 'workshop');
+}
+
+$workshop = new workshop_api($workshop, $cm);
+
+require_login($course, false, $cm);
+
+$context = $PAGE->context;
+require_capability('mod/workshop:allocate', $context);
+
+$PAGE->set_title($workshop->name);
+$PAGE->set_heading($course->fullname);
+
+// todo navigation will be changed yet for Moodle 2.0
+$navigation = build_navigation(get_string('allocation', 'workshop'), $cm);
+
+$allocator = workshop_allocator_instance($workshop, $method);
+try {
+    $allocator->init();
+} 
+catch (moodle_workshop_exception $e) {
+    echo $OUTPUT->header($navigation);
+    throw $e;
+}
+
+//
+// Output starts here
+//
+echo $OUTPUT->header($navigation);
+
+$allocators = workshop_installed_allocators();
+$tabrow = array();
+foreach ($allocators as $methodid => $methodname) {
+    $tabrow[] = new tabobject($methodid, "allocation.php?cmid={$cmid}&amp;method={$methodid}", $methodname);
+}
+print_tabs(array($tabrow), $method);
+
+echo $OUTPUT->container_start('allocator allocator-' . $method);
+echo $allocator->ui();
+echo $OUTPUT->container_end();
+
+echo $OUTPUT->footer();
diff --git a/mod/workshop/allocation/lib.php b/mod/workshop/allocation/lib.php
new file mode 100644 (file)
index 0000000..e65bc04
--- /dev/null
@@ -0,0 +1,170 @@
+<?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/>.
+
+
+/**
+ * Code for the submissions allocation support is defined here
+ *
+ * @package   mod-workshop
+ * @copyright 2009 David Mudrak <david.mudrak@gmail.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Allocators are responsible for assigning submissions to reviewers for assessments
+ *
+ * The task of the allocator is to assign the correct number of submissions to reviewers 
+ * for assessment. Several allocation methods are expected and they can be combined. For
+ * example, teacher can allocate several submissions manually (by 'manual' allocator) and
+ * then let the other submissions being allocated randomly (by 'random' allocator).
+ * Allocation is actually done by creating an initial assessment record in the
+ * workshop_assessments table.
+ */
+interface workshop_allocator {
+    
+    /**
+     * Initialize the allocator and eventually process submitted data
+     *
+     * This method is called soon after the allocator is constructed and before any output 
+     * is generated. Therefore is may process any data submitted and do other tasks.
+     * It should not generate any output
+     *
+     * @throws moodle_workshop_exception
+     * @return void
+     */
+    public function init();
+
+
+    /**
+     * Returns HTML to be displayed as the user interface
+     *
+     * If a form is part of the UI, the caller should have call $PAGE->set_url(...)
+     * 
+     * @access public
+     * @return string HTML to be displayed
+     */
+    public function ui();
+
+}
+
+
+/**
+ * Return list of available allocation methods
+ *
+ * @access public
+ * @return array Array ['string' => 'string'] of localized allocation method names
+ */
+function workshop_installed_allocators() {
+
+    $installed = get_list_of_plugins('mod/workshop/allocation');
+    $forms = array();
+    foreach ($installed as $allocation) {
+        $forms[$allocation] = get_string('allocation' . $allocation, 'workshop');
+    }
+    // usability - make sure that manual allocation appears the first
+    if (isset($forms['manual'])) {
+        $m = array('manual' => $forms['manual']);
+        unset($forms['manual']);
+        $forms = array_merge($m, $forms);
+    }
+    return $forms;
+}
+
+
+/**
+ * Returns instance of submissions allocator
+ * 
+ * @param object $workshop Workshop record
+ * @param object $method The name of the allocation method, must be PARAM_ALPHA
+ * @return object Instance of submissions allocator
+ */
+function workshop_allocator_instance(workshop $workshop, $method) {
+
+    $allocationlib = dirname(__FILE__) . '/' . $method . '/allocator.php';
+    if (is_readable($allocationlib)) {
+        require_once($allocationlib);
+    } else {
+        throw new moodle_exception('missingallocator', 'workshop');
+    }
+    $classname = 'workshop_' . $method . '_allocator';
+    return new $classname($workshop);
+}
+
+
+/**
+ * Returns the list of submissions and assessments allocated to them in the given workshop
+ *
+ * Submissions without allocated assessment are returned too, having assessment attributes null.
+ * This also fetches all other associated information (like details about the author and reviewer)
+ * needed to produce allocation reports.
+ * The returned structure is recordset of objects with following properties:
+ * [submissionid] [submissiontitle] [authorid] [authorfirstname] 
+ * [authorlastname] [authorpicture] [authorimagealt] [assessmentid] 
+ * [timeallocated] [reviewerid] [reviewerfirstname] [reviewerlastname] 
+ * [reviewerpicture] [reviewerimagealt]
+ *
+ * @param object $workshop The workshop object
+ * @return object Recordset of allocations
+ */
+function workshop_get_allocations(workshop $workshop) {
+    global $DB;
+
+    $sql = 'SELECT s.id AS submissionid, s.title AS submissiontitle, s.userid AS authorid, 
+                    author.firstname AS authorfirstname, author.lastname AS authorlastname, 
+                    author.picture AS authorpicture, author.imagealt AS authorimagealt,
+                    a.id AS assessmentid, a.timecreated AS timeallocated, a.userid AS reviewerid, 
+                    reviewer.firstname AS reviewerfirstname, reviewer.lastname AS reviewerlastname,
+                    reviewer.picture as reviewerpicture, reviewer.imagealt AS reviewerimagealt
+            FROM {workshop_submissions} s
+                LEFT JOIN {workshop_assessments} a ON (s.id = a.submissionid)
+                LEFT JOIN {user} author ON (s.userid = author.id)
+                LEFT JOIN {user} reviewer ON (a.userid = reviewer.id)
+            WHERE s.workshopid = ?
+            ORDER BY author.lastname,author.firstname,reviewer.lastname,reviewer.firstname';
+    return $DB->get_recordset_sql($sql, array($workshop->id));
+}
+
+
+/**
+ * Allocate a submission to a user for review
+ * 
+ * @param object $workshop Workshop record
+ * @param object $submission Submission record
+ * @param int $reviewerid User ID
+ * @access public
+ * @return int ID of the new assessment or an error code
+ */
+function workshop_add_allocation(workshop $workshop, stdClass $submission, $reviewerid) {
+    global $DB;
+
+    if ($DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'userid' => $reviewerid))) {
+        return WORKSHOP_ALLOCATION_EXISTS;
+    }
+
+    $now = time();
+    $assessment = new stdClass();
+    $assessment->submissionid = $submission->id;
+    $assessment->userid         = $reviewerid;
+    $assessment->timecreated    = $now;
+    $assessment->timemodified   = $now;
+
+    return $DB->insert_record('workshop_assessments', $assessment);
+}
+
diff --git a/mod/workshop/allocation/manual/allocator.php b/mod/workshop/allocation/manual/allocator.php
new file mode 100644 (file)
index 0000000..310fde1
--- /dev/null
@@ -0,0 +1,395 @@
+<?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/>.
+
+
+/**
+ * Allows user to allocate the submissions manually
+ * 
+ * @package   mod-workshop
+ * @copyright 2009 David Mudrak <david.mudrak@gmail.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(dirname(dirname(__FILE__)) . '/lib.php');                  // interface definition
+require_once(dirname(dirname(dirname(__FILE__))) . '/locallib.php');    // workshop internal API
+
+
+/**
+ * These constants are used to pass status messages between init() and ui()
+ */
+define('WORKSHOP_ALLOCATION_MANUAL_MSG_ADDED',          1);
+define('WORKSHOP_ALLOCATION_MANUAL_MSG_NOSUBMISSION',   2);
+define('WORKSHOP_ALLOCATION_MANUAL_MSG_EXISTS',         3);
+define('WORKSHOP_ALLOCATION_MANUAL_MSG_WOSUBMISSION',   4);
+define('WORKSHOP_ALLOCATION_MANUAL_MSG_CONFIRM_DEL',    5);
+define('WORKSHOP_ALLOCATION_MANUAL_MSG_DELETED',        6);
+
+
+/**
+ * Allows users to allocate submissions for review manually
+ */
+class workshop_manual_allocator implements workshop_allocator {
+
+    /** workshop instance */
+    protected $workshop;
+
+
+    /**
+     * @param stdClass $workshop Workshop record
+     */
+    public function __construct(workshop $workshop) {
+    
+        $this->workshop = $workshop;
+    }
+
+
+    /**
+     * Allocate submissions as requested by user
+     */
+    public function init() {
+        global $PAGE;
+
+        $mode = optional_param('mode', 'display', PARAM_ALPHA);
+
+        switch ($mode) {
+        case 'new':
+            if (!confirm_sesskey()) {
+                throw new moodle_workshop_exception($this->workshop, 'confirmsesskeybad');
+            }
+            $reviewerid = required_param('by', PARAM_INT);
+            $authorid   = required_param('of', PARAM_INT);
+            $m          = array();  // message object to be passed to the next page
+            $rs         = $this->workshop->get_submissions($authorid);
+            $submission = $rs->current();
+            $rs->close();
+            if (!$submission) {
+                // nothing submitted by the given user
+                $m[] = WORKSHOP_ALLOCATION_MANUAL_MSG_NOSUBMISSION;
+                $m[] = $authorid;
+                
+            } else {
+                // ok, we have the submission
+                $res = $this->workshop->add_allocation($submission, $reviewerid);
+                if ($res == WORKSHOP_ALLOCATION_EXISTS) {
+                    $m[] = WORKSHOP_ALLOCATION_MANUAL_MSG_EXISTS;
+                    $m[] = $submission->userid;
+                    $m[] = $reviewerid;
+                } elseif ($res == WORKSHOP_ALLOCATION_WOSUBMISSION) {
+                    $m[] = WORKSHOP_ALLOCATION_MANUAL_MSG_WOSUBMISSION;
+                    $m[] = $submission->userid;
+                    $m[] = $reviewerid;
+                } else {
+                    $m[] = WORKSHOP_ALLOCATION_MANUAL_MSG_ADDED;
+                    $m[] = $submission->userid;
+                    $m[] = $reviewerid;
+                }
+            }
+            $m = implode('-', $m);  // serialize message object to be passed via URL
+            redirect($PAGE->url->out(false, array('m' => $m), false));
+            break;
+        case 'del':
+            if (!confirm_sesskey()) {
+                throw new moodle_workshop_exception($this->workshop, 'confirmsesskeybad');
+            }
+            $assessmentid   = required_param('what', PARAM_INT);
+            $confirmed      = optional_param('confirm', 0, PARAM_INT);
+            $rs             = $this->workshop->get_assessments('all', $assessmentid);
+            $assessment     = $rs->current();
+            $rs->close();
+            if ($assessment) {
+                if (!$confirmed) {
+                    $m[] = WORKSHOP_ALLOCATION_MANUAL_MSG_CONFIRM_DEL;
+                    $m[] = $assessment->id;
+                    $m[] = $assessment->authorid;
+                    $m[] = $assessment->reviewerid;
+                    if (is_null($assessment->grade)) {
+                        $m[] = 0;
+                    } else {
+                        $m[] = 1;
+                    }
+                } else {
+                    $res = $this->workshop->delete_assessment($assessment->id);
+                    $m[] = WORKSHOP_ALLOCATION_MANUAL_MSG_DELETED;
+                    $m[] = $assessment->authorid;
+                    $m[] = $assessment->reviewerid;
+                }
+                $m = implode('-', $m);  // serialize message object to be passed via URL
+                redirect($PAGE->url->out(false, array('m' => $m), false));
+            }
+            break;
+        }
+
+        // if we stay on this page, set the environment
+        $PAGE->requires->css('mod/workshop/allocation/manual/ui.css');
+    }
+
+
+    /**
+     * Prints user interface - current allocation and a form to edit it
+     */
+    public function ui() {
+        global $PAGE;
+
+        $o              = '';   // output buffer
+        $hlauthorid     = -1;   // highlight this author
+        $hlreviewerid   = -1;   // highlight this reviewer
+        $msg            = '';   // msg text
+        $sty            = '';   // msg style
+        $m = optional_param('m', '', PARAM_ALPHANUMEXT);   // message object
+
+        if ($m) {
+            $m = explode('-', $m);  // unserialize
+            switch ($m[0]) {
+            case WORKSHOP_ALLOCATION_MANUAL_MSG_ADDED:
+                $hlauthorid     = $m[1];
+                $hlreviewerid   = $m[2];
+                $msg            = get_string('allocationadded', 'workshop');
+                $sty            = 'ok';
+                break;
+            case WORKSHOP_ALLOCATION_MANUAL_MSG_EXISTS:
+                $hlauthorid     = $m[1];
+                $hlreviewerid   = $m[2];
+                $msg            = get_string('allocationexists', 'workshop');
+                $sty            = 'info';
+                break;
+            case WORKSHOP_ALLOCATION_MANUAL_MSG_NOSUBMISSION:
+                $hlauthorid     = $m[1];
+                $msg            = get_string('nosubmissionfound', 'workshop');
+                $sty            = 'error';
+                break;
+            case WORKSHOP_ALLOCATION_MANUAL_MSG_WOSUBMISSION:
+                $hlauthorid     = $m[1];
+                $hlreviewerid   = $m[2];
+                $msg            = get_string('cantassesswosubmission', 'workshop');
+                $sty            = 'error';
+                break;
+            case WORKSHOP_ALLOCATION_MANUAL_MSG_CONFIRM_DEL:
+                $hlauthorid     = $m[2];
+                $hlreviewerid   = $m[3];
+                if ($m[4] == 0) {
+                    $msg            = get_string('areyousuretodeallocate', 'workshop');
+                    $sty            = 'info';
+                } else {
+                    $msg            = get_string('areyousuretodeallocategraded', 'workshop');
+                    $sty            = 'error';
+                }
+                break;
+            case WORKSHOP_ALLOCATION_MANUAL_MSG_DELETED:
+                $hlauthorid     = $m[1];
+                $hlreviewerid   = $m[2];
+                $msg            = get_string('assessmentdeleted', 'workshop');
+                $sty            = 'ok';
+                break;
+            }
+            $o .= '<div id="message" class="' . $sty . '">';
+            $o .= '  <span>' . $msg . '</span>';
+            $o .= '  <div id="message-close"><a href="' . $PAGE->url->out() . '">' . 
+                                                get_string('messageclose', 'workshop') . '</a></div>';
+            if ($m[0] == WORKSHOP_ALLOCATION_MANUAL_MSG_CONFIRM_DEL) {
+                $handler = $PAGE->url->out_action();
+                $o .= print_single_button($handler, array('mode' => 'del', 'what' => $m[1], 'confirm' => 1),
+                                get_string('iamsure', 'workshop'), 'post', '', true);
+            }
+            $o .= '</div>';
+        }
+
+        $peer = array(); // singular chosen due to readibility
+        $rs = $this->workshop->get_allocations();
+        foreach ($rs as $allocation) {
+            $currentuserid = $allocation->authorid;
+            if (!isset($peer[$currentuserid])) {
+                $peer[$currentuserid]                   = new stdClass();
+                $peer[$currentuserid]->id               = $allocation->authorid;
+                $peer[$currentuserid]->firstname        = $allocation->authorfirstname;
+                $peer[$currentuserid]->lastname         = $allocation->authorlastname;
+                $peer[$currentuserid]->picture          = $allocation->authorpicture;
+                $peer[$currentuserid]->imagealt         = $allocation->authorimagealt;
+                $peer[$currentuserid]->avatar           = print_user_picture($peer[$currentuserid],
+                                                                            $this->workshop->course, null, 16, true);
+                $peer[$currentuserid]->submissionid     = $allocation->submissionid;
+                $peer[$currentuserid]->submissiontitle  = $allocation->submissiontitle;
+                $peer[$currentuserid]->submissiongrade  = $allocation->submissiongrade;
+                $peer[$currentuserid]->reviewedby       = array(); // users who are reviewing this user's submission
+                $peer[$currentuserid]->reviewerof       = array(); // users whom submission is being reviewed by this user
+            }
+            if (!empty($allocation->reviewerid)) {
+                // example: "submission of user with id 45 is reviewed by user with id 87 in the assessment record 12"
+                $peer[$currentuserid]->reviewedby[$allocation->reviewerid] = $allocation->assessmentid;
+            }
+        }
+        $rs->close();
+
+        foreach ($peer as $author) {
+            foreach ($author->reviewedby as $reviewerid => $assessmentid) {
+                // example: "user with id 87 is reviewer of the work submitted by user id 45 in the assessment record 12"
+                if (isset($peer[$reviewerid])) {
+                    $peer[$reviewerid]->reviewerof[$author->id] = $assessmentid;
+                }
+            }
+        }
+
+        if (empty($peer)) {
+            $o .= '<div id="message" class="info">' . get_string('nosubmissions', 'workshop') . '</div>';
+        } else {
+            $o .= '<table class="allocations">' . "\n";
+            $o .= '<thead><tr>';
+            $o .= '<th>' . get_string('participantreviewedby', 'workshop') . '</th>';
+            $o .= '<th>' . get_string('participant', 'workshop') . '</th>';
+            $o .= '<th>' . get_string('participantrevierof', 'workshop') . '</th>';
+            $o .= '</thead><tbody>';
+            $counter = 0;
+            foreach ($peer as $user) {
+                $o .= '<tr class="r' . $counter % 2 . '">' . "\n";
+
+                if ($user->id == $hlauthorid) {
+                    $highlight=' highlight';
+                } else {
+                    $highlight='';
+                }
+                $o .= '<td class="reviewedby' . $highlight . '">' . "\n";
+                if (is_null($user->submissionid)) {
+                    $o .= '<span class="info">' . "\n";
+                    $o .= get_string('nothingtoreview', 'workshop');
+                    $o .= '</span>' . "\n";
+                } else {
+                    $handler = $PAGE->url->out_action() . '&amp;mode=new&amp;of=' . $user->id . '&amp;by=';
+                    $o .= popup_form($handler, $this->available_reviewers($user->id), 'addreviewof' . $user->id, '',
+                             get_string('chooseuser', 'workshop'), '', '', true, 'self', get_string('addreviewer', 'workshop'));
+                }
+                $o .= '<ul>' . "\n";
+                foreach ($user->reviewedby as $reviewerid => $assessmentid) {
+                    $o .= '<li>';
+                    $o .= print_user_picture($peer[$reviewerid], $this->workshop->course, null, 16, true);
+                    $o .= fullname($peer[$reviewerid]);
+
+                    // delete
+                    $handler = $PAGE->url->out_action(array('mode' => 'del', 'what' => $assessmentid));
+                    $o .= '<a class="action" href="' . $handler . '"> X </a>'; // todo icon and link title
+
+                    $o .= '</li>';
+                }
+                $o .= '</ul>' . "\n";
+
+                $o .= '</td>' . "\n";
+                $o .= '<td class="peer">' . "\n";
+                $o .= print_user_picture($user, $this->workshop->course, null, 35, true);
+                $o .= fullname($user);
+                $o .= '<div class="submission">' . "\n";
+                if (is_null($user->submissionid)) {
+                    $o .= '<span class="info">' . get_string('nosubmissionfound', 'workshop');
+                } else {
+                    $o .= '<div class="title"><a href="#">' . s($user->submissiontitle) . '</a></div>';
+                    if (is_null($user->submissiongrade)) {
+                        $o .= '<div class="grade missing">' . get_string('nogradeyet', 'workshop') . '</div>';
+                    } else {
+                        $o .= '<div class="grade">' . s($user->submissiongrade) . '</div>'; // todo calculate
+                    }
+                }
+                $o .= '</div>' . "\n";
+                $o .= '</td>' . "\n";
+
+                if ($user->id == $hlreviewerid) {
+                    $highlight=' highlight';
+                } else {
+                    $highlight='';
+                }
+                $o .= '<td class="reviewerof' . $highlight . '">' . "\n";
+                if (!($this->workshop->assesswosubmission) && is_null($user->submissionid)) {
+                    $o .= '<span class="info">' . "\n";
+                    $o .= get_string('cantassesswosubmission', 'workshop');
+                    $o .= '</span>' . "\n";
+                } else {
+                    $handler = $PAGE->url->out_action() . '&mode=new&amp;by=' . $user->id . '&amp;of=';
+                    $o .= popup_form($handler, $this->available_reviewees($user->id), 'addreviewby' . $user->id, '',
+                             get_string('chooseuser', 'workshop'), '', '', true, 'self', get_string('addreviewee', 'workshop'));
+                    $o .= '<ul>' . "\n";
+                    foreach ($user->reviewerof as $authorid => $assessmentid) {
+                        $o .= '<li>';
+                        $o .= print_user_picture($peer[$authorid], $this->workshop->course, null, 16, true);
+                        $o .= fullname($peer[$authorid]);
+
+                        // delete
+                        $handler = $PAGE->url->out_action(array('mode' => 'del', 'what' => $assessmentid));
+                        $o .= '<a class="action" href="' . $handler . '"> X </a>'; // todo icon and link title
+
+                        $o .= '</li>';
+                    }   
+                    $o .= '</ul>' . "\n";
+                }
+                $o .= '</td>' . "\n";
+                $o .= '</tr>' . "\n";
+                $counter++;
+            }
+            $o .= '</tbody></table>' . "\n";
+        }
+        return $o;
+    }
+
+
+    /**
+     * Return a list of reviewers that can review a submission
+     *
+     * @param int $authorid User ID of the submission author
+     * @return array Select options
+     */
+    protected function available_reviewers($authorid) {
+
+        $users = $this->workshop->get_peer_reviewers();
+        $options = array();
+        foreach ($users as $user) {
+            $options[$user->id] = fullname($user);
+        }
+        if (0 == $this->workshop->useselfassessment) {
+            // students can not review their own submissions in this workshop
+            if (isset($options[$authorid])) {
+                unset($options[$authorid]);
+            }
+        }
+
+        return $options;
+    }
+
+
+    /**
+     * Return a list of reviewees whom work can be reviewed by a given user
+     *
+     * @param int $reviewerid User ID of the reviewer
+     * @return array Select options
+     */
+    protected function available_reviewees($reviewerid) {
+
+        $rs = $this->workshop->get_submissions();
+        $options = array();
+        foreach ($rs as $submission) {
+            $options[$submission->userid] = fullname((object)array('firstname' => $submission->authorfirstname, 
+                                                                   'lastname' =>  $submission->authorlastname));
+        }
+        $rs->close();
+        if (0 == $this->workshop->useselfassessment) {
+            // students can not be reviewed by themselves in this workshop
+            if (isset($options[$reviewerid])) {
+                unset($options[$reviewerid]);
+            }
+        }
+
+        return $options;
+    }
+
+}
+
diff --git a/mod/workshop/allocation/manual/ui.css b/mod/workshop/allocation/manual/ui.css
new file mode 100644 (file)
index 0000000..1439d67
--- /dev/null
@@ -0,0 +1,81 @@
+.allocations {
+    margin: 0px auto;
+}
+
+.allocations .r0 {
+    background-color: #eee;
+}
+
+.allocations .highlight {
+    background-color: #fff3d2;
+}
+
+.allocations tr td {
+    vertical-align: top;
+    padding: 5px;
+}
+
+.allocations tr td.peer {
+    border-left: 1px solid #ccc;
+    border-right: 1px solid #ccc;
+}
+
+.allocations .reviewedby .info,
+.allocations .peer .info,
+.allocations .reviewerof .info {
+    font-size: 80%;
+    color: #888;
+    font-style: italic;
+}
+
+.allocations .reviewedby img.userpicture,
+.allocations .reviewerof img.userpicture {
+    height: 16px;
+    width: 16px;
+    margin-right: 3px;
+    vertical-align: middle;
+}
+
+.allocations .peer img.userpicture {
+    height: 35px;
+    width: 35px;
+    vertical-align: middle;
+    margin-right: 5px;
+}
+
+.allocations .peer .submission {
+    font-size: 90%;
+    margin-top: 1em;
+}
+
+#message {
+    padding: 5px 5em 5px 15px;
+    margin: 0px auto 20px auto;
+    width: 60%;
+    font-size: 80%;
+    position: relative;
+}
+
+#message-close {
+    font-weight: bold;
+    position: absolute;
+    top: 5px;
+    right: 15px;
+}
+
+#message.ok {
+    color: #547c22;
+    background-color: #e7f1c3;
+}
+
+#message.error {
+    color: #dd0221;
+    background-color: #ffd3d9;
+}
+
+#message.info {
+    color: #1666a9;
+    background-color: #d2ebff;
+}
+
+
diff --git a/mod/workshop/allocation/random/allocator.php b/mod/workshop/allocation/random/allocator.php
new file mode 100644 (file)
index 0000000..228824d
--- /dev/null
@@ -0,0 +1,79 @@
+<?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/>.
+
+
+/**
+ * Allocates the submissions randomly
+ * 
+ * @package   mod-workshop
+ * @copyright 2009 David Mudrak <david.mudrak@gmail.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(dirname(dirname(__FILE__)) . '/lib.php');  // interface definition
+
+
+class workshop_random_allocator implements workshop_allocator {
+
+    /** workshop instance */
+    protected $workshop;
+
+    /** array of allocations */
+    protected $allocation = array();
+
+    public function __construct(stdClass $workshop) {
+        global $DB, $USER;
+
+        $this->workshop = $workshop;
+
+        // submissions to be allocated
+        $submissions = $DB->get_records('workshop_submissions', array('workshopid' => $this->workshop->id, 'example' => 0),
+                                         '', 'id,userid,title');
+
+
+        // dummy allocation - allocate all submissions to the current USER
+        foreach ($submissions as $submissionid => $submission) {
+            $this->allocation[$submissionid]                = new stdClass;
+            $this->allocation[$submissionid]->submissionid  = $submissionid;
+            $this->allocation[$submissionid]->title         = $submission->title;
+            $this->allocation[$submissionid]->authorid      = $submission->userid;
+            $this->allocation[$submissionid]->reviewerid    = $USER->id;
+            $this->allocation[$submissionid]->assessmentid  = NULL;
+        }
+
+        // already created assessments
+        $assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($submissions),
+                                                '', 'id,submissionid,userid');
+        
+        foreach ($assessments as $assessmentid => $assessment) {
+            $this->allocation[$assessment->submissionid]->assessmentid  = $assessmentid;
+        }
+    }
+
+
+    public function init() {
+    }
+
+
+    public function ui() {
+        return 'TODO';
+    }
+
+
+}
index 34c9f68d8f4cd155b3e648200e749fd78edb0935..9b926434c31389f3332a721bab17da00efb6df1a 100644 (file)
@@ -28,7 +28,6 @@
  */
 
 require_once(dirname(dirname(dirname(__FILE__))).'/config.php');
-//require_once(dirname(__FILE__).'/lib.php');
 require_once(dirname(__FILE__).'/locallib.php');
 
 if ($preview = optional_param('preview', 0, PARAM_INT)) {
@@ -66,7 +65,9 @@ if ($preview = optional_param('preview', 0, PARAM_INT)) {
 
 require_login($course, false, $cm);
 
-$context = get_context_instance(CONTEXT_MODULE, $cm->id);
+$workshop = new workshop_api($workshop, $cm);
+
+$context = $PAGE->context;
 
 if (isguestuser()) {
     print_error('err_noguests', 'workshop', "$CFG->wwwroot/mod/workshop/view.php?id=$cmid");
@@ -84,7 +85,7 @@ if ($mode == 'preview') {
 $editurl = "{$CFG->wwwroot}/mod/workshop/editform.php?cmid={$cm->id}";
 
 // load the grading strategy logic
-$strategy = workshop_strategy_instance($workshop);
+$strategy = $workshop->grading_strategy_instance();
 
 // load the assessment form definition from the database
 // this must be called before get_assessment_form() where we have to know
index b675ae06bf1cf501d32e4665ddf6a309833eb659..87b9030b8c03471d7cc9b6ca5a70f756810aeca7 100644 (file)
@@ -25,7 +25,6 @@
  */
 
 require_once(dirname(dirname(dirname(__FILE__))).'/config.php');
-require_once(dirname(__FILE__).'/lib.php');
 require_once(dirname(__FILE__).'/locallib.php');
 
 $cmid = required_param('cmid', PARAM_INT);            // course module id
@@ -40,7 +39,7 @@ if (!$course = $DB->get_record('course', array('id' => $cm->course))) {
 
 require_login($course, false, $cm);
 
-$context = get_context_instance(CONTEXT_MODULE, $cm->id);
+$context = $PAGE->context;
 
 if (isguestuser()) {
     print_error('err_noguests', 'workshop', "$CFG->wwwroot/mod/workshop/view.php?id=$cmid");
@@ -50,6 +49,8 @@ if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
     print_error('err_invalidworkshopid', 'workshop');
 }
 
+$workshop = new workshop_api($workshop, $cm)l
+
 // where should the user be sent after closing the editing form
 $returnurl  = "{$CFG->wwwroot}/mod/workshop/view.php?id={$cm->id}";
 // the URL of this editing form
@@ -58,7 +59,7 @@ $selfurl    = "{$CFG->wwwroot}/mod/workshop/editform.php?cmid={$cm->id}";
 $previewurl = "{$CFG->wwwroot}/mod/workshop/assessment.php?preview={$cm->id}";
 
 // load the grading strategy logic
-$strategy = workshop_strategy_instance($workshop);
+$strategy = $workshop->grading_strategy_instance();
 
 // load the assessment form definition from the database
 // this must be called before get_edit_strategy_form() where we have to know
index 61ab3f81c2452962989af8384684de5b87d29143..67c7ee1461cb66a40e844347e3b7f2a86cf1c473 100644 (file)
@@ -40,26 +40,26 @@ $string[''] = '';
 $string[''] = '';
 $string[''] = '';
 $string[''] = '';
-$string[''] = '';
-$string[''] = '';
-$string[''] = '';
-$string[''] = '';
-$string[''] = '';
-$string[''] = '';
-$string[''] = '';
-$string[''] = '';
-$string[''] = '';
-$string[''] = '';
-$string[''] = '';
-$string[''] = '';
 $string['accesscontrol'] = 'Access control';
 $string['addmoredimensionsaccumulative'] = 'Blanks for $a more aspects';
 $string['addmoredimensionsnoerrors'] = 'Blanks for $a more assertions';
+$string['addreviewee'] = 'Add reviewee';
+$string['addreviewer'] = 'Add reviewer';
+$string['addsubmissiontoreview'] = 'Assign submission';
 $string['agreeassessments'] = 'Assessments must be agreed';
 $string['agreeassessmentsdesc'] = 'Authors may comment assessments of their work and agree/disagree with it';
+$string['allocationadded'] = 'The submission has been successfully allocated';
+$string['allocationexists'] = 'The allocation already exists';
+$string['allocationmanual'] = 'Manual allocation';
+$string['allocationrandom'] = 'Random allocation';
+$string['allocation'] = 'Submission allocation';
+$string['allocationview'] = 'View current allocations';
+$string['areyousuretodeallocate'] = 'Are you sure you want deallocate the selected assessment?';
+$string['areyousuretodeallocategraded'] = 'You are going to remove the assessment that has already been graded. Are you really sure you want to do it?';
 $string['assessallexamples'] = 'Assess all examples';
 $string['assessingsubmission'] = 'Assessing submission';
 $string['assessmentcomps'] = 'Required level of assessments similarity';
+$string['assessmentdeleted'] = 'Submission deallocated and assessment deleted';
 $string['assessmentend'] = 'End of assessment phase';
 $string['assessmentform'] = 'Assessment form';
 $string['assessmentsettings'] = 'Assessment settings';
@@ -67,6 +67,7 @@ $string['assessmentstart'] = 'Start of assessment phase';
 $string['assesswosubmission'] = 'Assess without submission';
 $string['assesswosubmissiondesc'] = 'Users can assess peers even without their own submission';
 $string['backtoeditform'] = 'Back to editing form';
+$string['cantassesswosubmission'] = 'Users can\'t assess without own submission in this workshop';
 $string['comparisonhigh'] = 'High';
 $string['comparisonlow'] = 'Low';
 $string['comparisonnormal'] = 'Normal';
@@ -97,10 +98,13 @@ $string['examplesvoluntary'] = 'Assessment of example submission is voluntary';
 $string['gradeforassessment'] = 'Grade for assessment';
 $string['gradeforsubmission'] = 'Grade for submission';
 $string['gradingsettings'] = 'Grading settings';
+$string['chooseuser'] = 'Choose user...';
+$string['iamsure'] = 'Yes, I am sure';
 $string['introduction'] = 'Introduction';
 $string['latesubmissionsdesc'] = 'Allow submitting the work after the deadline';
 $string['latesubmissions'] = 'Late submissions';
 $string['maxbytes'] = 'Maximum file size';
+$string['messageclose'] = '(hide)';
 $string['modulenameplural'] = 'Workshops';
 $string['modulename'] = 'Workshop';
 $string['nattachments'] = 'Maximum number of submission attachments';
@@ -112,7 +116,14 @@ $string['noerrorsgrade1default'] = 'Yes';
 $string['noerrorsgrade1'] = 'Word for the success';
 $string['noerrorsmaperror'] = 'Number of errors is less than or equals';
 $string['noerrorsmapgrade'] = 'Grade for submission';
+$string['nogradeyet'] = 'No grade yet';
+$string['nosubmissionfound'] = 'No submission found for this user';
+$string['nosubmissions'] = 'No submissions yet in this workshop';
+$string['nothingtoreview'] = 'Nothing to review';
 $string['nsassessments'] = 'Number of required assessments of other users\' work';
+$string['participant'] = 'Participant';
+$string['participantrevierof'] = 'Participant is reviewer of';
+$string['participantreviewedby'] = 'Participant is reviewed by';
 $string['percents'] = '$a%';
 $string['previewassessmentform'] = 'Preview';
 $string['releasegrades'] = 'Push final grades into the gradebook';
index 0ab7d0143d4b665a458f434f112ee61f91ca9b4c..73b6aac4f8b28bc2644864be56b13ff0366cb262 100644 (file)
@@ -50,22 +50,73 @@ define('WORKSHOP_COMPARISON_HIGH',      3);     /* f = 3.00 */
 define('WORKSHOP_COMPARISON_VERYHIGH',  4);     /* f = 5.00 */
 
 
+/**
+ * The base class of workshop instances
+ *
+ * The class just wraps the database record from the {workshop} table and adds some
+ * methods that implement the compulsory activity module API.
+ * For full-featured class see {@link workshop_api}.
+ */
+class workshop {
+
+    /** course module record */
+    public $cm;
+
+    /**
+     * Defines methods that are part of any activity module API and may be called by Moodle core
+     *
+     * Initializes the object using the data from DB. Makes deep copy of all $dbrecord properties.
+     *
+     * @param object $instance  The instance data row from {workshop} table
+     * @param object $cm        Course module record
+     */
+    public function __construct(stdClass $instance, stdClass $cm) {
+
+        foreach ($instance as $key => $val) {
+            if (is_object($val) || (is_array($val))) {
+                // this should not happen if the $dbrecord is really just the record returned by $DB
+                $this->{$key} = unserialize(serialize($val));
+            } else {
+                $this->{$key} = $val;
+            }
+        }
+        $this->cm = $cm;
+    }
+
+
+    /**
+     * Saves a new instance of the workshop into the database
+     *
+     * Given an object containing all the necessary data,
+     * (defined by the form in mod_form.php) this function
+     * will save a new instance and return the id number
+     * of the new instance.
+     *
+     * @param object $data An object from the form in mod_form.php
+     * @return int The id of the newly inserted workshop record
+     */
+    public static function add_instance($data) {
+        global $DB;
+
+        $data->timecreated = time();
+        $data->timemodified = $data->timecreated;
+
+        return $DB->insert_record('workshop', $data);
+    }
+}
+
+
 /**
  * Given an object containing all the necessary data,
  * (defined by the form in mod_form.php) this function
  * will create a new instance and return the id number
  * of the new instance.
  *
- * @param object $workshop An object from the form in mod_form.php
+ * @param object $data An object from the form in mod_form.php
  * @return int The id of the newly inserted workshop record
  */
-function workshop_add_instance($workshop) {
-    global $DB;
-
-    $workshop->timecreated = time();
-    $workshop->timemodified = $workshop->timecreated;
-
-    return $DB->insert_record('workshop', $workshop);
+function workshop_add_instance($data) {
+    return workshop::add_instance($data);
 }
 
 
@@ -339,24 +390,6 @@ function workshop_get_strategies() {
 }
 
 
-/**
- * Return an array of the localized allocation names
- * 
- * @access public
- * @return array Array ['string' => 'string']
- */
-function workshop_get_allocations() {
-
-    $installed = get_list_of_plugins('mod/workshop/allocation');
-    $forms = array();
-    foreach ($installed as $allocation) {
-        $forms[$allocation] = get_string('allocation' . $allocation, 'workshop');
-    }
-
-    return $forms;
-}
-
-
 /**
  * Return an array of available example assessment modes
  *
index 9fe1d30c3e03a0b27cefe51fa5573d5c790176e1..8d31faf6e78b6747306a20b8070e5fab7db3d659 100644 (file)
  
  
 /**
- * Library of internal functions for module workshop
+ * Library of internal classes and functions for module workshop
  *
  * All the workshop specific functions, needed to implement the module 
- * logic, should go to here.
+ * logic, should go to here. Instead of having bunch of function named
+ * workshop_something() taking the workshop instance as the first 
+ * parameter, we use a class workshop_api that provides all methods.
  * 
  * @package   mod-workshop
  * @copyright 2009 David Mudrak <david.mudrak@gmail.com>
 
 defined('MOODLE_INTERNAL') || die();
 
-function workshop_strategy_instance($workshop) {
-    /** static variable to hold the singleton */
-    static $instance = null;
+require_once(dirname(__FILE__).'/lib.php'); // we extend this library here
 
-    if (is_null($instance)) {
-        $strategylib = dirname(__FILE__) . '/grading/' . $workshop->strategy . '/strategy.php';
-        if (is_readable($strategylib)) {
-            require_once($strategylib);
+define('WORKSHOP_ALLOCATION_EXISTS',        -1);    // return status of {@link add_allocation}
+define('WORKSHOP_ALLOCATION_WOSUBMISSION',  -2);    // return status of {@link add_allocation}
+
+
+/**
+ * Full-featured workshop API
+ *
+ * This extends the module base API and adds the internal methods that are called 
+ * from the module itself. The class should be initialized right after you get
+ * $workshop and $cm records at the begining of the script.
+ */
+class workshop_api extends workshop {
+
+    /** grading strategy instance */
+    protected $strategy_api=null;
+
+    /**
+     * Initialize the object using the data from DB
+     *
+     * @param object $instance  The instance data row from {workshop} table
+     * @param object $md        Course module record
+     */
+    public function __construct($instance, $cm) {
+        parent::__construct($instance, $cm);
+    }
+
+
+    /**
+     * Fetches all users with the capability mod/workshop:submit in the current context
+     *
+     * Static variable used to cache the results. The returned objects contain id, lastname
+     * and firstname properties and are ordered by lastname,firstname
+     * 
+     * @param object $context The context instance where the capability should be checked
+     * @return array Array of '(int)userid => (stdClass)userinfo'
+     */
+    public function get_peer_authors() {
+        static $users=null;
+
+        if (is_null($users)) {
+            $context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
+            $users = get_users_by_capability($context, 'mod/workshop:submit',
+                        'u.id, u.lastname, u.firstname', 'u.lastname,u.firstname', '', '', '', '', false, false, true);
+        }
+        return $users;
+    }
+
+
+    /**
+     * Fetches all users with the capability mod/workshop:peerassess in the current context
+     *
+     * Static variable used to cache the results. The returned objects contain id, lastname
+     * and firstname properties and are ordered by lastname,firstname
+     * 
+     * @param object $context The context instance where the capability should be checked
+     * @return array Array of '(int)userid => (stdClass)userinfo'
+     */
+    public function get_peer_reviewers() {
+        global $DB;
+        static $users=null;
+
+        if (is_null($users)) {
+            $context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
+            $users = get_users_by_capability($context, 'mod/workshop:peerassess',
+                        'u.id, u.lastname, u.firstname', 'u.lastname,u.firstname', '', '', '', '', false, false, true);
+        }
+        if (!$this->assesswosubmission) {
+            $userswithsubmission = array();
+            // users without their own submission can not be reviewers
+            $rs = $DB->get_recordset_list('workshop_submissions', 'userid', array_keys($users),'', 'id,userid');
+            foreach ($rs as $submission) {
+                if (isset($users[$submission->userid])) {
+                    $userswithsubmission[$submission->userid] = 'submission_exists';
+                } else {
+                    // this is a submission by a user who does not have mod/workshop:peerassess
+                    // this is either bug or workshop capabilities have been overridden after the submission
+                }
+            }
+            $rs->close();
+            return array_intersect_key($users, $userswithsubmission);
         } else {
-            throw new moodle_exception('missingstrategy', 'workshop');
+            return $users;
         }
-        $classname = 'workshop_' . $workshop->strategy . '_strategy';
-        $instance = new $classname($workshop);
     }
 
-    return $instance;
+
+    /**
+     * Returns submissions from this workshop
+     *
+     * Fetches data from {workshop_submissions} and adds some useful information from other
+     * tables.
+     * 
+     * @param mixed $userid If set to integer ID, return submission of the given user only
+     * @param mixed $examples false|true|all Only regular submissions, only examples, all submissions
+     * @todo unittest
+     * @return object moodle_recordset
+     */
+    public function get_submissions($userid='all', $examples=false) {
+        global $DB;
+
+        $sql = 'SELECT s.*, u.lastname AS authorlastname, u.firstname AS authorfirstname
+                FROM {workshop_submissions} s
+                JOIN {user} u ON (s.userid = u.id)
+                WHERE s.workshopid = ?';
+        $params[0] = $this->id;
+
+        if ($examples === false) {
+            $sql .= ' AND example = 0';
+        }
+        if ($examples === true) {
+            $sql .= ' AND example = 1';
+        }
+        if (is_int($userid)) {
+            $sql .= ' AND userid = ?';
+            $params = array_merge($params, array($userid));
+        }
+        if (is_array($userid)) {
+            list($usql, $uparams) = $DB->get_in_or_equal($userid);
+            $sql .= ' AND userid ' . $usql;
+            $params = array_merge($params, $uparams);
+        }
+
+        return $DB->get_recordset_sql($sql, $params);
+    }
+
+
+    /**
+     * Returns the list of assessments with some data added
+     *
+     * Fetches data from {workshop_assessments} and adds some useful information from other
+     * tables.
+      *
+     * @param mixed $reviewerid 'all'|int|array User ID of the reviewer
+     * @param mixed $id         'all'|int Assessment ID
+     * @return object moodle_recordset
+     */
+    public function get_assessments($reviewerid='all', $id='all') {
+        global $DB;
+        
+        $sql = 'SELECT  a.*,
+                        reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname,
+                        s.title,
+                        author.id AS authorid, author.firstname AS authorfirstname,author.lastname as authorlastname
+                FROM {workshop_assessments} a
+                LEFT JOIN {user} reviewer ON (a.userid = reviewer.id)
+                LEFT JOIN {workshop_submissions} s ON (a.submissionid = s.id)
+                LEFT JOIN {user} author ON (s.userid = author.id)
+                WHERE s.workshopid = ?';
+        $params = array($this->id);
+        if (is_int($reviewerid)) {
+            $sql .= ' AND reviewerid = ?';
+            $params = array_merge($params, array($reviewerid));
+        }
+        if (is_array($reviewerid)) {
+            list($usql, $uparams) = $DB->get_in_or_equal($reviewerid);
+            $sql .= ' AND reviewerid ' . $usql;
+            $params = array_merge($params, $uparams);
+        }
+        if (is_int($id)) {
+            $sql .= ' AND a.id = ?';
+            $params = array_merge($params, array($id));
+        }
+
+        return $DB->get_recordset_sql($sql, $params);
+    }
+
+
+    /**
+     * Returns the list of allocations in the workshop
+     *
+     * This returns the list of all users who can submit their work or review submissions (or both
+     * which is the common case). So basically this is to return list of all students participating
+     * in the workshop. For every participant, it adds information about their submission and their
+     * reviews. This is mainly intended for allocation reports and originally was written for 
+     * manula allocation ui.
+     *
+     * The returned structure is recordset of objects with following properties:
+     * [authorid] [authorfirstname] [authorlastname] [authorpicture] [authorimagealt]
+     * [submissionid] [submissiontitle] [submissiongrade] [assessmentid]
+     * [timeallocated] [reviewerid] [reviewerfirstname] [reviewerlastname] 
+     * [reviewerpicture] [reviewerimagealt]
+     *
+     * This should be refactored when capability handling proposed by Petr is implemented so that
+     * we can check capabilities directly in SQL joins.
+     *
+     * @return object moodle_recordset
+     */
+    public function get_allocations() {
+        global $DB;
+        static $users=null;
+
+        if (is_null($users)) {
+            $context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
+            $users = get_users_by_capability($context, array('mod/workshop:submit', 'mod/workshop:peerassess'),
+                        'u.id', 'u.lastname,u.firstname', '', '', '', '', false, false, true);
+        }
+
+        list($usql, $params) = $DB->get_in_or_equal(array_keys($users));
+        $params[] = $this->id;
+
+        $sql = 'SELECT  author.id AS authorid, author.firstname AS authorfirstname, author.lastname AS authorlastname, 
+                        author.picture AS authorpicture, author.imagealt AS authorimagealt,
+                        s.id AS submissionid, s.title AS submissiontitle, s.grade AS submissiongrade, 
+                        a.id AS assessmentid, a.timecreated AS timeallocated, a.userid AS reviewerid, 
+                        reviewer.firstname AS reviewerfirstname, reviewer.lastname AS reviewerlastname,
+                        reviewer.picture as reviewerpicture, reviewer.imagealt AS reviewerimagealt
+                FROM {user} author
+                    LEFT JOIN {workshop_submissions} s ON (s.userid = author.id)
+                    LEFT JOIN {workshop_assessments} a ON (s.id = a.submissionid)
+                    LEFT JOIN {user} reviewer ON (a.userid = reviewer.id)
+                WHERE author.id ' . $usql . ' AND (s.workshopid = ? OR s.workshopid IS NULL)
+                ORDER BY author.lastname,author.firstname,reviewer.lastname,reviewer.firstname';
+        return $DB->get_recordset_sql($sql, $params);
+    }
+
+
+    /**
+     * Allocate a submission to a user for review
+     * 
+     * @param object $submission Submission record
+     * @param int $reviewerid User ID
+     * @return int ID of the new assessment or an error code
+     */
+    public function add_allocation(stdClass $submission, $reviewerid) {
+        global $DB;
+
+        if ($DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'userid' => $reviewerid))) {
+            return WORKSHOP_ALLOCATION_EXISTS;
+        }
+
+        if (!$this->assesswosubmission) {
+            // reviewer must have submitted his own work
+            if (!$DB->record_exists('workshop_submissions', array('workshopid' => $this->id, 'userid' => $reviewerid))) {
+                return WORKSHOP_ALLOCATION_WOSUBMISSION;
+            }
+        }
+
+        $now = time();
+        $assessment = new stdClass();
+        $assessment->submissionid = $submission->id;
+        $assessment->userid         = $reviewerid;
+        $assessment->timecreated    = $now;
+        $assessment->timemodified   = $now;
+
+        return $DB->insert_record('workshop_assessments', $assessment);
+    }
+
+
+    /**
+     * delete_assessment 
+     *
+     * @todo finish and document this method
+     * 
+     */
+    public function delete_assessment($id) {
+        global $DB;
+
+        // todo remove all given grades from workshop_grades;
+        return $DB->delete_records('workshop_assessments', array('id' => $id));
+    }
+
+
+    /**
+     * Returns instance of grading strategy class
+     * 
+     * @param object $workshop Workshop record
+     * @return object Instance of a grading strategy
+     */
+    public function grading_strategy_instance() {
+
+        if (!($this->strategy === clean_param($workshop->strategy, PARAM_ALPHA))) {
+            throw new moodle_workshop_exception($this, 'invalidstrategyname');
+        }
+
+        if (is_null($this->strategy_api)) {
+            $strategylib = dirname(__FILE__) . '/grading/' . $workshop->strategy . '/strategy.php';
+            if (is_readable($strategylib)) {
+                require_once($strategylib);
+            } else {
+                throw new moodle_exception('missingstrategy', 'workshop');
+            }
+            $classname = 'workshop_' . $workshop->strategy . '_strategy';
+            $this->strategy_api = new $classname($this);
+            if (!in_array('workshop_strategy', class_implements($this->strategy_api))) {
+                throw new moodle_workshop_exception($this, 'strategynotimplemented');
+            }
+        }
+
+        return $this->strategy_api;
+    }
+
+
+
 }
 
 
+
+
 /**
- * Return the user's submission record in the given workshop
+ * Class for workshop exceptions. Just saves a couple of arguments of the
+ * constructor for a moodle_exception.
  *
- * Example submissions are not returned. This is intended to return a submission for given
- * student.
- * 
- * @param int $workshopid Workshop id
- * @param int $userid Owner id
- * @return mixed A fieldset object containing the first matching record or false if not found
+ * @param object $workshop Should be workshop or its subclass
+ * @param string $errorcode
+ * @param mixed $a Object/variable to pass to get_string
+ * @param string $link URL to continue after the error notice
+ * @param $debuginfo
  */
-function workshop_get_user_submission($workshopid, $userid) {
-    global $DB;
+class moodle_workshop_exception extends moodle_exception {
 
-    return $DB->get_record('workshop_submissions', array('workshopid' => $workshopid, 'userid' => $userid, 'example' => 0));
+    function __construct($workshop, $errorcode, $a = NULL, $link = '', $debuginfo = null) {
+        global $CFG;
+
+        if (!$link) {
+            $link = $CFG->wwwroot . '/mod/workshop/view.php?a=' . $workshop->id;
+        }
+        if ('confirmsesskeybad' == $errorcode) {
+            $module = '';
+        } else {
+            $module = 'workshop';
+        }
+        parent::__construct($errorcode, $module, $link, $a, $debuginfo);
+    }
 }
 
 
+
index 4b8b431ae73f3c2f77f75cb762686acca3cbd1a5..7496308564a43cf53b74c8a4c42dd3cec612ce62 100644 (file)
@@ -24,7 +24,7 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-defined('MOODLE_INTERNAL')) || die();
+defined('MOODLE_INTERNAL') || die();
  
 // Make sure the code being tested is accessible.
 require_once($CFG->dirroot . '/mod/workshop/lib.php'); // Include the code to test
diff --git a/mod/workshop/simpletest/testworkshopapi.php b/mod/workshop/simpletest/testworkshopapi.php
new file mode 100644 (file)
index 0000000..3777d8c
--- /dev/null
@@ -0,0 +1,65 @@
+<?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/>.
+/**
+ * Unit tests for workshop_api class defined in mod/workshop/locallib.php
+ *
+ * @package   mod-workshop
+ * @copyright 2009 David Mudrak <david.mudrak@gmail.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+// Make sure the code being tested is accessible.
+require_once($CFG->dirroot . '/mod/workshop/locallib.php'); // Include the code to test
+
+/**
+ * Test subclass that makes all the protected methods we want to test public.
+ * Also re-implements bridging methods so we can test more easily.
+ */
+class testable_workshop_api extends workshop_api {
+
+}
+
+
+/** 
+ * Test cases for the internal workshop api
+ */
+class workshop_api_test extends UnitTestCase {
+
+    /** workshop instance emulation */
+    protected $workshop;
+
+    /** setup testing environment */
+    public function setUp() {
+        $workshoprecord         = new stdClass;
+        $workshoprecord->id     = 42;
+
+        $cm                     = new stdClass;
+        $this->workshop = new testable_workshop_api($workshoprecord, $cm);
+    }
+
+    public function tearDown() {
+        $this->workshop = null;
+    }
+
+
+}
index a1c06b40aec092c4755b0b4f97252c5b940e9376..766051e32408447932f6b241513a5055c43a1baa 100644 (file)
@@ -61,7 +61,7 @@ if ($id) { // submission is specified
 
 } else { // no submission specified
     //todo require_capability('mod/workshop:submit', $context);
-    if (!$submission = workshop_get_user_submission($workshop->id, $USER->id)) {
+    if (!$submission = workshop_get_user_submission($workshop, $USER->id)) {
         $submission = new object();
         $submission->id = null;
     } 
@@ -84,14 +84,8 @@ $submission->cmid = $cm->id;
 $mform = new workshop_submission_form(null, array('current' => $submission, 'cm' => $cm, 'workshop'=>$workshop,
                                                  'dataoptions' => $dataoptions, 'attachmentoptions'=>$attachmentoptions));
 
-if ($mform->is_cancelled()){
-    die(); // todo
-    if ($id){
-        redirect("view.php?id=$cm->id");
-    } else {
-        redirect("view.php?id=$cm->id");
-    }
-
+if ($mform->is_cancelled()) {
+    redirect("view.php?id=$cm->id");
 } else if ($submission = $mform->get_data()) {
 
     $timenow = time();
index cc507f90307729f007c0031b12f07acf11a2c3ca..871c167f2888fcc0e56fbb65dcd4e4b12651c479 100644 (file)
@@ -28,7 +28,6 @@
  */
 
 require_once(dirname(dirname(dirname(__FILE__))).'/config.php');
-require_once(dirname(__FILE__).'/lib.php');
 require_once(dirname(__FILE__).'/locallib.php');
 
 $id = optional_param('id', 0, PARAM_INT); // course_module ID, or
@@ -64,13 +63,18 @@ if ($id) {
 
 require_login($course, true, $cm);
 
+$context = $PAGE->context;
+$workshop = new workshop_api($workshop, $cm);
+
+// todo has_capability() check
+
 add_to_log($course->id, "workshop", "view", "view.php?id=$cm->id", "$workshop->id");
 
 /// Print the page header
 
 $PAGE->set_url('mod/workshop/view.php', array('id' => $cm->id));
 $PAGE->set_title($workshop->name);
-$PAGE->set_heading($course->shortname);
+$PAGE->set_heading($course->fullname);
 $PAGE->set_button(update_module_button($cm->id, $course->id, get_string('modulename', 'workshop')));
 
 // other things you may want to set - remove if not needed
@@ -94,7 +98,10 @@ echo $OUTPUT->header($navigation, $menu);
 
 echo $OUTPUT->box_start();
 echo $OUTPUT->heading('Workshop administration tools', 3);
-echo "<a href=\"editform.php?cmid={$cm->id}\">Edit grading form (".get_string('strategy' . $workshop->strategy, 'workshop').")</a>";
+echo '<ul>';
+echo "<li><a href=\"editform.php?cmid={$cm->id}\">Edit grading form (".get_string('strategy' . $workshop->strategy, 'workshop').")</a></li>";
+echo "<li><a href=\"allocation.php?cmid={$cm->id}\">Allocate submissions</a></li>";
+echo '</ul>';
 echo $OUTPUT->box_end();
 
 echo $OUTPUT->box_start();
@@ -105,13 +112,14 @@ echo $OUTPUT->box_end();
 echo $OUTPUT->box_start();
 echo $OUTPUT->heading(get_string('assessment', 'workshop'), 3);
 
-$reviewstogive = workshop_get_assessments_for_reviewer($workshop->id, $USER->id);
+$rs = $workshop->get_assessments($USER->id);
 echo "You are expected to assess following submissions:";
 echo "<ul>";
-foreach ($reviewstogive as $review) {
-    echo "<li><a href=\"assessment.php?asid={$review->assessmentid}\">Assessment of '{$review->title}' by {$review->authorid}</a></li>";
+foreach ($rs as $assessment) {
+    echo "<li><a href=\"assessment.php?asid={$assessment->id}\">Assessment of '{$assessment->title}' by {$assessment->authorid}</a></li>";
 }
 echo "</ul>";
+$rs->close();
 echo $OUTPUT->box_end();
 
 echo $OUTPUT->footer();