MDL-68665 assignfeedback_editpdf: Coding style fixes
[moodle.git] / mod / assign / feedback / editpdf / locallib.php
CommitLineData
5c386472
DW
1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * This file contains the definition for the library class for PDF feedback plugin
19 *
20 *
21 * @package assignfeedback_editpdf
22 * @copyright 2012 Davo Smith
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26defined('MOODLE_INTERNAL') || die();
27
28use \assignfeedback_editpdf\document_services;
29use \assignfeedback_editpdf\page_editor;
30
31/**
32 * library class for editpdf feedback plugin extending feedback plugin base class
33 *
34 * @package assignfeedback_editpdf
35 * @copyright 2012 Davo Smith
36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37 */
38class assign_feedback_editpdf extends assign_feedback_plugin {
39
c70de965
DW
40 /** @var boolean|null $enabledcache Cached lookup of the is_enabled function */
41 private $enabledcache = null;
42
5c386472
DW
43 /**
44 * Get the name of the file feedback plugin
45 * @return string
46 */
47 public function get_name() {
48 return get_string('pluginname', 'assignfeedback_editpdf');
49 }
50
51 /**
52 * Create a widget for rendering the editor.
53 *
54 * @param int $userid
55 * @param stdClass $grade
56 * @param bool $readonly
57 * @return assignfeedback_editpdf_widget
58 */
59 public function get_widget($userid, $grade, $readonly) {
60 $attempt = -1;
fc9bc658 61 if ($grade && isset($grade->attemptnumber)) {
5c386472
DW
62 $attempt = $grade->attemptnumber;
63 } else {
64 $grade = $this->assignment->get_user_grade($userid, true);
65 }
66
b2d43a7d
AN
67 $feedbackfile = document_services::get_feedback_document(
68 $this->assignment->get_instance()->id,
69 $userid,
70 $attempt
71 );
5c386472
DW
72
73 $stampfiles = array();
74 $fs = get_file_storage();
75 $syscontext = context_system::instance();
12fb5964
JS
76 $asscontext = $this->assignment->get_context();
77
78 // Three file areas are used for stamps.
79 // Current stamps are those configured as a site administration setting to be available for new uses.
80 // When a stamp is removed from this filearea it is no longer available for new grade items.
c2ba7890 81 $currentstamps = $fs->get_area_files($syscontext->id, 'assignfeedback_editpdf', 'stamps', 0, 'filename', false);
12fb5964
JS
82
83 // Grade stamps are those which have been assigned for a specific grade item.
84 // The stamps associated with a grade item are always used for that grade item, even if the stamp is removed
85 // from the list of current stamps.
c2ba7890 86 $gradestamps = $fs->get_area_files($asscontext->id, 'assignfeedback_editpdf', 'stamps', $grade->id, 'filename', false);
12fb5964
JS
87
88 // The system stamps are perpetual and always exist.
89 // They allow Moodle to serve a common URL for all users for any possible combination of stamps.
90 // Files in the perpetual stamp filearea are within the system context, in itemid 0, and use the original stamps
91 // contenthash as a folder name. This ensures that the combination of stamp filename, and stamp file content is
92 // unique.
c2ba7890 93 $systemstamps = $fs->get_area_files($syscontext->id, 'assignfeedback_editpdf', 'systemstamps', 0, 'filename', false);
12fb5964
JS
94
95 // First check that all current stamps are listed in the grade stamps.
96 foreach ($currentstamps as $stamp) {
97 // Ensure that the current stamp is in the list of perpetual stamps.
98 $systempathnamehash = $this->get_system_stamp_path($stamp);
99 if (!array_key_exists($systempathnamehash, $systemstamps)) {
100 $filerecord = (object) [
101 'filearea' => 'systemstamps',
102 'filepath' => '/' . $stamp->get_contenthash() . '/',
103 ];
104 $newstamp = $fs->create_file_from_storedfile($filerecord, $stamp);
105 $systemstamps[$newstamp->get_pathnamehash()] = $newstamp;
106 }
5c386472 107
12fb5964
JS
108 // Ensure that the current stamp is in the list of stamps for the current grade item.
109 $gradeitempathhash = $this->get_assignment_stamp_path($stamp, $grade->id);
110 if (!array_key_exists($gradeitempathhash, $gradestamps)) {
111 $filerecord = (object) [
112 'contextid' => $asscontext->id,
113 'filearea' => 'stamps',
114 'itemid' => $grade->id,
115 ];
116 $newstamp = $fs->create_file_from_storedfile($filerecord, $stamp);
117 $gradestamps[$newstamp->get_pathnamehash()] = $newstamp;
5c386472
DW
118 }
119 }
120
12fb5964
JS
121 foreach ($gradestamps as $stamp) {
122 // All gradestamps should be available in the systemstamps filearea, but some legacy stamps may not be.
123 // These need to be copied over.
124 // Note: This should ideally be performed as an upgrade step, but there may be other cases that these do not
125 // exist, for example restored backups.
126 // In any case this is a cheap operation as it is solely performing an array lookup.
127 $systempathnamehash = $this->get_system_stamp_path($stamp);
128 if (!array_key_exists($systempathnamehash, $systemstamps)) {
129 $filerecord = (object) [
130 'contextid' => $syscontext->id,
131 'itemid' => 0,
132 'filearea' => 'systemstamps',
133 'filepath' => '/' . $stamp->get_contenthash() . '/',
134 ];
135 $systemstamp = $fs->create_file_from_storedfile($filerecord, $stamp);
136 $systemstamps[$systemstamp->get_pathnamehash()] = $systemstamp;
5c386472 137 }
12fb5964
JS
138
139 // Always serve the perpetual system stamp.
140 // This ensures that the stamp is highly cached and reduces the hit on the application server.
141 $gradestamp = $systemstamps[$systempathnamehash];
142 $url = moodle_url::make_pluginfile_url(
143 $gradestamp->get_contextid(),
144 $gradestamp->get_component(),
145 $gradestamp->get_filearea(),
c2ba7890 146 null,
12fb5964
JS
147 $gradestamp->get_filepath(),
148 $gradestamp->get_filename(),
149 false
150 );
151 array_push($stampfiles, $url->out());
5c386472
DW
152 }
153
154 $url = false;
155 $filename = '';
156 if ($feedbackfile) {
b2d43a7d
AN
157 $url = moodle_url::make_pluginfile_url(
158 $this->assignment->get_context()->id,
159 'assignfeedback_editpdf',
160 document_services::FINAL_PDF_FILEAREA,
161 $grade->id,
162 '/',
163 $feedbackfile->get_filename(),
164 false
165 );
5c386472
DW
166 $filename = $feedbackfile->get_filename();
167 }
168
b2d43a7d
AN
169 $widget = new assignfeedback_editpdf_widget(
170 $this->assignment->get_instance()->id,
171 $userid,
172 $attempt,
173 $url,
174 $filename,
175 $stampfiles,
176 $readonly
177 );
5c386472
DW
178 return $widget;
179 }
180
12fb5964
JS
181 /**
182 * Get the pathnamehash for the specified stamp if in the system stamps.
183 *
184 * @param stored_file $file
185 * @return string
186 */
187 protected function get_system_stamp_path(stored_file $stamp): string {
188 $systemcontext = context_system::instance();
189
190 return file_storage::get_pathname_hash(
191 $systemcontext->id,
192 'assignfeedback_editpdf',
193 'systemstamps',
194 0,
195 '/' . $stamp->get_contenthash() . '/',
196 $stamp->get_filename()
197 );
198 }
199
200 /**
201 * Get the pathnamehash for the specified stamp if in the current assignment stamps.
202 *
203 * @param stored_file $file
204 * @param int $gradeid
205 * @return string
206 */
207 protected function get_assignment_stamp_path(stored_file $stamp, int $gradeid): string {
208 return file_storage::get_pathname_hash(
209 $this->assignment->get_context()->id,
210 'assignfeedback_editpdf',
211 'stamps',
212 $gradeid,
213 $stamp->get_filepath(),
214 $stamp->get_filename()
215 );
216 }
217
5c386472
DW
218 /**
219 * Get form elements for grading form
220 *
221 * @param stdClass $grade
222 * @param MoodleQuickForm $mform
223 * @param stdClass $data
224 * @param int $userid
225 * @return bool true if elements were added to the form
226 */
227 public function get_form_elements_for_user($grade, MoodleQuickForm $mform, stdClass $data, $userid) {
228 global $PAGE;
229
230 $attempt = -1;
231 if ($grade) {
232 $attempt = $grade->attemptnumber;
233 }
234
bb690849 235 $renderer = $PAGE->get_renderer('assignfeedback_editpdf');
5c386472 236
574b2f1f
DW
237 // Links to download the generated pdf...
238 if ($attempt > -1 && page_editor::has_annotations_or_comments($grade->id, false)) {
239 $html = $this->assignment->render_area_files('assignfeedback_editpdf',
240 document_services::FINAL_PDF_FILEAREA,
241 $grade->id);
242 $mform->addElement('static', 'editpdf_files', get_string('downloadfeedback', 'assignfeedback_editpdf'), $html);
243 }
244
bb690849 245 $widget = $this->get_widget($userid, $grade, false);
5c386472 246
bb690849
DW
247 $html = $renderer->render($widget);
248 $mform->addElement('static', 'editpdf', get_string('editpdf', 'assignfeedback_editpdf'), $html);
249 $mform->addHelpButton('editpdf', 'editpdf', 'assignfeedback_editpdf');
250 $mform->addElement('hidden', 'editpdf_source_userid', $userid);
251 $mform->setType('editpdf_source_userid', PARAM_INT);
252 $mform->setConstant('editpdf_source_userid', $userid);
5c386472
DW
253 }
254
238c05c2
AG
255 /**
256 * Check to see if the grade feedback for the pdf has been modified.
257 *
258 * @param stdClass $grade Grade object.
259 * @param stdClass $data Data from the form submission (not used).
260 * @return boolean True if the pdf has been modified, else false.
261 */
262 public function is_feedback_modified(stdClass $grade, stdClass $data) {
181c20bc
AG
263 // We only need to know if the source user's PDF has changed. If so then all
264 // following users will have the same status. If it's only an individual annotation
265 // then only one user will come through this method.
266 // Source user id is only added to the form if there was a pdf.
267 if (!empty($data->editpdf_source_userid)) {
268 $sourceuserid = $data->editpdf_source_userid;
269 // Retrieve the grade information for the source user.
270 $sourcegrade = $this->assignment->get_user_grade($sourceuserid, true, $grade->attemptnumber);
271 $pagenumbercount = document_services::page_number_for_attempt($this->assignment, $sourceuserid, $sourcegrade->attemptnumber);
272 for ($i = 0; $i < $pagenumbercount; $i++) {
273 // Select all annotations.
274 $draftannotations = page_editor::get_annotations($sourcegrade->id, $i, true);
275 $nondraftannotations = page_editor::get_annotations($grade->id, $i, false);
276 // Check to see if the count is the same.
277 if (count($draftannotations) != count($nondraftannotations)) {
278 // The count is different so we have a modification.
279 return true;
280 } else {
281 $matches = 0;
282 // Have a closer look and see if the draft files match all the non draft files.
283 foreach ($nondraftannotations as $ndannotation) {
284 foreach ($draftannotations as $dannotation) {
285 foreach ($ndannotation as $key => $value) {
286 if ($key != 'id' && $value != $dannotation->{$key}) {
287 continue 2;
288 }
4edd8e77 289 }
181c20bc 290 $matches++;
238c05c2 291 }
181c20bc
AG
292 }
293 if ($matches !== count($nondraftannotations)) {
294 return true;
238c05c2
AG
295 }
296 }
181c20bc
AG
297 // Select all comments.
298 $draftcomments = page_editor::get_comments($sourcegrade->id, $i, true);
299 $nondraftcomments = page_editor::get_comments($grade->id, $i, false);
300 if (count($draftcomments) != count($nondraftcomments)) {
4edd8e77 301 return true;
181c20bc
AG
302 } else {
303 // Go for a closer inspection.
304 $matches = 0;
305 foreach ($nondraftcomments as $ndcomment) {
306 foreach ($draftcomments as $dcomment) {
307 foreach ($ndcomment as $key => $value) {
308 if ($key != 'id' && $value != $dcomment->{$key}) {
309 continue 2;
310 }
4edd8e77 311 }
181c20bc 312 $matches++;
238c05c2
AG
313 }
314 }
181c20bc
AG
315 if ($matches !== count($nondraftcomments)) {
316 return true;
317 }
4edd8e77 318 }
238c05c2
AG
319 }
320 }
321 return false;
322 }
323
5c386472
DW
324 /**
325 * Generate the pdf.
326 *
327 * @param stdClass $grade
328 * @param stdClass $data
329 * @return bool
330 */
331 public function save(stdClass $grade, stdClass $data) {
177cc379
DW
332 // Source user id is only added to the form if there was a pdf.
333 if (!empty($data->editpdf_source_userid)) {
334 $sourceuserid = $data->editpdf_source_userid;
335 // Copy drafts annotations and comments if current user is different to sourceuserid.
336 if ($sourceuserid != $grade->userid) {
337 page_editor::copy_drafts_from_to($this->assignment, $grade, $sourceuserid);
338 }
028c9d20 339 }
5c386472
DW
340 if (page_editor::has_annotations_or_comments($grade->id, true)) {
341 document_services::generate_feedback_document($this->assignment, $grade->userid, $grade->attemptnumber);
342 }
343
344 return true;
345 }
346
347 /**
348 * Display the list of files in the feedback status table.
349 *
350 * @param stdClass $grade
351 * @param bool $showviewlink (Always set to false).
352 * @return string
353 */
354 public function view_summary(stdClass $grade, & $showviewlink) {
355 $showviewlink = false;
356 return $this->view($grade);
357 }
358
359 /**
360 * Display the list of files in the feedback status table.
361 *
362 * @param stdClass $grade
363 * @return string
364 */
365 public function view(stdClass $grade) {
366 global $PAGE;
367 $html = '';
368 // Show a link to download the pdf.
369 if (page_editor::has_annotations_or_comments($grade->id, false)) {
370 $html = $this->assignment->render_area_files('assignfeedback_editpdf',
371 document_services::FINAL_PDF_FILEAREA,
372 $grade->id);
373
374 // Also show the link to the read-only interface.
375 $renderer = $PAGE->get_renderer('assignfeedback_editpdf');
376 $widget = $this->get_widget($grade->userid, $grade, true);
377
378 $html .= $renderer->render($widget);
379 }
380 return $html;
381 }
382
383 /**
384 * Return true if there are no released comments/annotations.
385 *
386 * @param stdClass $grade
387 */
388 public function is_empty(stdClass $grade) {
389 global $DB;
390
391 $comments = $DB->count_records('assignfeedback_editpdf_cmnt', array('gradeid'=>$grade->id, 'draft'=>0));
392 $annotations = $DB->count_records('assignfeedback_editpdf_annot', array('gradeid'=>$grade->id, 'draft'=>0));
393 return $comments == 0 && $annotations == 0;
394 }
395
396 /**
397 * The assignment has been deleted - remove the plugin specific data
398 *
399 * @return bool
400 */
401 public function delete_instance() {
402 global $DB;
9f5193a4
DW
403 $grades = $DB->get_records('assign_grades', array('assignment'=>$this->assignment->get_instance()->id), '', 'id');
404 if ($grades) {
405 list($gradeids, $params) = $DB->get_in_or_equal(array_keys($grades), SQL_PARAMS_NAMED);
406 $DB->delete_records_select('assignfeedback_editpdf_annot', 'gradeid ' . $gradeids, $params);
407 $DB->delete_records_select('assignfeedback_editpdf_cmnt', 'gradeid ' . $gradeids, $params);
408 }
409 return true;
5c386472
DW
410 }
411
f159ad73 412 /**
9a73d11c 413 * Determine if ghostscript is available and working.
f159ad73 414 *
415 * @return bool
416 */
9a73d11c 417 public function is_available() {
c70de965
DW
418 if ($this->enabledcache === null) {
419 $testpath = assignfeedback_editpdf\pdf::test_gs_path(false);
420 $this->enabledcache = ($testpath->status == assignfeedback_editpdf\pdf::GSPATH_OK);
f159ad73 421 }
c70de965 422 return $this->enabledcache;
f159ad73 423 }
424 /**
9a73d11c 425 * Prevent enabling this plugin if ghostscript is not available.
f159ad73 426 *
427 * @return bool false
428 */
429 public function is_configurable() {
9a73d11c 430 return $this->is_available();
f159ad73 431 }
2fa6c632
JL
432
433 /**
434 * Get file areas returns a list of areas this plugin stores files.
435 *
436 * @return array - An array of fileareas (keys) and descriptions (values)
437 */
438 public function get_file_areas() {
439 return array(document_services::FINAL_PDF_FILEAREA => $this->get_name());
440 }
bb690849
DW
441
442 /**
443 * This plugin will inject content into the review panel with javascript.
444 * @return bool true
445 */
446 public function supports_review_panel() {
447 return true;
448 }
30cdddb0
JL
449
450 /**
451 * Return the plugin configs for external functions.
452 *
453 * @return array the list of settings
454 * @since Moodle 3.2
455 */
456 public function get_config_for_external() {
457 return (array) $this->get_config();
458 }
5c386472 459}