MDL-68665 assignfeedback_editpdf: Coding style fixes
[moodle.git] / mod / assign / feedback / editpdf / locallib.php
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/>.
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  */
26 defined('MOODLE_INTERNAL') || die();
28 use \assignfeedback_editpdf\document_services;
29 use \assignfeedback_editpdf\page_editor;
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  */
38 class assign_feedback_editpdf extends assign_feedback_plugin {
40     /** @var boolean|null $enabledcache Cached lookup of the is_enabled function */
41     private $enabledcache = null;
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     }
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;
61         if ($grade && isset($grade->attemptnumber)) {
62             $attempt = $grade->attemptnumber;
63         } else {
64             $grade = $this->assignment->get_user_grade($userid, true);
65         }
67         $feedbackfile = document_services::get_feedback_document(
68             $this->assignment->get_instance()->id,
69             $userid,
70             $attempt
71         );
73         $stampfiles = array();
74         $fs = get_file_storage();
75         $syscontext = context_system::instance();
76         $asscontext = $this->assignment->get_context();
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.
81         $currentstamps = $fs->get_area_files($syscontext->id, 'assignfeedback_editpdf', 'stamps', 0, 'filename', false);
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.
86         $gradestamps = $fs->get_area_files($asscontext->id, 'assignfeedback_editpdf', 'stamps', $grade->id, 'filename', false);
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.
93         $systemstamps = $fs->get_area_files($syscontext->id, 'assignfeedback_editpdf', 'systemstamps', 0, 'filename', false);
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             }
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;
118             }
119         }
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;
137             }
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(),
146                 null,
147                 $gradestamp->get_filepath(),
148                 $gradestamp->get_filename(),
149                 false
150             );
151             array_push($stampfiles, $url->out());
152         }
154         $url = false;
155         $filename = '';
156         if ($feedbackfile) {
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             );
166            $filename = $feedbackfile->get_filename();
167         }
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         );
178         return $widget;
179     }
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();
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     }
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     }
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;
230         $attempt = -1;
231         if ($grade) {
232             $attempt = $grade->attemptnumber;
233         }
235         $renderer = $PAGE->get_renderer('assignfeedback_editpdf');
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         }
245         $widget = $this->get_widget($userid, $grade, false);
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);
253     }
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) {
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                                 }
289                             }
290                             $matches++;
291                         }
292                     }
293                     if ($matches !== count($nondraftannotations)) {
294                         return true;
295                     }
296                 }
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)) {
301                     return true;
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                                 }
311                             }
312                             $matches++;
313                         }
314                     }
315                     if ($matches !== count($nondraftcomments)) {
316                         return true;
317                     }
318                 }
319             }
320         }
321         return false;
322     }
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) {
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             }
339         }
340         if (page_editor::has_annotations_or_comments($grade->id, true)) {
341             document_services::generate_feedback_document($this->assignment, $grade->userid, $grade->attemptnumber);
342         }
344         return true;
345     }
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     }
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);
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);
378             $html .= $renderer->render($widget);
379         }
380         return $html;
381     }
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;
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     }
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;
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;
410     }
412     /**
413      * Determine if ghostscript is available and working.
414      *
415      * @return bool
416      */
417     public function is_available() {
418         if ($this->enabledcache === null) {
419             $testpath = assignfeedback_editpdf\pdf::test_gs_path(false);
420             $this->enabledcache = ($testpath->status == assignfeedback_editpdf\pdf::GSPATH_OK);
421         }
422         return $this->enabledcache;
423     }
424     /**
425      * Prevent enabling this plugin if ghostscript is not available.
426      *
427      * @return bool false
428      */
429     public function is_configurable() {
430         return $this->is_available();
431     }
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     }
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     }
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     }