From 30a11c7ee4ffc54767a16ebe81156c2ca67fc7ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luca=20B=C3=B6sch?= Date: Wed, 24 May 2023 09:40:18 +0200 Subject: [PATCH] Correctly include privacy provider fixture files. --- .github/workflows/moodle-plugin-ci.yml | 13 + .../moodle2/backup_pdfannotator_stepslib.php | 2 +- classes/output/comment.php | 130 +- classes/subscriptions.php | 884 +++++++++ db/install.xml | 1 + db/upgrade.php | 14 + lang/en/pdfannotator.php | 1031 +++++----- lib.php | 1683 +++++++++-------- mod_form.php | 28 +- templates/comment.mustache | 90 + tests/.DS_Store | Bin 0 -> 6148 bytes tests/behat/add_pdfannotator.feature | 31 + tests/behat/annotate_pdfannotator.feature | 114 ++ tests/behat/behat_pdfannotator_editpdf.php | 59 + tests/fixtures/submission.pdf | Bin 0 -> 24751 bytes tests/privacy/provider_test.php | 12 +- version.php | 2 +- 17 files changed, 2704 insertions(+), 1390 deletions(-) create mode 100644 classes/subscriptions.php create mode 100644 tests/.DS_Store create mode 100644 tests/behat/add_pdfannotator.feature create mode 100644 tests/behat/annotate_pdfannotator.feature create mode 100644 tests/behat/behat_pdfannotator_editpdf.php create mode 100644 tests/fixtures/submission.pdf diff --git a/.github/workflows/moodle-plugin-ci.yml b/.github/workflows/moodle-plugin-ci.yml index bf554fa..fdc3274 100644 --- a/.github/workflows/moodle-plugin-ci.yml +++ b/.github/workflows/moodle-plugin-ci.yml @@ -28,6 +28,16 @@ jobs: fail-fast: false matrix: include: +<<<<<<< HEAD +======= + - php: 8.1 + moodle-branch: MOODLE_401_STABLE + database: pgsql + - php: 8.1 + moodle-branch: MOODLE_401_STABLE + database: mariadb + +>>>>>>> 123e1fd (Pdfannotator comment subscription like forum fixes #20.) - php: 8.0 moodle-branch: MOODLE_401_STABLE database: pgsql @@ -35,6 +45,7 @@ jobs: moodle-branch: MOODLE_401_STABLE database: mariadb +<<<<<<< HEAD - php: 8.0 moodle-branch: MOODLE_400_STABLE database: pgsql @@ -42,6 +53,8 @@ jobs: moodle-branch: MOODLE_400_STABLE database: mariadb +======= +>>>>>>> 123e1fd (Pdfannotator comment subscription like forum fixes #20.) - php: 7.4 moodle-branch: MOODLE_401_STABLE database: pgsql diff --git a/backup/moodle2/backup_pdfannotator_stepslib.php b/backup/moodle2/backup_pdfannotator_stepslib.php index bd0cdec..441022b 100644 --- a/backup/moodle2/backup_pdfannotator_stepslib.php +++ b/backup/moodle2/backup_pdfannotator_stepslib.php @@ -55,7 +55,7 @@ protected function define_structure() { // 2. Define each element separately. $pdfannotator = new backup_nested_element('pdfannotator', array('id'), array( 'name', 'intro', 'introformat', 'usevotes', 'useprint', 'useprintcomments', 'use_studenttextbox', 'use_studentdrawing', - 'useprivatecomments', 'useprotectedcomments', 'timecreated', 'timemodified')); + 'useprivatecomments', 'useprotectedcomments', 'forcesubscribe', 'timecreated', 'timemodified')); $annotations = new backup_nested_element('annotations'); $annotation = new backup_nested_element('annotation', array('id'), array('page', 'userid', 'annotationtypeid', diff --git a/classes/output/comment.php b/classes/output/comment.php index e37ca65..cea7566 100644 --- a/classes/output/comment.php +++ b/classes/output/comment.php @@ -29,16 +29,22 @@ */ class comment implements \renderable, \templatable { + /** + * @var array An array of comments + */ private $comments = []; + + /** + * @var bool Visibility of a question + */ private $questionvisibility; /** * Constructor of renderable for comments. * - * @param object $data Comment or array of comments - * @param object $cm Course module + * @param stdClass $data Comment or array of comments + * @param stdClass $cm course module object * @param object $context Context - * @return type */ public function __construct($data, $cm, $context) { global $USER; @@ -46,7 +52,6 @@ public function __construct($data, $cm, $context) { if (!is_array($data)) { $data = [$data]; } - $report = has_capability('mod/pdfannotator:report', $context); $closequestion = has_capability('mod/pdfannotator:closequestion', $context); $closeanyquestion = has_capability('mod/pdfannotator:closeanyquestion', $context); @@ -83,7 +88,7 @@ public function __construct($data, $cm, $context) { $this->addeditbutton($comment, $editanypost); $this->addhidebutton($comment, $seehiddencomments, $hidecomments); $this->adddeletebutton($comment, $deleteown, $deleteany); - $this->addsubscribebutton($comment, $subscribe); + $this->addsubscribebutton($comment, $subscribe, $cm); $this->addforwardbutton($comment, $forwardquestions, $cm); $this->addmarksolvedbutton($comment, $solve); @@ -94,8 +99,8 @@ public function __construct($data, $cm, $context) { } if (!empty($comment->modifiedby) && ($comment->modifiedby != $comment->userid) && ($comment->userid != 0)) { - $comment->modifiedby = get_string('modifiedby', 'pdfannotator') . ' '. - pdfannotator_get_username($comment->modifiedby); + $comment->modifiedby = get_string('modifiedby', 'pdfannotator') . ' ' . + pdfannotator_get_username($comment->modifiedby); } else { $comment->modifiedby = null; } @@ -112,8 +117,9 @@ public function __construct($data, $cm, $context) { /** * This function is required by any renderer to retrieve the data structure * passed into the template. + * * @param \renderer_base $output - * @return type + * @return stdClass */ public function export_for_template(\renderer_base $output) { $data = []; @@ -122,6 +128,12 @@ public function export_for_template(\renderer_base $output) { return $data; } + /** + * Add css class to a comment + * + * @param object $comment + * @param bool $owner + */ private function addcssclasses($comment, $owner) { $comment->wrapperClass = 'chat-message comment-list-item'; if ($comment->isquestion) { @@ -137,6 +149,12 @@ private function addcssclasses($comment, $owner) { } } + /** + * Set votes to a comment + * + * @param object $comment + * @throws \coding_exception + */ public function setvotes($comment) { if ($comment->usevotes && !$comment->isdeleted) { if ($comment->owner) { @@ -164,7 +182,8 @@ public function setvotes($comment) { /** * Add check icon if comment is marked as correct. - * @param type $comment + * + * @param object $comment */ public function addsolvedicon($comment) { if ($comment->solved) { @@ -180,9 +199,10 @@ public function addsolvedicon($comment) { /** * Report comment if user is not the owner. - * @param type $comment - * @param type $owner - * @param type $report + * + * @param object $comment + * @param bool $report + * @param stdClass $cm course module object */ private function addreportbutton($comment, $report, $cm) { if (!$comment->isdeleted && $report && !$comment->owner && !isset($comment->type)) { @@ -194,10 +214,11 @@ private function addreportbutton($comment, $report, $cm) { /** * Open/close question if user is owner of the question or manager. - * @param type $comment - * @param type $owner - * @param type $closequestion - * @param type $closeanyquestion + * + * @param object $comment + * @param bool $closequestion + * @param bool $closeanyquestion + * @throws \coding_exception */ private function addcloseopenbutton($comment, $closequestion, $closeanyquestion) { @@ -216,9 +237,10 @@ private function addcloseopenbutton($comment, $closequestion, $closeanyquestion) /** * Button for editing comment if user is owner of the comment or manager. - * @param type $comment - * @param type $owner - * @param type $editanypost + * + * @param object $comment + * @param bool $editanypost + * @throws \coding_exception */ private function addeditbutton($comment, $editanypost) { if (!$comment->isdeleted && !isset($comment->type) && ($comment->owner || $editanypost)) { @@ -229,6 +251,14 @@ private function addeditbutton($comment, $editanypost) { } } + /** + * Add a hide button + * + * @param object $comment + * @param bool $seehiddencomments + * @param bool $hidecomments + * @throws \coding_exception + */ private function addhidebutton($comment, $seehiddencomments, $hidecomments) { // Don't need to hide personal notes. if ($this->questionvisibility == 'private') { @@ -257,32 +287,53 @@ private function addhidebutton($comment, $seehiddencomments, $hidecomments) { /** * Delete comment if user is owner of the comment or manager. - * @param type $comment - * @param type $owner - * @param type $deleteown - * @param type $deleteany + * + * @param object $comment + * @param bool $deleteown + * @param bool $deleteany + * @throws \coding_exception */ private function adddeletebutton($comment, $deleteown, $deleteany) { if (!$comment->isdeleted && ($deleteany || ($deleteown && $comment->owner))) { $comment->buttons[] = ["classes" => "comment-delete-a", "text" => get_string('delete', 'pdfannotator'), "moodleicon" => ["key" => "delete", "component" => "pdfannotator", - "title" => get_string('delete', 'pdfannotator')]]; + "title" => get_string('delete', 'pdfannotator')]]; } } - private function addsubscribebutton($comment, $subscribe) { + /** + * Add a subscribe button + * + * @param object $comment + * @param bool $subscribe + * @param stdClass $cm course module object + * @throws \coding_exception + */ + private function addsubscribebutton($comment, $subscribe, $cm) { if (!isset($comment->type) && $comment->isquestion && $subscribe && $comment->visibility != 'private') { - // Only set for textbox and drawing. - if (!empty($comment->issubscribed)) { - $comment->buttons[] = ["classes" => "comment-subscribe-a", "faicon" => ["class" => "fa-bell-slash"], - "text" => get_string('unsubscribeQuestion', 'pdfannotator')]; - } else { - $comment->buttons[] = ["classes" => "comment-subscribe-a", "faicon" => ["class" => "fa-bell"], - "text" => get_string('subscribeQuestion', 'pdfannotator')]; + // Only set for textbox and drawing, and only if subscription mode is not disabled or forced. + if ((pdfannotator_get_subscriptionmode($cm->instance) == PDFANNOTATOR_CHOOSESUBSCRIBE) || + (pdfannotator_get_subscriptionmode($cm->instance) == PDFANNOTATOR_INITIALSUBSCRIBE)) { + if (!empty($comment->issubscribed)) { + $comment->buttons[] = ["classes" => "comment-subscribe-a", "faicon" => ["class" => "fa-bell-slash"], + "text" => get_string('unsubscribeQuestion', 'pdfannotator')]; + } else { + $comment->buttons[] = ["classes" => "comment-subscribe-a", "faicon" => ["class" => "fa-bell"], + "text" => get_string('subscribeQuestion', 'pdfannotator')]; + } } } } + /** + * Add a forward button + * + * @param object $comment + * @param bool $forwardquestions + * @param stdClass $cm course module object + * @throws \coding_exception + * @throws \moodle_exception + */ private function addforwardbutton($comment, $forwardquestions, $cm) { if (!isset($comment->type) && $comment->isquestion && !$comment->isdeleted && $forwardquestions && $comment->visibility != 'private') { @@ -291,22 +342,29 @@ private function addforwardbutton($comment, $forwardquestions, $cm) { $url = new moodle_url($CFG->wwwroot . '/mod/pdfannotator/view.php', $urlparams); $comment->buttons[] = ["classes" => "comment-forward-a", "attributes" => ["name" => "onclick", - "value" => "window.location.href = '$url';"], "faicon" => ["class" => "fa-share"], - "text" => get_string('forward', 'pdfannotator')]; + "value" => "window.location.href = '$url';"], + "faicon" => ["class" => "fa-share"], "text" => get_string('forward', 'pdfannotator')]; } } + /** + * Add a Mark as correct or a Remove mark as correct button + * + * @param object $comment + * @param bool $solve + * @throws \coding_exception + */ private function addmarksolvedbutton($comment, $solve) { if ($solve && !$comment->isquestion && !$comment->isdeleted && !isset($comment->type) && $this->questionvisibility != 'private') { if ($comment->solved) { $comment->buttons[] = ["classes" => "comment-solve-a", "text" => get_string('removeCorrect', 'pdfannotator'), "moodleicon" => ["key" => "i/completion-manual-n", "component" => "core", - "title" => get_string('removeCorrect', 'pdfannotator')]]; + "title" => get_string('removeCorrect', 'pdfannotator')]]; } else { $comment->buttons[] = ["classes" => "comment-solve-a", "text" => get_string('markCorrect', 'pdfannotator'), "moodleicon" => ["key" => "i/completion-manual-enabled", "component" => "core", - "title" => get_string('markCorrect', 'pdfannotator')]]; + "title" => get_string('markCorrect', 'pdfannotator')]]; } } } diff --git a/classes/subscriptions.php b/classes/subscriptions.php new file mode 100644 index 0000000..65ef799 --- /dev/null +++ b/classes/subscriptions.php @@ -0,0 +1,884 @@ +. + +/** + * Pdfannotator subscription manager. + * + * @package mod_pdfannotator + * @copyright 2021 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_pdfannotator; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Pdfannotator subscription manager. + * + * @copyright 2021 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class subscriptions { + + /** + * The status value for an unsubscribed discussion. + * + * @var int + */ + const PDFANNOTATOR_DISCUSSION_UNSUBSCRIBED = -1; + + /** + * The subscription cache for pdfannotators. + * + * The first level key is the user ID + * The second level is the pdfannotator ID + * The Value then is bool for subscribed of not. + * + * @var array[] An array of arrays. + */ + protected static $pdfannotatorcache = array(); + + /** + * The list of pdfannotators which have been wholly retrieved for the pdfannotator subscription cache. + * + * This allows for prior caching of an entire pdfannotator to reduce the + * number of DB queries in a subscription check loop. + * + * @var bool[] + */ + protected static $fetchedpdfannotators = array(); + + /** + * The subscription cache for pdfannotator discussions. + * + * The first level key is the user ID + * The second level is the pdfannotator ID + * The third level key is the discussion ID + * The value is then the users preference (int) + * + * @var array[] + */ + protected static $pdfannotatordiscussioncache = array(); + + /** + * The list of pdfannotators which have been wholly retrieved for the pdfannotator discussion subscription cache. + * + * This allows for prior caching of an entire pdfannotator to reduce the + * number of DB queries in a subscription check loop. + * + * @var bool[] + */ + protected static $discussionfetchedpdfannotators = array(); + + /** + * Whether a user is subscribed to this pdfannotator, or a discussion within + * the pdfannotator. + * + * If a discussion is specified, then report whether the user is + * subscribed to posts to this particular discussion, taking into + * account the pdfannotator preference. + * + * If it is not specified then only the pdfannotator preference is considered. + * + * @param int $userid The user ID + * @param \stdClass $pdfannotator The record of the pdfannotator to test + * @param int $discussionid The ID of the discussion to check + * @param object $cm The coursemodule record. If not supplied, this will be calculated using get_fast_modinfo instead. + * @return bool + * @throws \coding_exception + * @throws \moodle_exception + */ + public static function is_subscribed($userid, $pdfannotator, $discussionid = null, $cm = null) { + // If pdfannotator is force subscribed and has allowforcesubscribe, then user is subscribed. + if (self::is_forcesubscribed($pdfannotator)) { + if (!$cm) { + $cm = get_fast_modinfo($pdfannotator->course)->instances['pdfannotator'][$pdfannotator->id]; + } + if (has_capability('mod/pdfannotator:allowforcesubscribe', \context_module::instance($cm->id), $userid)) { + return true; + } + } + + if ($discussionid === null) { + return self::is_subscribed_to_pdfannotator($userid, $pdfannotator); + } + + $subscriptions = self::fetch_discussion_subscription($pdfannotator->id, $userid); + + // Check whether there is a record for this discussion subscription. + if (isset($subscriptions[$discussionid])) { + return ($subscriptions[$discussionid] != self::PDFANNOTATOR_DISCUSSION_UNSUBSCRIBED); + } + + return self::is_subscribed_to_pdfannotator($userid, $pdfannotator); + } + + /** + * Whether a user is subscribed to this pdfannotator. + * + * @param int $userid The user ID + * @param \stdClass $pdfannotator The record of the pdfannotator to test + * @return boolean + */ + protected static function is_subscribed_to_pdfannotator($userid, $pdfannotator) { + return self::fetch_subscription_cache($pdfannotator->id, $userid); + } + + /** + * Helper to determine whether a pdfannotator has it's subscription mode set + * to forced subscription. + * + * @param \stdClass $pdfannotator The record of the pdfannotator to test + * @return bool + */ + public static function is_forcesubscribed($pdfannotator) { + return ($pdfannotator->forcesubscribe == pdfannotator_FORCESUBSCRIBE); + } + + /** + * Helper to determine whether a pdfannotator has it's subscription mode set to disabled. + * + * @param \stdClass $pdfannotator The record of the pdfannotator to test + * @return bool + */ + public static function subscription_disabled($pdfannotator) { + return ($pdfannotator->forcesubscribe == pdfannotator_DISALLOWSUBSCRIBE); + } + + /** + * Helper to determine whether the specified pdfannotator can be subscribed to. + * + * @param \stdClass $pdfannotator The record of the pdfannotator to test + * @return bool + */ + public static function is_subscribable($pdfannotator) { + return (isloggedin() && !isguestuser() && + !self::is_forcesubscribed($pdfannotator) && + !self::subscription_disabled($pdfannotator)); + } + + /** + * Set the pdfannotator subscription mode. + * + * By default when called without options, this is set to PDFANNOTATOR_FORCESUBSCRIBE. + * + * @param \stdClass $pdfannotatorid The id of the pdfannotator to set the state + * @param int $status The new subscription state + * @return bool + * @throws \dml_exception + */ + public static function set_subscription_mode($pdfannotatorid, $status = 1) { + global $DB; + return $DB->set_field("pdfannotator", "forcesubscribe", $status, array("id" => $pdfannotatorid)); + } + + /** + * Returns the current subscription mode for the pdfannotator. + * + * @param \stdClass $pdfannotator The record of the pdfannotator to set + * @return int The pdfannotator subscription mode + */ + public static function get_subscription_mode($pdfannotator) { + return $pdfannotator->forcesubscribe; + } + + /** + * Returns an array of pdfannotators that the current user is subscribed to and is allowed to unsubscribe from + * + * @return array An array of unsubscribable pdfannotators + */ + public static function get_unsubscribable_pdfannotators() { + global $USER, $DB; + + // Get courses that $USER is enrolled in and can see. + $courses = enrol_get_my_courses(); + if (empty($courses)) { + return array(); + } + + $courseids = array(); + foreach ($courses as $course) { + $courseids[] = $course->id; + } + list($coursesql, $courseparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'c'); + + // Get all pdfannotators from the user's courses that they are subscribed to and which are not set to forced. + // It is possible for users to be subscribed to a pdfannotator in subscription disallowed mode so they must be listed + // here so that that can be unsubscribed from. + $sql = "SELECT f.id, cm.id as cm, cm.visible, f.course + FROM {pdfannotator} f + JOIN {course_modules} cm ON cm.instance = f.id + JOIN {modules} m ON m.name = :modulename AND m.id = cm.module + LEFT JOIN {pdfannotator_subscriptions} fs ON (fs.pdfannotator = f.id AND fs.userid = :userid) + WHERE f.forcesubscribe <> :forcesubscribe + AND fs.id IS NOT NULL + AND cm.course + $coursesql"; + $params = array_merge($courseparams, array( + 'modulename' => 'pdfannotator', + 'userid' => $USER->id, + 'forcesubscribe' => pdfannotator_FORCESUBSCRIBE, + )); + $pdfannotators = $DB->get_recordset_sql($sql, $params); + + $unsubscribablepdfannotators = array(); + foreach ($pdfannotators as $pdfannotator) { + if (empty($pdfannotator->visible)) { + // The pdfannotator is hidden - check if the user can view the pdfannotator. + $context = \context_module::instance($pdfannotator->cm); + if (!has_capability('moodle/course:viewhiddenactivities', $context)) { + // The user can't see the hidden pdfannotator to cannot unsubscribe. + continue; + } + } + + $unsubscribablepdfannotators[] = $pdfannotator; + } + $pdfannotators->close(); + + return $unsubscribablepdfannotators; + } + + /** + * Get the list of potential subscribers to a pdfannotator. + * + * @param context_module $context the pdfannotator context. + * @param integer $groupid the id of a group, or 0 for all groups. + * @param string $fields the list of fields to return for each user. As for get_users_by_capability. + * @param string $sort sort order. As for get_users_by_capability. + * @return array list of users. + */ + public static function get_potential_subscribers($context, $groupid, $fields, $sort = '') { + global $DB; + + // Only active enrolled users or everybody on the frontpage. + list($esql, $params) = get_enrolled_sql($context, 'mod/pdfannotator:allowforcesubscribe', $groupid, true); + if (!$sort) { + list($sort, $sortparams) = users_order_by_sql('u'); + $params = array_merge($params, $sortparams); + } + + $sql = "SELECT $fields + FROM {user} u + JOIN ($esql) je ON je.id = u.id + WHERE u.auth <> 'nologin' AND u.suspended = 0 AND u.confirmed = 1 + ORDER BY $sort"; + + return $DB->get_records_sql($sql, $params); + } + + /** + * Fetch the pdfannotator subscription data for the specified userid and pdfannotator. + * + * @param int $pdfannotatorid The pdfannotator to retrieve a cache for + * @param int $userid The user ID + * @return boolean + */ + public static function fetch_subscription_cache($pdfannotatorid, $userid) { + if (isset(self::$pdfannotatorcache[$userid]) && isset(self::$pdfannotatorcache[$userid][$pdfannotatorid])) { + return self::$pdfannotatorcache[$userid][$pdfannotatorid]; + } + self::fill_subscription_cache($pdfannotatorid, $userid); + + if (!isset(self::$pdfannotatorcache[$userid]) || !isset(self::$pdfannotatorcache[$userid][$pdfannotatorid])) { + return false; + } + + return self::$pdfannotatorcache[$userid][$pdfannotatorid]; + } + + /** + * Fill the pdfannotator subscription data for the specified userid and pdfannotator. + * + * If the userid is not specified, then all subscription data for that pdfannotator is fetched in a single query and used + * for subsequent lookups without requiring further database queries. + * + * @param int $pdfannotatorid The pdfannotator to retrieve a cache for + * @param int $userid The user ID + * @return void + */ + public static function fill_subscription_cache($pdfannotatorid, $userid = null) { + global $DB; + + if (!isset(self::$fetchedpdfannotators[$pdfannotatorid])) { + // This pdfannotator has not been fetched as a whole. + if (isset($userid)) { + if (!isset(self::$pdfannotatorcache[$userid])) { + self::$pdfannotatorcache[$userid] = array(); + } + + if (!isset(self::$pdfannotatorcache[$userid][$pdfannotatorid])) { + if ($DB->record_exists('pdfannotator_subscriptions', array( + 'userid' => $userid, + 'pdfannotator' => $pdfannotatorid, + ))) { + self::$pdfannotatorcache[$userid][$pdfannotatorid] = true; + } else { + self::$pdfannotatorcache[$userid][$pdfannotatorid] = false; + } + } + } else { + $subscriptions = $DB->get_recordset('pdfannotator_subscriptions', array( + 'pdfannotator' => $pdfannotatorid, + ), '', 'id, userid'); + foreach ($subscriptions as $id => $data) { + if (!isset(self::$pdfannotatorcache[$data->userid])) { + self::$pdfannotatorcache[$data->userid] = array(); + } + self::$pdfannotatorcache[$data->userid][$pdfannotatorid] = true; + } + self::$fetchedpdfannotators[$pdfannotatorid] = true; + $subscriptions->close(); + } + } + } + + /** + * Fill the pdfannotator subscription data for all pdfannotators that the specified userid can subscribe to in the specified + * course. + * + * @param int $courseid The course to retrieve a cache for + * @param int $userid The user ID + * @return void + */ + public static function fill_subscription_cache_for_course($courseid, $userid) { + global $DB; + + if (!isset(self::$pdfannotatorcache[$userid])) { + self::$pdfannotatorcache[$userid] = array(); + } + + $sql = "SELECT + f.id AS pdfannotatorid, + s.id AS subscriptionid + FROM {pdfannotator} f + LEFT JOIN {pdfannotator_subscriptions} s ON (s.pdfannotator = f.id AND s.userid = :userid) + WHERE f.course = :course + AND f.forcesubscribe <> :subscriptionforced"; + + $subscriptions = $DB->get_recordset_sql($sql, array( + 'course' => $courseid, + 'userid' => $userid, + 'subscriptionforced' => pdfannotator_FORCESUBSCRIBE, + )); + + foreach ($subscriptions as $id => $data) { + self::$pdfannotatorcache[$userid][$id] = !empty($data->subscriptionid); + } + $subscriptions->close(); + } + + /** + * Returns a list of user objects who are subscribed to this pdfannotator. + * + * @param stdClass $pdfannotator The pdfannotator record. + * @param int $groupid The group id if restricting subscriptions to a group of users, or 0 for all. + * @param context_module $context the pdfannotator context, to save re-fetching it where possible. + * @param string $fields requested user fields (with "u." table prefix). + * @param boolean $includediscussionsubscriptions Whether to take discussion subscriptions and unsubscriptions into + * consideration. + * @return array list of users. + */ + public static function fetch_subscribed_users($pdfannotator, $groupid = 0, $context = null, $fields = null, + $includediscussionsubscriptions = false) { + global $CFG, $DB; + + if (empty($fields)) { + $userfieldsapi = \core_user\fields::for_name(); + $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; + $fields = "u.id, + u.username, + $allnames, + u.maildisplay, + u.mailformat, + u.maildigest, + u.imagealt, + u.email, + u.emailstop, + u.city, + u.country, + u.lastaccess, + u.lastlogin, + u.picture, + u.timezone, + u.theme, + u.lang, + u.trackpdfannotators, + u.mnethostid"; + } + + // Retrieve the pdfannotator context if it wasn't specified. + $context = pdfannotator_get_context($pdfannotator->id, $context); + + if (self::is_forcesubscribed($pdfannotator)) { + $results = self::get_potential_subscribers($context, $groupid, $fields, "u.email ASC"); + + } else { + // Only active enrolled users or everybody on the frontpage. + list($esql, $params) = get_enrolled_sql($context, '', $groupid, true); + $params['pdfannotatorid'] = $pdfannotator->id; + + if ($includediscussionsubscriptions) { + $params['spdfannotatorid'] = $pdfannotator->id; + $params['dspdfannotatorid'] = $pdfannotator->id; + $params['unsubscribed'] = self::PDFANNOTATOR_DISCUSSION_UNSUBSCRIBED; + + $sql = "SELECT $fields + FROM ( + SELECT userid FROM {pdfannotator_subscriptions} s + WHERE + s.pdfannotator = :spdfannotatorid + UNION + SELECT userid FROM {pdfannotator_discussion_subs} ds + WHERE + ds.pdfannotator = :dspdfannotatorid AND ds.preference <> :unsubscribed + ) subscriptions + JOIN {user} u ON u.id = subscriptions.userid + JOIN ($esql) je ON je.id = u.id + WHERE u.auth <> 'nologin' AND u.suspended = 0 AND u.confirmed = 1 + ORDER BY u.email ASC"; + + } else { + $sql = "SELECT $fields + FROM {user} u + JOIN ($esql) je ON je.id = u.id + JOIN {pdfannotator_subscriptions} s ON s.userid = u.id + WHERE + s.pdfannotator = :pdfannotatorid AND u.auth <> 'nologin' AND u.suspended = 0 AND u.confirmed = 1 + ORDER BY u.email ASC"; + } + $results = $DB->get_records_sql($sql, $params); + } + + // Guest user should never be subscribed to a pdfannotator. + unset($results[$CFG->siteguest]); + + // Apply the activity module availability resetrictions. + $cm = get_coursemodule_from_instance('pdfannotator', $pdfannotator->id, $pdfannotator->course); + $modinfo = get_fast_modinfo($pdfannotator->course); + $info = new \core_availability\info_module($modinfo->get_cm($cm->id)); + $results = $info->filter_user_list($results); + + return $results; + } + + /** + * Retrieve the discussion subscription data for the specified userid and pdfannotator. + * + * This is returned as an array of discussions for that pdfannotator which contain the preference in a stdClass. + * + * @param int $pdfannotatorid The pdfannotator to retrieve a cache for + * @param int $userid The user ID + * @return array of stdClass objects with one per discussion in the pdfannotator. + */ + public static function fetch_discussion_subscription($pdfannotatorid, $userid = null) { + self::fill_discussion_subscription_cache($pdfannotatorid, $userid); + + if (!isset(self::$pdfannotatordiscussioncache[$userid]) || + !isset(self::$pdfannotatordiscussioncache[$userid][$pdfannotatorid])) { + return array(); + } + + return self::$pdfannotatordiscussioncache[$userid][$pdfannotatorid]; + } + + /** + * Fill the discussion subscription data for the specified userid and pdfannotator. + * + * If the userid is not specified, then all discussion subscription data for that pdfannotator is fetched in a single query + * and used for subsequent lookups without requiring further database queries. + * + * @param int $pdfannotatorid The pdfannotator to retrieve a cache for + * @param int $userid The user ID + * @return void + */ + public static function fill_discussion_subscription_cache($pdfannotatorid, $userid = null) { + global $DB; + + if (!isset(self::$discussionfetchedpdfannotators[$pdfannotatorid])) { + // This pdfannotator hasn't been fetched as a whole yet. + if (isset($userid)) { + if (!isset(self::$pdfannotatordiscussioncache[$userid])) { + self::$pdfannotatordiscussioncache[$userid] = array(); + } + + if (!isset(self::$pdfannotatordiscussioncache[$userid][$pdfannotatorid])) { + $subscriptions = $DB->get_recordset('pdfannotator_discussion_subs', array( + 'userid' => $userid, + 'pdfannotator' => $pdfannotatorid, + ), null, 'id, discussion, preference'); + + self::$pdfannotatordiscussioncache[$userid][$pdfannotatorid] = array(); + foreach ($subscriptions as $id => $data) { + self::add_to_discussion_cache($pdfannotatorid, $userid, $data->discussion, $data->preference); + } + + $subscriptions->close(); + } + } else { + $subscriptions = $DB->get_recordset('pdfannotator_discussion_subs', array( + 'pdfannotator' => $pdfannotatorid, + ), null, 'id, userid, discussion, preference'); + foreach ($subscriptions as $id => $data) { + self::add_to_discussion_cache($pdfannotatorid, $data->userid, $data->discussion, $data->preference); + } + self::$discussionfetchedpdfannotators[$pdfannotatorid] = true; + $subscriptions->close(); + } + } + } + + /** + * Add the specified discussion and user preference to the discussion + * subscription cache. + * + * @param int $pdfannotatorid The ID of the pdfannotator that this preference belongs to + * @param int $userid The ID of the user that this preference belongs to + * @param int $discussion The ID of the discussion that this preference relates to + * @param int $preference The preference to store + */ + protected static function add_to_discussion_cache($pdfannotatorid, $userid, $discussion, $preference) { + if (!isset(self::$pdfannotatordiscussioncache[$userid])) { + self::$pdfannotatordiscussioncache[$userid] = array(); + } + + if (!isset(self::$pdfannotatordiscussioncache[$userid][$pdfannotatorid])) { + self::$pdfannotatordiscussioncache[$userid][$pdfannotatorid] = array(); + } + + self::$pdfannotatordiscussioncache[$userid][$pdfannotatorid][$discussion] = $preference; + } + + /** + * Reset the discussion cache. + * + * This cache is used to reduce the number of database queries when + * checking pdfannotator discussion subscription states. + */ + public static function reset_discussion_cache() { + self::$pdfannotatordiscussioncache = array(); + self::$discussionfetchedpdfannotators = array(); + } + + /** + * Reset the pdfannotator cache. + * + * This cache is used to reduce the number of database queries when + * checking pdfannotator subscription states. + */ + public static function reset_pdfannotator_cache() { + self::$pdfannotatorcache = array(); + self::$fetchedpdfannotators = array(); + } + + /** + * Adds user to the subscriber list. + * + * @param int $userid The ID of the user to subscribe + * @param \stdClass $pdfannotator The pdfannotator record for this pdfannotator. + * @param \context_module|null $context Module context, may be omitted if not known or if called for the current + * module set in page. + * @param boolean $userrequest Whether the user requested this change themselves. This has an effect on whether + * discussion subscriptions are removed too. + * @return bool|int Returns true if the user is already subscribed, or the pdfannotator_subscriptions ID if the user was + * successfully subscribed. + */ + public static function subscribe_user($userid, $pdfannotator, $context = null, $userrequest = false) { + global $DB; + + if (self::is_subscribed($userid, $pdfannotator)) { + return true; + } + + $sub = new \stdClass(); + $sub->userid = $userid; + $sub->pdfannotator = $pdfannotator->id; + + $result = $DB->insert_record("pdfannotator_subscriptions", $sub); + + if ($userrequest) { + $discussionsubscriptions = $DB->get_recordset('pdfannotator_discussion_subs', array('userid' => $userid, + 'pdfannotator' => $pdfannotator->id)); + $DB->delete_records_select('pdfannotator_discussion_subs', + 'userid = :userid AND pdfannotator = :pdfannotatorid AND preference <> :preference', array( + 'userid' => $userid, + 'pdfannotatorid' => $pdfannotator->id, + 'preference' => self::PDFANNOTATOR_DISCUSSION_UNSUBSCRIBED, + )); + + // Reset the subscription caches for this pdfannotator. + // We know that the there were previously entries and there aren't any more. + if (isset(self::$pdfannotatordiscussioncache[$userid]) && + isset(self::$pdfannotatordiscussioncache[$userid][$pdfannotator->id])) { + foreach (self::$pdfannotatordiscussioncache[$userid][$pdfannotator->id] as $discussionid => $preference) { + if ($preference != self::PDFANNOTATOR_DISCUSSION_UNSUBSCRIBED) { + unset(self::$pdfannotatordiscussioncache[$userid][$pdfannotator->id][$discussionid]); + } + } + } + } + + // Reset the cache for this pdfannotator. + self::$pdfannotatorcache[$userid][$pdfannotator->id] = true; + + $context = pdfannotator_get_context($pdfannotator->id, $context); + $params = array( + 'context' => $context, + 'objectid' => $result, + 'relateduserid' => $userid, + 'other' => array('pdfannotatorid' => $pdfannotator->id), + + ); + $event = event\subscription_created::create($params); + if ($userrequest && $discussionsubscriptions) { + foreach ($discussionsubscriptions as $subscription) { + $event->add_record_snapshot('pdfannotator_discussion_subs', $subscription); + } + $discussionsubscriptions->close(); + } + $event->trigger(); + + return $result; + } + + /** + * Removes user from the subscriber list + * + * @param int $userid The ID of the user to unsubscribe + * @param \stdClass $pdfannotator The pdfannotator record for this pdfannotator. + * @param \context_module|null $context Module context, may be omitted if not known or if called for the current + * module set in page. + * @param boolean $userrequest Whether the user requested this change themselves. This has an effect on whether + * discussion subscriptions are removed too. + * @return boolean Always returns true. + */ + public static function unsubscribe_user($userid, $pdfannotator, $context = null, $userrequest = false) { + global $DB; + + $sqlparams = array( + 'userid' => $userid, + 'pdfannotator' => $pdfannotator->id, + ); + $DB->delete_records('pdfannotator_digests', $sqlparams); + + if ($pdfannotatorsubscription = $DB->get_record('pdfannotator_subscriptions', $sqlparams)) { + $DB->delete_records('pdfannotator_subscriptions', array('id' => $pdfannotatorsubscription->id)); + + if ($userrequest) { + $discussionsubscriptions = $DB->get_recordset('pdfannotator_discussion_subs', $sqlparams); + $DB->delete_records('pdfannotator_discussion_subs', + array('userid' => $userid, 'pdfannotator' => $pdfannotator->id, + 'preference' => self::PDFANNOTATOR_DISCUSSION_UNSUBSCRIBED)); + + // We know that the there were previously entries and there aren't any more. + if (isset(self::$pdfannotatordiscussioncache[$userid]) && + isset(self::$pdfannotatordiscussioncache[$userid][$pdfannotator->id])) { + self::$pdfannotatordiscussioncache[$userid][$pdfannotator->id] = array(); + } + } + + // Reset the cache for this pdfannotator. + self::$pdfannotatorcache[$userid][$pdfannotator->id] = false; + + $context = pdfannotator_get_context($pdfannotator->id, $context); + $params = array( + 'context' => $context, + 'objectid' => $pdfannotatorsubscription->id, + 'relateduserid' => $userid, + 'other' => array('pdfannotatorid' => $pdfannotator->id), + + ); + $event = event\subscription_deleted::create($params); + $event->add_record_snapshot('pdfannotator_subscriptions', $pdfannotatorsubscription); + if ($userrequest && $discussionsubscriptions) { + foreach ($discussionsubscriptions as $subscription) { + $event->add_record_snapshot('pdfannotator_discussion_subs', $subscription); + } + $discussionsubscriptions->close(); + } + $event->trigger(); + } + + return true; + } + + /** + * Subscribes the user to the specified discussion. + * + * @param int $userid The userid of the user being subscribed + * @param \stdClass $discussion The discussion to subscribe to + * @param \context_module|null $context Module context, may be omitted if not known or if called for the current + * module set in page. + * @return boolean Whether a change was made + */ + public static function subscribe_user_to_discussion($userid, $discussion, $context = null) { + global $DB; + + // First check whether the user is subscribed to the discussion already. + $subscription = $DB->get_record('pdfannotator_discussion_subs', array('userid' => $userid, + 'discussion' => $discussion->id)); + if ($subscription) { + if ($subscription->preference != self::PDFANNOTATOR_DISCUSSION_UNSUBSCRIBED) { + // The user is already subscribed to the discussion. Ignore. + return false; + } + } + // No discussion-level subscription. Check for a pdfannotator level subscription. + if ($DB->record_exists('pdfannotator_subscriptions', array('userid' => $userid, + 'pdfannotator' => $discussion->pdfannotator))) { + if ($subscription && $subscription->preference == self::PDFANNOTATOR_DISCUSSION_UNSUBSCRIBED) { + // The user is subscribed to the pdfannotator, but unsubscribed from the discussion, delete the discussion + // preference. + $DB->delete_records('pdfannotator_discussion_subs', array('id' => $subscription->id)); + unset(self::$pdfannotatordiscussioncache[$userid][$discussion->pdfannotator][$discussion->id]); + } else { + // The user is already subscribed to the pdfannotator. Ignore. + return false; + } + } else { + if ($subscription) { + $subscription->preference = time(); + $DB->update_record('pdfannotator_discussion_subs', $subscription); + } else { + $subscription = new \stdClass(); + $subscription->userid = $userid; + $subscription->pdfannotator = $discussion->pdfannotator; + $subscription->discussion = $discussion->id; + $subscription->preference = time(); + + $subscription->id = $DB->insert_record('pdfannotator_discussion_subs', $subscription); + self::$pdfannotatordiscussioncache[$userid][$discussion->pdfannotator][$discussion->id] = $subscription->preference; + } + } + + $context = pdfannotator_get_context($discussion->pdfannotator, $context); + $params = array( + 'context' => $context, + 'objectid' => $subscription->id, + 'relateduserid' => $userid, + 'other' => array( + 'pdfannotatorid' => $discussion->pdfannotator, + 'discussion' => $discussion->id, + ), + + ); + $event = event\discussion_subscription_created::create($params); + $event->trigger(); + + return true; + } + /** + * Unsubscribes the user from the specified discussion. + * + * @param int $userid The userid of the user being unsubscribed + * @param \stdClass $discussion The discussion to unsubscribe from + * @param \context_module|null $context Module context, may be omitted if not known or if called for the current + * module set in page. + * @return boolean Whether a change was made + */ + public static function unsubscribe_user_from_discussion($userid, $discussion, $context = null) { + global $DB; + + // First check whether the user's subscription preference for this discussion. + $subscription = $DB->get_record('pdfannotator_discussion_subs', array('userid' => $userid, + 'discussion' => $discussion->id)); + if ($subscription) { + if ($subscription->preference == self::PDFANNOTATOR_DISCUSSION_UNSUBSCRIBED) { + // The user is already unsubscribed from the discussion. Ignore. + return false; + } + } + // No discussion-level preference. Check for a pdfannotator level subscription. + if (!$DB->record_exists('pdfannotator_subscriptions', array('userid' => $userid, + 'pdfannotator' => $discussion->pdfannotator))) { + if ($subscription && $subscription->preference != self::PDFANNOTATOR_DISCUSSION_UNSUBSCRIBED) { + // The user is not subscribed to the pdfannotator, but subscribed from the discussion, delete the discussion + // subscription. + $DB->delete_records('pdfannotator_discussion_subs', array('id' => $subscription->id)); + unset(self::$pdfannotatordiscussioncache[$userid][$discussion->pdfannotator][$discussion->id]); + } else { + // The user is not subscribed from the pdfannotator. Ignore. + return false; + } + } else { + if ($subscription) { + $subscription->preference = self::PDFANNOTATOR_DISCUSSION_UNSUBSCRIBED; + $DB->update_record('pdfannotator_discussion_subs', $subscription); + } else { + $subscription = new \stdClass(); + $subscription->userid = $userid; + $subscription->pdfannotator = $discussion->pdfannotator; + $subscription->discussion = $discussion->id; + $subscription->preference = self::PDFANNOTATOR_DISCUSSION_UNSUBSCRIBED; + + $subscription->id = $DB->insert_record('pdfannotator_discussion_subs', $subscription); + } + self::$pdfannotatordiscussioncache[$userid][$discussion->pdfannotator][$discussion->id] = $subscription->preference; + } + + $context = pdfannotator_get_context($discussion->pdfannotator, $context); + $params = array( + 'context' => $context, + 'objectid' => $subscription->id, + 'relateduserid' => $userid, + 'other' => array( + 'pdfannotatorid' => $discussion->pdfannotator, + 'discussion' => $discussion->id, + ), + + ); + $event = event\discussion_subscription_deleted::create($params); + $event->trigger(); + + return true; + } + + /** + * Gets the default subscription value for the logged in user. + * + * @param \stdClass $pdfannotator The pdfannotator record + * @param \context $context The course context + * @param \cm_info $cm cm_info + * @param int|null $discussionid The discussion we are checking against + * @return bool Default subscription + * @throws coding_exception + */ + public static function get_user_default_subscription($pdfannotator, $context, $cm, ?int $discussionid) { + global $USER; + $manageactivities = has_capability('moodle/course:manageactivities', $context); + if (self::subscription_disabled($pdfannotator) && !$manageactivities) { + // User does not have permission to subscribe to this discussion at all. + $discussionsubscribe = false; + } else if (self::is_forcesubscribed($pdfannotator)) { + // User does not have permission to unsubscribe from this discussion at all. + $discussionsubscribe = true; + } else { + if (isset($discussionid) && self::is_subscribed($USER->id, $pdfannotator, $discussionid, $cm)) { + // User is subscribed to the discussion - continue the subscription. + $discussionsubscribe = true; + } else if (!isset($discussionid) && self::is_subscribed($USER->id, $pdfannotator, null, $cm)) { + // Starting a new discussion, and the user is subscribed to the pdfannotator - subscribe to the discussion. + $discussionsubscribe = true; + } else { + // User is not subscribed to either pdfannotator or discussion. Follow user preference. + $discussionsubscribe = $USER->autosubscribe ?? false; + } + } + + return $discussionsubscribe; + } +} diff --git a/db/install.xml b/db/install.xml index 102667d..e8dac02 100755 --- a/db/install.xml +++ b/db/install.xml @@ -18,6 +18,7 @@ + diff --git a/db/upgrade.php b/db/upgrade.php index d831d7a..bea56a9 100755 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -644,5 +644,19 @@ function xmldb_pdfannotator_upgrade($oldversion) { upgrade_mod_savepoint(true, 2022110200, 'pdfannotator'); } + if ($oldversion < 2023052301) { + + // Define field forcesubscribe to be added to pdfannotator. + $table = new xmldb_table('pdfannotator'); + $field = new xmldb_field('forcesubscribe', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'useprotectedcomments'); + + // Conditionally launch add field forcesubscribe. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Pdfannotator savepoint reached. + upgrade_mod_savepoint(true, 2023052301, 'pdfannotator'); + } return true; } diff --git a/lang/en/pdfannotator.php b/lang/en/pdfannotator.php index f8d65b5..4b7641a 100644 --- a/lang/en/pdfannotator.php +++ b/lang/en/pdfannotator.php @@ -1,509 +1,522 @@ -. - -/** - * @package mod_pdfannotator - * @copyright 2018 RWTH Aachen (see README.md) - * @author Rabea de Groot, Anna Heynkes, Friederike Schwager - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * - */ - -$string['actiondropdown'] = "Options"; -$string['activities'] = 'Activities'; -$string['addAComment'] = 'Add a comment'; -$string['add_protected_comment'] = 'Add a comment to private question'; -$string['add_private_comment'] = 'Add a comment to personal note'; -$string['ago'] = '{$a} ago'; -$string['all'] = 'all'; -$string['allanswers'] = 'all'; -$string['all_answers'] = 'All answers'; -$string['allquestions'] = 'all'; -$string['all_questions'] = 'All questions'; -$string['allquestionsimgtitle'] = "Show all questions in this document"; -$string['allquestionstitle'] = 'All questions in'; -$string['allreports'] = 'all reports'; -$string['annotationDeleted'] = 'Annotation has been deleted'; -$string['anonymous'] = 'Anonymous'; -$string['answer'] = 'Answer'; -$string['answerButton'] = 'Answer'; -$string['answercounthelpicon'] = 'Number of answers'; -$string['answercounthelpicon_help'] = 'This column tells you how many answers a question has received.'; -$string['answers'] = 'answers'; -$string['answerSolved'] = 'This answer was marked as correct by the manager.'; -$string['answerstab'] = 'Answers'; -$string['answerstabicon'] = 'Answers'; -$string['answerstabicon_help'] = "This page can show you all answers or only answers to questions you subscribed* to. The list covers all annotators in this course.
*When you post a question yourself, you are automatically subscribed to it as long as you don't unsubscribe."; -$string['author'] = 'Author'; -$string['average'] = 'average'; -$string['average_answers'] = 'Average answers'; -$string['average_help'] = 'Only users who wrote at least one comment are included in the calculation of the average (arithmetic mean)'; -$string['average_questions'] = 'Average questions'; - -$string['by'] = 'by'; -$string['by_other_users'] = 'by other users'; -$string['bynameondate'] = 'by {$a->name} - {$a->date}'; - -$string['cancelButton'] = 'Cancel'; -$string['chart_title'] = 'Questions and answers in the annotators in this course'; -$string['clicktoopen2'] = 'Click {$a} link to view the file.'; -$string['closedquestions'] = 'solved'; -$string['colorPicker'] = 'Pick a color'; -$string['comment'] = 'Comment'; -$string['commentDeleted'] = 'Comment has been deleted'; -$string['comments'] = 'Comments'; -$string['configmaxbytes'] = 'Maximum file size'; -$string['correct'] = 'correct'; -$string['count'] = 'count'; -$string['createAnnotation'] = 'Create Annotation'; -$string['currentPage'] = 'current page number'; - -$string['day'] = 'day'; -$string['days'] = 'days'; -$string['decision:overlappingAnnotation'] = 'You clicked an area, in which is more than one annotation. Decide which one you wanted to click.'; -$string['decision'] = 'Make a decision'; -$string['delete'] = 'Delete'; -$string['deleteComment'] = 'Delete comment'; -$string['deletedComment'] = 'deleted comment'; -$string['deletedQuestion'] = 'deleted question'; -$string['deletingAnnotation_manager'] = 'The annotation and all corresponding comments will be deleted.'; -$string['deletingAnnotation_student'] = "The annotation and all corresponding comments will be deleted.
You may delete your own annotations as long as they haven't been commented by other users."; -$string['deletingComment'] = 'The comment will be deleted. It will be displayed as deleted unless it is the last comment in its thread.'; -$string['deletingCommentTitle'] = 'Are you sure?'; -$string['deletingQuestion_manager'] = 'The comment will be deleted.
Hint: If you want to delete all answers as well, delete the annotation in the document.'; -$string['deletingQuestion_student'] = 'The question will be deleted.
If it is not answered, the annotation will be deleted too, otherwise the question will be displayed as deleted'; -$string['deletionForbidden'] = 'Deletion not allowed'; -$string['didyouknow'] = 'Did you know?'; -$string['dnduploadpdfannotator'] = 'Create file for PDF Annotation'; -$string['document'] = 'Document'; -$string['drawing'] = 'Draw in the document with the pen.'; - -$string['edit'] = 'Edit'; -$string['editAnnotation'] = 'The annotation will be moved.
This might change the context of the question.'; -$string['editAnnotationTitle'] = 'Are you sure?'; -$string['editButton'] = 'Save'; -$string['editedComment'] = 'last edited'; -$string['editNotAllowed'] = 'Panning not allowed!'; -$string['emptypdf'] = 'There are no comments in this annotator at present.'; -$string['enterText'] = 'Enter text'; -$string['entity_helptitle'] = 'Help for'; -$string['error:addAnnotation'] = 'An error has occurred while adding an annotation.'; -$string['error:addComment'] = 'An error has occurred while adding the comment.'; -$string['error:closequestion'] = 'An error has occurred while closing/opening the question.'; -$string['error:deleteAnnotation'] = 'An error has occurred while deleting an annotation.'; -$string['error:editAnnotation'] = 'An error has occurred while editing an annotation.'; -$string['error:editcomment'] = 'An error has occurred while trying to edit a comment.'; -$string['error:findimage'] = 'An error occurred while trying to find image {$a}.'; -$string['error:forwardquestion'] = 'An error has occurred while forwarding the question.'; -$string['error:forwardquestionnorecipient'] = 'An error has occurerd while forwarding the question.: No person in this course has the capability to receive forwarded questions.'; -$string['error:getAllQuestions'] = 'An error has occurred while getting the questions of this document.'; -$string['error:getAnnotation'] = 'An error has occurred while getting the annotation.'; -$string['error:getAnnotations'] = 'An error has occurred while getting all annotations.'; -$string['error:getComments'] = 'An error has occurred while getting the comments.'; -$string['error:getimageheight'] = 'An error has occurred while getting image height of {$a}.'; -$string['error:getimagewidth'] = 'An error has occurred while getting image width of {$a}.'; -$string['error:getQuestions'] = 'An error has occurred while getting the questions for this page.'; -$string['error:hideComment'] = "An error has occurred while trying to hide the comment from participants' view."; -$string['error:markasread'] = 'The item could not be marked as read.'; -$string['error:markasunread'] = 'The item could not be marked as unread.'; -$string['error:markcorrectanswer'] = 'An error has occurred while marking the answer as correct.'; -$string['error:maximalsizeoffile'] = 'Your file {$a->filename}, because it exceeds {$a->filesize} as the maximum size of files. You can attach file(s) with at most {$a->maxfilesize} to a single comment.'; -$string['error:missingAnnotationtype'] = 'Annotationtype does not exists. Possibly the entry in table pdfannotator_annotationtypes is missing.'; -$string['error:openingPDF'] = 'An error occurred while opening the PDF file.'; -$string['error:openprintview'] = 'An error has occurred while trying to open the pdf in Acrobat Reader.'; -$string['error:printcomments'] = 'An error has occurred while trying to open the comments in a pdf.'; -$string['error:printcommentsdata'] = 'Error with data from server.'; -$string['error:printlatex'] = 'An error has occurred while trying to add a LaTeX formula to the pdf.'; -$string['error:redisplayComment'] = 'An error has occurred while redisplaying the comment.'; -$string['error:renderPage'] = 'An error has occurred while rendering the page.'; -$string['error:reportComment'] = 'An error has occurred while saving the report.'; -$string['error:subscribe'] = 'An error has occurred while subscribing to the question.'; -$string['error:unsubscribe'] = 'An error has occurred while unsubscribing to the question.'; -$string['error:unsupportedextension'] = 'The extension of submitted data is not supported. Please select other extension.'; -$string['error:redihideCommentsplayComment'] = 'An error occurred while re-inserting the comment for attendees.'; -$string['error:voteComment'] = 'An error has occurred while saving the vote.'; -$string['error'] = 'Error!'; -$string['eventreport_added'] = 'A comment was reported'; - -$string['filenotfound'] = 'File not found, sorry.'; -$string['forward'] = 'Forward'; -$string['forwardedquestionhtml'] = '{$a->sender} forwarded the following question to you:

- "{$a->questioncontent}"

- with the message:

- "{$a->message}"

- The question is available here.'; -$string['forwardedquestiontext'] = '{$a->sender} forwarded the following question to you: - - "{$a->questioncontent}" - - with the message: - - "{$a->message}" - - The question is available at: {$a->urltoquestion}'; -$string['fullscreen'] = 'Fullscreen'; -$string['fullscreenBack'] = 'Exit Fullscreen'; - -$string['global_setting_anonymous'] = 'Allow anonymous posting?'; -$string['global_setting_anonymous_desc'] = 'With this option you allow your user to post comments anonymously. This option activates anonymous posting globally'; -$string['global_setting_attobuttons'] = 'Atto editor toolbar config'; -$string['global_setting_attobuttons_desc'] = 'The list of plugins and the order they are displayed can be configured here. The configuration consists of groups (one per line) followed by the ordered list of plugins for that group. The group is separated from the plugins with an equals sign and the plugins are separated with commas. The group names must be unique and should indicate what the buttons have in common. Button and group names should not be repeated and may only contain alphanumeric characters.'; -$string['global_setting_latexapisetting'] = 'LaTeX to PNG API'; -$string['global_setting_latexapisetting_desc'] = 'API for converting Latex to PNG for PDF Downloads.
- Note: If you use the Google Chart API, Google will get all formulas in the document if someone chooses to use LaTeX
- If you use the Moodle API, you need a latex, dvips and convert binary installed on your server. - (See Moodle Documentation)'; -$string['global_setting_latexusemoodle'] = 'Internal Moodle API'; -$string['global_setting_latexusegoogle'] = 'Google Chart API'; -$string['global_setting_use_studentdrawing'] = 'Allow drawings for participants?'; -$string['global_setting_use_studentdrawing_desc'] = 'Please note that drawings are anonymous and can neither be commented nor reported.'; -$string['global_setting_use_studenttextbox'] = 'Allow textboxes for participants?'; -$string['global_setting_use_studenttextbox_desc'] = "Please note that textbox annotations are anonymous and can neither be commented nor reported."; -$string['global_setting_useprint'] = 'Allow save and print?'; -$string['global_setting_useprint_comments'] = 'Allow saving/printing comments?'; -$string['global_setting_useprint_comments_desc'] = 'Allow participants to save and print the annotations and comments'; -$string['global_setting_use_private_comments'] = 'Allow personal notes?'; -$string['global_setting_use_private_comments_desc'] = 'Allow participants to write personal annotations and personal notes'; -$string['global_setting_use_protected_comments'] = 'Allow private comments?'; -$string['global_setting_use_protected_comments_desc'] = 'Allow participants to write private annotations and private comments. Only author and manager can see this comment.'; -$string['global_setting_useprint_desc'] = 'Allow participants to save and print the pdf document and its comments'; -$string['global_setting_useprint_document'] = 'Allow saving/printing document?'; -$string['global_setting_useprint_document_desc'] = 'Allow participants to save and print the pdf document'; -$string['global_setting_usevotes'] = 'Allow liking of comments?'; -$string['global_setting_usevotes_desc'] = 'With this option users can like / vote for posts other than their own.'; - -$string['hiddenComment'] = 'hidden comment'; -$string['hiddenforparticipants'] = 'Hidden from students'; -$string['hideAnnotations'] = 'Hide Annotations'; -$string['highlight'] = 'Highlight text and add a comment.'; -$string['hour'] = 'hour'; -$string['hours'] = 'hours'; - -$string['in_course'] = 'in this course'; -$string['in_document'] = 'in this document'; -$string['infonocomments'] = "This document contains no comments at present."; -$string['iscorrecthelpicon'] = 'Correct'; -$string['iscorrecthelpicon_help'] = 'When a teacher or manager has marked an answer as correct, a green check mark appears next to it.'; -$string['itemsperpage'] = 'Items per page'; - -$string['justnow'] = 'just now'; - -$string['lastanswered'] = 'Last Answer'; -$string['lastedited'] = 'last edited'; -$string['legacyfiles'] = 'Migration of old course file'; -$string['legacyfilesactive'] = 'Active'; -$string['legacyfilesdone'] = 'Finished'; -$string['like'] = 'like'; -$string['likeAnswer'] = 'helpful'; -$string['likeAnswerForbidden'] = 'already marked as helpful'; -$string['likeCountAnswer'] = 'persons think this answer is helpful'; -$string['likeCountQuestion'] = 'persons are also interested in this question'; -$string['likeForbidden'] = 'You are not allowed to like this comment'; -$string['likeOwnComment'] = 'own comment'; -$string['likeQuestion'] = 'interesting question'; -$string['likeQuestionForbidden'] = 'already marked as helpful'; -$string['loading'] = 'Loading!'; - -$string['markasread'] = 'Mark as read'; -$string['markasunread'] = 'Mark as unread'; -$string['markCorrect'] = 'Mark as correct'; -$string['markhidden'] = 'Hide'; -$string['markSolved'] = 'Close question'; -$string['markUnsolved'] = 'Reopen question'; -$string['maximumfilesize'] = 'Maximum file size'; -$string['maximumfilesize_help'] = 'Files uploaded by users may be up to this size.'; -$string['me'] = 'me'; -$string['messageforwardform'] = 'Your message to the recipient/s'; -$string['messageprovider:forwardedquestion'] = 'When a question was forwarded to you'; -$string['messageprovider:newanswer'] = 'When a question you subscribed to was answered'; -$string['messageprovider:newquestion'] = 'When a new question was asked'; -$string['messageprovider:newreport'] = 'When a comment was reported'; -$string['min0Chars'] = 'An empty question or comment is not allowed.'; -$string['minute'] = 'minute'; -$string['minutes'] = 'minutes'; -$string['missingAnnotation'] = 'The corresponding annotation could not be found!'; -$string['modifiedby'] = 'by'; -$string['modulename'] = 'PDF Annotation'; -$string['modulename_help'] = 'This Tool enables collaborative markup on PDF Documents. The users are able to annotate specific parts of an PDF and discuss them with other users.'; -$string['modulename_link'] = 'mod/pdfannotator/view'; -$string['modulenameplural'] = 'PDF Annotations'; -$string['month'] = 'month'; -$string['months'] = 'months'; -$string['myanswers'] = 'My answers'; -$string['mypost'] = 'My post'; -$string['myprivate'] = 'My personal notes'; -$string['myprotectedanswers'] = 'My private answers'; -$string['myprotectedquestions'] = 'My private questions'; -$string['mypublicanswers'] = 'My public answers'; -$string['mypublicquestions'] = 'My public questions'; -$string['myquestion'] = 'Question'; -$string['myquestions'] = 'My questions'; - -$string['newanswerhtml'] = 'Your subscribed question "{$a->question}" was answered by {$a->answeruser} with the comment:

"{$a->content}"

-The answer is here available.'; -$string['newanswertext'] = 'Your subscribed question "{$a->question}" was answered by {$a->answeruser} with the comment: - - "{$a->content}" - -The answer is available under: {$a->urltoanswer}'; -$string['newquestionhtml'] = 'A new Questions was added by {$a->answeruser} with the content:

"{$a->content}"

-The question is hier available.'; -$string['newquestions'] = 'Recently asked'; -$string['newquestiontext'] = 'A new Questions was added by {$a->answeruser} with the content: - - "{$a->content}" - -The question is available under: {$a->urltoanswer}'; -$string['nextPage'] = 'Next page'; -$string['noanswers'] = 'There are no answers in this course at present.'; -$string['noanswerssubscribed'] = 'There are no answers to subscribed questions in this course at present.'; -$string['noCommentsupported'] = 'This kind of annotation does not support comments.'; -$string['nomyposts'] = 'You have posted no question or answer in this course yet.'; -$string['noquestions'] = 'No questions on this page!'; -$string['noquestions_overview'] = 'There are no questions in this course at present.'; -$string['noquestions_view'] = 'There are no questions in this document at present.'; -$string['noquestionsclosed_overview'] = 'There are no closed questions in this course at present.'; -$string['noquestionsopen_overview'] = 'There are no open questions in this course at present.'; -$string['noreadreports'] = 'There are no read reports in this course at present.'; -$string['noreports'] = 'There are no reports in this course at present.'; -$string['nosearchresults'] = 'No search results found.'; -$string['notificationsubject:forwardedquestion'] = 'Forwarded question in {$a}'; -$string['notificationsubject:newanswer'] = 'New answer to subscribed question in {$a}'; -$string['notificationsubject:newquestion'] = 'New question in {$a}'; -$string['notificationsubject:newreport'] = 'A comment was reported in {$a}'; -$string['nounreadreports'] = 'There are no unread reports in this course at present.'; - -$string['on'] = 'on'; -$string['onlyDeleteOwnAnnotations'] = ", because it belongs to another user."; -$string['onlyDeleteUncommentedPosts'] = ", because the other users comments would be deleted as well."; -$string['openquestions'] = 'unsolved'; -$string['overview'] = 'Overview'; -$string['overviewactioncolumn'] = 'Manage'; -$string['ownpoststab'] = 'My posts'; -$string['ownpoststabicon'] = 'My posts'; -$string['ownpoststabicon_help'] = 'This page displays all comments that you posted in this course.'; - -$string['page'] = 'page'; -$string['pdfannotator:addinstance'] = 'add instance'; -$string['pdfannotator:administrateuserinput'] = 'Administrate comments'; -$string['pdfannotator:closeanyquestion'] = 'Close any question'; -$string['pdfannotator:closequestion'] = 'Close own questions'; -$string['pdfannotator:create'] = 'Create annotations and comments'; -$string['pdfannotator:deleteany'] = 'Delete any annotation and comment'; -$string['pdfannotator:deleteown'] = 'Delete your own annotations and comments'; -$string['pdfannotator:edit'] = 'Edit your own annotations and comments'; -$string['pdfannotator:editanypost'] = 'Edit any annotation and comment'; -$string['pdfannotator:forwardquestions'] = 'Forward questions'; -$string['pdfannotator:getforwardedquestions'] = 'Receive forwarded questions'; -$string['pdfannotator:hidecomments'] = 'Hide comments for participants'; -$string['pdfannotator:markcorrectanswer'] = 'Mark answers as correct'; -$string['pdfannotator:printcomments'] = 'Download the comments (even if the option is disabled for a PDF-Annotator)'; -$string['pdfannotator:printdocument'] = 'Download the document (even if the option is disabled for a PDF-Annotator)'; -$string['pdfannotator:recievenewquestionnotifications'] = 'Recieve notifications about new questions'; -$string['pdfannotator:report'] = 'Report inappropriate comments to the course manager'; -$string['pdfannotator:seehiddencomments'] = 'See hidden comments'; -$string['pdfannotator:subscribe'] = 'Subscribe to a question'; -$string['pdfannotator:usedrawing'] = 'Use drawing (even if the option is disabled for a PDF-Annotator)'; -$string['pdfannotator:usetextbox'] = 'Use textbox (even if the option is disabled for a PDF-Annotator)'; -$string['pdfannotator:view'] = 'View PDF Annotation'; -$string['pdfannotator:viewanswers'] = 'View answers to subscribed questions (overview page)'; -$string['pdfannotator:viewposts'] = 'View own comments (overview page)'; -$string['pdfannotator:viewprotectedcomments'] = 'See private comments'; -$string['pdfannotator:viewquestions'] = 'View open questions (overview page)'; -$string['pdfannotator:viewreports'] = 'View reported comments (overview page)'; -$string['pdfannotator:viewstatistics'] = 'View statistics page'; -$string['pdfannotator:viewteacherstatistics'] = 'See additional information on statistics page'; -$string['pdfannotator:vote'] = "Vote for an interesting question or helpful answer"; -$string['pdfannotator:writeprivatecomments'] = 'Make personal notes'; -$string['pdfannotator:writeprotectedcomments'] = 'Write private comments'; -$string['pdfannotator'] = 'Document'; -$string['pdfannotatorcolumn'] = 'Document'; -$string['pdfannotatorcontent'] = 'Files and subfolders'; -$string['pdfannotatorname'] = 'PDF Annotation Tool'; -$string['pdfannotatorpost'] = 'Comments and questions'; -$string['pdfButton'] = 'Document'; -$string['pluginadministration'] = 'PDF Annotation administration'; -$string['pluginname'] = 'PDF Annotation'; -$string['point'] = 'Add a pin in the document and write a comment.'; -$string['prevPage'] = 'Previous page'; -$string['print'] = 'download document'; -$string['printButton'] = 'Download'; -$string['printviewtitle'] = 'Comments'; -$string['printwithannotations'] = 'download comments'; -$string['privacy:metadata:core_files'] = 'The Pdfannotator stores files which have been uploaded by the user as a basis for annotation and discussion.'; -$string['privacy:metadata:pdfannotator_annotations:annotationid'] = 'The ID of the annotation that was made. It refers to the data listed above.'; -$string['privacy:metadata:pdfannotator_annotations:userid'] = 'The ID of the user who made this annotation.'; -$string['privacy:metadata:pdfannotator_annotations'] = "Information about the annotations a user made. This includes the type of annotation (e.g. highlight or drawing), its position within a specific file, as well as the time of creation."; -$string['privacy:metadata:pdfannotator_comments:annotationid'] = 'The ID of the underlying annotation.'; -$string['privacy:metadata:pdfannotator_comments:content'] = 'The literal comment.'; -$string['privacy:metadata:pdfannotator_comments:userid'] = "The ID of the comment's author."; -$string['privacy:metadata:pdfannotator_comments'] = "Information about a user's comments. This includes the content and time of creation of the comment, as well as the underlying annotation."; -$string['privacy:metadata:pdfannotator_reports:commentid'] = 'The ID of the reported comment.'; -$string['privacy:metadata:pdfannotator_reports:message'] = 'The text content of the report.'; -$string['privacy:metadata:pdfannotator_reports:userid'] = 'The author of the report.'; -$string['privacy:metadata:pdfannotator_reports'] = "Users can report other users' comments as inappropriate. These reports stored. This includes the ID of the reported comment as well as the author, content and time of the report."; -$string['privacy:metadata:pdfannotator_subscriptions:annotationid'] = 'The ID of the question/discussion that was subscribed to.'; -$string['privacy:metadata:pdfannotator_subscriptions:userid'] = 'The ID of the user with this subscription.'; -$string['privacy:metadata:pdfannotator_subscriptions'] = "Information about the subscriptions to individual questions/discussions."; -$string['privacy:metadata:pdfannotator_votes:commentid'] = "The ID of the comment."; -$string['privacy:metadata:pdfannotator_votes:userid'] = "The ID of the user who marked the comment as interesting or helpful. It is saved in order to prevent users from voting for the same comment repeatedly."; -$string['privacy:metadata:pdfannotator_votes'] = "Information about questions and comments that were marked as interesting or helpful."; -$string['private_comments'] = "Personal notes"; -$string['private_comments_help'] = 'Visible only for you.'; -$string['protected_answers'] = 'Private answers'; -$string['protected_comments'] = "Private comments"; -$string['protected_comments_help'] = 'Visible only for you and teachers.'; -$string['protected_questions'] = 'Private questions'; -$string['publicanswers'] = 'Public answers'; -$string['public_comments'] = 'Public comments'; -$string['publicquestions'] = 'Public questions'; - -$string['question'] = 'Question'; -$string['questionsimgtitle'] = "Show all questions on this page"; -$string['questionSolved'] = 'Questions is closed. However, you can still create new comments.'; -$string['questionstab'] = 'Questions'; -$string['questionstabicon'] = 'Questions'; -$string['questionstabicon_help'] = 'This page displays all unsolved questions that were asked in this course. You can also choose to see all or all solved questions in this course.'; -$string['questionstitle'] = 'Questions on page'; - -$string['read'] = 'Read'; -$string['reason'] = 'Explanation'; -$string['recievenewquestionnotifications'] = 'Notify about new questions'; -$string['recipient'] = 'Recipient/s'; -$string['recipient_help'] = 'To select several persons, hold down "Ctrl"'; -$string['recipientforwardform'] = 'Forward to'; -$string['recipientrequired'] = 'Please select recipient/s'; -$string['rectangle'] = 'Add a Rectangle in the document and write a comment.'; -$string['removeCorrect'] = 'Remove marking as correct'; -$string['removehidden'] = 'Show'; -$string['report'] = 'Report'; -$string['reportaddedhtml'] = '{$a->reportinguser} has reported a comment with the message:

"{$a->introduction}"

-It is available on the web site.'; -$string['reportaddedtext'] = '{$a->reportinguser} has reported a comment with the message: - - "{$a->introduction}" - -It is available under: {$a->urltoreport}'; -$string['reportedby'] = 'by / on'; -$string['reportedcomment'] = 'Reported comment'; -$string['reports'] = 'Reported comments'; -$string['reportsendbutton'] = 'Send'; -$string['reportstab'] = 'Reported comments'; -$string['reportstabicon'] = 'Reported comments'; -$string['reportstabicon_help'] = 'This page displays comments that were reported as inappropriate in this course. You can choose to see only unread/read* reports or all reports.
* Any manager of this course can mark a report as read.'; -$string['reportwassentoff'] = 'The comment has been reported.'; - -$string['search'] = 'Search'; -$string['searchresults'] = 'Search results'; -$string['second'] = 'second'; -$string['seconds'] = 'seconds'; -$string['seeabove'] = ''; -$string['seenreports'] = 'read only'; -$string['send'] = 'Send'; -$string['sendAnonymous'] = 'post anonymous'; -$string['sendPrivate'] = 'post personal note'; -$string['sendProtected'] = 'post private comment'; -$string['setting_alternative_name'] = 'Name'; -$string['setting_alternative_name_desc'] = 'Provide an alternative name for the PDF. If empty, the name of the pdf will be taken as representative name'; -$string['setting_alternative_name_help'] = "If the name is more than 20 characters long, the remaining characters will be replaced with '...' in the annotator's internal tab navigation."; -$string['setting_anonymous'] = 'Allow anonymous posting?'; -$string['setting_fileupload'] = 'Select a pdf-file'; -$string['setting_fileupload_help'] = "You can only change the selected file until the annotator has been created by a click on 'Save'."; -$string['setting_use_studentdrawing'] = "Drawing"; -$string['setting_use_studentdrawing_help'] = "Allow participants to save and print the pdf document without annotations or comments"; -$string['setting_use_studenttextbox'] = "Textbox"; -$string['setting_use_studenttextbox_help'] = "Please note that textbox annotations are not anonymous and can neither be commented nor reported."; -$string['setting_useprint'] = "save and print"; -$string['setting_useprint_comments'] = 'Save and print comments'; -$string['setting_useprint_comments_help'] = 'Allow participants to save and print the annotations and comments'; -$string['setting_useprint_document'] = 'Save and print pdf document'; -$string['setting_useprint_document_help'] = 'Allow participants to save and print the pdf document'; -$string['setting_useprint_help'] = "Please note that drawings are not anonymous and can neither be commented nor reported."; -$string['setting_use_private_comments'] = "Allow personal notes"; -$string['setting_use_private_comments_help'] = "Allow participants to write personal notes. Other person cannot see this comment."; -$string['setting_use_protected_comments'] = "Allow private comments"; -$string['setting_use_protected_comments_help'] = "Allow participants to write private comments. Only the author and teachers can see this comment."; -$string['setting_usevotes'] = "Votes/Likes"; -$string['setting_usevotes_help'] = "With this option enabled, users can like / vote for posts other than their own."; -$string['show'] = 'Show'; -$string['showAnnotations'] = 'Show Annotations'; -$string['showless'] = 'less'; -$string['showmore'] = 'more'; -$string['slotdatetimelabel'] = 'Date and time'; -$string['startDiscussion'] = 'Start a discussion'; -$string['statistic'] = 'Statistics'; -$string['strftimedatetime'] = '%d %b %Y, %I:%M %p'; -$string['strikeout'] = 'Strikeout text and add a comment.'; -$string['studentdrawingforbidden'] = 'This annotator does not support drawings for your user role.'; -$string['studenttextboxforbidden'] = 'This annotator does not support textboxes for your user role.'; -$string['subscribe'] = 'Subscribe to this Annotations'; -$string['subscribed'] = 'Subscribed'; -$string['subscribedanswers'] = 'to my subscribed questions'; -$string['subscribeQuestion'] = 'Subscribe'; -$string['subtitleforreportcommentform'] = 'Your message for the course manager'; -$string['successfullyEdited'] = 'Changes saved'; -$string['successfullyHidden'] = 'Participants now see this comment as hidden.'; -$string['successfullymarkedasread'] = 'The report was marked as read.'; -$string['successfullymarkedasreadandnolongerdisplayed'] = 'The report was marked as read and removed from the table.'; -$string['successfullymarkedasunread'] = 'The report was marked as unread.'; -$string['successfullymarkedasunreadandnolongerdisplayed'] = 'The report was marked as unread and removed from the table.'; -$string['successfullyRedisplayed'] = 'The comment is visible to participants once more'; -$string['successfullySubscribed'] = 'Subscribed to question.'; -$string['successfullySubscribed'] = 'Your subscription to the question was registered.'; -$string['successfullyUnsubscribed'] = 'Your subscribtion was cancelled.'; -$string['successfullyUnsubscribedPlural'] = 'Your subscribtion was cancelled. All {$a} answers to the question were removed from this table.'; -$string['successfullyUnsubscribedSingular'] = 'Your subscribtion to the question was cancelled and the only answer removed from this table.'; -$string['successfullyUnsubscribedTwo'] = 'Your subscribtion was cancelled. Both answers to the question were removed from this table.'; -$string['sumPages'] = 'Number of pages'; - -$string['text'] = 'Add a text in the document.'; -$string['titleforreportcommentform'] = 'Report comment'; -$string['titleforwardform'] = 'Forward question'; -$string['toreport'] = 'Report'; - -$string['unseenreports'] = 'unread only'; -$string['unsolvedquestionstitle'] = 'Unsolved Questions'; -$string['unsolvedquestionstitle_help'] = 'All unsolved questions in this course are listed.'; -$string['unsubscribe'] = 'Unsubscribe from this Annotations'; -$string['unsubscribe_notification'] = 'To unsubscribe from notification, please click here.'; -$string['unsubscribeQuestion'] = 'Unsubscribe'; -$string['unsubscribingDidNotWork'] = 'The subscription could not be cancelled.'; -$string['use_studentdrawing'] = "Enable drawing for participants?"; -$string['use_studenttextbox'] = "Enable textbox tool for participants?"; -$string['useprint'] = "Give participants access to the PDF?"; -$string['useprint_comments'] = "Give participants access to the PDF and its comments?"; -$string['use_private_comments'] = "Allow participants to write personal notes?"; -$string['use_protected_comments'] = "Allow participants to write private comments?"; -$string['useprint_document'] = "Give participants access to the PDF?"; -$string['usevotes'] = "Allow users to like comments."; - -$string['view'] = 'Document'; -$string['votes'] = 'Likes'; -$string['voteshelpicon'] = 'Likes'; -$string['voteshelpicon_help'] = 'This column tells you how many other people take an interest in the question.'; -$string['voteshelpicontwo'] = 'Likes'; -$string['voteshelpicontwo_help'] = 'This column tells you how often your posts were liked.'; - -$string['week'] = 'week'; -$string['weeks'] = 'weeks'; - -$string['year'] = 'year'; -$string['years'] = 'years'; -$string['yesButton'] = 'Yes'; - -$string['zoom'] = 'zoom'; -$string['zoomin'] = 'zoom in'; -$string['zoomout'] = 'zoom out'; +. + +/** + * @package mod_pdfannotator + * @copyright 2018 RWTH Aachen (see README.md) + * @author Rabea de Groot, Anna Heynkes, Friederike Schwager + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * + */ + +$string['actiondropdown'] = "Options"; +$string['activities'] = 'Activities'; +$string['addAComment'] = 'Add a comment'; +$string['add_protected_comment'] = 'Add a comment to private question'; +$string['add_private_comment'] = 'Add a comment to personal note'; +$string['ago'] = '{$a} ago'; +$string['all'] = 'all'; +$string['allanswers'] = 'all'; +$string['all_answers'] = 'All answers'; +$string['allquestions'] = 'all'; +$string['all_questions'] = 'All questions'; +$string['allquestionsimgtitle'] = "Show all questions in this document"; +$string['allquestionstitle'] = 'All questions in'; +$string['allreports'] = 'all reports'; +$string['annotationDeleted'] = 'Annotation has been deleted'; +$string['anonymous'] = 'Anonymous'; +$string['answer'] = 'Answer'; +$string['answerButton'] = 'Answer'; +$string['answercounthelpicon'] = 'Number of answers'; +$string['answercounthelpicon_help'] = 'This column tells you how many answers a question has received.'; +$string['answers'] = 'answers'; +$string['answerSolved'] = 'This answer was marked as correct by the manager.'; +$string['answerstab'] = 'Answers'; +$string['answerstabicon'] = 'Answers'; +$string['answerstabicon_help'] = "This page can show you all answers or only answers to questions you subscribed* to. The list covers all annotators in this course.
*When you post a question yourself, you are automatically subscribed to it as long as you don't unsubscribe."; +$string['author'] = 'Author'; +$string['average'] = 'average'; +$string['average_answers'] = 'Average answers'; +$string['average_help'] = 'Only users who wrote at least one comment are included in the calculation of the average (arithmetic mean)'; +$string['average_questions'] = 'Average questions'; + +$string['by'] = 'by'; +$string['by_other_users'] = 'by other users'; +$string['bynameondate'] = 'by {$a->name} - {$a->date}'; + +$string['cancelButton'] = 'Cancel'; +$string['chart_title'] = 'Questions and answers in the annotators in this course'; +$string['clicktoopen2'] = 'Click {$a} link to view the file.'; +$string['closedquestions'] = 'solved'; +$string['colorPicker'] = 'Pick a color'; +$string['comment'] = 'Comment'; +$string['commentDeleted'] = 'Comment has been deleted'; +$string['comments'] = 'Comments'; +$string['configmaxbytes'] = 'Maximum file size'; +$string['correct'] = 'correct'; +$string['count'] = 'count'; +$string['createAnnotation'] = 'Create Annotation'; +$string['currentPage'] = 'current page number'; + +$string['day'] = 'day'; +$string['days'] = 'days'; +$string['decision:overlappingAnnotation'] = 'You clicked an area, in which is more than one annotation. Decide which one you wanted to click.'; +$string['decision'] = 'Make a decision'; +$string['delete'] = 'Delete'; +$string['deleteComment'] = 'Delete comment'; +$string['deletedComment'] = 'deleted comment'; +$string['deletedQuestion'] = 'deleted question'; +$string['deletingAnnotation_manager'] = 'The annotation and all corresponding comments will be deleted.'; +$string['deletingAnnotation_student'] = "The annotation and all corresponding comments will be deleted.
You may delete your own annotations as long as they haven't been commented by other users."; +$string['deletingComment'] = 'The comment will be deleted. It will be displayed as deleted unless it is the last comment in its thread.'; +$string['deletingCommentTitle'] = 'Are you sure?'; +$string['deletingQuestion_manager'] = 'The comment will be deleted.
Hint: If you want to delete all answers as well, delete the annotation in the document.'; +$string['deletingQuestion_student'] = 'The question will be deleted.
If it is not answered, the annotation will be deleted too, otherwise the question will be displayed as deleted'; +$string['deletionForbidden'] = 'Deletion not allowed'; +$string['didyouknow'] = 'Did you know?'; +$string['dnduploadpdfannotator'] = 'Create file for PDF Annotation'; +$string['document'] = 'Document'; +$string['drawing'] = 'Draw in the document with the pen.'; + +$string['edit'] = 'Edit'; +$string['editAnnotation'] = 'The annotation will be moved.
This might change the context of the question.'; +$string['editAnnotationTitle'] = 'Are you sure?'; +$string['editButton'] = 'Save'; +$string['editedComment'] = 'last edited'; +$string['editNotAllowed'] = 'Panning not allowed!'; +$string['emptypdf'] = 'There are no comments in this annotator at present.'; +$string['enterText'] = 'Enter text'; +$string['entity_helptitle'] = 'Help for'; +$string['error:addAnnotation'] = 'An error has occurred while adding an annotation.'; +$string['error:addComment'] = 'An error has occurred while adding the comment.'; +$string['error:closequestion'] = 'An error has occurred while closing/opening the question.'; +$string['error:deleteAnnotation'] = 'An error has occurred while deleting an annotation.'; +$string['error:editAnnotation'] = 'An error has occurred while editing an annotation.'; +$string['error:editcomment'] = 'An error has occurred while trying to edit a comment.'; +$string['error:findimage'] = 'An error occurred while trying to find image {$a}.'; +$string['error:forwardquestion'] = 'An error has occurred while forwarding the question.'; +$string['error:forwardquestionnorecipient'] = 'An error has occurerd while forwarding the question.: No person in this course has the capability to receive forwarded questions.'; +$string['error:getAllQuestions'] = 'An error has occurred while getting the questions of this document.'; +$string['error:getAnnotation'] = 'An error has occurred while getting the annotation.'; +$string['error:getAnnotations'] = 'An error has occurred while getting all annotations.'; +$string['error:getComments'] = 'An error has occurred while getting the comments.'; +$string['error:getimageheight'] = 'An error has occurred while getting image height of {$a}.'; +$string['error:getimagewidth'] = 'An error has occurred while getting image width of {$a}.'; +$string['error:getQuestions'] = 'An error has occurred while getting the questions for this page.'; +$string['error:hideComment'] = "An error has occurred while trying to hide the comment from participants' view."; +$string['error:markasread'] = 'The item could not be marked as read.'; +$string['error:markasunread'] = 'The item could not be marked as unread.'; +$string['error:markcorrectanswer'] = 'An error has occurred while marking the answer as correct.'; +$string['error:maximalsizeoffile'] = 'Your file {$a->filename}, because it exceeds {$a->filesize} as the maximum size of files. You can attach file(s) with at most {$a->maxfilesize} to a single comment.'; +$string['error:missingAnnotationtype'] = 'Annotationtype does not exists. Possibly the entry in table pdfannotator_annotationtypes is missing.'; +$string['error:openingPDF'] = 'An error occurred while opening the PDF file.'; +$string['error:openprintview'] = 'An error has occurred while trying to open the pdf in Acrobat Reader.'; +$string['error:printcomments'] = 'An error has occurred while trying to open the comments in a pdf.'; +$string['error:printcommentsdata'] = 'Error with data from server.'; +$string['error:printlatex'] = 'An error has occurred while trying to add a LaTeX formula to the pdf.'; +$string['error:redisplayComment'] = 'An error has occurred while redisplaying the comment.'; +$string['error:renderPage'] = 'An error has occurred while rendering the page.'; +$string['error:reportComment'] = 'An error has occurred while saving the report.'; +$string['error:subscribe'] = 'An error has occurred while subscribing to the question.'; +$string['error:unsubscribe'] = 'An error has occurred while unsubscribing to the question.'; +$string['error:unsupportedextension'] = 'The extension of submitted data is not supported. Please select other extension.'; +$string['error:redihideCommentsplayComment'] = 'An error occurred while re-inserting the comment for attendees.'; +$string['error:voteComment'] = 'An error has occurred while saving the vote.'; +$string['error'] = 'Error!'; +$string['eventreport_added'] = 'A comment was reported'; + +$string['filenotfound'] = 'File not found, sorry.'; +$string['forward'] = 'Forward'; +$string['forwardedquestionhtml'] = '{$a->sender} forwarded the following question to you:

+ "{$a->questioncontent}"

+ with the message:

+ "{$a->message}"

+ The question is available here.'; +$string['forwardedquestiontext'] = '{$a->sender} forwarded the following question to you: + + "{$a->questioncontent}" + + with the message: + + "{$a->message}" + + The question is available at: {$a->urltoquestion}'; +$string['fullscreen'] = 'Fullscreen'; +$string['fullscreenBack'] = 'Exit Fullscreen'; + +$string['global_setting_anonymous'] = 'Allow anonymous posting?'; +$string['global_setting_anonymous_desc'] = 'With this option you allow your user to post comments anonymously. This option activates anonymous posting globally'; +$string['global_setting_attobuttons'] = 'Atto editor toolbar config'; +$string['global_setting_attobuttons_desc'] = 'The list of plugins and the order they are displayed can be configured here. The configuration consists of groups (one per line) followed by the ordered list of plugins for that group. The group is separated from the plugins with an equals sign and the plugins are separated with commas. The group names must be unique and should indicate what the buttons have in common. Button and group names should not be repeated and may only contain alphanumeric characters.'; +$string['global_setting_latexapisetting'] = 'LaTeX to PNG API'; +$string['global_setting_latexapisetting_desc'] = 'API for converting Latex to PNG for PDF Downloads.
+ Note: If you use the Google Chart API, Google will get all formulas in the document if someone chooses to use LaTeX
+ If you use the Moodle API, you need a latex, dvips and convert binary installed on your server. + (See Moodle Documentation)'; +$string['global_setting_latexusemoodle'] = 'Internal Moodle API'; +$string['global_setting_latexusegoogle'] = 'Google Chart API'; +$string['global_setting_use_studentdrawing'] = 'Allow drawings for participants?'; +$string['global_setting_use_studentdrawing_desc'] = 'Please note that drawings are anonymous and can neither be commented nor reported.'; +$string['global_setting_use_studenttextbox'] = 'Allow textboxes for participants?'; +$string['global_setting_use_studenttextbox_desc'] = "Please note that textbox annotations are anonymous and can neither be commented nor reported."; +$string['global_setting_useprint'] = 'Allow save and print?'; +$string['global_setting_useprint_comments'] = 'Allow saving/printing comments?'; +$string['global_setting_useprint_comments_desc'] = 'Allow participants to save and print the annotations and comments'; +$string['global_setting_use_private_comments'] = 'Allow personal notes?'; +$string['global_setting_use_private_comments_desc'] = 'Allow participants to write personal annotations and personal notes'; +$string['global_setting_use_protected_comments'] = 'Allow private comments?'; +$string['global_setting_use_protected_comments_desc'] = 'Allow participants to write private annotations and private comments. Only author and manager can see this comment.'; +$string['global_setting_useprint_desc'] = 'Allow participants to save and print the pdf document and its comments'; +$string['global_setting_useprint_document'] = 'Allow saving/printing document?'; +$string['global_setting_useprint_document_desc'] = 'Allow participants to save and print the pdf document'; +$string['global_setting_usevotes'] = 'Allow liking of comments?'; +$string['global_setting_usevotes_desc'] = 'With this option users can like / vote for posts other than their own.'; + +$string['hiddenComment'] = 'hidden comment'; +$string['hiddenforparticipants'] = 'Hidden from students'; +$string['hideAnnotations'] = 'Hide Annotations'; +$string['highlight'] = 'Highlight text and add a comment.'; +$string['hour'] = 'hour'; +$string['hours'] = 'hours'; + +$string['in_course'] = 'in this course'; +$string['in_document'] = 'in this document'; +$string['infonocomments'] = "This document contains no comments at present."; +$string['iscorrecthelpicon'] = 'Correct'; +$string['iscorrecthelpicon_help'] = 'When a teacher or manager has marked an answer as correct, a green check mark appears next to it.'; +$string['itemsperpage'] = 'Items per page'; + +$string['justnow'] = 'just now'; + +$string['lastanswered'] = 'Last Answer'; +$string['lastedited'] = 'last edited'; +$string['legacyfiles'] = 'Migration of old course file'; +$string['legacyfilesactive'] = 'Active'; +$string['legacyfilesdone'] = 'Finished'; +$string['like'] = 'like'; +$string['likeAnswer'] = 'helpful'; +$string['likeAnswerForbidden'] = 'already marked as helpful'; +$string['likeCountAnswer'] = 'persons think this answer is helpful'; +$string['likeCountQuestion'] = 'persons are also interested in this question'; +$string['likeForbidden'] = 'You are not allowed to like this comment'; +$string['likeOwnComment'] = 'own comment'; +$string['likeQuestion'] = 'interesting question'; +$string['likeQuestionForbidden'] = 'already marked as helpful'; +$string['loading'] = 'Loading!'; + +$string['markasread'] = 'Mark as read'; +$string['markasunread'] = 'Mark as unread'; +$string['markCorrect'] = 'Mark as correct'; +$string['markhidden'] = 'Hide'; +$string['markSolved'] = 'Close question'; +$string['markUnsolved'] = 'Reopen question'; +$string['maximumfilesize'] = 'Maximum file size'; +$string['maximumfilesize_help'] = 'Files uploaded by users may be up to this size.'; +$string['me'] = 'me'; +$string['messageforwardform'] = 'Your message to the recipient/s'; +$string['messageprovider:forwardedquestion'] = 'When a question was forwarded to you'; +$string['messageprovider:newanswer'] = 'When a question you subscribed to was answered'; +$string['messageprovider:newquestion'] = 'When a new question was asked'; +$string['messageprovider:newreport'] = 'When a comment was reported'; +$string['min0Chars'] = 'An empty question or comment is not allowed.'; +$string['minute'] = 'minute'; +$string['minutes'] = 'minutes'; +$string['missingAnnotation'] = 'The corresponding annotation could not be found!'; +$string['modifiedby'] = 'by'; +$string['modulename'] = 'PDF Annotation'; +$string['modulename_help'] = 'This Tool enables collaborative markup on PDF Documents. The users are able to annotate specific parts of an PDF and discuss them with other users.'; +$string['modulename_link'] = 'mod/pdfannotator/view'; +$string['modulenameplural'] = 'PDF Annotations'; +$string['month'] = 'month'; +$string['months'] = 'months'; +$string['myanswers'] = 'My answers'; +$string['mypost'] = 'My post'; +$string['myprivate'] = 'My personal notes'; +$string['myprotectedanswers'] = 'My private answers'; +$string['myprotectedquestions'] = 'My private questions'; +$string['mypublicanswers'] = 'My public answers'; +$string['mypublicquestions'] = 'My public questions'; +$string['myquestion'] = 'Question'; +$string['myquestions'] = 'My questions'; + +$string['newanswerhtml'] = 'Your subscribed question "{$a->question}" was answered by {$a->answeruser} with the comment:

"{$a->content}"

+The answer is here available.'; +$string['newanswertext'] = 'Your subscribed question "{$a->question}" was answered by {$a->answeruser} with the comment: + + "{$a->content}" + +The answer is available under: {$a->urltoanswer}'; +$string['newquestionhtml'] = 'A new Questions was added by {$a->answeruser} with the content:

"{$a->content}"

+The question is hier available.'; +$string['newquestions'] = 'Recently asked'; +$string['newquestiontext'] = 'A new Questions was added by {$a->answeruser} with the content: + + "{$a->content}" + +The question is available under: {$a->urltoanswer}'; +$string['nextPage'] = 'Next page'; +$string['noanswers'] = 'There are no answers in this course at present.'; +$string['noanswerssubscribed'] = 'There are no answers to subscribed questions in this course at present.'; +$string['noCommentsupported'] = 'This kind of annotation does not support comments.'; +$string['nomyposts'] = 'You have posted no question or answer in this course yet.'; +$string['noquestions'] = 'No questions on this page!'; +$string['noquestions_overview'] = 'There are no questions in this course at present.'; +$string['noquestions_view'] = 'There are no questions in this document at present.'; +$string['noquestionsclosed_overview'] = 'There are no closed questions in this course at present.'; +$string['noquestionsopen_overview'] = 'There are no open questions in this course at present.'; +$string['noreadreports'] = 'There are no read reports in this course at present.'; +$string['noreports'] = 'There are no reports in this course at present.'; +$string['nosearchresults'] = 'No search results found.'; +$string['notificationsubject:forwardedquestion'] = 'Forwarded question in {$a}'; +$string['notificationsubject:newanswer'] = 'New answer to subscribed question in {$a}'; +$string['notificationsubject:newquestion'] = 'New question in {$a}'; +$string['notificationsubject:newreport'] = 'A comment was reported in {$a}'; +$string['nounreadreports'] = 'There are no unread reports in this course at present.'; + +$string['on'] = 'on'; +$string['onlyDeleteOwnAnnotations'] = ", because it belongs to another user."; +$string['onlyDeleteUncommentedPosts'] = ", because the other users comments would be deleted as well."; +$string['openquestions'] = 'unsolved'; +$string['overview'] = 'Overview'; +$string['overviewactioncolumn'] = 'Manage'; +$string['ownpoststab'] = 'My posts'; +$string['ownpoststabicon'] = 'My posts'; +$string['ownpoststabicon_help'] = 'This page displays all comments that you posted in this course.'; + +$string['page'] = 'page'; +$string['pdfannotator:addinstance'] = 'add instance'; +$string['pdfannotator:administrateuserinput'] = 'Administrate comments'; +$string['pdfannotator:closeanyquestion'] = 'Close any question'; +$string['pdfannotator:closequestion'] = 'Close own questions'; +$string['pdfannotator:create'] = 'Create annotations and comments'; +$string['pdfannotator:deleteany'] = 'Delete any annotation and comment'; +$string['pdfannotator:deleteown'] = 'Delete your own annotations and comments'; +$string['pdfannotator:edit'] = 'Edit your own annotations and comments'; +$string['pdfannotator:editanypost'] = 'Edit any annotation and comment'; +$string['pdfannotator:forwardquestions'] = 'Forward questions'; +$string['pdfannotator:getforwardedquestions'] = 'Receive forwarded questions'; +$string['pdfannotator:hidecomments'] = 'Hide comments for participants'; +$string['pdfannotator:markcorrectanswer'] = 'Mark answers as correct'; +$string['pdfannotator:printcomments'] = 'Download the comments (even if the option is disabled for a PDF-Annotator)'; +$string['pdfannotator:printdocument'] = 'Download the document (even if the option is disabled for a PDF-Annotator)'; +$string['pdfannotator:recievenewquestionnotifications'] = 'Recieve notifications about new questions'; +$string['pdfannotator:report'] = 'Report inappropriate comments to the course manager'; +$string['pdfannotator:seehiddencomments'] = 'See hidden comments'; +$string['pdfannotator:subscribe'] = 'Subscribe to a question'; +$string['pdfannotator:usedrawing'] = 'Use drawing (even if the option is disabled for a PDF-Annotator)'; +$string['pdfannotator:usetextbox'] = 'Use textbox (even if the option is disabled for a PDF-Annotator)'; +$string['pdfannotator:view'] = 'View PDF Annotation'; +$string['pdfannotator:viewanswers'] = 'View answers to subscribed questions (overview page)'; +$string['pdfannotator:viewposts'] = 'View own comments (overview page)'; +$string['pdfannotator:viewprotectedcomments'] = 'See private comments'; +$string['pdfannotator:viewquestions'] = 'View open questions (overview page)'; +$string['pdfannotator:viewreports'] = 'View reported comments (overview page)'; +$string['pdfannotator:viewstatistics'] = 'View statistics page'; +$string['pdfannotator:viewteacherstatistics'] = 'See additional information on statistics page'; +$string['pdfannotator:vote'] = "Vote for an interesting question or helpful answer"; +$string['pdfannotator:writeprivatecomments'] = 'Make personal notes'; +$string['pdfannotator:writeprotectedcomments'] = 'Write private comments'; +$string['pdfannotator'] = 'Document'; +$string['pdfannotatorcolumn'] = 'Document'; +$string['pdfannotatorcontent'] = 'Files and subfolders'; +$string['pdfannotatorname'] = 'PDF Annotation Tool'; +$string['pdfannotatorpost'] = 'Comments and questions'; +$string['pdfButton'] = 'Document'; +$string['pluginadministration'] = 'PDF Annotation administration'; +$string['pluginname'] = 'PDF Annotation'; +$string['point'] = 'Add a pin in the document and write a comment.'; +$string['prevPage'] = 'Previous page'; +$string['print'] = 'download document'; +$string['printButton'] = 'Download'; +$string['printviewtitle'] = 'Comments'; +$string['printwithannotations'] = 'download comments'; +$string['privacy:metadata:core_files'] = 'The Pdfannotator stores files which have been uploaded by the user as a basis for annotation and discussion.'; +$string['privacy:metadata:pdfannotator_annotations:annotationid'] = 'The ID of the annotation that was made. It refers to the data listed above.'; +$string['privacy:metadata:pdfannotator_annotations:userid'] = 'The ID of the user who made this annotation.'; +$string['privacy:metadata:pdfannotator_annotations'] = "Information about the annotations a user made. This includes the type of annotation (e.g. highlight or drawing), its position within a specific file, as well as the time of creation."; +$string['privacy:metadata:pdfannotator_comments:annotationid'] = 'The ID of the underlying annotation.'; +$string['privacy:metadata:pdfannotator_comments:content'] = 'The literal comment.'; +$string['privacy:metadata:pdfannotator_comments:userid'] = "The ID of the comment's author."; +$string['privacy:metadata:pdfannotator_comments'] = "Information about a user's comments. This includes the content and time of creation of the comment, as well as the underlying annotation."; +$string['privacy:metadata:pdfannotator_reports:commentid'] = 'The ID of the reported comment.'; +$string['privacy:metadata:pdfannotator_reports:message'] = 'The text content of the report.'; +$string['privacy:metadata:pdfannotator_reports:userid'] = 'The author of the report.'; +$string['privacy:metadata:pdfannotator_reports'] = "Users can report other users' comments as inappropriate. These reports stored. This includes the ID of the reported comment as well as the author, content and time of the report."; +$string['privacy:metadata:pdfannotator_subscriptions:annotationid'] = 'The ID of the question/discussion that was subscribed to.'; +$string['privacy:metadata:pdfannotator_subscriptions:userid'] = 'The ID of the user with this subscription.'; +$string['privacy:metadata:pdfannotator_subscriptions'] = "Information about the subscriptions to individual questions/discussions."; +$string['privacy:metadata:pdfannotator_votes:commentid'] = "The ID of the comment."; +$string['privacy:metadata:pdfannotator_votes:userid'] = "The ID of the user who marked the comment as interesting or helpful. It is saved in order to prevent users from voting for the same comment repeatedly."; +$string['privacy:metadata:pdfannotator_votes'] = "Information about questions and comments that were marked as interesting or helpful."; +$string['private_comments'] = "Personal notes"; +$string['private_comments_help'] = 'Visible only for you.'; +$string['protected_answers'] = 'Private answers'; +$string['protected_comments'] = "Private comments"; +$string['protected_comments_help'] = 'Visible only for you and teachers.'; +$string['protected_questions'] = 'Private questions'; +$string['publicanswers'] = 'Public answers'; +$string['public_comments'] = 'Public comments'; +$string['publicquestions'] = 'Public questions'; + +$string['question'] = 'Question'; +$string['questionsimgtitle'] = "Show all questions on this page"; +$string['questionSolved'] = 'Questions is closed. However, you can still create new comments.'; +$string['questionstab'] = 'Questions'; +$string['questionstabicon'] = 'Questions'; +$string['questionstabicon_help'] = 'This page displays all unsolved questions that were asked in this course. You can also choose to see all or all solved questions in this course.'; +$string['questionstitle'] = 'Questions on page'; + +$string['read'] = 'Read'; +$string['reason'] = 'Explanation'; +$string['recievenewquestionnotifications'] = 'Notify about new questions'; +$string['recipient'] = 'Recipient/s'; +$string['recipient_help'] = 'To select several persons, hold down "Ctrl"'; +$string['recipientforwardform'] = 'Forward to'; +$string['recipientrequired'] = 'Please select recipient/s'; +$string['rectangle'] = 'Add a Rectangle in the document and write a comment.'; +$string['removeCorrect'] = 'Remove marking as correct'; +$string['removehidden'] = 'Show'; +$string['report'] = 'Report'; +$string['reportaddedhtml'] = '{$a->reportinguser} has reported a comment with the message:

"{$a->introduction}"

+It is available on the web site.'; +$string['reportaddedtext'] = '{$a->reportinguser} has reported a comment with the message: + + "{$a->introduction}" + +It is available under: {$a->urltoreport}'; +$string['reportedby'] = 'by / on'; +$string['reportedcomment'] = 'Reported comment'; +$string['reports'] = 'Reported comments'; +$string['reportsendbutton'] = 'Send'; +$string['reportstab'] = 'Reported comments'; +$string['reportstabicon'] = 'Reported comments'; +$string['reportstabicon_help'] = 'This page displays comments that were reported as inappropriate in this course. You can choose to see only unread/read* reports or all reports.
* Any manager of this course can mark a report as read.'; +$string['reportwassentoff'] = 'The comment has been reported.'; + +$string['search'] = 'Search'; +$string['searchresults'] = 'Search results'; +$string['second'] = 'second'; +$string['seconds'] = 'seconds'; +$string['seeabove'] = ''; +$string['seenreports'] = 'read only'; +$string['send'] = 'Send'; +$string['sendAnonymous'] = 'post anonymous'; +$string['sendPrivate'] = 'post personal note'; +$string['sendProtected'] = 'post private comment'; +$string['setting_alternative_name'] = 'Name'; +$string['setting_alternative_name_desc'] = 'Provide an alternative name for the PDF. If empty, the name of the pdf will be taken as representative name'; +$string['setting_alternative_name_help'] = "If the name is more than 20 characters long, the remaining characters will be replaced with '...' in the annotator's internal tab navigation."; +$string['setting_anonymous'] = 'Allow anonymous posting?'; +$string['setting_fileupload'] = 'Select a pdf-file'; +$string['setting_fileupload_help'] = "You can only change the selected file until the annotator has been created by a click on 'Save'."; +$string['setting_use_studentdrawing'] = "Drawing"; +$string['setting_use_studentdrawing_help'] = "Allow participants to save and print the pdf document without annotations or comments"; +$string['setting_use_studenttextbox'] = "Textbox"; +$string['setting_use_studenttextbox_help'] = "Please note that textbox annotations are not anonymous and can neither be commented nor reported."; +$string['setting_useprint'] = "save and print"; +$string['setting_useprint_comments'] = 'Save and print comments'; +$string['setting_useprint_comments_help'] = 'Allow participants to save and print the annotations and comments'; +$string['setting_useprint_document'] = 'Save and print pdf document'; +$string['setting_useprint_document_help'] = 'Allow participants to save and print the pdf document'; +$string['setting_useprint_help'] = "Please note that drawings are not anonymous and can neither be commented nor reported."; +$string['setting_use_private_comments'] = "Allow personal notes"; +$string['setting_use_private_comments_help'] = "Allow participants to write personal notes. Other person cannot see this comment."; +$string['setting_use_protected_comments'] = "Allow private comments"; +$string['setting_use_protected_comments_help'] = "Allow participants to write private comments. Only the author and teachers can see this comment."; +$string['setting_usevotes'] = "Votes/Likes"; +$string['setting_usevotes_help'] = "With this option enabled, users can like / vote for posts other than their own."; +$string['show'] = 'Show'; +$string['showAnnotations'] = 'Show Annotations'; +$string['showless'] = 'less'; +$string['showmore'] = 'more'; +$string['slotdatetimelabel'] = 'Date and time'; +$string['startDiscussion'] = 'Start a discussion'; +$string['statistic'] = 'Statistics'; +$string['strftimedatetime'] = '%d %b %Y, %I:%M %p'; +$string['strikeout'] = 'Strikeout text and add a comment.'; +$string['studentdrawingforbidden'] = 'This annotator does not support drawings for your user role.'; +$string['studenttextboxforbidden'] = 'This annotator does not support textboxes for your user role.'; +$string['subscribe'] = 'Subscribe to this Annotations'; +$string['subscribed'] = 'Subscribed'; +$string['subscribedanswers'] = 'to my subscribed questions'; +$string['subscribeQuestion'] = 'Subscribe'; +$string['subscription'] = 'Subscription'; +$string['subscriptionauto'] = 'Auto subscription'; +$string['subscriptiondisabled'] = 'Subscription disabled'; +$string['subscriptionforced'] = 'Forced subscription'; +$string['subscriptionmode'] = 'Subscription mode'; +$string['subscriptionmode_help'] = 'When a participant is subscribed to a question it means they will receive notifications for questions. There are 4 subscription +mode options: +* Auto subscription - Everyone is subscribed initially to notifications for questions but can choose to unsubscribe at any time +* Optional subscription - Participants can choose whether notifications for questions are subscribed +* Forced subscription - Everyone is subscribed to notifications for questions and cannot unsubscribe +* Subscription disabled - Subscriptions to notifications for questions are not allowed'; +$string['subscriptionoptional'] = 'Optional subscription'; +$string['subtitleforreportcommentform'] = 'Your message for the course +manager'; +$string['successfullyEdited'] = 'Changes saved'; +$string['successfullyHidden'] = 'Participants now see this comment as hidden.'; +$string['successfullymarkedasread'] = 'The report was marked as read.'; +$string['successfullymarkedasreadandnolongerdisplayed'] = 'The report was marked as read and removed from the table.'; +$string['successfullymarkedasunread'] = 'The report was marked as unread.'; +$string['successfullymarkedasunreadandnolongerdisplayed'] = 'The report was marked as unread and removed from the table.'; +$string['successfullyRedisplayed'] = 'The comment is visible to participants once more'; +$string['successfullySubscribed'] = 'Subscribed to question.'; +$string['successfullySubscribed'] = 'Your subscription to the question was registered.'; +$string['successfullyUnsubscribed'] = 'Your subscribtion was cancelled.'; +$string['successfullyUnsubscribedPlural'] = 'Your subscribtion was cancelled. All {$a} answers to the question were removed from this table.'; +$string['successfullyUnsubscribedSingular'] = 'Your subscribtion to the question was cancelled and the only answer removed from this table.'; +$string['successfullyUnsubscribedTwo'] = 'Your subscribtion was cancelled. Both answers to the question were removed from this table.'; +$string['sumPages'] = 'Number of pages'; + +$string['text'] = 'Add a text in the document.'; +$string['titleforreportcommentform'] = 'Report comment'; +$string['titleforwardform'] = 'Forward question'; +$string['toreport'] = 'Report'; + +$string['unseenreports'] = 'unread only'; +$string['unsolvedquestionstitle'] = 'Unsolved Questions'; +$string['unsolvedquestionstitle_help'] = 'All unsolved questions in this course are listed.'; +$string['unsubscribe'] = 'Unsubscribe from this Annotations'; +$string['unsubscribe_notification'] = 'To unsubscribe from notification, please click here.'; +$string['unsubscribeQuestion'] = 'Unsubscribe'; +$string['unsubscribingDidNotWork'] = 'The subscription could not be cancelled.'; +$string['use_studentdrawing'] = "Enable drawing for participants?"; +$string['use_studenttextbox'] = "Enable textbox tool for participants?"; +$string['useprint'] = "Give participants access to the PDF?"; +$string['useprint_comments'] = "Give participants access to the PDF and its comments?"; +$string['use_private_comments'] = "Allow participants to write personal notes?"; +$string['use_protected_comments'] = "Allow participants to write private comments?"; +$string['useprint_document'] = "Give participants access to the PDF?"; +$string['usevotes'] = "Allow users to like comments."; + +$string['view'] = 'Document'; +$string['votes'] = 'Likes'; +$string['voteshelpicon'] = 'Likes'; +$string['voteshelpicon_help'] = 'This column tells you how many other people take an interest in the question.'; +$string['voteshelpicontwo'] = 'Likes'; +$string['voteshelpicontwo_help'] = 'This column tells you how often your posts were liked.'; + +$string['week'] = 'week'; +$string['weeks'] = 'weeks'; + +$string['year'] = 'year'; +$string['years'] = 'years'; +$string['yesButton'] = 'Yes'; + +$string['zoom'] = 'zoom'; +$string['zoomin'] = 'zoom in'; +$string['zoomout'] = 'zoom out'; diff --git a/lib.php b/lib.php index bcd3c4b..bfcedb8 100644 --- a/lib.php +++ b/lib.php @@ -1,831 +1,852 @@ -. -/** - * @package mod_pdfannotator - * @copyright 2018 RWTH Aachen (see README.md) - * @author Ahmad Obeid, Rabea de Groot, Anna Heynkes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -defined('MOODLE_INTERNAL') || die; - -require_once($CFG->dirroot . '/mod/pdfannotator/locallib.php'); - -// Ugly hack to make 3.11 and 4.0 work seamlessly. -if (!defined('FEATURE_MOD_PURPOSE')) { - define('FEATURE_MOD_PURPOSE', 'mod_purpose'); -} -if (!defined('MOD_PURPOSE_COMMUNICATION')) { - define('MOD_PURPOSE_COMMUNICATION', 'communication'); -} - -/** - * List of features supported in pdfannotator module - * @param string $feature FEATURE_xx constant for requested feature - * @return mixed True if module supports feature, false if not, null if doesn't know - */ -function pdfannotator_supports($feature) { - switch($feature) { - case FEATURE_GROUPS: - return true; - case FEATURE_GROUPINGS: - return true; - case FEATURE_MOD_INTRO: - return true; - case FEATURE_COMPLETION_TRACKS_VIEWS: - return true; - case FEATURE_COMPLETION_HAS_RULES: - return false; - case FEATURE_GRADE_HAS_GRADE: - return false; - case FEATURE_GRADE_OUTCOMES: - return false; - case FEATURE_BACKUP_MOODLE2: - return true; - case FEATURE_SHOW_DESCRIPTION: - return true; - case FEATURE_ADVANCED_GRADING: - return false; - case FEATURE_PLAGIARISM: - return false; - case FEATURE_COMMENT: - return false; - case FEATURE_MOD_PURPOSE: - return MOD_PURPOSE_COMMUNICATION; - default: - return null; - } -} -/** - * Function currently unused. - * - * @return string - */ -function mod_pdfannotator_before_standard_html_head() { - -} -/** - * Returns all other caps used in module - * @return array - */ -function pdfannotator_get_extra_capabilities() { - return array('moodle/site:accessallgroups'); -} -/** - * This function is used by the reset_course_userdata function in moodlelib. - * @param $data the data submitted from the reset course. - * @return array status array - */ -function pdfannotator_reset_userdata($data) { - return array(); -} - -/** - * List the actions that correspond to a view of this module. - * This is used by the participation report. - * - * Note: This is not used by new logging system. Event with - * crud = 'r' and edulevel = LEVEL_PARTICIPATING will - * be considered as view action. - * - * @return array - */ -function pdfannotator_get_view_actions() { - return array('view', 'view all'); -} - -/** - * List the actions that correspond to a post of this module. - * This is used by the participation report. - * - * Note: This is not used by new logging system. Event with - * crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING - * will be considered as post action. - * - * @return array - */ -function pdfannotator_get_post_actions() { - return array('update', 'add'); -} - -/** - * Add pdfannotator instance. - * @param object $data - * @param mod_pdfannotator_mod_form $mform - * @return int new pdfannotator instance id - */ -function pdfannotator_add_instance($data, $mform) { - global $CFG, $DB; - require_once("$CFG->libdir/resourcelib.php"); - require_once("$CFG->dirroot/mod/pdfannotator/locallib.php"); - $cmid = $data->coursemodule; - $data->timemodified = time(); - pdfannotator_set_display_options($data); - - $data->id = $DB->insert_record('pdfannotator', $data); - - // We need to use context now, so we need to make sure all needed info is already in db. - $DB->set_field('course_modules', 'instance', $data->id, array('id' => $cmid)); - pdfannotator_set_mainfile($data); - - $completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null; - \core_completion\api::update_completion_date_event($cmid, 'pdfannotator', $data->id, $completiontimeexpected); - - return $data->id; -} - -/** - * Update pdfannotator instance. - * @param object $data - * @param object $mform - * @return bool true - */ -function pdfannotator_update_instance($data, $mform) { - global $CFG, $DB; - require_once("$CFG->libdir/resourcelib.php"); - require_once("$CFG->dirroot/mod/pdfannotator/locallib.php"); - $data->timemodified = time(); - $data->id = $data->instance; - $data->revision++; - - pdfannotator_set_display_options($data); // Can be deleted or extended. - - $DB->update_record('pdfannotator', $data); - pdfannotator_set_mainfile($data); - - $completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null; - \core_completion\api::update_completion_date_event($data->coursemodule, 'pdfannotator', $data->id, $completiontimeexpected); - - return true; -} - -/** - * Updates display options based on form input. - * - * Shared code used by pdfannotator_add_instance and pdfannotator_update_instance. - * keep it, if you want defind more disply options - * @param object $data Data object - */ -function pdfannotator_set_display_options($data) { - $displayoptions = array(); - $displayoptions['printintro'] = (int) !empty($data->printintro); - $data->displayoptions = serialize($displayoptions); -} - -/** - * Delete pdfannotator instance. - * @param int $id in mdl_pdfannotator - * @return bool true - */ -function pdfannotator_delete_instance($id) { - - global $DB; - - if (!$pdfannotator = $DB->get_record('pdfannotator', array('id' => $id))) { - return false; - } - - $cm = get_coursemodule_from_instance('pdfannotator', $id); - \core_completion\api::update_completion_date_event($cm->id, 'pdfannotator', $id, null); - - // Note: all context files are deleted automatically. - // 1.a) Get all annotations of the annotator. - $annotations = $DB->get_records('pdfannotator_annotations', ['pdfannotatorid' => $id]); - - // 1.b) For every annotation delete all subscriptions attached to it. - foreach ($annotations as $annotation) { - if (!$DB->delete_records('pdfannotator_subscriptions', ['annotationid' => $annotation->id]) == 1) { - return false; - } - } - // 1.c) Then delete the annotations from the annotations table. - if (!$DB->delete_records('pdfannotator_annotations', ['pdfannotatorid' => $id]) == 1) { - return false; - } - - // 2.a) Get all comments in this annotator. - $comments = $DB->get_records('pdfannotator_comments', ['pdfannotatorid' => $id]); - - // 2.b) Delete all votes in this annotator. - foreach ($comments as $comment) { - if (!$DB->delete_records('pdfannotator_votes', ['commentid' => $comment->id]) == 1) { - return false; - } - } - // 2.c) Delete all comments in this annotator. - if (!$DB->delete_records('pdfannotator_comments', ['pdfannotatorid' => $id]) == 1) { - return false; - } - - // 3. Deleting all the reports. - if (!$DB->delete_records('pdfannotator_reports', ['pdfannotatorid' => $id])) { - return false; - } - - // 4. Delete the annotator itself. - if (!$DB->delete_records('pdfannotator', array('id' => $id)) == 1) { - return false; - } - - return true; -} - -/** - * Given a course_module object, this function returns any - * "extra" information that may be needed when printing - * this activity in a course listing. - * - * See {@see get_array_of_activities()} in course/lib.php - * - * @param stdClass $coursemodule - * @return cached_cm_info info - */ -function pdfannotator_get_coursemodule_info($coursemodule) { - global $CFG, $DB; - require_once("$CFG->libdir/filelib.php"); - require_once("$CFG->dirroot/mod/pdfannotator/locallib.php"); - require_once($CFG->libdir . '/completionlib.php'); - - $context = context_module::instance($coursemodule->id); - - if (!$pdfannotator = $DB->get_record('pdfannotator', array('id' => $coursemodule->instance), 'id, name, course, - timemodified, timecreated, intro, introformat')) { - return null; - } - - $info = new cached_cm_info(); - $info->name = $pdfannotator->name; - if ($coursemodule->showdescription) { - // Convert intro to html. Do not filter cached version, filters run at display time. - $info->content = format_module_intro('pdfannotator', $pdfannotator, $coursemodule->id, false); - } - - // See if there is at least one file. - $fs = get_file_storage(); - $files = $fs->get_area_files($context->id, 'mod_pdfannotator', 'content', 0, 'sortorder DESC, id ASC', false, 0, 0, 1); - if (count($files) >= 1) { - $mainfile = reset($files); - // $info->icon = file_file_icon($mainfile, 24); // Uncomment to use pdf icon. - $pdfannotator->mainfile = $mainfile->get_filename(); - } - // If any optional extra details are turned on, store in custom data, - // add some file details as well to be used later by pdfannotator_get_optional_details() without retriving. - // Do not store filedetails if this is a reference - they will still need to be retrieved every time. - - return $info; -} - -/** - * Called when viewing course page. Shows extra details after the link if - * enabled. - * - * @param cm_info $cm Course module information - */ -function pdfannotator_cm_info_view(cm_info $cm) { - global $CFG; - require_once($CFG->dirroot . '/mod/pdfannotator/locallib.php'); - $cm->set_after_link(' ' . html_writer::tag('span', '', // Use this to show details. - array('class' => 'pdfannotatorlinkdetails'))); -} - -/** - * Lists all browsable file areas - * - * @package mod_pdfannotator - * @category files - * @param stdClass $course course object - * @param stdClass $cm course module object - * @param stdClass $context context object - * @return array - */ -function pdfannotator_get_file_areas($course, $cm, $context) { - $areas = array(); - $areas['content'] = get_string('pdfannotatorcontent', 'pdfannotator'); - $areas['post'] = get_string('pdfannotatorpost', 'pdfannotator'); - return $areas; -} - -/** - * File browsing support for pdfannotator module content area. - * - * @package mod_pdfannotator - * @category files - * @param stdClass $browser file browser instance - * @param stdClass $areas file areas - * @param stdClass $course course object - * @param stdClass $cm course module object - * @param stdClass $context context object - * @param string $filearea file area - * @param int $itemid item ID - * @param string $filepath file path - * @param string $filename file name - * @return file_info instance or null if not found - */ -function pdfannotator_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) { - global $CFG; - - if (!has_capability('moodle/course:managefiles', $context)) { - // Students can not peak here! - return null; - } - - $fs = get_file_storage(); - - if ($filearea === 'content') { - $filepath = is_null($filepath) ? '/' : $filepath; - $filename = is_null($filename) ? '.' : $filename; - - $urlbase = $CFG->wwwroot . '/pluginfile.php'; - if (!$storedfile = $fs->get_file($context->id, 'mod_pdfannotator', 'content', 0, $filepath, $filename)) { - if ($filepath === '/' and $filename === '.') { - $storedfile = new virtual_root_file($context->id, 'mod_pdfannotator', 'content', 0); - } else { - // Not found. - return null; - } - } - require_once("$CFG->dirroot/mod/pdfannotator/locallib.php"); - return new pdfannotator_content_file_info($browser, $context, $storedfile, $urlbase, $areas[$filearea], true, true, true, false); - } - - // Note: pdfannotator_intro handled in file_browser automatically. - - return null; -} - -/** - * Serves the pdfannotator files. - * - * @package mod_pdfannotator - * @category files - * @param stdClass $course course object - * @param stdClass $cm course module object - * @param stdClass $context context object - * @param string $filearea file area - * @param array $args extra arguments - * @param bool $forcedownload whether or not force download - * @param array $options additional options affecting the file serving - * @return bool false if file not found, does not return if found - just send the file - */ -function pdfannotator_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = array()) { - global $CFG, $DB; - require_once("$CFG->libdir/resourcelib.php"); - - if ($context->contextlevel != CONTEXT_MODULE) { - return false; - } - - require_course_login($course, true, $cm); - if (!has_capability('mod/pdfannotator:view', $context)) { - return false; - } - - if ($filearea !== 'content' && $filearea !== 'post') { - // Intro is handled automatically in pluginfile.php. - return false; - } - $fs = get_file_storage(); - if ($filearea === 'content') { - array_shift($args); // Ignore revision - designed to prevent caching problems only. - $relativepath = implode('/', $args); - $fullpath = rtrim("/$context->id/mod_pdfannotator/$filearea/0/$relativepath", '/'); - do { - if (!$file = $fs->get_file_by_hash(sha1($fullpath))) { - if ($fs->get_file_by_hash(sha1("$fullpath/."))) { - if ($file = $fs->get_file_by_hash(sha1("$fullpath/index.htm"))) { - break; - } - if ($file = $fs->get_file_by_hash(sha1("$fullpath/index.html"))) { - break; - } - if ($file = $fs->get_file_by_hash(sha1("$fullpath/Default.htm"))) { - break; - } - } - $pdfannotator = $DB->get_record('pdfannotator', array('id' => $cm->instance), 'id, legacyfiles', MUST_EXIST); - if ($pdfannotator->legacyfiles != RESOURCELIB_LEGACYFILES_ACTIVE) { - return false; - } - if (!$file = resourcelib_try_file_migration('/' . $relativepath, $cm->id, $cm->course, 'mod_pdfannotator', 'content', 0)) { - return false; - } - // File migrate - update flag. - $pdfannotator->legacyfileslast = time(); - $DB->update_record('pdfannotator', $pdfannotator); - } - } while (false); - - // Should we apply filters? - // $mimetype = $file->get_mimetype(); - $filter = 0; - // Finally send the file. - send_stored_file($file, null, $filter, $forcedownload, $options); - } - - if ($filearea === 'post') { - $commentid = 0; - foreach ($args as $param) { - if (filter_var($param, FILTER_VALIDATE_INT) && $DB->record_exists('pdfannotator_comments', ['id' => $param])) { - $commentid = $param; - break; - } - } - array_shift($args); - $relativepath = implode('/', $args); - $fullpath = rtrim("/$context->id/mod_pdfannotator/$filearea/$commentid/$relativepath", '/'); - if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { - //Annotations from other documents might have another contextid. - $pdfid = $DB->get_record('pdfannotator_comments', ['id' => $commentid], 'pdfannotatorid'); - if ($pdfid) { - $pdfannotator = $DB->get_record('pdfannotator', ['id' => $pdfid->pdfannotatorid], '*', MUST_EXIST); - $cm = get_coursemodule_from_instance('pdfannotator', $pdfid->pdfannotatorid, $pdfannotator->course, false, MUST_EXIST); - $context2 = context_module::instance($cm->id); - $fullpath = rtrim("/$context2->id/mod_pdfannotator/$filearea/$commentid/$relativepath", '/'); - if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { - return false; - } - send_stored_file($file, null, 0, true, $options); - } - } - send_stored_file($file, null, 0, true, $options); - } -} - -/** - * Return a list of page types - * @param string $pagetype current page type - * @param stdClass $parentcontext Block's parent context - * @param stdClass $currentcontext Current context of block - */ -function pdfannotator_page_type_list($pagetype, $parentcontext, $currentcontext) { - $modulepagetype = array('mod-pdfannotator-*' => get_string('page-mod-pdfannotator-x', 'pdfannotator')); - return $modulepagetype; -} - -/** - * Export file pdfannotator contents - * - * @return array of file content - */ -function pdfannotator_export_contents($cm, $baseurl) { - global $CFG, $DB; - $contents = array(); - $context = context_module::instance($cm->id); - $pdfannotator = $DB->get_record('pdfannotator', array('id' => $cm->instance), '*', MUST_EXIST); - if ($pdfannotator->useprint == 1) { - $fs = get_file_storage(); - $files = $fs->get_area_files($context->id, 'mod_pdfannotator', 'content', 0, 'sortorder DESC, id ASC', false); - $fileinfo = reset($files); - $file = array(); - $file['type'] = 'file'; - $file['filename'] = $fileinfo->get_filename(); - $file['filepath'] = $fileinfo->get_filepath(); - $file['filesize'] = $fileinfo->get_filesize(); - $file['mimetype'] = 'pdf'; - $file['fileurl'] = moodle_url::make_webservice_pluginfile_url( - $context->id, 'mod_pdfannotator', 'content', '1', $fileinfo->get_filepath(), $fileinfo->get_filename())->out(false); - $file['timecreated'] = $fileinfo->get_timecreated(); - $file['timemodified'] = $fileinfo->get_timemodified(); - $file['sortorder'] = $fileinfo->get_sortorder(); - $file['userid'] = $fileinfo->get_userid(); - $file['author'] = $fileinfo->get_author(); - $file['license'] = $fileinfo->get_license(); - $file['mimetype'] = $fileinfo->get_mimetype(); - $file['isexternalfile'] = $fileinfo->is_external_file(); - if ($file['isexternalfile']) { - $file['repositorytype'] = $fileinfo->get_repository_type(); - } - $contents[] = $file; - } - return $contents; -} - -/** - * Register the ability to handle drag and drop file uploads - * @return array containing details of the files / types the mod can handle - */ -// function pdfannotator_dndupload_register() { - // return array('files' => array( - // array('extension' => 'pdf', 'message' => get_string('dnduploadpdfannotator', 'mod_pdfannotator')) - // )); -// } - -/** - * Handle a file that has been uploaded - * @param object $uploadinfo details of the file / content that has been uploaded - * @return int instance id of the newly created mod - */ -// function pdfannotator_dndupload_handle($uploadinfo) { -// // Gather the required info. -// $data = new stdClass(); -// $data->course = $uploadinfo->course->id; -// $data->name = $uploadinfo->displayname; -// $data->intro = ''; -// $data->introformat = FORMAT_HTML; -// $data->coursemodule = $uploadinfo->coursemodule; -// $data->files = $uploadinfo->draftitemid; -// -// // Set the display options to the site defaults. -// $config = get_config('pdfannotator');// -// -// return pdfannotator_add_instance($data, null); -// } - -/** - * Mark the activity completed (if required) and trigger the course_module_viewed event. - * - * @param stdClass $pdfannotator pdfannotator object - * @param stdClass $course course object - * @param stdClass $cm course module object - * @param stdClass $context context object - * @since Moodle 3.0 - */ -function pdfannotator_view($pdfannotator, $course, $cm, $context) { - - // Trigger course_module_viewed event. - $params = array( - 'context' => $context, - 'objectid' => $pdfannotator->id - ); - - $event = \mod_pdfannotator\event\course_module_viewed::create($params); - $event->add_record_snapshot('course_modules', $cm); - $event->add_record_snapshot('course', $course); - $event->add_record_snapshot('pdfannotator', $pdfannotator); - $event->trigger(); - - // Completion. - $completion = new completion_info($course); - $completion->set_module_viewed($cm); -} - -/** - * Check if the module has any update that affects the current user since a given time. - * - * @param cm_info $cm course module data - * @param int $from the time to check updates from - * @param array $filter if we need to check only specific updates - * @return stdClass an object with the different type of areas indicating if they were updated or not - * @since Moodle 3.2 - */ -function pdfannotator_check_updates_since(cm_info $cm, $from, $filter = array()) { - $updates = course_check_module_updates_since($cm, $from, array('content'), $filter); - return $updates; -} - -/** - * This function receives a calendar event and returns the action associated with it, or null if there is none. - * - * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event - * is not displayed on the block. - * - * @param calendar_event $event - * @param \core_calendar\action_factory $factory - * @return \core_calendar\local\event\entities\action_interface|null - */ -function mod_pdfannotator_core_calendar_provide_event_action(calendar_event $event, \core_calendar\action_factory $factory) { - $cm = get_fast_modinfo($event->courseid)->instances['pdfannotator'][$event->instance]; - - $completion = new \completion_info($cm->get_course()); - - $completiondata = $completion->get_data($cm, false); - - if ($completiondata->completionstate != COMPLETION_INCOMPLETE) { - return null; - } - - return $factory->create_instance( - get_string('view'), new \moodle_url('/mod/pdfannotator/view.php', ['id' => $cm->id]), 1, true - ); -} - -/** - * Returns all annotations comments since a given time in specified annotator. - * - * @todo Document this functions args - * @param $activities - * @param $index - * @param $timestart - * @param $courseid - * @param $cmid - * @param int $userid - * @param int $groupid - * @throws dml_exception - * @throws moodle_exception - */ -function pdfannotator_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid = 0, $groupid = 0) { - global $CFG, $COURSE, $USER, $DB; - require_once($CFG->dirroot . '/mod/pdfannotator/locallib.php'); - if ($COURSE->id == $courseid) { - $course = $COURSE; - } else { - $course = $DB->get_record('course', array('id' => $courseid)); - } - - $modinfo = get_fast_modinfo($course); - - $cm = $modinfo->cms[$cmid]; - $params = array($timestart, $cm->instance); - - if ($userid) { - $userselect = "AND u.id = ? AND c.visibility='public'"; - $params[] = $userid; - } else { - $userselect = ""; - } - if ($groupid) { - $groupselect = "AND d.groupid = ?"; - $params[] = $groupid; - } else { - $groupselect = ""; - } - $allnames = get_all_user_name_fields(true, 'u'); - if (!$posts = $DB->get_records_sql("SELECT p.*,c.id, c.userid AS userid, c.visibility, c.content, c.timecreated, c.annotationid, c.isquestion, - $allnames, u.email, u.picture, u.imagealt, u.email, a.page - FROM {pdfannotator} p - JOIN {pdfannotator_annotations} a ON a.pdfannotatorid=p.id - JOIN {pdfannotator_comments} c ON c.annotationid = a.id - JOIN {user} u ON u.id = a.userid - WHERE c.timecreated > ? AND p.id = ? - $userselect AND c.isdeleted=0 - ORDER BY p.id ASC ", $params)) { // Order by initial posting date. - return; - } - $printposts = array(); - $context = context_course::instance($courseid); - foreach ($posts as $post) { - if (!pdfannotator_can_see_comment($post, $context)) { - continue; - } - $printposts[] = $post; - } - if (!$printposts) { - return; - } - - foreach ($printposts as $post) { - $tmpactivity = new stdClass(); - - $tmpactivity->type = 'pdfannotator'; - $tmpactivity->cmid = $cm->id; - $tmpactivity->name = format_string($cm->name, true); - $tmpactivity->sectionnum = $cm->sectionnum; - $tmpactivity->timestamp = $post->timecreated; - - $tmpactivity->content = new stdClass(); - $tmpactivity->content->id = $post->annotationid; - $tmpactivity->content->commid = $post->id; - $tmpactivity->content->isquestion = $post->isquestion; - $tmpactivity->content->discussion = format_string($post->content); - - $tmpactivity->content->page = $post->page; - $tmpactivity->visible = $post->visibility; - - $tmpactivity->user = new stdClass(); - // $additionalfields = array('id' => 'userid', 'picture', 'imagealt', 'email'); - $additionalfields = explode(',', user_picture::fields()); - $tmpactivity->user = username_load_fields_from_object($tmpactivity->user, $post, null, $additionalfields); - $tmpactivity->user->id = $post->userid; - - $activities[$index++] = $tmpactivity; - } - - return; -} - -/** - * Outputs the pdfannotator post indicated by $activity. - * - * @param object $activity the activity object the annotator resides in - * @param int $courseid the id of the course the annotator resides in - * @param bool $detail not used, but required for compatibilty with other modules - * @param int $modnames not used, but required for compatibilty with other modules - * @param bool $viewfullnames not used, but required for compatibilty with other modules - */ -function pdfannotator_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) { - global $OUTPUT; - - $content = $activity->content; - - $class = 'discussion'; - - $tableoptions = [ - 'border-top' => '0', - 'cellpadding' => '3', - 'cellspacing' => '0', - 'class' => '' - ]; - $output = html_writer::start_tag('table', $tableoptions); - $output .= html_writer::start_tag('tr'); - - $authorhidden = ($activity->visible == 'anonymous') ? 1 : 0; - - // Show user picture if author should not be hidden. - $pictureoptions = [ - 'courseid' => $courseid - ]; - if (!$authorhidden) { - $picture = $OUTPUT->user_picture($activity->user, $pictureoptions); - } else { - // $pictureoptions = [ 'courseid' => $courseid, 'link' => $authorhidden, 'alttext' => $authorhidden, ]; - $pic = $OUTPUT->image_url('/u/f2'); - $picture = '' . get_string('anonymous', 'pdfannotator') . ''; - } - $output .= html_writer::tag('td', $picture, ['class' => 'userpicture', 'valign' => 'top']); - - // Discussion title and author. - $output .= html_writer::start_tag('td', ['class' => $class]); - - $class = 'title'; - - $output .= html_writer::start_div($class); - if ($detail) { - $aname = s($activity->name); - $output .= $OUTPUT->image_icon('icon', $aname, $activity->type); - } - $isquestion = ($content->isquestion) ? '' . get_string('question', 'pdfannotator')
-            . ' ' : ''; - $discussionurl = new moodle_url('/mod/pdfannotator/view.php', ['id' => $activity->cmid, 'page' => $content->page, 'annoid' => $content->id, 'commid' => $content->commid]); - // $discussionurl->set_anchor('p' . $activity->content->id); - $output .= html_writer::link($discussionurl, ($isquestion . $content->discussion)); - $output .= html_writer::end_div(); - - $timestamp = userdate($activity->timestamp); - if ($authorhidden) { - $by = new stdClass(); - $by->name = get_string('anonymous', 'pdfannotator'); - $by->date = $timestamp; - $authornamedate = get_string('bynameondate', 'pdfannotator', $by); - } else { - $fullname = fullname($activity->user, $viewfullnames); - $userurl = new moodle_url('/user/view.php'); - $userurl->params(['id' => $activity->user->id, 'course' => $courseid]); - $by = new stdClass(); - $by->name = html_writer::link($userurl, $fullname); - $by->date = $timestamp; - $authornamedate = get_string('bynameondate', 'pdfannotator', $by); - } - $output .= html_writer::div($authornamedate, 'user'); - $output .= html_writer::end_tag('td'); - $output .= html_writer::end_tag('tr'); - $output .= html_writer::end_tag('table'); - - echo $output; -} - -/** - * Initialize the editor for editing a comment. - * @param type $args - * @return string - */ -function mod_pdfannotator_output_fragment_open_edit_comment_editor($args) { - global $DB; - - $context = context_module::instance($args['cmid']); - - $data = pdfannotator_data_preprocessing($context, 'editarea' . $args['uuid'], 0); - $comment = $DB->get_record('pdfannotator_comments', ['id' => $args['uuid']]); - $displaycontent = pdfannotator_file_prepare_draft_area($data['draftItemId'], $context->id, 'mod_pdfannotator', 'post', - $args['uuid'], pdfannotator_get_editor_options($context), $comment->content); - - /* $params = ['draftitemid' => $data['draftItemId'], 'editorformat' => $data['editorFormat'], $args['action'], 'targetId' => 'editor-editcomment-inputs-' . $args['uuid'], $args['uuid']]; - $PAGE->requires->js_init_call('loadEditorInputFields', $params); */ - - // Input fields. - $out = ''; - $out .= html_writer::empty_tag('input', ['type' => 'hidden', 'class' => 'pdfannotator_' . $args['action'] . 'comment' . '_editoritemid', 'name' => 'input_value_editor', 'value' => $data['draftItemId']]); - $out .= html_writer::empty_tag('input', ['type' => 'hidden', 'class' => 'pdfannotator_' . $args['action'] . 'comment' . '_editorformat', 'name' => 'input_value_editor', 'value' => $data['editorFormat']]); - $out .= 'displaycontent:' . $displaycontent; - - return $out; -} - -/** - * Initialize the editor for adding a comment. - * @param type $args - * @return string - */ -function mod_pdfannotator_output_fragment_open_add_comment_editor($args) { - $context = context_module::instance($args['cmid']); - - $data = pdfannotator_data_preprocessing($context, 'id_pdfannotator_content', 0); - $text = file_prepare_draft_area($data['draftItemId'], $context->id, 'mod_pdfannotator', 'post', 0, pdfannotator_get_editor_options($context)); - - $out = ''; - $out = html_writer::empty_tag('input', ['type' => 'hidden', 'class' => 'pdfannotator_' . $args['action'] . 'comment' . '_editoritemid', 'name' => 'input_value_editor', 'value' => $data['draftItemId']]); - $out .= html_writer::empty_tag('input', ['type' => 'hidden', 'class' => 'pdfannotator_' . $args['action'] . 'comment' . '_editorformat', 'name' => 'input_value_editor', 'value' => $data['editorFormat']]); - - return $out; -} +. +/** + * @package mod_pdfannotator + * @copyright 2018 RWTH Aachen (see README.md) + * @author Ahmad Obeid, Rabea de Groot, Anna Heynkes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +defined('MOODLE_INTERNAL') || die; + +define('PDFANNOTATOR_CHOOSESUBSCRIBE', 0); +define('PDFANNOTATOR_FORCESUBSCRIBE', 1); +define('PDFANNOTATOR_INITIALSUBSCRIBE', 2); +define('PDFANNOTATOR_DISALLOWSUBSCRIBE',3); + +require_once($CFG->dirroot . '/mod/pdfannotator/locallib.php'); + +// Ugly hack to make 3.11 and 4.0 work seamlessly. +if (!defined('FEATURE_MOD_PURPOSE')) { + define('FEATURE_MOD_PURPOSE', 'mod_purpose'); +} +if (!defined('MOD_PURPOSE_COMMUNICATION')) { + define('MOD_PURPOSE_COMMUNICATION', 'communication'); +} + +/** + * List of features supported in pdfannotator module + * @param string $feature FEATURE_xx constant for requested feature + * @return mixed True if module supports feature, false if not, null if doesn't know + */ +function pdfannotator_supports($feature) { + switch($feature) { + case FEATURE_GROUPS: + return true; + case FEATURE_GROUPINGS: + return true; + case FEATURE_MOD_INTRO: + return true; + case FEATURE_COMPLETION_TRACKS_VIEWS: + return true; + case FEATURE_COMPLETION_HAS_RULES: + return false; + case FEATURE_GRADE_HAS_GRADE: + return false; + case FEATURE_GRADE_OUTCOMES: + return false; + case FEATURE_BACKUP_MOODLE2: + return true; + case FEATURE_SHOW_DESCRIPTION: + return true; + case FEATURE_ADVANCED_GRADING: + return false; + case FEATURE_PLAGIARISM: + return false; + case FEATURE_COMMENT: + return false; + case FEATURE_MOD_PURPOSE: + return MOD_PURPOSE_COMMUNICATION; + default: + return null; + } +} +/** + * Function currently unused. + * + * @return string + */ +function mod_pdfannotator_before_standard_html_head() { + +} +/** + * Returns all other caps used in module + * @return array + */ +function pdfannotator_get_extra_capabilities() { + return array('moodle/site:accessallgroups'); +} +/** + * This function is used by the reset_course_userdata function in moodlelib. + * @param $data the data submitted from the reset course. + * @return array status array + */ +function pdfannotator_reset_userdata($data) { + return array(); +} + +/** + * List the actions that correspond to a view of this module. + * This is used by the participation report. + * + * Note: This is not used by new logging system. Event with + * crud = 'r' and edulevel = LEVEL_PARTICIPATING will + * be considered as view action. + * + * @return array + */ +function pdfannotator_get_view_actions() { + return array('view', 'view all'); +} + +/** + * List the actions that correspond to a post of this module. + * This is used by the participation report. + * + * Note: This is not used by new logging system. Event with + * crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING + * will be considered as post action. + * + * @return array + */ +function pdfannotator_get_post_actions() { + return array('update', 'add'); +} + +/** + * Add pdfannotator instance. + * @param object $data + * @param mod_pdfannotator_mod_form $mform + * @return int new pdfannotator instance id + */ +function pdfannotator_add_instance($data, $mform) { + global $CFG, $DB; + require_once("$CFG->libdir/resourcelib.php"); + require_once("$CFG->dirroot/mod/pdfannotator/locallib.php"); + $cmid = $data->coursemodule; + $data->timemodified = time(); + pdfannotator_set_display_options($data); + + $data->id = $DB->insert_record('pdfannotator', $data); + + // We need to use context now, so we need to make sure all needed info is already in db. + $DB->set_field('course_modules', 'instance', $data->id, array('id' => $cmid)); + pdfannotator_set_mainfile($data); + + $completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null; + \core_completion\api::update_completion_date_event($cmid, 'pdfannotator', $data->id, $completiontimeexpected); + + return $data->id; +} + +/** + * Update pdfannotator instance. + * @param object $data + * @param object $mform + * @return bool true + */ +function pdfannotator_update_instance($data, $mform) { + global $CFG, $DB; + require_once("$CFG->libdir/resourcelib.php"); + require_once("$CFG->dirroot/mod/pdfannotator/locallib.php"); + $data->timemodified = time(); + $data->id = $data->instance; + $data->revision++; + + pdfannotator_set_display_options($data); // Can be deleted or extended. + + $DB->update_record('pdfannotator', $data); + pdfannotator_set_mainfile($data); + + $completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null; + \core_completion\api::update_completion_date_event($data->coursemodule, 'pdfannotator', $data->id, $completiontimeexpected); + + return true; +} + +/** + * Updates display options based on form input. + * + * Shared code used by pdfannotator_add_instance and pdfannotator_update_instance. + * keep it, if you want defind more disply options + * @param object $data Data object + */ +function pdfannotator_set_display_options($data) { + $displayoptions = array(); + $displayoptions['printintro'] = (int) !empty($data->printintro); + $data->displayoptions = serialize($displayoptions); +} + +/** + * Delete pdfannotator instance. + * @param int $id in mdl_pdfannotator + * @return bool true + */ +function pdfannotator_delete_instance($id) { + + global $DB; + + if (!$pdfannotator = $DB->get_record('pdfannotator', array('id' => $id))) { + return false; + } + + $cm = get_coursemodule_from_instance('pdfannotator', $id); + \core_completion\api::update_completion_date_event($cm->id, 'pdfannotator', $id, null); + + // Note: all context files are deleted automatically. + // 1.a) Get all annotations of the annotator. + $annotations = $DB->get_records('pdfannotator_annotations', ['pdfannotatorid' => $id]); + + // 1.b) For every annotation delete all subscriptions attached to it. + foreach ($annotations as $annotation) { + if (!$DB->delete_records('pdfannotator_subscriptions', ['annotationid' => $annotation->id]) == 1) { + return false; + } + } + // 1.c) Then delete the annotations from the annotations table. + if (!$DB->delete_records('pdfannotator_annotations', ['pdfannotatorid' => $id]) == 1) { + return false; + } + + // 2.a) Get all comments in this annotator. + $comments = $DB->get_records('pdfannotator_comments', ['pdfannotatorid' => $id]); + + // 2.b) Delete all votes in this annotator. + foreach ($comments as $comment) { + if (!$DB->delete_records('pdfannotator_votes', ['commentid' => $comment->id]) == 1) { + return false; + } + } + // 2.c) Delete all comments in this annotator. + if (!$DB->delete_records('pdfannotator_comments', ['pdfannotatorid' => $id]) == 1) { + return false; + } + + // 3. Deleting all the reports. + if (!$DB->delete_records('pdfannotator_reports', ['pdfannotatorid' => $id])) { + return false; + } + + // 4. Delete the annotator itself. + if (!$DB->delete_records('pdfannotator', array('id' => $id)) == 1) { + return false; + } + + return true; +} + +/** + * Given a course_module object, this function returns any + * "extra" information that may be needed when printing + * this activity in a course listing. + * + * See {@see get_array_of_activities()} in course/lib.php + * + * @param stdClass $coursemodule + * @return cached_cm_info info + */ +function pdfannotator_get_coursemodule_info($coursemodule) { + global $CFG, $DB; + require_once("$CFG->libdir/filelib.php"); + require_once("$CFG->dirroot/mod/pdfannotator/locallib.php"); + require_once($CFG->libdir . '/completionlib.php'); + + $context = context_module::instance($coursemodule->id); + + if (!$pdfannotator = $DB->get_record('pdfannotator', array('id' => $coursemodule->instance), 'id, name, course, + timemodified, timecreated, intro, introformat')) { + return null; + } + + $info = new cached_cm_info(); + $info->name = $pdfannotator->name; + if ($coursemodule->showdescription) { + // Convert intro to html. Do not filter cached version, filters run at display time. + $info->content = format_module_intro('pdfannotator', $pdfannotator, $coursemodule->id, false); + } + + // See if there is at least one file. + $fs = get_file_storage(); + $files = $fs->get_area_files($context->id, 'mod_pdfannotator', 'content', 0, 'sortorder DESC, id ASC', false, 0, 0, 1); + if (count($files) >= 1) { + $mainfile = reset($files); + // $info->icon = file_file_icon($mainfile, 24); // Uncomment to use pdf icon. + $pdfannotator->mainfile = $mainfile->get_filename(); + } + // If any optional extra details are turned on, store in custom data, + // add some file details as well to be used later by pdfannotator_get_optional_details() without retriving. + // Do not store filedetails if this is a reference - they will still need to be retrieved every time. + + return $info; +} + +/** + * Called when viewing course page. Shows extra details after the link if + * enabled. + * + * @param cm_info $cm Course module information + */ +function pdfannotator_cm_info_view(cm_info $cm) { + global $CFG; + require_once($CFG->dirroot . '/mod/pdfannotator/locallib.php'); + $cm->set_after_link(' ' . html_writer::tag('span', '', // Use this to show details. + array('class' => 'pdfannotatorlinkdetails'))); +} + +/** + * Lists all browsable file areas + * + * @package mod_pdfannotator + * @category files + * @param stdClass $course course object + * @param stdClass $cm course module object + * @param stdClass $context context object + * @return array + */ +function pdfannotator_get_file_areas($course, $cm, $context) { + $areas = array(); + $areas['content'] = get_string('pdfannotatorcontent', 'pdfannotator'); + $areas['post'] = get_string('pdfannotatorpost', 'pdfannotator'); + return $areas; +} + +/** + * File browsing support for pdfannotator module content area. + * + * @package mod_pdfannotator + * @category files + * @param stdClass $browser file browser instance + * @param stdClass $areas file areas + * @param stdClass $course course object + * @param stdClass $cm course module object + * @param stdClass $context context object + * @param string $filearea file area + * @param int $itemid item ID + * @param string $filepath file path + * @param string $filename file name + * @return file_info instance or null if not found + */ +function pdfannotator_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) { + global $CFG; + + if (!has_capability('moodle/course:managefiles', $context)) { + // Students can not peak here! + return null; + } + + $fs = get_file_storage(); + + if ($filearea === 'content') { + $filepath = is_null($filepath) ? '/' : $filepath; + $filename = is_null($filename) ? '.' : $filename; + + $urlbase = $CFG->wwwroot . '/pluginfile.php'; + if (!$storedfile = $fs->get_file($context->id, 'mod_pdfannotator', 'content', 0, $filepath, $filename)) { + if ($filepath === '/' and $filename === '.') { + $storedfile = new virtual_root_file($context->id, 'mod_pdfannotator', 'content', 0); + } else { + // Not found. + return null; + } + } + require_once("$CFG->dirroot/mod/pdfannotator/locallib.php"); + return new pdfannotator_content_file_info($browser, $context, $storedfile, $urlbase, $areas[$filearea], true, true, true, false); + } + + // Note: pdfannotator_intro handled in file_browser automatically. + + return null; +} + +/** + * Serves the pdfannotator files. + * + * @package mod_pdfannotator + * @category files + * @param stdClass $course course object + * @param stdClass $cm course module object + * @param stdClass $context context object + * @param string $filearea file area + * @param array $args extra arguments + * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving + * @return bool false if file not found, does not return if found - just send the file + */ +function pdfannotator_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = array()) { + global $CFG, $DB; + require_once("$CFG->libdir/resourcelib.php"); + + if ($context->contextlevel != CONTEXT_MODULE) { + return false; + } + + require_course_login($course, true, $cm); + if (!has_capability('mod/pdfannotator:view', $context)) { + return false; + } + + if ($filearea !== 'content' && $filearea !== 'post') { + // Intro is handled automatically in pluginfile.php. + return false; + } + $fs = get_file_storage(); + if ($filearea === 'content') { + array_shift($args); // Ignore revision - designed to prevent caching problems only. + $relativepath = implode('/', $args); + $fullpath = rtrim("/$context->id/mod_pdfannotator/$filearea/0/$relativepath", '/'); + do { + if (!$file = $fs->get_file_by_hash(sha1($fullpath))) { + if ($fs->get_file_by_hash(sha1("$fullpath/."))) { + if ($file = $fs->get_file_by_hash(sha1("$fullpath/index.htm"))) { + break; + } + if ($file = $fs->get_file_by_hash(sha1("$fullpath/index.html"))) { + break; + } + if ($file = $fs->get_file_by_hash(sha1("$fullpath/Default.htm"))) { + break; + } + } + $pdfannotator = $DB->get_record('pdfannotator', array('id' => $cm->instance), 'id, legacyfiles', MUST_EXIST); + if ($pdfannotator->legacyfiles != RESOURCELIB_LEGACYFILES_ACTIVE) { + return false; + } + if (!$file = resourcelib_try_file_migration('/' . $relativepath, $cm->id, $cm->course, 'mod_pdfannotator', 'content', 0)) { + return false; + } + // File migrate - update flag. + $pdfannotator->legacyfileslast = time(); + $DB->update_record('pdfannotator', $pdfannotator); + } + } while (false); + + // Should we apply filters? + // $mimetype = $file->get_mimetype(); + $filter = 0; + // Finally send the file. + send_stored_file($file, null, $filter, $forcedownload, $options); + } + + if ($filearea === 'post') { + $commentid = 0; + foreach ($args as $param) { + if (filter_var($param, FILTER_VALIDATE_INT) && $DB->record_exists('pdfannotator_comments', ['id' => $param])) { + $commentid = $param; + break; + } + } + array_shift($args); + $relativepath = implode('/', $args); + $fullpath = rtrim("/$context->id/mod_pdfannotator/$filearea/$commentid/$relativepath", '/'); + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { + //Annotations from other documents might have another contextid. + $pdfid = $DB->get_record('pdfannotator_comments', ['id' => $commentid], 'pdfannotatorid'); + if ($pdfid) { + $pdfannotator = $DB->get_record('pdfannotator', ['id' => $pdfid->pdfannotatorid], '*', MUST_EXIST); + $cm = get_coursemodule_from_instance('pdfannotator', $pdfid->pdfannotatorid, $pdfannotator->course, false, MUST_EXIST); + $context2 = context_module::instance($cm->id); + $fullpath = rtrim("/$context2->id/mod_pdfannotator/$filearea/$commentid/$relativepath", '/'); + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { + return false; + } + send_stored_file($file, null, 0, true, $options); + } + } + send_stored_file($file, null, 0, true, $options); + } +} + +/** + * Return a list of page types + * @param string $pagetype current page type + * @param stdClass $parentcontext Block's parent context + * @param stdClass $currentcontext Current context of block + */ +function pdfannotator_page_type_list($pagetype, $parentcontext, $currentcontext) { + $modulepagetype = array('mod-pdfannotator-*' => get_string('page-mod-pdfannotator-x', 'pdfannotator')); + return $modulepagetype; +} + +/** + * Export file pdfannotator contents + * + * @return array of file content + */ +function pdfannotator_export_contents($cm, $baseurl) { + global $CFG, $DB; + $contents = array(); + $context = context_module::instance($cm->id); + $pdfannotator = $DB->get_record('pdfannotator', array('id' => $cm->instance), '*', MUST_EXIST); + if ($pdfannotator->useprint == 1) { + $fs = get_file_storage(); + $files = $fs->get_area_files($context->id, 'mod_pdfannotator', 'content', 0, 'sortorder DESC, id ASC', false); + $fileinfo = reset($files); + $file = array(); + $file['type'] = 'file'; + $file['filename'] = $fileinfo->get_filename(); + $file['filepath'] = $fileinfo->get_filepath(); + $file['filesize'] = $fileinfo->get_filesize(); + $file['mimetype'] = 'pdf'; + $file['fileurl'] = moodle_url::make_webservice_pluginfile_url( + $context->id, 'mod_pdfannotator', 'content', '1', $fileinfo->get_filepath(), $fileinfo->get_filename())->out(false); + $file['timecreated'] = $fileinfo->get_timecreated(); + $file['timemodified'] = $fileinfo->get_timemodified(); + $file['sortorder'] = $fileinfo->get_sortorder(); + $file['userid'] = $fileinfo->get_userid(); + $file['author'] = $fileinfo->get_author(); + $file['license'] = $fileinfo->get_license(); + $file['mimetype'] = $fileinfo->get_mimetype(); + $file['isexternalfile'] = $fileinfo->is_external_file(); + if ($file['isexternalfile']) { + $file['repositorytype'] = $fileinfo->get_repository_type(); + } + $contents[] = $file; + } + return $contents; +} + +/** + * Register the ability to handle drag and drop file uploads + * @return array containing details of the files / types the mod can handle + */ +// function pdfannotator_dndupload_register() { + // return array('files' => array( + // array('extension' => 'pdf', 'message' => get_string('dnduploadpdfannotator', 'mod_pdfannotator')) + // )); +// } + +/** + * Handle a file that has been uploaded + * @param object $uploadinfo details of the file / content that has been uploaded + * @return int instance id of the newly created mod + */ +// function pdfannotator_dndupload_handle($uploadinfo) { +// // Gather the required info. +// $data = new stdClass(); +// $data->course = $uploadinfo->course->id; +// $data->name = $uploadinfo->displayname; +// $data->intro = ''; +// $data->introformat = FORMAT_HTML; +// $data->coursemodule = $uploadinfo->coursemodule; +// $data->files = $uploadinfo->draftitemid; +// +// // Set the display options to the site defaults. +// $config = get_config('pdfannotator');// +// +// return pdfannotator_add_instance($data, null); +// } + +/** + * Mark the activity completed (if required) and trigger the course_module_viewed event. + * + * @param stdClass $pdfannotator pdfannotator object + * @param stdClass $course course object + * @param stdClass $cm course module object + * @param stdClass $context context object + * @since Moodle 3.0 + */ +function pdfannotator_view($pdfannotator, $course, $cm, $context) { + + // Trigger course_module_viewed event. + $params = array( + 'context' => $context, + 'objectid' => $pdfannotator->id + ); + + $event = \mod_pdfannotator\event\course_module_viewed::create($params); + $event->add_record_snapshot('course_modules', $cm); + $event->add_record_snapshot('course', $course); + $event->add_record_snapshot('pdfannotator', $pdfannotator); + $event->trigger(); + + // Completion. + $completion = new completion_info($course); + $completion->set_module_viewed($cm); +} + +/** + * Check if the module has any update that affects the current user since a given time. + * + * @param cm_info $cm course module data + * @param int $from the time to check updates from + * @param array $filter if we need to check only specific updates + * @return stdClass an object with the different type of areas indicating if they were updated or not + * @since Moodle 3.2 + */ +function pdfannotator_check_updates_since(cm_info $cm, $from, $filter = array()) { + $updates = course_check_module_updates_since($cm, $from, array('content'), $filter); + return $updates; +} + +/** + * This function receives a calendar event and returns the action associated with it, or null if there is none. + * + * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event + * is not displayed on the block. + * + * @param calendar_event $event + * @param \core_calendar\action_factory $factory + * @return \core_calendar\local\event\entities\action_interface|null + */ +function mod_pdfannotator_core_calendar_provide_event_action(calendar_event $event, \core_calendar\action_factory $factory) { + $cm = get_fast_modinfo($event->courseid)->instances['pdfannotator'][$event->instance]; + + $completion = new \completion_info($cm->get_course()); + + $completiondata = $completion->get_data($cm, false); + + if ($completiondata->completionstate != COMPLETION_INCOMPLETE) { + return null; + } + + return $factory->create_instance( + get_string('view'), new \moodle_url('/mod/pdfannotator/view.php', ['id' => $cm->id]), 1, true + ); +} + +/** + * Returns all annotations comments since a given time in specified annotator. + * + * @todo Document this functions args + * @param $activities + * @param $index + * @param $timestart + * @param $courseid + * @param $cmid + * @param int $userid + * @param int $groupid + * @throws dml_exception + * @throws moodle_exception + */ +function pdfannotator_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid = 0, $groupid = 0) { + global $CFG, $COURSE, $USER, $DB; + require_once($CFG->dirroot . '/mod/pdfannotator/locallib.php'); + if ($COURSE->id == $courseid) { + $course = $COURSE; + } else { + $course = $DB->get_record('course', array('id' => $courseid)); + } + + $modinfo = get_fast_modinfo($course); + + $cm = $modinfo->cms[$cmid]; + $params = array($timestart, $cm->instance); + + if ($userid) { + $userselect = "AND u.id = ? AND c.visibility='public'"; + $params[] = $userid; + } else { + $userselect = ""; + } + if ($groupid) { + $groupselect = "AND d.groupid = ?"; + $params[] = $groupid; + } else { + $groupselect = ""; + } + $allnames = get_all_user_name_fields(true, 'u'); + if (!$posts = $DB->get_records_sql("SELECT p.*,c.id, c.userid AS userid, c.visibility, c.content, c.timecreated, c.annotationid, c.isquestion, + $allnames, u.email, u.picture, u.imagealt, u.email, a.page + FROM {pdfannotator} p + JOIN {pdfannotator_annotations} a ON a.pdfannotatorid=p.id + JOIN {pdfannotator_comments} c ON c.annotationid = a.id + JOIN {user} u ON u.id = a.userid + WHERE c.timecreated > ? AND p.id = ? + $userselect AND c.isdeleted=0 + ORDER BY p.id ASC ", $params)) { // Order by initial posting date. + return; + } + $printposts = array(); + $context = context_course::instance($courseid); + foreach ($posts as $post) { + if (!pdfannotator_can_see_comment($post, $context)) { + continue; + } + $printposts[] = $post; + } + if (!$printposts) { + return; + } + + foreach ($printposts as $post) { + $tmpactivity = new stdClass(); + + $tmpactivity->type = 'pdfannotator'; + $tmpactivity->cmid = $cm->id; + $tmpactivity->name = format_string($cm->name, true); + $tmpactivity->sectionnum = $cm->sectionnum; + $tmpactivity->timestamp = $post->timecreated; + + $tmpactivity->content = new stdClass(); + $tmpactivity->content->id = $post->annotationid; + $tmpactivity->content->commid = $post->id; + $tmpactivity->content->isquestion = $post->isquestion; + $tmpactivity->content->discussion = format_string($post->content); + + $tmpactivity->content->page = $post->page; + $tmpactivity->visible = $post->visibility; + + $tmpactivity->user = new stdClass(); + // $additionalfields = array('id' => 'userid', 'picture', 'imagealt', 'email'); + $additionalfields = explode(',', user_picture::fields()); + $tmpactivity->user = username_load_fields_from_object($tmpactivity->user, $post, null, $additionalfields); + $tmpactivity->user->id = $post->userid; + + $activities[$index++] = $tmpactivity; + } + + return; +} + +/** + * Outputs the pdfannotator post indicated by $activity. + * + * @param object $activity the activity object the annotator resides in + * @param int $courseid the id of the course the annotator resides in + * @param bool $detail not used, but required for compatibilty with other modules + * @param int $modnames not used, but required for compatibilty with other modules + * @param bool $viewfullnames not used, but required for compatibilty with other modules + */ +function pdfannotator_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) { + global $OUTPUT; + + $content = $activity->content; + + $class = 'discussion'; + + $tableoptions = [ + 'border-top' => '0', + 'cellpadding' => '3', + 'cellspacing' => '0', + 'class' => '' + ]; + $output = html_writer::start_tag('table', $tableoptions); + $output .= html_writer::start_tag('tr'); + + $authorhidden = ($activity->visible == 'anonymous') ? 1 : 0; + + // Show user picture if author should not be hidden. + $pictureoptions = [ + 'courseid' => $courseid + ]; + if (!$authorhidden) { + $picture = $OUTPUT->user_picture($activity->user, $pictureoptions); + } else { + // $pictureoptions = [ 'courseid' => $courseid, 'link' => $authorhidden, 'alttext' => $authorhidden, ]; + $pic = $OUTPUT->image_url('/u/f2'); + $picture = '' . get_string('anonymous', 'pdfannotator') . ''; + } + $output .= html_writer::tag('td', $picture, ['class' => 'userpicture', 'valign' => 'top']); + + // Discussion title and author. + $output .= html_writer::start_tag('td', ['class' => $class]); + + $class = 'title'; + + $output .= html_writer::start_div($class); + if ($detail) { + $aname = s($activity->name); + $output .= $OUTPUT->image_icon('icon', $aname, $activity->type); + } + $isquestion = ($content->isquestion) ? '' . get_string('question', 'pdfannotator')
+            . ' ' : ''; + $discussionurl = new moodle_url('/mod/pdfannotator/view.php', ['id' => $activity->cmid, 'page' => $content->page, 'annoid' => $content->id, 'commid' => $content->commid]); + // $discussionurl->set_anchor('p' . $activity->content->id); + $output .= html_writer::link($discussionurl, ($isquestion . $content->discussion)); + $output .= html_writer::end_div(); + + $timestamp = userdate($activity->timestamp); + if ($authorhidden) { + $by = new stdClass(); + $by->name = get_string('anonymous', 'pdfannotator'); + $by->date = $timestamp; + $authornamedate = get_string('bynameondate', 'pdfannotator', $by); + } else { + $fullname = fullname($activity->user, $viewfullnames); + $userurl = new moodle_url('/user/view.php'); + $userurl->params(['id' => $activity->user->id, 'course' => $courseid]); + $by = new stdClass(); + $by->name = html_writer::link($userurl, $fullname); + $by->date = $timestamp; + $authornamedate = get_string('bynameondate', 'pdfannotator', $by); + } + $output .= html_writer::div($authornamedate, 'user'); + $output .= html_writer::end_tag('td'); + $output .= html_writer::end_tag('tr'); + $output .= html_writer::end_tag('table'); + + echo $output; +} + +/** + * Initialize the editor for editing a comment. + * @param type $args + * @return string + */ +function mod_pdfannotator_output_fragment_open_edit_comment_editor($args) { + global $DB; + + $context = context_module::instance($args['cmid']); + + $data = pdfannotator_data_preprocessing($context, 'editarea' . $args['uuid'], 0); + $comment = $DB->get_record('pdfannotator_comments', ['id' => $args['uuid']]); + $displaycontent = pdfannotator_file_prepare_draft_area($data['draftItemId'], $context->id, 'mod_pdfannotator', 'post', + $args['uuid'], pdfannotator_get_editor_options($context), $comment->content); + + /* $params = ['draftitemid' => $data['draftItemId'], 'editorformat' => $data['editorFormat'], $args['action'], 'targetId' => 'editor-editcomment-inputs-' . $args['uuid'], $args['uuid']]; + $PAGE->requires->js_init_call('loadEditorInputFields', $params); */ + + // Input fields. + $out = ''; + $out .= html_writer::empty_tag('input', ['type' => 'hidden', 'class' => 'pdfannotator_' . $args['action'] . 'comment' . '_editoritemid', 'name' => 'input_value_editor', 'value' => $data['draftItemId']]); + $out .= html_writer::empty_tag('input', ['type' => 'hidden', 'class' => 'pdfannotator_' . $args['action'] . 'comment' . '_editorformat', 'name' => 'input_value_editor', 'value' => $data['editorFormat']]); + $out .= 'displaycontent:' . $displaycontent; + + return $out; +} + +/** + * Initialize the editor for adding a comment. + * @param type $args + * @return string + */ +function mod_pdfannotator_output_fragment_open_add_comment_editor($args) { + $context = context_module::instance($args['cmid']); + + $data = pdfannotator_data_preprocessing($context, 'id_pdfannotator_content', 0); + $text = file_prepare_draft_area($data['draftItemId'], $context->id, 'mod_pdfannotator', 'post', 0, pdfannotator_get_editor_options($context)); + + $out = ''; + $out = html_writer::empty_tag('input', ['type' => 'hidden', 'class' => 'pdfannotator_' . $args['action'] . 'comment' . '_editoritemid', 'name' => 'input_value_editor', 'value' => $data['draftItemId']]); + $out .= html_writer::empty_tag('input', ['type' => 'hidden', 'class' => 'pdfannotator_' . $args['action'] . 'comment' . '_editorformat', 'name' => 'input_value_editor', 'value' => $data['editorFormat']]); + + return $out; +} + +/** + * List the options for pdfannotator subscription modes. + * This is used by the settings page and by the mod_form page. + * + * @return array + */ +function pdfannotator_get_subscriptionmode_options() { + $options = array(); + $options[PDFANNOTATOR_INITIALSUBSCRIBE] = get_string('subscriptionauto', 'pdfannotator'); + $options[PDFANNOTATOR_CHOOSESUBSCRIBE] = get_string('subscriptionoptional', 'pdfannotator'); + $options[PDFANNOTATOR_FORCESUBSCRIBE] = get_string('subscriptionforced', 'pdfannotator'); + $options[PDFANNOTATOR_DISALLOWSUBSCRIBE] = get_string('subscriptiondisabled', 'pdfannotator'); + return $options; +} + diff --git a/mod_form.php b/mod_form.php index cf4f7fd..dbbbbf5 100644 --- a/mod_form.php +++ b/mod_form.php @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** + * The pdfannotator module form + * * @package mod_pdfannotator * @copyright 2018 RWTH Aachen (see README.md) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -94,20 +96,20 @@ public function definition() { $mform->setDefault('useprint', $config->useprint); $mform->addHelpButton('useprint', 'setting_useprint_document', 'pdfannotator'); - $mform->addElement('advcheckbox', 'useprintcomments', get_string('setting_useprint_comments', 'pdfannotator'), - get_string('useprint_comments', 'pdfannotator'), null, array(0, 1)); + $mform->addElement('advcheckbox', 'useprintcomments', get_string('setting_useprint_comments', + 'pdfannotator'), get_string('useprint_comments', 'pdfannotator'), null, array(0, 1)); $mform->setType('useprintcomments', PARAM_BOOL); $mform->setDefault('useprintcomments', $config->useprintcomments); $mform->addHelpButton('useprintcomments', 'setting_useprint_comments', 'pdfannotator'); - $mform->addElement('advcheckbox', 'useprivatecomments', get_string('setting_use_private_comments', 'pdfannotator'), - get_string('use_private_comments', 'pdfannotator'), null, array(0, 1)); + $mform->addElement('advcheckbox', 'useprivatecomments', get_string('setting_use_private_comments', + 'pdfannotator'), get_string('use_private_comments', 'pdfannotator'), null, array(0, 1)); $mform->setType('useprivatecomments', PARAM_BOOL); $mform->setDefault('useprivatecomments', $config->use_private_comments); $mform->addHelpButton('useprivatecomments', 'setting_use_private_comments', 'pdfannotator'); - $mform->addElement('advcheckbox', 'useprotectedcomments', get_string('setting_use_protected_comments', 'pdfannotator'), - get_string('use_protected_comments', 'pdfannotator'), null, array(0, 1)); + $mform->addElement('advcheckbox', 'useprotectedcomments', get_string('setting_use_protected_comments', + 'pdfannotator'), get_string('use_protected_comments', 'pdfannotator'), null, array(0, 1)); $mform->setType('useprotectedcomments', PARAM_BOOL); $mform->setDefault('useprotectedcomments', $config->use_protected_comments); $mform->addHelpButton('useprotectedcomments', 'setting_use_protected_comments', 'pdfannotator'); @@ -119,6 +121,20 @@ public function definition() { $mform->addElement('select', 'legacyfiles', get_string('legacyfiles', 'pdfannotator'), $options); } + + // Subscription and tracking. + $mform->addElement('header', 'subscriptionandtrackinghdr', get_string('subscription', 'pdfannotator')); + + $options = pdfannotator_get_subscriptionmode_options(); + $mform->addElement('select', 'forcesubscribe', get_string('subscriptionmode', 'pdfannotator'), $options); + $mform->addHelpButton('forcesubscribe', 'subscriptionmode', 'pdfannotator'); + if (isset($CFG->pdfannotator_subscription)) { + $defaultpdfannotatorsubscription = $CFG->pdfannotator_subscription; + } else { + $defaultpdfannotatorsubscription = PDFANNOTATOR_INITIALSUBSCRIBE; + } + $mform->setDefault('forcesubscribe', $defaultpdfannotatorsubscription); + $this->standard_coursemodule_elements(); $this->add_action_buttons(); diff --git a/templates/comment.mustache b/templates/comment.mustache index 39e147d..f194032 100644 --- a/templates/comment.mustache +++ b/templates/comment.mustache @@ -1,3 +1,93 @@ +{{! + 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 . +}} +{{! + @template mod_pdfannotator/comment + Template which displays a comment. + Classes required for JS: + * none + Data attributes required for JS: + * none + Example context (json): + { + "comments": [ + { + "userid": "103", + "visibility": "public", + "isquestion": true, + "annotationid": 1, + "annotation": 1, + "timecreated": "58 minutes ago", + "isdeleted": false, + "uuid": "1", + "ishidden": 0, + "content": "Comment", + "username": "me", + "solved": false, + "votes": "0", + "isvoted": false, + "usevotes": true, + "issubscribed": true, + "buttons": [ + { + "classes": "comment-solve-a", + "faicon": { + "class": "fa-lock" + }, + "text": "Close question" + }, + { + "classes": "comment-edit-a", + "attributes": { + "name": "id", + "value": "editButton1" + }, + "moodleicon": { + "key": "i/edit", + "component": "core", + "title": "Edit" + }, + "text": "Edit" + }, + { + "classes": "comment-delete-a", + "text": "Delete", + "moodleicon": { + "key": "delete", + "component": "pdfannotator", + "title": "Delete" + } + }, + { + "classes": "comment-subscribe-a", + "faicon": { + "class": "fa-bell-slash" + }, + "text": "Unsubscribe" + } + ], + "owner": true, + "private": false, + "protected": false, + "wrapperClass": "chat-message comment-list-item questioncomment owner usevotes", + "voteBtn": "own comment", + "voteTitle": "0 persons are also interested in this question", + "modifiedby": null, + "dropdown": true + } + ] + } +}} {{#comments }}
diff --git a/tests/.DS_Store b/tests/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..da5154159fe9d6a913fe1ced93bdfb4abc0eb8b8 GIT binary patch literal 6148 zcmeHKJ8DBQ5S)!&JV@ixrLK@02;-a}7YK=M2m}mq3@KmbbLD86{S+}T?wrQV!fK?| zj)bOo`xbz$j++Ny0brmz;^f2BeBXUycNH-roo5{Jfh`WZ&Ea*D{dd5*19o`B7e0UU zU-tWV-;_xKDIf);fE17dS5=_O>u`V7Q+1dWkOH@$fPWtv-LV&riSg;+5G??4&Tts# z(Mu4U2Z+6JOk{*+NhK!Ls>QIRGu|q%7mkTZhgI`ob+c86VsSgqZ;=k`i5jJV6gXF4 zmdl0L|3~_T{{Nh$l@yQyH>H5h*H7yepH#JV@;I-xjs8scoNu}t=Rx5R<(L@dm. + +/** + * Behat pdfannotator-related steps definitions. + * + * @package pdfannotator + * @category test + * @copyright 2021 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. + +require_once(__DIR__ . '/../../../../lib/behat/behat_base.php'); + +/** + * Steps definitions related with the pdfannotator. + * + * @package pdfannotator + * @category test + * @copyright 2021 Luca Bösch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class behat_pdfannotator_editpdf extends behat_base { + + /** + * Point at the pdfannotator pdf. + * + * @When /^I point at the pdfannotator canvas$/ + */ + public function i_point_at_the_pdfannotator_canvas() { + $node = $this->find('xpath', '//div[@id=\'pageContainer1\']'); + $this->execute('behat_general::i_click_on', [$node, 'NodeElement']); + } + + /** + * Point at the pdfannotator pdf. + * + * @When /^I click the pdfannotator public comment dropdown menu button$/ + */ + public function i_click_the_pdfannotator_public_comment_dropdown_menu_button() { + $node = $this->find('xpath', '//a[@id=\'dropdownMenuButton\']'); + $this->execute('behat_general::i_click_on', [$node, 'NodeElement']); + } +} diff --git a/tests/fixtures/submission.pdf b/tests/fixtures/submission.pdf new file mode 100644 index 0000000000000000000000000000000000000000..576d37832e921287cfbd2e5f63bdea43534789c9 GIT binary patch literal 24751 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 z4wr+Xget_records_select('pdfannotator_annotations', $sql, $params)); - $this->assertCount(0, $count_annotations); + $this->assertCount(0, $count_annotations); $count_reports = count($DB->get_records_select('pdfannotator_reports', $sql, $params)); $this->assertCount(0, $count_reports); $count_comments = count($DB->get_records_select('pdfannotator_comments', $sql, $params)); @@ -226,6 +226,6 @@ public function test_delete_data_for_users() { AND imgs.userid {$userinsql} AND imgs.itemid {$commentinsql}", array_merge($userinparams, $commentinparams)); - $this->assertCount(0, $count_pics); + $this->assertCount(0, $count_pics); } } \ No newline at end of file diff --git a/version.php b/version.php index 10c067d..deae53d 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'mod_pdfannotator'; -$plugin->version = 2023112900; +$plugin->version = 2023112901; $plugin->release = 'PDF Annotator v1.5 release 5'; $plugin->requires = 2021051700; $plugin->maturity = MATURITY_STABLE;