Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]
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 26 defined('MOODLE_INTERNAL') || die(); 27 28 use \assignfeedback_editpdf\document_services; 29 use \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 */ 38 class assign_feedback_editpdf extends assign_feedback_plugin { 39 40 /** @var boolean|null $enabledcache Cached lookup of the is_enabled function */ 41 private $enabledcache = null; 42 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; 61 if ($grade && isset($grade->attemptnumber)) { 62 $attempt = $grade->attemptnumber; 63 } else { 64 $grade = $this->assignment->get_user_grade($userid, true); 65 } 66 67 $feedbackfile = document_services::get_feedback_document( 68 $this->assignment->get_instance()->id, 69 $userid, 70 $attempt 71 ); 72 73 $stampfiles = array(); 74 $fs = get_file_storage(); 75 $syscontext = context_system::instance(); 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. 81 $currentstamps = $fs->get_area_files($syscontext->id, 'assignfeedback_editpdf', 'stamps', 0, 'filename', false); 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. 86 $gradestamps = $fs->get_area_files($asscontext->id, 'assignfeedback_editpdf', 'stamps', $grade->id, 'filename', false); 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. 93 $systemstamps = $fs->get_area_files($syscontext->id, 'assignfeedback_editpdf', 'systemstamps', 0, 'filename', false); 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 } 107 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 } 120 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 } 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(), 146 null, 147 $gradestamp->get_filepath(), 148 $gradestamp->get_filename(), 149 false 150 ); 151 array_push($stampfiles, $url->out()); 152 } 153 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 } 168 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 } 180 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 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 235 $renderer = $PAGE->get_renderer('assignfeedback_editpdf'); 236 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 245 $widget = $this->get_widget($userid, $grade, false); 246 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 } 254 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 // As the $draft was included in the class annotation, 287 // it is necessary to omit it in the condition below as well, 288 // otherwise, an error would be raised. 289 if ($key != 'id' && $key != 'draft' && $value != $dannotation->{$key}) { 290 continue 2; 291 } 292 } 293 $matches++; 294 } 295 } 296 if ($matches !== count($nondraftannotations)) { 297 return true; 298 } 299 } 300 // Select all comments. 301 $draftcomments = page_editor::get_comments($sourcegrade->id, $i, true); 302 $nondraftcomments = page_editor::get_comments($grade->id, $i, false); 303 if (count($draftcomments) != count($nondraftcomments)) { 304 return true; 305 } else { 306 // Go for a closer inspection. 307 $matches = 0; 308 foreach ($nondraftcomments as $ndcomment) { 309 foreach ($draftcomments as $dcomment) { 310 foreach ($ndcomment as $key => $value) { 311 // As the $draft was included in the class comment, 312 // it is necessary to omit it in the condition below as well, 313 // otherwise, an error would be raised. 314 if ($key != 'id' && $key != 'draft' && $value != $dcomment->{$key}) { 315 continue 2; 316 } 317 } 318 $matches++; 319 } 320 } 321 if ($matches !== count($nondraftcomments)) { 322 return true; 323 } 324 } 325 } 326 } 327 return false; 328 } 329 330 /** 331 * Generate the pdf. 332 * 333 * @param stdClass $grade 334 * @param stdClass $data 335 * @return bool 336 */ 337 public function save(stdClass $grade, stdClass $data) { 338 // Source user id is only added to the form if there was a pdf. 339 if (!empty($data->editpdf_source_userid)) { 340 $sourceuserid = $data->editpdf_source_userid; 341 // Copy drafts annotations and comments if current user is different to sourceuserid. 342 if ($sourceuserid != $grade->userid) { 343 page_editor::copy_drafts_from_to($this->assignment, $grade, $sourceuserid); 344 } 345 } 346 if (page_editor::has_annotations_or_comments($grade->id, true)) { 347 document_services::generate_feedback_document($this->assignment, $grade->userid, $grade->attemptnumber); 348 } 349 350 return true; 351 } 352 353 /** 354 * Display the list of files in the feedback status table. 355 * 356 * @param stdClass $grade 357 * @param bool $showviewlink (Always set to false). 358 * @return string 359 */ 360 public function view_summary(stdClass $grade, & $showviewlink) { 361 $showviewlink = false; 362 return $this->view($grade); 363 } 364 365 /** 366 * Display the list of files in the feedback status table. 367 * 368 * @param stdClass $grade 369 * @return string 370 */ 371 public function view(stdClass $grade) { 372 global $PAGE; 373 $html = ''; 374 // Show a link to download the pdf. 375 if (page_editor::has_annotations_or_comments($grade->id, false)) { 376 $html = $this->assignment->render_area_files('assignfeedback_editpdf', 377 document_services::FINAL_PDF_FILEAREA, 378 $grade->id); 379 380 // Also show the link to the read-only interface. 381 $renderer = $PAGE->get_renderer('assignfeedback_editpdf'); 382 $widget = $this->get_widget($grade->userid, $grade, true); 383 384 $html .= $renderer->render($widget); 385 } 386 return $html; 387 } 388 389 /** 390 * Return true if there are no released comments/annotations. 391 * 392 * @param stdClass $grade 393 */ 394 public function is_empty(stdClass $grade) { 395 global $DB; 396 397 $comments = $DB->count_records('assignfeedback_editpdf_cmnt', array('gradeid'=>$grade->id, 'draft'=>0)); 398 $annotations = $DB->count_records('assignfeedback_editpdf_annot', array('gradeid'=>$grade->id, 'draft'=>0)); 399 return $comments == 0 && $annotations == 0; 400 } 401 402 /** 403 * The assignment has been deleted - remove the plugin specific data 404 * 405 * @return bool 406 */ 407 public function delete_instance() { 408 global $DB; 409 $grades = $DB->get_records('assign_grades', array('assignment'=>$this->assignment->get_instance()->id), '', 'id'); 410 if ($grades) { 411 list($gradeids, $params) = $DB->get_in_or_equal(array_keys($grades), SQL_PARAMS_NAMED); 412 $DB->delete_records_select('assignfeedback_editpdf_annot', 'gradeid ' . $gradeids, $params); 413 $DB->delete_records_select('assignfeedback_editpdf_cmnt', 'gradeid ' . $gradeids, $params); 414 $DB->delete_records_select('assignfeedback_editpdf_rot', 'gradeid ' . $gradeids, $params); 415 } 416 return true; 417 } 418 419 /** 420 * Determine if ghostscript is available and working. 421 * 422 * @return bool 423 */ 424 public function is_available() { 425 if ($this->enabledcache === null) { 426 $testpath = assignfeedback_editpdf\pdf::test_gs_path(false); 427 $this->enabledcache = ($testpath->status == assignfeedback_editpdf\pdf::GSPATH_OK); 428 } 429 return $this->enabledcache; 430 } 431 /** 432 * Prevent enabling this plugin if ghostscript is not available. 433 * 434 * @return bool false 435 */ 436 public function is_configurable() { 437 return $this->is_available(); 438 } 439 440 /** 441 * Get file areas returns a list of areas this plugin stores files. 442 * 443 * @return array - An array of fileareas (keys) and descriptions (values) 444 */ 445 public function get_file_areas() { 446 return [ 447 document_services::FINAL_PDF_FILEAREA => $this->get_name(), 448 document_services::COMBINED_PDF_FILEAREA => $this->get_name(), 449 document_services::PARTIAL_PDF_FILEAREA => $this->get_name(), 450 document_services::IMPORT_HTML_FILEAREA => $this->get_name(), 451 document_services::PAGE_IMAGE_FILEAREA => $this->get_name(), 452 document_services::PAGE_IMAGE_READONLY_FILEAREA => $this->get_name(), 453 document_services::STAMPS_FILEAREA => $this->get_name(), 454 document_services::TMP_JPG_TO_PDF_FILEAREA => $this->get_name(), 455 document_services::TMP_ROTATED_JPG_FILEAREA => $this->get_name() 456 ]; 457 } 458 459 /** 460 * Get all file areas for user data related to this plugin. 461 * 462 * @return array - An array of user data fileareas (keys) and descriptions (values) 463 */ 464 public function get_user_data_file_areas(): array { 465 return [ 466 document_services::FINAL_PDF_FILEAREA => $this->get_name(), 467 ]; 468 } 469 470 /** 471 * This plugin will inject content into the review panel with javascript. 472 * @return bool true 473 */ 474 public function supports_review_panel() { 475 return true; 476 } 477 478 /** 479 * Return the plugin configs for external functions. 480 * 481 * @return array the list of settings 482 * @since Moodle 3.2 483 */ 484 public function get_config_for_external() { 485 return (array) $this->get_config(); 486 } 487 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body