MDL-19717 First drafts of allocation support
[moodle.git] / mod / workshop / locallib.php
CommitLineData
de811c0c
DM
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18
19/**
6e309973 20 * Library of internal classes and functions for module workshop
de811c0c
DM
21 *
22 * All the workshop specific functions, needed to implement the module
6e309973
DM
23 * logic, should go to here. Instead of having bunch of function named
24 * workshop_something() taking the workshop instance as the first
25 * parameter, we use a class workshop_api that provides all methods.
de811c0c
DM
26 *
27 * @package mod-workshop
28 * @copyright 2009 David Mudrak <david.mudrak@gmail.com>
29 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
30 */
31
32defined('MOODLE_INTERNAL') || die();
33
6e309973 34require_once(dirname(__FILE__).'/lib.php'); // we extend this library here
0968b1a3 35
6e309973
DM
36define('WORKSHOP_ALLOCATION_EXISTS', -1); // return status of {@link add_allocation}
37define('WORKSHOP_ALLOCATION_WOSUBMISSION', -2); // return status of {@link add_allocation}
38
39
40/**
41 * Full-featured workshop API
42 *
43 * This extends the module base API and adds the internal methods that are called
44 * from the module itself. The class should be initialized right after you get
45 * $workshop and $cm records at the begining of the script.
46 */
47class workshop_api extends workshop {
48
49 /** grading strategy instance */
50 protected $strategy_api=null;
51
52 /**
53 * Initialize the object using the data from DB
54 *
55 * @param object $instance The instance data row from {workshop} table
56 * @param object $md Course module record
57 */
58 public function __construct($instance, $cm) {
59 parent::__construct($instance, $cm);
60 }
61
62
63 /**
64 * Fetches all users with the capability mod/workshop:submit in the current context
65 *
66 * Static variable used to cache the results. The returned objects contain id, lastname
67 * and firstname properties and are ordered by lastname,firstname
68 *
69 * @param object $context The context instance where the capability should be checked
70 * @return array Array of '(int)userid => (stdClass)userinfo'
71 */
72 public function get_peer_authors() {
73 static $users=null;
74
75 if (is_null($users)) {
76 $context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
77 $users = get_users_by_capability($context, 'mod/workshop:submit',
78 'u.id, u.lastname, u.firstname', 'u.lastname,u.firstname', '', '', '', '', false, false, true);
79 }
80 return $users;
81 }
82
83
84 /**
85 * Fetches all users with the capability mod/workshop:peerassess in the current context
86 *
87 * Static variable used to cache the results. The returned objects contain id, lastname
88 * and firstname properties and are ordered by lastname,firstname
89 *
90 * @param object $context The context instance where the capability should be checked
91 * @return array Array of '(int)userid => (stdClass)userinfo'
92 */
93 public function get_peer_reviewers() {
94 global $DB;
95 static $users=null;
96
97 if (is_null($users)) {
98 $context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
99 $users = get_users_by_capability($context, 'mod/workshop:peerassess',
100 'u.id, u.lastname, u.firstname', 'u.lastname,u.firstname', '', '', '', '', false, false, true);
101 }
102 if (!$this->assesswosubmission) {
103 $userswithsubmission = array();
104 // users without their own submission can not be reviewers
105 $rs = $DB->get_recordset_list('workshop_submissions', 'userid', array_keys($users),'', 'id,userid');
106 foreach ($rs as $submission) {
107 if (isset($users[$submission->userid])) {
108 $userswithsubmission[$submission->userid] = 'submission_exists';
109 } else {
110 // this is a submission by a user who does not have mod/workshop:peerassess
111 // this is either bug or workshop capabilities have been overridden after the submission
112 }
113 }
114 $rs->close();
115 return array_intersect_key($users, $userswithsubmission);
0968b1a3 116 } else {
6e309973 117 return $users;
0968b1a3 118 }
0968b1a3
DM
119 }
120
6e309973
DM
121
122 /**
123 * Returns submissions from this workshop
124 *
125 * Fetches data from {workshop_submissions} and adds some useful information from other
126 * tables.
127 *
128 * @param mixed $userid If set to integer ID, return submission of the given user only
129 * @param mixed $examples false|true|all Only regular submissions, only examples, all submissions
130 * @todo unittest
131 * @return object moodle_recordset
132 */
133 public function get_submissions($userid='all', $examples=false) {
134 global $DB;
135
136 $sql = 'SELECT s.*, u.lastname AS authorlastname, u.firstname AS authorfirstname
137 FROM {workshop_submissions} s
138 JOIN {user} u ON (s.userid = u.id)
139 WHERE s.workshopid = ?';
140 $params[0] = $this->id;
141
142 if ($examples === false) {
143 $sql .= ' AND example = 0';
144 }
145 if ($examples === true) {
146 $sql .= ' AND example = 1';
147 }
148 if (is_int($userid)) {
149 $sql .= ' AND userid = ?';
150 $params = array_merge($params, array($userid));
151 }
152 if (is_array($userid)) {
153 list($usql, $uparams) = $DB->get_in_or_equal($userid);
154 $sql .= ' AND userid ' . $usql;
155 $params = array_merge($params, $uparams);
156 }
157
158 return $DB->get_recordset_sql($sql, $params);
159 }
160
161
162 /**
163 * Returns the list of assessments with some data added
164 *
165 * Fetches data from {workshop_assessments} and adds some useful information from other
166 * tables.
167 *
168 * @param mixed $reviewerid 'all'|int|array User ID of the reviewer
169 * @param mixed $id 'all'|int Assessment ID
170 * @return object moodle_recordset
171 */
172 public function get_assessments($reviewerid='all', $id='all') {
173 global $DB;
174
175 $sql = 'SELECT a.*,
176 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname,
177 s.title,
178 author.id AS authorid, author.firstname AS authorfirstname,author.lastname as authorlastname
179 FROM {workshop_assessments} a
180 LEFT JOIN {user} reviewer ON (a.userid = reviewer.id)
181 LEFT JOIN {workshop_submissions} s ON (a.submissionid = s.id)
182 LEFT JOIN {user} author ON (s.userid = author.id)
183 WHERE s.workshopid = ?';
184 $params = array($this->id);
185 if (is_int($reviewerid)) {
186 $sql .= ' AND reviewerid = ?';
187 $params = array_merge($params, array($reviewerid));
188 }
189 if (is_array($reviewerid)) {
190 list($usql, $uparams) = $DB->get_in_or_equal($reviewerid);
191 $sql .= ' AND reviewerid ' . $usql;
192 $params = array_merge($params, $uparams);
193 }
194 if (is_int($id)) {
195 $sql .= ' AND a.id = ?';
196 $params = array_merge($params, array($id));
197 }
198
199 return $DB->get_recordset_sql($sql, $params);
200 }
201
202
203 /**
204 * Returns the list of allocations in the workshop
205 *
206 * This returns the list of all users who can submit their work or review submissions (or both
207 * which is the common case). So basically this is to return list of all students participating
208 * in the workshop. For every participant, it adds information about their submission and their
209 * reviews. This is mainly intended for allocation reports and originally was written for
210 * manula allocation ui.
211 *
212 * The returned structure is recordset of objects with following properties:
213 * [authorid] [authorfirstname] [authorlastname] [authorpicture] [authorimagealt]
214 * [submissionid] [submissiontitle] [submissiongrade] [assessmentid]
215 * [timeallocated] [reviewerid] [reviewerfirstname] [reviewerlastname]
216 * [reviewerpicture] [reviewerimagealt]
217 *
218 * This should be refactored when capability handling proposed by Petr is implemented so that
219 * we can check capabilities directly in SQL joins.
220 *
221 * @return object moodle_recordset
222 */
223 public function get_allocations() {
224 global $DB;
225 static $users=null;
226
227 if (is_null($users)) {
228 $context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
229 $users = get_users_by_capability($context, array('mod/workshop:submit', 'mod/workshop:peerassess'),
230 'u.id', 'u.lastname,u.firstname', '', '', '', '', false, false, true);
231 }
232
233 list($usql, $params) = $DB->get_in_or_equal(array_keys($users));
234 $params[] = $this->id;
235
236 $sql = 'SELECT author.id AS authorid, author.firstname AS authorfirstname, author.lastname AS authorlastname,
237 author.picture AS authorpicture, author.imagealt AS authorimagealt,
238 s.id AS submissionid, s.title AS submissiontitle, s.grade AS submissiongrade,
239 a.id AS assessmentid, a.timecreated AS timeallocated, a.userid AS reviewerid,
240 reviewer.firstname AS reviewerfirstname, reviewer.lastname AS reviewerlastname,
241 reviewer.picture as reviewerpicture, reviewer.imagealt AS reviewerimagealt
242 FROM {user} author
243 LEFT JOIN {workshop_submissions} s ON (s.userid = author.id)
244 LEFT JOIN {workshop_assessments} a ON (s.id = a.submissionid)
245 LEFT JOIN {user} reviewer ON (a.userid = reviewer.id)
246 WHERE author.id ' . $usql . ' AND (s.workshopid = ? OR s.workshopid IS NULL)
247 ORDER BY author.lastname,author.firstname,reviewer.lastname,reviewer.firstname';
248 return $DB->get_recordset_sql($sql, $params);
249 }
250
251
252 /**
253 * Allocate a submission to a user for review
254 *
255 * @param object $submission Submission record
256 * @param int $reviewerid User ID
257 * @return int ID of the new assessment or an error code
258 */
259 public function add_allocation(stdClass $submission, $reviewerid) {
260 global $DB;
261
262 if ($DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'userid' => $reviewerid))) {
263 return WORKSHOP_ALLOCATION_EXISTS;
264 }
265
266 if (!$this->assesswosubmission) {
267 // reviewer must have submitted his own work
268 if (!$DB->record_exists('workshop_submissions', array('workshopid' => $this->id, 'userid' => $reviewerid))) {
269 return WORKSHOP_ALLOCATION_WOSUBMISSION;
270 }
271 }
272
273 $now = time();
274 $assessment = new stdClass();
275 $assessment->submissionid = $submission->id;
276 $assessment->userid = $reviewerid;
277 $assessment->timecreated = $now;
278 $assessment->timemodified = $now;
279
280 return $DB->insert_record('workshop_assessments', $assessment);
281 }
282
283
284 /**
285 * delete_assessment
286 *
287 * @todo finish and document this method
288 *
289 */
290 public function delete_assessment($id) {
291 global $DB;
292
293 // todo remove all given grades from workshop_grades;
294 return $DB->delete_records('workshop_assessments', array('id' => $id));
295 }
296
297
298 /**
299 * Returns instance of grading strategy class
300 *
301 * @param object $workshop Workshop record
302 * @return object Instance of a grading strategy
303 */
304 public function grading_strategy_instance() {
305
306 if (!($this->strategy === clean_param($workshop->strategy, PARAM_ALPHA))) {
307 throw new moodle_workshop_exception($this, 'invalidstrategyname');
308 }
309
310 if (is_null($this->strategy_api)) {
311 $strategylib = dirname(__FILE__) . '/grading/' . $workshop->strategy . '/strategy.php';
312 if (is_readable($strategylib)) {
313 require_once($strategylib);
314 } else {
315 throw new moodle_exception('missingstrategy', 'workshop');
316 }
317 $classname = 'workshop_' . $workshop->strategy . '_strategy';
318 $this->strategy_api = new $classname($this);
319 if (!in_array('workshop_strategy', class_implements($this->strategy_api))) {
320 throw new moodle_workshop_exception($this, 'strategynotimplemented');
321 }
322 }
323
324 return $this->strategy_api;
325 }
326
327
328
0968b1a3
DM
329}
330
331
6e309973
DM
332
333
de811c0c 334/**
6e309973
DM
335 * Class for workshop exceptions. Just saves a couple of arguments of the
336 * constructor for a moodle_exception.
de811c0c 337 *
6e309973
DM
338 * @param object $workshop Should be workshop or its subclass
339 * @param string $errorcode
340 * @param mixed $a Object/variable to pass to get_string
341 * @param string $link URL to continue after the error notice
342 * @param $debuginfo
de811c0c 343 */
6e309973 344class moodle_workshop_exception extends moodle_exception {
de811c0c 345
6e309973
DM
346 function __construct($workshop, $errorcode, $a = NULL, $link = '', $debuginfo = null) {
347 global $CFG;
348
349 if (!$link) {
350 $link = $CFG->wwwroot . '/mod/workshop/view.php?a=' . $workshop->id;
351 }
352 if ('confirmsesskeybad' == $errorcode) {
353 $module = '';
354 } else {
355 $module = 'workshop';
356 }
357 parent::__construct($errorcode, $module, $link, $a, $debuginfo);
358 }
de811c0c
DM
359}
360
361
6e309973 362