Skip to content
Snippets Groups Projects
locallib.php 71.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • Friederike Schwager's avatar
    Friederike Schwager committed
    <?php
    // This file is part of Moodle - http://moodle.org/
    //
    // Moodle is free software: you can redistribute it and/or modify
    // it under the terms of the GNU General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // (at your option) any later version.
    //
    // Moodle is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU General Public License for more details.
    //
    // You should have received a copy of the GNU General Public License
    // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
    /**
     * @package   mod_pdfannotator
     * @copyright 2018 RWTH Aachen (see README)
    
    Luca Bösch's avatar
    Luca Bösch committed
     * @author    Rabea de Groot, Anna Heynkes, Friederike Schwager
    
    Friederike Schwager's avatar
    Friederike Schwager committed
     * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
     */
    
    
    use mod_pdfannotator\output\answermenu;
    use mod_pdfannotator\output\questionmenu;
    use mod_pdfannotator\output\reportmenu;
    use mod_pdfannotator\output\index;
    
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    defined('MOODLE_INTERNAL') || die;
    
    require_once("$CFG->libdir/filelib.php");
    require_once("$CFG->libdir/resourcelib.php");
    require_once("$CFG->dirroot/mod/pdfannotator/lib.php");
    
    /**
     * Display embedded pdfannotator file.
     * @param object $pdfannotator
     * @param object $cm
     * @param object $course
     * @param stored_file $file main file
     * @return does not return
     */
    function pdfannotator_display_embed($pdfannotator, $cm, $course, $file, $page = 1, $annoid = null, $commid = null) {
        global $CFG, $PAGE, $OUTPUT, $USER;
    
        // The revision attribute's existance is demanded by moodle for versioning and could be saved in the pdfannotator table in the future.
        // Note, however, that we forbid file replacement in order to prevent a change of meaning in other people's comments.
        $pdfannotator->revision = 1;
    
        $context = context_module::instance($cm->id);
        $path = '/' . $context->id . '/mod_pdfannotator/content/' . $pdfannotator->revision . $file->get_filepath() . $file->get_filename();
        $fullurl = file_encode_url($CFG->wwwroot . '/pluginfile.php', $path, false);
    
        $documentobject = new stdClass();
        $documentobject->annotatorid = $pdfannotator->id;
        $documentobject->fullurl = $fullurl;
    
        $stringman = get_string_manager();
        // With this method you get the strings of the language-Files.
        $strings = $stringman->load_component_strings('pdfannotator', 'en');
        // Method to use the language-strings in javascript.
        $PAGE->requires->strings_for_js(array_keys($strings), 'pdfannotator');
        // Load and execute the javascript files.
        $PAGE->requires->js(new moodle_url("/mod/pdfannotator/shared/pdf.js"));
        $PAGE->requires->js(new moodle_url("/mod/pdfannotator/shared/pdf_viewer.js"));
        $PAGE->requires->js(new moodle_url("/mod/pdfannotator/shared/textclipper.js"));
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
        $PAGE->requires->js(new moodle_url("/mod/pdfannotator/shared/index.js?ver=00014"));
        $PAGE->requires->js(new moodle_url("/mod/pdfannotator/shared/locallib.js?ver=00003"));
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    
        // Pass parameters from PHP to JavaScript.
    
        // 1. Toolbar settings.
        $toolbarsettings = new stdClass();
        $toolbarsettings->use_studenttextbox = $pdfannotator->use_studenttextbox;
        $toolbarsettings->use_studentdrawing = $pdfannotator->use_studentdrawing;
        $toolbarsettings->useprint = $pdfannotator->useprint;
        $toolbarsettings->useprintcomments = $pdfannotator->useprintcomments;
        // 2. Capabilities.
        $capabilities = new stdClass();
        $capabilities->viewquestions = has_capability('mod/pdfannotator:viewquestions', $context);
        $capabilities->viewanswers = has_capability('mod/pdfannotator:viewanswers', $context);
        $capabilities->viewposts = has_capability('mod/pdfannotator:viewposts', $context);
        $capabilities->viewreports = has_capability('mod/pdfannotator:viewreports', $context);
        $capabilities->deleteany = has_capability('mod/pdfannotator:deleteany', $context);
        $capabilities->hidecomment = has_capability('mod/pdfannotator:hidecomments', $context);
        $capabilities->seehiddencomments = has_capability('mod/pdfannotator:seehiddencomments', $context);
        $capabilities->usetextbox = has_capability('mod/pdfannotator:usetextbox', $context);
        $capabilities->usedrawing = has_capability('mod/pdfannotator:usedrawing', $context);
        $capabilities->useprint = has_capability('mod/pdfannotator:printdocument', $context);
        $capabilities->useprintcomments = has_capability('mod/pdfannotator:printcomments', $context);
    
        $params = [$cm, $documentobject, $USER->id, $capabilities, $toolbarsettings, $page, $annoid, $commid];
        $PAGE->requires->js_init_call('adjustPdfannotatorNavbar', null, true);
        $PAGE->requires->js_init_call('startIndex', $params, true);
        // The renderer renders the original index.php / takes the template and renders it.
        $myrenderer = $PAGE->get_renderer('mod_pdfannotator');
        echo $myrenderer->render_index(new index($pdfannotator, $capabilities, $file));
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
        $PAGE->requires->js_init_call('checkOnlyOneCheckbox', null, true);
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    
        pdfannotator_print_intro($pdfannotator, $cm, $course);
    
        echo $OUTPUT->footer();
        die;
    }
    
    function pdfannotator_get_instance_name($id) {
    
        global $DB;
        return $DB->get_field('pdfannotator', 'name', array('id' => $id), $strictness = MUST_EXIST);
    }
    
    function pdfannotator_get_course_name_by_id($courseid) {
        global $DB;
        return $DB->get_field('course', 'fullname', array('id' => $courseid), $strictness = MUST_EXIST);
    }
    
    function pdfannotator_get_username($userid) {
        global $DB;
        $user = $DB->get_record('user', array('id' => $userid));
        return fullname($user);
    }
    
    function pdfannotator_get_annotationtype_id($typename) {
        global $DB;
        if ($typename == 'point') {
            $typename = 'pin';
        }
        $result = $DB->get_records('pdfannotator_annotationtypes', array('name' => $typename));
        foreach ($result as $r) {
            return $r->id;
        }
    }
    
    function pdfannotator_get_annotationtype_name($typeid) {
        global $DB;
        $result = $DB->get_records('pdfannotator_annotationtypes', array('id' => $typeid));
        foreach ($result as $r) {
            return $r->name;
        }
    }
    
    
    function pdfannotator_handle_latex($context, string $subject) {
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        global $CFG;
        require_once($CFG->dirroot . '/mod/pdfannotator/constants.php');
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
        $latexapi = get_config('mod_pdfannotator', 'latexapi');
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    
        // Look for these formulae: $$ ... $$, \( ... \) and \[ ... \]
        // !!! keep indentation!
        $pattern = <<<'SIGN'
    ~(?:\$\$.*?\$\$)|(?:\\\(.*?\\\))|(?:\\\[.*?\\\])~
    SIGN;
    
        $matches = array();
        $hits = preg_match_all($pattern, $subject, $matches, PREG_OFFSET_CAPTURE);
    
        if ($hits == 0) {
            return $subject;
        }
    
        $textstart = 0;
        $formulalength = 0;
        $formulaoffset = 0;
        $result = [];
        $matches = $matches[0];
        foreach ($matches as $match) {
            $formulalength = strlen($match[0]);
            $formulaoffset = $match[1];
    
            $string = $match[0];
            $string = str_replace('\xrightarrow', '\rightarrow', $string);
            $string = str_replace('\xlefttarrow', '\leftarrow', $string);
    
            $pos = strpos($string, '\\[');
            if ($pos !== false) {
                $string = substr_replace($string, '', $pos, strlen('\\['));
            }
    
            $pos = strpos($string, '\\(');
            if ($pos !== false) {
                $string = substr_replace($string, '', $pos, strlen('\\('));
            }
    
            $string = str_replace('\\]', '', $string);
    
            $string = str_replace('\\)', '', $string);
    
            $string = str_replace('\begin{aligned}', '', $string);
            $string = str_replace('\end{aligned}', '', $string);
    
            $string = str_replace('\begin{align*}', '', $string);
            $string = str_replace('\end{align*}', '', $string);
    
            // Find any backslash preceding a ( or [ and replace it with \backslash
            $pattern = '~\\\\(?=[\\\(\\\[])~';
            $string = preg_replace($pattern, '\\backslash', $string);
            $match[0] = $string;
    
    
    Friederike Schwager's avatar
    Friederike Schwager committed
            $result[] = trim(substr($subject, $textstart, $formulaoffset - $textstart));
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
            if ($latexapi == LATEX_TO_PNG_GOOGLE_API) {
    
                $result[] = pdfannotator_process_latex_google($match[0]);
            } else {
                $result[] = pdfannotator_process_latex_moodle($context, $match[0]);
            }
    
    Friederike Schwager's avatar
    Friederike Schwager committed
            $textstart = $formulaoffset + $formulalength;
        }
        if ($textstart != strlen($subject) - 1) {
            $result[] = trim(substr($subject, $textstart, strlen($subject) - $textstart));
        }
        return $result;
    }
    
    
    function pdfannotator_process_latex_moodle($context, $string) {
        global $CFG;
        require_once($CFG->libdir . '/moodlelib.php');
        require_once($CFG->dirroot . '/filter/tex/latex.php');
        require_once($CFG->dirroot . '/filter/tex/lib.php');
        $result = array();
        $tex = new latex();
        $md5 = md5($string);
        $image = $tex->render($string, $md5 . 'png');
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
        if ($image == false) {
    
        $imagedata = file_get_contents($image);
        $result['image'] = IMAGE_PREFIX . base64_encode($imagedata);
    
        // Imageinfo returns an array with the info of the size of the image. In Parameter 1 there is the height, which is the only
        // thing needed here.
    
        $imageinfo = getimagesize($image);
        $result['imageheight'] = $imageinfo[1];
        return $result;
    }
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    /**
     * Function takes a latex code string, modifies and url encodes it for the Google Api to process,
     * and returns the resulting image along with its height
     *
     * @param type $string
     * @return type
     */
    
    function pdfannotator_process_latex_google(string $string) {
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
    
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        $length = strlen($string);
        $im = null;
        if ($length <= 200) { // Google API constraint XXX find better alternative if possible.
            $latexdata = urlencode($string);
            $requesturl = LATEX_TO_PNG_REQUEST . $latexdata;
            $im = @file_get_contents($requesturl); // '@' suppresses warnings so that one failed google request doesn't prevent the pdf from being printed,
    
            // but just the one formula from being presented as a picture.
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        }
        if ($im != null) {
            $array = [];
            try {
                list($width, $height) = getimagesize($requesturl); // XXX alternative: acess height by decoding the string (saving the extra server request)?
                if ($height != null) {
                    $imagedata = IMAGE_PREFIX . base64_encode($im); // Image.
                    $array['image'] = $imagedata;
                    $array['imageheight'] = $height;
                    return $array;
                }
            } catch (Exception $ex) {
                return $string;
            }
        } else {
            return $string;
        }
    }
    
    
    function pdfannotator_send_forward_message($recipients, $messageparams, $course, $cm, $context) {
    
        $name = 'forwardedquestion';
        $text = new stdClass();
        $module = get_string('modulename', 'pdfannotator');
    
        $modulename = format_string($cm->name, true);
        $text->text = pdfannotator_format_notification_message_text($course, $cm, $context, $module, $modulename, $messageparams, $name);
    
        $text->url = $messageparams->urltoquestion;
    
        foreach ($recipients as $recipient) {
    
            $text->html = pdfannotator_format_notification_message_html($course, $cm, $context, $module, $modulename, $messageparams, $name, $recipient);
    
            pdfannotator_notify_manager($recipient, $course, $cm, $name, $text);
        }
    }
    
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    function pdfannotator_notify_manager($recipient, $course, $cm, $name, $messagetext, $anonymous = false) {
    
    
        global $USER;
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        $userfrom = $USER;
    
        $modulename = format_string($cm->name, true);
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        if ($anonymous) {
            $userfrom = clone($USER);
            $userfrom->firstname = get_string('pdfannotatorname', 'pdfannotator') . ':';
    
            $userfrom->lastname = $modulename;
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        }
        $message = new \core\message\message();
        $message->component = 'mod_pdfannotator';
        $message->name = $name;
        $message->courseid = $course->id;
        $message->userfrom = $userfrom;
        $message->userto = $recipient;
    
        $message->subject = get_string('notificationsubject:' . $name, 'pdfannotator', $modulename);
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        $message->fullmessage = $messagetext->text;
        $message->fullmessageformat = FORMAT_PLAIN;
        $message->fullmessagehtml = $messagetext->html;
    
        $message->smallmessage = get_string('notificationsubject:' . $name, 'pdfannotator', $modulename);
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        $message->notification = 1; // For personal messages '0'. Important: the 1 without '' and 0 with ''.
        $message->contexturl = $messagetext->url;
        $message->contexturlname = 'Context name';
        $content = array('*' => array('header' => ' test ', 'footer' => ' test ')); // Extra content for specific processor.
    
        $messageid = message_send($message);
    
        return $messageid;
    }
    
    function pdfannotator_format_notification_message_text($course, $cm, $context, $modulename, $pdfannotatorname, $paramsforlanguagestring, $messagetype) {
        global $CFG;
        $formatparams = array('context' => $context->get_course_context());
        $posttext = format_string($course->shortname, true, $formatparams) .
    
            ' -> ' .
            $modulename .
            ' -> ' .
            format_string($pdfannotatorname, true, $formatparams) . "\n";
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        $posttext .= '---------------------------------------------------------------------' . "\n";
        $posttext .= "\n";
        $posttext .= get_string($messagetype . 'text', 'pdfannotator', $paramsforlanguagestring) . "\n---------------------------------------------------------------------\n";
        return $posttext;
    }
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    /**
     * Format a notification for HTML.
     *
     * @param string $messagetype
     * @param stdClass $info
     * @param stdClass $course
     * @param stdClass $context
     * @param string $modulename
     * @param stdClass $coursemodule
     * @param string $assignmentname
     */
    
    function pdfannotator_format_notification_message_html($course, $cm, $context, $modulename, $pdfannotatorname, $report, $messagetype, $recipientid) {
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        global $CFG, $USER;
        $formatparams = array('context' => $context->get_course_context());
        $posthtml = '<p><font face="sans-serif">' .
    
            '<a href="' . $CFG->wwwroot . '/course/view.php?id=' . $course->id . '">' .
            format_string($course->shortname, true, $formatparams) .
            '</a> ->' .
            '<a href="' . $CFG->wwwroot . '/mod/pdfannotator/index.php?id=' . $course->id . '">' .
            $modulename .
            '</a> ->' .
            '<a href="' . $CFG->wwwroot . '/mod/pdfannotator/view.php?id=' . $cm->id . '">' .
            format_string($pdfannotatorname, true, $formatparams) .
            '</a></font></p>';
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        $posthtml .= '<hr /><font face="sans-serif">';
        $report->urltoreport = $CFG->wwwroot . '/mod/pdfannotator/view.php?id=' . $cm->id . '&action=overviewreports';
        $posthtml .= '<p>' . get_string($messagetype . 'html', 'pdfannotator', $report) . '</p>';
    
        $linktonotificationsettingspage = new moodle_url('/message/notificationpreferences.php', array('userid' => $recipientid));
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        $linktonotificationsettingspage = $linktonotificationsettingspage->__toString();
        $posthtml .= '</font><hr />';
        $posthtml .= '<font face="sans-serif"><p>' . get_string('unsubscribe_notification', 'pdfannotator', $linktonotificationsettingspage) . '</p></font>';
        return $posthtml;
    }
    
    /**
     * Internal function - create click to open text with link.
     */
    function pdfannotator_get_clicktoopen($file, $revision, $extra = '') {
        global $CFG;
    
        $filename = $file->get_filename();
        $path = '/' . $file->get_contextid() . '/mod_pdfannotator/content/' . $revision . $file->get_filepath() . $file->get_filename();
        $fullurl = file_encode_url($CFG->wwwroot . '/pluginfile.php', $path, false);
    
        $string = get_string('clicktoopen2', 'pdfannotator', "<a href=\"$fullurl\" $extra>$filename</a>");
    
        return $string;
    }
    
    /**
     * Internal function - create click to open text with link.
     */
    function pdfannotator_get_clicktodownload($file, $revision) {
        global $CFG;
    
        $filename = $file->get_filename();
        $path = '/' . $file->get_contextid() . '/mod_pdfannotator/content/' . $revision . $file->get_filepath() . $file->get_filename();
        $fullurl = file_encode_url($CFG->wwwroot . '/pluginfile.php', $path, true);
    
        $string = get_string('clicktodownload', 'pdfannotator', "<a href=\"$fullurl\">$filename</a>");
    
        return $string;
    }
    
    /**
     * Print pdfannotator header.
     * @param object $pdfannotator
     * @param object $cm
     * @param object $course
     * @return void
     */
    function pdfannotator_print_header($pdfannotator, $cm, $course) {
        global $PAGE, $OUTPUT;
        $PAGE->set_title($course->shortname . ': ' . $pdfannotator->name);
        $PAGE->set_heading($course->fullname);
        $PAGE->set_activity_record($pdfannotator);
        echo $OUTPUT->header();
    }
    
    /**
    
     * Gets details of the file to cache in course cache to be displayed using {@see pdfannotator_get_optional_details()}
    
    Friederike Schwager's avatar
    Friederike Schwager committed
     *
     * @param object $pdfannotator pdfannotator table row (only property 'displayoptions' is used here)
     * @param object $cm Course-module table row
     * @return string Size and type or empty string if show options are not enabled
     */
    function pdfannotator_get_file_details($pdfannotator, $cm) {
        $filedetails = array();
    
        $context = context_module::instance($cm->id);
        $fs = get_file_storage();
        $files = $fs->get_area_files($context->id, 'mod_pdfannotator', 'content', 0, 'sortorder DESC, id ASC', false);
        // For a typical file pdfannotator, the sortorder is 1 for the main file
        // and 0 for all other files. This sort approach is used just in case
        // there are situations where the file has a different sort order.
        $mainfile = $files ? reset($files) : null;
    
        foreach ($files as $file) {
            // This will also synchronize the file size for external files if needed.
            $filedetails['size'] += $file->get_filesize();
            if ($file->get_repository_id()) {
                // If file is a reference the 'size' attribute can not be cached.
                $filedetails['isref'] = true;
            }
        }
    
        return $filedetails;
    }
    
    /**
     * Print pdfannotator introduction.
     * @param object $pdfannotator
     * @param object $cm
     * @param object $course
     * @param bool $ignoresettings print even if not specified in modedit
     * @return void
     */
    function pdfannotator_print_intro($pdfannotator, $cm, $course, $ignoresettings = false) {
        global $OUTPUT;
        if ($ignoresettings) {
            $gotintro = trim(strip_tags($pdfannotator->intro));
            if ($gotintro || $extraintro) {
                echo $OUTPUT->box_start('mod_introbox', 'pdfannotatorintro');
                if ($gotintro) {
                    echo format_module_intro('pdfannotator', $pdfannotator, $cm->id);
                }
                echo $extraintro;
                echo $OUTPUT->box_end();
            }
        }
    }
    
    /**
     * Print warning that file can not be found.
     * @param object $pdfannotator
     * @param object $cm
     * @param object $course
     * @return void, does not return
     */
    function pdfannotator_print_filenotfound($pdfannotator, $cm, $course) {
        global $DB, $OUTPUT;
    
        pdfannotator_print_header($pdfannotator, $cm, $course);
        // pdfannotator_print_heading($pdfannotator, $cm, $course);//TODO Method is not defined.
        pdfannotator_print_intro($pdfannotator, $cm, $course);
        echo $OUTPUT->notification(get_string('filenotfound', 'pdfannotator'));
    
        echo $OUTPUT->footer();
        die;
    }
    
    /**
     * Function returns the number of new comments, drawings and textboxes*
     * in this annotator. 'New' is defined here as 'no older than 24h' but
     * can easily be changed to another time span.
     * *Drawings and textboxes cannot be commented. In their case (only),
     * therefore, annotations are counted.
     *
     */
    function pdfannotator_get_number_of_new_activities($annotatorid) {
    
        global $DB;
    
        $parameters = array();
        $parameters[] = $annotatorid;
        $parameters[] = strtotime("-1 day");
    
        $sql = "SELECT c.id FROM {pdfannotator_annotations} a JOIN {pdfannotator_comments} c ON c.annotationid = a.id "
    
            . "WHERE a.pdfannotatorid = ? AND c.timemodified >= ?";
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        $sql2 = "SELECT a.id FROM {pdfannotator_annotations} a JOIN {pdfannotator_annotationtypes} t ON a.annotationtypeid = t.id "
    
            . "WHERE a.pdfannotatorid = ? AND a.timecreated >= ? AND t.name IN('drawing','textbox')";
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    
        return ( count($DB->get_records_sql($sql, $parameters)) + count($DB->get_records_sql($sql2, $parameters)) );
    }
    
    /**
     * Function returns the datetime of the last modification on or in the specified annotator.
     * The modification can be the creation of the annotator, a change of title or description,
     * a new annotation or a new comment. Reports are not considered.
     *
     * @param int $annotatorid
     * @return datetime $timemodified
     * The timestamp can be transformed into a readable string with this moodle method:
     * userdate($timestamp, $format = '', $timezone = 99, $fixday = true, $fixhour = true);
     */
    function pdfannotator_get_datetime_of_last_modification($annotatorid) {
    
        global $DB;
    
        // 1. When was the last time the annotator itself (i.e. its title, description or pdf) was modified?
        $timemodified = $DB->get_record('pdfannotator', array('id' => $annotatorid), 'timemodified', MUST_EXIST);
        $timemodified = $timemodified->timemodified;
    
        // 2. When was the last time an annotation or a comment was added in the specified annotator?
    
    hendrik.donath's avatar
    hendrik.donath committed
        $sql = "SELECT max(a.timecreated) AS last_annotation, max(c.timemodified) AS last_comment "
    
            . "FROM {pdfannotator_annotations} a LEFT OUTER JOIN {pdfannotator_comments} c ON a.id = c.annotationid "
            . "WHERE a.pdfannotatorid = ?";
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        $newposts = $DB->get_records_sql($sql, array($annotatorid));
    
        if (!empty($newposts)) {
    
            foreach ($newposts as $entry) {
    
                // 2.a) If there is an annotation younger than the creation/modification of the annotator, set timemodified to the annotation time.
                if (!empty($entry->last_annotation) && ($entry->last_annotation > $timemodified)) {
                    $timemodified = $entry->last_annotation;
                }
                // 2.b) If there is a comment younger than the creation/modification of the annotator or its newest annotation, set timemodified to the comment time.
                if (!empty($entry->last_comment) && ($entry->last_comment > $timemodified)) {
                    $timemodified = $entry->last_comment;
                }
                return $timemodified;
            }
        }
    }
    
    /**
     * File browsing support class
     */
    class pdfannotator_content_file_info extends file_info_stored {
    
        public function get_parent() {
            if ($this->lf->get_filepath() === '/' and $this->lf->get_filename() === '.') {
                return $this->browser->get_file_info($this->context);
            }
            return parent::get_parent();
        }
    
        public function get_visible_name() {
            if ($this->lf->get_filepath() === '/' and $this->lf->get_filename() === '.') {
                return $this->topvisiblename;
            }
            return parent::get_visible_name();
        }
    
    }
    
    function pdfannotator_set_mainfile($data) {
        global $DB;
        $fs = get_file_storage();
        $cmid = $data->coursemodule;
        $draftitemid = $data->files; // Name from the filemanger.
    
        $context = context_module::instance($cmid);
        if ($draftitemid) {
            file_save_draft_area_files($draftitemid, $context->id, 'mod_pdfannotator', 'content', 0, array('subdirs' => true));
        }
        $files = $fs->get_area_files($context->id, 'mod_pdfannotator', 'content', 0, 'sortorder', false);
        if (count($files) == 1) {
            // Only one file attached, set it as main file automatically.
            $file = reset($files);
            file_set_sortorder($context->id, 'mod_pdfannotator', 'content', 0, $file->get_filepath(), $file->get_filename(), 1);
        }
    }
    
    function pdfannotator_render_listitem_actions(array $actions = null) {
        $menu = new action_menu();
        $menu->attributes['class'] .= ' course-item-actions item-actions';
        $hasitems = false;
        foreach ($actions as $key => $action) {
            $hasitems = true;
            $menu->add(new action_menu_link(
    
                $action['url'], $action['icon'], $action['string'], in_array($key, []), ['data-action' => $key, 'class' => 'action-' . $key]
    
    Friederike Schwager's avatar
    Friederike Schwager committed
            ));
        }
        if (!$hasitems) {
            return '';
        }
        return pdfannotator_render_action_menu($menu);
    }
    
    function pdfannotator_render_action_menu($menu) {
        global $OUTPUT;
        return $OUTPUT->render($menu);
    }
    
    
    function pdfannotator_subscribe_all($annotatorid, $context) {
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        global $DB;
        $sql = "SELECT id FROM {pdfannotator_annotations} "
    
            . "WHERE pdfannotatorid = ? AND annotationtypeid NOT IN "
            . "(SELECT id FROM {pdfannotator_annotationtypes} WHERE name = ? OR name = ?)";
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        $params = [$annotatorid, 'drawing', 'textbox'];
        $ids = $DB->get_fieldset_sql($sql, $params);
        foreach ($ids as $annotationid) {
    
            pdfannotator_comment::insert_subscription($annotationid, $context);
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        }
    }
    
    function pdfannotator_unsubscribe_all($annotatorid) {
        global $DB, $USER;
        $sql = "SELECT a.id FROM {pdfannotator_annotations} a JOIN {pdfannotator_subscriptions} s "
    
            . "ON s.annotationid = a.id AND s.userid = ? WHERE pdfannotatorid = ?";
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        $ids = $DB->get_fieldset_sql($sql, [$USER->id, $annotatorid]);
        foreach ($ids as $annotationid) {
            pdfannotator_comment::delete_subscription($annotationid);
        }
    }
    
    /**
     * Checks wether a user has subscribed to all questions in an annotator.
     * Returns 1 if all questions are subscribed, 0 if no questions are subscribed and -1 if at least one but not all questions are subscribed.
     * @param type $annotatorid
     */
    function pdfannotator_subscribed($annotatorid) {
        global $DB, $USER;
        $sql = "SELECT COUNT(*) FROM {pdfannotator_annotations} a JOIN {pdfannotator_subscriptions} s "
    
            . "ON s.annotationid = a.id AND s.userid = ? WHERE a.pdfannotatorid = ?";
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        $subscriptions = $DB->count_records_sql($sql, [$USER->id, $annotatorid]);
        $sql = "SELECT COUNT(*) FROM {pdfannotator_annotations} "
    
            . "WHERE pdfannotatorid = ? AND annotationtypeid NOT IN "
            . "(SELECT id FROM {pdfannotator_annotationtypes} WHERE name = ? OR name = ?)";
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        $params = [$annotatorid, 'drawing', 'textbox'];
        $annotations = $DB->count_records_sql($sql, $params);
    
        if ($subscriptions === 0) {
            return 0;
        } else if ($subscriptions === $annotations) {
            return 1;
        } else {
            return -1;
        }
    }
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    /**
     *
     * @param type $timestamp
     * @return string Day, D Month Y, Time
     */
    function pdfannotator_get_user_datetime($timestamp) {
        $userdatetime = userdate($timestamp, $format = '', $timezone = 99, $fixday = true, $fixhour = true); // Method in lib/moodlelib.php
        return $userdatetime;
    }
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    /**
     *
     * @param type $timestamp
     * @return string
     */
    function pdfannotator_get_user_datetime_shortformat($timestamp) {
        $shortformat = get_string('strftimedatetime', 'pdfannotator'); // Format strings in moodle\lang\en\langconfig.php.
        $userdatetime = userdate($timestamp, $shortformat, $timezone = 99, $fixday = true, $fixhour = true); // Method in lib/moodlelib.php
        return $userdatetime;
    }
    
    /**
     * Function is executed each time one of the overview categories is accessed.
     * It creates the tab navigation and makes javascript accessible.
     *
     * @param type $CFG
     * @param type $PAGE
     * @param type $myrenderer
     * @param type $taburl
     * @param type $action
     * @param type $pdfannotator
     * @param type $context
     */
    function pdfannotator_prepare_overviewpage($cmid, $myrenderer, $taburl, $action, $pdfannotator, $context) {
    
        global $CFG, $PAGE;
    
        $PAGE->set_title("overview");
    
        // 1.1 Display tab navigation.
    
        echo $myrenderer->pdfannotator_render_tabs($taburl, $pdfannotator->name, $context, $action['tab']);
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    
        // 1.2 Give javascript (see below) access to the language string repository.
        $stringman = get_string_manager();
        $strings = $stringman->load_component_strings('pdfannotator', 'en'); // Method gets the strings of the language files.
        $PAGE->requires->strings_for_js(array_keys($strings), 'pdfannotator'); // Method to use the language-strings in javascript.
        // 1.3 Add the javascript file that determines the dynamic behaviour of the page.
        $PAGE->requires->js(new moodle_url("/mod/pdfannotator/shared/locallib.js?ver=00002"));
        $PAGE->requires->js(new moodle_url("/mod/pdfannotator/shared/overview.js?ver=00002"));
    
        // 1.4 Check user capabilities to view the different categories.
        // The argument 'false' disregards administrator's magical 'doanything' power.
        $capabilities = new stdClass();
        $capabilities->viewquestions = has_capability('mod/pdfannotator:viewquestions', $context);
        $capabilities->viewanswers = has_capability('mod/pdfannotator:viewanswers', $context);
        $capabilities->viewposts = has_capability('mod/pdfannotator:viewposts', $context);
        $capabilities->viewreports = has_capability('mod/pdfannotator:viewreports', $context);
    
        $params = array($pdfannotator->id, $cmid, $capabilities, $action['action']);
        $PAGE->requires->js_init_call('startOverview', $params, true); // 1. name of JS function, 2. parameters.
    }
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    /**
     * Function serves as subcontroller that tells the annotator model to collect
     * all or all unsolved/solved questions asked in this course.
     *
     * @param int $openannotator
     * @param int $courseid
     * @param type $questionfilter
     * @return type
     */
    function pdfannotator_get_questions($courseid, $context, $questionfilter) {
    
        global $DB;
    
        $cminfo = pdfannotator_instance::get_cm_info($courseid);
        list($insql, $inparams) = $DB->get_in_or_equal(array_keys($cminfo));
    
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
        $sql = "SELECT a.id as annoid, a.page, a.pdfannotatorid, p.name AS pdfannotatorname, p.usevotes, cm.id AS cmid, c.isquestion, "
    
            . "c.id as commentid, c.content, c.userid, c.visibility, c.timecreated, c.isdeleted, c.ishidden, "
            . "SUM(vote) AS votes, MAX(answ.timecreated) AS lastanswered "
            . "FROM {pdfannotator_annotations} a "
            . "JOIN {pdfannotator_comments} c ON c.annotationid = a.id "
            . "JOIN {pdfannotator} p ON a.pdfannotatorid = p.id "
            . "JOIN {course_modules} cm ON p.id = cm.instance "
            . "LEFT JOIN {pdfannotator_votes} v ON c.id=v.commentid "
            . "LEFT JOIN {pdfannotator_comments} answ ON answ.annotationid = a.id "
            . "WHERE c.isquestion = 1 AND p.course = ? AND cm.id $insql";
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        if ($questionfilter == 0) {
            $sql = $sql . ' AND c.solved = 0 ';
        }
        if ($questionfilter == 1) {
            $sql = $sql . ' AND NOT c.solved = 0 ';
        }
    
        $sql = $sql . "GROUP BY a.id, p.name, p.usevotes, cm.id, c.id, a.page, a.pdfannotatorid, c.content, c.userid, c.visibility,"
            . "c.timecreated, c.isdeleted, c.ishidden, c.isquestion";
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        $params = array_merge([$courseid], $inparams);
        $questions = $DB->get_records_sql($sql, $params);
    
        $seehidden = has_capability('mod/pdfannotator:seehiddencomments', $context);
        $labelhidden = "<br><span class='tag tag-info'>" . get_string('hiddenfromstudents') . "</span>"; // XXX use moodle method if exists.
        $labelunavailable = "<br><span class='tag tag-info'>" . get_string('restricted') . "</span>";
    
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
        $res = [];
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        foreach ($questions as $key => $question) {
    
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
            if (!pdfannotator_can_see_comment($question, $context)) {
                continue;
            }
    
    
    Friederike Schwager's avatar
    Friederike Schwager committed
            if (empty($question->votes)) {
                $question->votes = 0;
            }
            if ($question->usevotes == 0) {
                $question->votes = '-';
            }
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
            $question->answercount = pdfannotator_count_answers($question->annoid, $context);
    
            $lastanswer = pdfannotator_get_last_answer($question->annoid, $context);
            if ($lastanswer) {
                $question->lastuser = $lastanswer->userid;
                $question->lastuservisibility = $lastanswer->visibility;
    
    Friederike Schwager's avatar
    Friederike Schwager committed
            } else {
                $question->lastanswered = false;
            }
    
            if ($question->isdeleted == 1) {
                $question->content = "<em>" . get_string('deletedQuestion', 'pdfannotator') . "</em>";
            } else if ($question->ishidden) {
                switch ($seehidden) {
                    case 0:
                        $question->content = "<em>" . get_string('hiddenComment', 'pdfannotator') . "</em>";
                        break;
                    case 1:
                        $question->content = $question->content . $labelhidden;
                        $question->displayhidden = true;
                        break;
                    default:
                        $question->content = "<em>" . get_string('hiddenComment', 'pdfannotator') . "</em>";
                }
            }
    
            if (!$cminfo[$question->cmid]['visible']) { // Annotator is not visible for students.
                $question->content = $question->content . $labelhidden;
                $question->displayhidden = true;
            }
            if ($cminfo[$question->cmid]['availableinfo']) { // Annotator is restricted.
    
                $question->content = $question->content . $labelunavailable . " " . $cminfo[$question->cmid]['availableinfo'];
    
    Friederike Schwager's avatar
    Friederike Schwager committed
                $question->displayhidden = true;
            }
    
            $question->content = format_text($question->content);
            $question->link = (new moodle_url('/mod/pdfannotator/view.php', array('id' => $question->cmid,
                'page' => $question->page, 'annoid' => $question->annoid, 'commid' => $question->commentid)))->out();
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
    
            $res[] = $question;
    
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        }
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
        return $res;
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    }
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    /**
     * Function serves as subcontroller that tells the annotator model to collect all
     * questions and answers this user posted in the course.
     *
     * @param int $courseid
     * @return type
     */
    function pdfannotator_get_posts_by_this_user($courseid, $context) {
    
        global $DB, $USER;
    
        $cminfo = pdfannotator_instance::get_cm_info($courseid);
        list($insql, $inparams) = $DB->get_in_or_equal(array_keys($cminfo));
    
        $seehidden = has_capability('mod/pdfannotator:seehiddencomments', $context);
        $labelhidden = "<br><span class='tag tag-info'>" . get_string('hiddenforparticipants', 'pdfannotator') . "</span>";
        $labelunavailable = "<br><span class='tag tag-info'>" . get_string('restricted') . "</span>";
    
        $sql = "SELECT c.id as commid, c.annotationid, c.content, c.timemodified, c.ishidden, a.id AS annoid, "
    
            . "a.page, a.pdfannotatorid, p.name AS pdfannotatorname, p.usevotes, cm.id AS cmid, "
            . "SUM(v.vote) AS votes "
            . "FROM {pdfannotator_comments} c "
            . "JOIN {pdfannotator_annotations} a ON c.annotationid = a.id "
            . "JOIN {pdfannotator} p ON a.pdfannotatorid = p.id "
            . "JOIN {course_modules} cm ON p.id = cm.instance "
            . "LEFT JOIN {pdfannotator_votes} v ON c.id = v.commentid "
            . "WHERE c.userid = ? AND p.course = ? AND cm.id $insql "
            . "GROUP BY a.id, p.name, p.usevotes, cm.id, c.id, c.annotationid, c.content, c.timemodified, c.ishidden, a.page, a.pdfannotatorid";
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    
        $params = array_merge([$USER->id, $courseid], $inparams);
    
        $posts = $DB->get_records_sql($sql, $params);
    
        foreach ($posts as $key => $post) {
            if (empty($post->votes)) {
                $post->votes = 0;
            }
            if ($post->usevotes == 0) {
                $post->votes = '-';
            }
    
            if ($post->ishidden) { // Post in annotator is hidden.
                switch ($seehidden) {
                    case 0:
                        $post->content = "<em>" . get_string('hiddenComment', 'pdfannotator') . "</em>";
                        break;
                    case 1:
                        $post->content = $post->content . $labelhidden;
                        $post->displayhidden = true;
                        break;
                    default:
                        $post->content = "<em>" . get_string('hiddenComment', 'pdfannotator') . "</em>";
                }
            }
    
            if (!$cminfo[$post->cmid]['visible']) {  // Annotator is hidden.
                $post->content = $post->content . $labelhidden;
                $post->displayhidden = true;
            }
            if ($cminfo[$post->cmid]['availableinfo']) {  // Annotator is restricted.
    
                $post->content = $post->content . $labelunavailable . " " . $cminfo[$post->cmid]['availableinfo'];
    
    Friederike Schwager's avatar
    Friederike Schwager committed
                $post->displayhidden = true;
            }
    
            $params = array('id' => $post->cmid, 'page' => $post->page, 'annoid' => $post->annotationid, 'commid' => $post->commid);
            $post->link = (new moodle_url('/mod/pdfannotator/view.php', $params))->out();
            $post->content = format_text($post->content);
        }
        return $posts;
    }
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    /**
     * Function serves as subcontroller that tells the annotator model to collect
     * all answers given to questions that the current user asked or subscribed to
     * in this course.
     *
     * @param int $courseid
     * @param Moodle object? $context
     * @param int $answerfilter
     * @return array of stdClass objects
     */
    function pdfannotator_get_answers_for_this_user($courseid, $context, $answerfilter = 1) {
    
        global $DB, $USER;
    
        $cminfo = pdfannotator_instance::get_cm_info($courseid);
        list($insql, $inparams) = $DB->get_in_or_equal(array_keys($cminfo));
    
        $seehidden = has_capability('mod/pdfannotator:seehiddencomments', $context);
        $labelhidden = "<br><span class='tag tag-info'>" . get_string('hiddenforparticipants', 'pdfannotator') . "</span>";
        $labelunavailable = "<br><span class='tag tag-info'>" . get_string('restricted') . "</span>";
    
        if ($answerfilter == 0) { // Either: get all answers in this annotator.
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
            $sql = "SELECT c.id AS answerid, c.content AS answer, c.userid AS userid, c.visibility, "
    
                . "c.timemodified, c.solved AS correct, c.ishidden AS answerhidden, a.id AS annoid, a.page, q.id AS questionid,"
                . "q.userid AS questionuserid, c.isquestion, c.annotationid, "
    
                . "q.visibility AS questionvisibility, "
                . "q.content AS answeredquestion, q.isdeleted AS questiondeleted, q.ishidden AS questionhidden, p.id AS annotatorid, "
                . "p.name AS pdfannotatorname, cm.id AS cmid, s.id AS issubscribed "
                . "FROM {pdfannotator_annotations} a "
                . "LEFT JOIN {pdfannotator_subscriptions} s ON a.id = s.annotationid AND s.userid = ? "
                . "JOIN {pdfannotator_comments} q ON q.annotationid = a.id " // Question comment.
                . "JOIN {pdfannotator_comments} c ON c.annotationid = a.id " // Answer comment.
                . "JOIN {pdfannotator} p ON a.pdfannotatorid = p.id "
                . "JOIN {course_modules} cm ON p.id = cm.instance "
                . "WHERE p.course = ? AND q.isquestion = 1 AND NOT c.isquestion = 1 AND NOT c.isdeleted = 1 AND cm.id $insql "
                . "ORDER BY annoid ASC";
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        } else { // Or: get answers to those questions the user subscribed to.
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
            $sql = "SELECT c.id AS answerid, c.content AS answer, c.userid AS userid, c.visibility, "
    
                . "c.timemodified, c.solved AS correct, c.ishidden AS answerhidden, a.id AS annoid, a.page, q.id AS questionid, "
                . "q.userid AS questionuserid, c.isquestion, c.annotationid, "
    
                . "q.visibility AS questionvisibility, "
                . "q.content AS answeredquestion, q.isdeleted AS questiondeleted, q.ishidden AS questionhidden, p.id AS annotatorid, "
                . "p.name AS pdfannotatorname, cm.id AS cmid "
                . "FROM {pdfannotator_subscriptions} s "
                . "JOIN {pdfannotator_annotations} a ON a.id = s.annotationid "
                . "JOIN {pdfannotator_comments} q ON q.annotationid = a.id " // Question comment.
                . "JOIN {pdfannotator_comments} c ON c.annotationid = a.id " // Answer comment.
                . "JOIN {pdfannotator} p ON a.pdfannotatorid = p.id "
                . "JOIN {course_modules} cm ON p.id = cm.instance "
                . "WHERE s.userid = ? AND p.course = ? AND q.isquestion = 1 AND NOT c.isquestion = 1 AND NOT c.isdeleted = 1 AND cm.id $insql "
                . "ORDER BY annoid ASC";
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        }
    
        $params = array_merge([$USER->id, $courseid], $inparams);
    
        $entries = $DB->get_records_sql($sql, $params);
    
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
        $res = [];
    
    Friederike Schwager's avatar
    Friederike Schwager committed
        foreach ($entries as $key => $entry) {
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
            if (!pdfannotator_can_see_comment($entry, $context)) {
                continue;
            }
    
    Friederike Schwager's avatar
    Friederike Schwager committed
            $entry->link = (new moodle_url('/mod/pdfannotator/view.php',
    
                array('id' => $entry->cmid, 'page' => $entry->page, 'annoid' => $entry->annoid, 'commid' => $entry->answerid)))->out();
    
    Friederike Schwager's avatar
    Friederike Schwager committed
            $entry->questionlink = (new moodle_url('/mod/pdfannotator/view.php',
    
                array('id' => $entry->cmid, 'page' => $entry->page, 'annoid' => $entry->annoid, 'commid' => $entry->questionid)))->out();
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    
            if ($entry->questiondeleted == 1) {
                $entry->answeredquestion = get_string('deletedComment', 'pdfannotator');
            } else if ($entry->questionhidden) {
                switch ($seehidden) {
                    case 0:
                        $entry->answeredquestion = "<em>" . get_string('hiddenComment', 'pdfannotator') . "</em>";
                        break;
                    case 1:
                        $entry->displayquestionhidden = true;
                        $entry->answeredquestion = $entry->answeredquestion . $labelhidden;
                        break;
                    default:
                        $entry->answeredquestion = "<em>" . get_string('hiddenComment', 'pdfannotator') . "</em>";
                }
            }
    
            if ($entry->answerhidden) {
                switch ($seehidden) {
                    case 0:
                        $entry->answer = "<em>" . get_string('hiddenComment', 'pdfannotator') . "</em>";
                        break;
                    case 1:
                        $entry->answer = $entry->answer . $labelhidden;
                        $entry->displayhidden = true;
                        break;
                    default:
                        $entry->answer = "<em>" . get_string('hiddenComment', 'pdfannotator') . "</em>";
                }
            }
    
            if (!$cminfo[$entry->cmid]['visible']) {  // Annotator is hidden.
                $entry->answeredquestion = $entry->answeredquestion . $labelhidden;
                $entry->answer = $entry->answer . $labelhidden;
                $entry->displayhidden = true;
            }
            if ($cminfo[$entry->cmid]['availableinfo']) {  // Annotator is restricted.
    
                $entry->answeredquestion = $entry->answeredquestion . $labelunavailable . " ". $cminfo[$entry->cmid]['availableinfo'];;
    
    Friederike Schwager's avatar
    Friederike Schwager committed
                $entry->answer = $entry->answer . $labelunavailable . " ". $cminfo[$entry->cmid]['availableinfo'];
                $entry->displayhidden = true;
            }
    
            $entry->answeredquestion = format_text($entry->answeredquestion);
            $entry->answer = format_text($entry->answer);
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
    
            $res[] = $entry;
    
    anisa kusumadewi's avatar
    anisa kusumadewi committed
        return $res;
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    }
    
    Friederike Schwager's avatar
    Friederike Schwager committed
    /**
     * Function retrieves reports and their respective reported comments from db.
     * Depending on the reportfilter, only read/unread reports or all reports are retrieved.
     *
     * @param int $courseid