/mod_forumng_post.php
PHP | 2334 lines | 1452 code | 257 blank | 625 comment | 280 complexity | 35cf0632da6f2449617c95779da7ca4a MD5 | raw file
- <?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/>.
- /**
- * Represents a single forum post.
- * @see mod_forumng_discussion
- * @see forum
- * @package mod
- * @subpackage forumng
- * @copyright 2011 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- class mod_forumng_post {
- const PARENT_NOT_LOADED = 'not_loaded';
- const PARENTPOST_DEPTH_PER_QUERY = 8;
- // For option definitions, see forumngtype.php display_post function
- const OPTION_EMAIL = 'email';
- const OPTION_DIGEST = 'digest';
- const OPTION_COMMAND_REPLY = 'command_reply';
- const OPTION_COMMAND_EDIT = 'command_edit';
- const OPTION_COMMAND_DELETE = 'command_delete';
- const OPTION_COMMAND_UNDELETE = 'command_undelete';
- const OPTION_COMMAND_SPLIT = 'command_split';
- const OPTION_COMMAND_HISTORY = 'command_history';
- const OPTION_COMMAND_REPORT = 'command_report';
- const OPTION_COMMAND_DIRECTLINK = 'command_directlink';
- const OPTION_VIEW_FULL_NAMES = 'view_full_names';
- const OPTION_TIME_ZONE = 'time_zone';
- const OPTION_SUMMARY = 'summary';
- const OPTION_NO_COMMANDS = 'no_commands';
- const OPTION_RATINGS_VIEW = 'ratings_view';
- const OPTION_RATINGS_EDIT = 'ratings_edit';
- const OPTION_VIEW_DELETED_INFO = 'deleted_info';
- const OPTION_EXPANDED = 'short';
- const OPTION_FLAG_CONTROL = 'flag_control';
- const OPTION_READ_TIME = 'read_time';
- const OPTION_CHILDREN_EXPANDED = 'children_expanded';
- const OPTION_CHILDREN_COLLAPSED = 'children_collapsed';
- const OPTION_INCLUDE_LOCK = 'include_lock';
- const OPTION_EXPORT = 'export';
- const OPTION_FULL_ADDRESSES = 'full_addresses';
- const OPTION_DISCUSSION_SUBJECT = 'discussion_subject';
- const OPTION_SELECTABLE = 'selectable';
- const OPTION_VISIBLE_POST_NUMBERS = 'visible_post_numbers';
- const OPTION_USER_IMAGE = 'user_image';
- const OPTION_PRINTABLE_VERSION = 'printable_version';
- const OPTION_JUMP_NEXT = 'jump_next';
- const OPTION_JUMP_PREVIOUS = 'jump_previous';
- const OPTION_JUMP_PARENT = 'jump_parent';
- const OPTION_FIRST_UNREAD = 'first_unread';
- const OPTION_UNREAD_NOT_HIGHLIGHTED = 'unread_not_highlighted';
- const OPTION_SINGLE_POST = 'single_post';
- /** Constant indicating that post is not rated by user */
- const NO_RATING = 999;
- // Object variables and accessors
- /////////////////////////////////
- private $discussion, $parentpost, $postfields, $full, $children,
- $forceexpand, $nextunread, $previousunread;
- /** @return mod_forumng The forum that this post is in */
- public function get_forum() { return $this->discussion->get_forum(); }
- /** @return mod_forumng_post Parent post*/
- public function get_parent() {
- if ($this->parentpost==self::PARENT_NOT_LOADED) {
- throw new coding_exception('Parent post not loaded');
- }
- return $this->parentpost;
- }
- /** @return mod_forumng_discussion The discussion that this post is in */
- public function get_discussion() { return $this->discussion; }
- /** @return int ID of this post */
- public function get_id() {
- return $this->postfields->id;
- }
- /** @return string Subject or null if no change in subject */
- public function get_subject() {
- return $this->postfields->subject;
- }
- /** @return int Post number [within discussion] */
- public function get_number() {
- if (!property_exists($this->postfields, 'number')) {
- throw new coding_exception('Post number not available here');
- }
- return $this->postfields->number;
- }
- /**
- * Use to obtain link parameters when linking to any page that has anything
- * to do with posts.
- */
- public function get_link_params($type, $currentuser = false) {
- global $USER;
- $params = 'p=' . $this->postfields->id .
- $this->get_forum()->get_clone_param($type);
- if ($currentuser) {
- $author = $this->get_user();
- if ($author->id == $USER->id) {
- $params .= '¤tuser=1';
- }
- }
- return $params;
- }
- /**
- * @return bool True if can flag
- */
- function can_flag() {
- // Cannot flag for deleted post
- if ($this->get_deleted() || $this->discussion->is_deleted()) {
- return false;
- }
- // The guest user cannot flag
- if (isguestuser()) {
- return false;
- }
- return true;
- }
- /** @return bool True if post is flagged by current user */
- public function is_flagged() {
- if (!property_exists($this->postfields, 'flagged')) {
- throw new coding_exception('Flagged information not available here');
- }
- return $this->postfields->flagged ? true : false;
- }
- /**
- * @param bool $flag True to set flag
- * @param int $userid User ID or 0 for current
- */
- public function set_flagged($flag, $userid=0) {
- global $DB;
- $userid = mod_forumng_utils::get_real_userid($userid);
- if ($flag) {
- $transaction = $DB->start_delegated_transaction();
- // Check there is not already a row
- if (!$DB->record_exists('forumng_flags',
- array('postid' => $this->get_id(), 'userid' => $userid))) {
- // Insert new row
- $newflag = (object)array('postid' => $this->get_id(),
- 'userid' => $userid, 'flagged' => time());
- $DB->insert_record('forumng_flags', $newflag);
- }
- // Note: Under rare circumstances this could result in two rows
- // for the same post and user, resulting in duplicates being
- // returned. This is dealt with in mod_forumng::get_flagged_posts.
- $transaction->allow_commit();
- } else {
- $DB->delete_records('forumng_flags',
- array('postid' => $this->get_id(), 'userid' => $userid));
- }
- }
- /**
- * Obtains the subject to use for this post where a subject is required
- * (should not be blank), such as in email. May be of the form Re:
- * <parent subject>. This function call makes a database query if the full
- * discussion was not loaded into memory.
- * @param bool $expectingquery Set to true if you think this might make
- * a db query (to prevent the warning)
- * @return string Subject
- */
- public function get_effective_subject($expectingquery = false) {
- if (property_exists($this->postfields, 'effectivesubject')) {
- return $this->postfields->effectivesubject;
- }
- // If subject is set in this post, return it
- if (!is_null($this->postfields->subject)) {
- $this->postfields->effectivesubject = $this->postfields->subject;
- return $this->postfields->effectivesubject;
- }
- // See if we already have other posts loaded
- if ($this->parentpost == self::PARENT_NOT_LOADED) {
- // Posts are not loaded, do a database query
- if (!$expectingquery) {
- debugging('This get method made a DB query; if this is expected,
- set the flag to say so', DEBUG_DEVELOPER);
- }
- $this->postfields->effectivesubject = self::
- inner_get_recursive_subject($this->postfields->parentpostid);
- return $this->postfields->effectivesubject;
- } else {
- // Posts are loaded, loop through them to find subject
- for ($parent = $this->parentpost; $parent!=null;
- $parent = $parent->parentpost) {
- if ($parent->postfields->subject!==null) {
- return get_string('re', 'forumng',
- $parent->postfields->subject);
- }
- }
- return '[subject error]'; // shouldn't get here
- }
- }
- /**
- * Given a post id - or the id of some ancestor of a post - this query
- * obtains the next (up to) 8 ancestors and returns a 'Re:' subject line
- * corresponding to the first ancestor which has a subject. If none of
- * the 8 have a subject, it makes another query to retrieve the next 8,
- * and so on.
- * @param int $parentid ID of a child post that we are trying to find
- * the subject from a parent of
- * @return string Subject of post ('Re: something')
- */
- private static function inner_get_recursive_subject($parentid) {
- global $DB;
- // Although the query looks scary because it has so many left joins,
- // in testing it worked quickly. The db just does eight primary-key
- // lookups. Analysis of existing posts in our database showed that
- // doing 8 levels is currently sufficient for about 98.7% of posts.
- $select = '';
- $join = '';
- $maxdepth = self::PARENTPOST_DEPTH_PER_QUERY;
- for ($depth = 1; $depth <= $maxdepth; $depth++) {
- $select .= "p$depth.subject AS s$depth, p$depth.deleted AS d$depth, ";
- if ($depth >= 2) {
- $prev = $depth - 1;
- $join .= "LEFT JOIN {forumng_posts} p$depth
- ON p$depth.id = p$prev.parentpostid ";
- }
- }
- do {
- $rec = $DB->get_record_sql("
- SELECT
- $select
- p$maxdepth.parentpostid AS nextparent
- FROM
- {forumng_posts} p1
- $join
- WHERE
- p1.id = ?
- ", array($parentid), '*', MUST_EXIST);
- for ($depth = 1; $depth <= $maxdepth; $depth++) {
- $var = "s$depth";
- $var2 = "d$depth";
- if ($rec->{$var} !== null && $rec->{$var2}==0) {
- return get_string('re', 'forumng', $rec->{$var});
- }
- }
- $parentid = $rec->nextparent;
- } while ($parentid);
- // If the database and memory representations are correct, we shouldn't
- // really get here because the top-level post always has a subject
- return '';
- }
- /** @return object User who created this post */
- public function get_user() {
- if (!property_exists($this->postfields, 'user')) {
- throw new coding_exception('User is not available at this point.');
- }
- return $this->postfields->user;
- }
- /** @return object User who last edited this post or null if no edits */
- public function get_edit_user() {
- if (!property_exists($this->postfields, 'edituser')) {
- throw new coding_exception('Edit user is not available at this point.');
- }
- return is_null($this->postfields->edituserid)
- ? null : $this->postfields->edituser;
- }
- /** @return int Time post was originally created */
- public function get_created() { return $this->postfields->created; }
- /** @return int Time post was most recently modified */
- public function get_modified() { return $this->postfields->modified; }
- /** @return int 0 if post is not deleted, otherwise time of deletion */
- public function get_deleted() { return $this->postfields->deleted; }
- /** @return object User object (basic fields) of deleter */
- public function get_delete_user() { return $this->postfields->deleteuser; }
- /** @return bool True if this is an old version of a post */
- public function is_old_version() {
- return $this->postfields->oldversion ? true : false;
- }
- /** @return bool True if the post is important */
- public function is_important() {
- return $this->postfields->important ? true : false;
- }
- /** @return string Message data from database (May be in arbitrary format) */
- public function get_raw_message() {
- return $this->postfields->message;
- }
- /** @return int Format of message (Moodle FORMAT_xx constant) */
- public function get_format() {
- return $this->postfields->messageformat;
- }
- /**
- * @return string Message after format_text and replacing file URLs
- */
- public function get_formatted_message() {
- global $CFG;
- require_once($CFG->dirroot . '/lib/filelib.php');
- $text = $this->postfields->message;
- $forum = $this->get_forum();
- // Add clone param to end of pluginfile requests
- if ($forum->is_shared()) {
- // "@@PLUGINFILE@@/cheese.gif?download=1"
- $text = preg_replace('~([\'"]@@PLUGINFILE@@[^\'"?]+)\?~',
- '$1?clone=' . $forum->get_course_module_id() . '&', $text);
- // "@@PLUGINFILE@@/cheese.gif"
- $text = preg_replace('~([\'"]@@PLUGINFILE@@[^\'"?]+)([\'"])~',
- '$1?clone=' . $forum->get_course_module_id() . '$2', $text);
- }
- $text = file_rewrite_pluginfile_urls($text, 'pluginfile.php',
- $forum->get_context(true)->id, 'mod_forumng', 'message',
- $this->postfields->id);
- $textoptions = new stdClass();
- // Don't put a <p> tag round post
- $textoptions->para = false;
- // Does not indicate that we trust the text, only that the
- // TRUSTTEXT marker would be supported. At present though it isn't (hm)
- $textoptions->trusttext = false;
- return format_text($text, $this->postfields->messageformat, $textoptions,
- $forum->get_course_id());
- }
- /**
- * @return string Message after format_text_email and replacing file URLs
- */
- public function get_email_message() {
- global $CFG;
- require_once($CFG->dirroot . '/lib/filelib.php');
- $text = file_rewrite_pluginfile_urls($this->postfields->message, 'pluginfile.php',
- $this->get_forum()->get_context(true)->id, 'mod_forumng', 'message',
- $this->postfields->id);
- return format_text_email($text, $this->postfields->messageformat);
- }
- /** @return bool True if this message has one or more attachments */
- public function has_attachments() {
- return $this->postfields->attachments ? true : false;
- }
- /**
- * Gets the names of all attachments (if any)
- * @return array Array of attachment names (may be empty). Names only,
- * not including path to attachment folder
- */
- public function get_attachment_names() {
- $result = array();
- if (!$this->has_attachments()) {
- return $result;
- }
- $filecontext = $this->get_forum()->get_context(true);
- $fs = get_file_storage();
- foreach ($fs->get_area_files($filecontext->id, 'mod_forumng', 'attachment',
- $this->get_id(), 'filename', false) as $file) {
- $result[] = $file->get_filename();
- }
- return $result;
- }
- /**
- * @param string $attachment Attachment name (will not be checked for existence)
- * @return moodle_url URL to attachment
- */
- public function get_attachment_url($attachment) {
- $filecontext = $this->get_forum()->get_context(true);
- $params = array();
- if ($this->get_forum()->is_shared()) {
- $params['clone'] = $this->get_forum()->get_course_module_id();
- }
- return new moodle_url('/pluginfile.php/' . $filecontext->id . '/mod_forumng/attachment/' .
- $this->get_id() . '/' . $attachment, $params);
- }
- /**
- * @return string URL of this discussion
- */
- public function get_url() {
- return $this->get_discussion()->get_url() . '#p' . $this->get_id();
- }
- /**
- * Checks unread status (only available when requested as part of whole
- * discussion).
- * @return bool True if this post is unread
- * @throws mod_forumng_exception If unread data is not available
- */
- public function is_unread() {
- // Your own posts are always read (note: technically you can request
- // unread data for another user - so we use the id for whom data was
- // requested, not $USER->id directly).
- $userid = $this->discussion->get_unread_data_user_id();
- if (($this->postfields->edituserid == $userid) ||
- (!$this->postfields->edituserid
- && $this->postfields->userid==$userid)) {
- return false;
- }
- // Posts past sell-by are always read
- $deadline = mod_forumng::get_read_tracking_deadline();
- if ($this->postfields->modified < $deadline) {
- return false;
- }
- // Compare date to discussion read data
- return $this->postfields->modified > $this->discussion->get_time_read();
- }
- /**
- * Checks unread status of child posts (only available when requested as
- * part of whole discussion). Not a recursive method - checks only one
- * level of children.
- * @return bool True if any of the children of this post are unread
- */
- public function has_unread_child() {
- $this->require_children();
- foreach ($this->children as $child) {
- if ($child->is_unread()) {
- return true;
- }
- }
- return false;
- }
- /**
- * Checks if this post has any children (replies).
- * @return bool True if post has one or more replies
- */
- public function has_children() {
- $this->require_children();
- return count($this->children) > 0;
- }
- /**
- * Marks this post as being expanded from the start.
- */
- public function force_expand() {
- $this->forceexpand = true;
- }
- /** @return bool True if this is the first post of a discussion */
- public function is_root_post() {
- return $this->postfields->parentpostid ? false : true;
- }
- /**
- * @throws mod_forumng_exception If rating information wasn't queried
- */
- private function check_ratings() {
- if (!property_exists($this->postfields, 'averagerating')) {
- throw new coding_exception('Rating information not retrieved');
- }
- }
- /**
- * @param bool $astext If true, returns a string rather than a number
- * @return mixed Average rating as float, or a string description if
- * $astext is true
- * @throws mod_forumng_exception If rating information wasn't queried
- */
- public function get_average_rating($astext = false) {
- $this->check_ratings();
- if ($astext) {
- $options = $this->get_forum()->get_rating_options();
- $value = (int)round($this->postfields->averagerating);
- if (array_key_exists($value, $options)) {
- return $options[$value];
- } else {
- return '?'; // Can occur if rating scale is changed
- }
- } else {
- return $this->postfields->averagerating;
- }
- }
- /**
- * @return int Number of ratings of this post (may be 0)
- */
- public function get_num_ratings() {
- $this->check_ratings();
- return $this->postfields->numratings;
- }
- /**
- * @return int Current user's rating of this post or null if none
- * @throws mod_forumng_exception If rating information wasn't queried
- */
- public function get_own_rating() {
- $this->check_ratings();
- return $this->postfields->ownrating;
- }
- /**
- * Obtains search document representing this post.
- * @return local_ousearch_document Document object
- */
- function search_get_document() {
- $doc = new local_ousearch_document();
- $doc->init_module_instance('forumng',
- $this->get_forum()->get_course_module(true));
- if ($groupid = $this->discussion->get_group_id()) {
- $doc->set_group_id($groupid);
- }
- $doc->set_int_refs($this->get_id());
- return $doc;
- }
- /**
- * @param array $out Array that receives list of this post and all
- * children (including nested children) in order
- */
- public function build_linear_children(&$out) {
- $this->require_children();
- $out[count($out)] = $this;
- foreach ($this->children as $child) {
- $child->build_linear_children($out);
- }
- }
- /**
- * Finds a child post (or this one) with the specified ID.
- * @param int $id Post ID
- * @param bool $toplevel True for initial request (makes it throw
- * exception if not found)
- * @return mod_forumng_post Child post
- */
- public function find_child($id, $toplevel=true) {
- if ($this->postfields->id == $id) {
- return $this;
- }
- $this->require_children();
- foreach ($this->children as $child) {
- $result = $child->find_child($id, false);
- if ($result) {
- return $result;
- }
- }
- if ($toplevel) {
- throw new coding_exception("Child id $id not found");
- }
- return null;
- }
- /**
- * Finds which child post (or this) has the most recent modified date.
- * @param mod_forumng_post &$newest Newest post (must be null when calling)
- */
- public function find_newest_child(&$newest) {
- if (!$newest || $newest->get_modified() < $this->get_modified()) {
- $newest = $this;
- }
- $this->require_children();
- foreach ($this->children as $child) {
- $child->find_newest_child($newest);
- }
- }
- /**
- * Adds the ID of all children (and this post itself) to a list.
- * @param array &$list List of IDs
- */
- public function list_child_ids(&$list) {
- $list[] = $this->get_id();
- $this->require_children();
- foreach ($this->children as $child) {
- $child->list_child_ids($list);
- }
- }
- /**
- * @return mod_forumng_post Next unread post or null if there are no more
- */
- public function get_next_unread() {
- $this->require_children();
- return $this->nextunread;
- }
- /**
- * @return mod_forumng_post Previous unread post or null if there are no more
- */
- public function get_previous_unread() {
- $this->require_children();
- return $this->previousunread;
- }
- /**
- * Used by discussion to set up the unread posts.
- * @param mod_forumng_post $nextunread
- * @param mod_forumng_post $previousunread
- */
- function set_unread_list($nextunread, $previousunread) {
- $this->nextunread = $nextunread;
- $this->previousunread = $previousunread;
- }
- // Factory method
- /////////////////
- /**
- * Creates a forum post object, forum object, and all related data from a
- * single forum post ID. Intended when entering a page which uses post ID
- * as a parameter.
- * @param int $id ID of forum post
- * @param int $cloneid If this is in a shared forum, must be the id of the
- * clone forum currently in use, or CLONE_DIRECT; otherwise must be 0
- * @param bool $wholediscussion If true, retrieves entire discussion
- * instead of just this single post
- * @param bool $usecache True to use cache when retrieving the discussion
- * @param int $userid User ID to get post on behalf of (controls flag data
- * retrieved)
- * @return mod_forumng_post Post object
- */
- public static function get_from_id($id, $cloneid,
- $wholediscussion=false, $usecache=false, $userid=0) {
- if ($wholediscussion) {
- $discussion = mod_forumng_discussion::get_from_post_id($id, $cloneid,
- $usecache, $usecache);
- $root = $discussion->get_root_post();
- return $root->find_child($id);
- } else {
- // Get post data (including extra data such as ratings and flags)
- $records = self::query_posts('fp.id = ?', array($id), 'fp.id', true,
- true, false, $userid);
- if (count($records)!=1) {
- throw new coding_exception("Invalid post ID $id");
- }
- $postfields = reset($records);
- $discussion = mod_forumng_discussion::get_from_id($postfields->discussionid, $cloneid);
- $newpost = new mod_forumng_post($discussion, $postfields);
- return $newpost;
- }
- }
- /**
- * Obtains a search document given the ousearch parameters.
- * @param object $document Object containing fields from the ousearch documents table
- * @return mixed False if object can't be found, otherwise object containing the following
- * fields: ->content, ->title, ->url, ->activityname, ->activityurl,
- * and optionally ->extrastrings array, ->data, ->hide
- */
- static function search_get_page($document) {
- global $DB, $CFG, $USER;
- // Implemented directly in SQL for performance, rather than using the
- // objects themselves
- $result = $DB->get_record_sql("
- SELECT
- fp.message AS content, fp.subject, firstpost.subject AS firstpostsubject,
- firstpost.id AS firstpostid, fd.id AS discussionid,
- f.name AS activityname, cm.id AS cmid, fd.timestart, fd.timeend,
- f.shared AS shared, f.type AS forumtype
- FROM
- {forumng_posts} fp
- INNER JOIN {forumng_discussions} fd ON fd.id = fp.discussionid
- INNER JOIN {forumng_posts} firstpost ON fd.postid = firstpost.id
- INNER JOIN {forumng} f ON fd.forumngid = f.id
- INNER JOIN {course_modules} cm ON cm.instance = f.id AND cm.course = f.course
- INNER JOIN {modules} m ON cm.module = m.id
- WHERE
- fp.id = ? AND m.name = 'forumng'", array($document->intref1), '*', IGNORE_MISSING);
- if (!$result) {
- return false;
- }
- // Title is either the post subject or Re: plus the discussion subject
- // if the post subject is blank
- $result->title = $result->subject;
- if (is_null($result->title)) {
- $result->title = get_string('re', 'forumng', $result->firstpostsubject);
- }
- // Link is to value in url if present, otherwise to original forum
- $cloneparam = $result->cmid;
- if ($result->shared) {
- global $FORUMNG_CLONE_MAP;
- if (!empty($FORUMNG_CLONE_MAP)) {
- $cloneparam = $FORUMNG_CLONE_MAP[$result->cmid]->id;
- $clonebit = '&clone=' . $cloneparam;
- } else {
- $clonebit = '&clone=' .
- ($cloneparam = optional_param('clone', $result->cmid, PARAM_INT));
- }
- } else {
- $clonebit = '';
- }
- // Work out URL to post
- $result->url = $CFG->wwwroot . '/mod/forumng/discuss.php?d=' .
- $result->discussionid . $clonebit . '#p' . $document->intref1;
- // Activity URL
- $result->activityurl = $CFG->wwwroot . '/mod/forumng/view.php?id=' .
- $result->cmid . $clonebit;
- // Hide results outside their time range (unless current user can see)
- $now = time();
- if ($now < $result->timestart || ($result->timeend && $now>=$result->timeend) &&
- !has_capability('mod/forumng:viewallposts',
- context_module::instance($result->cmid))) {
- $result->hide = true;
- }
- // Handle annoying forum types that hide discussions
- $type = forumngtype::get_new($result->forumtype);
- if ($type->has_unread_restriction()) {
- // TODO The name of the _unread_restriction should be _discussion_restriction.
- // This is going to be slow, we need to load the discussion
- $discussion = mod_forumng_discussion::get_from_id($result->discussionid, $cloneparam);
- if (!$type->can_view_discussion($discussion, $USER->id)) {
- $result->hide = true;
- }
- }
- return $result;
- }
- // Object methods
- /////////////////
- /**
- * @param mod_forumng_discussion $discussion Discussion object
- * @param object $postfields Post fields from DB table (may also include
- * some extra fields provided by mod_forumng_post::query_posts)
- * @param mod_forumng_post $parentpost Parent post or null if this is root post,
- * or PARENT_NOT_LOADED if not available
- */
- function __construct($discussion, $postfields, $parentpost=self::PARENT_NOT_LOADED) {
- $this->discussion = $discussion;
- $this->postfields = $postfields;
- // Extract the user details into Moodle user-like objects
- if (property_exists($postfields, 'u_id')) {
- $postfields->user = mod_forumng_utils::extract_subobject($postfields, 'u_');
- $postfields->edituser = mod_forumng_utils::extract_subobject($postfields, 'eu_');
- $postfields->deleteuser = mod_forumng_utils::extract_subobject($postfields, 'du_');
- }
- $this->parentpost = $parentpost;
- $this->children = false;
- }
- /**
- * Used to inform the post that all its children will be supplied.
- * Call before calling add_child(), or even if there are no children.
- */
- function init_children() {
- $this->children = array();
- }
- /**
- * For internal use only. Adds a child to this post while constructing
- * the tree of posts
- * @param mod_forumng_post $child Child post
- */
- function add_child($child) {
- $this->require_children();
- $this->children[] = $child;
- }
- /**
- * Checks that children are available.
- * @throws mod_forumng_exception If children have not been loaded
- */
- function require_children() {
- if (!is_array($this->children)) {
- throw new coding_exception('Requires child post data');
- }
- }
- /**
- * Internal function. Queries for posts.
- * @param string $where Where clause (fp is alias for post table)
- * @param array $whereparams Parameters (values for ? parameters) in where clause
- * @param string $order Sort order; the default is fp.id - note this is preferable
- * to fp.timecreated because it works correctly if there are two posts in
- * the same second
- * @param bool $ratings True if ratings should be included in the query
- * @param bool $flags True if flags should be included in the query
- * @param bool $effectivesubjects True if the query should include the
- * (complicated!) logic to obtain the 'effective subject'. This may result
- * in additional queries afterward for posts which are very deeply nested.
- * @param int $userid 0 = current user (at present this is only used for
- * flags)
- * @return array Resulting posts as array of Moodle records, empty array
- * if none
- */
- static function query_posts($where, $whereparams, $order='fp.id', $ratings=true,
- $flags=false, $effectivesubjects=false,
- $userid=0, $joindiscussion=false, $discussionsubject=false, $limitfrom='',
- $limitnum='') {
- global $DB, $USER;
- $userid = mod_forumng_utils::get_real_userid($userid);
- $queryparams = array();
- // We include ratings if these are enabled, otherwise save the database
- // some effort and don't bother
- if ($ratings) {
- $ratingsquery = ",
- (SELECT AVG(rating) FROM {forumng_ratings}
- WHERE postid = fp.id) AS averagerating,
- (SELECT COUNT(1) FROM {forumng_ratings}
- WHERE postid = fp.id) AS numratings,
- (SELECT rating FROM {forumng_ratings}
- WHERE postid = fp.id AND userid = ?) AS ownrating";
- // Add parameter to start of params list
- $queryparams[] = $USER->id;
- } else {
- $ratingsquery = '';
- }
- if ($flags) {
- $flagsjoin = "
- LEFT JOIN {forumng_flags} ff ON ff.postid = fp.id AND ff.userid = ?";
- $flagsquery = ", ff.flagged";
- $queryparams[] = $userid;
- } else {
- $flagsjoin = '';
- $flagsquery = '';
- }
- if ($joindiscussion) {
- $discussionjoin = "
- INNER JOIN {forumng_discussions} fd ON fp.discussionid = fd.id";
- $discussionquery = ',' . mod_forumng_utils::select_discussion_fields('fd');
- if ($discussionsubject) {
- $discussionjoin .= "
- INNER JOIN {forumng_posts} fdfp ON fd.postid = fdfp.id";
- $discussionquery .= ', fdfp.subject AS fd_subject';
- }
- } else {
- $discussionjoin = '';
- $discussionquery = '';
- }
- if ($effectivesubjects) {
- $maxdepth = self::PARENTPOST_DEPTH_PER_QUERY;
- $subjectsjoin = '';
- $subjectsquery = ", p$maxdepth.parentpostid AS nextparent ";
- for ($depth = 2; $depth <= $maxdepth; $depth++) {
- $subjectsquery .= ", p$depth.subject AS s$depth, p$depth.deleted AS d$depth";
- $prev = 'p'. ($depth - 1);
- if ($prev == 'p1') {
- $prev = 'fp';
- }
- $subjectsjoin .= "LEFT JOIN {forumng_posts} p$depth
- ON p$depth.id = $prev.parentpostid ";
- }
- } else {
- $subjectsjoin = '';
- $subjectsquery = '';
- }
- // Retrieve posts from discussion with incorporated user information
- // and ratings info if specified
- $results = $DB->get_records_sql("
- SELECT
- fp.*,
- ".mod_forumng_utils::select_username_fields('u').",
- ".mod_forumng_utils::select_username_fields('eu').",
- ".mod_forumng_utils::select_username_fields('du')."
- $ratingsquery
- $flagsquery
- $subjectsquery
- $discussionquery
- FROM
- {forumng_posts} fp
- INNER JOIN {user} u ON fp.userid = u.id
- LEFT JOIN {user} eu ON fp.edituserid = eu.id
- LEFT JOIN {user} du ON fp.deleteuserid = du.id
- $discussionjoin
- $flagsjoin
- $subjectsjoin
- WHERE
- $where
- ORDER BY
- $order
- ", array_merge($queryparams, $whereparams), $limitfrom, $limitnum);
- if ($effectivesubjects) {
- // Figure out the effective subject for each result
- foreach ($results as $result) {
- $got = false;
- if ($result->subject !== null) {
- $result->effectivesubject = $result->subject;
- $got = true;
- continue;
- }
- for ($depth = 2; $depth <= $maxdepth; $depth++) {
- $var = "s$depth";
- $var2 = "d$depth";
- if (!$got && $result->{$var} !== null && $result->{$var2}==0) {
- $result->effectivesubject = get_string('re', 'forumng', $result->{$var});
- $got = true;
- }
- unset($result->{$var});
- unset($result->{$var2});
- }
- if (!$got) {
- // Do extra queries to pick up subjects for posts where it
- // was unknown within the default depth. We can use the
- // 'nextparent' to get the ID of the parent post of the last
- // one that we checked already
- $result->effectivesubject = self::inner_get_recursive_subject(
- $result->nextparent);
- }
- }
- }
- return $results;
- }
- /**
- * Replies to the post
- * @param string $subject Subject
- * @param string $message Message
- * @param int $messageformat Moodle format used for message
- * @param bool $attachments True if post contains attachments
- * @param bool $setimportant If true, highlight the post
- * @param bool $mailnow If true, sends mail ASAP
- * @param int $userid User ID (0 = current)
- * @param bool $log True to log this reply
- * @return int ID of newly-created post
- */
- function reply($subject, $message, $messageformat,
- $attachments=false, $setimportant=false, $mailnow=false, $userid=0, $log=true) {
- global $DB;
- $transaction = $DB->start_delegated_transaction();
- $id = $this->discussion->create_reply($this, $subject, $message, $messageformat,
- $attachments, $setimportant, $mailnow, $userid);
- if ($log) {
- $this->log('add reply', $id);
- }
- $transaction->allow_commit();
- $this->get_discussion()->uncache();
- return $id;
- }
- /**
- * Updates the message field of a post entry. This is necessary in some cases where
- * the user includes images etc. in the message; these are initially included using
- * a draft URL which has to be changed to a special relative path on convert, and we
- * can't do that until the post ID is known. Additionally, we don't have a post object
- * at that point, hence use of static function.
- * @param int $postid ID of post to update
- * @param string $newtext Updated message text
- */
- static function update_message_for_files($postid, $newtext) {
- global $DB;
- $DB->set_field('forumng_posts', 'message', $newtext, array('id'=>$postid));
- }
- /**
- * Obtains a list of previous versions of this post (if any), in descending
- * order of modification date.
- * @return array Array of mod_forumng_post objects (empty if none)
- */
- function get_old_versions() {
- $postdata = self::query_posts(
- 'fp.oldversion = 1 AND fp.parentpostid = ?', array($this->postfields->id),
- 'fp.modified DESC', false, false);
- $posts = array();
- foreach ($postdata as $postfields) {
- $newpost = new mod_forumng_post($this->discussion, $postfields, $this);
- $posts[] = $newpost;
- }
- return $posts;
- }
- /**
- * Recursive function obtains all users IDs that made this post and all
- * child posts.
- * @param array &$userids Associative array from id=>true that receives
- * all user IDs
- */
- function list_all_user_ids(&$userids) {
- $this->require_children();
- // Add current ID
- $userid = $this->get_user()->id;
- if (!array_key_exists($userid, $userids)) {
- $userids[$userid] = true;
- }
- foreach ($this->children as $post) {
- $post->list_all_user_ids($userids);
- }
- }
- /**
- * NOTE: This method is the second stage of editing and must be called
- * after edit_start and after files are being updated. This is because
- * it depends on the result of file_save_draft_area_files.
- * @param string $message Message
- * @param int $messageformat Moodle format ID
- * @param bool $gotsubject True if message subject changed
- */
- function edit_finish($message, $messageformat, $gotsubject) {
- global $DB;
- $transaction = $DB->start_delegated_transaction();
- $update = new StdClass;
- if ($message!==$this->postfields->message) {
- $update->message = $message;
- }
- if ($messageformat != $this->postfields->messageformat) {
- $update->messageformat = $messageformat;
- }
- if (count((array)$update)>0) {
- $update->id = $this->postfields->id;
- $DB->update_record('forumng_posts', $update);
- }
- // Update in-memory representation
- foreach ((array)$update as $name=>$value) {
- $this->postfields->{$name} = $value;
- }
- // Uncache before updating search (want to make sure that the recursive
- // update gets latest data)
- $this->get_discussion()->uncache();
- // Update search index
- if ((isset($update->message) || $gotsubject)) {
- // Update for this post
- $this->search_update();
- // If changing the subject of a root post, update all posts in the
- // discussion (ugh)
- if ($this->is_root_post() && $gotsubject) {
- $this->search_update_children();
- }
- }
- $transaction->allow_commit();
- }
- /**
- * Edits an existing message. The previous version of the message is
- * retained for admins to view if needed.
- *
- * NOTE: This method is the first stage of editing and must be called
- * BEFORE files are updated. Ensure that there is a DB transaction around
- * the calls to these two methods.
- * @param string $subject Subject
- * @param bool $attachments True if post now contains attachments
- * @param bool $setimportant If true, highlight the post
- * @param bool $mailnow New value of mailnow flag (ignored if message was already mailed)
- * @param int $userid Userid doing the editing (0 = current)
- * @return bool True if subject changed (this is weird, but edit_finish
- * needs it)
- */
- function edit_start($subject, $attachments=false, $setimportant=false,
- $mailnow=false, $userid=0, $log=true) {
- global $DB;
- $now = time();
- // Create copy of existing entry ('old version')
- $copy = clone($this->postfields);
- // Copy has oldversion set to 1 and parentpost set to id of real post
- $copy->oldversion = 1;
- $copy->parentpostid = $copy->id;
- unset($copy->id);
- // OK, add copy
- $transaction = $DB->start_delegated_transaction();
- $copyid = $DB->insert_record('forumng_posts', $copy);
- // Move old attachments to copy (note: we will save new attachments from filemanager draft
- // area later)
- if ($this->has_attachments()) {
- $fs = get_file_storage();
- $filecontext = $this->get_forum()->get_context(true);
- foreach (array('attachment', 'message') as $filearea) {
- $oldfiles = $fs->get_area_files($filecontext->id, 'mod_forumng', $filearea,
- $this->get_id(), 'id', false);
- foreach ($oldfiles as $oldfile) {
- $filerecord = new stdClass();
- $filerecord->itemid = $copyid;
- $fs->create_file_from_storedfile($filerecord, $oldfile);
- }
- $fs->delete_area_files($filecontext->id, 'mod_forumng',
- $filearea, $this->get_id());
- }
- }
- // Update existing entry with new data where it changed
- $update = new StdClass;
- $gotsubject = false;
- if ($subject!==$this->postfields->subject) {
- $update->subject = strlen(trim($subject)) == 0 ? null : $subject;
- $gotsubject = true;
- }
- if (!$attachments && $this->postfields->attachments) {
- $update->attachments = 0;
- } else if ($attachments && !$this->postfields->attachments) {
- $update->attachments = 1;
- }
- if ($setimportant) {
- $update->important = 1;
- } else {
- $update->important = 0;
- }
- if ($this->postfields->mailstate==mod_forumng::MAILSTATE_NOT_MAILED &&
- $mailnow) {
- $update->mailstate = mod_forumng::MAILSTATE_NOW_NOT_MAILED;
- } else if ($this->postfields->mailstate==mod_forumng::MAILSTATE_NOW_NOT_MAILED &&
- !$mailnow) {
- $update->mailstate = mod_forumng::MAILSTATE_NOT_MAILED;
- }
- $update->modified = $now;
- $update->edituserid = mod_forumng_utils::get_real_userid($userid);
- $update->id = $this->postfields->id;
- $DB->update_record('forumng_posts', $update);
- if ($log) {
- $this->log('edit post');
- }
- // Update in-memory representation
- foreach ((array)$update as $name=>$value) {
- $this->postfields->{$name} = $value;
- }
- // If this is the root post, then changing its subject affects
- // the discussion subhject
- if ($this->is_root_post() && $gotsubject) {
- $this->discussion->hack_subject($this->postfields->subject);
- }
- $transaction->allow_commit();
- return $gotsubject;
- }
- /**
- * Updates search data for this post.
- * @param bool $expectingquery True if it might need to make a query to
- * get the subject
- */
- function search_update($expectingquery = false) {
- if (!mod_forumng::search_installed()) {
- return;
- }
- global $DB;
- $searchdoc = $this->search_get_document();
- $transaction = $DB->start_delegated_transaction();
- if ($this->get_deleted() || $this->get_discussion()->is_deleted() ||
- $this->get_discussion()->is_making_search_change()) {
- if ($searchdoc->find()) {
- $searchdoc->delete();
- }
- } else {
- // $title here is not the title appearing in the search result
- // but the text which decides the search score
- $title = $this->get_subject();
- $searchdoc->update($title, $this->get_formatted_message());
- }
- $transaction->allow_commit();
- }
- /**
- * Calls search_update on each child of the current post, and recurses.
- * Used when the subject's discussion is changed.
- */
- function search_update_children() {
- if (!mod_forumng::search_installed()) {
- return;
- }
- // If the in-memory post object isn't already part of a full
- // discussion...
- if (!is_array($this->children)) {
- // ...then get one
- $discussion = mod_forumng_discussion::get_from_id(
- $this->discussion->get_id(),
- $this->get_forum()->get_course_module_id());
- $post = $discussion->get_root_post()->find_child($this->get_id());
- // Do this update on the new discussion
- $post->search_update_children();
- return;
- }
- // Loop through all children
- foreach ($this->children as $child) {
- // Update its search fields
- $child->search_update();
- // Recurse
- $child->search_update_children();
- }
- }
- /**
- * Marks a post as deleted.
- * @param int $userid User ID to mark as having deleted the post
- * @param bool $log If true, adds entry to Moodle log
- */
- function delete($userid=0, $log=true) {
- global $DB;
- if ($this->postfields->deleted) {
- return;
- }
- if (!$this->postfields->parentpostid) {
- throw new coding_exception('Cannot delete discussion root post');
- }
- $transaction = $DB->start_delegated_transaction();
- // Mark this post as deleted
- $update = new StdClass;
- $update->id = $this->postfields->id;
- $update->deleted = time();
- $update->deleteuserid = mod_forumng_utils::get_real_userid($userid);
- $DB->update_record('forumng_posts', $update);
- $this->postfields->deleted = $update->deleted;
- $this->postfields->deleteuserid = $update->deleteuserid;
- // In case this post is the last one, update the discussion field
- $this->get_discussion()->possible_lastpost_change($this);
- // May result in user becoming incomplete
- $this->update_completion(false);
- if ($log) {
- $this->log('delete post');
- }
- $this->search_update();
- $transaction->allow_commit();
- $this->get_discussion()->uncache();
- }
- /**
- * Marks a post as undeleted.
- * @param bool $log If true, adds entry to Moodle log
- */
- function undelete($log=true) {
- global $DB;
- if (!$this->postfields->deleted) {
- return;
- }
- $transaction = $DB->start_delegated_transaction();
- // Undelete this post
- $update = new StdClass;
- $update->id = $this->postfields->id;
- $update->deleted = 0;
- $update->deleteuserid = 0;
- $DB->update_record('forumng_posts', $update);
- $this->postfields->deleted = 0;
- $this->postfields->deleteuserid = 0;
- // In case this post is the last one, update the discussion field
- $this->get_discussion()->possible_lastpost_change($this);
- // May result in user becoming complete
- $this->update_completion(true);
- if ($log) {
- $this->log('undelete post');
- }
- $this->search_update();
- $transaction->allow_commit();
- $this->get_discussion()->uncache();
- }
- /**
- * Splits this post to become a new discussion
- * @param $newsubject
- * @param bool $log True to log action
- * @return int ID of new discussion
- */
- function split($newsubject, $log=true) {
- global $DB;
- $this->require_children();
- // Begin a transaction
- $transaction = $DB->start_delegated_transaction();
- $olddiscussion = $this->get_discussion();
- // Create new discussion
- $newest = null;
- $this->find_newest_child($newest);
- $newdiscussionid = $olddiscussion->clone_for_split(
- $this->get_id(), $newest->get_id());
- // Update all child posts
- $list = array();
- $this->list_child_ids($list);
- unset($list[0]); // Don't include this post itself
- if (count($list) > 0) {
- list($listsql, $listparams) = mod_forumng_utils::get_in_array_sql('id', $list);
- $DB->execute("
- UPDATE
- {forumng_posts}
- SET
- discussionid = ?
- WHERE
- $listsql", array_merge(array($newdiscussionid), $listparams));
- }
- // Update any edited posts in this discussion. Edited posts are
- // not included in the child id list above because they are not
- // loaded as children, but they are conceptually stored as children
- // of one of the posts being moved.
- $parentlist = $list;
- $parentlist[] = $this->get_id();
- list($parentlistsql, $parentlistparams) = mod_forumng_utils::get_in_array_sql(
- 'parentpostid', $parentlist);
- $DB->execute("
- UPDATE
- {forumng_posts}
- SET
- discussionid = ?
- WHERE
- oldversion = 1 AND $parentlistsql", array_merge(array($newdiscussionid), $parentlistparams));
- // Update this post
- $changes = new stdClass;
- $changes->id = $this->get_id();
- $changes->subject = $newsubject;
- $changes->parentpostid = null;
- //When split the post, reset the important to 0 so that it is not highlighted.
- $changes->important = 0;
- // Note don't update modified time, or it makes this post unread,
- // which isn't very helpful
- $changes->discussionid = $newdiscussionid;
- $DB->update_record('forumng_posts', $changes);
- // Update read data if relevant
- if (mod_forumng::enabled_read_tracking() &&
- ($newest->get_modified() >= mod_forumng::get_read_tracking_deadline())) {
- $rs = $DB->get_recordset_sql("
- SELECT
- userid, time
- FROM
- {forumng_read}
- WHERE
- discussionid = ? AND time >= ?", array($olddiscussion->get_id(), $this->get_created()));
- foreach ($rs as $rec) {
- $rec->discussionid = $newdiscussionid;
- $DB->insert_record('forumng_read', $rec);
- }
- $rs->close();
- }
- $olddiscussion->possible_lastpost_change();
- if ($log) {
- $this->log('split post');
- }
- $transaction->allow_commit();
- $this->get_discussion()->uncache();
- // If discussion-based completion is turned on, this may enable someone
- // to complete
- if ($this->get_forum()->get_completion_discussions()) {
- $this->update_completion(true);
- }
- return $newdiscussionid;
- }
- /**
- * Rates this post or updates an existing rating.
- * @param $rating Rating (value depends on scale used) or NO_RATING
- * @param $userid User ID or 0 for current user
- */
- function rate($rating, $userid=0) {
- global $DB;
- $userid = mod_forumng_utils::get_real_userid($userid);
- $transaction = $DB->start_delegated_transaction();
- // Delete any existing rating
- $DB->delete_records('forumng_ratings',
- array('postid' => $this->postfields->id, 'userid' => $userid));
- // Add new rating
- if ($rating != self::NO_RATING) {
- $ratingobj = new StdClass;
- $ratingobj->userid = $userid;
- $ratingobj->postid = $this->postfields->id;
- $ratingobj->time = time();
- $ratingobj->rating = $rating;
- $DB->insert_record('forumng_ratings', $ratingobj);
- }
- // Tell grade to update
- if ($this->get_forum()->get_grading()) {
- $this->get_forum()->update_grades($this->get_user()->id);
- }
- $transaction->allow_commit();
- $this->get_discussion()->uncache();
- }
- /**
- * Records an action in the Moodle log for current user.
- * @param string $action Action name - see datalib.php for suggested verbs
- * and this code for example usage
- * @param int $replyid Specify only when adding a reply; when specified,
- * this is the reply ID (used because the reply entry is logged under
- * the new post, not the old one)
- */
- function log($action, $replyid=0) {
- if ($replyid) {
- $postid = $replyid;
- } else {
- $postid = $this->postfields->id;
- }
- add_to_log($this->get_forum()->get_course_id(), 'forumng',
- $action,
- $this->discussion->get_log_url() . '#p' . $postid,
- $this->postfields->id,
- $this->get_forum()->get_course_module_id());
- }
- // Permissions
- //////////////
- /**
- * Makes security checks for viewing this post. Will not return if
- * user cannot view it.
- * This function should be a complete access check. It calls the
- * discussion's equivalent method.
- * Note that this function only works for the current user when used in
- * interactive mode (ordinary web page view). It cannot be called in cron,
- * web services, etc.
- */
- function require_view() {
- // Check forum and discussion view permission, group access, etc.
- $this->discussion->require_view();
- // Other than being able to view the discussion, no additional
- // requirements to view a normal post
- if (!$this->get_deleted() && !$this->is_old_version()) {
- return true;
- }
- // Deleted posts and old versions of edited posts require viewallposts
- require_capability('mod/forumng:viewallposts',
- $this->get_forum()->get_context());
- }
- /**
- * Checks whether the user can add a new reply to this post, assuming that
- * they can view the discussion.
- * @param string &$whynot
- * @param int $userid
- * @return unknown_type
- */
- function can_reply(&$whynot, $userid=0) {
- $userid = mod_forumng_utils::get_real_userid($userid);
- $context = $this->get_forum()->get_context();
- // Check if post is a special case
- if ($this->get_deleted() || $this->is_old_version()
- || $this->get_discussion()->is_deleted()) {
- $whynot = 'reply_notcurrentpost';
- return false;
- }
- // Check if discussion is different group
- if (!$this->get_discussion()->can_write_to_group()) {
- $whynot = 'reply_wronggroup';
- return false;
- }
- // Check if discussion is locked
- if ($this->get_discussion()->is_locked()) {
- $whynot = 'edit_locked';
- return false;
- }
- // Check read-only dates
- if ($this->get_forum()->is_read_only($userid)) {
- $whynot = 'reply_readonly';
- return false;
- }
- // Check permission
- if (!has_capability('mod/forumng:replypost', $context, $userid)) {
- $whynot = 'reply_nopermission';
- return false;
- }
- // Let forum type veto reply if required
- if (!$this->get_forum()->get_type()->can_reply($this, $userid)) {
- $whynot = 'reply_typelimit';
- return false;
- }
- // Throttling
- if ($this->get_forum()->get_remaining_post_quota($userid) == 0) {
- $whynot = 'reply_postquota';
- return false;
- }
- return true;
- }
- /**
- * @param int $userid User ID or 0 for current
- * @return bool True if user can rate this post
- */
- function can_rate($userid=0) {
- $userid = mod_forumng_utils::get_real_userid($userid);
- return
- !$this->get_deleted() && !$this->is_old_version()
- && !$this->get_discussion()->is_deleted()
- && !$this->get_discussion()->is_locked()
- && $this->get_discussion()->can_write_to_group()
- && $this->get_forum()->can_rate($this->get_created()) &&
- $this->get_user()->id != $userid;
- }
- /**
- * @param int $userid User ID or 0 for current
- * @return bool True if user can view ratings for this post
- */
- function can_view_ratings($userid=0) {
- $userid = mod_forumng_utils::get_real_userid($userid);
- return !$this->get_deleted() && !$this->is_old_version()
- && $this->get_forum()->has_ratings() &&
- has_capability($this->get_user()->id == $userid
- ? 'mod/forumng:viewrating'
- : 'mod/forumng:viewanyrating', $this->get_forum()->get_context());
- }
- function can_split(&$whynot, $userid=0) {
- // Check if this is a special case
- if ($this->get_deleted() || $this->is_old_version()
- || $this->get_discussion()->is_deleted()) {
- $whynot = 'edit_notcurrentpost';
- return false;
- }
- // Check if discussion is different group
- if (!$this->get_discussion()->can_write_to_group()) {
- $whynot = 'edit_wronggroup';
- return false;
- }
- // Can't split root post
- if ($this->is_root_post()) {
- $whynot = 'edit_rootpost';
- return false;
- }
- // Check permission
- if (!$this->get_discussion()->can_split($whynot, $userid)) {
- return false;
- }
- return true;
- }
- /**
- * @param string &$whynot
- * @return bool True if user can alert this post
- */
- function can_alert(&$whynot) {
- // Check if the post has been deleted
- if ($this->get_deleted() || $this->discussion->is_deleted()) {
- $whynot = 'alert_notcurrentpost';
- return false;
- }
- // If not site level or forum level reporting email has been set
- if (!$this->get_forum()->has_reporting_email()) {
- $whynot = 'alert_turnedoff';
- return false;
- }
- return true;
- }
- /**
- * @param string &$whynot
- * @return bool True if can display the direct link
- */
- function can_showdirectlink() {
- // Check if the post has been deleted
- if ($this->get_deleted() || $this->discussion->is_deleted()) {
- return false;
- }
- return true;
- }
- /**
- * Checks whether the user can delete the post, assuming that they can
- * view the discussion.
- * @param string &$whynot If returning false, set to the language string defining
- * reason for not being able to edit
- * @param int $userid User ID or 0 if current
- * @return bool True if user can edit this post
- */
- function can_undelete(&$whynot, $userid=0) {
- $userid = mod_forumng_utils::get_real_userid($userid);
- $context = $this->get_forum()->get_context();
- // Check if post is a special case
- if ($this->is_old_version() || $this->get_discussion()->is_deleted()) {
- $whynot = 'edit_notcurrentpost';
- return false;
- }
- // Check if discussion is different group
- if (!$this->get_discussion()->can_write_to_group()) {
- $whynot = 'edit_wronggroup';
- return false;
- }
- // Check if discussion is locked
- if ($this->get_discussion()->is_locked()) {
- $whynot = 'edit_locked';
- return false;
- }
- if (!$this->get_deleted()) {
- $whynot = 'edit_notdeleted';
- return false;
- }
- // Check the 'edit any' capability (always required to undelete)
- if (!has_capability('mod/forumng:editanypost', $context, $userid)) {
- $whynot = 'edit_nopermission';
- return false;
- }
- // Check read-only dates
- if ($this->get_forum()->is_read_only($userid)) {
- $whynot = 'edit_readonly';
- return false;
- }
- // OK! They're allowed to undelete (whew)
- $whynot = '';
- return true;
- }
- /**
- * Checks whether the user can delete the post, assuming that they can
- * view the discussion.
- * @param string &$whynot If returning false, set to the language string defining
- * reason for not being able to edit
- * @param int $userid User ID or 0 if current
- * @return bool True if user can edit this post
- */
- function can_delete(&$whynot, $userid=0) {
- // At present the logic for this is identical to the edit logic
- // except that you can't delete the root post
- return !$this->is_root_post() && $this->can_edit($whynot, $userid);
- }
- /**
- * Checks whether the user can view edits to posts.
- * @param string $whynot If returning false, set to the language string
- * defining reason for not being able to view edits
- * @param int $userid User ID or 0 for current
- * @return bool True if user can view edits
- */
- function can_view_history(&$whynot, $userid=0) {
- $userid = mod_forumng_utils::get_real_userid($userid);
- // Check the 'edit any' capability
- if (!has_capability('mod/forumng:editanypost',
- $this->get_forum()->get_context(), $userid)) {
- $whynot = 'edit_nopermission';
- return false;
- }
- return true;
- }
- /**
- * Checks whether the user can edit the post, assuming that they can
- * view the discussion.
- * @param string &$whynot If returning false, set to the language string defining
- * reason for not being able to edit
- * @param int $userid User ID or 0 if current
- * @return bool True if user can edit this post
- */
- function can_edit(&$whynot, $userid=0) {
- $userid = mod_forumng_utils::get_real_userid($userid);
- $context = $this->get_forum()->get_context();
- // Check if post is a special case
- if ($this->get_deleted() || $this->is_old_version()
- || $this->get_discussion()->is_deleted()) {
- $whynot = 'edit_notcurrentpost';
- return false;
- }
- // Check if discussion is different group
- if (!$this->get_discussion()->can_write_to_group()) {
- $whynot = 'edit_wronggroup';
- return false;
- }
- // Check if discussion is locked
- if ($this->get_discussion()->is_locked()) {
- $whynot = 'edit_locked';
- return false;
- }
- // Check the 'edit any' capability
- $editanypost = has_capability('mod/forumng:editanypost',
- $context, $userid);
- if (!$editanypost) {
- // If they don't have edit any, they must have either the
- // 'start discussion' or 'reply post' capability (the same
- // one they needed to create the post in the first place)
- if (($this->is_root_post() &&
- !has_capability('mod/forumng:startdiscussion', $context, $userid))
- && (!$this->is_root_post() &&
- !has_capability('mod/forumng:replypost', $context, $userid))) {
- $whynot = 'edit_nopermission';
- return false;
- }
- }
- // Check post belongs to specified user
- if (($this->get_user()->id != $userid) && !$editanypost) {
- $whynot = 'edit_notyours';
- return false;
- }
- // Check editing timeout
- if ((time() > $this->get_edit_time_limit()) && !$editanypost) {
- $whynot = 'edit_timeout';
- return false;
- }
- // Check read-only dates
- if ($this->get_forum()->is_read_only($userid)) {
- $whynot = 'edit_readonly';
- return false;
- }
- // OK! They're allowed to edit (whew)
- $whynot = '';
- return true;
- }
- /**
- * @param int $userid User ID or 0 for current
- * @return True if user can ignore the post editing time limit
- */
- function can_ignore_edit_time_limit($userid=0) {
- $userid = mod_forumng_utils::get_real_userid($userid);
- $context = $this->get_forum()->get_context();
- return has_capability('mod/forumng:editanypost',
- $context, $userid);
- }
- /**
- * @return int Time limit after which users who don't have the edit-all
- * permission are not allowed to edit this post (as epoch value)
- */
- function get_edit_time_limit() {
- global $CFG;
- return $this->get_created() + $CFG->maxeditingtime;
- }
- /**
- * Checks that the user can edit this post - requiring all higher-level
- * access too.
- */
- function require_edit() {
- // Check forum and discussion view permission, group access, etc.
- $this->discussion->require_view();
- // Check post edit
- $whynot = '';
- if (!$this->can_edit($whynot)) {
- print_error($whynot, 'forumng', 'discuss.php?' .
- $this->discussion->get_link_params(mod_forumng::PARAM_HTML));
- }
- }
- /**
- * Checks that the user can reply to this post - requiring all higher-level
- * access too.
- */
- function require_reply() {
- // Check forum and discussion view permission, group access, etc.
- $this->discussion->require_view();
- // Check post reply
- $whynot = '';
- if (!$this->can_reply($whynot)) {
- print_error($whynot, 'forumng', 'discuss.php?' .
- $this->discussion->get_link_params(mod_forumng::PARAM_HTML));
- }
- }
- // Email
- ////////
- /**
- * Obtains a version of this post as an email.
- * @param mod_forumng_post $inreplyto Message this one's replying to, or null
- * if none
- * @param string &$subject Output: Message subject
- * @param string $text Output: Message plain text
- * @param string $html Output: Message HTML (or blank if not in HTML mode)
- * @param bool $ishtml True if in HTML mode
- * @param bool $canreply True if user can reply
- * @param bool $viewfullnames True if user gets to see full names even when
- * these are normally hidden
- * @param string $lang Language of receiving user
- * @param number $timezone Time zone of receiving user
- * @param bool $digest True if in digest mode (does not include parent
- * message or surrounding links).
- * @param bool $discussionemail True if digest is of a single disussion;
- * includes 'post 1' information
- * @param array $extraoptions Set values here to add or override post
- * display options
- */
- function build_email($inreplyto, &$subject, &$text, &$html,
- $ishtml, $canreply, $viewfullnames, $lang, $timezone, $digest=false,
- $discussionemail=false, $extraoptions = array()) {
- global $CFG, $USER;
- $oldlang = $USER->lang;
- $USER->lang = $lang;
- $forum = $this->get_forum();
- $cmid = $forum->get_course_module_id();
- $course = $forum->get_course();
- $discussion = $this->get_discussion();
- // Get subject (may make DB query, unfortunately)
- $subject = $course->shortname . ': ' .
- format_string($this->get_effective_subject(true), true);
- $canunsubscribe = mod_forumng::SUBSCRIPTION_FORCED !=
- $forum->get_effective_subscription_option();
- // Header
- $text = '';
- $html = '';
- if (!$discussionemail && !$digest) {
- $html .= "\n<body id='forumng-email'>\n\n";
- }
- // Navigation bar (breadcrumbs)
- if (!$digest) {
- $text .= $forum->get_course()->shortname . ' -> ';
- $html .= "<div class='forumng-email-navbar'><a target='_blank' " .
- "href='$CFG->wwwroot/course/view.php?id=$course->id'>" .
- "$course->shortname</a> » ";
- $text .= format_string($forum->get_name(), true);
- $html .= "<a target='_blank' " .
- "href='$CFG->wwwroot/mod/forumng/view.php?" .
- $forum->get_link_params(mod_forumng::PARAM_HTML) . "'>" .
- format_string($forum->get_name(), true) . '</a>';
- // Makes a query :(
- if ($discussionsubject = $discussion->get_subject(true)) {
- $text .= ' -> ' . format_string($discussionsubject, true);
- $html .= " » <a target='_blank' " .
- "href='$CFG->wwwroot/mod/forumng/discuss.php?" .
- $discussion->get_link_params(mod_forumng::PARAM_HTML) . "'>" .
- format_string($discussionsubject, true).'</a>';
- }
- $html .= '</div>';
- }
- // Main part of email
- $options = array(
- self::OPTION_EMAIL => true,
- self::OPTION_DIGEST => $digest ? true : false,
- self::OPTION_COMMAND_REPLY => ($canreply && !$digest),
- self::OPTION_VIEW_FULL_NAMES => $viewfullnames ? true : false,
- self::OPTION_TIME_ZONE => $timezone,
- self::OPTION_VISIBLE_POST_NUMBERS => $discussionemail,
- self::OPTION_USER_IMAGE => true);
- foreach ($extraoptions as $key => $value) {
- $options[$key] = $value;
- }
- $html .= $this->display(true, $options);
- $displaytext = $this->display(false, $options);
- // In digest, don't display mail divider if mail is blank (== deleted).t
- if ($displaytext !== '' || !$digest) {
- $text .= "\n" . mod_forumng_cron::EMAIL_DIVIDER;
- }
- $text .= $displaytext;
- // Now we need to display the parent post (if any, and if not in digest)
- if ($this->postfields->parentpostid && !$digest) {
- // Print the 'In reply to' heading
- $html .= '<h2>' . get_string('inreplyto', 'forumng') . '</h2>';
- $text .= "\n" . mod_forumng_cron::EMAIL_DIVIDER;
- $text .= get_string('inreplyto', 'forumng'). ":\n\n";
- // Get parent post (unfortunately this requires extra queries)
- $parent = mod_forumng_post::get_from_id(
- $this->postfields->parentpostid,
- $this->get_forum()->get_course_module_id(), false);
- $options = array(
- self::OPTION_EMAIL => true,
- self::OPTION_NO_COMMANDS => true,
- self::OPTION_TIME_ZONE => $timezone);
- foreach ($extraoptions as $key => $value) {
- $options[$key] = $value;
- }
- $html .= $parent->display(true, $options);
- $text .= $parent->display(false, $options);
- }
- if (!$digest && $canunsubscribe) {
- $text .= "\n" . mod_forumng_cron::EMAIL_DIVIDER;
- $text .= get_string("unsubscribe", "forum");
- $text .= ": $CFG->wwwroot/mod/forumng/subscribe.php?" .
- $this->get_forum()->get_link_params(mod_forumng::PARAM_PLAIN) . "\n";
- $html .= "<hr size='1' noshade='noshade' />" .
- "<div class='forumng-email-unsubscribe'>" .
- "<a href='$CFG->wwwroot/mod/forumng/subscribe.php?" .
- $this->get_forum()->get_link_params(mod_forumng::PARAM_HTML) . "'>" .
- get_string('unsubscribe', 'forumng'). '</a></div>';
- }
- if (!$digest && !$discussionemail) {
- $html .= '</body>';
- }
- $USER->lang = $oldlang;
- // If not in HTML mode, chuck away the HTML version
- if (!$ishtml) {
- $html = '';
- }
- }
- // UI
- /////
- /**
- * Displays this post.
- * @param array $html True for HTML format, false for plain text
- * @param array $options See forumngtype::display_post for details
- * @return string HTML or text of post
- */
- function display($html, $options=null) {
- global $USER;
- // Initialise options array
- if (!is_array($options)) {
- $options = array();
- }
- // Default for other options
- if (!array_key_exists(self::OPTION_EMAIL, $options)) {
- $options[self::OPTION_EMAIL] = false;
- }
- if (!array_key_exists(self::OPTION_EXPORT, $options)) {
- $options[self::OPTION_EXPORT] = false;
- }
- if (!array_key_exists(self::OPTION_DIGEST, $options)) {
- $options[self::OPTION_DIGEST] = false;
- }
- if (!array_key_exists(self::OPTION_SINGLE_POST, $options)) {
- $options[self::OPTION_SINGLE_POST] = false;
- }
- if (!array_key_exists(self::OPTION_NO_COMMANDS, $options)) {
- $options[self::OPTION_NO_COMMANDS] = $options[self::OPTION_EXPORT];
- }
- if (!array_key_exists(self::OPTION_COMMAND_REPLY, $options)) {
- $options[self::OPTION_COMMAND_REPLY] =
- !$options[self::OPTION_NO_COMMANDS] && $this->can_reply($junk);
- }
- if (!array_key_exists(self::OPTION_COMMAND_EDIT, $options)) {
- $options[self::OPTION_COMMAND_EDIT] =
- !$options[self::OPTION_NO_COMMANDS] &&
- !$options[self::OPTION_EMAIL] && $this->can_edit($junk);
- }
- if (!array_key_exists(self::OPTION_COMMAND_DELETE, $options)) {
- $options[self::OPTION_COMMAND_DELETE] =
- !$options[self::OPTION_NO_COMMANDS] &&
- !$options[self::OPTION_EMAIL] && $this->can_delete($junk);
- }
- if (!array_key_exists(self::OPTION_COMMAND_REPORT, $options)) {
- $options[self::OPTION_COMMAND_REPORT] =
- !$options[self::OPTION_NO_COMMANDS] &&
- !$options[self::OPTION_EMAIL] && $this->can_alert($junk);
- }
- if (!array_key_exists(self::OPTION_COMMAND_DIRECTLINK, $options)) {
- $options[self::OPTION_COMMAND_DIRECTLINK] =
- !$options[self::OPTION_NO_COMMANDS] && !$options[self::OPTION_EMAIL] &&
- $this->can_showdirectlink();
- }
- if (!array_key_exists(self::OPTION_COMMAND_UNDELETE, $options)) {
- $options[self::OPTION_COMMAND_UNDELETE] =
- !$options[self::OPTION_NO_COMMANDS] &&
- !$options[self::OPTION_EMAIL] && $this->can_undelete($junk);
- }
- if (!array_key_exists(self::OPTION_COMMAND_SPLIT, $options)) {
- $options[self::OPTION_COMMAND_SPLIT] =
- !$options[self::OPTION_NO_COMMANDS] &&
- !$options[self::OPTION_EMAIL] && $this->can_split($junk);
- }
- if (!array_key_exists(self::OPTION_COMMAND_HISTORY, $options)) {
- $options[self::OPTION_COMMAND_HISTORY] =
- !$options[self::OPTION_NO_COMMANDS] &&
- !$options[self::OPTION_EMAIL] && $this->can_view_history($junk);
- }
- if (!array_key_exists(self::OPTION_READ_TIME, $options)) {
- $options[self::OPTION_READ_TIME] = time();
- }
- if (!array_key_exists(self::OPTION_VIEW_FULL_NAMES, $options)) {
- // Default to whether current user has the permission in context
- $options[self::OPTION_VIEW_FULL_NAMES] = has_capability(
- 'moodle/site:viewfullnames', $this->get_forum()->get_context());
- }
- if (!array_key_exists(self::OPTION_TIME_ZONE, $options)) {
- // Default to current user timezone
- $options[self::OPTION_TIME_ZONE] = $USER->timezone;
- }
- if (!array_key_exists(self::OPTION_RATINGS_EDIT, $options)) {
- $options[self::OPTION_RATINGS_EDIT] =
- !$options[self::OPTION_NO_COMMANDS] &&
- !$options[self::OPTION_EMAIL] && $this->can_rate();
- }
- if (!array_key_exists(self::OPTION_EXPANDED, $options)) {
- $options[self::OPTION_EXPANDED] = true;
- }
- if (!array_key_exists(self::OPTION_FLAG_CONTROL, $options)) {
- $options[self::OPTION_FLAG_CONTROL] =
- !$options[self::OPTION_NO_COMMANDS] &&
- !$options[self::OPTION_EMAIL] && $this->can_flag() &&
- $options[self::OPTION_EXPANDED];
- }
- if (!array_key_exists(self::OPTION_VIEW_DELETED_INFO, $options)) {
- $options[self::OPTION_VIEW_DELETED_INFO] =
- $this->can_undelete($junk) && !$options[self::OPTION_EXPORT];
- }
- if (!array_key_exists(self::OPTION_FULL_ADDRESSES, $options)) {
- $options[self::OPTION_FULL_ADDRESSES] =
- $options[self::OPTION_EXPORT] || $options[self::OPTION_EMAIL];
- }
- if (!array_key_exists(self::OPTION_DISCUSSION_SUBJECT, $options)) {
- $options[self::OPTION_DISCUSSION_SUBJECT] = false;
- }
- if (!array_key_exists(self::OPTION_SELECTABLE, $options)) {
- $options[self::OPTION_SELECTABLE] = false;
- }
- if (!array_key_exists(self::OPTION_VISIBLE_POST_NUMBERS, $options)) {
- $options[self::OPTION_VISIBLE_POST_NUMBERS] = false;
- }
- if (!array_key_exists(self::OPTION_USER_IMAGE, $options)) {
- $options[self::OPTION_USER_IMAGE] = true;
- }
- if (!array_key_exists(self::OPTION_PRINTABLE_VERSION, $options)) {
- $options[self::OPTION_PRINTABLE_VERSION] = false;
- }
- if (!array_key_exists(self::OPTION_RATINGS_VIEW, $options)) {
- $options[self::OPTION_RATINGS_VIEW] =
- ((!$options[self::OPTION_NO_COMMANDS] && !$options[self::OPTION_EMAIL]) ||
- $options[self::OPTION_PRINTABLE_VERSION]) &&
- $this->can_view_ratings();
- }
- $dojumps = !$options[self::OPTION_NO_COMMANDS] && !$options[self::OPTION_EMAIL] &&
- !$options[self::OPTION_SINGLE_POST];
- if (!array_key_exists(self::OPTION_JUMP_NEXT, $options)) {
- $options[self::OPTION_JUMP_NEXT] =
- ($dojumps && $this->is_unread() && ($next=$this->get_next_unread()))
- ? $next->get_id() : null;
- }
- if (!array_key_exists(self::OPTION_JUMP_PREVIOUS, $options)) {
- $options[self::OPTION_JUMP_PREVIOUS] =
- ($dojumps && $this->is_unread() && $this->get_previous_unread())
- ? $this->get_previous_unread()->get_id() : null;
- }
- if (!array_key_exists(self::OPTION_JUMP_PARENT, $options)) {
- $options[self::OPTION_JUMP_PARENT] =
- ($dojumps && !$this->is_root_post()) ? $this->get_parent()->get_id() : null;
- }
- if (!array_key_exists(self::OPTION_FIRST_UNREAD, $options)) {
- $options[self::OPTION_FIRST_UNREAD] = !$options[self::OPTION_EMAIL] &&
- !$options[self::OPTION_SINGLE_POST] && $this->is_unread() &&
- !$this->get_previous_unread();
- }
- if (!array_key_exists(self::OPTION_UNREAD_NOT_HIGHLIGHTED, $options)) {
- $options[self::OPTION_UNREAD_NOT_HIGHLIGHTED] = false;
- }
- // Get forum type to do actual display
- $out = mod_forumng_utils::get_renderer();
- return $out->render_post($this, $html, $options);
- }
- function display_with_children($options = null, $recursing = false) {
- global $USER;
- $this->require_children();
- if (!$recursing) {
- // Initialise options array
- if (!is_array($options)) {
- $options = array();
- }
- if (!array_key_exists(self::OPTION_EXPORT, $options)) {
- $options[self::OPTION_EXPORT] = false;
- }
- if (!array_key_exists(self::OPTION_CHILDREN_EXPANDED, $options)) {
- $options[self::OPTION_CHILDREN_EXPANDED] =
- $options[self::OPTION_EXPORT];
- }
- if (!array_key_exists(self::OPTION_CHILDREN_COLLAPSED, $options)) {
- $options[self::OPTION_CHILDREN_COLLAPSED] = false;
- }
- if (!array_key_exists(self::OPTION_INCLUDE_LOCK, $options)) {
- $options[self::OPTION_INCLUDE_LOCK] = false;
- }
- }
- $export = $options[self::OPTION_EXPORT];
- // Decide ID of locked post to hide (if any)
- if ($this->discussion->is_locked() &&
- !$options[self::OPTION_INCLUDE_LOCK]) {
- $lockpostid = $this->discussion->get_last_post_id();
- } else {
- $lockpostid = 0;
- }
- // Display this post. It should be 'short' unless it is unread, parent
- // of unread post, top post, or flagged
- $options[self::OPTION_EXPANDED] = !$recursing ||
- ( !$options[self::OPTION_CHILDREN_COLLAPSED] &&
- ($this->is_unread()
- || $this->is_flagged()
- || $this->has_unread_child() || $this->forceexpand || !$recursing
- || $options[self::OPTION_CHILDREN_EXPANDED]));
- $output = $this->display(true, $options);
- // Are there any children?
- if (count($this->children) > 0 && !($lockpostid
- && count($this->children)==1
- && reset($this->children)->get_id()==$lockpostid)) {
- $output .= $export ? '<blockquote>' : '<div class="forumng-replies">';
- foreach ($this->children as $child) {
- if ($child->get_id()!=$lockpostid) {
- $output .= $child->display_with_children($options, true);
- }
- }
- $output .= $export ? '</blockquote>' : '</div>';
- }
- if (!$recursing) {
- $out = mod_forumng_utils::get_renderer();
- $output = $out->render_post_group($this->get_discussion(), $output);
- }
- return $output;
- }
- /** @return string User picture HTML (for post author) */
- function display_user_picture() {
- $out = mod_forumng_utils::get_renderer();
- return $out->user_picture($this->get_user(),
- array('courseid'=>$this->get_forum()->get_course_id()));
- }
- /**
- * Displays group pictures. This may make a (single) DB query if group
- * data has not yet been retrieved for this discussion.
- * @return string Group pictures HTML (empty string if none) for groups
- * that post author belongs to
- */
- function display_group_pictures() {
- $groups = $this->discussion->get_user_groups($this->get_user()->id);
- if (count($groups) == 0) {
- return '';
- }
- return print_group_picture($groups, $this->get_forum()->get_course_id(),
- false, true);
- }
- /**
- * Displays this draft as an item on the list.
- * @param bool $last True if this is last in list
- * @return string HTML code for the item
- */
- public function display_flagged_list_item($last) {
- return $this->get_forum()->get_type()->display_flagged_list_item(
- $this, $last);
- }
- /**
- * Describes the post fields in JSON format. This is used for the AJAX
- * edit code.
- * @return string JSON structure listing key post fields.
- */
- function prepare_edit_json() {
- global $USER;
- $forum = $this->get_forum();
- $filecontext = $forum->get_context(true);
- $fileoptions = array('subdirs'=>false, 'maxbytes'=>$forum->get_max_bytes());
- // Prepare draft area for attachments
- $draftitemid = 0;
- file_prepare_draft_area($draftitemid, $filecontext->id, 'mod_forumng', 'attachment',
- $this->get_id(), $fileoptions);
- // Prepare draft area for message files
- $messagedraftitemid = 0;
- $message = $this->get_raw_message();
- $message = file_prepare_draft_area($messagedraftitemid, $filecontext->id, 'mod_forumng',
- 'message', $this->get_id(), $fileoptions, $message);
- // Get list of files for main attachment area
- $options = file_get_drafarea_files($draftitemid, '/');
- $usercontext = context_user::instance($USER->id);
- $fs = get_file_storage();
- $files = $fs->get_area_files($usercontext->id, 'user', 'draft',
- $options->itemid, 'id', false);
- $options->filecount = count($files);
- // Get list of files for message area
- $messageoptions = file_get_drafarea_files($messagedraftitemid, '/');
- $files = $fs->get_area_files($usercontext->id, 'user', 'draft',
- $messageoptions->itemid, 'id', false);
- $messageoptions->filecount = count($files);
- // Put everything together with basic data
- $basicvalues = (object)array('subject'=>$this->get_subject(),
- 'message'=>$message, 'format'=>$this->get_format(),
- 'setimportant'=>$this->is_important() ? 1 : 0);
- $basicvalues->options = $options;
- $basicvalues->messageoptions = $messageoptions;
- // Add time limit info
- $timelimit = $this->can_ignore_edit_time_limit()
- ? 0 : $this->get_edit_time_limit();
- if ($timelimit) {
- $basicvalues->editlimit = $timelimit-time();
- $basicvalues->editlimitmsg = get_string('editlimited', 'forumng',
- userdate($timelimit-30, get_string('strftimetime', 'langconfig')));
- } else {
- $basicvalues->editlimit = 0;
- }
- // JSON encoding
- return json_encode($basicvalues);
- }
- /**
- * Prints AJAX version of the post to output, and exits.
- * @param mixed $postorid Post object or ID of post
- * @param int $cloneid If $postorid is an id, a clone id may be necessary
- * to construct the post
- * @param array $options Post options if any
- * @param int $postid ID of post
- */
- public static function print_for_ajax_and_exit($postorid, $cloneid=null,
- $options=array()) {
- if (is_object($postorid)) {
- $post = $postorid;
- } else {
- $post = mod_forumng_post::get_from_id($postorid, $cloneid, true);
- }
- header('Content-Type: text/plain');
- print trim($post->display(true, $options));
- exit;
- }
- // Completion
- /////////////
- public function update_completion($positive) {
- // Do nothing if completion isn't enabled
- if (!$this->get_forum()->is_auto_completion_enabled(true)) {
- return;
- }
- $course = $this->get_forum()->get_course();
- $cm = $this->get_forum()->get_course_module();
- $completion = new completion_info($course);
- $completion->update_state($cm, $positive ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE,
- $this->postfields->userid);
- }
- }