Merge branch 'MDL-372-int-master' of git://github.com/ryanwyllie/moodle
authorAndrew Nicols <andrew@nicols.co.uk>
Wed, 13 Jan 2016 02:22:24 +0000 (10:22 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Wed, 13 Jan 2016 02:22:24 +0000 (10:22 +0800)
27 files changed:
blocks/news_items/block_news_items.php
mod/forum/backup/moodle2/backup_forum_stepslib.php
mod/forum/classes/event/discussion_pinned.php [new file with mode: 0644]
mod/forum/classes/event/discussion_unpinned.php [new file with mode: 0644]
mod/forum/classes/post_form.php
mod/forum/db/access.php
mod/forum/db/install.xml [changed mode: 0644->0755]
mod/forum/db/log.php
mod/forum/db/upgrade.php
mod/forum/discuss.php
mod/forum/externallib.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/pix/i/pinned.png [new file with mode: 0644]
mod/forum/pix/i/pinned.svg [new file with mode: 0644]
mod/forum/post.php
mod/forum/styles.css
mod/forum/tests/behat/discussion_navigation.feature
mod/forum/tests/behat/move_discussion.feature
mod/forum/tests/behat/posts_ordering_blog.feature
mod/forum/tests/behat/posts_ordering_general.feature
mod/forum/tests/externallib_test.php
mod/forum/tests/generator/lib.php
mod/forum/tests/generator_test.php
mod/forum/tests/lib_test.php
mod/forum/version.php
mod/forum/view.php

index de57a2d..c8b7f2c 100644 (file)
@@ -93,7 +93,8 @@ class block_news_items extends block_base {
             // 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').')';
index c423af3..ed374a6 100644 (file)
@@ -51,7 +51,7 @@ class backup_forum_activity_structure_step extends backup_activity_structure_ste
         $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');
 
diff --git a/mod/forum/classes/event/discussion_pinned.php b/mod/forum/classes/event/discussion_pinned.php
new file mode 100644 (file)
index 0000000..fd74bdd
--- /dev/null
@@ -0,0 +1,123 @@
+<?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;
+    }
+}
diff --git a/mod/forum/classes/event/discussion_unpinned.php b/mod/forum/classes/event/discussion_unpinned.php
new file mode 100644 (file)
index 0000000..5c38d42
--- /dev/null
@@ -0,0 +1,123 @@
+<?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;
+    }
+}
index 6d53e6a..0fbe196 100644 (file)
@@ -138,6 +138,11 @@ class mod_forum_post_form extends moodleform {
             $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'));
         }
index 396a550..e3eae61 100644 (file)
@@ -224,6 +224,17 @@ $capabilities = array(
         )
     ),
 
+    '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,
old mode 100644 (file)
new mode 100755 (executable)
index 7353758..4e44b1b
@@ -1,5 +1,5 @@
 <?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"
 >
@@ -52,6 +52,7 @@
         <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"/>
index 22ae66c..aaad41f 100644 (file)
 
 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
index 4edc4b6..a3e8fd3 100644 (file)
@@ -253,5 +253,18 @@ function xmldb_forum_upgrade($oldversion) {
     // 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;
 }
