// descending order. The call to default sort order here will use
// that unless the discussion that post is in has a timestart set
// in the future.
- $sort = forum_get_default_sort_order(true, 'p.modified');
+ // This sort will ignore pinned posts as we want the most recent.
+ $sort = forum_get_default_sort_order(true, 'p.modified', 'd', false);
if (! $discussions = forum_get_discussions($cm, $sort, false,
$currentgroup, $this->page->course->newsitems) ) {
$text .= '('.get_string('nonews', 'forum').')';
$discussion = new backup_nested_element('discussion', array('id'), array(
'name', 'firstpost', 'userid', 'groupid',
'assessed', 'timemodified', 'usermodified', 'timestart',
- 'timeend'));
+ 'timeend', 'pinned'));
$posts = new backup_nested_element('posts');
--- /dev/null
+<?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/>.
+/**
+ * The mod_forum discussion pinned event.
+ *
+ * @package mod_forum
+ * @copyright 2014 Charles Fulton <fultonc@lafayette.edu>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_forum\event;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The mod_forum discussion pinned event.
+ *
+ * @package mod_forum
+ * @copyright 2014 Charles Fulton <fultonc@lafayette.edu>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class discussion_pinned extends \core\event\base {
+ /**
+ * Init method.
+ *
+ * @return void
+ */
+ protected function init() {
+ $this->data['crud'] = 'u';
+ $this->data['edulevel'] = self::LEVEL_OTHER;
+ $this->data['objecttable'] = 'forum_discussions';
+ }
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return "The user {$this->userid} has pinned the discussion {$this->objectid} in the forum {$this->other['forumid']}";
+ }
+
+ /**
+ * Return localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventdiscussionpinned', 'mod_forum');
+ }
+
+ /**
+ * Get URL related to the action
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/mod/forum/discuss.php', array('d' => $this->objectid));
+ }
+
+ /**
+ * Return the legacy event log data.
+ *
+ * @return array|null
+ */
+ protected function get_legacy_logdata() {
+ // The legacy log table expects a relative path to /mod/forum/.
+ $logurl = substr($this->get_url()->out_as_local_url(), strlen('/mod/forum/'));
+ return array($this->courseid, 'forum', 'pin discussion', $logurl, $this->objectid, $this->contextinstanceid);
+ }
+
+ /**
+ * Custom validation.
+ *
+ * @throws \coding_exception
+ * @return void
+ */
+ protected function validate_data() {
+ parent::validate_data();
+ if (!isset($this->other['forumid'])) {
+ throw new \coding_exception('forumid must be set in $other.');
+ }
+ if ($this->contextlevel != CONTEXT_MODULE) {
+ throw new \coding_exception('Context passed must be module context.');
+ }
+ if (!isset($this->objectid)) {
+ throw new \coding_exception('objectid must be set to the discussionid.');
+ }
+ }
+
+ /**
+ * Forum discussion object id mappings.
+ *
+ * @return array
+ */
+ public static function get_objectid_mapping() {
+ return array('db' => 'forum_discussions', 'restore' => 'forum_discussion');
+ }
+
+ /**
+ * Forum id mappings.
+ *
+ * @return array
+ */
+ public static function get_other_mapping() {
+ $othermapped = array();
+ $othermapped['forumid'] = array('db' => 'forum', 'restore' => 'forum');
+
+ return $othermapped;
+ }
+}
--- /dev/null
+<?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/>.
+/**
+ * The mod_forum discussion unpinned event.
+ *
+ * @package mod_forum
+ * @copyright 2014 Charles Fulton <fultonc@lafayette.edu>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_forum\event;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The mod_forum discussion unpinned event.
+ *
+ * @package mod_forum
+ * @copyright 2014 Charles Fulton <fultonc@lafayette.edu>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class discussion_unpinned extends \core\event\base {
+ /**
+ * Init method.
+ *
+ * @return void
+ */
+ protected function init() {
+ $this->data['crud'] = 'u';
+ $this->data['edulevel'] = self::LEVEL_OTHER;
+ $this->data['objecttable'] = 'forum_discussions';
+ }
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return "The user {$this->userid} has unpinned the discussion {$this->objectid} in the forum {$this->other['forumid']}";
+ }
+
+ /**
+ * Return localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventdiscussionunpinned', 'mod_forum');
+ }
+
+ /**
+ * Get URL related to the action
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/mod/forum/discuss.php', array('d' => $this->objectid));
+ }
+
+ /**
+ * Return the legacy event log data.
+ *
+ * @return array|null
+ */
+ protected function get_legacy_logdata() {
+ // The legacy log table expects a relative path to /mod/forum/.
+ $logurl = substr($this->get_url()->out_as_local_url(), strlen('/mod/forum/'));
+ return array($this->courseid, 'forum', 'unpin discussion', $logurl, $this->objectid, $this->contextinstanceid);
+ }
+
+ /**
+ * Custom validation.
+ *
+ * @throws \coding_exception
+ * @return void
+ */
+ protected function validate_data() {
+ parent::validate_data();
+ if (!isset($this->other['forumid'])) {
+ throw new \coding_exception('forumid must be set in $other.');
+ }
+ if ($this->contextlevel != CONTEXT_MODULE) {
+ throw new \coding_exception('Context passed must be module context.');
+ }
+ if (!isset($this->objectid)) {
+ throw new \coding_exception('objectid must be set to the discussionid.');
+ }
+ }
+
+ /**
+ * Forum discussion object id mappings.
+ *
+ * @return array
+ */
+ public static function get_objectid_mapping() {
+ return array('db' => 'forum_discussions', 'restore' => 'forum_discussion');
+ }
+
+ /**
+ * Forum id mappings.
+ *
+ * @return array
+ */
+ public static function get_other_mapping() {
+ $othermapped = array();
+ $othermapped['forumid'] = array('db' => 'forum', 'restore' => 'forum');
+
+ return $othermapped;
+ }
+}
$mform->addHelpButton('attachments', 'attachment', 'forum');
}
+ if (!$post->parent && has_capability('mod/forum:pindiscussions', $modcontext)) {
+ $mform->addElement('checkbox', 'pinned', get_string('discussionpinned', 'forum'));
+ $mform->addHelpButton('pinned', 'discussionpinned', 'forum');
+ }
+
if (empty($post->id) && $manageactivities) {
$mform->addElement('checkbox', 'mailnow', get_string('mailnow', 'forum'));
}
)
),
+ 'mod/forum:pindiscussions' => array(
+
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_MODULE,
+ 'archetypes' => array(
+ 'teacher' => CAP_ALLOW,
+ 'editingteacher' => CAP_ALLOW,
+ 'manager' => CAP_ALLOW
+ )
+ ),
+
'mod/forum:editanypost' => array(
'riskbitmask' => RISK_SPAM,
<?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="mod/forum/db" VERSION="20141028" COMMENT="XMLDB file for Moodle mod/forum"
+<XMLDB PATH="mod/forum/db" VERSION="20160113" COMMENT="XMLDB file for Moodle mod/forum"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timestart" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timeend" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+ <FIELD NAME="pinned" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
defined('MOODLE_INTERNAL') || die();
-global $DB; // TODO: this is a hack, we should really do something with the SQL in SQL tables
+global $DB; // TODO: this is a hack, we should really do something with the SQL in SQL tables.
$logs = array(
- array('module'=>'forum', 'action'=>'add', 'mtable'=>'forum', 'field'=>'name'),
- array('module'=>'forum', 'action'=>'update', 'mtable'=>'forum', 'field'=>'name'),
- array('module'=>'forum', 'action'=>'add discussion', 'mtable'=>'forum_discussions', 'field'=>'name'),
- array('module'=>'forum', 'action'=>'add post', 'mtable'=>'forum_posts', 'field'=>'subject'),
- array('module'=>'forum', 'action'=>'update post', 'mtable'=>'forum_posts', 'field'=>'subject'),
- array('module'=>'forum', 'action'=>'user report', 'mtable'=>'user', 'field'=>$DB->sql_concat('firstname', "' '" , 'lastname')),
- array('module'=>'forum', 'action'=>'move discussion', 'mtable'=>'forum_discussions', 'field'=>'name'),
- array('module'=>'forum', 'action'=>'view subscribers', 'mtable'=>'forum', 'field'=>'name'),
- array('module'=>'forum', 'action'=>'view discussion', 'mtable'=>'forum_discussions', 'field'=>'name'),
- array('module'=>'forum', 'action'=>'view forum', 'mtable'=>'forum', 'field'=>'name'),
- array('module'=>'forum', 'action'=>'subscribe', 'mtable'=>'forum', 'field'=>'name'),
- array('module'=>'forum', 'action'=>'unsubscribe', 'mtable'=>'forum', 'field'=>'name'),
+ array('module' => 'forum', 'action' => 'add', 'mtable' => 'forum', 'field' => 'name'),
+ array('module' => 'forum', 'action' => 'update', 'mtable' => 'forum', 'field' => 'name'),
+ array('module' => 'forum', 'action' => 'add discussion', 'mtable' => 'forum_discussions', 'field' => 'name'),
+ array('module' => 'forum', 'action' => 'add post', 'mtable' => 'forum_posts', 'field' => 'subject'),
+ array('module' => 'forum', 'action' => 'update post', 'mtable' => 'forum_posts', 'field' => 'subject'),
+ array('module' => 'forum', 'action' => 'user report', 'mtable' => 'user',
+ 'field' => $DB->sql_concat('firstname', "' '", 'lastname')),
+ array('module' => 'forum', 'action' => 'move discussion', 'mtable' => 'forum_discussions', 'field' => 'name'),
+ array('module' => 'forum', 'action' => 'view subscribers', 'mtable' => 'forum', 'field' => 'name'),
+ array('module' => 'forum', 'action' => 'view discussion', 'mtable' => 'forum_discussions', 'field' => 'name'),
+ array('module' => 'forum', 'action' => 'view forum', 'mtable' => 'forum', 'field' => 'name'),
+ array('module' => 'forum', 'action' => 'subscribe', 'mtable' => 'forum', 'field' => 'name'),
+ array('module' => 'forum', 'action' => 'unsubscribe', 'mtable' => 'forum', 'field' => 'name'),
+ array('module' => 'forum', 'action' => 'pin discussion', 'mtable' => 'forum_discussions', 'field' => 'name'),
+ array('module' => 'forum', 'action' => 'unpin discussion', 'mtable' => 'forum_discussions', 'field' => 'name'),
);
\ No newline at end of file
// Moodle v3.0.0 release upgrade line.
// Put any upgrade step following this.
+ if ($oldversion < 2015120800) {
+
+ // Add support for pinned discussions.
+ $table = new xmldb_table('forum_discussions');
+ $field = new xmldb_field('pinned', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'timeend');
+
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Forum savepoint reached.
+ upgrade_mod_savepoint(true, 2015120800, 'forum');
+ }
return true;
}
$move = optional_param('move', 0, PARAM_INT); // If set, moves this discussion to another forum
$mark = optional_param('mark', '', PARAM_ALPHA); // Used for tracking read posts if user initiated.
$postid = optional_param('postid', 0, PARAM_INT); // Used for tracking read posts if user initiated.
+$pin = optional_param('pin', -1, PARAM_INT); // If set, pin or unpin this discussion.
$url = new moodle_url('/mod/forum/discuss.php', array('d'=>$d));
if ($parent !== 0) {
redirect($return.'&move=-1&sesskey='.sesskey());
}
+// Pin or unpin discussion if requested.
+if ($pin !== -1 && confirm_sesskey()) {
+ require_capability('mod/forum:pindiscussions', $modcontext);
+
+ $params = array('context' => $modcontext, 'objectid' => $discussion->id, 'other' => array('forumid' => $forum->id));
+
+ switch ($pin) {
+ case FORUM_DISCUSSION_PINNED:
+ // Pin the discussion and trigger discussion pinned event.
+ forum_discussion_pin($modcontext, $forum, $discussion);
+ break;
+ case FORUM_DISCUSSION_UNPINNED:
+ // Unpin the discussion and trigger discussion unpinned event.
+ forum_discussion_unpin($modcontext, $forum, $discussion);
+ break;
+ default:
+ echo $OUTPUT->notification("Invalid value when attempting to pin/unpin discussion");
+ break;
+ }
+
+ redirect(new moodle_url('/mod/forum/discuss.php', array('d' => $discussion->id)));
+}
// Trigger discussion viewed event.
forum_discussion_view($modcontext, $forum, $discussion);
echo $neighbourlinks;
/// Print the controls across the top
-echo '<div class="discussioncontrols clearfix">';
+echo '<div class="discussioncontrols clearfix"><div class="controlscontainer">';
if (!empty($CFG->enableportfolios) && has_capability('mod/forum:exportdiscussion', $modcontext)) {
require_once($CFG->libdir.'/portfoliolib.php');
}
echo "</div>";
}
-echo '<div class="clearfloat"> </div>';
-echo "</div>";
+
+if (has_capability('mod/forum:pindiscussions', $modcontext)) {
+ if ($discussion->pinned == FORUM_DISCUSSION_PINNED) {
+ $pinlink = FORUM_DISCUSSION_UNPINNED;
+ $pintext = get_string('discussionunpin', 'forum');
+ } else {
+ $pinlink = FORUM_DISCUSSION_PINNED;
+ $pintext = get_string('discussionpin', 'forum');
+ }
+ $button = new single_button(new moodle_url('discuss.php', array('pin' => $pinlink, 'd' => $discussion->id)), $pintext, 'post');
+ echo html_writer::tag('div', $OUTPUT->render($button), array('class' => 'discussioncontrol pindiscussion'));
+}
+
+
+echo "</div></div>";
if (!empty($forum->blockafter) && !empty($forum->blockperiod)) {
$a = new stdClass();
if ($forum->type == 'qanda' && !has_capability('mod/forum:viewqandawithoutposting', $modcontext) &&
!forum_user_has_posted($forum->id,$discussion->id,$USER->id)) {
- echo $OUTPUT->notification(get_string('qandanotify','forum'));
+ echo $OUTPUT->notification(get_string('qandanotify', 'forum'));
}
if ($move == -1 and confirm_sesskey()) {
// Check they have the view forum capability.
require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
- $sort = 'd.' . $sortby . ' ' . $sortdirection;
+ $sort = 'd.pinned DESC, d.' . $sortby . ' ' . $sortdirection;
$alldiscussions = forum_get_discussions($cm, $sort, true, -1, -1, true, $page, $perpage, FORUM_POSTS_ALL_USER_GROUPS);
if ($alldiscussions) {
'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.'),
'usermodifiedpictureurl' => new external_value(PARAM_URL, 'Post modifier picture.'),
'numreplies' => new external_value(PARAM_TEXT, 'The number of replies in the discussion'),
- 'numunread' => new external_value(PARAM_INT, 'The number of unread discussions.')
+ 'numunread' => new external_value(PARAM_INT, 'The number of unread discussions.'),
+ 'pinned' => new external_value(PARAM_BOOL, 'Is the discussion pinned')
), 'post'
)
),
'name' => new external_value(PARAM_ALPHANUM,
'The allowed keys (value format) are:
discussionsubscribe (bool); subscribe to the discussion?, default to true
+ discussionpinned (bool); is the discussion pinned, default to false
'),
'value' => new external_value(PARAM_RAW, 'The value of the option,
This param is validated in the external function.'
));
// Validate options.
$options = array(
- 'discussionsubscribe' => true
+ 'discussionsubscribe' => true,
+ 'discussionpinned' => false
);
foreach ($params['options'] as $option) {
$name = trim($option['name']);
case 'discussionsubscribe':
$value = clean_param($option['value'], PARAM_BOOL);
break;
+ case 'discussionpinned':
+ $value = clean_param($option['value'], PARAM_BOOL);
+ break;
default:
throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
}
$discussion->name = $discussion->subject;
$discussion->timestart = 0;
$discussion->timeend = 0;
+ if (has_capability('mod/forum:pindiscussions', $context) && $options['discussionpinned']) {
+ $discussion->pinned = FORUM_DISCUSSION_PINNED;
+ } else {
+ $discussion->pinned = FORUM_DISCUSSION_UNPINNED;
+ }
if ($discussionid = forum_add_discussion($discussion)) {
$string['discussionname'] = 'Discussion name';
$string['discussionnownotsubscribed'] = '{$a->name} will NOT be notified of new posts in \'{$a->discussion}\' of \'{$a->forum}\'';
$string['discussionnowsubscribed'] = '{$a->name} will be notified of new posts in \'{$a->discussion}\' of \'{$a->forum}\'';
+$string['discussionpin'] = 'Pin';
+$string['discussionpinned'] = 'Pinned';
+$string['discussionpinned_help'] = 'Pinned discussions will appear at the top of a forum.';
$string['discussionsubscribestop'] = 'I don\'t want to be notified of new posts in this discussion';
$string['discussionsubscribestart'] = 'Send me notifications of new posts in this discussion';
$string['discussionsubscription'] = 'Discussion subscription';
$string['discussionsstartedby'] = 'Discussions started by {$a}';
$string['discussionsstartedbyrecent'] = 'Discussions recently started by {$a}';
$string['discussionsstartedbyuserincourse'] = 'Discussions started by {$a->fullname} in {$a->coursename}';
+$string['discussionunpin'] = 'Unpin';
$string['discussthistopic'] = 'Discuss this topic';
$string['displayend'] = 'Display end';
$string['displayend_help'] = 'This setting specifies whether a forum post should be hidden after a certain date. Note that administrators can always view forum posts.';
$string['eventdiscussionviewed'] = 'Discussion viewed';
$string['eventdiscussionsubscriptioncreated'] = 'Discussion subscription created';
$string['eventdiscussionsubscriptiondeleted'] = 'Discussion subscription deleted';
+$string['eventdiscussionpinned'] = 'Discussion pinned';
+$string['eventdiscussionunpinned'] = 'Discussion unpinned';
$string['eventuserreportviewed'] = 'User report viewed';
$string['eventpostcreated'] = 'Post created';
$string['eventpostdeleted'] = 'Post deleted';
$string['forumintro'] = 'Description';
$string['forum:managesubscriptions'] = 'Manage subscriptions';
$string['forum:movediscussions'] = 'Move discussions';
+$string['forum:pindiscussions'] = 'Pin discussions';
$string['forum:postwithoutthrottling'] = 'Exempt from post threshold';
$string['forumname'] = 'Forum name';
$string['forumposts'] = 'Forum posts';
*/
define('FORUM_POSTS_ALL_USER_GROUPS', -2);
+define('FORUM_DISCUSSION_PINNED', 1);
+define('FORUM_DISCUSSION_UNPINNED', 0);
+
/// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
/**
}
$allnames = get_all_user_name_fields(true, 'u');
- $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend, $allnames,
+ $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend, d.pinned, $allnames,
u.email, u.picture, u.imagealt $umfields
FROM {forum_discussions} d
JOIN {forum_posts} p ON p.discussion = d.id
$umtable
WHERE d.forum = ? AND p.parent = 0
$timelimit $groupselect
- ORDER BY $forumsort";
+ ORDER BY $forumsort, d.id DESC";
return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
}
/**
* Gets the neighbours (previous and next) of a discussion.
*
- * The calculation is based on the timemodified of the discussion and does not handle
- * the neighbours having an identical timemodified. The reason is that we do not have any
- * other mean to sort the records, e.g. we cannot use IDs as a greater ID can have a lower
- * timemodified.
+ * The calculation is based on the timemodified when time modified or time created is identical
+ * It will revert to using the ID to sort consistently. This is better tha skipping a discussion.
*
* For blog-style forums, the calculation is based on the original creation time of the
* blog post.
}
}
- if ($forum->type === 'blog') {
- $params['forumid'] = $cm->instance;
- $params['discid1'] = $discussion->id;
- $params['discid2'] = $discussion->id;
-
- $sql = "SELECT d.id, d.name, d.timemodified, d.groupid, d.timestart, d.timeend
- FROM {forum_discussions} d
- JOIN {forum_posts} p ON d.firstpost = p.id
- WHERE d.forum = :forumid
- AND d.id <> :discid1
- $timelimit
- $groupselect";
+ $params['forumid'] = $cm->instance;
+ $params['discid1'] = $discussion->id;
+ $params['discid2'] = $discussion->id;
+ $params['discid3'] = $discussion->id;
+ $params['discid4'] = $discussion->id;
+ $params['disctimecompare1'] = $discussion->timemodified;
+ $params['disctimecompare2'] = $discussion->timemodified;
+ $params['pinnedstate1'] = (int) $discussion->pinned;
+ $params['pinnedstate2'] = (int) $discussion->pinned;
+ $params['pinnedstate3'] = (int) $discussion->pinned;
+ $params['pinnedstate4'] = (int) $discussion->pinned;
- $sub = "SELECT pp.created
- FROM {forum_discussions} dd
- JOIN {forum_posts} pp ON dd.firstpost = pp.id
- WHERE dd.id = :discid2";
-
- $prevsql = $sql . " AND p.created < ($sub)
- ORDER BY p.created DESC";
+ $sql = "SELECT d.id, d.name, d.timemodified, d.groupid, d.timestart, d.timeend
+ FROM {forum_discussions} d
+ JOIN {forum_posts} p ON d.firstpost = p.id
+ WHERE d.forum = :forumid
+ AND d.id <> :discid1
+ $timelimit
+ $groupselect";
+ $comparefield = "d.timemodified";
+ $comparevalue = ":disctimecompare1";
+ $comparevalue2 = ":disctimecompare2";
+ if (!empty($CFG->forum_enabletimedposts)) {
+ // Here we need to take into account the release time (timestart)
+ // if one is set, of the neighbouring posts and compare it to the
+ // timestart or timemodified of *this* post depending on if the
+ // release date of this post is in the future or not.
+ // This stops discussions that appear later because of the
+ // timestart value from being buried under discussions that were
+ // made afterwards.
+ $comparefield = "CASE WHEN d.timemodified < d.timestart
+ THEN d.timestart ELSE d.timemodified END";
+ if ($discussion->timemodified < $discussion->timestart) {
+ // Normally we would just use the timemodified for sorting
+ // discussion posts. However, when timed discussions are enabled,
+ // then posts need to be sorted base on the later of timemodified
+ // or the release date of the post (timestart).
+ $params['disctimecompare1'] = $discussion->timestart;
+ $params['disctimecompare2'] = $discussion->timestart;
+ }
+ }
+ $orderbydesc = forum_get_default_sort_order(true, $comparefield, 'd', false);
+ $orderbyasc = forum_get_default_sort_order(false, $comparefield, 'd', false);
- $nextsql = $sql . " AND p.created > ($sub)
- ORDER BY p.created ASC";
+ if ($forum->type === 'blog') {
+ $subselect = "SELECT pp.created
+ FROM {forum_discussions} dd
+ JOIN {forum_posts} pp ON dd.firstpost = pp.id ";
- $neighbours['prev'] = $DB->get_record_sql($prevsql, $params, IGNORE_MULTIPLE);
- $neighbours['next'] = $DB->get_record_sql($nextsql, $params, IGNORE_MULTIPLE);
+ $subselectwhere1 = " WHERE dd.id = :discid3";
+ $subselectwhere2 = " WHERE dd.id = :discid4";
- } else {
- $params['forumid'] = $cm->instance;
- $params['discid'] = $discussion->id;
- $params['disctimemodified'] = $discussion->timemodified;
+ $comparefield = "p.created";
- $sql = "SELECT d.id, d.name, d.timemodified, d.groupid, d.timestart, d.timeend
- FROM {forum_discussions} d
- WHERE d.forum = :forumid
- AND d.id <> :discid
- $timelimit
- $groupselect";
+ $sub1 = $subselect.$subselectwhere1;
+ $comparevalue = "($sub1)";
- if (empty($CFG->forum_enabletimedposts)) {
- $prevsql = $sql . " AND d.timemodified < :disctimemodified";
- $nextsql = $sql . " AND d.timemodified > :disctimemodified";
+ $sub2 = $subselect.$subselectwhere2;
+ $comparevalue2 = "($sub2)";
- } else {
- // Normally we would just use the timemodified for sorting
- // discussion posts. However, when timed discussions are enabled,
- // then posts need to be sorted base on the later of timemodified
- // or the release date of the post (timestart).
- $params['disctimecompare'] = $discussion->timemodified;
- if ($discussion->timemodified < $discussion->timestart) {
- $params['disctimecompare'] = $discussion->timestart;
- }
+ $orderbydesc = "d.pinned, p.created DESC";
+ $orderbyasc = "d.pinned, p.created ASC";
+ }
- // Here we need to take into account the release time (timestart)
- // if one is set, of the neighbouring posts and compare it to the
- // timestart or timemodified of *this* post depending on if the
- // release date of this post is in the future or not.
- // This stops discussions that appear later because of the
- // timestart value from being buried under discussions that were
- // made afterwards.
- $prevsql = $sql . " AND CASE WHEN d.timemodified < d.timestart
- THEN d.timestart ELSE d.timemodified END < :disctimecompare";
- $nextsql = $sql . " AND CASE WHEN d.timemodified < d.timestart
- THEN d.timestart ELSE d.timemodified END > :disctimecompare";
- }
- $prevsql .= ' ORDER BY '.forum_get_default_sort_order();
- $nextsql .= ' ORDER BY '.forum_get_default_sort_order(false);
+ $prevsql = $sql . " AND ( (($comparefield < $comparevalue) AND :pinnedstate1 = d.pinned)
+ OR ($comparefield = $comparevalue2 AND (d.pinned = 0 OR d.pinned = :pinnedstate4) AND d.id < :discid2)
+ OR (d.pinned = 0 AND d.pinned <> :pinnedstate2))
+ ORDER BY CASE WHEN d.pinned = :pinnedstate3 THEN 1 ELSE 0 END DESC, $orderbydesc, d.id DESC";
- $neighbours['prev'] = $DB->get_record_sql($prevsql, $params, IGNORE_MULTIPLE);
- $neighbours['next'] = $DB->get_record_sql($nextsql, $params, IGNORE_MULTIPLE);
- }
+ $nextsql = $sql . " AND ( (($comparefield > $comparevalue) AND :pinnedstate1 = d.pinned)
+ OR ($comparefield = $comparevalue2 AND (d.pinned = 1 OR d.pinned = :pinnedstate4) AND d.id > :discid2)
+ OR (d.pinned = 1 AND d.pinned <> :pinnedstate2))
+ ORDER BY CASE WHEN d.pinned = :pinnedstate3 THEN 1 ELSE 0 END DESC, $orderbyasc, d.id ASC";
+ $neighbours['prev'] = $DB->get_record_sql($prevsql, $params, IGNORE_MULTIPLE);
+ $neighbours['next'] = $DB->get_record_sql($nextsql, $params, IGNORE_MULTIPLE);
return $neighbours;
}
* @param bool $desc True for DESC, False for ASC.
* @param string $compare The field in the SQL to compare to normally sort by.
* @param string $prefix The prefix being used for the discussion table.
+ * @param bool $pinned sort pinned posts to the top
* @return string
*/
-function forum_get_default_sort_order($desc = true, $compare = 'd.timemodified', $prefix = 'd') {
+function forum_get_default_sort_order($desc = true, $compare = 'd.timemodified', $prefix = 'd', $pinned = true) {
global $CFG;
if (!empty($prefix)) {
$dir = $desc ? 'DESC' : 'ASC';
+ if ($pinned == true) {
+ $pinned = "{$prefix}pinned DESC,";
+ } else {
+ $pinned = '';
+ }
+
$sort = "{$prefix}timemodified";
if (!empty($CFG->forum_enabletimedposts)) {
$sort = "CASE WHEN {$compare} < {$prefix}timestart
ELSE {$compare}
END";
}
- return "$sort $dir";
+ return "$pinned $sort $dir";
}
/**
echo "\n\n";
echo '<tr class="discussion r'.$rowcount.$timedoutsidewindow.'">';
- // Topic
- echo '<td class="topic starter">';
-
+ $topicclass = 'topic starter';
+ if (FORUM_DISCUSSION_PINNED == $post->pinned) {
+ $topicclass .= ' pinned';
+ }
+ echo '<td class="'.$topicclass.'">';
+ if (FORUM_DISCUSSION_PINNED == $post->pinned) {
+ echo $OUTPUT->pix_icon('i/pinned', get_string('discussionpinned', 'forum'), 'mod_forum');
+ }
$canalwaysseetimedpost = $USER->id == $post->userid || $canviewhiddentimedposts;
if ($timeddiscussion && $canalwaysseetimedpost) {
echo $PAGE->get_renderer('mod_forum')->timed_discussion_tooltip($post, empty($timedoutsidewindow));
$discussion->name = $post->subject;
$discussion->timestart = $post->timestart;
$discussion->timeend = $post->timeend;
+
+ if (isset($post->pinned)) {
+ $discussion->pinned = $post->pinned;
+ }
}
$post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id,
mod_forum_post_form::editor_options($context, $post->id), $post->message);
function forum_add_discussion($discussion, $mform=null, $unused=null, $userid=null) {
global $USER, $CFG, $DB;
- $timenow = time();
+ $timenow = isset($discussion->timenow) ? $discussion->timenow : time();
if (is_null($userid)) {
$userid = $USER->id;
} else {
$ownpost=false;
}
- // Use discussion name instead of subject of first post
+ // Use discussion name instead of subject of first post.
$discussion->subject = $discussion->name;
switch ($displayformat) {
$event->trigger();
}
+/**
+ * Set the discussion to pinned and trigger the discussion pinned event
+ *
+ * @param stdClass $modcontext module context object
+ * @param stdClass $forum forum object
+ * @param stdClass $discussion discussion object
+ * @since Moodle 3.1
+ */
+function forum_discussion_pin($modcontext, $forum, $discussion) {
+ global $DB;
+
+ $DB->set_field('forum_discussions', 'pinned', FORUM_DISCUSSION_PINNED, array('id' => $discussion->id));
+
+ $params = array(
+ 'context' => $modcontext,
+ 'objectid' => $discussion->id,
+ 'other' => array('forumid' => $forum->id)
+ );
+
+ $event = \mod_forum\event\discussion_pinned::create($params);
+ $event->add_record_snapshot('forum_discussions', $discussion);
+ $event->trigger();
+}
+
+/**
+ * Set discussion to unpinned and trigger the discussion unpin event
+ *
+ * @param stdClass $modcontext module context object
+ * @param stdClass $forum forum object
+ * @param stdClass $discussion discussion object
+ * @since Moodle 3.1
+ */
+function forum_discussion_unpin($modcontext, $forum, $discussion) {
+ global $DB;
+
+ $DB->set_field('forum_discussions', 'pinned', FORUM_DISCUSSION_UNPINNED, array('id' => $discussion->id));
+
+ $params = array(
+ 'context' => $modcontext,
+ 'objectid' => $discussion->id,
+ 'other' => array('forumid' => $forum->id)
+ );
+
+ $event = \mod_forum\event\discussion_unpinned::create($params);
+ $event->add_record_snapshot('forum_discussions', $discussion);
+ $event->trigger();
+}
+
/**
* Add nodes to myprofile page.
*
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\r
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"\r
+ viewBox="-0.8 0 16 16" enable-background="new -0.8 0 16 16" xml:space="preserve" width="16px" height="16px">\r
+<path fill="#999999" d="M6.3,7.1V2.8c0-0.1,0-0.2-0.1-0.2C6.2,2.5,6.1,2.5,6,2.5c-0.1,0-0.2,0-0.2,0.1C5.7,2.6,5.7,2.7,5.7,2.8v4.3\r
+ c0,0.1,0,0.2,0.1,0.2S5.9,7.4,6,7.4c0.1,0,0.2,0,0.2-0.1S6.3,7.2,6.3,7.1L6.3,7.1z M12.8,10.5c0,0.2-0.1,0.3-0.2,0.4\r
+ c-0.1,0.1-0.3,0.2-0.4,0.2H8l-0.5,4.6c0,0.1,0,0.1-0.1,0.2C7.4,16,7.3,16,7.2,16h0c-0.2,0-0.3-0.1-0.3-0.3l-0.7-4.7H2.3\r
+ c-0.2,0-0.3-0.1-0.4-0.2c-0.1-0.1-0.2-0.3-0.2-0.4c0-0.8,0.3-1.5,0.8-2.1c0.5-0.6,1.1-0.9,1.7-0.9V2.5c-0.3,0-0.6-0.1-0.9-0.4\r
+ S2.9,1.6,2.9,1.2S3,0.6,3.3,0.4S3.8,0,4.1,0h6.2c0.3,0,0.6,0.1,0.9,0.4s0.4,0.5,0.4,0.9s-0.1,0.6-0.4,0.9s-0.5,0.4-0.9,0.4v4.9\r
+ c0.6,0,1.2,0.3,1.7,0.9C12.5,9,12.8,9.7,12.8,10.5L12.8,10.5z"/>\r
+</svg>\r
'timeend'=>$discussion->timeend):
array())+
+ (isset($discussion->pinned) ? array(
+ 'pinned' => $discussion->pinned) :
+ array()) +
+
(isset($post->groupid)?array(
'groupid'=>$post->groupid):
array())+
$DB->set_field('forum_discussions' ,'groupid' , $fromform->groupinfo, array('firstpost' => $fromform->id));
}
-
+ // When editing first post/discussion.
+ if (!$fromform->parent) {
+ if (has_capability('mod/forum:pindiscussions', $modcontext)) {
+ // Can change pinned if we have capability.
+ $fromform->pinned = !empty($fromform->pinned) ? FORUM_DISCUSSION_PINNED : FORUM_DISCUSSION_UNPINNED;
+ } else {
+ // We don't have the capability to change so keep to previous value.
+ unset($fromform->pinned);
+ }
+ }
$updatepost = $fromform; //realpost
$updatepost->forum = $forum->id;
if (!forum_update_post($updatepost, $mform_post, $message)) {
$discussion->timestart = $fromform->timestart;
$discussion->timeend = $fromform->timeend;
+ if (has_capability('mod/forum:pindiscussions', $modcontext) && !empty($fromform->pinned)) {
+ $discussion->pinned = FORUM_DISCUSSION_PINNED;
+ } else {
+ $discussion->pinned = FORUM_DISCUSSION_UNPINNED;
+ }
+
$allowedgroups = array();
$groupstopostto = array();
$mform_post->display();
echo $OUTPUT->footer();
-
.path-mod-forum .forumheaderlist th.header.replies .iconsmall { margin: 0 .3em;}
.path-mod-forum .forumheaderlist .picture {width: 35px;}
.path-mod-forum .forumheaderlist .discussion .starter {vertical-align: middle;}
+.path-mod-forum .forumheaderlist .discussion .pinned img {padding: 5px;}
.path-mod-forum .forumheaderlist .discussion .lastpost {white-space: nowrap;text-align: right;}
.path-mod-forum .forumheaderlist .replies,
.path-mod-forum .forumheaderlist .discussion .author {white-space: nowrap;}
/** Styles for discuss.php **/
#page-mod-forum-discuss .discussioncontrols {width:100%;margin:5px;}
-#page-mod-forum-discuss .discussioncontrols .discussioncontrol {width:33%;float:left;}
+#page-mod-forum-discuss .discussioncontrols .controlscontainer {width:100%;float:right;}
+#page-mod-forum-discuss .discussioncontrols .discussioncontrol {float:left;}
#page-mod-forum-discuss .discussioncontrol.exporttoportfolio {text-align:left;}
-#page-mod-forum-discuss .discussioncontrol.displaymode {text-align:center;}
-#page-mod-forum-discuss .discussioncontrol.movediscussion {float:right;width:auto;text-align:right;padding-right:10px;}
+#page-mod-forum-discuss .discussioncontrol.displaymode {padding-right:10px;}
+#page-mod-forum-discuss .discussioncontrol.movediscussion {padding-right:10px;}
#page-mod-forum-discuss .discussioncontrol.movediscussion .movediscussionoption {}
/** Styles for view.php **/
And I add a new discussion to "Test forum name" forum with:
| Subject | Discussion 1 |
| Message | Test post message |
- And I wait "1" seconds
And I add a new discussion to "Test forum name" forum with:
| Subject | Discussion 2 |
| Message | Test post message |
- And I wait "1" seconds
And I add a new discussion to "Test forum name" forum with:
| Subject | Discussion 3 |
| Message | Test post message |
And I follow "Discussion 1"
And I should see "Discussion 2"
And I should not see "Discussion 3"
- And I wait "1" seconds
And I follow "Reply"
And I set the following fields to these values:
| Message | Answer to discussion |
And I add a new discussion to "Test forum name" forum with:
| Subject | Discussion 1 Group 0 |
| Message | Test post message |
- And I wait "1" seconds
And I add a new discussion to "Test forum name" forum with:
| Subject | Discussion 2 Group 0 |
| Message | Test post message |
- And I wait "1" seconds
And I add a new discussion to "Test forum name" forum with:
| Subject | Discussion 1 Group 1 |
| Message | Test post message |
| Group | Group 1 |
- And I wait "1" seconds
And I add a new discussion to "Test forum name" forum with:
| Subject | Discussion 2 Group 1 |
| Message | Test post message |
| Group | Group 1 |
- And I wait "1" seconds
And I add a new discussion to "Test forum name" forum with:
| Subject | Discussion 1 Group 2 |
| Message | Test post message |
| Group | Group 2 |
- And I wait "1" seconds
And I add a new discussion to "Test forum name" forum with:
| Subject | Discussion 2 Group 2 |
| Message | Test post message |
And I add a new discussion to "Test forum name" forum with:
| Subject | Discussion 1 Group 0 |
| Message | Test post message |
- And I wait "1" seconds
And I add a new discussion to "Test forum name" forum with:
| Subject | Discussion 2 Group 0 |
| Message | Test post message |
- And I wait "1" seconds
And I add a new discussion to "Test forum name" forum with:
| Subject | Discussion 1 Group 1 |
| Message | Test post message |
| Group | Group 1 |
- And I wait "1" seconds
And I add a new discussion to "Test forum name" forum with:
| Subject | Discussion 2 Group 1 |
| Message | Test post message |
| Group | Group 1 |
- And I wait "1" seconds
And I add a new discussion to "Test forum name" forum with:
| Subject | Discussion 1 Group 2 |
| Message | Test post message |
| Group | Group 2 |
- And I wait "1" seconds
And I add a new discussion to "Test forum name" forum with:
| Subject | Discussion 2 Group 2 |
| Message | Test post message |
And I add a new discussion to "Test forum 1" forum with:
| Subject | Discussion 1 |
| Message | Test post message |
- And I wait "1" seconds
And I log out
And I log in as "teacher1"
And I follow "Course 1"
When I add a new topic to "Course blog forum" forum with:
| Subject | Blog post 1 |
| Message | This is the first post |
- And I wait "1" seconds
And I add a new topic to "Course blog forum" forum with:
| Subject | Blog post 2 |
| Message | This is the second post |
- And I wait "1" seconds
And I add a new topic to "Course blog forum" forum with:
| Subject | Blog post 3 |
| Message | This is the third post |
When I add a new discussion to "Course general forum" forum with:
| Subject | Forum post 1 |
| Message | This is the first post |
- And I wait "1" seconds
And I add a new discussion to "Course general forum" forum with:
| Subject | Forum post 2 |
| Message | This is the second post |
- And I wait "1" seconds
And I add a new discussion to "Course general forum" forum with:
| Subject | Forum post 3 |
| Message | This is the third post |
'userpictureurl' => '',
'usermodifiedpictureurl' => '',
'numreplies' => 3,
- 'numunread' => 0
+ 'numunread' => 0,
+ 'pinned' => FORUM_DISCUSSION_UNPINNED
);
// Call the external function passing forum id.
$this->assertEquals('the subject', $discussions['discussions'][0]['subject']);
$this->assertEquals('some text here...', $discussions['discussions'][0]['message']);
+ $discussion2pinned = mod_forum_external::add_discussion($forum->id, 'the pinned subject', 'some 2 text here...', -1,
+ array('options' => array('name' => 'discussionpinned',
+ 'value' => true)));
+ $discussion3 = mod_forum_external::add_discussion($forum->id, 'the non pinnedsubject', 'some 3 text here...');
+ $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
+ $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
+ $this->assertCount(3, $discussions['discussions']);
+ $this->assertEquals($discussion2pinned['discussionid'], $discussions['discussions'][0]['discussion']);
}
/**
$timemodified = $record['timemodified'];
}
+ if (!isset($record['pinned'])) {
+ $record['pinned'] = FORUM_DISCUSSION_UNPINNED;
+ }
+
$record = (object) $record;
// Add the discussion.
$record['course'] = $course->id;
$record['forum'] = $forum->id;
$record['userid'] = $user->id;
+ $record['pinned'] = FORUM_DISCUSSION_PINNED; // Pin one discussion.
self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
+ $record['pinned'] = FORUM_DISCUSSION_UNPINNED; // No pin for others.
self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
global $CFG, $DB;
$this->resetAfterTest();
+ $timenow = time();
+ $timenext = $timenow;
+
// Setup test data.
$forumgen = $this->getDataGenerator()->get_plugin_generator('mod_forum');
$course = $this->getDataGenerator()->create_course();
$CFG->forum_enabletimedposts = false;
$this->setAdminUser();
- // Two discussions with identical timemodified ignore each other.
- $record->timemodified++;
+ // Two discussions with identical timemodified will sort by id.
+ $record->timemodified += 25;
$DB->update_record('forum_discussions', (object) array('id' => $disc3->id, 'timemodified' => $record->timemodified));
$DB->update_record('forum_discussions', (object) array('id' => $disc2->id, 'timemodified' => $record->timemodified));
+ $DB->update_record('forum_discussions', (object) array('id' => $disc12->id, 'timemodified' => $record->timemodified - 5));
$disc2 = $DB->get_record('forum_discussions', array('id' => $disc2->id));
$disc3 = $DB->get_record('forum_discussions', array('id' => $disc3->id));
+ $neighbours = forum_get_discussion_neighbours($cm, $disc3, $forum);
+ $this->assertEquals($disc2->id, $neighbours['prev']->id);
+ $this->assertEmpty($neighbours['next']);
+
$neighbours = forum_get_discussion_neighbours($cm, $disc2, $forum);
- $this->assertEquals($disc13->id, $neighbours['prev']->id);
+ $this->assertEquals($disc12->id, $neighbours['prev']->id);
+ $this->assertEquals($disc3->id, $neighbours['next']->id);
+
+ // Set timemodified to not be identical.
+ $DB->update_record('forum_discussions', (object) array('id' => $disc2->id, 'timemodified' => $record->timemodified - 1));
+
+ // Test pinned posts behave correctly.
+ $disc8->pinned = FORUM_DISCUSSION_PINNED;
+ $DB->update_record('forum_discussions', (object) array('id' => $disc8->id, 'pinned' => $disc8->pinned));
+ $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum);
+ $this->assertEquals($disc3->id, $neighbours['prev']->id);
$this->assertEmpty($neighbours['next']);
$neighbours = forum_get_discussion_neighbours($cm, $disc3, $forum);
- $this->assertEquals($disc13->id, $neighbours['prev']->id);
+ $this->assertEquals($disc2->id, $neighbours['prev']->id);
+ $this->assertEquals($disc8->id, $neighbours['next']->id);
+
+ // Test 3 pinned posts.
+ $disc6->pinned = FORUM_DISCUSSION_PINNED;
+ $DB->update_record('forum_discussions', (object) array('id' => $disc6->id, 'pinned' => $disc6->pinned));
+ $disc4->pinned = FORUM_DISCUSSION_PINNED;
+ $DB->update_record('forum_discussions', (object) array('id' => $disc4->id, 'pinned' => $disc4->pinned));
+
+ $neighbours = forum_get_discussion_neighbours($cm, $disc6, $forum);
+ $this->assertEquals($disc4->id, $neighbours['prev']->id);
+ $this->assertEquals($disc8->id, $neighbours['next']->id);
+
+ $neighbours = forum_get_discussion_neighbours($cm, $disc4, $forum);
+ $this->assertEquals($disc3->id, $neighbours['prev']->id);
+ $this->assertEquals($disc6->id, $neighbours['next']->id);
+
+ $neighbours = forum_get_discussion_neighbours($cm, $disc8, $forum);
+ $this->assertEquals($disc6->id, $neighbours['prev']->id);
$this->assertEmpty($neighbours['next']);
}
global $CFG, $DB;
$this->resetAfterTest();
+ $timenow = time();
+ $timenext = $timenow;
+
// Setup test data.
$forumgen = $this->getDataGenerator()->get_plugin_generator('mod_forum');
$course = $this->getDataGenerator()->create_course();
$CFG->forum_enabletimedposts = false;
$this->setAdminUser();
- // Two blog posts with identical creation time ignore each other.
$record->timemodified++;
+ // Two blog posts with identical creation time will sort by id.
$DB->update_record('forum_posts', (object) array('id' => $disc2->firstpost, 'created' => $record->timemodified));
$DB->update_record('forum_posts', (object) array('id' => $disc3->firstpost, 'created' => $record->timemodified));
$neighbours = forum_get_discussion_neighbours($cm, $disc2, $forum);
$this->assertEquals($disc12->id, $neighbours['prev']->id);
- $this->assertEmpty($neighbours['next']);
+ $this->assertEquals($disc3->id, $neighbours['next']->id);
$neighbours = forum_get_discussion_neighbours($cm, $disc3, $forum);
- $this->assertEquals($disc12->id, $neighbours['prev']->id);
+ $this->assertEquals($disc2->id, $neighbours['prev']->id);
$this->assertEmpty($neighbours['next']);
}
public function test_forum_get_neighbours_with_groups() {
$this->resetAfterTest();
+ $timenow = time();
+ $timenext = $timenow;
+
// Setup test data.
$forumgen = $this->getDataGenerator()->get_plugin_generator('mod_forum');
$course = $this->getDataGenerator()->create_course();
public function test_forum_get_neighbours_with_groups_blog() {
$this->resetAfterTest();
+ $timenow = time();
+ $timenext = $timenow;
+
// Setup test data.
$forumgen = $this->getDataGenerator()->get_plugin_generator('mod_forum');
$course = $this->getDataGenerator()->create_course();
$record->groupid = $group1->id;
$record->timemodified = time();
$disc11 = $forumgen->create_discussion($record);
+ $record->timenow = $timenext++;
$record->forum = $forum2->id;
$record->timemodified++;
$disc21 = $forumgen->create_discussion($record);
$this->assertEquals($discussionid, $row->discussion);
}
}
-
+ public function test_discussion_pinned_sort() {
+ list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5);
+ $cm = get_coursemodule_from_instance('forum', $forum->id);
+ $discussions = forum_get_discussions($cm);
+ // First discussion should be pinned.
+ $first = reset($discussions);
+ $this->assertEquals(1, $first->pinned, "First discussion should be pinned discussion");
+ }
public function test_forum_view() {
global $CFG;
// Create 10 discussions with replies.
$discussionids = array();
for ($i = 0; $i < $discussioncount; $i++) {
- $discussion = $this->create_single_discussion_with_replies($forum, $user, $replycount);
+ // Pin 3rd discussion.
+ if ($i == 3) {
+ $discussion = $this->create_single_discussion_pinned_with_replies($forum, $user, $replycount);
+ } else {
+ $discussion = $this->create_single_discussion_with_replies($forum, $user, $replycount);
+ }
+
$discussionids[] = $discussion->id;
}
return array($forum, $discussionids);
return $discussion;
}
+ /**
+ * Create a discussion with a number of replies.
+ *
+ * @param object $forum The forum which has been created
+ * @param object $user The user making the discussion and replies
+ * @param int $replycount The number of replies
+ * @return object $discussion
+ */
+ protected function create_single_discussion_pinned_with_replies($forum, $user, $replycount) {
+ global $DB;
+
+ $generator = self::getDataGenerator()->get_plugin_generator('mod_forum');
+
+ $record = new stdClass();
+ $record->course = $forum->course;
+ $record->forum = $forum->id;
+ $record->userid = $user->id;
+ $record->pinned = FORUM_DISCUSSION_PINNED;
+ $discussion = $generator->create_discussion($record);
+
+ // Retrieve the first post.
+ $replyto = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
+
+ // Create the replies.
+ $post = new stdClass();
+ $post->userid = $user->id;
+ $post->discussion = $discussion->id;
+ $post->parent = $replyto->id;
+
+ for ($i = 0; $i < $replycount; $i++) {
+ $generator->create_post($post);
+ }
+
+ return $discussion;
+ }
/**
* Tests for mod_forum_rating_can_see_item_ratings().
),
);
}
+
+ /**
+ * Test test_pinned_discussion_with_group.
+ */
+ public function test_pinned_discussion_with_group() {
+ global $SESSION;
+
+ $this->resetAfterTest();
+ $course1 = $this->getDataGenerator()->create_course();
+ $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
+
+ // Create an author user.
+ $author = $this->getDataGenerator()->create_user();
+ $this->getDataGenerator()->enrol_user($author->id, $course1->id);
+
+ // Create two viewer users - one in a group, one not.
+ $viewer1 = $this->getDataGenerator()->create_user((object) array('trackforums' => 1));
+ $this->getDataGenerator()->enrol_user($viewer1->id, $course1->id);
+
+ $viewer2 = $this->getDataGenerator()->create_user((object) array('trackforums' => 1));
+ $this->getDataGenerator()->enrol_user($viewer2->id, $course1->id);
+ $this->getDataGenerator()->create_group_member(array('userid' => $viewer2->id, 'groupid' => $group1->id));
+
+ $forum1 = $this->getDataGenerator()->create_module('forum', (object) array(
+ 'course' => $course1->id,
+ 'groupmode' => SEPARATEGROUPS,
+ ));
+
+ $coursemodule = get_coursemodule_from_instance('forum', $forum1->id);
+
+ $alldiscussions = array();
+ $group1discussions = array();
+
+ // Create 4 discussions in all participants group and group1, where the first
+ // discussion is pinned in each group.
+ $allrecord = new stdClass();
+ $allrecord->course = $course1->id;
+ $allrecord->userid = $author->id;
+ $allrecord->forum = $forum1->id;
+ $allrecord->pinned = FORUM_DISCUSSION_PINNED;
+
+ $group1record = new stdClass();
+ $group1record->course = $course1->id;
+ $group1record->userid = $author->id;
+ $group1record->forum = $forum1->id;
+ $group1record->groupid = $group1->id;
+ $group1record->pinned = FORUM_DISCUSSION_PINNED;
+
+ $alldiscussions[] = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($allrecord);
+ $group1discussions[] = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($group1record);
+
+ // Create unpinned discussions.
+ $allrecord->pinned = FORUM_DISCUSSION_UNPINNED;
+ $group1record->pinned = FORUM_DISCUSSION_UNPINNED;
+ for ($i = 0; $i < 3; $i++) {
+ $alldiscussions[] = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($allrecord);
+ $group1discussions[] = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($group1record);
+ }
+
+ // As viewer1 (no group). This user shouldn't see any of group1's discussions
+ // so their expected discussion order is (where rightmost is highest priority):
+ // Ad1, ad2, ad3, ad0.
+ $this->setUser($viewer1->id);
+
+ // CHECK 1.
+ // Take the neighbours of ad3, which should be prev: ad2 and next: ad0.
+ $neighbours = forum_get_discussion_neighbours($coursemodule, $alldiscussions[3], $forum1);
+ // Ad2 check.
+ $this->assertEquals($alldiscussions[2]->id, $neighbours['prev']->id);
+ // Ad0 check.
+ $this->assertEquals($alldiscussions[0]->id, $neighbours['next']->id);
+
+ // CHECK 2.
+ // Take the neighbours of ad0, which should be prev: ad3 and next: null.
+ $neighbours = forum_get_discussion_neighbours($coursemodule, $alldiscussions[0], $forum1);
+ // Ad3 check.
+ $this->assertEquals($alldiscussions[3]->id, $neighbours['prev']->id);
+ // Null check.
+ $this->assertEmpty($neighbours['next']);
+
+ // CHECK 3.
+ // Take the neighbours of ad1, which should be prev: null and next: ad2.
+ $neighbours = forum_get_discussion_neighbours($coursemodule, $alldiscussions[1], $forum1);
+ // Null check.
+ $this->assertEmpty($neighbours['prev']);
+ // Ad2 check.
+ $this->assertEquals($alldiscussions[2]->id, $neighbours['next']->id);
+
+ // Temporary hack to workaround for MDL-52656.
+ $SESSION->currentgroup = null;
+
+ // As viewer2 (group1). This user should see all of group1's posts and the all participants group.
+ // The expected discussion order is (rightmost is highest priority):
+ // Ad1, gd1, ad2, gd2, ad3, gd3, ad0, gd0.
+ $this->setUser($viewer2->id);
+
+ // CHECK 1.
+ // Take the neighbours of ad1, which should be prev: null and next: gd1.
+ $neighbours = forum_get_discussion_neighbours($coursemodule, $alldiscussions[1], $forum1);
+ // Null check.
+ $this->assertEmpty($neighbours['prev']);
+ // Gd1 check.
+ $this->assertEquals($group1discussions[1]->id, $neighbours['next']->id);
+
+ // CHECK 2.
+ // Take the neighbours of ad3, which should be prev: gd2 and next: gd3.
+ $neighbours = forum_get_discussion_neighbours($coursemodule, $alldiscussions[3], $forum1);
+ // Gd2 check.
+ $this->assertEquals($group1discussions[2]->id, $neighbours['prev']->id);
+ // Gd3 check.
+ $this->assertEquals($group1discussions[3]->id, $neighbours['next']->id);
+
+ // CHECK 3.
+ // Take the neighbours of gd3, which should be prev: ad3 and next: ad0.
+ $neighbours = forum_get_discussion_neighbours($coursemodule, $group1discussions[3], $forum1);
+ // Ad3 check.
+ $this->assertEquals($alldiscussions[3]->id, $neighbours['prev']->id);
+ // Ad0 check.
+ $this->assertEquals($alldiscussions[0]->id, $neighbours['next']->id);
+
+ // CHECK 4.
+ // Take the neighbours of gd0, which should be prev: ad0 and next: null.
+ $neighbours = forum_get_discussion_neighbours($coursemodule, $group1discussions[0], $forum1);
+ // Ad0 check.
+ $this->assertEquals($alldiscussions[0]->id, $neighbours['prev']->id);
+ // Null check.
+ $this->assertEmpty($neighbours['next']);
+ }
+
+ /**
+ * Test test_pinned_with_timed_discussions.
+ */
+ public function test_pinned_with_timed_discussions() {
+ global $CFG;
+
+ $CFG->forum_enabletimedposts = true;
+
+ $this->resetAfterTest();
+ $course = $this->getDataGenerator()->create_course();
+
+ // Create an user.
+ $user = $this->getDataGenerator()->create_user();
+ $this->getDataGenerator()->enrol_user($user->id, $course->id);
+
+ // Create a forum.
+ $record = new stdClass();
+ $record->course = $course->id;
+ $forum = $this->getDataGenerator()->create_module('forum', (object) array(
+ 'course' => $course->id,
+ 'groupmode' => SEPARATEGROUPS,
+ ));
+
+ $coursemodule = get_coursemodule_from_instance('forum', $forum->id);
+ $now = time();
+ $discussions = array();
+ $discussiongenerator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
+
+ $record = new stdClass();
+ $record->course = $course->id;
+ $record->userid = $user->id;
+ $record->forum = $forum->id;
+ $record->pinned = FORUM_DISCUSSION_PINNED;
+ $record->timemodified = $now;
+
+ $discussions[] = $discussiongenerator->create_discussion($record);
+
+ $record->pinned = FORUM_DISCUSSION_UNPINNED;
+ $record->timestart = $now + 10;
+
+ $discussions[] = $discussiongenerator->create_discussion($record);
+
+ $record->timestart = $now;
+
+ $discussions[] = $discussiongenerator->create_discussion($record);
+
+ // Expected order of discussions:
+ // D2, d1, d0.
+ $this->setUser($user->id);
+
+ // CHECK 1.
+ $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[2], $forum);
+ // Null check.
+ $this->assertEmpty($neighbours['prev']);
+ // D1 check.
+ $this->assertEquals($discussions[1]->id, $neighbours['next']->id);
+
+ // CHECK 2.
+ $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[1], $forum);
+ // D2 check.
+ $this->assertEquals($discussions[2]->id, $neighbours['prev']->id);
+ // D0 check.
+ $this->assertEquals($discussions[0]->id, $neighbours['next']->id);
+
+ // CHECK 3.
+ $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[0], $forum);
+ // D2 check.
+ $this->assertEquals($discussions[1]->id, $neighbours['prev']->id);
+ // Null check.
+ $this->assertEmpty($neighbours['next']);
+ }
+
+ /**
+ * Test test_pinned_timed_discussions_with_timed_discussions.
+ */
+ public function test_pinned_timed_discussions_with_timed_discussions() {
+ global $CFG;
+
+ $CFG->forum_enabletimedposts = true;
+
+ $this->resetAfterTest();
+ $course = $this->getDataGenerator()->create_course();
+
+ // Create an user.
+ $user = $this->getDataGenerator()->create_user();
+ $this->getDataGenerator()->enrol_user($user->id, $course->id);
+
+ // Create a forum.
+ $record = new stdClass();
+ $record->course = $course->id;
+ $forum = $this->getDataGenerator()->create_module('forum', (object) array(
+ 'course' => $course->id,
+ 'groupmode' => SEPARATEGROUPS,
+ ));
+
+ $coursemodule = get_coursemodule_from_instance('forum', $forum->id);
+ $now = time();
+ $discussions = array();
+ $discussiongenerator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
+
+ $record = new stdClass();
+ $record->course = $course->id;
+ $record->userid = $user->id;
+ $record->forum = $forum->id;
+ $record->pinned = FORUM_DISCUSSION_PINNED;
+ $record->timemodified = $now;
+ $record->timestart = $now + 10;
+
+ $discussions[] = $discussiongenerator->create_discussion($record);
+
+ $record->pinned = FORUM_DISCUSSION_UNPINNED;
+
+ $discussions[] = $discussiongenerator->create_discussion($record);
+
+ $record->timestart = $now;
+
+ $discussions[] = $discussiongenerator->create_discussion($record);
+
+ $record->pinned = FORUM_DISCUSSION_PINNED;
+
+ $discussions[] = $discussiongenerator->create_discussion($record);
+
+ // Expected order of discussions:
+ // D2, d1, d3, d0.
+ $this->setUser($user->id);
+
+ // CHECK 1.
+ $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[2], $forum);
+ // Null check.
+ $this->assertEmpty($neighbours['prev']);
+ // D1 check.
+ $this->assertEquals($discussions[1]->id, $neighbours['next']->id);
+
+ // CHECK 2.
+ $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[1], $forum);
+ // D2 check.
+ $this->assertEquals($discussions[2]->id, $neighbours['prev']->id);
+ // D3 check.
+ $this->assertEquals($discussions[3]->id, $neighbours['next']->id);
+
+ // CHECK 3.
+ $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[3], $forum);
+ // D1 check.
+ $this->assertEquals($discussions[1]->id, $neighbours['prev']->id);
+ // D0 check.
+ $this->assertEquals($discussions[0]->id, $neighbours['next']->id);
+
+ // CHECK 4.
+ $neighbours = forum_get_discussion_neighbours($coursemodule, $discussions[0], $forum);
+ // D3 check.
+ $this->assertEquals($discussions[3]->id, $neighbours['prev']->id);
+ // Null check.
+ $this->assertEmpty($neighbours['next']);
+ }
}
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2015111601; // The current module version (Date: YYYYMMDDXX)
+$plugin->version = 2015120800; // The current module version (Date: YYYYMMDDXX)
$plugin->requires = 2015111000; // Requires this Moodle version
$plugin->component = 'mod_forum'; // Full name of the plugin (used for diagnostics)
case 'blog':
echo '<br />';
if (!empty($showall)) {
- forum_print_latest_discussions($course, $forum, 0, 'plain', 'p.created DESC', -1, -1, -1, 0, $cm);
+ forum_print_latest_discussions($course, $forum, 0, 'plain', 'd.pinned DESC, p.created DESC', -1, -1, -1, 0, $cm);
} else {
- forum_print_latest_discussions($course, $forum, -1, 'plain', 'p.created DESC', -1, -1, $page,
+ forum_print_latest_discussions($course, $forum, -1, 'plain', 'd.pinned DESC, p.created DESC', -1, -1, $page,
$CFG->forum_manydiscussions, $cm);
}
break;