Merge branch 'MDL-42023-master' of git://github.com/damyon/moodle

This commit is contained in:
Marina Glancy
2013-10-03 13:08:03 +10:00
112 changed files with 18438 additions and 2 deletions
+1 -1
View File
@@ -869,7 +869,7 @@ class plugin_manager {
),
'assignfeedback' => array(
'comments', 'file', 'offline'
'comments', 'file', 'offline', 'editpdf'
),
'atto' => array(
+7
View File
@@ -315,4 +315,11 @@
<version>1.2</version>
<licenseversion></licenseversion>
</library>
<library>
<location>mod/assign/feedback/editpdf/fpdi</location>
<name>FPDI</name>
<license>Apache</license>
<version>1.4.4</version>
<licenseversion>2.0</licenseversion>
</library>
</libraries>
+179
View File
@@ -0,0 +1,179 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Process ajax requests
*
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use \assignfeedback_editpdf\document_services;
use \assignfeedback_editpdf\page_editor;
use \assignfeedback_editpdf\comments_quick_list;
if (!defined('AJAX_SCRIPT')) {
define('AJAX_SCRIPT', true);
}
require_once('../../../../config.php');
require_once($CFG->dirroot . '/mod/assign/locallib.php');
require_sesskey();
$action = optional_param('action', '', PARAM_ALPHANUM);
$assignmentid = required_param('assignmentid', PARAM_INT);
$userid = required_param('userid', PARAM_INT);
$attemptnumber = required_param('attemptnumber', PARAM_INT);
$cm = \get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
$context = \context_module::instance($cm->id);
$assignment = new \assign($context, null, null);
if (!$assignment->can_view_submission($userid)) {
print_error('nopermission');
}
if ($action == 'loadallpages') {
$draft = true;
if (!has_capability('mod/assign:grade', $context)) {
$draft = false;
require_capability('mod/assign:submit', $context);
}
$pages = document_services::get_page_images_for_attempt($assignment,
$userid,
$attemptnumber);
$response = new stdClass();
$response->pagecount = count($pages);
$response->pages = array();
$grade = $assignment->get_user_grade($userid, true);
foreach ($pages as $id => $pagefile) {
$index = count($response->pages);
$page = new stdClass();
$comments = page_editor::get_comments($grade->id, $index, $draft);
$page->url = moodle_url::make_pluginfile_url($context->id,
'assignfeedback_editpdf',
document_services::PAGE_IMAGE_FILEAREA,
$grade->id,
'/',
$pagefile->get_filename())->out();
$page->comments = $comments;
$annotations = page_editor::get_annotations($grade->id, $index, $draft);
$page->annotations = $annotations;
array_push($response->pages, $page);
}
echo json_encode($response);
die();
} else if ($action == 'savepage') {
require_capability('mod/assign:grade', $context);
$response = new stdClass();
$response->errors = array();
$grade = $assignment->get_user_grade($userid, true);
$pagejson = required_param('page', PARAM_RAW);
$page = json_decode($pagejson);
$index = required_param('index', PARAM_INT);
$added = page_editor::set_comments($grade->id, $index, $page->comments);
if ($added != count($page->comments)) {
array_push($response->errors, get_string('couldnotsavepage', 'assignfeedback_editpdf', $index+1));
}
$added = page_editor::set_annotations($grade->id, $index, $page->annotations);
if ($added != count($page->annotations)) {
array_push($response->errors, get_string('couldnotsavepage', 'assignfeedback_editpdf', $index+1));
}
echo json_encode($response);
die();
} else if ($action == 'generatepdf') {
require_capability('mod/assign:grade', $context);
$response = new stdClass();
$grade = $assignment->get_user_grade($userid, true);
$file = document_services::generate_feedback_document($assignment, $userid, $attemptnumber);
$response->url = '';
if ($file) {
$url = moodle_url::make_pluginfile_url($assignment->get_context()->id,
'assignfeedback_editpdf',
document_services::FINAL_PDF_FILEAREA,
$grade->id,
'/',
$file->get_filename(),
false);
$response->url = $url->out(true);
$response->filename = $file->get_filename();
}
echo json_encode($response);
die();
} else if ($action == 'loadquicklist') {
require_capability('mod/assign:grade', $context);
$result = comments_quick_list::get_comments();
echo json_encode($result);
die();
} else if ($action == 'addtoquicklist') {
require_capability('mod/assign:grade', $context);
$comment = required_param('commenttext', PARAM_RAW);
$width = required_param('width', PARAM_INT);
$colour = required_param('colour', PARAM_ALPHA);
$result = comments_quick_list::add_comment($comment, $width, $colour);
echo json_encode($result);
die();
} else if ($action == 'revertchanges') {
require_capability('mod/assign:grade', $context);
$grade = $assignment->get_user_grade($userid, true);
$result = page_editor::revert_drafts($gradeid);
echo json_encode($result);
die();
} else if ($action == 'removefromquicklist') {
require_capability('mod/assign:grade', $context);
$commentid = required_param('commentid', PARAM_INT);
$result = comments_quick_list::remove_comment($commentid);
echo json_encode($result);
die();
} else if ($action == 'deletefeedbackdocument') {
require_capability('mod/assign:grade', $context);
$grade = $assignment->get_user_grade($userid, true);
$result = document_services::delete_feedback_document($assignment, $userid, $attemptnumber);
$result = $result && page_editor::unrelease_drafts($grade->id);
echo json_encode($result);
die();
}
@@ -0,0 +1,70 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the backup code for the feedback_editpdf plugin.
*
* @package assignfeedback_editpdf
* @copyright 2013 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Provides the information to backup feedback pdf annotations.
*
* This just adds its fileareas to the annotations and the comments and annotation data.
*
* @package assignfeedback_editpdf
* @copyright 2013 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_assignfeedback_editpdf_subplugin extends backup_subplugin {
/**
* Returns the subplugin information to attach to feedback element
* @return backup_subplugin_element
*/
protected function define_grade_subplugin_structure() {
// Create XML elements.
$subplugin = $this->get_subplugin_element();
$subpluginwrapper = new backup_nested_element($this->get_recommended_name());
$subpluginelementfiles = new backup_nested_element('feedback_editpdf_files', null, array('gradeid'));
$subpluginelementannotations = new backup_nested_element('feedback_editpdf_annotations');
$subpluginelementannotation = new backup_nested_element('annotation', null, array('gradeid', 'pageno', 'type', 'x', 'y', 'endx', 'endy', 'colour', 'path', 'draft'));
$subpluginelementcomments = new backup_nested_element('feedback_editpdf_comments');
$subpluginelementcomment = new backup_nested_element('comment', null, array('gradeid', 'pageno', 'x', 'y', 'width', 'rawtext', 'colour', 'draft'));
// Connect XML elements into the tree.
$subplugin->add_child($subpluginwrapper);
$subpluginelementannotations->add_child($subpluginelementannotation);
$subpluginelementcomments->add_child($subpluginelementcomment);
$subpluginwrapper->add_child($subpluginelementfiles);
$subpluginwrapper->add_child($subpluginelementannotations);
$subpluginwrapper->add_child($subpluginelementcomments);
// Set source to populate the data.
$subpluginelementfiles->set_source_sql('SELECT id AS gradeid from {assign_grades} where id = :gradeid', array('gradeid' => backup::VAR_PARENTID));
$subpluginelementannotation->set_source_table('assignfeedback_editpdf_annot', array('gradeid' => backup::VAR_PARENTID));
$subpluginelementcomment->set_source_table('assignfeedback_editpdf_cmnt', array('gradeid' => backup::VAR_PARENTID));
// We only need to backup the files in the final pdf area - all the others can be regenerated.
$subpluginelementfiles->annotate_files('assignfeedback_editpdf', 'download', 'gradeid');
$subpluginelementfiles->annotate_files('assignfeedback_editpdf', 'stamps', 'gradeid');
return $subplugin;
}
}
@@ -0,0 +1,109 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the restore code for the feedback_editpdf plugin.
*
* @package assignfeedback_editpdf
* @copyright 2013 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Restore subplugin class.
*
* Provides the necessary information needed
* to restore one assign_feedback subplugin.
*
* @package assignfeedback_editpdf
* @copyright 2013 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_assignfeedback_editpdf_subplugin extends restore_subplugin {
/**
* Returns the paths to be handled by the subplugin at assignment level
* @return array
*/
protected function define_grade_subplugin_structure() {
$paths = array();
// We used get_recommended_name() so this works.
// The files node is a placeholder just containing gradeid so we can restore files once per grade.
$elename = $this->get_namefor('files');
$elepath = $this->get_pathfor('/feedback_editpdf_files');
$paths[] = new restore_path_element($elename, $elepath);
// Now we have the list of comments and annotations per grade.
$elename = $this->get_namefor('comment');
$elepath = $this->get_pathfor('/feedback_editpdf_comments/comment');
$paths[] = new restore_path_element($elename, $elepath);
$elename = $this->get_namefor('annotation');
$elepath = $this->get_pathfor('/feedback_editpdf_annotations/annotation');
$paths[] = new restore_path_element($elename, $elepath);
return $paths;
}
/**
* Processes one feedback_editpdf_files element
* @param mixed $data
*/
public function process_assignfeedback_editpdf_files($data) {
$data = (object)$data;
// In this case the id is the old gradeid which will be mapped.
$this->add_related_files('assignfeedback_editpdf', 'download', 'grade', null, $data->gradeid);
$this->add_related_files('assignfeedback_editpdf', 'stamps', 'grade', null, $data->gradeid);
}
/**
* Processes one feedback_editpdf_annotations/annotation element
* @param mixed $data
*/
public function process_assignfeedback_editpdf_annotation($data) {
global $DB;
$data = (object)$data;
$oldgradeid = $data->gradeid;
// The mapping is set in the restore for the core assign activity
// when a grade node is processed.
$data->gradeid = $this->get_mappingid('grade', $data->gradeid);
$DB->insert_record('assignfeedback_editpdf_annot', $data);
}
/**
* Processes one feedback_editpdf_comments/comment element
* @param mixed $data
*/
public function process_assignfeedback_editpdf_comment($data) {
global $DB;
$data = (object)$data;
$oldgradeid = $data->gradeid;
// The mapping is set in the restore for the core assign activity
// when a grade node is processed.
$data->gradeid = $this->get_mappingid('grade', $data->gradeid);
$DB->insert_record('assignfeedback_editpdf_cmnt', $data);
}
}
@@ -0,0 +1,82 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the annotation class for the assignfeedback_editpdf plugin
*
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace assignfeedback_editpdf;
/**
* This class adds and removes annotations from a page of a response.
*
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class annotation {
/** @var int unique id for this annotation */
public $id = 0;
/** @var int gradeid for this annotation */
public $gradeid = 0;
/** @var int page number for this annotation */
public $pageno = 0;
/** @var int starting location in pixels. Image resolution is 100 pixels per inch */
public $x = 0;
/** @var int ending location in pixels. Image resolution is 100 pixels per inch */
public $endx = 0;
/** @var int starting location in pixels. Image resolution is 100 pixels per inch */
public $y = 0;
/** @var int ending location in pixels. Image resolution is 100 pixels per inch */
public $endy = 0;
/** @var string path information for drawing the annotation. */
public $path = '';
/** @var string colour - One of red, yellow, green, blue, white */
public $colour = 'yellow';
/** @var string type - One of line, oval, rect, etc */
public $type = 'line';
/**
* Convert a compatible stdClass into an instance of this class.
* @param stdClass $record
*/
public function __construct(\stdClass $record) {
$intcols = array('endx', 'endy', 'x', 'y');
foreach ($this as $key => $value) {
if (isset($record->$key)) {
if (in_array($key, $intcols)) {
$this->$key = intval($record->$key);
} else {
$this->$key = $record->$key;
}
}
}
}
}
@@ -0,0 +1,74 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the comment class for the assignfeedback_editpdf plugin
*
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace assignfeedback_editpdf;
/**
* This class represents a comment box on a page of feedback.
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class comment {
/** @var int unique id for this annotation */
public $id = 0;
/** @var int gradeid for this annotation */
public $gradeid = 0;
/** @var int page number for this annotation */
public $pageno = 0;
/** @var int starting location in pixels. Image resolution is 100 pixels per inch */
public $x = 0;
/** @var int starting location in pixels. Image resolution is 100 pixels per inch */
public $y = 0;
/** @var int width of the comment box */
public $width = 120;
/** @var string The comment text. */
public $rawtext = '';
/** @var string colour - One of red, yellow, green, blue, white */
public $colour = 'yellow';
/**
* Convert a compatible stdClass into an instance of a comment.
* @param \stdClass $record
*/
public function __construct(\stdClass $record) {
$intcols = array('width', 'x', 'y');
foreach ($this as $key => $value) {
if (isset($record->$key)) {
if (in_array($key, $intcols)) {
$this->$key = intval($record->$key);
} else {
$this->$key = $record->$key;
}
}
}
}
}
@@ -0,0 +1,94 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the functions for managing a users comments quicklist.
*
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace assignfeedback_editpdf;
/**
* This class performs crud operations on a users quicklist comments.
*
* No capability checks are done - they should be done by the calling class.
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class comments_quick_list {
/**
* Get all comments for the current user.
* @return array(comment)
*/
public static function get_comments() {
global $DB, $USER;
$comments = array();
$records = $DB->get_records('assignfeedback_editpdf_quick', array('userid'=>$USER->id));
return $records;
}
/**
* Add a comment to the quick list.
* @param string $commenttext
* @param int $width
* @param string $colour
* @return stdClass - the comment record (with new id set)
*/
public static function add_comment($commenttext, $width, $colour) {
global $DB, $USER;
$comment = new \stdClass();
$comment->userid = $USER->id;
$comment->rawtext = $commenttext;
$comment->width = $width;
$comment->colour = $colour;
$comment->id = $DB->insert_record('assignfeedback_editpdf_quick', $comment);
return $comment;
}
/**
* Get a single comment by id.
* @param int $commentid
* @return comment or false
*/
public static function get_comment($commentid) {
global $DB;
$record = $DB->get_record('assignfeedback_editpdf_quick', array('id'=>$commentid), '*', IGNORE_MISSING);
if ($record) {
return $record;
}
return false;
}
/**
* Remove a comment from the quick list.
* @param int $commentid
* @return bool
*/
public static function remove_comment($commentid) {
global $DB, $USER;
return $DB->delete_records('assignfeedback_editpdf_quick', array('id'=>$commentid, 'userid'=>$USER->id));
}
}
@@ -0,0 +1,561 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the ingest manager for the assignfeedback_editpdf plugin
*
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace assignfeedback_editpdf;
/**
* Functions for generating the annotated pdf.
*
* This class controls the ingest of student submission files to a normalised
* PDF 1.4 document with all submission files concatinated together. It also
* provides the functions to generate a downloadable pdf with all comments and
* annotations embedded.
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class document_services {
/** File area for generated pdf */
const FINAL_PDF_FILEAREA = 'download';
/** File area for combined pdf */
const COMBINED_PDF_FILEAREA = 'combined';
/** File area for page images */
const PAGE_IMAGE_FILEAREA = 'pages';
/** Filename for combined pdf */
const COMBINED_PDF_FILENAME = 'combined.pdf';
/**
* This function will take an int or an assignment instance and
* return an assignment instance. It is just for convenience.
* @param int|\assign $assignment
* @return assign
*/
private static function get_assignment_from_param($assignment) {
global $CFG;
require_once($CFG->dirroot . '/mod/assign/locallib.php');
if (!is_object($assignment)) {
$cm = \get_coursemodule_from_instance('assign', $assignment, 0, false, MUST_EXIST);
$context = \context_module::instance($cm->id);
$assignment = new \assign($context, null, null);
}
return $assignment;
}
/**
* Get a hash that will be unique and can be used in a path name.
* @param int|\assign $assignment
* @param int $userid
* @param int $attemptnumber (-1 means latest attempt)
*/
private static function hash($assignment, $userid, $attemptnumber) {
if (is_object($assignment)) {
$assignmentid = $assignment->get_instance()->id;
} else {
$assignmentid = $assignment;
}
return sha1($assignmentid . '_' . $userid . '_' . $attemptnumber);
}
/**
* This function will search for all files that can be converted
* and concatinated into a PDF (1.4) - for any submission plugin
* for this students attempt.
* @param int|\assign $assignment
* @param int $userid
* @param int $attemptnumber (-1 means latest attempt)
* @return array(stored_file)
*/
public static function list_compatible_submission_files_for_attempt($assignment, $userid, $attemptnumber) {
global $USER, $DB;
$assignment = self::get_assignment_from_param($assignment);
// Capability checks.
if (!$assignment->can_view_submission($userid)) {
\print_error('nopermission');
}
$files = array();
if ($assignment->get_instance()->teamsubmission) {
$submission = $assignment->get_group_submission($userid, 0, false);
} else {
$submission = $assignment->get_user_submission($userid, false);
}
$user = $DB->get_record('user', array('id' => $userid));
// User has not submitted anything yet.
if (!$submission) {
return $files;
}
// Ask each plugin for it's list of files.
foreach ($assignment->get_submission_plugins() as $plugin) {
if ($plugin->is_enabled() && $plugin->is_visible()) {
$pluginfiles = $plugin->get_files($submission, $user);
foreach ($pluginfiles as $filename => $file) {
if ($file->get_mimetype() === 'application/pdf') {
$files[$filename] = $file;
}
}
}
}
return $files;
}
/**
* This function return the combined pdf for all valid submission files.
* @param int|\assign $assignment
* @param int $userid
* @param int $attemptnumber (-1 means latest attempt)
* @return stored_file
*/
public static function get_combined_pdf_for_attempt($assignment, $userid, $attemptnumber) {
global $USER, $DB;
$assignment = self::get_assignment_from_param($assignment);
// Capability checks.
if (!$assignment->can_view_submission($userid)) {
\print_error('nopermission');
}
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
if ($assignment->get_instance()->teamsubmission) {
$submission = $assignment->get_group_submission($userid, 0, false);
} else {
$submission = $assignment->get_user_submission($userid, false);
}
$contextid = $assignment->get_context()->id;
$component = 'assignfeedback_editpdf';
$filearea = self::COMBINED_PDF_FILEAREA;
$itemid = $grade->id;
$filepath = '/';
$filename = self::COMBINED_PDF_FILENAME;
$fs = \get_file_storage();
$combinedpdf = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename);
if (!$combinedpdf ||
($submission && ($combinedpdf->get_timemodified() < $submission->timemodified))) {
return self::generate_combined_pdf_for_attempt($assignment, $userid, $attemptnumber);
}
return $combinedpdf;
}
/**
* This function will take all of the compatible files for a submission
* and combine them into one PDF.
* @param int|\assign $assignment
* @param int $userid
* @param int $attemptnumber (-1 means latest attempt)
* @return stored_file
*/
public static function generate_combined_pdf_for_attempt($assignment, $userid, $attemptnumber) {
global $CFG;
require_once($CFG->libdir . '/pdflib.php');
$assignment = self::get_assignment_from_param($assignment);
if (!$assignment->can_view_submission($userid)) {
\print_error('nopermission');
}
$files = self::list_compatible_submission_files_for_attempt($assignment, $userid, $attemptnumber);
$pdf = new pdf();
if (!$files) {
// No valid submission files - create an empty pdf.
$pdf->AddPage();
} else {
// Create a mega joined PDF.
$compatiblepdfs = array();
foreach ($files as $file) {
$compatiblepdf = pdf::ensure_pdf_compatible($file);
if ($compatiblepdf) {
array_push($compatiblepdfs, $compatiblepdf);
}
}
$tmpdir = \make_temp_directory('assignfeedback_editpdf/combined/' . self::hash($assignment, $userid, $attemptnumber));
$tmpfile = $tmpdir . '/' . self::COMBINED_PDF_FILENAME;
@unlink($tmpfile);
$pagecount = $pdf->combine_pdfs($compatiblepdfs, $tmpfile);
if ($pagecount == 0) {
// We at least want a single blank page.
$pdf->AddPage();
@unlink($tmpfile);
$files = false;
}
}
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
$record = new \stdClass();
$record->contextid = $assignment->get_context()->id;
$record->component = 'assignfeedback_editpdf';
$record->filearea = self::COMBINED_PDF_FILEAREA;
$record->itemid = $grade->id;
$record->filepath = '/';
$record->filename = self::COMBINED_PDF_FILENAME;
$fs = \get_file_storage();
$fs->delete_area_files($record->contextid, $record->component, $record->filearea, $record->itemid);
if (!$files) {
// This was a blank pdf.
$content = $pdf->Output(self::COMBINED_PDF_FILENAME, 'S');
$file = $fs->create_file_from_string($record, $content);
} else {
// This was a combined pdf.
$file = $fs->create_file_from_pathname($record, $tmpfile);
@unlink($tmpfile);
}
return $file;
}
/**
* This function will generate and return a list of the page images from a pdf.
* @param int|\assign $assignment
* @param int $userid
* @param int $attemptnumber (-1 means latest attempt)
* @return array(stored_file)
*/
public static function generate_page_images_for_attempt($assignment, $userid, $attemptnumber) {
global $CFG;
require_once($CFG->libdir . '/pdflib.php');
$assignment = self::get_assignment_from_param($assignment);
if (!$assignment->can_view_submission($userid)) {
\print_error('nopermission');
}
// Need to generate the page images - first get a combined pdf.
$file = self::get_combined_pdf_for_attempt($assignment, $userid, $attemptnumber);
if (!$file) {
throw \moodle_exception('Could not generate combined pdf.');
}
$tmpdir = \make_temp_directory('assignfeedback_editpdf/pageimages/' . self::hash($assignment, $userid, $attemptnumber));
$combined = $tmpdir . '/' . self::COMBINED_PDF_FILENAME;
$file->copy_content_to($combined); // Copy the file.
$pdf = new pdf();
$pdf->set_image_folder($tmpdir);
$pagecount = $pdf->set_pdf($combined);
$i = 0;
$images = array();
for ($i = 0; $i < $pagecount; $i++) {
$images[$i] = $pdf->get_image($i);
}
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
$files = array();
$record = new \stdClass();
$record->contextid = $assignment->get_context()->id;
$record->component = 'assignfeedback_editpdf';
$record->filearea = self::PAGE_IMAGE_FILEAREA;
$record->itemid = $grade->id;
$record->filepath = '/';
$fs = \get_file_storage();
foreach ($images as $index => $image) {
$record->filename = basename($image);
$files[$index] = $fs->create_file_from_pathname($record, $tmpdir . '/' . $image);
@unlink($tmpdir . '/' . $image);
}
@unlink($combined);
@rmdir($tmpdir);
return $files;
}
/**
* This function returns a list of the page images from a pdf.
* @param int|\assign $assignment
* @param int $userid
* @param int $attemptnumber (-1 means latest attempt)
* @return array(stored_file)
*/
public static function get_page_images_for_attempt($assignment, $userid, $attemptnumber) {
$assignment = self::get_assignment_from_param($assignment);
if (!$assignment->can_view_submission($userid)) {
\print_error('nopermission');
}
if ($assignment->get_instance()->teamsubmission) {
$submission = $assignment->get_group_submission($userid, 0, false);
} else {
$submission = $assignment->get_user_submission($userid, false);
}
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
$contextid = $assignment->get_context()->id;
$component = 'assignfeedback_editpdf';
$filearea = self::PAGE_IMAGE_FILEAREA;
$itemid = $grade->id;
$filepath = '/';
$fs = \get_file_storage();
$files = $fs->get_directory_files($contextid, $component, $filearea, $itemid, $filepath);
if (!empty($files)) {
$first = reset($files);
if ($first->get_timemodified() < $submission->timemodified) {
$fs->delete_area_files($contextid, $component, $filearea, $itemid);
// Image files are stale - regenerate them.
$files = array();
} else {
return $files;
}
}
return self::generate_page_images_for_attempt($assignment, $userid, $attemptnumber);
}
/**
* This function returns sensible filename for a feedback file.
* @param int|\assign $assignment
* @param int $userid
* @param int $attemptnumber (-1 means latest attempt)
* @return string
*/
protected static function get_downloadable_feedback_filename($assignment, $userid, $attemptnumber) {
global $DB;
$assignment = self::get_assignment_from_param($assignment);
$groupmode = groups_get_activity_groupmode($assignment->get_course_module());
$groupname = '';
if ($groupmode) {
$groupid = groups_get_activity_group($assignment->get_course_module(), true);
$groupname = groups_get_group_name($groupid).'-';
}
if ($groupname == '-') {
$groupname = '';
}
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
$user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
if ($assignment->is_blind_marking()) {
$prefix = $groupname . get_string('participant', 'assign');
$prefix = str_replace('_', ' ', $prefix);
$prefix = clean_filename($prefix . '_' . $assignment->get_uniqueid_for_user($userid) . '_');
} else {
$prefix = $groupname . fullname($user);
$prefix = str_replace('_', ' ', $prefix);
$prefix = clean_filename($prefix . '_' . $assignment->get_uniqueid_for_user($userid) . '_');
}
$prefix .= $grade->attemptnumber;
return $prefix . '.pdf';
}
/**
* This function takes the combined pdf and embeds all the comments and annotations.
* @param int|\assign $assignment
* @param int $userid
* @param int $attemptnumber (-1 means latest attempt)
* @return stored_file
*/
public static function generate_feedback_document($assignment, $userid, $attemptnumber) {
$assignment = self::get_assignment_from_param($assignment);
if (!$assignment->can_view_submission($userid)) {
\print_error('nopermission');
}
if (!$assignment->can_grade()) {
\print_error('nopermission');
}
// Need to generate the page images - first get a combined pdf.
$file = self::get_combined_pdf_for_attempt($assignment, $userid, $attemptnumber);
if (!$file) {
throw \moodle_exception('Could not generate combined pdf.');
}
$tmpdir = \make_temp_directory('assignfeedback_editpdf/final/' . self::hash($assignment, $userid, $attemptnumber));
$combined = $tmpdir . '/' . self::COMBINED_PDF_FILENAME;
$file->copy_content_to($combined); // Copy the file.
$pdf = new pdf();
$fs = \get_file_storage();
$stamptmpdir = \make_temp_directory('assignfeedback_editpdf/stamps/' . self::hash($assignment, $userid, $attemptnumber));
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
// Copy any new stamps to this instance.
if ($files = $fs->get_area_files($assignment->get_context()->id,
'assignfeedback_editpdf',
'stamps',
$grade->id,
"filename",
false)) {
foreach ($files as $file) {
$filename = $stamptmpdir . '/' . $file->get_filename();
$file->copy_content_to($filename); // Copy the file.
}
}
$pagecount = $pdf->set_pdf($combined);
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
page_editor::release_drafts($grade->id);
for ($i = 0; $i < $pagecount; $i++) {
$pdf->copy_page();
$comments = page_editor::get_comments($grade->id, $i, false);
$annotations = page_editor::get_annotations($grade->id, $i, false);
foreach ($comments as $comment) {
$pdf->add_comment($comment->rawtext,
$comment->x,
$comment->y,
$comment->width,
$comment->colour);
}
foreach ($annotations as $annotation) {
$pdf->add_annotation($annotation->x,
$annotation->y,
$annotation->endx,
$annotation->endy,
$annotation->colour,
$annotation->type,
$annotation->path,
$stamptmpdir);
}
}
fulldelete($stamptmpdir);
$filename = self::get_downloadable_feedback_filename($assignment, $userid, $attemptnumber);
$filename = clean_param($filename, PARAM_FILE);
$generatedpdf = $tmpdir . '/' . $filename;
$pdf->save_pdf($generatedpdf);
$record = new \stdClass();
$record->contextid = $assignment->get_context()->id;
$record->component = 'assignfeedback_editpdf';
$record->filearea = self::FINAL_PDF_FILEAREA;
$record->itemid = $grade->id;
$record->filepath = '/';
$record->filename = $filename;
// Only keep one current version of the generated pdf.
$fs->delete_area_files($record->contextid, $record->component, $record->filearea, $record->itemid);
$file = $fs->create_file_from_pathname($record, $generatedpdf);
// Cleanup.
@unlink($generatedpdf);
@unlink($combined);
@rmdir($tmpdir);
return $file;
}
/**
* This function returns the generated pdf (if it exists).
* @param int|\assign $assignment
* @param int $userid
* @param int $attemptnumber (-1 means latest attempt)
* @return stored_file
*/
public static function get_feedback_document($assignment, $userid, $attemptnumber) {
$assignment = self::get_assignment_from_param($assignment);
if (!$assignment->can_view_submission($userid)) {
\print_error('nopermission');
}
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
$contextid = $assignment->get_context()->id;
$component = 'assignfeedback_editpdf';
$filearea = self::FINAL_PDF_FILEAREA;
$itemid = $grade->id;
$filepath = '/';
$fs = \get_file_storage();
$files = $fs->get_area_files($contextid,
$component,
$filearea,
$itemid,
"itemid, filepath, filename",
false);
if ($files) {
return reset($files);
}
return false;
}
/**
* This function deletes the generated pdf for a student.
* @param int|\assign $assignment
* @param int $userid
* @param int $attemptnumber (-1 means latest attempt)
* @return bool
*/
public static function delete_feedback_document($assignment, $userid, $attemptnumber) {
$assignment = self::get_assignment_from_param($assignment);
if (!$assignment->can_view_submission($userid)) {
\print_error('nopermission');
}
if (!$assignment->can_grade()) {
\print_error('nopermission');
}
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
$contextid = $assignment->get_context()->id;
$component = 'assignfeedback_editpdf';
$filearea = self::FINAL_PDF_FILEAREA;
$itemid = $grade->id;
$fs = \get_file_storage();
return $fs->delete_area_files($contextid, $component, $filearea, $itemid);
}
}
@@ -0,0 +1,303 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the editor class for the assignfeedback_editpdf plugin
*
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace assignfeedback_editpdf;
/**
* This class performs crud operations on comments and annotations from a page of a response.
*
* No capability checks are done - they should be done by the calling class.
*
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class page_editor {
/**
* Get all comments for a page.
* @param int $gradeid
* @param int $pageno
* @param bool $draft
* @return comment[]
*/
public static function get_comments($gradeid, $pageno, $draft) {
global $DB;
$comments = array();
$params = array('gradeid'=>$gradeid, 'pageno'=>$pageno, 'draft'=>1);
if (!$draft) {
$params['draft'] = 0;
}
$records = $DB->get_records('assignfeedback_editpdf_cmnt', $params);
foreach ($records as $record) {
array_push($comments, new comment($record));
}
return $comments;
}
/**
* Set all comments for a page.
* @param int $gradeid
* @param int $pageno
* @param comment[] $comments
* @return int - the number of comments.
*/
public static function set_comments($gradeid, $pageno, $comments) {
global $DB;
$DB->delete_records('assignfeedback_editpdf_cmnt', array('gradeid'=>$gradeid, 'pageno'=>$pageno, 'draft'=>1));
$added = 0;
foreach ($comments as $record) {
// Force these.
$comment = new comment($record);
if (trim($comment->rawtext) === '') {
continue;
}
$comment->gradeid = $gradeid;
$comment->pageno = $pageno;
$comment->draft = 1;
if (self::add_comment($comment)) {
$added++;
}
}
return $added;
}
/**
* Get a single comment by id.
* @param int $commentid
* @return comment or false
*/
public static function get_comment($commentid) {
$record = $DB->get_record('assignfeedback_editpdf_cmnt', array('id'=>$commentid), '*', IGNORE_MISSING);
if ($record) {
return new comment($record);
}
return false;
}
/**
* Add a comment to a page.
* @param comment $comment
* @return bool
*/
public static function add_comment(comment $comment) {
global $DB;
$comment->id = null;
return $DB->insert_record('assignfeedback_editpdf_cmnt', $comment);
}
/**
* Remove a comment from a page.
* @param int $commentid
* @return bool
*/
public static function remove_comment($commentid) {
global $DB;
return $DB->delete_records('assignfeedback_editpdf_cmnt', array('id'=>$commentid));
}
/**
* Get all annotations for a page.
* @param int $gradeid
* @param int $pageno
* @param bool $draft
* @return annotation[]
*/
public static function get_annotations($gradeid, $pageno, $draft) {
global $DB;
$params = array('gradeid'=>$gradeid, 'pageno'=>$pageno, 'draft'=>1);
if (!$draft) {
$params['draft'] = 0;
}
$annotations = array();
$records = $DB->get_records('assignfeedback_editpdf_annot', $params);
foreach ($records as $record) {
array_push($annotations, new annotation($record));
}
return $annotations;
}
/**
* Set all annotations for a page.
* @param int $gradeid
* @param int $pageno
* @param annotation[] $annotations
* @return int - the number of annotations.
*/
public static function set_annotations($gradeid, $pageno, $annotations) {
global $DB;
$DB->delete_records('assignfeedback_editpdf_annot', array('gradeid'=>$gradeid, 'pageno'=>$pageno));
$added = 0;
foreach ($annotations as $record) {
// Force these.
$annotation = new annotation($record);
$annotation->gradeid = $gradeid;
$annotation->pageno = $pageno;
$annotation->draft = 1;
if (self::add_annotation($annotation)) {
$added++;
}
}
return $added;
}
/**
* Get a single annotation by id.
* @param int $annotationid
* @return annotation or false
*/
public static function get_annotation($annotationid) {
global $DB;
$record = $DB->get_record('assignfeedback_editpdf_annot', array('id'=>$annotationid), '*', IGNORE_MISSING);
if ($record) {
return new annotation($record);
}
return false;
}
/**
* Unrelease drafts
* @param int $gradeid
* @return bool
*/
public static function unrelease_drafts($gradeid) {
global $DB;
// Delete the non-draft annotations and comments.
$result = $DB->delete_records('assignfeedback_editpdf_cmnt', array('gradeid'=>$gradeid, 'draft'=>0));
$result = $DB->delete_records('assignfeedback_editpdf_annot', array('gradeid'=>$gradeid, 'draft'=>0)) && $result;
return $result;
}
/**
* Release the draft comments and annotations to students.
* @param int $gradeid
* @return bool
*/
public static function release_drafts($gradeid) {
global $DB;
// Delete the previous non-draft annotations and comments.
$DB->delete_records('assignfeedback_editpdf_cmnt', array('gradeid'=>$gradeid, 'draft'=>0));
$DB->delete_records('assignfeedback_editpdf_annot', array('gradeid'=>$gradeid, 'draft'=>0));
// Copy all the draft annotations and comments to non-drafts.
$records = $DB->get_records('assignfeedback_editpdf_annot', array('gradeid'=>$gradeid, 'draft'=>1));
foreach ($records as $record) {
unset($record->id);
$record->draft = 0;
$DB->insert_record('assignfeedback_editpdf_annot', $record);
}
$records = $DB->get_records('assignfeedback_editpdf_cmnt', array('gradeid'=>$gradeid, 'draft'=>1));
foreach ($records as $record) {
unset($record->id);
$record->draft = 0;
$DB->insert_record('assignfeedback_editpdf_cmnt', $record);
}
return true;
}
/**
* Has annotations or comments.
* @param int $gradeid
* @return bool
*/
public static function has_annotations_or_comments($gradeid, $includedraft) {
global $DB;
$params = array('gradeid'=>$gradeid);
if (!$includedraft) {
$params['draft'] = 0;
}
if ($DB->count_records('assignfeedback_editpdf_cmnt', $params)) {
return true;
}
if ($DB->count_records('assignfeedback_editpdf_annot', $params)) {
return true;
}
return false;
}
/**
* Aborts all draft annotations and reverts to the last version released to students.
* @param int $gradeid
* @return bool
*/
public static function revert_drafts($gradeid) {
global $DB;
// Delete the previous non-draft annotations and comments.
$DB->delete_records('assignfeedback_editpdf_cmnt', array('gradeid'=>$gradeid, 'draft'=>1));
$DB->delete_records('assignfeedback_editpdf_annot', array('gradeid'=>$gradeid, 'draft'=>1));
// Copy all the draft annotations and comments to non-drafts.
$records = $DB->get_records('assignfeedback_editpdf_annot', array('gradeid'=>$gradeid, 'draft'=>0));
foreach ($records as $record) {
unset($record->id);
$record->draft = 0;
$DB->insert_record('assignfeedback_editpdf_annot', $record);
}
$records = $DB->get_records('assignfeedback_editpdf_cmnt', array('gradeid'=>$gradeid, 'draft'=>0));
foreach ($records as $record) {
unset($record->id);
$record->draft = 0;
$DB->insert_record('assignfeedback_editpdf_annot', $record);
}
return true;
}
/**
* Add a annotation to a page.
* @param annotation $annotation
* @return bool
*/
public static function add_annotation(annotation $annotation) {
global $DB;
$annotation->id = null;
return $DB->insert_record('assignfeedback_editpdf_annot', $annotation);
}
/**
* Remove a annotation from a page.
* @param int $annotationid
* @return bool
*/
public static function remove_annotation($annotationid) {
global $DB;
return $DB->delete_records('assignfeedback_editpdf_annot', array('id'=>$annotationid));
}
}
+533
View File
@@ -0,0 +1,533 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Library code for manipulating PDFs
*
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace assignfeedback_editpdf;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir.'/pdflib.php');
require_once($CFG->dirroot.'/mod/assign/feedback/editpdf/fpdi/fpdi.php');
/**
* Library code for manipulating PDFs
*
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class pdf extends \FPDI {
/** @var int the number of the current page in the PDF being processed */
protected $currentpage = 0;
/** @var int the total number of pages in the PDF being processed */
protected $pagecount = 0;
/** @var float used to scale the pixel position of annotations (in the database) to the position in the final PDF */
protected $scale = 0.0;
/** @var string the path in which to store generated page images */
protected $imagefolder = null;
/** @var string the path to the PDF currently being processed */
protected $filename = null;
/** No errors */
const GSPATH_OK = 'ok';
/** Not set */
const GSPATH_EMPTY = 'empty';
/** Does not exist */
const GSPATH_DOESNOTEXIST = 'doesnotexist';
/** Is a dir */
const GSPATH_ISDIR = 'isdir';
/** Not executable */
const GSPATH_NOTEXECUTABLE = 'notexecutable';
/** Test file missing */
const GSPATH_NOTESTFILE = 'notestfile';
/** Any other error */
const GSPATH_ERROR = 'error';
/**
* Combine the given PDF files into a single PDF. Optionally add a coversheet and coversheet fields.
* @param string[] $pdflist the filenames of the files to combine
* @param string $outfilename the filename to write to
* @return int the number of pages in the combined PDF
*/
public function combine_pdfs($pdflist, $outfilename) {
$this->setPageUnit('pt');
$this->setPrintHeader(false);
$this->setPrintFooter(false);
$this->scale = 72.0 / 100.0;
$this->SetFont('helvetica', '', 16.0 * $this->scale);
$this->SetTextColor(0, 0, 0);
$totalpagecount = 0;
foreach ($pdflist as $file) {
$pagecount = $this->setSourceFile($file);
$totalpagecount += $pagecount;
for ($i = 1; $i<=$pagecount; $i++) {
$this->create_page_from_source($i);
}
}
$this->save_pdf($outfilename);
return $totalpagecount;
}
/**
* The number of the current page in the PDF being processed
* @return int
*/
public function current_page() {
return $this->currentpage;
}
/**
* The total number of pages in the PDF being processed
* @return int
*/
public function page_count() {
return $this->pagecount;
}
/**
* Load the specified PDF and set the initial output configuration
* Used when processing comments and outputting a new PDF
* @param string $filename the path to the PDF to load
* @return int the number of pages in the PDF
*/
public function load_pdf($filename) {
$this->setPageUnit('pt');
$this->scale = 72.0 / 100.0;
$this->SetFont('helvetica', '', 16.0 * $this->scale);
$this->SetFillColor(255, 255, 176);
$this->SetDrawColor(0, 0, 0);
$this->SetLineWidth(1.0 * $this->scale);
$this->SetTextColor(0, 0, 0);
$this->setPrintHeader(false);
$this->setPrintFooter(false);
$this->pagecount = $this->setSourceFile($filename);
$this->filename = $filename;
return $this->pagecount;
}
/**
* Sets the name of the PDF to process, but only loads the file if the
* pagecount is zero (in order to count the number of pages)
* Used when generating page images (but not a new PDF)
* @param string $filename the path to the PDF to process
* @param int $pagecount optional the number of pages in the PDF, if known
* @return int the number of pages in the PDF
*/
public function set_pdf($filename, $pagecount = 0) {
if ($pagecount == 0) {
return $this->load_pdf($filename);
} else {
$this->filename = $filename;
$this->pagecount = $pagecount;
return $pagecount;
}
}
/**
* Copy the next page from the source file and set it as the current page
* @return bool true if successful
*/
public function copy_page() {
if (!$this->filename) {
return false;
}
if ($this->currentpage>=$this->pagecount) {
return false;
}
$this->currentpage++;
$this->create_page_from_source($this->currentpage);
return true;
}
/**
* Create a page from a source PDF.
*
* @param int $pageno
*/
protected function create_page_from_source($pageno) {
// Get the size (and deduce the orientation) of the next page.
$template = $this->importPage($pageno);
$size = $this->getTemplateSize($template);
$orientation = 'P';
if ($size['w'] > $size['h']) {
$orientation = 'L';
}
// Create a page of the required size / orientation.
$this->AddPage($orientation, array($size['w'], $size['h']));
// Prevent new page creation when comments are at the bottom of a page.
$this->setPageOrientation($orientation, false, 0);
// Fill in the page with the original contents from the student.
$this->useTemplate($template);
}
/**
* Copy all the remaining pages in the file
*/
public function copy_remaining_pages() {
$morepages = true;
while ($morepages) {
$morepages = $this->copy_page();
}
}
/**
* Add a comment to the current page
* @param string $text the text of the comment
* @param int $x the x-coordinate of the comment (in pixels)
* @param int $y the y-coordinate of the comment (in pixels)
* @param int $width the width of the comment (in pixels)
* @param string $colour optional the background colour of the comment (red, yellow, green, blue, white, clear)
* @return bool true if successful (always)
*/
public function add_comment($text, $x, $y, $width, $colour = 'yellow') {
if (!$this->filename) {
return false;
}
switch ($colour) {
case 'red':
$this->SetFillColor(255, 176, 176);
break;
case 'green':
$this->SetFillColor(176, 255, 176);
break;
case 'blue':
$this->SetFillColor(208, 208, 255);
break;
case 'white':
$this->SetFillColor(255, 255, 255);
break;
default: /* Yellow */
$this->SetFillColor(255, 255, 176);
break;
}
$x *= $this->scale;
$y *= $this->scale;
$width *= $this->scale;
$text = str_replace('&lt;', '<', $text);
$text = str_replace('&gt;', '>', $text);
// Draw the text with a border, but no background colour (using a background colour would cause the fill to
// appear behind any existing content on the page, hence the extra filled rectangle drawn below).
$this->MultiCell($width, 1.0, $text, 0, 'L', 0, 4, $x, $y); /* width, height, text, border, justify, fill, ln, x, y */
if ($colour != 'clear') {
$newy = $this->GetY();
// Now we know the final size of the comment, draw a rectangle with the background colour.
$this->Rect($x, $y, $width, $newy - $y, 'DF');
// Re-draw the text over the top of the background rectangle.
$this->MultiCell($width, 1.0, $text, 0, 'L', 0, 4, $x, $y); /* width, height, text, border, justify, fill, ln, x, y */
}
return true;
}
/**
* Add an annotation to the current page
* @param int $sx starting x-coordinate (in pixels)
* @param int $sy starting y-coordinate (in pixels)
* @param int $ex ending x-coordinate (in pixels)
* @param int $ey ending y-coordinate (in pixels)
* @param string $colour optional the colour of the annotation (red, yellow, green, blue, white, black)
* @param string $type optional the type of annotation (line, oval, rectangle, highlight, pen, stamp)
* @param int[]|string $path optional for 'pen' annotations this is an array of x and y coordinates for
* the line, for 'stamp' annotations it is the name of the stamp file (without the path)
* @param string $imagefolder - Folder containing stamp images.
* @return bool true if successful (always)
*/
public function add_annotation($sx, $sy, $ex, $ey, $colour = 'yellow', $type = 'line', $path, $imagefolder) {
global $CFG;
if (!$this->filename) {
return false;
}
switch ($colour) {
case 'yellow':
$colourarray = array(255, 255, 0);
break;
case 'green':
$colourarray = array(0, 255, 0);
break;
case 'blue':
$colourarray = array(0, 0, 255);
break;
case 'white':
$colourarray = array(255, 255, 255);
break;
case 'black':
$colourarray = array(0, 0, 0);
break;
default: /* Red */
$colour = 'red';
$colourarray = array(255, 0, 0);
break;
}
$this->SetDrawColorArray($colourarray);
$sx *= $this->scale;
$sy *= $this->scale;
$ex *= $this->scale;
$ey *= $this->scale;
$this->SetLineWidth(3.0 * $this->scale);
switch ($type) {
case 'oval':
$rx = abs($sx - $ex) / 2;
$ry = abs($sy - $ey) / 2;
$sx = min($sx, $ex) + $rx;
$sy = min($sy, $ey) + $ry;
$this->Ellipse($sx, $sy, $rx, $ry);
break;
case 'rectangle':
$w = abs($sx - $ex);
$h = abs($sy - $ey);
$sx = min($sx, $ex);
$sy = min($sy, $ey);
$this->Rect($sx, $sy, $w, $h);
break;
case 'highlight':
$w = abs($sx - $ex);
$h = 8.0 * $this->scale;
$sx = min($sx, $ex);
$sy = min($sy, $ey) + ($h * 0.5);
$this->SetAlpha(0.5, 'Normal', 0.5, 'Normal');
$this->SetLineWidth(8.0 * $this->scale);
$this->Rect($sx, $sy, $w, $h);
$this->SetAlpha(1.0, 'Normal', 1.0, 'Normal');
break;
case 'pen':
if ($path) {
$scalepath = array();
$points = preg_split('/[,:]/', $path);
foreach ($points as $point) {
$scalepath[] = intval($point) * $this->scale;
}
$this->PolyLine($scalepath, 'S');
}
break;
case 'stamp':
$imgfile = $imagefolder . '/' . clean_filename($path);
$w = abs($sx - $ex);
$h = abs($sy - $ey);
$sx = min($sx, $ex);
$sy = min($sy, $ey);
$this->Image($imgfile, $sx, $sy, $w, $h);
break;
default: // Line.
$this->Line($sx, $sy, $ex, $ey);
break;
}
$this->SetDrawColor(0, 0, 0);
$this->SetLineWidth(1.0 * $this->scale);
return true;
}
/**
* Save the completed PDF to the given file
* @param string $filename the filename for the PDF (including the full path)
*/
public function save_pdf($filename) {
$this->Output($filename, 'F');
}
/**
* Set the path to the folder in which to generate page image files
* @param string $folder
*/
public function set_image_folder($folder) {
$this->imagefolder = $folder;
}
/**
* Generate an image of the specified page in the PDF
* @param int $pageno the page to generate the image of
* @throws moodle_exception
* @throws coding_exception
* @return string the filename of the generated image
*/
public function get_image($pageno) {
if (!$this->filename) {
throw new \coding_exception('Attempting to generate a page image without first setting the PDF filename');
}
if (!$this->imagefolder) {
throw new \coding_exception('Attempting to generate a page image without first specifying the image output folder');
}
if (!is_dir($this->imagefolder)) {
throw new \coding_exception('The specified image output folder is not a valid folder');
}
$imagefile = $this->imagefolder.'/image_page' . $pageno . '.png';
$generate = true;
if (file_exists($imagefile)) {
if (filemtime($imagefile)>filemtime($this->filename)) {
// Make sure the image is newer than the PDF file.
$generate = false;
}
}
if ($generate) {
// Use ghostscript to generate an image of the specified page.
$gsexec = \get_config('assignfeedback_editpdf', 'gspath');
$imageres = 100;
$filename = $this->filename;
$pagenoinc = $pageno + 1;
$command = "$gsexec -q -sDEVICE=png16m -dSAFER -dBATCH -dNOPAUSE -r$imageres -dFirstPage=$pagenoinc -dLastPage=$pagenoinc ".
"-dGraphicsAlphaBits=4 -dTextAlphaBits=4 -sOutputFile=\"$imagefile\" \"$filename\"";
//$command = escapeshellcmd($command);
$result = exec($command);
if (!file_exists($imagefile)) {
$fullerror = 'Command:' . ($command) . '<br/>';
$fullerror .= 'Result:' . htmlspecialchars($result) . '<br/>';
throw new \moodle_exception('errorgenerateimage', 'assignfeedback_pdf', '', $fullerror);
}
}
return 'image_page'.$pageno.'.png';
}
/**
* Check to see if PDF is version 1.4 (or below); if not: use ghostscript to convert it
* @param stored_file $file
* @return string path to copy or converted pdf (false == fail)
*/
public static function ensure_pdf_compatible(\stored_file $file) {
global $CFG;
$fp = $file->get_content_file_handle();
$ident = fread($fp, 10);
if (substr_compare('%PDF-', $ident, 0, 5) !== 0) {
return false; // This is not a PDF file at all.
}
$ident = substr($ident, 5); // Remove the '%PDF-' part.
$ident = explode('\x0A', $ident); // Truncate to first '0a' character.
list($major, $minor) = explode('.', $ident[0]); // Split the major / minor version.
$major = intval($major);
$minor = intval($minor);
if ($major == 0 || $minor == 0) {
return false; // Not a valid PDF version number.
}
$temparea = \make_temp_directory('assignfeedback_editpdf');
$hash = $file->get_contenthash(); // Use the contenthash to make sure the temp files have unique names.
$tempsrc = $temparea . "/src-$hash.pdf";
$tempdst = $temparea . "/dst-$hash.pdf";
if ($major = 1 && $minor<=4) {
// PDF is valid version - just create a copy we can use.
$file->copy_content_to($tempdst); // Copy the file.
return $tempdst;
}
$file->copy_content_to($tempsrc); // Copy the file.
$gsexec = \get_config('assignfeedback_editpdf', 'gspath');
$command = "$gsexec -q -sDEVICE=pdfwrite -dBATCH -dNOPAUSE -sOutputFile=\"$tempdst\" \"$tempsrc\"";
//$command = escapeshellcmd($command);
exec($command);
@unlink($tempsrc);
if (!file_exists($tempdst)) {
// Something has gone wrong in the conversion.
return false;
}
return $tempdst;
}
/**
* Test that the configured path to ghostscript is correct and working.
* @param bool $generateimage - If true - a test image will be generated to verify the install.
* @return bool
*/
public static function test_gs_path($generateimage = true) {
global $CFG;
$ret = (object)array(
'status' => self::GSPATH_OK,
'message' => null,
);
$gspath = \get_config('assignfeedback_editpdf', 'gspath');
if (empty($gspath)) {
$ret->status = self::GSPATH_EMPTY;
return $ret;
}
if (!file_exists($gspath)) {
$ret->status = self::GSPATH_DOESNOTEXIST;
return $ret;
}
if (is_dir($gspath)) {
$ret->status = self::GSPATH_ISDIR;
return $ret;
}
if (!is_executable($gspath)) {
$ret->status = self::GSPATH_NOTEXECUTABLE;
return $ret;
}
$testfile = $CFG->dirroot.'/mod/assign/feedback/editpdf/tests/fixtures/testgs.pdf';
if (!file_exists($testfile)) {
$ret->status = self::GSPATH_NOTESTFILE;
return $ret;
}
if (!$generateimage) {
return $ret;
}
$testimagefolder = \make_temp_directory('assignfeedback_editpdf_test');
@unlink($testimagefolder.'/image_page0.png'); // Delete any previous test images.
$pdf = new pdf();
$pdf->set_pdf($testfile);
$pdf->set_image_folder($testimagefolder);
try {
$pdf->get_image(0);
} catch (\moodle_exception $e) {
$ret->status = self::GSPATH_ERROR;
$ret->message = $e->getMessage();
}
return $ret;
}
/**
* If the test image has been generated correctly - send it direct to the browser.
*/
public static function send_test_image() {
global $CFG;
header('Content-type: image/png');
require_once($CFG->libdir.'/filelib.php');
$testimagefolder = \make_temp_directory('assignfeedback_editpdf_test');
$testimage = $testimagefolder.'/image_page0.png';
send_file($testimage, basename($testimage));
die();
}
}
@@ -0,0 +1,247 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the definition for the library class for edit PDF renderer.
*
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* A custom renderer class that extends the plugin_renderer_base and is used by the editpdf feedback plugin.
*
* @package assignfeedback_editpdf
* @copyright 2013 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class assignfeedback_editpdf_renderer extends plugin_renderer_base {
/**
* Return the PDF button shortcut.
*
* @param string $name the name of a specific button.
* @return string the specific shortcut.
*/
private function get_shortcut($name) {
$shortcuts = array('navigate-previous-button' => 'j',
'navigate-page-select' => 'k',
'navigate-next-button' => 'l',
'searchcomments' => 'h',
'comment' => 'z',
'commentcolour' => 'x',
'select' => 'c',
'pen' => 'y',
'line' => 'u',
'rectangle' => 'i',
'oval' => 'o',
'highlight' => 'p',
'annotationcolour' => 'r',
'stamp' => 'n',
'currentstamp' => 'm');
// Return the shortcut.
return $shortcuts[$name];
}
/**
* Render a single colour button.
*
* @param string $icon - The key for the icon
* @param string $tool - The key for the lang string.
* @param string $accesskey Optional - The access key for the button.
* @param bool $disabled Optional - Is this button disabled.
* @return string
*/
private function render_toolbar_button($icon, $tool, $accesskey = null, $disabled=false) {
// Build button alt text.
$alttext = new stdClass();
$alttext->tool = $tool;
if (!empty($accesskey)) {
$alttext->shortcut = '(Alt/Shift-Alt/Ctrl-Option + ' . $accesskey . ')';
} else {
$alttext->shortcut = '';
}
$iconalt = get_string('toolbarbutton', 'assignfeedback_editpdf', $alttext);
$iconhtml = $this->pix_icon($icon, $iconalt, 'assignfeedback_editpdf');
$iconparams = array('data-tool'=>$tool, 'class'=>$tool . 'button');
if ($disabled) {
$iconparams['disabled'] = 'true';
}
if (!empty($accesskey)) {
$iconparams['accesskey'] = $accesskey;
}
return html_writer::tag('button', $iconhtml, $iconparams);
}
/**
* Render the editpdf widget in the grading form.
*
* @param assignfeedback_editpdf_widget $widget - Renderable widget containing assignment, user and attempt number.
* @return string
*/
public function render_assignfeedback_editpdf_widget(assignfeedback_editpdf_widget $widget) {
global $CFG;
$html = '';
$html .= html_writer::div(get_string('jsrequired', 'assignfeedback_editpdf'), 'hiddenifjs');
$linkid = html_writer::random_id();
if ($widget->readonly) {
$launcheditorlink = html_writer::tag('a',
get_string('viewfeedbackonline', 'assignfeedback_editpdf'),
array('id'=>$linkid, 'class'=>'btn', 'href'=>'#'));
} else {
$launcheditorlink = html_writer::tag('a',
get_string('launcheditor', 'assignfeedback_editpdf'),
array('id'=>$linkid, 'class'=>'btn', 'href'=>'#'));
}
$links = $launcheditorlink;
$links .= html_writer::tag('div',
get_string('unsavedchanges', 'assignfeedback_editpdf'),
array('class'=>'assignfeedback_editpdf_unsavedchanges warning'));
$html .= html_writer::div($links, 'visibleifjs');
$header = get_string('pluginname', 'assignfeedback_editpdf');
$body = '';
// Create the page navigation.
$navigation1 = '';
$navigation2 = '';
// Pick the correct arrow icons for right to left mode.
if (right_to_left()) {
$nav_prev = 'nav_next';
$nav_next = 'nav_prev';
} else {
$nav_prev = 'nav_prev';
$nav_next = 'nav_next';
}
$iconalt = get_string('navigateprevious', 'assignfeedback_editpdf');
$iconhtml = $this->pix_icon($nav_prev, $iconalt, 'assignfeedback_editpdf');
$navigation1 .= html_writer::tag('button', $iconhtml, array('disabled'=>'true',
'class'=>'navigate-previous-button', 'accesskey' => $this->get_shortcut('navigate-previous-button')));
$pageoptions = html_writer::tag('option', get_string('gotopage', 'assignfeedback_editpdf'), array('value'=>''));
$navigation1 .= html_writer::tag('select', $pageoptions, array('disabled'=>'true',
'class'=>'navigate-page-select', 'accesskey' => $this->get_shortcut('navigate-page-select')));
$iconalt = get_string('navigatenext', 'assignfeedback_editpdf');
$iconhtml = $this->pix_icon($nav_next, $iconalt, 'assignfeedback_editpdf');
$navigation1 .= html_writer::tag('button', $iconhtml, array('disabled'=>'true',
'class'=>'navigate-next-button', 'accesskey' => $this->get_shortcut('navigate-next-button')));
$navigation1 = html_writer::div($navigation1, 'navigation', array('role'=>'navigation'));
$navigation2 .= $this->render_toolbar_button('comment_search', 'searchcomments', $this->get_shortcut('searchcomments'));
$navigation2 = html_writer::div($navigation2, 'navigation-search', array('role'=>'navigation'));
$toolbar1 = '';
$toolbar2 = '';
$toolbar3 = '';
$toolbar4 = '';
$clearfix = html_writer::div('', 'clearfix');
if (!$widget->readonly) {
// Comments.
$toolbar1 .= $this->render_toolbar_button('comment', 'comment', $this->get_shortcut('comment'));
$toolbar1 .= $this->render_toolbar_button('background_colour_clear', 'commentcolour', $this->get_shortcut('commentcolour'));
$toolbar1 = html_writer::div($toolbar1, 'toolbar', array('role'=>'toolbar'));
// Select Tool.
$toolbar2 .= $this->render_toolbar_button('select', 'select', $this->get_shortcut('select'));
$toolbar2 = html_writer::div($toolbar2, 'toolbar', array('role'=>'toolbar'));
// Other Tools.
$toolbar3 = $this->render_toolbar_button('pen', 'pen', $this->get_shortcut('pen'));
$toolbar3 .= $this->render_toolbar_button('line', 'line', $this->get_shortcut('line'));
$toolbar3 .= $this->render_toolbar_button('rectangle', 'rectangle', $this->get_shortcut('rectangle'));
$toolbar3 .= $this->render_toolbar_button('oval', 'oval', $this->get_shortcut('oval'));
$toolbar3 .= $this->render_toolbar_button('highlight', 'highlight', $this->get_shortcut('highlight'));
$toolbar3 .= $this->render_toolbar_button('background_colour_clear', 'annotationcolour', $this->get_shortcut('annotationcolour'));
$toolbar3 = html_writer::div($toolbar3, 'toolbar', array('role'=>'toolbar'));
// Stamps.
$toolbar4 .= $this->render_toolbar_button('stamp', 'stamp', 'n');
$toolbar4 .= $this->render_toolbar_button('background_colour_clear', 'currentstamp', $this->get_shortcut('currentstamp'));
$toolbar4 = html_writer::div($toolbar4, 'toolbar', array('role'=>'toolbar'));
}
// Toobars written in reverse order because they are floated right.
$pageheader = html_writer::div($navigation1 .
$navigation2 .
$toolbar4 .
$toolbar3 .
$toolbar2 .
$toolbar1 .
$clearfix,
'pageheader');
$body = $pageheader;
$loading = $this->pix_icon('i/loading', get_string('loadingeditor', 'assignfeedback_editpdf'), 'moodle', array('class'=>'loading'));
$canvas = html_writer::div($loading, 'drawingcanvas');
$body .= html_writer::div($canvas, 'drawingregion');
$body .= '<hr/>';
$footer = '';
$editorparams = array(array('header'=>$header,
'body'=>$body,
'footer'=>$footer,
'linkid'=>$linkid,
'assignmentid'=>$widget->assignment,
'userid'=>$widget->userid,
'attemptnumber'=>$widget->attemptnumber,
'stampfiles'=>$widget->stampfiles,
'readonly'=>$widget->readonly));
$this->page->requires->yui_module('moodle-assignfeedback_editpdf-editor',
'M.assignfeedback_editpdf.editor.init',
$editorparams);
$this->page->requires->strings_for_js(array(
'yellow',
'white',
'red',
'blue',
'green',
'black',
'clear',
'colourpicker',
'loadingeditor',
'pagexofy',
'deletecomment',
'addtoquicklist',
'filter',
'searchcomments',
'commentcontextmenu',
'deleteannotation',
'stamp',
'stamppicker',
'cannotopenpdf'
), 'assignfeedback_editpdf');
return $html;
}
}
@@ -0,0 +1,70 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the definition for the library class for edit PDF renderer.
*
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* A custom renderer class that extends the plugin_renderer_base and is used by the editpdf feedback plugin.
*
* @package assignfeedback_editpdf
* @copyright 2013 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class assignfeedback_editpdf_widget implements renderable {
/** @var int $assignment - Assignment instance id */
public $assignment = 0;
/** @var int $userid - The user id we are grading */
public $userid = 0;
/** @var mixed $attemptnumber - The attempt number we are grading */
public $attemptnumber = 0;
/** @var moodle_url $downloadurl */
public $downloadurl = null;
/** @var string $downloadfilename */
public $downloadfilename = null;
/** @var string[] $stampfiles */
public $stampfiles = array();
/** @var bool $readonly */
public $readonly = true;
/**
* Constructor
* @param int $assignment - Assignment instance id
* @param int $userid - The user id we are grading
* @param int $attemptnumber - The attempt number we are grading
* @param moodle_url $downloadurl - A url to download the current generated pdf.
* @param string $downloadfilename - Name of the generated pdf.
* @param string[] $stampfiles - The file names of the stamps.
* @param bool $readonly - Show the readonly interface (no tools).
*/
public function __construct($assignment, $userid, $attemptnumber, $downloadurl, $downloadfilename, $stampfiles, $readonly) {
$this->assignment = $assignment;
$this->userid = $userid;
$this->attemptnumber = $attemptnumber;
$this->downloadurl = $downloadurl;
$this->downloadfilename = $downloadfilename;
$this->stampfiles = $stampfiles;
$this->readonly = $readonly;
}
}
+63
View File
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/assign/feedback/editpdf/db" VERSION="20130926" COMMENT="XMLDB file for Moodle mod/assign/feedback/editpdf"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="assignfeedback_editpdf_cmnt" COMMENT="Stores comments added to pdfs">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="gradeid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="x" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="x-position of the top-left corner of the comment (in pixels - image resolution is set to 100 pixels per inch)"/>
<FIELD NAME="y" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="y-position of the top-left corner of the comment (in pixels - image resolution is set to 100 pixels per inch)"/>
<FIELD NAME="width" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="120" SEQUENCE="false" COMMENT="width, in pixels, of the comment box"/>
<FIELD NAME="rawtext" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Raw text of the comment"/>
<FIELD NAME="pageno" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The page in the PDF that this comment appears on"/>
<FIELD NAME="colour" TYPE="char" LENGTH="10" NOTNULL="false" DEFAULT="black" SEQUENCE="false" COMMENT="Can be red, yellow, green, blue, white, black"/>
<FIELD NAME="draft" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Is this a draft comment?"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="gradeid" TYPE="foreign" FIELDS="gradeid" REFTABLE="assign_grade" REFFIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="gradeid_pageno" UNIQUE="false" FIELDS="gradeid, pageno"/>
</INDEXES>
</TABLE>
<TABLE NAME="assignfeedback_editpdf_annot" COMMENT="stores annotations added to pdfs submitted by students">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="gradeid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="pageno" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The page in the PDF that this annotation appears on"/>
<FIELD NAME="x" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="x-position of the start of the annotation (in pixels - image resolution is set to 100 pixels per inch)"/>
<FIELD NAME="y" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="y-position of the start of the annotation (in pixels - image resolution is set to 100 pixels per inch)"/>
<FIELD NAME="endx" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="x-position of the end of the annotation"/>
<FIELD NAME="endy" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="y-position of the end of the annotation"/>
<FIELD NAME="path" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="SVG path describing the freehand line"/>
<FIELD NAME="type" TYPE="char" LENGTH="10" NOTNULL="false" DEFAULT="line" SEQUENCE="false" COMMENT="line, oval, rect, etc."/>
<FIELD NAME="colour" TYPE="char" LENGTH="10" NOTNULL="false" DEFAULT="black" SEQUENCE="false" COMMENT="Can be red, yellow, green, blue, white, black"/>
<FIELD NAME="draft" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Is this a draft annotation?"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="gradeid" TYPE="foreign" FIELDS="gradeid" REFTABLE="assign_grade" REFFIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="gradeid_pageno" UNIQUE="false" FIELDS="gradeid, pageno"/>
</INDEXES>
</TABLE>
<TABLE NAME="assignfeedback_editpdf_quick" COMMENT="Stores teacher specified quicklist comments">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="rawtext" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="width" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="120" SEQUENCE="false"/>
<FIELD NAME="colour" TYPE="char" LENGTH="10" NOTNULL="false" DEFAULT="yellow" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,101 @@
<?php
//
// FPDI - Version 1.4.4
//
// Copyright 2004-2013 Setasign - Jan Slabon
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
if (!defined('ORD_z'))
define('ORD_z',ord('z'));
if (!defined('ORD_exclmark'))
define('ORD_exclmark', ord('!'));
if (!defined('ORD_u'))
define('ORD_u', ord('u'));
if (!defined('ORD_tilde'))
define('ORD_tilde', ord('~'));
if (!class_exists('FilterASCII85', false)) {
class FilterASCII85 {
function error($msg) {
die($msg);
}
function decode($in) {
$out = '';
$state = 0;
$chn = null;
$l = strlen($in);
for ($k = 0; $k < $l; ++$k) {
$ch = ord($in[$k]) & 0xff;
if ($ch == ORD_tilde) {
break;
}
if (preg_match('/^\s$/',chr($ch))) {
continue;
}
if ($ch == ORD_z && $state == 0) {
$out .= chr(0) . chr(0) . chr(0) . chr(0);
continue;
}
if ($ch < ORD_exclmark || $ch > ORD_u) {
return $this->error('Illegal character in ASCII85Decode.');
}
$chn[$state++] = $ch - ORD_exclmark;
if ($state == 5) {
$state = 0;
$r = 0;
for ($j = 0; $j < 5; ++$j)
$r = $r * 85 + $chn[$j];
$out .= chr($r >> 24);
$out .= chr($r >> 16);
$out .= chr($r >> 8);
$out .= chr($r);
}
}
$r = 0;
if ($state == 1)
return $this->error('Illegal length in ASCII85Decode.');
if ($state == 2) {
$r = $chn[0] * 85 * 85 * 85 * 85 + ($chn[1]+1) * 85 * 85 * 85;
$out .= chr($r >> 24);
}
else if ($state == 3) {
$r = $chn[0] * 85 * 85 * 85 * 85 + $chn[1] * 85 * 85 * 85 + ($chn[2]+1) * 85 * 85;
$out .= chr($r >> 24);
$out .= chr($r >> 16);
}
else if ($state == 4) {
$r = $chn[0] * 85 * 85 * 85 * 85 + $chn[1] * 85 * 85 * 85 + $chn[2] * 85 * 85 + ($chn[3]+1) * 85 ;
$out .= chr($r >> 24);
$out .= chr($r >> 16);
$out .= chr($r >> 8);
}
return $out;
}
function encode($in) {
return $this->error("ASCII85 encoding not implemented.");
}
}
}
@@ -0,0 +1,33 @@
<?php
//
// FPDI - Version 1.4.4
//
// Copyright 2004-2013 Setasign - Jan Slabon
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
require_once('FilterASCII85.php');
class FilterASCII85_FPDI extends FilterASCII85 {
var $fpdi;
function FilterASCII85_FPDI(&$fpdi) {
$this->fpdi =& $fpdi;
}
function error($msg) {
$this->fpdi->error($msg);
}
}
@@ -0,0 +1,157 @@
<?php
//
// FPDI - Version 1.4.4
//
// Copyright 2004-2013 Setasign - Jan Slabon
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
if (!class_exists('FilterLZW', false)) {
class FilterLZW {
var $sTable = array();
var $data = null;
var $dataLength = 0;
var $tIdx;
var $bitsToGet = 9;
var $bytePointer;
var $bitPointer;
var $nextData = 0;
var $nextBits = 0;
var $andTable = array(511, 1023, 2047, 4095);
function error($msg) {
die($msg);
}
/**
* Method to decode LZW compressed data.
*
* @param string data The compressed data.
*/
function decode($data) {
if($data[0] == 0x00 && $data[1] == 0x01) {
$this->error('LZW flavour not supported.');
}
$this->initsTable();
$this->data = $data;
$this->dataLength = strlen($data);
// Initialize pointers
$this->bytePointer = 0;
$this->bitPointer = 0;
$this->nextData = 0;
$this->nextBits = 0;
$oldCode = 0;
$string = '';
$uncompData = '';
while (($code = $this->getNextCode()) != 257) {
if ($code == 256) {
$this->initsTable();
$code = $this->getNextCode();
if ($code == 257) {
break;
}
$uncompData .= $this->sTable[$code];
$oldCode = $code;
} else {
if ($code < $this->tIdx) {
$string = $this->sTable[$code];
$uncompData .= $string;
$this->addStringToTable($this->sTable[$oldCode], $string[0]);
$oldCode = $code;
} else {
$string = $this->sTable[$oldCode];
$string = $string . $string[0];
$uncompData .= $string;
$this->addStringToTable($string);
$oldCode = $code;
}
}
}
return $uncompData;
}
/**
* Initialize the string table.
*/
function initsTable() {
$this->sTable = array();
for ($i = 0; $i < 256; $i++)
$this->sTable[$i] = chr($i);
$this->tIdx = 258;
$this->bitsToGet = 9;
}
/**
* Add a new string to the string table.
*/
function addStringToTable ($oldString, $newString='') {
$string = $oldString.$newString;
// Add this new String to the table
$this->sTable[$this->tIdx++] = $string;
if ($this->tIdx == 511) {
$this->bitsToGet = 10;
} else if ($this->tIdx == 1023) {
$this->bitsToGet = 11;
} else if ($this->tIdx == 2047) {
$this->bitsToGet = 12;
}
}
// Returns the next 9, 10, 11 or 12 bits
function getNextCode() {
if ($this->bytePointer == $this->dataLength) {
return 257;
}
$this->nextData = ($this->nextData << 8) | (ord($this->data[$this->bytePointer++]) & 0xff);
$this->nextBits += 8;
if ($this->nextBits < $this->bitsToGet) {
$this->nextData = ($this->nextData << 8) | (ord($this->data[$this->bytePointer++]) & 0xff);
$this->nextBits += 8;
}
$code = ($this->nextData >> ($this->nextBits - $this->bitsToGet)) & $this->andTable[$this->bitsToGet-9];
$this->nextBits -= $this->bitsToGet;
return $code;
}
function encode($in) {
$this->error("LZW encoding not implemented.");
}
}
}
@@ -0,0 +1,33 @@
<?php
//
// FPDI - Version 1.4.4
//
// Copyright 2004-2013 Setasign - Jan Slabon
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
require_once('FilterLZW.php');
class FilterLZW_FPDI extends FilterLZW {
var $fpdi;
function FilterLZW_FPDI(&$fpdi) {
$this->fpdi =& $fpdi;
}
function error($msg) {
$this->fpdi->error($msg);
}
}
@@ -0,0 +1,460 @@
<?php
//
// FPDF_TPL - Version 1.2.3
//
// Copyright 2004-2013 Setasign - Jan Slabon
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
class FPDF_TPL extends FPDF {
/**
* Array of Tpl-Data
* @var array
*/
var $tpls = array();
/**
* Current Template-ID
* @var int
*/
var $tpl = 0;
/**
* "In Template"-Flag
* @var boolean
*/
var $_intpl = false;
/**
* Nameprefix of Templates used in Resources-Dictonary
* @var string A String defining the Prefix used as Template-Object-Names. Have to beginn with an /
*/
var $tplprefix = "/TPL";
/**
* Resources used By Templates and Pages
* @var array
*/
var $_res = array();
/**
* Last used Template data
*
* @var array
*/
var $lastUsedTemplateData = array();
/**
* Start a Template
*
* This method starts a template. You can give own coordinates to build an own sized
* Template. Pay attention, that the margins are adapted to the new templatesize.
* If you want to write outside the template, for example to build a clipped Template,
* you have to set the Margins and "Cursor"-Position manual after beginTemplate-Call.
*
* If no parameter is given, the template uses the current page-size.
* The Method returns an ID of the current Template. This ID is used later for using this template.
* Warning: A created Template is used in PDF at all events. Still if you don't use it after creation!
*
* @param int $x The x-coordinate given in user-unit
* @param int $y The y-coordinate given in user-unit
* @param int $w The width given in user-unit
* @param int $h The height given in user-unit
* @return int The ID of new created Template
*/
function beginTemplate($x = null, $y = null, $w = null, $h = null) {
if (is_subclass_of($this, 'TCPDF')) {
$this->Error('This method is only usable with FPDF. Use TCPDF methods startTemplate() instead.');
return;
}
if ($this->page <= 0)
$this->error("You have to add a page to fpdf first!");
if ($x == null)
$x = 0;
if ($y == null)
$y = 0;
if ($w == null)
$w = $this->w;
if ($h == null)
$h = $this->h;
// Save settings
$this->tpl++;
$tpl =& $this->tpls[$this->tpl];
$tpl = array(
'o_x' => $this->x,
'o_y' => $this->y,
'o_AutoPageBreak' => $this->AutoPageBreak,
'o_bMargin' => $this->bMargin,
'o_tMargin' => $this->tMargin,
'o_lMargin' => $this->lMargin,
'o_rMargin' => $this->rMargin,
'o_h' => $this->h,
'o_w' => $this->w,
'o_FontFamily' => $this->FontFamily,
'o_FontStyle' => $this->FontStyle,
'o_FontSizePt' => $this->FontSizePt,
'o_FontSize' => $this->FontSize,
'buffer' => '',
'x' => $x,
'y' => $y,
'w' => $w,
'h' => $h
);
$this->SetAutoPageBreak(false);
// Define own high and width to calculate possitions correct
$this->h = $h;
$this->w = $w;
$this->_intpl = true;
$this->SetXY($x + $this->lMargin, $y + $this->tMargin);
$this->SetRightMargin($this->w - $w + $this->rMargin);
if ($this->CurrentFont) {
$fontkey = $this->FontFamily . $this->FontStyle;
$this->_res['tpl'][$this->tpl]['fonts'][$fontkey] =& $this->fonts[$fontkey];
$this->_out(sprintf('BT /F%d %.2f Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
}
return $this->tpl;
}
/**
* End Template
*
* This method ends a template and reset initiated variables on beginTemplate.
*
* @return mixed If a template is opened, the ID is returned. If not a false is returned.
*/
function endTemplate() {
if (is_subclass_of($this, 'TCPDF')) {
$args = func_get_args();
return call_user_func_array(array($this, 'TCPDF::endTemplate'), $args);
}
if ($this->_intpl) {
$this->_intpl = false;
$tpl =& $this->tpls[$this->tpl];
$this->SetXY($tpl['o_x'], $tpl['o_y']);
$this->tMargin = $tpl['o_tMargin'];
$this->lMargin = $tpl['o_lMargin'];
$this->rMargin = $tpl['o_rMargin'];
$this->h = $tpl['o_h'];
$this->w = $tpl['o_w'];
$this->SetAutoPageBreak($tpl['o_AutoPageBreak'], $tpl['o_bMargin']);
$this->FontFamily = $tpl['o_FontFamily'];
$this->FontStyle = $tpl['o_FontStyle'];
$this->FontSizePt = $tpl['o_FontSizePt'];
$this->FontSize = $tpl['o_FontSize'];
$fontkey = $this->FontFamily . $this->FontStyle;
if ($fontkey)
$this->CurrentFont =& $this->fonts[$fontkey];
return $this->tpl;
} else {
return false;
}
}
/**
* Use a Template in current Page or other Template
*
* You can use a template in a page or in another template.
* You can give the used template a new size like you use the Image()-method.
* All parameters are optional. The width or height is calculated automaticaly
* if one is given. If no parameter is given the origin size as defined in
* beginTemplate() is used.
* The calculated or used width and height are returned as an array.
*
* @param int $tplidx A valid template-Id
* @param int $_x The x-position
* @param int $_y The y-position
* @param int $_w The new width of the template
* @param int $_h The new height of the template
* @retrun array The height and width of the template
*/
function useTemplate($tplidx, $_x = null, $_y = null, $_w = 0, $_h = 0) {
if ($this->page <= 0)
$this->error('You have to add a page first!');
if (!isset($this->tpls[$tplidx]))
$this->error('Template does not exist!');
if ($this->_intpl) {
$this->_res['tpl'][$this->tpl]['tpls'][$tplidx] =& $this->tpls[$tplidx];
}
$tpl =& $this->tpls[$tplidx];
$w = $tpl['w'];
$h = $tpl['h'];
if ($_x == null)
$_x = 0;
if ($_y == null)
$_y = 0;
$_x += $tpl['x'];
$_y += $tpl['y'];
$wh = $this->getTemplateSize($tplidx, $_w, $_h);
$_w = $wh['w'];
$_h = $wh['h'];
$tData = array(
'x' => $this->x,
'y' => $this->y,
'w' => $_w,
'h' => $_h,
'scaleX' => ($_w / $w),
'scaleY' => ($_h / $h),
'tx' => $_x,
'ty' => ($this->h - $_y - $_h),
'lty' => ($this->h - $_y - $_h) - ($this->h - $h) * ($_h / $h)
);
$this->_out(sprintf('q %.4F 0 0 %.4F %.4F %.4F cm', $tData['scaleX'], $tData['scaleY'], $tData['tx'] * $this->k, $tData['ty'] * $this->k)); // Translate
$this->_out(sprintf('%s%d Do Q', $this->tplprefix, $tplidx));
$this->lastUsedTemplateData = $tData;
return array('w' => $_w, 'h' => $_h);
}
/**
* Get The calculated Size of a Template
*
* If one size is given, this method calculates the other one.
*
* @param int $tplidx A valid template-Id
* @param int $_w The width of the template
* @param int $_h The height of the template
* @return array The height and width of the template
*/
function getTemplateSize($tplidx, $_w = 0, $_h = 0) {
if (!isset($this->tpls[$tplidx]))
return false;
$tpl =& $this->tpls[$tplidx];
$w = $tpl['w'];
$h = $tpl['h'];
if ($_w == 0 and $_h == 0) {
$_w = $w;
$_h = $h;
}
if($_w == 0)
$_w = $_h * $w / $h;
if($_h == 0)
$_h = $_w * $h / $w;
return array("w" => $_w, "h" => $_h);
}
/**
* See FPDF/TCPDF-Documentation ;-)
*/
public function SetFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
if (is_subclass_of($this, 'TCPDF')) {
$args = func_get_args();
return call_user_func_array(array($this, 'TCPDF::SetFont'), $args);
}
parent::SetFont($family, $style, $size);
$fontkey = $this->FontFamily . $this->FontStyle;
if ($this->_intpl) {
$this->_res['tpl'][$this->tpl]['fonts'][$fontkey] =& $this->fonts[$fontkey];
} else {
$this->_res['page'][$this->page]['fonts'][$fontkey] =& $this->fonts[$fontkey];
}
}
/**
* See FPDF/TCPDF-Documentation ;-)
*/
function Image(
$file, $x = '', $y = '', $w = 0, $h = 0, $type = '', $link = '', $align = '', $resize = false,
$dpi = 300, $palign = '', $ismask = false, $imgmask = false, $border = 0, $fitbox = false,
$hidden = false, $fitonpage = false, $alt = false, $altimgs = array()
) {
if (is_subclass_of($this, 'TCPDF')) {
$args = func_get_args();
return call_user_func_array(array($this, 'TCPDF::Image'), $args);
}
$ret = parent::Image($file, $x, $y, $w, $h, $type, $link);
if ($this->_intpl) {
$this->_res['tpl'][$this->tpl]['images'][$file] =& $this->images[$file];
} else {
$this->_res['page'][$this->page]['images'][$file] =& $this->images[$file];
}
return $ret;
}
/**
* See FPDF-Documentation ;-)
*
* AddPage is not available when you're "in" a template.
*/
function AddPage($orientation = '', $format = '', $keepmargins = false, $tocpage = false) {
if (is_subclass_of($this, 'TCPDF')) {
$args = func_get_args();
return call_user_func_array(array($this, 'TCPDF::AddPage'), $args);
}
if ($this->_intpl)
$this->Error('Adding pages in templates isn\'t possible!');
parent::AddPage($orientation, $format);
}
/**
* Preserve adding Links in Templates ...won't work
*/
function Link($x, $y, $w, $h, $link, $spaces = 0) {
if (is_subclass_of($this, 'TCPDF')) {
$args = func_get_args();
return call_user_func_array(array($this, 'TCPDF::Link'), $args);
}
if ($this->_intpl)
$this->Error('Using links in templates aren\'t possible!');
parent::Link($x, $y, $w, $h, $link);
}
function AddLink() {
if (is_subclass_of($this, 'TCPDF')) {
$args = func_get_args();
return call_user_func_array(array($this, 'TCPDF::AddLink'), $args);
}
if ($this->_intpl)
$this->Error('Adding links in templates aren\'t possible!');
return parent::AddLink();
}
function SetLink($link, $y = 0, $page = -1) {
if (is_subclass_of($this, 'TCPDF')) {
$args = func_get_args();
return call_user_func_array(array($this, 'TCPDF::SetLink'), $args);
}
if ($this->_intpl)
$this->Error('Setting links in templates aren\'t possible!');
parent::SetLink($link, $y, $page);
}
/**
* Private Method that writes the form xobjects
*/
function _putformxobjects() {
$filter=($this->compress) ? '/Filter /FlateDecode ' : '';
reset($this->tpls);
foreach($this->tpls AS $tplidx => $tpl) {
$p=($this->compress) ? gzcompress($tpl['buffer']) : $tpl['buffer'];
$this->_newobj();
$this->tpls[$tplidx]['n'] = $this->n;
$this->_out('<<'.$filter.'/Type /XObject');
$this->_out('/Subtype /Form');
$this->_out('/FormType 1');
$this->_out(sprintf('/BBox [%.2F %.2F %.2F %.2F]',
// llx
$tpl['x'] * $this->k,
// lly
-$tpl['y'] * $this->k,
// urx
($tpl['w'] + $tpl['x']) * $this->k,
// ury
($tpl['h'] - $tpl['y']) * $this->k
));
if ($tpl['x'] != 0 || $tpl['y'] != 0) {
$this->_out(sprintf('/Matrix [1 0 0 1 %.5F %.5F]',
-$tpl['x'] * $this->k * 2, $tpl['y'] * $this->k * 2
));
}
$this->_out('/Resources ');
$this->_out('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
if (isset($this->_res['tpl'][$tplidx]['fonts']) && count($this->_res['tpl'][$tplidx]['fonts'])) {
$this->_out('/Font <<');
foreach($this->_res['tpl'][$tplidx]['fonts'] as $font)
$this->_out('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R');
$this->_out('>>');
}
if(isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images']) ||
isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls']))
{
$this->_out('/XObject <<');
if (isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images'])) {
foreach($this->_res['tpl'][$tplidx]['images'] as $image)
$this->_out('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R');
}
if (isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls'])) {
foreach($this->_res['tpl'][$tplidx]['tpls'] as $i => $tpl)
$this->_out($this->tplprefix . $i . ' ' . $tpl['n'] . ' 0 R');
}
$this->_out('>>');
}
$this->_out('>>');
$this->_out('/Length ' . strlen($p) . ' >>');
$this->_putstream($p);
$this->_out('endobj');
}
}
/**
* Overwritten to add _putformxobjects() after _putimages()
*
*/
function _putimages() {
parent::_putimages();
$this->_putformxobjects();
}
function _putxobjectdict() {
parent::_putxobjectdict();
if (count($this->tpls)) {
foreach($this->tpls as $tplidx => $tpl) {
$this->_out(sprintf('%s%d %d 0 R', $this->tplprefix, $tplidx, $tpl['n']));
}
}
}
/**
* Private Method
*/
function _out($s) {
if ($this->state == 2 && $this->_intpl) {
$this->tpls[$this->tpl]['buffer'] .= $s . "\n";
} else {
parent::_out($s);
}
}
}
+588
View File
@@ -0,0 +1,588 @@
<?php
//
// FPDI - Version 1.4.4
//
// Copyright 2004-2013 Setasign - Jan Slabon
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
define('FPDI_VERSION', '1.4.4');
// Check for TCPDF and remap TCPDF to FPDF
if (class_exists('TCPDF', false)) {
require_once('fpdi2tcpdf_bridge.php');
}
require_once('fpdf_tpl.php');
require_once('fpdi_pdf_parser.php');
class FPDI extends FPDF_TPL {
/**
* Actual filename
* @var string
*/
var $current_filename;
/**
* Parser-Objects
* @var array
*/
var $parsers;
/**
* Current parser
* @var object
*/
var $current_parser;
/**
* object stack
* @var array
*/
var $_obj_stack;
/**
* done object stack
* @var array
*/
var $_don_obj_stack;
/**
* Current Object Id.
* @var integer
*/
var $_current_obj_id;
/**
* The name of the last imported page box
* @var string
*/
var $lastUsedPageBox;
/**
* Cache for imported pages/template ids
* @var array
*/
var $_importedPages = array();
/**
* Set a source-file
*
* @param string $filename a valid filename
* @return int number of available pages
*/
function setSourceFile($filename) {
$this->current_filename = $filename;
if (!isset($this->parsers[$filename]))
$this->parsers[$filename] = $this->_getPdfParser($filename);
$this->current_parser =& $this->parsers[$filename];
return $this->parsers[$filename]->getPageCount();
}
/**
* Returns a PDF parser object
*
* @param string $filename
* @return fpdi_pdf_parser
*/
function _getPdfParser($filename) {
return new fpdi_pdf_parser($filename, $this);
}
/**
* Get the current PDF version
*
* @return string
*/
function getPDFVersion() {
return $this->PDFVersion;
}
/**
* Set the PDF version
*
* @return string
*/
function setPDFVersion($version = '1.3') {
$this->PDFVersion = $version;
}
/**
* Import a page
*
* @param int $pageno pagenumber
* @return int Index of imported page - to use with fpdf_tpl::useTemplate()
*/
function importPage($pageno, $boxName = '/CropBox') {
if ($this->_intpl) {
return $this->error('Please import the desired pages before creating a new template.');
}
$fn = $this->current_filename;
// check if page already imported
$pageKey = $fn . '-' . ((int)$pageno) . $boxName;
if (isset($this->_importedPages[$pageKey]))
return $this->_importedPages[$pageKey];
$parser =& $this->parsers[$fn];
$parser->setPageno($pageno);
if (!in_array($boxName, $parser->availableBoxes))
return $this->Error(sprintf('Unknown box: %s', $boxName));
$pageboxes = $parser->getPageBoxes($pageno, $this->k);
/**
* MediaBox
* CropBox: Default -> MediaBox
* BleedBox: Default -> CropBox
* TrimBox: Default -> CropBox
* ArtBox: Default -> CropBox
*/
if (!isset($pageboxes[$boxName]) && ($boxName == '/BleedBox' || $boxName == '/TrimBox' || $boxName == '/ArtBox'))
$boxName = '/CropBox';
if (!isset($pageboxes[$boxName]) && $boxName == '/CropBox')
$boxName = '/MediaBox';
if (!isset($pageboxes[$boxName]))
return false;
$this->lastUsedPageBox = $boxName;
$box = $pageboxes[$boxName];
$this->tpl++;
$this->tpls[$this->tpl] = array();
$tpl =& $this->tpls[$this->tpl];
$tpl['parser'] =& $parser;
$tpl['resources'] = $parser->getPageResources();
$tpl['buffer'] = $parser->getContent();
$tpl['box'] = $box;
// To build an array that can be used by PDF_TPL::useTemplate()
$this->tpls[$this->tpl] = array_merge($this->tpls[$this->tpl], $box);
// An imported page will start at 0,0 everytime. Translation will be set in _putformxobjects()
$tpl['x'] = 0;
$tpl['y'] = 0;
// handle rotated pages
$rotation = $parser->getPageRotation($pageno);
$tpl['_rotationAngle'] = 0;
if (isset($rotation[1]) && ($angle = $rotation[1] % 360) != 0) {
$steps = $angle / 90;
$_w = $tpl['w'];
$_h = $tpl['h'];
$tpl['w'] = $steps % 2 == 0 ? $_w : $_h;
$tpl['h'] = $steps % 2 == 0 ? $_h : $_w;
if ($angle < 0)
$angle += 360;
$tpl['_rotationAngle'] = $angle * -1;
}
$this->_importedPages[$pageKey] = $this->tpl;
return $this->tpl;
}
/**
* Returns the last used page box
*
* @return string
*/
function getLastUsedPageBox() {
return $this->lastUsedPageBox;
}
function useTemplate($tplidx, $_x = null, $_y = null, $_w = 0, $_h = 0, $adjustPageSize = false) {
if ($adjustPageSize == true && is_null($_x) && is_null($_y)) {
$size = $this->getTemplateSize($tplidx, $_w, $_h);
$orientation = $size['w'] > $size['h'] ? 'L' : 'P';
$size = array($size['w'], $size['h']);
if (is_subclass_of($this, 'TCPDF')) {
$this->setPageFormat($size, $orientation);
} else {
$size = $this->_getpagesize($size);
if($orientation!=$this->CurOrientation || $size[0]!=$this->CurPageSize[0] || $size[1]!=$this->CurPageSize[1])
{
// New size or orientation
if($orientation=='P')
{
$this->w = $size[0];
$this->h = $size[1];
}
else
{
$this->w = $size[1];
$this->h = $size[0];
}
$this->wPt = $this->w*$this->k;
$this->hPt = $this->h*$this->k;
$this->PageBreakTrigger = $this->h-$this->bMargin;
$this->CurOrientation = $orientation;
$this->CurPageSize = $size;
$this->PageSizes[$this->page] = array($this->wPt, $this->hPt);
}
}
}
$this->_out('q 0 J 1 w 0 j 0 G 0 g'); // reset standard values
$s = parent::useTemplate($tplidx, $_x, $_y, $_w, $_h);
$this->_out('Q');
return $s;
}
/**
* Private method, that rebuilds all needed objects of source files
*/
function _putimportedobjects() {
if (is_array($this->parsers) && count($this->parsers) > 0) {
foreach($this->parsers AS $filename => $p) {
$this->current_parser =& $this->parsers[$filename];
if (isset($this->_obj_stack[$filename]) && is_array($this->_obj_stack[$filename])) {
while(($n = key($this->_obj_stack[$filename])) !== null) {
$nObj = $this->current_parser->pdf_resolve_object($this->current_parser->c, $this->_obj_stack[$filename][$n][1]);
$this->_newobj($this->_obj_stack[$filename][$n][0]);
if ($nObj[0] == PDF_TYPE_STREAM) {
$this->pdf_write_value($nObj);
} else {
$this->pdf_write_value($nObj[1]);
}
$this->_out('endobj');
$this->_obj_stack[$filename][$n] = null; // free memory
unset($this->_obj_stack[$filename][$n]);
reset($this->_obj_stack[$filename]);
}
}
}
}
}
/**
* Private Method that writes the form xobjects
*/
function _putformxobjects() {
$filter=($this->compress) ? '/Filter /FlateDecode ' : '';
reset($this->tpls);
foreach($this->tpls AS $tplidx => $tpl) {
$p=($this->compress) ? gzcompress($tpl['buffer']) : $tpl['buffer'];
$this->_newobj();
$cN = $this->n; // TCPDF/Protection: rem current "n"
$this->tpls[$tplidx]['n'] = $this->n;
$this->_out('<<' . $filter . '/Type /XObject');
$this->_out('/Subtype /Form');
$this->_out('/FormType 1');
$this->_out(sprintf('/BBox [%.2F %.2F %.2F %.2F]',
(isset($tpl['box']['llx']) ? $tpl['box']['llx'] : $tpl['x']) * $this->k,
(isset($tpl['box']['lly']) ? $tpl['box']['lly'] : -$tpl['y']) * $this->k,
(isset($tpl['box']['urx']) ? $tpl['box']['urx'] : $tpl['w'] + $tpl['x']) * $this->k,
(isset($tpl['box']['ury']) ? $tpl['box']['ury'] : $tpl['h'] - $tpl['y']) * $this->k
));
$c = 1;
$s = 0;
$tx = 0;
$ty = 0;
if (isset($tpl['box'])) {
$tx = -$tpl['box']['llx'];
$ty = -$tpl['box']['lly'];
if ($tpl['_rotationAngle'] <> 0) {
$angle = $tpl['_rotationAngle'] * M_PI/180;
$c=cos($angle);
$s=sin($angle);
switch($tpl['_rotationAngle']) {
case -90:
$tx = -$tpl['box']['lly'];
$ty = $tpl['box']['urx'];
break;
case -180:
$tx = $tpl['box']['urx'];
$ty = $tpl['box']['ury'];
break;
case -270:
$tx = $tpl['box']['ury'];
$ty = -$tpl['box']['llx'];
break;
}
}
} elseif ($tpl['x'] != 0 || $tpl['y'] != 0) {
$tx = -$tpl['x'] * 2;
$ty = $tpl['y'] * 2;
}
$tx *= $this->k;
$ty *= $this->k;
if ($c != 1 || $s != 0 || $tx != 0 || $ty != 0) {
$this->_out(sprintf('/Matrix [%.5F %.5F %.5F %.5F %.5F %.5F]',
$c, $s, -$s, $c, $tx, $ty
));
}
$this->_out('/Resources ');
if (isset($tpl['resources'])) {
$this->current_parser =& $tpl['parser'];
$this->pdf_write_value($tpl['resources']); // "n" will be changed
} else {
$this->_out('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
if (isset($this->_res['tpl'][$tplidx]['fonts']) && count($this->_res['tpl'][$tplidx]['fonts'])) {
$this->_out('/Font <<');
foreach($this->_res['tpl'][$tplidx]['fonts'] as $font)
$this->_out('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R');
$this->_out('>>');
}
if(isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images']) ||
isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls']))
{
$this->_out('/XObject <<');
if (isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images'])) {
foreach($this->_res['tpl'][$tplidx]['images'] as $image)
$this->_out('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R');
}
if (isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls'])) {
foreach($this->_res['tpl'][$tplidx]['tpls'] as $i => $tpl)
$this->_out($this->tplprefix . $i . ' ' . $tpl['n'] . ' 0 R');
}
$this->_out('>>');
}
$this->_out('>>');
}
$this->_out('/Group <</Type/Group/S/Transparency>>');
$nN = $this->n; // TCPDF: rem new "n"
$this->n = $cN; // TCPDF: reset to current "n"
if (is_subclass_of($this, 'TCPDF')) {
$p = $this->_getrawstream($p);
$this->_out('/Length ' . strlen($p) . ' >>');
$this->_out("stream\n" . $p . "\nendstream");
} else {
$this->_out('/Length ' . strlen($p) . ' >>');
$this->_putstream($p);
}
$this->_out('endobj');
$this->n = $nN; // TCPDF: reset to new "n"
}
$this->_putimportedobjects();
}
/**
* Rewritten to handle existing own defined objects
*/
function _newobj($obj_id = false, $onlynewobj = false) {
if (!$obj_id) {
$obj_id = ++$this->n;
}
//Begin a new object
if (!$onlynewobj) {
$this->offsets[$obj_id] = is_subclass_of($this, 'TCPDF') ? $this->bufferlen : strlen($this->buffer);
$this->_out($obj_id . ' 0 obj');
$this->_current_obj_id = $obj_id; // for later use with encryption
}
return $obj_id;
}
/**
* Writes a value
* Needed to rebuild the source document
*
* @param mixed $value A PDF-Value. Structure of values see cases in this method
*/
function pdf_write_value(&$value)
{
if (is_subclass_of($this, 'TCPDF')) {
parent::pdf_write_value($value);
}
switch ($value[0]) {
case PDF_TYPE_TOKEN:
$this->_straightOut($value[1] . ' ');
break;
case PDF_TYPE_NUMERIC:
case PDF_TYPE_REAL:
if (is_float($value[1]) && $value[1] != 0) {
$this->_straightOut(rtrim(rtrim(sprintf('%F', $value[1]), '0'), '.') . ' ');
} else {
$this->_straightOut($value[1] . ' ');
}
break;
case PDF_TYPE_ARRAY:
// An array. Output the proper
// structure and move on.
$this->_straightOut('[');
for ($i = 0; $i < count($value[1]); $i++) {
$this->pdf_write_value($value[1][$i]);
}
$this->_out(']');
break;
case PDF_TYPE_DICTIONARY:
// A dictionary.
$this->_straightOut('<<');
reset ($value[1]);
while (list($k, $v) = each($value[1])) {
$this->_straightOut($k . ' ');
$this->pdf_write_value($v);
}
$this->_straightOut('>>');
break;
case PDF_TYPE_OBJREF:
// An indirect object reference
// Fill the object stack if needed
$cpfn =& $this->current_parser->filename;
if (!isset($this->_don_obj_stack[$cpfn][$value[1]])) {
$this->_newobj(false, true);
$this->_obj_stack[$cpfn][$value[1]] = array($this->n, $value);
$this->_don_obj_stack[$cpfn][$value[1]] = array($this->n, $value); // Value is maybee obsolete!!!
}
$objid = $this->_don_obj_stack[$cpfn][$value[1]][0];
$this->_out($objid . ' 0 R');
break;
case PDF_TYPE_STRING:
// A string.
$this->_straightOut('(' . $value[1] . ')');
break;
case PDF_TYPE_STREAM:
// A stream. First, output the
// stream dictionary, then the
// stream data itself.
$this->pdf_write_value($value[1]);
$this->_out('stream');
$this->_out($value[2][1]);
$this->_out('endstream');
break;
case PDF_TYPE_HEX:
$this->_straightOut('<' . $value[1] . '>');
break;
case PDF_TYPE_BOOLEAN:
$this->_straightOut($value[1] ? 'true ' : 'false ');
break;
case PDF_TYPE_NULL:
// The null object.
$this->_straightOut('null ');
break;
}
}
/**
* Modified so not each call will add a newline to the output.
*/
function _straightOut($s) {
if (!is_subclass_of($this, 'TCPDF')) {
if($this->state==2)
$this->pages[$this->page] .= $s;
else
$this->buffer .= $s;
} else {
if ($this->state == 2) {
if ($this->inxobj) {
// we are inside an XObject template
$this->xobjects[$this->xobjid]['outdata'] .= $s;
} elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
// puts data before page footer
$pagebuff = $this->getPageBuffer($this->page);
$page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
$footer = substr($pagebuff, -$this->footerlen[$this->page]);
$this->setPageBuffer($this->page, $page.$s.$footer);
// update footer position
$this->footerpos[$this->page] += strlen($s);
} else {
// set page data
$this->setPageBuffer($this->page, $s, true);
}
} elseif ($this->state > 0) {
// set general data
$this->setBuffer($s);
}
}
}
/**
* rewritten to close opened parsers
*
*/
function _enddoc() {
parent::_enddoc();
$this->_closeParsers();
}
/**
* close all files opened by parsers
*/
function _closeParsers() {
if ($this->state > 2 && count($this->parsers) > 0) {
$this->cleanUp();
return true;
}
return false;
}
/**
* Removes cylced references and closes the file handles of the parser objects
*/
function cleanUp() {
foreach ($this->parsers as $k => $_){
$this->parsers[$k]->cleanUp();
$this->parsers[$k] = null;
unset($this->parsers[$k]);
}
}
}
@@ -0,0 +1,171 @@
<?php
//
// FPDI - Version 1.4.4
//
// Copyright 2004-2013 Setasign - Jan Slabon
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
global $CFG;
require_once($CFG->libdir.'/pdflib.php');
/**
* This class is used as a bridge between TCPDF and FPDI
* and will create the possibility to use both FPDF and TCPDF
* via one FPDI version.
*
* We'll simply remap TCPDF to FPDF again.
*
* It'll be loaded and extended by FPDF_TPL.
* Modified to extend the moodle TCPDF wrapper instead.
*/
class FPDF extends pdf {
function _putstream($s, $n=0) {
$this->_out($this->_getstream($s));
}
function _getxobjectdict() {
$out = parent::_getxobjectdict();
if (count($this->tpls)) {
foreach($this->tpls as $tplidx => $tpl) {
$out .= sprintf('%s%d %d 0 R', $this->tplprefix, $tplidx, $tpl['n']);
}
}
return $out;
}
/**
* Encryption of imported data by FPDI
*
* @param array $value
*/
function pdf_write_value(&$value) {
switch ($value[0]) {
case PDF_TYPE_STRING:
if ($this->encrypted) {
$value[1] = $this->_unescape($value[1]);
$value[1] = $this->_encrypt_data($this->_current_obj_id, $value[1]);
$value[1] = TCPDF_STATIC::_escape($value[1]);
}
break;
case PDF_TYPE_STREAM:
if ($this->encrypted) {
$value[2][1] = $this->_encrypt_data($this->_current_obj_id, $value[2][1]);
$value[1][1]['/Length'] = array(
PDF_TYPE_NUMERIC,
strlen($value[2][1])
);
}
break;
case PDF_TYPE_HEX:
if ($this->encrypted) {
$value[1] = $this->hex2str($value[1]);
$value[1] = $this->_encrypt_data($this->_current_obj_id, $value[1]);
// remake hexstring of encrypted string
$value[1] = $this->str2hex($value[1]);
}
break;
}
}
/**
* Unescapes a PDF string
*
* @param string $s
* @return string
*/
function _unescape($s) {
$out = '';
for ($count = 0, $n = strlen($s); $count < $n; $count++) {
if ($s[$count] != '\\' || $count == $n-1) {
$out .= $s[$count];
} else {
switch ($s[++$count]) {
case ')':
case '(':
case '\\':
$out .= $s[$count];
break;
case 'f':
$out .= chr(0x0C);
break;
case 'b':
$out .= chr(0x08);
break;
case 't':
$out .= chr(0x09);
break;
case 'r':
$out .= chr(0x0D);
break;
case 'n':
$out .= chr(0x0A);
break;
case "\r":
if ($count != $n-1 && $s[$count+1] == "\n")
$count++;
break;
case "\n":
break;
default:
// Octal-Values
if (ord($s[$count]) >= ord('0') &&
ord($s[$count]) <= ord('9')) {
$oct = ''. $s[$count];
if (ord($s[$count+1]) >= ord('0') &&
ord($s[$count+1]) <= ord('9')) {
$oct .= $s[++$count];
if (ord($s[$count+1]) >= ord('0') &&
ord($s[$count+1]) <= ord('9')) {
$oct .= $s[++$count];
}
}
$out .= chr(octdec($oct));
} else {
$out .= $s[$count];
}
}
}
}
return $out;
}
/**
* Hexadecimal to string
*
* @param string $hex
* @return string
*/
function hex2str($hex) {
return pack('H*', str_replace(array("\r", "\n", ' '), '', $hex));
}
/**
* String to hexadecimal
*
* @param string $str
* @return string
*/
function str2hex($str) {
return current(unpack('H*', $str));
}
}
@@ -0,0 +1,423 @@
<?php
//
// FPDI - Version 1.4.4
//
// Copyright 2004-2013 Setasign - Jan Slabon
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
require_once('pdf_parser.php');
class fpdi_pdf_parser extends pdf_parser {
/**
* Pages
* Index beginns at 0
*
* @var array
*/
var $pages;
/**
* Page count
* @var integer
*/
var $page_count;
/**
* actual page number
* @var integer
*/
var $pageno;
/**
* PDF Version of imported Document
* @var string
*/
var $pdfVersion;
/**
* FPDI Reference
* @var object
*/
var $fpdi;
/**
* Available BoxTypes
*
* @var array
*/
var $availableBoxes = array('/MediaBox', '/CropBox', '/BleedBox', '/TrimBox', '/ArtBox');
/**
* Constructor
*
* @param string $filename Source-Filename
* @param object $fpdi Object of type fpdi
*/
function fpdi_pdf_parser($filename, &$fpdi) {
$this->fpdi =& $fpdi;
parent::pdf_parser($filename);
// resolve Pages-Dictonary
$pages = $this->pdf_resolve_object($this->c, $this->root[1][1]['/Pages']);
// Read pages
$this->read_pages($this->c, $pages, $this->pages);
// count pages;
$this->page_count = count($this->pages);
}
/**
* Removes reference to fpdi object and closes the file handle
*/
function cleanUp() {
$this->fpdi = null;
$this->closeFile();
}
/**
* Overwrite parent::error()
*
* @param string $msg Error-Message
*/
function error($msg) {
$this->fpdi->error($msg);
}
/**
* Get pagecount from sourcefile
*
* @return int
*/
function getPageCount() {
return $this->page_count;
}
/**
* Set pageno
*
* @param int $pageno Pagenumber to use
*/
function setPageno($pageno) {
$pageno = ((int) $pageno) - 1;
if ($pageno < 0 || $pageno >= $this->getPageCount()) {
$this->fpdi->error('Pagenumber is wrong!');
}
$this->pageno = $pageno;
}
/**
* Get page-resources from current page
*
* @return array
*/
function getPageResources() {
return $this->_getPageResources($this->pages[$this->pageno]);
}
/**
* Get page-resources from /Page
*
* @param array $obj Array of pdf-data
*/
function _getPageResources ($obj) { // $obj = /Page
$obj = $this->pdf_resolve_object($this->c, $obj);
// If the current object has a resources
// dictionary associated with it, we use
// it. Otherwise, we move back to its
// parent object.
if (isset ($obj[1][1]['/Resources'])) {
$res = $this->pdf_resolve_object($this->c, $obj[1][1]['/Resources']);
if ($res[0] == PDF_TYPE_OBJECT)
return $res[1];
return $res;
} else {
if (!isset ($obj[1][1]['/Parent'])) {
return false;
} else {
$res = $this->_getPageResources($obj[1][1]['/Parent']);
if ($res[0] == PDF_TYPE_OBJECT)
return $res[1];
return $res;
}
}
}
/**
* Get content of current page
*
* If more /Contents is an array, the streams are concated
*
* @return string
*/
function getContent() {
$buffer = '';
if (isset($this->pages[$this->pageno][1][1]['/Contents'])) {
$contents = $this->_getPageContent($this->pages[$this->pageno][1][1]['/Contents']);
foreach($contents AS $tmp_content) {
$buffer .= $this->_rebuildContentStream($tmp_content) . ' ';
}
}
return $buffer;
}
/**
* Resolve all content-objects
*
* @param array $content_ref
* @return array
*/
function _getPageContent($content_ref) {
$contents = array();
if ($content_ref[0] == PDF_TYPE_OBJREF) {
$content = $this->pdf_resolve_object($this->c, $content_ref);
if ($content[1][0] == PDF_TYPE_ARRAY) {
$contents = $this->_getPageContent($content[1]);
} else {
$contents[] = $content;
}
} elseif ($content_ref[0] == PDF_TYPE_ARRAY) {
foreach ($content_ref[1] AS $tmp_content_ref) {
$contents = array_merge($contents,$this->_getPageContent($tmp_content_ref));
}
}
return $contents;
}
/**
* Rebuild content-streams
*
* @param array $obj
* @return string
*/
function _rebuildContentStream($obj) {
$filters = array();
if (isset($obj[1][1]['/Filter'])) {
$_filter = $obj[1][1]['/Filter'];
if ($_filter[0] == PDF_TYPE_OBJREF) {
$tmpFilter = $this->pdf_resolve_object($this->c, $_filter);
$_filter = $tmpFilter[1];
}
if ($_filter[0] == PDF_TYPE_TOKEN) {
$filters[] = $_filter;
} elseif ($_filter[0] == PDF_TYPE_ARRAY) {
$filters = $_filter[1];
}
}
$stream = $obj[2][1];
foreach ($filters AS $_filter) {
switch ($_filter[1]) {
case '/FlateDecode':
case '/Fl':
// $stream .= "\x0F\x0D"; // in an errorious stream this suffix could work
// $stream .= "\x0A";
// $stream .= "\x0D";
if (function_exists('gzuncompress')) {
$oStream = $stream;
$stream = (strlen($stream) > 0) ? @gzuncompress($stream) : '';
} else {
$this->error(sprintf('To handle %s filter, please compile php with zlib support.',$_filter[1]));
}
if ($stream === false) {
$oStream = substr($oStream, 2);
$stream = @gzinflate($oStream);
if ($stream == false) {
$this->error('Error while decompressing stream.');
}
}
break;
case '/LZWDecode':
include_once('filters/FilterLZW_FPDI.php');
$decoder = new FilterLZW_FPDI($this->fpdi);
$stream = $decoder->decode($stream);
break;
case '/ASCII85Decode':
include_once('filters/FilterASCII85_FPDI.php');
$decoder = new FilterASCII85_FPDI($this->fpdi);
$stream = $decoder->decode($stream);
break;
case null:
$stream = $stream;
break;
default:
$this->error(sprintf('Unsupported Filter: %s',$_filter[1]));
}
}
return $stream;
}
/**
* Get a Box from a page
* Arrayformat is same as used by fpdf_tpl
*
* @param array $page a /Page
* @param string $box_index Type of Box @see $availableBoxes
* @param float Scale factor from user space units to points
* @return array
*/
function getPageBox($page, $box_index, $k) {
$page = $this->pdf_resolve_object($this->c, $page);
$box = null;
if (isset($page[1][1][$box_index]))
$box =& $page[1][1][$box_index];
if (!is_null($box) && $box[0] == PDF_TYPE_OBJREF) {
$tmp_box = $this->pdf_resolve_object($this->c, $box);
$box = $tmp_box[1];
}
if (!is_null($box) && $box[0] == PDF_TYPE_ARRAY) {
$b =& $box[1];
return array('x' => $b[0][1] / $k,
'y' => $b[1][1] / $k,
'w' => abs($b[0][1] - $b[2][1]) / $k,
'h' => abs($b[1][1] - $b[3][1]) / $k,
'llx' => min($b[0][1], $b[2][1]) / $k,
'lly' => min($b[1][1], $b[3][1]) / $k,
'urx' => max($b[0][1], $b[2][1]) / $k,
'ury' => max($b[1][1], $b[3][1]) / $k,
);
} elseif (!isset ($page[1][1]['/Parent'])) {
return false;
} else {
return $this->getPageBox($this->pdf_resolve_object($this->c, $page[1][1]['/Parent']), $box_index, $k);
}
}
/**
* Get all page boxes by page no
*
* @param int The page number
* @param float Scale factor from user space units to points
* @return array
*/
function getPageBoxes($pageno, $k) {
return $this->_getPageBoxes($this->pages[$pageno - 1], $k);
}
/**
* Get all boxes from /Page
*
* @param array a /Page
* @return array
*/
function _getPageBoxes($page, $k) {
$boxes = array();
foreach($this->availableBoxes AS $box) {
if ($_box = $this->getPageBox($page, $box, $k)) {
$boxes[$box] = $_box;
}
}
return $boxes;
}
/**
* Get the page rotation by pageno
*
* @param integer $pageno
* @return array
*/
function getPageRotation($pageno) {
return $this->_getPageRotation($this->pages[$pageno - 1]);
}
function _getPageRotation($obj) { // $obj = /Page
$obj = $this->pdf_resolve_object($this->c, $obj);
if (isset ($obj[1][1]['/Rotate'])) {
$res = $this->pdf_resolve_object($this->c, $obj[1][1]['/Rotate']);
if ($res[0] == PDF_TYPE_OBJECT)
return $res[1];
return $res;
} else {
if (!isset ($obj[1][1]['/Parent'])) {
return false;
} else {
$res = $this->_getPageRotation($obj[1][1]['/Parent']);
if ($res[0] == PDF_TYPE_OBJECT)
return $res[1];
return $res;
}
}
}
/**
* Read all /Page(es)
*
* @param object pdf_context
* @param array /Pages
* @param array the result-array
*/
function read_pages(&$c, &$pages, &$result) {
// Get the kids dictionary
$_kids = $this->pdf_resolve_object ($c, $pages[1][1]['/Kids']);
if (!is_array($_kids))
$this->error('Cannot find /Kids in current /Page-Dictionary');
if ($_kids[1][0] == PDF_TYPE_ARRAY) {
$kids = $_kids[1][1];
} else {
$kids = $_kids[1];
}
foreach ($kids as $v) {
$pg = $this->pdf_resolve_object ($c, $v);
if ($pg[1][1]['/Type'][1] === '/Pages') {
// If one of the kids is an embedded
// /Pages array, resolve it as well.
if ($pg !== $pages) {
$this->read_pages($c, $pg, $result);
}
} else {
$result[] = $pg;
}
}
}
/**
* Get PDF-Version
*
* And reset the PDF Version used in FPDI if needed
*/
function getPDFVersion() {
parent::getPDFVersion();
$this->fpdi->setPDFVersion(max($this->fpdi->getPDFVersion(), $this->pdfVersion));
}
}
@@ -0,0 +1,104 @@
<?php
//
// FPDI - Version 1.4.4
//
// Copyright 2004-2013 Setasign - Jan Slabon
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
if (!class_exists('pdf_context', false)) {
class pdf_context {
/**
* Modi
*
* @var integer 0 = file | 1 = string
*/
var $_mode = 0;
var $file;
var $buffer;
var $offset;
var $length;
var $stack;
// Constructor
function pdf_context(&$f) {
$this->file =& $f;
if (is_string($this->file))
$this->_mode = 1;
$this->reset();
}
// Optionally move the file
// pointer to a new location
// and reset the buffered data
function reset($pos = null, $l = 100) {
if ($this->_mode == 0) {
if (!is_null ($pos)) {
fseek ($this->file, $pos);
}
$this->buffer = $l > 0 ? fread($this->file, $l) : '';
$this->length = strlen($this->buffer);
if ($this->length < $l)
$this->increase_length($l - $this->length);
} else {
$this->buffer = $this->file;
$this->length = strlen($this->buffer);
}
$this->offset = 0;
$this->stack = array();
}
// Make sure that there is at least one
// character beyond the current offset in
// the buffer to prevent the tokenizer
// from attempting to access data that does
// not exist
function ensure_content() {
if ($this->offset >= $this->length - 1) {
return $this->increase_length();
} else {
return true;
}
}
// Forcefully read more data into the buffer
function increase_length($l = 100) {
if ($this->_mode == 0 && feof($this->file)) {
return false;
} elseif ($this->_mode == 0) {
$totalLength = $this->length + $l;
do {
$toRead = $totalLength - $this->length;
if ($toRead < 1)
break;
$this->buffer .= fread($this->file, $toRead);
} while ((($this->length = strlen($this->buffer)) != $totalLength) && !feof($this->file));
return true;
} else {
return false;
}
}
}
}
@@ -0,0 +1,719 @@
<?php
//
// FPDI - Version 1.4.4
//
// Copyright 2004-2013 Setasign - Jan Slabon
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
if (!defined ('PDF_TYPE_NULL'))
define ('PDF_TYPE_NULL', 0);
if (!defined ('PDF_TYPE_NUMERIC'))
define ('PDF_TYPE_NUMERIC', 1);
if (!defined ('PDF_TYPE_TOKEN'))
define ('PDF_TYPE_TOKEN', 2);
if (!defined ('PDF_TYPE_HEX'))
define ('PDF_TYPE_HEX', 3);
if (!defined ('PDF_TYPE_STRING'))
define ('PDF_TYPE_STRING', 4);
if (!defined ('PDF_TYPE_DICTIONARY'))
define ('PDF_TYPE_DICTIONARY', 5);
if (!defined ('PDF_TYPE_ARRAY'))
define ('PDF_TYPE_ARRAY', 6);
if (!defined ('PDF_TYPE_OBJDEC'))
define ('PDF_TYPE_OBJDEC', 7);
if (!defined ('PDF_TYPE_OBJREF'))
define ('PDF_TYPE_OBJREF', 8);
if (!defined ('PDF_TYPE_OBJECT'))
define ('PDF_TYPE_OBJECT', 9);
if (!defined ('PDF_TYPE_STREAM'))
define ('PDF_TYPE_STREAM', 10);
if (!defined ('PDF_TYPE_BOOLEAN'))
define ('PDF_TYPE_BOOLEAN', 11);
if (!defined ('PDF_TYPE_REAL'))
define ('PDF_TYPE_REAL', 12);
require_once('pdf_context.php');
if (!class_exists('pdf_parser', false)) {
class pdf_parser {
/**
* Filename
* @var string
*/
var $filename;
/**
* File resource
* @var resource
*/
var $f;
/**
* PDF Context
* @var object pdf_context-Instance
*/
var $c;
/**
* xref-Data
* @var array
*/
var $xref;
/**
* root-Object
* @var array
*/
var $root;
/**
* PDF version of the loaded document
* @var string
*/
var $pdfVersion;
/**
* For reading encrypted documents and xref/objectstreams are in use
*
* @var boolean
*/
var $readPlain = true;
/**
* Constructor
*
* @param string $filename Source-Filename
*/
function pdf_parser($filename) {
$this->filename = $filename;
$this->f = @fopen($this->filename, 'rb');
if (!$this->f)
$this->error(sprintf('Cannot open %s !', $filename));
$this->getPDFVersion();
$this->c = new pdf_context($this->f);
// Read xref-Data
$this->xref = array();
$this->pdf_read_xref($this->xref, $this->pdf_find_xref());
// Check for Encryption
$this->getEncryption();
// Read root
$this->pdf_read_root();
}
/**
* Close the opened file
*/
function closeFile() {
if (isset($this->f) && is_resource($this->f)) {
fclose($this->f);
unset($this->f);
}
}
/**
* Print Error and die
*
* @param string $msg Error-Message
*/
function error($msg) {
die('<b>PDF-Parser Error:</b> ' . $msg);
}
/**
* Check Trailer for Encryption
*/
function getEncryption() {
if (isset($this->xref['trailer'][1]['/Encrypt'])) {
$this->error('File is encrypted!');
}
}
/**
* Find/Return /Root
*
* @return array
*/
function pdf_find_root() {
if ($this->xref['trailer'][1]['/Root'][0] != PDF_TYPE_OBJREF) {
$this->error('Wrong Type of Root-Element! Must be an indirect reference');
}
return $this->xref['trailer'][1]['/Root'];
}
/**
* Read the /Root
*/
function pdf_read_root() {
// read root
$this->root = $this->pdf_resolve_object($this->c, $this->pdf_find_root());
}
/**
* Get PDF-Version
*
* And reset the PDF Version used in FPDI if needed
*/
function getPDFVersion() {
fseek($this->f, 0);
preg_match('/\d\.\d/',fread($this->f, 16), $m);
if (isset($m[0]))
$this->pdfVersion = $m[0];
return $this->pdfVersion;
}
/**
* Find the xref-Table
*/
function pdf_find_xref() {
$toRead = 1500;
$stat = fseek ($this->f, -$toRead, SEEK_END);
if ($stat === -1) {
fseek ($this->f, 0);
}
$data = fread($this->f, $toRead);
$pos = strlen($data) - strpos(strrev($data), strrev('startxref'));
$data = substr($data, $pos);
if (!preg_match('/\s*(\d+).*$/s', $data, $matches)) {
$this->error('Unable to find pointer to xref table');
}
return (int) $matches[1];
}
/**
* Read xref-table
*
* @param array $result Array of xref-table
* @param integer $offset of xref-table
*/
function pdf_read_xref(&$result, $offset) {
$o_pos = $offset-min(20, $offset);
fseek($this->f, $o_pos); // set some bytes backwards to fetch errorious docs
$data = fread($this->f, 100);
$xrefPos = strrpos($data, 'xref');
if ($xrefPos === false) {
fseek($this->f, $offset);
$c = new pdf_context($this->f);
$xrefStreamObjDec = $this->pdf_read_value($c);
if (is_array($xrefStreamObjDec) && isset($xrefStreamObjDec[0]) && $xrefStreamObjDec[0] == PDF_TYPE_OBJDEC) {
$this->error(sprintf('This document (%s) probably uses a compression technique which is not supported by the free parser shipped with FPDI.', $this->filename));
} else {
$this->error('Unable to find xref table.');
}
}
if (!isset($result['xref_location'])) {
$result['xref_location'] = $o_pos + $xrefPos;
$result['max_object'] = 0;
}
$cylces = -1;
$bytesPerCycle = 100;
fseek($this->f, $o_pos = $o_pos + $xrefPos + 4); // set the handle directly after the "xref"-keyword
$data = fread($this->f, $bytesPerCycle);
while (($trailerPos = strpos($data, 'trailer', max($bytesPerCycle * $cylces++, 0))) === false && !feof($this->f)) {
$data .= fread($this->f, $bytesPerCycle);
}
if ($trailerPos === false) {
$this->error('Trailer keyword not found after xref table');
}
$data = substr($data, 0, $trailerPos);
// get Line-Ending
preg_match_all("/(\r\n|\n|\r)/", substr($data, 0, 100), $m); // check the first 100 bytes for linebreaks
$differentLineEndings = count(array_unique($m[0]));
if ($differentLineEndings > 1) {
$lines = preg_split("/(\r\n|\n|\r)/", $data, -1, PREG_SPLIT_NO_EMPTY);
} else {
$lines = explode($m[0][1], $data);
}
$data = $differentLineEndings = $m = null;
unset($data, $differentLineEndings, $m);
$linesCount = count($lines);
$start = 1;
for ($i = 0; $i < $linesCount; $i++) {
$line = trim($lines[$i]);
if ($line) {
$pieces = explode(' ', $line);
$c = count($pieces);
switch($c) {
case 2:
$start = (int)$pieces[0];
$end = $start + (int)$pieces[1];
if ($end > $result['max_object'])
$result['max_object'] = $end;
break;
case 3:
if (!isset($result['xref'][$start]))
$result['xref'][$start] = array();
if (!array_key_exists($gen = (int) $pieces[1], $result['xref'][$start])) {
$result['xref'][$start][$gen] = $pieces[2] == 'n' ? (int) $pieces[0] : null;
}
$start++;
break;
default:
$this->error('Unexpected data in xref table');
}
}
}
$lines = $pieces = $line = $start = $end = $gen = null;
unset($lines, $pieces, $line, $start, $end, $gen);
fseek($this->f, $o_pos + $trailerPos + 7);
$c = new pdf_context($this->f);
$trailer = $this->pdf_read_value($c);
$c = null;
unset($c);
if (!isset($result['trailer'])) {
$result['trailer'] = $trailer;
}
if (isset($trailer[1]['/Prev'])) {
$this->pdf_read_xref($result, $trailer[1]['/Prev'][1]);
}
$trailer = null;
unset($trailer);
return true;
}
/**
* Reads an Value
*
* @param object $c pdf_context
* @param string $token a Token
* @return mixed
*/
function pdf_read_value(&$c, $token = null) {
if (is_null($token)) {
$token = $this->pdf_read_token($c);
}
if ($token === false) {
return false;
}
switch ($token) {
case '<':
// This is a hex string.
// Read the value, then the terminator
$pos = $c->offset;
while(1) {
$match = strpos ($c->buffer, '>', $pos);
// If you can't find it, try
// reading more data from the stream
if ($match === false) {
if (!$c->increase_length()) {
return false;
} else {
continue;
}
}
$result = substr ($c->buffer, $c->offset, $match - $c->offset);
$c->offset = $match + 1;
return array (PDF_TYPE_HEX, $result);
}
break;
case '<<':
// This is a dictionary.
$result = array();
// Recurse into this function until we reach
// the end of the dictionary.
while (($key = $this->pdf_read_token($c)) !== '>>') {
if ($key === false) {
return false;
}
if (($value = $this->pdf_read_value($c)) === false) {
return false;
}
// Catch missing value
if ($value[0] == PDF_TYPE_TOKEN && $value[1] == '>>') {
$result[$key] = array(PDF_TYPE_NULL);
break;
}
$result[$key] = $value;
}
return array (PDF_TYPE_DICTIONARY, $result);
case '[':
// This is an array.
$result = array();
// Recurse into this function until we reach
// the end of the array.
while (($token = $this->pdf_read_token($c)) !== ']') {
if ($token === false) {
return false;
}
if (($value = $this->pdf_read_value($c, $token)) === false) {
return false;
}
$result[] = $value;
}
return array (PDF_TYPE_ARRAY, $result);
case '(' :
// This is a string
$pos = $c->offset;
$openBrackets = 1;
do {
for (; $openBrackets != 0 && $pos < $c->length; $pos++) {
switch (ord($c->buffer[$pos])) {
case 0x28: // '('
$openBrackets++;
break;
case 0x29: // ')'
$openBrackets--;
break;
case 0x5C: // backslash
$pos++;
}
}
} while($openBrackets != 0 && $c->increase_length());
$result = substr($c->buffer, $c->offset, $pos - $c->offset - 1);
$c->offset = $pos;
return array (PDF_TYPE_STRING, $result);
case 'stream':
$o_pos = ftell($c->file)-strlen($c->buffer);
$o_offset = $c->offset;
$c->reset($startpos = $o_pos + $o_offset);
$e = 0; // ensure line breaks in front of the stream
if ($c->buffer[0] == chr(10) || $c->buffer[0] == chr(13))
$e++;
if ($c->buffer[1] == chr(10) && $c->buffer[0] != chr(10))
$e++;
if ($this->actual_obj[1][1]['/Length'][0] == PDF_TYPE_OBJREF) {
$tmp_c = new pdf_context($this->f);
$tmp_length = $this->pdf_resolve_object($tmp_c, $this->actual_obj[1][1]['/Length']);
$length = $tmp_length[1][1];
} else {
$length = $this->actual_obj[1][1]['/Length'][1];
}
if ($length > 0) {
$c->reset($startpos + $e,$length);
$v = $c->buffer;
} else {
$v = '';
}
$c->reset($startpos + $e + $length + 9); // 9 = strlen("endstream")
return array(PDF_TYPE_STREAM, $v);
default :
if (is_numeric ($token)) {
// A numeric token. Make sure that
// it is not part of something else.
if (($tok2 = $this->pdf_read_token ($c)) !== false) {
if (is_numeric ($tok2)) {
// Two numeric tokens in a row.
// In this case, we're probably in
// front of either an object reference
// or an object specification.
// Determine the case and return the data
if (($tok3 = $this->pdf_read_token ($c)) !== false) {
switch ($tok3) {
case 'obj':
return array (PDF_TYPE_OBJDEC, (int) $token, (int) $tok2);
case 'R':
return array (PDF_TYPE_OBJREF, (int) $token, (int) $tok2);
}
// If we get to this point, that numeric value up
// there was just a numeric value. Push the extra
// tokens back into the stack and return the value.
array_push ($c->stack, $tok3);
}
}
array_push ($c->stack, $tok2);
}
if ($token === (string)((int)$token))
return array (PDF_TYPE_NUMERIC, (int)$token);
else
return array (PDF_TYPE_REAL, (float)$token);
} elseif ($token == 'true' || $token == 'false') {
return array (PDF_TYPE_BOOLEAN, $token == 'true');
} elseif ($token == 'null') {
return array (PDF_TYPE_NULL);
} else {
// Just a token. Return it.
return array (PDF_TYPE_TOKEN, $token);
}
}
}
/**
* Resolve an object
*
* @param object $c pdf_context
* @param array $obj_spec The object-data
* @param boolean $encapsulate Must set to true, cause the parsing and fpdi use this method only without this para
*/
function pdf_resolve_object(&$c, $obj_spec, $encapsulate = true) {
// Exit if we get invalid data
if (!is_array($obj_spec)) {
$ret = false;
return $ret;
}
if ($obj_spec[0] == PDF_TYPE_OBJREF) {
// This is a reference, resolve it
if (isset($this->xref['xref'][$obj_spec[1]][$obj_spec[2]])) {
// Save current file position
// This is needed if you want to resolve
// references while you're reading another object
// (e.g.: if you need to determine the length
// of a stream)
$old_pos = ftell($c->file);
// Reposition the file pointer and
// load the object header.
$c->reset($this->xref['xref'][$obj_spec[1]][$obj_spec[2]]);
$header = $this->pdf_read_value($c);
if ($header[0] != PDF_TYPE_OBJDEC || $header[1] != $obj_spec[1] || $header[2] != $obj_spec[2]) {
$toSearchFor = $obj_spec[1] . ' ' . $obj_spec[2] . ' obj';
if (preg_match('/' . $toSearchFor . '/', $c->buffer)) {
$c->offset = strpos($c->buffer, $toSearchFor) + strlen($toSearchFor);
// reset stack
$c->stack = array();
} else {
$this->error("Unable to find object ({$obj_spec[1]}, {$obj_spec[2]}) at expected location");
}
}
// If we're being asked to store all the information
// about the object, we add the object ID and generation
// number for later use
$result = array();
$this->actual_obj =& $result;
if ($encapsulate) {
$result = array (
PDF_TYPE_OBJECT,
'obj' => $obj_spec[1],
'gen' => $obj_spec[2]
);
}
// Now simply read the object data until
// we encounter an end-of-object marker
while(1) {
$value = $this->pdf_read_value($c);
if ($value === false || count($result) > 4) {
// in this case the parser coudn't find an endobj so we break here
break;
}
if ($value[0] == PDF_TYPE_TOKEN && $value[1] === 'endobj') {
break;
}
$result[] = $value;
}
$c->reset($old_pos);
if (isset($result[2][0]) && $result[2][0] == PDF_TYPE_STREAM) {
$result[0] = PDF_TYPE_STREAM;
}
return $result;
}
} else {
return $obj_spec;
}
}
/**
* Reads a token from the file
*
* @param object $c pdf_context
* @return mixed
*/
function pdf_read_token(&$c)
{
// If there is a token available
// on the stack, pop it out and
// return it.
if (count($c->stack)) {
return array_pop($c->stack);
}
// Strip away any whitespace
do {
if (!$c->ensure_content()) {
return false;
}
$c->offset += strspn($c->buffer, "\x20\x0A\x0C\x0D\x09\x00", $c->offset);
} while ($c->offset >= $c->length - 1);
// Get the first character in the stream
$char = $c->buffer[$c->offset++];
switch ($char) {
case '[':
case ']':
case '(':
case ')':
// This is either an array or literal string
// delimiter, Return it
return $char;
case '<':
case '>':
// This could either be a hex string or
// dictionary delimiter. Determine the
// appropriate case and return the token
if ($c->buffer[$c->offset] == $char) {
if (!$c->ensure_content()) {
return false;
}
$c->offset++;
return $char . $char;
} else {
return $char;
}
case '%':
// This is a comment - jump over it!
$pos = $c->offset;
while(1) {
$match = preg_match("/(\r\n|\r|\n)/", $c->buffer, $m, PREG_OFFSET_CAPTURE, $pos);
if ($match === 0) {
if (!$c->increase_length()) {
return false;
} else {
continue;
}
}
$c->offset = $m[0][1]+strlen($m[0][0]);
return $this->pdf_read_token($c);
}
default:
// This is "another" type of token (probably
// a dictionary entry or a numeric value)
// Find the end and return it.
if (!$c->ensure_content()) {
return false;
}
while(1) {
// Determine the length of the token
$pos = strcspn($c->buffer, "\x20%[]<>()/\x0A\x0C\x0D\x09\x00", $c->offset);
if ($c->offset + $pos <= $c->length - 1) {
break;
} else {
// If the script reaches this point,
// the token may span beyond the end
// of the current buffer. Therefore,
// we increase the size of the buffer
// and try again--just to be safe.
$c->increase_length();
}
}
$result = substr($c->buffer, $c->offset - 1, $pos + 1);
$c->offset += $pos;
return $result;
}
}
}
}
@@ -0,0 +1,17 @@
FPDI
==================================
No changes from the upstream version have been made. Both FPDI and FPDF_TPL have
been downloaded and unzipped to this directory.
Information
-----------
URL: http://www.setasign.de/products/pdf-php-solutions/fpdi/
Download from: http://www.setasign.de/products/pdf-php-solutions/fpdi/downloads
Documentation: http://www.setasign.de/products/pdf-php-solutions/fpdi/manuals/
License: Apache Software License 2.0
Downloaded versions:
FPDI: 1.4.4
FPDF_TPL: 1.2.3
@@ -0,0 +1,85 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for component 'assignfeedback_editpdf', language 'en'
*
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['addtoquicklist'] = 'Add to quicklist';
$string['annotationcolour'] = 'Annotation color';
$string['black'] = 'Black';
$string['blue'] = 'Blue';
$string['cannotopenpdf'] = 'Cannot open the pdf file. The file may be corrupt, or in an unsupported format.';
$string['clear'] = 'Clear';
$string['colourpicker'] = 'Colour Picker';
$string['commentcolour'] = 'Comment color';
$string['comment'] = 'Comments';
$string['commentcontextmenu'] = 'Comment context menu';
$string['couldnotsavepage'] = 'Could not save page {$a}';
$string['currentstamp'] = 'Stamp';
$string['deleteannotation'] = 'Delete annotation';
$string['deletecomment'] = 'Delete comment';
$string['deletefeedback'] = 'Delete feedback PDF';
$string['downloadablefilename'] = 'feedback.pdf';
$string['downloadfeedback'] = 'Download feedback PDF';
$string['editpdf'] = 'Annotate PDF';
$string['editpdf_help'] = 'Annotate students submissions directly in the browser and produce an edited downloadable PDF.';
$string['enabled'] = 'Annotate PDF';
$string['enabled_help'] = 'If enabled, the teacher will be able to create annotated pdf files when marking the assignments. This allows the teacher to add comments, drawing and stamps directly on top of the students work. The annotating is done in the browser and no extra software is required.';
$string['filter'] = 'Filter comments...';
$string['generatefeedback'] = 'Generate feedback PDF';
$string['gotopage'] = 'Go to page';
$string['green'] = 'Green';
$string['gspath'] = 'Ghostscript path';
$string['gspath_help'] = 'On most Linux installs, this can be left as \'/usr/bin/gs\'. On Windows it will be something like \'c:\\gs\\bin\\gswin32c.exe\' (make sure there are no spaces in the path - if necessary copy the files \'gswin32c.exe\' and \'gsdll32.dll\' to a new folder without a space in the path)';
$string['highlight'] = 'Highlight';
$string['jsrequired'] = 'Annotating PDF documents requires javascript. Please enable javascript in your browser if you want to use this feature.';
$string['launcheditor'] = 'Launch PDF editor...';
$string['line'] = 'Line';
$string['loadingeditor'] = 'Loading PDF editor';
$string['navigatenext'] = 'Next page';
$string['navigateprevious'] = 'Previous page';
$string['oval'] = 'Oval';
$string['pagexofy'] = 'Page {$a->page} of {$a->total}';
$string['pen'] = 'Pen';
$string['pluginname'] = 'Annotate PDF';
$string['rectangle'] = 'Rectangle';
$string['red'] = 'Red';
$string['searchcomments'] = 'Search comments';
$string['select'] = 'Select';
$string['stamppicker'] = 'Stamp picker';
$string['stampsdesc'] = 'Stamps must be image files. These images can be used with the stamp tool to annotate the PDF.';
$string['stamps'] = 'Stamps';
$string['stamp'] = 'Stamp';
$string['test_doesnotexist'] = 'The ghostscript path points to a non-existent file';
$string['test_empty'] = 'The ghostscript path is empty - please enter the correct path';
$string['testgs'] = 'Test ghostscript path';
$string['test_isdir'] = 'The ghostscript path points to a folder, please include the ghostscript program in the path you specify';
$string['test_notestfile'] = 'The test PDF is missing';
$string['test_notexecutable'] = 'The ghostscript points to a file that is not executable';
$string['test_ok'] = 'The ghostscript path appears to be OK - please check you can see the message in the image below';
$string['toolbarbutton'] = '{$a->tool} {$a->shortcut}';
$string['tool'] = 'Tool';
$string['unsavedchanges'] = 'Unsaved changes';
$string['viewfeedbackonline'] = 'View annotated pdf...';
$string['white'] = 'White';
$string['yellow'] = 'Yellow';
+78
View File
@@ -0,0 +1,78 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the version information for the comments feedback plugin
*
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Serves assignment feedback and other files.
*
* @param mixed $course course or id of the course
* @param mixed $cm course module or id of the course module
* @param context $context
* @param string $filearea
* @param array $args
* @param bool $forcedownload
* @return bool false if file not found, does not return if found - just send the file
*/
function assignfeedback_editpdf_pluginfile($course,
$cm,
context $context,
$filearea,
$args,
$forcedownload) {
global $USER, $DB, $CFG;
if ($context->contextlevel == CONTEXT_MODULE) {
require_login($course, false, $cm);
$itemid = (int)array_shift($args);
if (!$assign = $DB->get_record('assign', array('id'=>$cm->instance))) {
return false;
}
$record = $DB->get_record('assign_grades', array('id'=>$itemid), 'userid,assignment', MUST_EXIST);
$userid = $record->userid;
if ($assign->id != $record->assignment) {
return false;
}
// Check is users feedback or has grading permission.
if ($USER->id != $userid and !has_capability('mod/assign:grade', $context)) {
return false;
}
$relativepath = implode('/', $args);
$fullpath = "/{$context->id}/assignfeedback_editpdf/$filearea/$itemid/$relativepath";
$fs = get_file_storage();
if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
return false;
}
// Download MUST be forced - security!
send_stored_file($file, 0, 0, true);// Check if we want to retrieve the stamps.
}
}
+253
View File
@@ -0,0 +1,253 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the definition for the library class for PDF feedback plugin
*
*
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
use \assignfeedback_editpdf\document_services;
use \assignfeedback_editpdf\page_editor;
/**
* library class for editpdf feedback plugin extending feedback plugin base class
*
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class assign_feedback_editpdf extends assign_feedback_plugin {
/**
* Get the name of the file feedback plugin
* @return string
*/
public function get_name() {
return get_string('pluginname', 'assignfeedback_editpdf');
}
/**
* Create a widget for rendering the editor.
*
* @param int $userid
* @param stdClass $grade
* @param bool $readonly
* @return assignfeedback_editpdf_widget
*/
public function get_widget($userid, $grade, $readonly) {
$attempt = -1;
if ($grade) {
$attempt = $grade->attemptnumber;
} else {
$grade = $this->assignment->get_user_grade($userid, true);
}
$feedbackfile = document_services::get_feedback_document($this->assignment->get_instance()->id,
$userid,
$attempt);
$stampfiles = array();
$fs = get_file_storage();
$syscontext = context_system::instance();
// Copy any new stamps to this instance.
if ($files = $fs->get_area_files($syscontext->id,
'assignfeedback_editpdf',
'stamps',
0,
"filename",
false)) {
foreach ($files as $file) {
$filename = $file->get_filename();
if ($filename !== '.') {
$existingfile = $fs->get_file($this->assignment->get_context()->id,
'assignfeedback_editpdf',
'stamps',
$grade->id,
'/',
$file->get_filename());
if (!$existingfile) {
$newrecord = new stdClass();
$newrecord->contextid = $this->assignment->get_context()->id;
$newrecord->itemid = $grade->id;
$fs->create_file_from_storedfile($newrecord, $file);
}
}
}
}
// Now get the full list of stamp files for this instance.
if ($files = $fs->get_area_files($this->assignment->get_context()->id,
'assignfeedback_editpdf',
'stamps',
$grade->id,
"filename",
false)) {
foreach ($files as $file) {
$filename = $file->get_filename();
if ($filename !== '.') {
$url = moodle_url::make_pluginfile_url($this->assignment->get_context()->id,
'assignfeedback_editpdf',
'stamps',
$grade->id,
'/',
$file->get_filename(),
false);
array_push($stampfiles, $url->out());
}
}
}
$url = false;
$filename = '';
if ($feedbackfile) {
$url = moodle_url::make_pluginfile_url($this->assignment->get_context()->id,
'assignfeedback_editpdf',
document_services::FINAL_PDF_FILEAREA,
$grade->id,
'/',
$feedbackfile->get_filename(),
false);
$filename = $feedbackfile->get_filename();
}
$widget = new assignfeedback_editpdf_widget($this->assignment->get_instance()->id,
$userid,
$attempt,
$url,
$filename,
$stampfiles,
$readonly);
return $widget;
}
/**
* Get form elements for grading form
*
* @param stdClass $grade
* @param MoodleQuickForm $mform
* @param stdClass $data
* @param int $userid
* @return bool true if elements were added to the form
*/
public function get_form_elements_for_user($grade, MoodleQuickForm $mform, stdClass $data, $userid) {
global $PAGE;
$attempt = -1;
if ($grade) {
$attempt = $grade->attemptnumber;
}
$files = document_services::list_compatible_submission_files_for_attempt($this->assignment, $userid, $attempt);
// Only show the editor if there was a compatible file submitted.
if (count($files)) {
$renderer = $PAGE->get_renderer('assignfeedback_editpdf');
$widget = $this->get_widget($userid, $grade, false);
$html = $renderer->render($widget);
$mform->addElement('static', 'editpdf', get_string('editpdf', 'assignfeedback_editpdf'), $html);
$mform->addHelpButton('editpdf', 'editpdf', 'assignfeedback_editpdf');
}
}
/**
* Generate the pdf.
*
* @param stdClass $grade
* @param stdClass $data
* @return bool
*/
public function save(stdClass $grade, stdClass $data) {
if (page_editor::has_annotations_or_comments($grade->id, true)) {
document_services::generate_feedback_document($this->assignment, $grade->userid, $grade->attemptnumber);
}
return true;
}
/**
* Display the list of files in the feedback status table.
*
* @param stdClass $grade
* @param bool $showviewlink (Always set to false).
* @return string
*/
public function view_summary(stdClass $grade, & $showviewlink) {
$showviewlink = false;
return $this->view($grade);
}
/**
* Display the list of files in the feedback status table.
*
* @param stdClass $grade
* @return string
*/
public function view(stdClass $grade) {
global $PAGE;
$html = '';
// Show a link to download the pdf.
if (page_editor::has_annotations_or_comments($grade->id, false)) {
$html = $this->assignment->render_area_files('assignfeedback_editpdf',
document_services::FINAL_PDF_FILEAREA,
$grade->id);
// Also show the link to the read-only interface.
$renderer = $PAGE->get_renderer('assignfeedback_editpdf');
$widget = $this->get_widget($grade->userid, $grade, true);
$html .= $renderer->render($widget);
}
return $html;
}
/**
* Return true if there are no released comments/annotations.
*
* @param stdClass $grade
*/
public function is_empty(stdClass $grade) {
global $DB;
$comments = $DB->count_records('assignfeedback_editpdf_cmnt', array('gradeid'=>$grade->id, 'draft'=>0));
$annotations = $DB->count_records('assignfeedback_editpdf_annot', array('gradeid'=>$grade->id, 'draft'=>0));
return $comments == 0 && $annotations == 0;
}
/**
* The assignment has been deleted - remove the plugin specific data
*
* @return bool
*/
public function delete_instance() {
global $DB;
$grades = $DB->get_records('assign_grades', array('assignment'=>$this->assignment->id), '', 'id');
list($gradeids, $params) = $DB->get_in_or_equal(array_keys($grades), SQL_PARAMS_NAMED);
$DB->delete_records_select('assignfeedback_editpdf_annot', $gradeids, $params);
$DB->delete_records_select('assignfeedback_editpdf_cmnt', $gradeids, $params);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#7D9FD3;" d="M16,15c0,0.5-0.5,1-1,1H1c-0.5,0-1-0.5-1-1V1c0-0.5,0.5-1,1-1h14c0.5,0,1,0.5,1,1V15z"/>
</svg>

After

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#999999;" d="M15,0H1C0.5,0,0,0.5,0,1v14c0,0.5,0.5,1,1,1h14c0.5,0,1-0.5,1-1V1C16,0.5,15.5,0,15,0z M15,8
c-1.4,0-3.5,0-7,0c0,4,0,6,0,7H2c-0.5,0-1-0.5-1-1V8c2.8,0,5.1,0,7,0c0-1.9,0-4.2,0-7h6c0.5,0,1,0.5,1,1V8z"/>
</svg>

After

Width:  |  Height:  |  Size: 832 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#99CA3E;" d="M16,15c0,0.5-0.5,1-1,1H1c-0.5,0-1-0.5-1-1V1c0-0.5,0.5-1,1-1h14c0.5,0,1,0.5,1,1V15z"/>
</svg>

After

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#EF4540;" d="M16,15c0,0.5-0.5,1-1,1H1c-0.5,0-1-0.5-1-1V1c0-0.5,0.5-1,1-1h14c0.5,0,1,0.5,1,1V15z"/>
</svg>

After

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<g>
<path style="fill:#FFFFFF;" d="M16,15c0,0.5-0.5,1-1,1H1c-0.5,0-1-0.5-1-1V1c0-0.5,0.5-1,1-1h14c0.5,0,1,0.5,1,1V15z"/>
</g>
<path style="fill:#999999;" d="M15,0H1C0.5,0,0,0.5,0,1v14c0,0.5,0.5,1,1,1h14c0.5,0,1-0.5,1-1V1C16,0.5,15.5,0,15,0z M15,14
c0,0.5-0.5,1-1,1H2c-0.5,0-1-0.5-1-1V2c0-0.5,0.5-1,1-1h12c0.5,0,1,0.5,1,1V14z"/>
</svg>

After

Width:  |  Height:  |  Size: 936 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#FFCF35;" d="M16,15c0,0.5-0.5,1-1,1H1c-0.5,0-1-0.5-1-1V1c0-0.5,0.5-1,1-1h14c0.5,0,1,0.5,1,1V15z"/>
</svg>

After

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="-2.5 0 16 16" style="overflow:visible;enable-background:new -2.5 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#333333;" d="M5.5,0C5.5,0,0,7,0,10.5c0,3,2.5,5.5,5.5,5.5s5.5-2.5,5.5-5.5C11,7,5.5,0,5.5,0z M4.2,13.1
c-0.7,0-1.2-0.5-1.2-1.2c0-0.8,1.2-2.3,1.2-2.3s1.2,1.6,1.2,2.3C5.4,12.6,4.9,13.1,4.2,13.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="-2.5 0 16 16" style="overflow:visible;enable-background:new -2.5 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#7D9ED3;" d="M5.5,0C5.5,0,0,7,0,10.5c0,3,2.5,5.5,5.5,5.5s5.5-2.5,5.5-5.5C11,7,5.5,0,5.5,0z M4.2,13.1
c-0.7,0-1.2-0.5-1.2-1.2c0-0.8,1.2-2.3,1.2-2.3s1.2,1.6,1.2,2.3C5.4,12.6,4.9,13.1,4.2,13.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="-2.5 0 16 16" style="overflow:visible;enable-background:new -2.5 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#99CA3C;" d="M5.5,0C5.5,0,0,7,0,10.5c0,3,2.5,5.5,5.5,5.5s5.5-2.5,5.5-5.5C11,7,5.5,0,5.5,0z M4.2,13.1
c-0.7,0-1.2-0.5-1.2-1.2c0-0.8,1.2-2.3,1.2-2.3s1.2,1.6,1.2,2.3C5.4,12.6,4.9,13.1,4.2,13.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="-2.5 0 16 16" style="overflow:visible;enable-background:new -2.5 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#EF4542;" d="M5.5,0C5.5,0,0,7,0,10.5c0,3,2.5,5.5,5.5,5.5s5.5-2.5,5.5-5.5C11,7,5.5,0,5.5,0z M4.2,13.1
c-0.7,0-1.2-0.5-1.2-1.2c0-0.8,1.2-2.3,1.2-2.3s1.2,1.6,1.2,2.3C5.4,12.6,4.9,13.1,4.2,13.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="-2.5 0 16 16" style="overflow:visible;enable-background:new -2.5 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#FFFFFF;" d="M11,10.5c0,3-2.5,5.5-5.5,5.5S0,13.5,0,10.5C0,7,5.5,0,5.5,0S11,7,11,10.5z"/>
<path style="fill:#999999;" d="M5.5,0C5.5,0,0,7,0,10.5c0,3,2.5,5.5,5.5,5.5s5.5-2.5,5.5-5.5C11,7,5.5,0,5.5,0z M5.5,15
C3,15,1,13,1,10.5C1,8.3,3.8,4,5.5,1.7C7.2,4,10,8.3,10,10.5C10,13,8,15,5.5,15z M5.4,11.9c0,0.7-0.5,1.2-1.2,1.2
c-0.7,0-1.2-0.5-1.2-1.2c0-0.8,1.2-2.3,1.2-2.3S5.4,11.1,5.4,11.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 1015 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="-2.5 0 16 16" style="overflow:visible;enable-background:new -2.5 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#FFCE34;" d="M5.5,0C5.5,0,0,7,0,10.5c0,3,2.5,5.5,5.5,5.5s5.5-2.5,5.5-5.5C11,7,5.5,0,5.5,0z M4.2,13.1
c-0.7,0-1.2-0.5-1.2-1.2c0-0.8,1.2-2.3,1.2-2.3s1.2,1.6,1.2,2.3C5.4,12.6,4.9,13.1,4.2,13.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#999999;" d="M15,0H1C0.5,0,0,0.5,0,1v14c0,0.5,0.5,1,1,1h14c0.5,0,1-0.5,1-1V1C16,0.5,15.5,0,15,0z M10,12H3v-1h7
V12z M13,10H3V9h10V10z M13,8H3V7h10V8z M13,6H3V5h10V6z M13,4H3V3h10V4z"/>
</svg>

After

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#999999;" d="M8.5,16H1c-0.5,0-1-0.5-1-1v-2.5C1,14,2.7,15,4.7,15c0.8,0,1.6-0.2,2.3-0.5L8.5,16z M15,0H1
C0.5,0,0,0.5,0,1v5.2c0.7-1.1,1.8-1.9,3-2.3V3h10v1H6.6c0.6,0.2,1.2,0.6,1.7,1H13v1H9.3c0.2,0.3,0.4,0.6,0.6,1H13v1h-2.8
c0.1,0.3,0.1,0.7,0.1,1H13v1h-2.7c-0.1,0.5-0.2,0.9-0.3,1.3v0.5l1.7,1.7c0.7,0.7,0.8,1.7,0.3,2.5h3c0.5,0,1-0.5,1-1V1
C16,0.5,15.5,0,15,0z M11,15.7c-0.4,0.4-1,0.4-1.4,0l-2.2-2.2c-0.1-0.1-0.1-0.1-0.1-0.2C6.5,13.7,5.6,14,4.7,14h0
C2.1,14,0,11.9,0,9.3c0-2.6,2.1-4.7,4.7-4.7c2.6,0,4.7,2.1,4.7,4.7c0,1-0.3,1.9-0.8,2.6C8.6,12,8.7,12,8.8,12l2.2,2.2
C11.4,14.6,11.4,15.3,11,15.7z M7.4,9.3c0-1.5-1.2-2.7-2.7-2.7C3.2,6.7,2,7.9,2,9.3C2,10.8,3.2,12,4.7,12C6.2,12,7.4,10.8,7.4,9.3z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#999999;" d="M1.3,5.8L5.5,0H8L4.2,5.2l6.5,3.8L14.7,0H16v1.9l-4.3,9.9l-0.4-0.2c-1.1,0.1-3.6,0.7-4.7,2.5
l-4.8-2.8C2.9,9.5,2.1,7,1.7,6L1.3,5.8z M4.6,16l0.8-1.5l-3.5-2l-2,3.4V16H4.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="-0.1 -0.1 16 16"
style="overflow:visible;enable-background:new -0.1 -0.1 16 16;" xml:space="preserve">
<defs>
</defs>
<path style="fill:#999999;" d="M15.5,14.1c0.4,0.4,0.4,1,0,1.4s-1,0.4-1.4,0L0.3,1.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0L15.5,14.1z"
/>
</svg>

After

Width:  |  Height:  |  Size: 747 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 B

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="-0.2 -0.2 16 16"
style="overflow:visible;enable-background:new -0.2 -0.2 16 16;" xml:space="preserve">
<defs>
</defs>
<path style="fill:#999999;" d="M14.7,6.9c1,0.5,1,1.3,0,1.8L1.8,15.4c-1,0.5-1.8,0-1.8-1.1v-13c0-1.1,0.8-1.6,1.8-1.1L14.7,6.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 742 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="-0.3 -0.2 16 16"
style="overflow:visible;enable-background:new -0.3 -0.2 16 16;" xml:space="preserve">
<defs>
</defs>
<path style="fill:#999999;" d="M0.7,6.9c-1,0.5-1,1.3,0,1.8l12.9,6.7c1,0.5,1.8,0,1.8-1.1v-13c0-1.1-0.8-1.6-1.8-1.1L0.7,6.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#999999;" d="M8,0C3.6,0,0,3.6,0,8s3.6,8,8,8s8-3.6,8-8S12.4,0,8,0z M8,14c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6
S11.3,14,8,14z"/>
</svg>

After

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#999999;" d="M1,15.8c-0.6,0-1-0.4-1-1s0.4-1,1-1c0.8,0,1.5-0.2,2.2-0.5c-0.2-0.2-0.4-0.4-0.5-0.7
C1.6,11.1,1,9,1,6.8C1,3.4,2.3,0,4.9,0c2.2,0,3.7,2.5,3.7,6.2c0,2.3-0.6,4.6-1.7,6.3c-0.2,0.2-0.3,0.5-0.5,0.7
c1.1,0.5,2.6,0.5,3.8,0.2c-0.8-1.2-1.2-2.7-1.2-4.3c0-2.9,1.4-4.9,3.3-4.9c2,0,3.4,1.8,3.4,4.5c0,1.7-0.6,3.4-1.7,4.6
c-0.1,0.1-0.2,0.2-0.3,0.3c0.4,0.1,0.8,0.2,1.2,0.2c0.6,0,1,0.4,1,1s-0.4,1-1,1c-1.2,0-2.2-0.3-3.1-0.8c-0.9,0.4-1.9,0.7-3.3,0.7
c-1.4,0-2.6-0.3-3.7-0.9C3.7,15.4,2.4,15.8,1,15.8z M12.3,6.2c-0.7,0-1.3,1.3-1.3,2.9c0,1.2,0.3,2.4,1,3.2c0,0,0.1,0.1,0.1,0.1
c0.2-0.1,0.3-0.3,0.5-0.5c0.8-0.9,1.2-2.1,1.2-3.3C13.6,7.4,13.2,6.2,12.3,6.2z M4.9,2C4.1,2,3,3.9,3,6.8c0,1.8,0.5,3.5,1.3,4.7
c0.1,0.2,0.3,0.4,0.4,0.5c0.2-0.2,0.3-0.4,0.4-0.6C6,10,6.6,8.1,6.6,6.2C6.6,3.9,5.8,2,4.9,2z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#999999;" d="M0,0v16h16V0H0z M14,14H2V2h12V14z"/>
</svg>

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="-3 -0.4 16 16"
style="overflow:visible;enable-background:new -3 -0.4 16 16;" xml:space="preserve">
<defs>
</defs>
<path style="fill:#999999;" d="M8.1,14.1l-2-4.5L9.4,9C9.9,8.9,10,8.6,9.6,8.2l-8.9-8C0.3-0.2,0,0,0,0.5l0,11.9
c0,0.5,0.4,0.7,0.8,0.4l2.6-2l2,4.5c0.1,0.3,0.4,0.4,0.7,0.3l1.8-0.8C8.1,14.6,8.2,14.3,8.1,14.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#999999;" d="M15,16H1v-1h14V16z M14,10H9.2C9.1,9.2,9,8.4,9,7.9c0-1.8,2-3.5,2-4.9c0-1.7-1.3-3-3-3S5,1.3,5,3
c0,1.4,1.9,3.2,2,4.9c0,0.5,0,1.2-0.1,2.1H2c-1.1,0-2,0.9-2,2v2c0,0,0,0,0,0l16,0c0,0,0,0,0,0v-2C16,10.9,15.1,10,14,10z"/>
</svg>

After

Width:  |  Height:  |  Size: 849 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#999999;" d="M2,5v9c0,1.1,0.9,2,2,2h8c1.1,0,2-0.9,2-2V5H2z M5,13.5C5,13.8,4.8,14,4.5,14S4,13.8,4,13.5v-6
C4,7.2,4.2,7,4.5,7S5,7.2,5,7.5V13.5z M8.5,13.5C8.5,13.8,8.3,14,8,14s-0.5-0.2-0.5-0.5v-6C7.5,7.2,7.7,7,8,7s0.5,0.2,0.5,0.5V13.5z
M12,13.5c0,0.3-0.2,0.5-0.5,0.5S11,13.8,11,13.5v-6C11,7.2,11.2,7,11.5,7S12,7.2,12,7.5V13.5z M0,4c0-1.1,0.9-2,2-2h4v0
c0-1.1,0.9-2,2-2s2,0.9,2,2v0h4c1.1,0,2,0.9,2,2H0z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+43
View File
@@ -0,0 +1,43 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Settings for assignfeedback PDF plugin
*
* @package assignfeedback_editpdf
* @copyright 2013 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
// Stamp files setting.
$name = 'assignfeedback_editpdf/stamps';
$title = get_string('stamps','assignfeedback_editpdf');
$description = get_string('stampsdesc', 'assignfeedback_editpdf');
$setting = new admin_setting_configstoredfile($name, $title, $description, 'stamps', 0,
array('maxfiles' => 8, 'accepted_types' => array('image')));
$settings->add($setting);
// Ghostscript setting.
$settings->add(new admin_setting_configtext('assignfeedback_editpdf/gspath',
get_string('gspath', 'assignfeedback_editpdf'),
get_string('gspath_help', 'assignfeedback_editpdf'), '/usr/bin/gs'));
$url = new moodle_url('/mod/assign/feedback/editpdf/testgs.php');
$link = html_writer::link($url, get_string('testgs', 'assignfeedback_editpdf'));
$settings->add(new admin_setting_heading('testgs', '', $link));
+242
View File
@@ -0,0 +1,242 @@
.assignfeedback_editpdf_widget .toolbar ul {
display: none;
}
.assignfeedback_editpdf_widget .toolbar li {
list-style-type: none;
}
.assignfeedback_editpdf_widget .drawingcanvas {
position: relative;
min-width: 817px;
min-height: 1169px;
cursor: crosshair;
}
.assignfeedback_editpdf_widget {
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
-o-user-select: none;
}
.assignfeedback_editpdf_widget .pageheader {
background-color: #ebebeb;
border-bottom: 1px solid #cccccc;
padding: 4px;
padding-left: 20px;
padding-right: 20px;
min-height: 50px;
}
.moodle-dialogue-base .moodle-dialogue.assignfeedback_editpdf_widget .moodle-dialogue-bd {
padding: 0px;
}
.assignfeedback_editpdf_unsavedchanges.haschanges{
display: block;
}
.assignfeedback_editpdf_unsavedchanges {
display: none;
margin-top: 1em;
}
.yui3-colourpicker-hidden,
.yui3-commentsearch-hidden,
.yui3-commentmenu-hidden {
display: none;
}
.assignfeedback_editpdf_widget .pageheader button:active {
background-color: #ccc;
}
.assignfeedback_editpdf_widget .pageheader select,
.assignfeedback_editpdf_widget .pageheader button {
background: none;
padding: 4px 10px;
border: 0px;
border-radius: 0px;
margin: 0px;
height: 30px;
cursor: pointer;
}
.assignfeedback_editpdf_widget .pageheader .navigation button + button,
.assignfeedback_editpdf_widget .pageheader .toolbar button + button,
.assignfeedback_editpdf_widget .pageheader .navigation select + button,
.assignfeedback_editpdf_widget .pageheader .toolbar select + button {
border-left: 1px solid #cccccc;
border-right: 0px;
}
.assignfeedback_editpdf_widget .pageheader .navigation button {
border-right: 1px solid #cccccc;
}
.assignfeedback_editpdf_widget .pageheader .toolbar,
.assignfeedback_editpdf_widget .pageheader .navigation-search,
.assignfeedback_editpdf_widget .pageheader .navigation {
border: 1px solid #cccccc;
border-bottom-color: #b3b3b3;
border-radius: 4px;
margin: 10px 4px;
background-color: white;
height: 30px;
line-height: 30px;
padding: 0px;
}
.assignfeedback_editpdf_commentsearch ul {
max-height: 400px;
overflow-y: auto;
padding: 1em;
}
.assignfeedback_editpdf_commentsearch ul li {
line-height: 1.4em;
}
.assignfeedback_editpdf_commentsearch a pre {
font-family: helvetica;
margin: 0px;
padding: 4px;
}
.assignfeedback_editpdf_widget .navigation-search,
.assignfeedback_editpdf_widget .navigation {
float: left;
}
.dir-rtl .assignfeedback_editpdf_widget .navigation-search,
.dir-rtl .assignfeedback_editpdf_widget .navigation {
float: right;
}
.assignfeedback_editpdf_widget .toolbar {
float: right;
}
.dir-rtl .assignfeedback_editpdf_widget .toolbar {
float: left;
}
.assignfeedback_editpdf_widget .navigation,
.assignfeedback_editpdf_widget .navigation-search,
.assignfeedback_editpdf_widget .toolbar {
display: inline-block;
}
.assignfeedback_editpdf_colourpicker ul {
margin: 0px;
}
.assignfeedback_editpdf_commentmenu li.quicklist_comment {
width: 150px;
}
.assignfeedback_editpdf_commentmenu li.quicklist_comment a {
white-space: nowrap;
display: inline-block;
max-width: 130px;
overflow: hidden;
text-overflow: ellipsis;
}
.assignfeedback_editpdf_commentmenu a.delete_quicklist_comment {
float: right;
}
.dir-rtl .assignfeedback_editpdf_commentmenu a.delete_quicklist_comment {
float: left;
}
.assignfeedback_editpdf_dropdown button {
border: 0px;
background: none;
padding-left: 12px;
padding-right: 12px;
padding-top: 6px;
padding-bottom: 6px;
border-radius: 0px;
border-top: 1px solid #cccccc;
}
.assignfeedback_editpdf_dropdown li:first-child button {
border-top: 0px;
}
.moodle-dialogue-base .moodle-dialogue.assignfeedback_editpdf_dropdown .moodle-dialogue-wrap {
box-shadow: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
}
.moodle-dialogue-base .moodle-dialogue.assignfeedback_editpdf_dropdown .moodle-dialogue-bd {
padding: 0px;
}
.assignfeedback_editpdf_dropdown .moodle-dialogue-hd,
.assignfeedback_editpdf_dropdown .moodle-dialogue-ft {
display: none;
}
.assignfeedback_editpdf_menu li hr {
margin: 0px;
}
.assignfeedback_editpdf_menu li a {
text-decoration: none;
color: #555;
margin: 10px;
}
.assignfeedback_editpdf_menu li:hover,
.assignfeedback_editpdf_menu li:hover a,
.assignfeedback_editpdf_menu li a:hover {
background-color: #08c;
color: white;
text-decoration: underline;
}
ul.assignfeedback_editpdf_menu {
margin: 0px;
}
.assignfeedback_editpdf_menu li {
list-style-type: none;
margin: 0px;
border-radius: 4px;
}
.assignfeedback_editpdf_menu li button {
margin: 0px;
}
.assignfeedback_editpdf_widget .pageheader button.assignfeedback_editpdf_selectedbutton:hover,
.assignfeedback_editpdf_widget .pageheader button.assignfeedback_editpdf_selectedbutton {
background-color: #CCF;
background-image: none;
}
.assignfeedback_editpdf_widget .commentdrawable img {
padding: 1px;
}
.assignfeedback_editpdf_widget .commentdrawable a {
float: right;
position: relative;
left: -17px;
top: 2px;
height: 14px;
background-color: white;
border-left: 1px solid #ccc;
border-bottom: 1px solid #ccc;
line-height: 14px;
}
.dir-rtl .assignfeedback_editpdf_widget .commentdrawable a {
float: left;
left: none;
right: -17px;
border-left: 0px;
border-right: 1px solid #ccc;
}
.assignfeedback_editpdf_widget .commentdrawable textarea {
padding: 4px;
padding-right: 20px;
resize: none;
overflow: hidden;
color: black;
border: 2px solid #ccc;
border-radius: 4px;
font-size: 16px;
font-family: helvetica;
min-height: 1.2em;
}
.assignfeedback_editpdf_widget .commentdrawable {
display: inline-block;
}
.dir-rtl .assignfeedback_editpdf_widget .commentdrawable textarea {
padding-left: 20px;
padding-right: 4px;
}
+67
View File
@@ -0,0 +1,67 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Test that ghostscript is configured correctly
*
* @package assignfeedback_editpdf
* @copyright 2013 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(dirname(__FILE__).'/../../../../config.php');
global $PAGE, $OUTPUT;
$PAGE->set_url(new moodle_url('/mod/assign/feedback/editpdf/testgs.php'));
$PAGE->set_context(context_system::instance());
require_capability('moodle/site:config', context_system::instance());
if (optional_param('sendimage', false, PARAM_BOOL)) {
// Serve the generated test image.
assignfeedback_editpdf\pdf::send_test_image();
die();
}
$result = assignfeedback_editpdf\pdf::test_gs_path();
switch ($result->status) {
case assignfeedback_editpdf\pdf::GSPATH_OK:
$msg = get_string('test_ok', 'assignfeedback_editpdf');
$msg .= html_writer::empty_tag('br');
$imgurl = new moodle_url($PAGE->url, array('sendimage' => 1));
$msg .= html_writer::empty_tag('img', array('src' => $imgurl));
break;
case assignfeedback_editpdf\pdf::GSPATH_ERROR:
$msg = $result->message;
break;
default:
$msg = get_string("test_{$result->status}", 'assignfeedback_editpdf');
break;
}
$returl = new moodle_url('/admin/settings.php', array('section' => 'assignfeedback_editpdf'));
$msg .= $OUTPUT->continue_button($returl);
$strheading = get_string('testgs', 'assignfeedback_editpdf');
$PAGE->set_heading($strheading);
$PAGE->set_title($strheading);
echo $OUTPUT->header();
echo $OUTPUT->box($msg, 'generalbox ');
echo $OUTPUT->footer();
@@ -0,0 +1,302 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for assignfeedback_editpdf\comments_quick_list
*
* @package assignfeedback_editpdf
* @category phpunit
* @copyright 2013 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
use \assignfeedback_editpdf\comments_quick_list;
use \assignfeedback_editpdf\document_services;
use \assignfeedback_editpdf\page_editor;
use \assignfeedback_editpdf\pdf;
use \assignfeedback_editpdf\comment;
use \assignfeedback_editpdf\annotation;
global $CFG;
require_once($CFG->dirroot . '/mod/assign/tests/base_test.php');
/**
* Unit tests for assignfeedback_editpdf\comments_quick_list
*
* @copyright 2013 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class assignfeedback_editpdf_testcase extends mod_assign_base_testcase {
protected function setUp() {
// Skip this test if ghostscript is not supported.
if (!pdf::test_gs_path(false)) {
$this->markTestSkipped('Ghostscript not setup');
return;
}
parent::setUp();
}
protected function create_assign_and_submit_pdf() {
global $CFG;
$assign = $this->create_instance(array('assignsubmission_file_enabled' => 1,
'assignsubmission_file_maxfiles' => 1,
'assignfeedback_editpdf_enabled' => 1,
'assignsubmission_file_maxsizebytes' => 1000000));
$user = $this->students[0];
$this->setUser($user);
// Create a file submission with the test pdf.
$submission = $assign->get_user_submission($user->id, true);
$fs = get_file_storage();
$pdfsubmission = (object) array(
'contextid' => $assign->get_context()->id,
'component' => 'assignsubmission_file',
'filearea' => ASSIGNSUBMISSION_FILE_FILEAREA,
'itemid' => $submission->id,
'filepath' => '/',
'filename' => 'submission.pdf'
);
$sourcefile = $CFG->dirroot.'/mod/assign/feedback/editpdf/tests/fixtures/submission.pdf';
$fi = $fs->create_file_from_pathname($pdfsubmission, $sourcefile);
$data = new stdClass();
$plugin = $assign->get_submission_plugin_by_type('file');
$plugin->save($submission, $data);
return $assign;
}
public function test_comments_quick_list() {
$this->setUser($this->teachers[0]);
$comments = comments_quick_list::get_comments();
$this->assertEmpty($comments);
$comment = comments_quick_list::add_comment('test', 45, 'red');
$comments = comments_quick_list::get_comments();
$this->assertEquals(count($comments), 1);
$first = reset($comments);
$this->assertEquals($comment, $first);
$commentbyid = comments_quick_list::get_comment($comment->id);
$this->assertEquals($comment, $commentbyid);
$result = comments_quick_list::remove_comment($comment->id);
$this->assertTrue($result);
$comments = comments_quick_list::get_comments();
$this->assertEmpty($comments);
}
public function test_page_editor() {
$assign = $this->create_assign_and_submit_pdf();
$this->setUser($this->teachers[0]);
$grade = $assign->get_user_grade($this->students[0]->id, true);
$notempty = page_editor::has_annotations_or_comments($grade->id, false);
$this->assertFalse($notempty);
$comment = new comment();
$comment->rawtext = 'Comment text';
$comment->width = 100;
$comment->x = 100;
$comment->y = 100;
$comment->colour = 'red';
$comment2 = new comment();
$comment2->rawtext = 'Comment text 2';
$comment2->width = 100;
$comment2->x = 200;
$comment2->y = 100;
$comment2->colour = 'clear';
page_editor::set_comments($grade->id, 0, array($comment, $comment2));
$annotation = new annotation();
$annotation->path = '';
$annotation->x = 100;
$annotation->y = 100;
$annotation->endx = 200;
$annotation->endy = 200;
$annotation->type = 'line';
$annotation->colour = 'red';
$annotation2 = new annotation();
$annotation2->path = '';
$annotation2->x = 100;
$annotation2->y = 100;
$annotation2->endx = 200;
$annotation2->endy = 200;
$annotation2->type = 'rectangle';
$annotation2->colour = 'yellow';
page_editor::set_annotations($grade->id, 0, array($annotation, $annotation2));
$notempty = page_editor::has_annotations_or_comments($grade->id, false);
// Still empty because all edits are still drafts.
$this->assertFalse($notempty);
$comments = page_editor::get_comments($grade->id, 0, false);
$this->assertEmpty($comments);
$comments = page_editor::get_comments($grade->id, 0, true);
$this->assertEquals(count($comments), 2);
$annotations = page_editor::get_annotations($grade->id, 0, false);
$this->assertEmpty($annotations);
$annotations = page_editor::get_annotations($grade->id, 0, true);
$this->assertEquals(count($annotations), 2);
$comment = reset($comments);
$annotation = reset($annotations);
page_editor::remove_comment($comment->id);
page_editor::remove_annotation($annotation->id);
$comments = page_editor::get_comments($grade->id, 0, true);
$this->assertEquals(count($comments), 1);
$annotations = page_editor::get_annotations($grade->id, 0, true);
$this->assertEquals(count($annotations), 1);
page_editor::release_drafts($grade->id);
$notempty = page_editor::has_annotations_or_comments($grade->id, false);
$this->assertTrue($notempty);
page_editor::unrelease_drafts($grade->id);
$notempty = page_editor::has_annotations_or_comments($grade->id, false);
$this->assertFalse($notempty);
}
public function test_document_services() {
$assign = $this->create_assign_and_submit_pdf();
$this->setUser($this->teachers[0]);
$grade = $assign->get_user_grade($this->students[0]->id, true);
$notempty = page_editor::has_annotations_or_comments($grade->id, false);
$this->assertFalse($notempty);
$comment = new comment();
$comment->rawtext = 'Comment text';
$comment->width = 100;
$comment->x = 100;
$comment->y = 100;
$comment->colour = 'red';
page_editor::set_comments($grade->id, 0, array($comment));
$annotations = array();
$annotation = new annotation();
$annotation->path = '';
$annotation->x = 100;
$annotation->y = 100;
$annotation->endx = 200;
$annotation->endy = 200;
$annotation->type = 'line';
$annotation->colour = 'red';
array_push($annotations, $annotation);
$annotation = new annotation();
$annotation->path = '';
$annotation->x = 100;
$annotation->y = 100;
$annotation->endx = 200;
$annotation->endy = 200;
$annotation->type = 'rectangle';
$annotation->colour = 'yellow';
array_push($annotations, $annotation);
$annotation = new annotation();
$annotation->path = '';
$annotation->x = 100;
$annotation->y = 100;
$annotation->endx = 200;
$annotation->endy = 200;
$annotation->type = 'oval';
$annotation->colour = 'green';
array_push($annotations, $annotation);
$annotation = new annotation();
$annotation->path = '';
$annotation->x = 100;
$annotation->y = 100;
$annotation->endx = 200;
$annotation->endy = 116;
$annotation->type = 'highlight';
$annotation->colour = 'blue';
array_push($annotations, $annotation);
$annotation = new annotation();
$annotation->path = '100,100:105,105:110,100';
$annotation->x = 100;
$annotation->y = 100;
$annotation->endx = 110;
$annotation->endy = 105;
$annotation->type = 'pen';
$annotation->colour = 'black';
array_push($annotations, $annotation);
page_editor::set_annotations($grade->id, 0, $annotations);
page_editor::release_drafts($grade->id);
$notempty = page_editor::has_annotations_or_comments($grade->id, false);
$this->assertTrue($notempty);
$file = document_services::generate_feedback_document($assign->get_instance()->id, $grade->userid, $grade->attemptnumber);
$this->assertNotEmpty($file);
$file2 = document_services::get_feedback_document($assign->get_instance()->id, $grade->userid, $grade->attemptnumber);
$this->assertEquals($file, $file2);
document_services::delete_feedback_document($assign->get_instance()->id, $grade->userid, $grade->attemptnumber);
$file3 = document_services::get_feedback_document($assign->get_instance()->id, $grade->userid, $grade->attemptnumber);
$this->assertEmpty($file3);
}
}
Binary file not shown.
Binary file not shown.
+30
View File
@@ -0,0 +1,30 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the version information for the comments feedback plugin
*
* @package assignfeedback_editpdf
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2013050103;
$plugin->requires = 2013050100;
$plugin->component = 'assignfeedback_editpdf';
File diff suppressed because one or more lines are too long
@@ -0,0 +1,30 @@
{
"name": "moodle-assignfeedback_editpdf-editor",
"builds": {
"moodle-assignfeedback_editpdf-editor": {
"jsfiles": [
"globals.js",
"point.js",
"rect.js",
"edit.js",
"drawable.js",
"annotation.js",
"annotationline.js",
"annotationrectangle.js",
"annotationoval.js",
"annotationpen.js",
"annotationhighlight.js",
"annotationstamp.js",
"dropdown.js",
"colourpicker.js",
"stamppicker.js",
"commentmenu.js",
"commentsearch.js",
"comment.js",
"quickcomment.js",
"quickcommentlist.js",
"editor.js"
]
}
}
}
@@ -0,0 +1,327 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class representing a list of annotations.
*
* @module moodle-assignfeedback_editpdf-editor
*/
ANNOTATION = function(config) {
ANNOTATION.superclass.constructor.apply(this, [config]);
};
ANNOTATION.NAME = "annotation";
ANNOTATION.ATTRS = {};
Y.extend(ANNOTATION, Y.Base, {
/**
* Reference to M.assignfeedback_editpdf.editor.
* @property editor
* @type M.assignfeedback_editpdf.editor
* @public
*/
editor : null,
/**
* Grade id
* @property gradeid
* @type Int
* @public
*/
gradeid : 0,
/**
* Comment page number
* @property pageno
* @type Int
* @public
*/
pageno : 0,
/**
* X position
* @property x
* @type Int
* @public
*/
x : 0,
/**
* Y position
* @property y
* @type Int
* @public
*/
y : 0,
/**
* Ending x position
* @property endx
* @type Int
* @public
*/
endx : 0,
/**
* Ending y position
* @property endy
* @type Int
* @public
*/
endy : 0,
/**
* Path
* @property path
* @type String - list of points like x1,y1:x2,y2
* @public
*/
path : '',
/**
* Tool.
* @property type
* @type String
* @public
*/
type : 'rect',
/**
* Annotation colour.
* @property colour
* @type String
* @public
*/
colour : 'red',
/**
* Reference to M.assignfeedback_editpdf.drawable
* @property drawable
* @type M.assignfeedback_editpdf.drawable
* @public
*/
drawable : false,
/**
* Initialise the annotation.
*
* @method initializer
* @return void
*/
initializer : function(config) {
this.editor = config.editor || null;
this.gradeid = parseInt(config.gradeid, 10) || 0;
this.pageno = parseInt(config.pageno, 10) || 0;
this.x = parseInt(config.x, 10) || 0;
this.y = parseInt(config.y, 10) || 0;
this.endx = parseInt(config.endx, 10) || 0;
this.endy = parseInt(config.endy, 10) || 0;
this.path = config.path || '';
this.type = config.type || 'rect';
this.colour = config.colour || 'red';
this.drawable = false;
},
/**
* Clean a comment record, returning an oject with only fields that are valid.
* @public
* @method clean
* @return {}
*/
clean : function() {
return {
gradeid : this.gradeid,
x : parseInt(this.x, 10),
y : parseInt(this.y, 10),
endx : parseInt(this.endx, 10),
endy : parseInt(this.endy, 10),
type : this.type,
path : this.path,
pageno : this.pageno,
colour : this.colour
};
},
/**
* Draw a selection around this annotation if it is selected.
* @public
* @method draw_highlight
* @return M.assignfeedback_editpdf.drawable
*/
draw_highlight : function() {
var bounds,
drawingregion = Y.one(SELECTOR.DRAWINGREGION),
offsetcanvas = Y.one(SELECTOR.DRAWINGCANVAS).getXY(),
shape;
if (this.editor.currentannotation === this) {
// Draw a highlight around the annotation.
bounds = new M.assignfeedback_editpdf.rect();
bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
shape = this.editor.graphic.addShape({
type: Y.Rect,
width: bounds.width,
height: bounds.height,
stroke: {
weight: STROKEWEIGHT,
color: SELECTEDBORDERCOLOUR
},
fill: {
color: SELECTEDFILLCOLOUR
},
x: bounds.x,
y: bounds.y
});
this.drawable.shapes.push(shape);
// Add a delete X to the annotation.
var deleteicon = Y.Node.create('<img src="' + M.util.image_url('trash', 'assignfeedback_editpdf') + '"/>'),
deletelink = Y.Node.create('<a href="#" role="button"></a>');
deleteicon.setAttrs({
'alt': M.util.get_string('deleteannotation', 'assignfeedback_editpdf')
});
deleteicon.setStyles({
'backgroundColor' : 'white'
});
deletelink.addClass('deleteannotationbutton');
deletelink.append(deleteicon);
drawingregion.append(deletelink);
deletelink.setData('annotation', this);
deletelink.setStyle('zIndex', '200');
deletelink.on('click', this.remove, this);
deletelink.on('key', this.remove, 'space,enter', this);
deletelink.setX(offsetcanvas[0] + bounds.x + bounds.width - 18);
deletelink.setY(offsetcanvas[1] + bounds.y + 6);
this.drawable.nodes.push(deletelink);
}
return this.drawable;
},
/**
* Draw an annotation
* @public
* @method draw
* @return M.assignfeedback_editpdf.drawable|false
*/
draw : function() {
// Should be overridden by the subclass.
this.draw_highlight();
return this.drawable;
},
/**
* Delete an annotation
* @protected
* @method remove
* @param event
*/
remove : function() {
var annotations;
annotations = this.editor.pages[this.editor.currentpage].annotations;
for (i = 0; i < annotations.length; i++) {
if (annotations[i] === this) {
annotations.splice(i, 1);
if (this.drawable) {
this.drawable.erase();
}
this.editor.currentannotation = false;
this.editor.save_current_page();
return;
}
}
},
/**
* Move an annotation to a new location.
* @public
* @param int newx
* @param int newy
* @method move_annotation
*/
move : function(newx, newy) {
var diffx = newx - this.x,
diffy = newy - this.y,
newpath, oldpath, xy,
x, y;
this.x += diffx;
this.y += diffy;
this.endx += diffx;
this.endy += diffy;
if (this.path) {
newpath = [];
oldpath = this.path.split(':');
Y.each(oldpath, function(position) {
xy = position.split(',');
x = parseInt(xy[0], 10);
y = parseInt(xy[1], 10);
newpath.push((x + diffx) + ',' + (y + diffy));
});
this.path = newpath.join(':');
}
if (this.drawable) {
this.drawable.erase();
}
this.editor.drawables.push(this.draw());
},
/**
* Draw the in progress edit.
*
* @public
* @method draw_current_edit
* @param M.assignfeedback_editpdf.edit edit
*/
draw_current_edit : function(edit) {
var noop = edit && false;
// Override me please.
return noop;
},
/**
* Promote the current edit to a real annotation.
*
* @public
* @method init_from_edit
* @param M.assignfeedback_editpdf.edit edit
*/
init_from_edit : function(edit) {
var bounds = new M.assignfeedback_editpdf.rect();
bounds.bound([edit.start, edit.end]);
this.gradeid = this.editor.get('gradeid');
this.pageno = this.editor.currentpage;
this.x = bounds.x;
this.y = bounds.y;
this.endx = bounds.x + bounds.width;
this.endy = bounds.y + bounds.height;
this.colour = edit.annotationcolour;
this.path = '';
}
});
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
M.assignfeedback_editpdf.annotation = ANNOTATION;
@@ -0,0 +1,139 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class representing a highlight.
*
* @namespace M.assignfeedback_editpdf
* @class annotationhighlight
* @extends annotation
* @module moodle-assignfeedback_editpdf-editor
*/
ANNOTATIONHIGHLIGHT = function(config) {
ANNOTATIONHIGHLIGHT.superclass.constructor.apply(this, [config]);
};
ANNOTATIONHIGHLIGHT.NAME = "annotationhighlight";
ANNOTATIONHIGHLIGHT.ATTRS = {};
Y.extend(ANNOTATIONHIGHLIGHT, M.assignfeedback_editpdf.annotation, {
/**
* Draw a highlight annotation
* @protected
* @method draw
* @return M.assignfeedback_editpdf.drawable
*/
draw : function() {
var drawable,
shape,
bounds,
highlightcolour;
drawable = new M.assignfeedback_editpdf.drawable(this.editor);
bounds = new M.assignfeedback_editpdf.rect();
bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
highlightcolour = ANNOTATIONCOLOUR[this.colour];
// Add an alpha channel to the rgb colour.
highlightcolour = highlightcolour.replace('rgb', 'rgba');
highlightcolour = highlightcolour.replace(')', ',0.5)');
shape = this.editor.graphic.addShape({
type: Y.Rect,
width: bounds.width,
height: bounds.height,
stroke: false,
fill: {
color: highlightcolour
},
x: bounds.x,
y: bounds.y
});
drawable.shapes.push(shape);
this.drawable = drawable;
return ANNOTATIONHIGHLIGHT.superclass.draw.apply(this);
},
/**
* Draw the in progress edit.
*
* @public
* @method draw_current_edit
* @param M.assignfeedback_editpdf.edit edit
*/
draw_current_edit : function(edit) {
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
shape,
bounds,
highlightcolour;
bounds = new M.assignfeedback_editpdf.rect();
bounds.bound([new M.assignfeedback_editpdf.point(edit.start.x, edit.start.y),
new M.assignfeedback_editpdf.point(edit.end.x, edit.end.y)]);
highlightcolour = ANNOTATIONCOLOUR[edit.annotationcolour];
// Add an alpha channel to the rgb colour.
highlightcolour = highlightcolour.replace('rgb', 'rgba');
highlightcolour = highlightcolour.replace(')', ',0.5)');
// We will draw a box with the current background colour.
shape = this.editor.graphic.addShape({
type: Y.Rect,
width: bounds.width,
height: 16,
stroke: false,
fill: {
color: highlightcolour
},
x: bounds.x,
y: edit.start.y
});
drawable.shapes.push(shape);
return drawable;
},
/**
* Promote the current edit to a real annotation.
*
* @public
* @method init_from_edit
* @param M.assignfeedback_editpdf.edit edit
*/
init_from_edit : function(edit) {
var bounds = new M.assignfeedback_editpdf.rect();
bounds.bound([edit.start, edit.end]);
this.gradeid = this.editor.get('gradeid');
this.pageno = this.editor.currentpage;
this.x = bounds.x;
this.y = edit.start.y;
this.endx = bounds.x + bounds.width;
this.endy = edit.start.y + 16;
this.colour = edit.annotationcolour;
this.page = '';
}
});
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
M.assignfeedback_editpdf.annotationhighlight = ANNOTATIONHIGHLIGHT;
@@ -0,0 +1,112 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class representing a line.
*
* @namespace M.assignfeedback_editpdf
* @class annotationline
* @extends annotation
* @module moodle-assignfeedback_editpdf-editor
*/
ANNOTATIONLINE = function(config) {
ANNOTATIONLINE.superclass.constructor.apply(this, [config]);
};
ANNOTATIONLINE.NAME = "annotationline";
ANNOTATIONLINE.ATTRS = {};
Y.extend(ANNOTATIONLINE, M.assignfeedback_editpdf.annotation, {
/**
* Draw a line annotation
* @protected
* @method draw
* @return M.assignfeedback_editpdf.drawable
*/
draw : function() {
var drawable,
shape;
drawable = new M.assignfeedback_editpdf.drawable(this.editor);
shape = this.editor.graphic.addShape({
type: Y.Path,
fill: false,
stroke: {
weight: STROKEWEIGHT,
color: ANNOTATIONCOLOUR[this.colour]
}
});
shape.moveTo(this.x, this.y);
shape.lineTo(this.endx, this.endy);
shape.end();
drawable.shapes.push(shape);
this.drawable = drawable;
return ANNOTATIONLINE.superclass.draw.apply(this);
},
/**
* Draw the in progress edit.
*
* @public
* @method draw_current_edit
* @param M.assignfeedback_editpdf.edit edit
*/
draw_current_edit : function(edit) {
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
shape;
shape = this.editor.graphic.addShape({
type: Y.Path,
fill: false,
stroke: {
weight: STROKEWEIGHT,
color: ANNOTATIONCOLOUR[edit.annotationcolour]
}
});
shape.moveTo(edit.start.x, edit.start.y);
shape.lineTo(edit.end.x, edit.end.y);
shape.end();
drawable.shapes.push(shape);
return drawable;
},
/**
* Promote the current edit to a real annotation.
*
* @public
* @method init_from_edit
* @param M.assignfeedback_editpdf.edit edit
*/
init_from_edit : function(edit) {
this.gradeid = this.editor.get('gradeid');
this.pageno = this.editor.currentpage;
this.x = edit.start.x;
this.y = edit.start.y;
this.endx = edit.end.x;
this.endy = edit.end.y;
this.colour = edit.annotationcolour;
this.path = '';
}
});
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
M.assignfeedback_editpdf.annotationline = ANNOTATIONLINE;
@@ -0,0 +1,100 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class representing a oval.
*
* @namespace M.assignfeedback_editpdf
* @class annotationoval
* @extends annotation
* @module moodle-assignfeedback_editpdf-editor
*/
ANNOTATIONOVAL = function(config) {
ANNOTATIONOVAL.superclass.constructor.apply(this, [config]);
};
ANNOTATIONOVAL.NAME = "annotationoval";
ANNOTATIONOVAL.ATTRS = {};
Y.extend(ANNOTATIONOVAL, M.assignfeedback_editpdf.annotation, {
/**
* Draw a oval annotation
* @protected
* @method draw
* @return M.assignfeedback_editpdf.drawable
*/
draw : function() {
var drawable,
shape;
drawable = new M.assignfeedback_editpdf.drawable(this.editor);
bounds = new M.assignfeedback_editpdf.rect();
bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
shape = this.editor.graphic.addShape({
type: Y.Ellipse,
width: bounds.width,
height: bounds.height,
stroke: {
weight: STROKEWEIGHT,
color: ANNOTATIONCOLOUR[this.colour]
},
x: bounds.x,
y: bounds.y
});
drawable.shapes.push(shape);
this.drawable = drawable;
return ANNOTATIONOVAL.superclass.draw.apply(this);
},
/**
* Draw the in progress edit.
*
* @public
* @method draw_current_edit
* @param M.assignfeedback_editpdf.edit edit
*/
draw_current_edit : function(edit) {
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
shape,
bounds;
bounds = new M.assignfeedback_editpdf.rect();
bounds.bound([new M.assignfeedback_editpdf.point(edit.start.x, edit.start.y),
new M.assignfeedback_editpdf.point(edit.end.x, edit.end.y)]);
shape = this.editor.graphic.addShape({
type: Y.Ellipse,
width: bounds.width,
height: bounds.height,
stroke: {
weight: STROKEWEIGHT,
color: ANNOTATIONCOLOUR[edit.annotationcolour]
},
x: bounds.x,
y: bounds.y
});
drawable.shapes.push(shape);
return drawable;
}
});
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
M.assignfeedback_editpdf.annotationoval = ANNOTATIONOVAL;
@@ -0,0 +1,152 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class representing a pen.
*
* @namespace M.assignfeedback_editpdf
* @class annotationpen
* @extends annotation
* @module moodle-assignfeedback_editpdf-editor
*/
ANNOTATIONPEN = function(config) {
ANNOTATIONPEN.superclass.constructor.apply(this, [config]);
};
ANNOTATIONPEN.NAME = "annotationpen";
ANNOTATIONPEN.ATTRS = {};
Y.extend(ANNOTATIONPEN, M.assignfeedback_editpdf.annotation, {
/**
* Draw a pen annotation
* @protected
* @method draw
* @return M.assignfeedback_editpdf.drawable
*/
draw : function() {
var drawable,
shape,
first,
positions,
xy;
drawable = new M.assignfeedback_editpdf.drawable(this.editor);
shape = this.editor.graphic.addShape({
type: Y.Path,
fill: false,
stroke: {
weight: STROKEWEIGHT,
color: ANNOTATIONCOLOUR[this.colour]
}
});
first = true;
// Recreate the pen path array.
positions = this.path.split(':');
// Redraw all the lines.
Y.each(positions, function(position) {
xy = position.split(',');
if (first) {
shape.moveTo(xy[0], xy[1]);
first = false;
} else {
shape.lineTo(xy[0], xy[1]);
}
}, this);
shape.end();
drawable.shapes.push(shape);
this.drawable = drawable;
return ANNOTATIONPEN.superclass.draw.apply(this);
},
/**
* Draw the in progress edit.
*
* @public
* @method draw_current_edit
* @param M.assignfeedback_editpdf.edit edit
*/
draw_current_edit : function(edit) {
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
shape,
first;
shape = this.editor.graphic.addShape({
type: Y.Path,
fill: false,
stroke: {
weight: STROKEWEIGHT,
color: ANNOTATIONCOLOUR[edit.annotationcolour]
}
});
first = true;
// Recreate the pen path array.
// Redraw all the lines.
Y.each(edit.path, function(position) {
if (first) {
shape.moveTo(position.x, position.y);
first = false;
} else {
shape.lineTo(position.x, position.y);
}
}, this);
shape.end();
drawable.shapes.push(shape);
return drawable;
},
/**
* Promote the current edit to a real annotation.
*
* @public
* @method init_from_edit
* @param M.assignfeedback_editpdf.edit edit
*/
init_from_edit : function(edit) {
var bounds = new M.assignfeedback_editpdf.rect(),
pathlist = [],
i = 0;
// This will get the boundaries of all points in the path.
bounds.bound(edit.path);
for (i = 0; i < edit.path.length; i++) {
pathlist.push(parseInt(edit.path[i].x, 10) + ',' + parseInt(edit.path[i].y, 10));
}
this.gradeid = this.editor.get('gradeid');
this.pageno = this.editor.currentpage;
this.x = bounds.x;
this.y = bounds.y;
this.endx = bounds.x + bounds.width;
this.endy = bounds.y + bounds.height;
this.colour = edit.annotationcolour;
this.path = pathlist.join(':');
}
});
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
M.assignfeedback_editpdf.annotationpen = ANNOTATIONPEN;
@@ -0,0 +1,100 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class representing a rectangle.
*
* @namespace M.assignfeedback_editpdf
* @class annotationrectangle
* @extends annotation
* @module moodle-assignfeedback_editpdf-editor
*/
ANNOTATIONRECTANGLE = function(config) {
ANNOTATIONRECTANGLE.superclass.constructor.apply(this, [config]);
};
ANNOTATIONRECTANGLE.NAME = "annotationrectangle";
ANNOTATIONRECTANGLE.ATTRS = {};
Y.extend(ANNOTATIONRECTANGLE, M.assignfeedback_editpdf.annotation, {
/**
* Draw a rectangle annotation
* @protected
* @method draw
* @return M.assignfeedback_editpdf.drawable
*/
draw : function() {
var drawable,
shape;
drawable = new M.assignfeedback_editpdf.drawable(this.editor);
bounds = new M.assignfeedback_editpdf.rect();
bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
shape = this.editor.graphic.addShape({
type: Y.Rect,
width: bounds.width,
height: bounds.height,
stroke: {
weight: STROKEWEIGHT,
color: ANNOTATIONCOLOUR[this.colour]
},
x: bounds.x,
y: bounds.y
});
drawable.shapes.push(shape);
this.drawable = drawable;
return ANNOTATIONRECTANGLE.superclass.draw.apply(this);
},
/**
* Draw the in progress edit.
*
* @public
* @method draw_current_edit
* @param M.assignfeedback_editpdf.edit edit
*/
draw_current_edit : function(edit) {
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
shape,
bounds;
bounds = new M.assignfeedback_editpdf.rect();
bounds.bound([new M.assignfeedback_editpdf.point(edit.start.x, edit.start.y),
new M.assignfeedback_editpdf.point(edit.end.x, edit.end.y)]);
shape = this.editor.graphic.addShape({
type: Y.Rect,
width: bounds.width,
height: bounds.height,
stroke: {
weight: STROKEWEIGHT,
color: ANNOTATIONCOLOUR[edit.annotationcolour]
},
x: bounds.x,
y: bounds.y
});
drawable.shapes.push(shape);
return drawable;
}
});
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
M.assignfeedback_editpdf.annotationrectangle = ANNOTATIONRECTANGLE;
@@ -0,0 +1,158 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class representing a stamp.
*
* @namespace M.assignfeedback_editpdf
* @class annotationstamp
* @extends annotation
* @module moodle-assignfeedback_editpdf-editor
*/
ANNOTATIONSTAMP = function(config) {
ANNOTATIONSTAMP.superclass.constructor.apply(this, [config]);
};
ANNOTATIONSTAMP.NAME = "annotationstamp";
ANNOTATIONSTAMP.ATTRS = {};
Y.extend(ANNOTATIONSTAMP, M.assignfeedback_editpdf.annotation, {
/**
* Draw a stamp annotation
* @protected
* @method draw
* @return M.assignfeedback_editpdf.drawable
*/
draw : function() {
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
drawingregion = Y.one(SELECTOR.DRAWINGREGION),
node,
position;
position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(this.x, this.y));
node = Y.Node.create('<div/>');
node.setStyles({
'display': 'inline-block',
'backgroundImage': 'url(' + this.editor.get_stamp_image_url(this.path) + ')',
'width': (this.endx - this.x),
'height': (this.endy - this.y),
'backgroundSize': '100% 100%',
'zIndex': 50
});
drawingregion.append(node);
node.setX(position.x);
node.setY(position.y);
// Pass throught the event handlers on the div.
node.on('gesturemovestart', this.editor.edit_start, null, this.editor);
node.on('gesturemove', this.editor.edit_move, null, this.editor);
node.on('gesturemoveend', this.editor.edit_end, null, this.editor);
drawable.nodes.push(node);
this.drawable = drawable;
return ANNOTATIONSTAMP.superclass.draw.apply(this);
},
/**
* Draw the in progress edit.
*
* @public
* @method draw_current_edit
* @param M.assignfeedback_editpdf.edit edit
*/
draw_current_edit : function(edit) {
var bounds = new M.assignfeedback_editpdf.rect(),
drawable = new M.assignfeedback_editpdf.drawable(this.editor),
drawingregion = Y.one(SELECTOR.DRAWINGREGION),
node,
position;
bounds.bound([edit.start, edit.end]);
position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(bounds.x, bounds.y));
node = Y.Node.create('<div/>');
node.setStyles({
'display': 'inline-block',
'backgroundImage': 'url(' + this.editor.get_stamp_image_url(edit.stamp) + ')',
'width': bounds.width,
'height': bounds.height,
'backgroundSize': '100% 100%',
'zIndex': 50
});
drawingregion.append(node);
node.setX(position.x);
node.setY(position.y);
drawable.nodes.push(node);
return drawable;
},
/**
* Promote the current edit to a real annotation.
*
* @public
* @method init_from_edit
* @param M.assignfeedback_editpdf.edit edit
*/
init_from_edit : function(edit) {
var bounds = new M.assignfeedback_editpdf.rect();
bounds.bound([edit.start, edit.end]);
if (bounds.width < 40) {
bounds.width = 40;
}
if (bounds.height < 40) {
bounds.height = 40;
}
this.gradeid = this.editor.get('gradeid');
this.pageno = this.editor.currentpage;
this.x = bounds.x;
this.y = bounds.y;
this.endx = bounds.x + bounds.width;
this.endy = bounds.y + bounds.height;
this.colour = edit.annotationcolour;
this.path = edit.stamp;
},
/**
* Move an annotation to a new location.
* @public
* @param int newx
* @param int newy
* @method move_annotation
*/
move : function(newx, newy) {
var diffx = newx - this.x,
diffy = newy - this.y;
this.x += diffx;
this.y += diffy;
this.endx += diffx;
this.endy += diffy;
if (this.drawable) {
this.drawable.erase();
}
this.editor.drawables.push(this.draw());
}
});
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
M.assignfeedback_editpdf.annotationstamp = ANNOTATIONSTAMP;
@@ -0,0 +1,122 @@
var COLOURPICKER_NAME = "Colourpicker",
COLOURPICKER;
/**
* COLOURPICKER
* This is a drop down list of colours.
*
* @namespace M.assignfeedback_editpdf.colourpicker
* @class dropdown
* @constructor
* @extends Y.Base
*/
COLOURPICKER = function(config) {
COLOURPICKER.superclass.constructor.apply(this, [config]);
};
Y.extend(COLOURPICKER, M.assignfeedback_editpdf.dropdown, {
/**
* Initialise the menu.
*
* @method initializer
* @return void
*/
initializer : function(config) {
var colourlist = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>'),
body;
// Build a list of coloured buttons.
Y.each(this.get('colours'), function(rgb, colour) {
var button, listitem, title, img, iconname;
title = M.util.get_string(colour, 'assignfeedback_editpdf');
iconname = this.get('iconprefix') + colour;
img = M.util.image_url(iconname, 'assignfeedback_editpdf');
button = Y.Node.create('<button><img alt="' + title + '" src="' + img + '"/></button>');
button.setAttribute('data-colour', colour);
button.setAttribute('data-rgb', rgb);
button.setStyle('backgroundImage', 'none');
listitem = Y.Node.create('<li/>');
listitem.append(button);
colourlist.append(listitem);
}, this);
body = Y.Node.create('<div/>');
// Set the call back.
colourlist.delegate('click', this.callback_handler, 'button', this);
colourlist.delegate('key', this.callback_handler, 'down:13', 'button', this);
// Set the accessible header text.
this.set('headerText', M.util.get_string('colourpicker', 'assignfeedback_editpdf'));
// Set the body content.
body.append(colourlist);
this.set('bodyContent', body);
COLOURPICKER.superclass.initializer.call(this, config);
},
callback_handler : function(e) {
var callback = this.get('callback'),
callbackcontext = this.get('context'),
bind;
this.hide();
// Call the callback with the specified context.
bind = Y.bind(callback, callbackcontext, e);
bind();
}
}, {
NAME : COLOURPICKER_NAME,
ATTRS : {
/**
* The list of colours this colour picker supports.
*
* @attribute colours
* @type {String: String} (The keys of the array are the colour names and the values are localized strings)
* @default {}
*/
colours : {
value : {}
},
/**
* The function called when a new colour is chosen.
*
* @attribute callback
* @type function
* @default null
*/
callback : {
value : null
},
/**
* The context passed to the callback when a colour is chosen.
*
* @attribute context
* @type Y.Node
* @default null
*/
context : {
value : null
},
/**
* The prefix for the icon image names.
*
* @attribute iconprefix
* @type String
* @default 'colour_'
*/
iconprefix : {
value : 'colour_'
}
}
});
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
M.assignfeedback_editpdf.colourpicker = COLOURPICKER;
+430
View File
@@ -0,0 +1,430 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class representing a list of comments.
*
* @module moodle-assignfeedback_editpdf-editor
*/
/**
* COMMENT
*
* @namespace M.assignfeedback_editpdf
* @class comment
* @param M.assignfeedback_editpdf.editor editor
* @param Int gradeid
* @param Int pageno
* @param Int x
* @param Int y
* @param Int width
* @param String colour
* @param String rawtext
*/
COMMENT = function(editor, gradeid, pageno, x, y, width, colour, rawtext) {
/**
* Reference to M.assignfeedback_editpdf.editor.
* @property editor
* @type M.assignfeedback_editpdf.editor
* @public
*/
this.editor = editor;
/**
* Grade id
* @property gradeid
* @type Int
* @public
*/
this.gradeid = gradeid || 0;
/**
* X position
* @property x
* @type Int
* @public
*/
this.x = parseInt(x, 10) || 0;
/**
* Y position
* @property y
* @type Int
* @public
*/
this.y = parseInt(y, 10) || 0;
/**
* Comment width
* @property width
* @type Int
* @public
*/
this.width = parseInt(width, 10) || 0;
/**
* Comment rawtext
* @property rawtext
* @type String
* @public
*/
this.rawtext = rawtext || '';
/**
* Comment page number
* @property pageno
* @type Int
* @public
*/
this.pageno = pageno || 0;
/**
* Comment background colour.
* @property colour
* @type String
* @public
*/
this.colour = colour || 'yellow';
/**
* Reference to M.assignfeedback_editpdf.drawable
* @property drawable
* @type M.assignfeedback_editpdf.drawable
* @public
*/
this.drawable = false;
/**
* Boolean used by a timeout to delete empty comments after a short delay.
* @property deleteme
* @type Boolean
* @public
*/
this.deleteme = false;
/**
* Reference to the link that opens the menu.
* @property menulink
* @type Y.Node
* @public
*/
this.menulink = null;
/**
* Reference to the dialogue that is the context menu.
* @property menu
* @type M.assignfeedback_editpdf.dropdown
* @public
*/
this.menu = null;
/**
* Clean a comment record, returning an oject with only fields that are valid.
* @public
* @method clean
* @return {}
*/
this.clean = function() {
return {
gradeid : this.gradeid,
x : parseInt(this.x, 10),
y : parseInt(this.y, 10),
width : parseInt(this.width, 10),
rawtext : this.rawtext,
pageno : this.currentpage,
colour : this.colour
};
};
/**
* Draw a comment.
* @public
* @method draw_comment
* @param boolean focus - Set the keyboard focus to the new comment if true
* @return M.assignfeedback_editpdf.drawable
*/
this.draw = function(focus) {
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
node,
drawingregion = Y.one(SELECTOR.DRAWINGREGION),
container,
menu,
position,
scrollheight;
// Lets add a contenteditable div.
node = Y.Node.create('<textarea/>');
container = Y.Node.create('<div class="commentdrawable"/>');
menu = Y.Node.create('<a href="#"><img src="' + M.util.image_url('t/contextmenu', 'core') + '"/></a>');
this.menulink = menu;
container.append(node);
if (!this.editor.get('readonly')) {
container.append(menu);
} else {
node.setAttribute('readonly', 'readonly');
}
if (this.width < 100) {
this.width = 100;
}
position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(this.x, this.y));
node.setStyles({
width: this.width + 'px',
backgroundColor: COMMENTCOLOUR[this.colour]
});
drawingregion.append(container);
container.setX(position.x);
container.setY(position.y);
drawable.nodes.push(container);
node.set('value', this.rawtext);
scrollheight = node.get('scrollHeight'),
node.setStyles({
'height' : scrollheight + 'px',
'overflow': 'hidden'
});
if (!this.editor.get('readonly')) {
this.attach_events(node, menu);
}
if (focus) {
node.focus();
}
this.drawable = drawable;
return drawable;
};
/**
* Delete an empty comment if it's menu hasn't been opened in time.
* @method delete_comment_later
*/
this.delete_comment_later = function() {
if (this.deleteme) {
this.remove();
}
};
/**
* Comment nodes have a bunch of event handlers attached to them directly.
* This is all done here for neatness.
*
* @protected
* @method attach_comment_events
* @param node - The Y.Node representing the comment.
* @param menu - The Y.Node representing the menu.
*/
this.attach_events = function(node, menu) {
// Save the text on blur.
node.on('blur', function() {
// Save the changes back to the comment.
this.rawtext = node.get('value');
this.width = parseInt(node.getStyle('width'), 10);
// Trim.
if (this.rawtext.replace(/^\s+|\s+$/g, "") === '') {
// Delete empty comments.
this.deleteme = true;
Y.later(400, this, this.delete_comment_later);
}
this.editor.save_current_page();
}, this);
// For delegated event handler.
menu.setData('comment', this);
node.on('keyup', function() {
var scrollheight = node.get('scrollHeight'),
height = parseInt(node.getStyle('height'), 10);
// Webkit scrollheight fix.
if (scrollheight === height + 8) {
scrollheight -= 8;
}
node.setStyle('height', scrollheight + 'px');
});
node.on('gesturemovestart', function(e) {
node.setData('dragging', true);
node.setData('offsetx', e.clientX - node.getX());
node.setData('offsety', e.clientY - node.getY());
});
node.on('gesturemoveend', function() {
node.setData('dragging', false);
this.editor.save_current_page();
}, null, this);
node.on('gesturemove', function(e) {
var x = e.clientX - node.getData('offsetx'),
y = e.clientY - node.getData('offsety'),
nodewidth,
nodeheight,
newlocation,
windowlocation,
bounds;
nodewidth = parseInt(node.getStyle('width'), 10);
nodeheight = parseInt(node.getStyle('height'), 10);
newlocation = this.editor.get_canvas_coordinates(new M.assignfeedback_editpdf.point(x, y));
bounds = this.editor.get_canvas_bounds(true);
bounds.x = 0;
bounds.y = 0;
bounds.width -= nodewidth + 42;
bounds.height -= nodeheight + 8;
// Clip to the window size - the comment size.
newlocation.clip(bounds);
this.x = newlocation.x;
this.y = newlocation.y;
windowlocation = this.editor.get_window_coordinates(newlocation);
node.ancestor().setX(windowlocation.x);
node.ancestor().setY(windowlocation.y);
}, null, this);
this.menu = new M.assignfeedback_editpdf.commentmenu({
buttonNode: this.menulink,
comment: this
});
};
/**
* Delete a comment.
* @method remove
*/
this.remove = function() {
var i = 0, comments;
comments = this.editor.pages[this.editor.currentpage].comments;
for (i = 0; i < comments.length; i++) {
if (comments[i] === this) {
comments.splice(i, 1);
this.drawable.erase();
this.editor.save_current_page();
return;
}
}
};
/**
* Event handler to remove a comment from the users quicklist.
*
* @protected
* @method remove_from_quicklist
*/
this.remove_from_quicklist = function(e, quickcomment) {
this.menu.hide();
this.editor.quicklist.remove(quickcomment);
};
/**
* A quick comment was selected in the list, update the active comment and redraw the page.
*
* @param Event e
* @protected
* @method set_from_quick_comment
*/
this.set_from_quick_comment = function(e, quickcomment) {
this.menu.hide();
this.rawtext = quickcomment.rawtext;
this.width = quickcomment.width;
this.colour = quickcomment.colour;
this.editor.save_current_page();
this.editor.redraw();
};
/**
* Event handler to add a comment to the users quicklist.
*
* @protected
* @method add_to_quicklist
*/
this.add_to_quicklist = function() {
this.menu.hide();
this.editor.quicklist.add(this);
};
/**
* Draw the in progress edit.
*
* @public
* @method draw_current_edit
* @param M.assignfeedback_editpdf.edit edit
*/
this.draw_current_edit = function(edit) {
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
shape,
bounds;
bounds = new M.assignfeedback_editpdf.rect();
bounds.bound([edit.start, edit.end]);
// We will draw a box with the current background colour.
shape = this.editor.graphic.addShape({
type: Y.Rect,
width: bounds.width,
height: bounds.height,
fill: {
color: COMMENTCOLOUR[edit.commentcolour]
},
x: bounds.x,
y: bounds.y
});
drawable.shapes.push(shape);
return drawable;
};
/**
* Promote the current edit to a real comment.
*
* @public
* @method init_from_edit
* @param M.assignfeedback_editpdf.edit edit
*/
this.init_from_edit = function(edit) {
var bounds = new M.assignfeedback_editpdf.rect();
bounds.bound([edit.start, edit.end]);
// Minimum comment width.
if (bounds.width < 100) {
bounds.width = 100;
}
// Save the current edit to the server and the current page list.
this.gradeid = this.editor.get('gradeid');
this.pageno = this.editor.currentpage;
this.x = bounds.x;
this.y = bounds.y;
this.width = bounds.width;
this.colour = edit.commentcolour;
this.rawtext = '';
};
};
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
M.assignfeedback_editpdf.comment = COMMENT;
@@ -0,0 +1,113 @@
var COMMENTMENUNAME = "Commentmenu",
COMMENTMENU;
/**
* COMMENTMENU
* This is a drop down list of comment context functions.
*
* @namespace M.assignfeedback_editpdf.editor
* @class commentmenu
* @constructor
* @extends Y.Base
*/
COMMENTMENU = function(config) {
COMMENTMENU.superclass.constructor.apply(this, [config]);
};
Y.extend(COMMENTMENU, M.assignfeedback_editpdf.dropdown, {
/**
* Initialise the menu.
*
* @method initializer
* @return void
*/
initializer : function(config) {
var commentlinks,
link,
body,
comment;
comment = this.get('comment');
// Build the list of menu items.
commentlinks = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>');
link = Y.Node.create('<li><a tabindex="-1" href="#">' + M.util.get_string('addtoquicklist', 'assignfeedback_editpdf') + '</a></li>');
link.on('click', comment.add_to_quicklist, comment);
link.on('key', comment.add_to_quicklist, 'enter,space', comment);
commentlinks.append(link);
link = Y.Node.create('<li><a tabindex="-1" href="#">' + M.util.get_string('deletecomment', 'assignfeedback_editpdf') + '</a></li>');
link.on('click', function() { comment.menu.hide(); comment.remove(); }, comment);
link.on('key', function() { comment.menu.hide(); comment.remove(); }, 'enter,space', comment);
commentlinks.append(link);
link = Y.Node.create('<li><hr/></li>');
commentlinks.append(link);
// Set the accessible header text.
this.set('headerText', M.util.get_string('commentcontextmenu', 'assignfeedback_editpdf'));
body = Y.Node.create('<div/>');
// Set the body content.
body.append(commentlinks);
this.set('bodyContent', body);
COMMENTMENU.superclass.initializer.call(this, config);
},
/**
* Show the menu.
*
* @method show
* @return void
*/
show : function() {
var commentlinks = this.get('boundingBox').one('ul');
commentlinks.all('.quicklist_comment').remove(true),
comment = this.get('comment');
// Now build the list of quicklist comments.
Y.each(comment.editor.quicklist.comments, function(quickcomment) {
var listitem = Y.Node.create('<li class="quicklist_comment"></li>'),
linkitem = Y.Node.create('<a href="#" tabindex="-1">' + quickcomment.rawtext + '</a>'),
deletelinkitem = Y.Node.create('<a href="#" tabindex="-1" class="delete_quicklist_comment">' +
'<img src="' + M.util.image_url('t/delete', 'core') + '" ' +
'alt="' + M.util.get_string('deletecomment', 'assignfeedback_editpdf') + '"/>' +
'</a>');
listitem.append(linkitem);
listitem.append(deletelinkitem);
commentlinks.append(listitem);
linkitem.on('click', comment.set_from_quick_comment, comment, quickcomment);
linkitem.on('key', comment.set_from_quick_comment, 'space,enter', comment, quickcomment);
deletelinkitem.on('click', comment.remove_from_quicklist, comment, quickcomment);
deletelinkitem.on('key', comment.remove_from_quicklist, 'space,enter', comment, quickcomment);
}, this);
COMMENTMENU.superclass.show.call(this);
}
}, {
NAME : COMMENTMENUNAME,
ATTRS : {
/**
* The comment this menu is attached to.
*
* @attribute comment
* @type M.assignfeedback_editpdf.comment
* @default null
*/
comment : {
value : null
}
}
});
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
M.assignfeedback_editpdf.commentmenu = COMMENTMENU;
@@ -0,0 +1,153 @@
var COMMENTSEARCHNAME = "commentsearch",
COMMENTSEARCH;
/**
* COMMENTSEARCH
* This is a searchable dialogue of comments.
*
* @namespace M.assignfeedback_editpdf.editor
* @class commentsearch
* @constructor
* @extends Y.Base
*/
COMMENTSEARCH = function(config) {
config.draggable = false;
config.centered = true;
config.width = '400px';
config.lightbox = true;
config.visible = false;
config.headerContent = M.util.get_string('searchcomments', 'assignfeedback_editpdf');
config.zIndex = 100;
config.footerContent = '';
COMMENTSEARCH.superclass.constructor.apply(this, [config]);
};
Y.extend(COMMENTSEARCH, M.core.dialogue, {
/**
* Initialise the menu.
*
* @method initializer
* @return void
*/
initializer : function(config) {
var editor,
container,
placeholder,
commentfilter,
commentlist,
bb;
bb = this.get('boundingBox');
bb.addClass('assignfeedback_editpdf_commentsearch');
editor = this.get('editor');
container = Y.Node.create('<div/>');
placeholder = M.util.get_string('filter', 'assignfeedback_editpdf');
commentfilter = Y.Node.create('<input type="text" size="20" placeholder="' + placeholder + '"/>');
container.append(commentfilter);
commentlist = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>');
container.append(commentlist);
commentfilter.on('keyup', this.filter_search_comments, null, this);
commentlist.delegate('click', this.focus_on_comment, 'a', this);
commentlist.delegate('key', this.focus_on_comment, 'enter,space', 'a', this);
// Set the body content.
this.set('bodyContent', container);
COMMENTSEARCH.superclass.initializer.call(this, config);
},
/**
* Event handler to filter the list of comments.
*
* @protected
* @method filter_search_comments
*/
filter_search_comments : function() {
var filternode,
commentslist,
filtertext;
filternode = Y.one(SELECTOR.SEARCHFILTER);
commentslist = Y.one(SELECTOR.SEARCHCOMMENTSLIST);
filtertext = filternode.get('value');
commentslist.all('li').each(function (node) {
if (node.get('text').indexOf(filtertext) !== -1) {
node.show();
} else {
node.hide();
}
});
},
/**
* Event handler to focus on a selected comment.
*
* @param Event e
* @protected
* @method focus_on_comment
*/
focus_on_comment : function(e) {
var target = e.target.ancestor('li'),
comment = target.getData('comment'),
editor = this.get('editor');
this.hide();
if (comment.pageno === editor.currentpage) {
comment.drawable.nodes[0].one('textarea').focus();
} else {
// Comment is on a different page.
editor.currentpage = comment.pageno;
editor.change_page();
comment.drawable.nodes[0].one('textarea').focus();
}
},
/**
* Show the menu.
*
* @method show
* @return void
*/
show : function() {
var commentlist = this.get('boundingBox').one('ul'),
editor = this.get('editor');
commentlist.all('li').remove(true);
// Rebuild the latest list of comments.
Y.each(editor.pages, function(page) {
Y.each(page.comments, function(comment) {
var commentnode = Y.Node.create('<li><a href="#" tabindex="-1"><pre>' + comment.rawtext + '</pre></a></li>');
commentlist.append(commentnode);
commentnode.setData('comment', comment);
}, this);
}, this);
this.centerDialogue();
COMMENTSEARCH.superclass.show.call(this);
}
}, {
NAME : COMMENTSEARCHNAME,
ATTRS : {
/**
* The editor this search window is attached to.
*
* @attribute editor
* @type M.assignfeedback_editpdf.editor
* @default null
*/
editor : {
value : null
}
}
});
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
M.assignfeedback_editpdf.commentsearch = COMMENTSEARCH;
@@ -0,0 +1,77 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class representing a drawable thing which contains both
* Y.Nodes, and Y.Shapes.
*
* @module moodle-assignfeedback_editpdf-editor
*/
/**
* DRAWABLE
*
* @namespace M.assignfeedback_editpdf
* @param M.assignfeedback_editpdf.editor editor
* @class drawable
*/
DRAWABLE = function(editor) {
/**
* Reference to M.assignfeedback_editpdf.editor.
* @property editor
* @type M.assignfeedback_editpdf.editor
* @public
*/
this.editor = editor;
/**
* Array of Y.Shape
* @property shapes
* @type Y.Shape[]
* @public
*/
this.shapes = [];
/**
* Array of Y.Node
* @property nodes
* @type Y.Node[]
* @public
*/
this.nodes = [];
/**
* Delete the shapes from the drawable.
* @protected
* @method erase_drawable
*/
this.erase = function() {
if (this.shapes) {
while (this.shapes.length > 0) {
this.editor.graphic.removeShape(this.shapes.pop());
}
}
if (this.nodes) {
while (this.nodes.length > 0) {
this.nodes.pop().remove();
}
}
};
};
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
M.assignfeedback_editpdf.drawable = DRAWABLE;

Some files were not shown because too many files have changed in this diff Show More