index 33fe847..29bfbb3 100644 (file)
@@ -32,6 +32,7 @@ $mode   = optional_param('mode', 0, PARAM_INT);          // If set, changes the
 $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) {
@@ -171,6 +172,28 @@ if ($move > 0 and confirm_sesskey()) {
 
     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);
@@ -271,7 +294,7 @@ $neighbourlinks = $renderer->neighbouring_discussion_navigation($neighbours['pre
 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');
@@ -333,8 +356,21 @@ if ($forum->type != 'single'
     }
     echo "</div>";
 }
-echo '<div class="clearfloat">&nbsp;</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();
@@ -345,7 +381,7 @@ if (!empty($forum->blockafter) && !empty($forum->blockperiod)) {
 
 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()) {
index 1b49192..d93a6f0 100644 (file)
@@ -649,7 +649,7 @@ class mod_forum_external extends external_api {
         // 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) {
@@ -801,7 +801,8 @@ class mod_forum_external extends external_api {
                                 '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'
                         )
                     ),
@@ -1115,6 +1116,7 @@ class mod_forum_external extends external_api {
                             '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.'
@@ -1151,7 +1153,8 @@ class mod_forum_external extends external_api {
                                             ));
         // Validate options.
         $options = array(
-            'discussionsubscribe' => true
+            'discussionsubscribe' => true,
+            'discussionpinned' => false
         );
         foreach ($params['options'] as $option) {
             $name = trim($option['name']);
@@ -1159,6 +1162,9 @@ class mod_forum_external extends external_api {
                 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);
             }
@@ -1210,6 +1216,11 @@ class mod_forum_external extends external_api {
         $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)) {
 
index aa23a28..ae32f1e 100644 (file)
@@ -138,6 +138,9 @@ $string['discussionmovedpost'] = 'This discussion has been moved to <a href="{$a
 $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';
@@ -146,6 +149,7 @@ $string['discussions'] = 'Discussions';
 $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.';
@@ -168,6 +172,8 @@ $string['eventdiscussionmoved'] = 'Discussion moved';
 $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';
@@ -229,6 +235,7 @@ $string['forum:exportpost'] = 'Export post';
 $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';
index 29f4f6d..d993c41 100644 (file)
@@ -69,6 +69,9 @@ if (!defined('FORUM_CRON_USER_CACHE')) {
  */
 define('FORUM_POSTS_ALL_USER_GROUPS', -2);
 
+define('FORUM_DISCUSSION_PINNED', 1);
+define('FORUM_DISCUSSION_UNPINNED', 0);
+
 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
 
 /**
@@ -2593,7 +2596,7 @@ function forum_get_discussions($cm, $forumsort="", $fullpost=true, $unused=-1, $
     }
 
     $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
@@ -2601,17 +2604,15 @@ function forum_get_discussions($cm, $forumsort="", $fullpost=true, $unused=-1, $
                    $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.
@@ -2675,78 +2676,82 @@ function forum_get_discussion_neighbours($cm, $discussion, $forum) {
         }
     }
 
-    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;
 }
 
@@ -2758,9 +2763,10 @@ function forum_get_discussion_neighbours($cm, $discussion, $forum) {
  * @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)) {
@@ -2769,6 +2775,12 @@ function forum_get_default_sort_order($desc = true, $compare = 'd.timemodified',
 
     $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
@@ -2776,7 +2788,7 @@ function forum_get_default_sort_order($desc = true, $compare = 'd.timemodified',
                  ELSE {$compare}
                  END";
     }
-    return "$sort $dir";
+    return "$pinned $sort $dir";
 }
 
 /**
@@ -3632,9 +3644,14 @@ function forum_print_discussion_header(&$post, $forum, $group = -1, $datestring
     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));
@@ -4350,6 +4367,10 @@ function forum_update_post($post, $mform, &$message) {
         $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);
@@ -4382,7 +4403,7 @@ function forum_update_post($post, $mform, &$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;
@@ -5426,7 +5447,7 @@ function forum_print_latest_discussions($course, $forum, $maxdiscussions = -1, $
         } 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) {
@@ -7765,6 +7786,54 @@ function forum_discussion_view($modcontext, $forum, $discussion) {
     $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.
  *
diff --git a/mod/forum/pix/i/pinned.png b/mod/forum/pix/i/pinned.png
new file mode 100644 (file)
index 0000000..74c8df0
Binary files /dev/null and b/mod/forum/pix/i/pinned.png differ
diff --git a/mod/forum/pix/i/pinned.svg b/mod/forum/pix/i/pinned.svg
new file mode 100644 (file)
index 0000000..5a09f9a
--- /dev/null
@@ -0,0 +1,12 @@
+<?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
index 3b61658..fa6ed46 100644 (file)
@@ -650,6 +650,10 @@ $mform_post->set_data(array(        'attachments'=>$draftitemid,
                                     'timeend'=>$discussion->timeend):
                                 array())+
 
+                            (isset($discussion->pinned) ? array(
+                                     'pinned' => $discussion->pinned) :
+                                array()) +
+
                             (isset($post->groupid)?array(
                                     'groupid'=>$post->groupid):
                                 array())+
@@ -713,7 +717,16 @@ if ($mform_post->is_cancelled()) {
 
             $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)) {
@@ -853,6 +866,12 @@ if ($mform_post->is_cancelled()) {
         $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();
 
@@ -1047,4 +1066,3 @@ if (!empty($formheading)) {
 $mform_post->display();
 
 echo $OUTPUT->footer();
-
index 25874a6..44a217c 100644 (file)
@@ -63,6 +63,7 @@
 .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 **/
index 49c83e3..36a0c79 100644 (file)
@@ -39,11 +39,9 @@ Feature: A user can navigate to previous and next discussions
     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 |
@@ -56,7 +54,6 @@ Feature: A user can navigate to previous and next discussions
     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 |
@@ -81,26 +78,21 @@ Feature: A user can navigate to previous and next discussions
     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 |
@@ -143,26 +135,21 @@ Feature: A user can navigate to previous and next discussions
     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 |
index 79f6d45..3bd4fbc 100644 (file)
@@ -28,7 +28,6 @@ Feature: A teacher can move discussions between forums
     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"
index 8d08a9d..01bf315 100644 (file)
@@ -41,11 +41,9 @@ Feature: Blog posts are always displayed in reverse chronological order
     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  |
index cf5df8a..01ff399 100644 (file)
@@ -41,11 +41,9 @@ Feature: New discussions and discussions with recently added replies are display
     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  |
index afe8d8b..a7ce2b0 100644 (file)
@@ -695,7 +695,8 @@ class mod_forum_external_testcase extends externallib_advanced_testcase {
                 'userpictureurl' => '',
                 'usermodifiedpictureurl' => '',
                 'numreplies' => 3,
-                'numunread' => 0
+                'numunread' => 0,
+                'pinned' => FORUM_DISCUSSION_UNPINNED
             );
 
         // Call the external function passing forum id.
@@ -907,6 +908,14 @@ class mod_forum_external_testcase extends externallib_advanced_testcase {
         $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']);
     }
 
     /**
index 3492860..d2a1d12 100644 (file)
@@ -189,6 +189,10 @@ class mod_forum_generator extends testing_module_generator {
             $timemodified = $record['timemodified'];
         }
 
+        if (!isset($record['pinned'])) {
+            $record['pinned'] = FORUM_DISCUSSION_UNPINNED;
+        }
+
         $record = (object) $record;
 
         // Add the discussion.
index 7ab2a4b..0be1d4a 100644 (file)
@@ -108,7 +108,9 @@ class mod_forum_generator_testcase extends advanced_testcase {
         $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);
 
index 8e43488..e51e51e 100644 (file)
@@ -797,6 +797,9 @@ class mod_forum_lib_testcase extends advanced_testcase {
         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();
@@ -987,19 +990,52 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $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']);
     }
 
@@ -1010,6 +1046,9 @@ class mod_forum_lib_testcase extends advanced_testcase {
         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();
@@ -1176,17 +1215,17 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $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']);
     }
 
@@ -1196,6 +1235,9 @@ class mod_forum_lib_testcase extends advanced_testcase {
     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();
@@ -1390,6 +1432,9 @@ class mod_forum_lib_testcase extends advanced_testcase {
     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();
@@ -1418,6 +1463,7 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $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);
@@ -1661,7 +1707,14 @@ class mod_forum_lib_testcase extends advanced_testcase {
             $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;
 
@@ -1759,7 +1812,13 @@ class mod_forum_lib_testcase extends advanced_testcase {
         // 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);
@@ -1799,6 +1858,41 @@ class mod_forum_lib_testcase extends advanced_testcase {
 
         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().
@@ -2455,4 +2549,287 @@ class mod_forum_lib_testcase extends advanced_testcase {
             ),
         );
     }
+
+    /**
+     * 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']);
+    }
 }
index 2b3ff68..50277d1 100644 (file)
@@ -24,6 +24,6 @@
 
 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)
index 483fed7..b5a7863 100644 (file)
         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;