weekly release 2.8dev
[moodle.git] / grade / export / lib.php
CommitLineData
e060e33d 1<?php
c10ddfb3 2
e060e33d 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/>.
ce34ed3a 17
89bd8357 18require_once($CFG->dirroot.'/lib/gradelib.php');
19require_once($CFG->dirroot.'/grade/lib.php');
a5bc4e6e 20require_once($CFG->dirroot.'/grade/export/grade_export_form.php');
0eeada79 21
1b074625 22/**
23 * Base export class
24 */
5c75a0a3 25abstract class grade_export {
ba74762b 26
5c75a0a3 27 public $plugin; // plgin name - must be filled in subclasses!
caffc55a 28
5c75a0a3 29 public $grade_items; // list of all course grade items
30 public $groupid; // groupid, 0 means all groups
31 public $course; // course object
32 public $columns; // array of grade_items selected for export
caffc55a 33
5c75a0a3 34 public $export_letters; // export letters
35 public $export_feedback; // export feedback
36 public $userkey; // export using private user key
caffc55a 37
5c75a0a3 38 public $updatedgradesonly; // only export updated grades
39 public $displaytype; // display type (e.g. real, percentages, letter) for exports
40 public $decimalpoints; // number of decimal points for exports
78ab98bc 41 public $onlyactive; // only include users with an active enrolment
61c8e0d7
FM
42 public $usercustomfields; // include users custom fields
43
c10ddfb3 44 /**
cca51baa
DW
45 * @deprecated since Moodle 2.8
46 * @var $previewrows Number of rows in preview.
47 */
48 public $previewrows;
49
50 /**
51 * Constructor should set up all the private variables ready to be pulled.
52 *
53 * This constructor used to accept the individual parameters as separate arguments, in
54 * 2.8 this was simplified to just accept the data from the moodle form.
55 *
5c75a0a3 56 * @access public
caffc55a 57 * @param object $course
cca51baa
DW
58 * @param int $groupid
59 * @param stdClass|null $formdata
0eeada79 60 * @note Exporting as letters will lead to data loss if that exported set it re-imported.
c10ddfb3 61 */
cca51baa
DW
62 public function __construct($course, $groupid, $formdata) {
63 if (func_num_args() != 3 || ($formdata != null && get_class($formdata) != "stdClass")) {
64 $args = func_get_args();
65 return call_user_func_array(array($this, "deprecated_constructor"), $args);
66 }
caffc55a 67 $this->course = $course;
68 $this->groupid = $groupid;
cca51baa 69
caffc55a 70 $this->grade_items = grade_item::fetch_all(array('courseid'=>$this->course->id));
71
cca51baa
DW
72 $this->process_form($formdata);
73 }
74
75 /**
76 * Old deprecated constructor.
77 *
78 * This deprecated constructor accepts the individual parameters as separate arguments, in
79 * 2.8 this was simplified to just accept the data from the moodle form.
80 *
81 * @deprecated since 2.8 MDL-46548. Instead call the shortened constructor which accepts the data
82 * directly from the grade_export_form.
83 */
84 protected function deprecated_constructor($course,
85 $groupid=0,
86 $itemlist='',
87 $export_feedback=false,
88 $updatedgradesonly = false,
89 $displaytype = GRADE_DISPLAY_TYPE_REAL,
90 $decimalpoints = 2,
91 $onlyactive = false,
92 $usercustomfields = false) {
93
94 debugging('Many argument constructor for class "grade_export" is deprecated. Call the 3 argument version instead.', DEBUG_DEVELOPER);
95
96 $this->course = $course;
97 $this->groupid = $groupid;
98
99 $this->grade_items = grade_item::fetch_all(array('courseid'=>$this->course->id));
112fdf47
AD
100 //Populating the columns here is required by /grade/export/(whatever)/export.php
101 //however index.php, when the form is submitted, will construct the collection here
102 //with an empty $itemlist then reconstruct it in process_form() using $formdata
caffc55a 103 $this->columns = array();
104 if (!empty($itemlist)) {
112fdf47
AD
105 if ($itemlist=='-1') {
106 //user deselected all items
107 } else {
108 $itemids = explode(',', $itemlist);
109 // remove items that are not requested
110 foreach ($itemids as $itemid) {
111 if (array_key_exists($itemid, $this->grade_items)) {
112 $this->columns[$itemid] =& $this->grade_items[$itemid];
113 }
caffc55a 114 }
115 }
116 } else {
117 foreach ($this->grade_items as $itemid=>$unused) {
118 $this->columns[$itemid] =& $this->grade_items[$itemid];
119 }
120 }
0e2d708e 121
caffc55a 122 $this->export_feedback = $export_feedback;
123 $this->userkey = '';
124 $this->previewrows = false;
59a7447a 125 $this->updatedgradesonly = $updatedgradesonly;
d24832f9 126
864d1f8c 127 $this->displaytype = $displaytype;
128 $this->decimalpoints = $decimalpoints;
78ab98bc 129 $this->onlyactive = $onlyactive;
61c8e0d7 130 $this->usercustomfields = $usercustomfields;
caffc55a 131 }
132
133 /**
134 * Init object based using data from form
135 * @param object $formdata
136 */
137 function process_form($formdata) {
138 global $USER;
139
140 $this->columns = array();
141 if (!empty($formdata->itemids)) {
112fdf47
AD
142 if ($formdata->itemids=='-1') {
143 //user deselected all items
144 } else {
145 foreach ($formdata->itemids as $itemid=>$selected) {
146 if ($selected and array_key_exists($itemid, $this->grade_items)) {
147 $this->columns[$itemid] =& $this->grade_items[$itemid];
148 }
caffc55a 149 }
150 }
151 } else {
152 foreach ($this->grade_items as $itemid=>$unused) {
153 $this->columns[$itemid] =& $this->grade_items[$itemid];
154 }
0e2d708e 155 }
156
0e2d708e 157 if (isset($formdata->key)) {
caffc55a 158 if ($formdata->key == 1 && isset($formdata->iprestriction) && isset($formdata->validuntil)) {
159 // Create a new key
160 $formdata->key = create_user_key('grade/export', $USER->id, $this->course->id, $formdata->iprestriction, $formdata->validuntil);
0e2d708e 161 }
162 $this->userkey = $formdata->key;
163 }
ba74762b 164
cca51baa
DW
165 if (isset($formdata->decimals)) {
166 $this->decimalpoints = $formdata->decimals;
167 }
168
caffc55a 169 if (isset($formdata->export_letters)) {
170 $this->export_letters = $formdata->export_letters;
c10ddfb3 171 }
ba74762b 172
caffc55a 173 if (isset($formdata->export_feedback)) {
174 $this->export_feedback = $formdata->export_feedback;
175 }
c10ddfb3 176
78ab98bc
AD
177 if (isset($formdata->export_onlyactive)) {
178 $this->onlyactive = $formdata->export_onlyactive;
179 }
180
caffc55a 181 if (isset($formdata->previewrows)) {
182 $this->previewrows = $formdata->previewrows;
0bfbab47 183 }
184
924cb72b
DW
185 if (isset($formdata->display)) {
186 $this->displaytype = $formdata->display;
187 }
188
189 if (isset($formdata->updatedgradesonly)) {
190 $this->updatedgradesonly = $formdata->updatedgradesonly;
191 }
caffc55a 192 }
193
194 /**
195 * Update exported field in grade_grades table
196 * @return boolean
197 */
5c75a0a3 198 public function track_exports() {
caffc55a 199 global $CFG;
200
201 /// Whether this plugin is entitled to update export time
202 if ($expplugins = explode(",", $CFG->gradeexport)) {
203 if (in_array($this->plugin, $expplugins)) {
204 return true;
11745964 205 } else {
caffc55a 206 return false;
207 }
208 } else {
209 return false;
11745964 210 }
caffc55a 211 }
b8ff92b6 212
caffc55a 213 /**
214 * Returns string representation of final grade
215 * @param $object $grade instance of grade_grade class
216 * @return string
217 */
5c75a0a3 218 public function format_grade($grade) {
864d1f8c 219 return grade_format_gradevalue($grade->finalgrade, $this->grade_items[$grade->itemid], false, $this->displaytype, $this->decimalpoints);
11745964 220 }
d9617050 221
caffc55a 222 /**
223 * Returns the name of column in export
224 * @param object $grade_item
225 * @param boolena $feedback feedback colum
226 * &return string
227 */
5c75a0a3 228 public function format_column_name($grade_item, $feedback=false) {
caffc55a 229 if ($grade_item->itemtype == 'mod') {
995f2d51 230 $name = get_string('modulename', $grade_item->itemmodule).get_string('labelsep', 'langconfig').$grade_item->get_name();
f67e327e 231 } else {
caffc55a 232 $name = $grade_item->get_name();
d9617050 233 }
ba74762b 234
caffc55a 235 if ($feedback) {
236 $name .= ' ('.get_string('feedback').')';
ba74762b 237 }
caffc55a 238
70be4430 239 return html_to_text($name, 0, false);
c10ddfb3 240 }
ba74762b 241
c10ddfb3 242 /**
caffc55a 243 * Returns formatted grade feedback
244 * @param object $feedback object with properties feedback and feedbackformat
245 * @return string
c10ddfb3 246 */
5c75a0a3 247 public function format_feedback($feedback) {
caffc55a 248 return strip_tags(format_text($feedback->feedback, $feedback->feedbackformat));
249 }
ba74762b 250
1b074625 251 /**
caffc55a 252 * Implemented by child class
1b074625 253 */
5c75a0a3 254 public abstract function print_grades();
11745964 255
caffc55a 256 /**
257 * Prints preview of exported grades on screen as a feedback mechanism
30f188f0 258 * @param bool $require_user_idnumber true means skip users without idnumber
cca51baa 259 * @deprecated since 2.8 MDL-46548. Previews are not useful on export.
caffc55a 260 */
30f188f0 261 public function display_preview($require_user_idnumber=false) {
c018f973 262 global $OUTPUT;
61c8e0d7 263
cca51baa
DW
264 debugging('function grade_export::display_preview is deprecated.', DEBUG_DEVELOPER);
265
61c8e0d7
FM
266 $userprofilefields = grade_helper::get_user_profile_fields($this->course->id, $this->usercustomfields);
267 $formatoptions = new stdClass();
268 $formatoptions->para = false;
269
c018f973 270 echo $OUTPUT->heading(get_string('previewrows', 'grades'));
567883c8 271
f5f4967e 272 echo '<table>';
273 echo '<tr>';
61c8e0d7 274 foreach ($userprofilefields as $field) {
293f42b6
CF
275 echo '<th>' . $field->fullname . '</th>';
276 }
38c1dd19
RT
277 if (!$this->onlyactive) {
278 echo '<th>'.get_string("suspended")."</th>";
279 }
caffc55a 280 foreach ($this->columns as $grade_item) {
281 echo '<th>'.$this->format_column_name($grade_item).'</th>';
ba74762b 282
283 /// add a column_feedback column
caffc55a 284 if ($this->export_feedback) {
285 echo '<th>'.$this->format_column_name($grade_item, true).'</th>';
ba74762b 286 }
1b074625 287 }
f5f4967e 288 echo '</tr>';
1b074625 289 /// Print all the lines of data.
f6eb15ad 290 $i = 0;
caffc55a 291 $gui = new graded_users_iterator($this->course, $this->columns, $this->groupid);
78ab98bc 292 $gui->require_active_enrolment($this->onlyactive);
61c8e0d7 293 $gui->allow_user_custom_fields($this->usercustomfields);
caffc55a 294 $gui->init();
295 while ($userdata = $gui->next_user()) {
f6eb15ad 296 // number of preview rows
bb9f1b4a 297 if ($this->previewrows and $this->previewrows <= $i) {
97599c0a 298 break;
f6eb15ad 299 }
caffc55a 300 $user = $userdata->user;
30f188f0 301 if ($require_user_idnumber and empty($user->idnumber)) {
7ce5df86 302 // some exports require user idnumber so we can match up students when importing the data
30f188f0 303 continue;
304 }
d24832f9 305
bb9f1b4a 306 $gradeupdated = false; // if no grade is update at all for this user, do not display this row
307 $rowstr = '';
caffc55a 308 foreach ($this->columns as $itemid=>$unused) {
309 $gradetxt = $this->format_grade($userdata->grades[$itemid]);
d24832f9 310
59a7447a 311 // get the status of this grade, and put it through track to get the status
312 $g = new grade_export_update_buffer();
313 $grade_grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$user->id));
314 $status = $g->track($grade_grade);
558cb86b 315
59a7447a 316 if ($this->updatedgradesonly && ($status == 'nochange' || $status == 'unknown')) {
bb9f1b4a 317 $rowstr .= '<td>'.get_string('unchangedgrade', 'grades').'</td>';
59a7447a 318 } else {
bb9f1b4a 319 $rowstr .= "<td>$gradetxt</td>";
320 $gradeupdated = true;
59a7447a 321 }
d24832f9 322
caffc55a 323 if ($this->export_feedback) {
bb9f1b4a 324 $rowstr .= '<td>'.$this->format_feedback($userdata->feedbacks[$itemid]).'</td>';
ba74762b 325 }
1b074625 326 }
bb9f1b4a 327
d24832f9 328 // if we are requesting updated grades only, we are not interested in this user at all
bb9f1b4a 329 if (!$gradeupdated && $this->updatedgradesonly) {
d24832f9 330 continue;
bb9f1b4a 331 }
332
333 echo '<tr>';
61c8e0d7
FM
334 foreach ($userprofilefields as $field) {
335 $fieldvalue = grade_helper::get_user_field_value($user, $field);
336 // @see profile_field_base::display_data().
337 echo '<td>' . format_text($fieldvalue, FORMAT_MOODLE, $formatoptions) . '</td>';
293f42b6 338 }
38c1dd19
RT
339 if (!$this->onlyactive) {
340 $issuspended = ($user->suspendedenrolment) ? get_string('yes') : '';
341 echo "<td>$issuspended</td>";
342 }
bb9f1b4a 343 echo $rowstr;
f5f4967e 344 echo "</tr>";
d24832f9 345
bb9f1b4a 346 $i++; // increment the counter
f5f4967e 347 }
348 echo '</table>';
caffc55a 349 $gui->close();
1b074625 350 }
0e2d708e 351
352 /**
caffc55a 353 * Returns array of parameters used by dump.php and export.php.
354 * @return array
355 */
5c75a0a3 356 public function get_export_params() {
caffc55a 357 $itemids = array_keys($this->columns);
112fdf47
AD
358 $itemidsparam = implode(',', $itemids);
359 if (empty($itemidsparam)) {
360 $itemidsparam = '-1';
361 }
caffc55a 362
59a7447a 363 $params = array('id' =>$this->course->id,
364 'groupid' =>$this->groupid,
112fdf47 365 'itemids' =>$itemidsparam,
59a7447a 366 'export_letters' =>$this->export_letters,
367 'export_feedback' =>$this->export_feedback,
864d1f8c 368 'updatedgradesonly' =>$this->updatedgradesonly,
369 'displaytype' =>$this->displaytype,
78ab98bc 370 'decimalpoints' =>$this->decimalpoints,
61c8e0d7
FM
371 'export_onlyactive' =>$this->onlyactive,
372 'usercustomfields' =>$this->usercustomfields);
caffc55a 373
374 return $params;
375 }
376
377 /**
378 * Either prints a "Export" box, which will redirect the user to the download page,
379 * or prints the URL for the published data.
cca51baa
DW
380 *
381 * @deprecated since 2.8 MDL-46548. Call get_export_url and set the
382 * action of the grade_export_form instead.
0e2d708e 383 * @return void
384 */
5c75a0a3 385 public function print_continue() {
c018f973 386 global $CFG, $OUTPUT;
0e2d708e 387
cca51baa 388 debugging('function grade_export::print_continue is deprecated.', DEBUG_DEVELOPER);
6c3ef410 389 $params = $this->get_export_params();
567883c8 390
c018f973 391 echo $OUTPUT->heading(get_string('export', 'grades'));
567883c8 392
03fcc729 393 echo $OUTPUT->container_start('gradeexportlink');
394
cca51baa
DW
395 if (!$this->userkey) {
396 // This button should trigger a download prompt.
397 $url = new moodle_url('/grade/export/'.$this->plugin.'/export.php', $params);
398 echo $OUTPUT->single_button($url, get_string('download', 'admin'));
0e2d708e 399
400 } else {
caffc55a 401 $paramstr = '';
402 $sep = '?';
403 foreach($params as $name=>$value) {
404 $paramstr .= $sep.$name.'='.$value;
03fcc729 405 $sep = '&';
caffc55a 406 }
407
52f1a9ff 408 $link = $CFG->wwwroot.'/grade/export/'.$this->plugin.'/dump.php'.$paramstr.'&key='.$this->userkey;
0e2d708e 409
75015e5f 410 echo get_string('download', 'admin').': ' . html_writer::link($link, $link);
0e2d708e 411 }
03fcc729 412 echo $OUTPUT->container_end();
cca51baa
DW
413
414 return;
0e2d708e 415 }
c10ddfb3 416}
417
eb859919 418/**
419 * This class is used to update the exported field in grade_grades.
420 * It does internal buffering to speedup the db operations.
421 */
422class grade_export_update_buffer {
5c75a0a3 423 public $update_list;
424 public $export_time;
eb859919 425
426 /**
427 * Constructor - creates the buffer and initialises the time stamp
428 */
5c75a0a3 429 public function grade_export_update_buffer() {
eb859919 430 $this->update_list = array();
431 $this->export_time = time();
432 }
433
5c75a0a3 434 public function flush($buffersize) {
435 global $CFG, $DB;
eb859919 436
437 if (count($this->update_list) > $buffersize) {
5c75a0a3 438 list($usql, $params) = $DB->get_in_or_equal($this->update_list);
439 $params = array_merge(array($this->export_time), $params);
440
441 $sql = "UPDATE {grade_grades} SET exported = ? WHERE id $usql";
655b09ca 442 $DB->execute($sql, $params);
eb859919 443 $this->update_list = array();
444 }
445 }
446
447 /**
448 * Track grade export status
449 * @param object $grade_grade
450 * @return string $status (unknow, new, regrade, nochange)
451 */
5c75a0a3 452 public function track($grade_grade) {
59a7447a 453
eb859919 454 if (empty($grade_grade->exported) or empty($grade_grade->timemodified)) {
455 if (is_null($grade_grade->finalgrade)) {
456 // grade does not exist yet
457 $status = 'unknown';
458 } else {
459 $status = 'new';
460 $this->update_list[] = $grade_grade->id;
461 }
462
463 } else if ($grade_grade->exported < $grade_grade->timemodified) {
464 $status = 'regrade';
465 $this->update_list[] = $grade_grade->id;
466
467 } else if ($grade_grade->exported >= $grade_grade->timemodified) {
468 $status = 'nochange';
469
470 } else {
471 // something is wrong?
472 $status = 'unknown';
473 }
474
475 $this->flush(100);
476
477 return $status;
478 }
479
480 /**
481 * Flush and close the buffer.
482 */
5c75a0a3 483 public function close() {
eb859919 484 $this->flush(0);
485 }
486}
6c3ef410 487
459843d4
AD
488/**
489 * Verify that there is a valid set of grades to export.
490 * @param $courseid int The course being exported
491 */
492function export_verify_grades($courseid) {
493 $regraderesult = grade_regrade_final_grades($courseid);
494 if (is_array($regraderesult)) {
63d2081e 495 throw new moodle_exception('gradecantregrade', 'error', '', implode(', ', array_unique($regraderesult)));
459843d4
AD
496 }
497}