MDL-64820 forum: convert view.php to new rendering
authorMihail Geshoski <mihail@moodle.com>
Fri, 15 Mar 2019 03:47:36 +0000 (11:47 +0800)
committerRyan Wyllie <ryan@moodle.com>
Fri, 22 Mar 2019 01:31:21 +0000 (09:31 +0800)
17 files changed:
lib/classes/output/notification.php
lib/templates/notification.mustache [new file with mode: 0644]
mod/forum/classes/local/builders/exported_discussion_summaries.php [new file with mode: 0644]
mod/forum/classes/local/exporters/post.php
mod/forum/classes/local/factories/builder.php
mod/forum/classes/local/factories/renderer.php
mod/forum/classes/local/factories/url.php
mod/forum/classes/local/managers/capability.php
mod/forum/classes/local/renderers/discussion.php
mod/forum/classes/local/renderers/discussion_list.php
mod/forum/classes/local/vaults/discussion.php
mod/forum/templates/blog_discussion_list.mustache
mod/forum/templates/discussion_list.mustache
mod/forum/templates/forum_discussion_post.mustache
mod/forum/templates/single_discussion_list.mustache [new file with mode: 0644]
mod/forum/tests/vaults_forum_test.php
mod/forum/view.php

index 93f8b59..1db678b 100644 (file)
@@ -162,6 +162,10 @@ class notification implements \renderable, \templatable {
             'extraclasses'  => implode(' ', $this->extraclasses),
             'announce'      => $this->announce,
             'closebutton'   => $this->closebutton,
+            'issuccess'         => $this->messagetype === 'success',
+            'isinfo'            => $this->messagetype === 'info',
+            'iswarning'         => $this->messagetype === 'warning',
+            'iserror'           => $this->messagetype === 'error',
         );
     }
 
diff --git a/lib/templates/notification.mustache b/lib/templates/notification.mustache
new file mode 100644 (file)
index 0000000..c3d4340
--- /dev/null
@@ -0,0 +1,50 @@
+{{!
+    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/>.
+}}
+{{!
+    @template core/notification
+
+    Moodle notification template.
+
+    The purpose of this template is to render a notification using other notification templates.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * message A cleaned string (use clean_text()) to display.
+    * extraclasses Additional classes to apply to the notification.
+    * closebutton Whether a close button should be displayed to dismiss the message.
+    * announce Whether the notification should be announced to screen readers.
+
+    Example context (json):
+    { "message": "Your pants are on fire!", "closebutton": 1, "announce": 1, "extraclasses": "foo bar", "isSuccess": 1}
+}}
+{{#issuccess}}
+    {{> core/notification_success}}
+{{/issuccess}}
+{{#isinfo}}
+    {{> core/notification_info}}
+{{/isinfo}}
+{{#iswarning}}
+    {{> core/notification_warning}}
+{{/iswarning}}
+{{#iserror}}
+    {{> core/notification_error}}
+{{/iserror}}
diff --git a/mod/forum/classes/local/builders/exported_discussion_summaries.php b/mod/forum/classes/local/builders/exported_discussion_summaries.php
new file mode 100644 (file)
index 0000000..2bfb769
--- /dev/null
@@ -0,0 +1,193 @@
+<?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/>.
+
+/**
+ * Exported discussion summaries builder class.
+ *
+ * @package    mod_forum
+ * @copyright  2019 Mihail Geshoski <mihail@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_forum\local\builders;
+
+defined('MOODLE_INTERNAL') || die();
+
+use mod_forum\local\entities\discussion as discussion_entity;
+use mod_forum\local\entities\forum as forum_entity;
+use mod_forum\local\entities\forum;
+use mod_forum\local\entities\post as post_entity;
+use mod_forum\local\factories\legacy_data_mapper as legacy_data_mapper_factory;
+use mod_forum\local\factories\exporter as exporter_factory;
+use mod_forum\local\factories\vault as vault_factory;
+use rating_manager;
+use renderer_base;
+use stdClass;
+
+/**
+ * This class is an implementation of the builder pattern (loosely). It is responsible
+ * for taking a set of related forums, discussions, and posts and generate the exported
+ * version of the discussion summaries.
+ *
+ * It encapsulates the complexity involved with exporting discussions summaries. All of the relevant
+ * additional resources will be loaded by this class in order to ensure the exporting
+ * process can happen.
+ *
+ * See this doc for more information on the builder pattern:
+ * https://designpatternsphp.readthedocs.io/en/latest/Creational/Builder/README.html
+ */
+class exported_discussion_summaries {
+    /** @var renderer_base $renderer Core renderer */
+    private $renderer;
+
+    /** @var legacy_data_mapper_factory $legacydatamapperfactory Data mapper factory */
+    private $legacydatamapperfactory;
+
+    /** @var exporter_factory $exporterfactory Exporter factory */
+    private $exporterfactory;
+
+    /** @var vault_factory $vaultfactory Vault factory */
+    private $vaultfactory;
+
+    /** @var rating_manager $ratingmanager Rating manager */
+    private $ratingmanager;
+
+    /**
+     * Constructor.
+     *
+     * @param renderer_base $renderer Core renderer
+     * @param legacy_data_mapper_factory $legacydatamapperfactory Legacy data mapper factory
+     * @param exporter_factory $exporterfactory Exporter factory
+     * @param vault_factory $vaultfactory Vault factory
+     * @param rating_manager $ratingmanager Rating manager
+     */
+    public function __construct(
+        renderer_base $renderer,
+        legacy_data_mapper_factory $legacydatamapperfactory,
+        exporter_factory $exporterfactory,
+        vault_factory $vaultfactory,
+        rating_manager $ratingmanager
+    ) {
+        $this->renderer = $renderer;
+        $this->legacydatamapperfactory = $legacydatamapperfactory;
+        $this->exporterfactory = $exporterfactory;
+        $this->vaultfactory = $vaultfactory;
+        $this->ratingmanager = $ratingmanager;
+    }
+
+    /**
+     * Build the exported discussion summaries for a given set of discussions.
+     *
+     * This will typically be used for a list of discussions in the same forum.
+     *
+     * @param stdClass $user The user to export the posts for.
+     * @param forum_entity $forum The forum that each of the $discussions belong to
+     * @param discussion_entity[] $discussions A list of all discussions that each of the $posts belong to
+     * @return stdClass[] List of exported posts in the same order as the $posts array.
+     */
+    public function build(
+        stdClass $user,
+        forum $forum,
+        array $discussions
+    ) : array {
+
+        $discussionids = array_keys($discussions);
+
+        $postvault = $this->vaultfactory->get_post_vault();
+        $posts = $postvault->get_from_discussion_ids($discussionids);
+        $groupsbyid = $this->get_groups_available_in_forum($forum);
+        $groupsbyauthorid = $this->get_author_groups_from_posts($posts, $forum);
+
+        $replycounts = $postvault->get_reply_count_for_discussion_ids($discussionids);
+        $latestposts = $postvault->get_latest_post_id_for_discussion_ids($discussionids);
+
+        $unreadcounts = [];
+
+        $forumdatamapper = $this->legacydatamapperfactory->get_forum_data_mapper();
+        $forumrecord = $forumdatamapper->to_legacy_object($forum);
+
+        if (forum_tp_can_track_forums($forumrecord)) {
+            $unreadcounts = $postvault->get_unread_count_for_discussion_ids($user, $discussionids);
+        }
+
+        $summaryexporter = $this->exporterfactory->get_discussion_summaries_exporter(
+            $user,
+            $forum,
+            $discussions,
+            $groupsbyid,
+            $groupsbyauthorid,
+            $replycounts,
+            $unreadcounts,
+            $latestposts
+        );
+
+        return (array) $summaryexporter->export($this->renderer);
+    }
+
+    /**
+     * Get the groups details for all groups available to the forum.
+     * @param forum_entity $forum The forum entity
+     * @return stdClass[]
+     */
+    private function get_groups_available_in_forum($forum) : array {
+        $course = $forum->get_course_record();
+        $coursemodule = $forum->get_course_module_record();
+
+        return groups_get_all_groups($course->id, 0, $coursemodule->groupingid);
+    }
+
+    /**
+     * Get the author's groups for a list of posts.
+     *
+     * @param post_entity[] $posts The list of posts
+     * @param forum_entity $forum The forum entity
+     * @return array Author groups indexed by author id
+     */
+    private function get_author_groups_from_posts(array $posts, $forum) : array {
+        $course = $forum->get_course_record();
+        $coursemodule = $forum->get_course_module_record();
+        $authorids = array_reduce($posts, function($carry, $post) {
+            $carry[$post->get_author_id()] = true;
+            return $carry;
+        }, []);
+        $authorgroups = groups_get_all_groups($course->id, array_keys($authorids), $coursemodule->groupingid,
+                'g.*, gm.id, gm.groupid, gm.userid');
+
+        $authorgroups = array_reduce($authorgroups, function($carry, $group) {
+            // Clean up data returned from groups_get_all_groups.
+            $userid = $group->userid;
+            $groupid = $group->groupid;
+
+            unset($group->userid);
+            unset($group->groupid);
+            $group->id = $groupid;
+
+            if (!isset($carry[$userid])) {
+                $carry[$userid] = [$group];
+            } else {
+                $carry[$userid][] = $group;
+            }
+
+            return $carry;
+        }, []);
+
+        foreach (array_diff(array_keys($authorids), array_keys($authorgroups)) as $authorid) {
+            $authorgroups[$authorid] = [];
+        }
+
+        return $authorgroups;
+    }
+}
index 7b250a8..e653b6f 100644 (file)
@@ -191,6 +191,12 @@ class post extends exporter {
                         'optional' => true,
                         'default' => null,
                         'null' => NULL_ALLOWED
+                    ],
+                    'discuss' => [
+                        'type' => PARAM_URL,
+                        'optional' => true,
+                        'default' => null,
+                        'null' => NULL_ALLOWED
                     ]
                 ]
             ],
@@ -294,6 +300,7 @@ class post extends exporter {
         $exporturl = $canexport ? $urlfactory->get_export_post_url_from_post($post) : null;
         $markasreadurl = $cancontrolreadstatus ? $urlfactory->get_mark_post_as_read_url_from_post($post) : null;
         $markasunreadurl = $cancontrolreadstatus ? $urlfactory->get_mark_post_as_unread_url_from_post($post) : null;
+        $discussurl = $canview ? $urlfactory->get_discussion_view_url_from_post($post) : null;
 
         $authorexporter = new author_exporter($author, $authorgroups, ($canview && !$isdeleted), $this->related);
         $exportedauthor = $authorexporter->export($output);
@@ -349,6 +356,7 @@ class post extends exporter {
                 'export' => $exporturl && $exporturl ? $exporturl->out(false) : null,
                 'markasread' => $markasreadurl ? $markasreadurl->out(false) : null,
                 'markasunread' => $markasunreadurl ? $markasunreadurl->out(false) : null,
+                'discuss' => $discussurl ? $discussurl->out(false) : null,
             ],
             'attachments' => ($exportattachments) ? $this->export_attachments($attachments, $post, $output, $canexport) : [],
             'tags' => ($loadcontent && $hastags) ? $this->export_tags($tags) : [],
index a89ba96..20da60c 100644 (file)
@@ -27,6 +27,7 @@ namespace mod_forum\local\factories;
 defined('MOODLE_INTERNAL') || die();
 
 use mod_forum\local\builders\exported_posts as exported_posts_builder;
+use mod_forum\local\builders\exported_discussion_summaries as exported_discussion_summaries_builder;
 use mod_forum\local\factories\vault as vault_factory;
 use mod_forum\local\factories\legacy_data_mapper as legacy_data_mapper_factory;
 use mod_forum\local\factories\exporter as exporter_factory;
@@ -92,4 +93,19 @@ class builder {
             $this->managerfactory->get_rating_manager()
         );
     }
+
+    /**
+     * Get an instance of the exported discussion summaries builder.
+     *
+     * @return exported_discussion_summaries_builder
+     */
+    public function get_exported_discussion_summaries_builder() : exported_discussion_summaries_builder {
+        return new exported_discussion_summaries_builder(
+            $this->rendererbase,
+            $this->legacydatamapperfactory,
+            $this->exporterfactory,
+            $this->vaultfactory,
+            $this->managerfactory->get_rating_manager()
+        );
+    }
 }
index c06676c..8260cd9 100644 (file)
@@ -39,7 +39,7 @@ use mod_forum\local\renderers\discussion as discussion_renderer;
 use mod_forum\local\renderers\discussion_list as discussion_list_renderer;
 use mod_forum\local\renderers\posts as posts_renderer;
 use moodle_page;
-use moodle_url;
+use core\output\notification;
 
 /**
  * Renderer factory.
@@ -120,23 +120,13 @@ class renderer {
         $capabilitymanager = $this->managerfactory->get_capability_manager($forum);
         $ratingmanager = $this->managerfactory->get_rating_manager();
         $rendererbase = $this->rendererbase;
+
         $baseurl = $this->urlfactory->get_discussion_view_url_from_discussion($discussion);
         $notifications = [];
 
-        switch ($forum->get_type()) {
-            case 'single':
-                $baseurl = new moodle_url("/mod/forum/view.php", ['f' => $forum->get_id()]);
-                break;
-            case 'qanda':
-                if ($capabilitymanager->must_post_before_viewing_discussion($user, $forum, $discussion)) {
-                    $notifications[] = $rendererbase->notification(get_string('qandanotify', 'forum'));
-                }
-                break;
-        }
-
         return new discussion_renderer(
-            $discussion,
             $forum,
+            $discussion,
             $displaymode,
             $rendererbase,
             $this->get_single_discussion_posts_renderer($displaymode, false),
@@ -357,7 +347,7 @@ class renderer {
     }
 
     /**
-     * Create a discussion list renderer.
+     * Create a standard type discussion list renderer.
      *
      * @param forum_entity $forum The forum that the discussions belong to
      * @return discussion_list_renderer
@@ -376,8 +366,121 @@ class renderer {
             $this->legacydatamapperfactory,
             $this->exporterfactory,
             $this->vaultfactory,
+            $this->builderfactory,
             $capabilitymanager,
             $this->urlfactory,
+            $notifications,
+            function($discussions, $user, $forum) {
+                $exporteddiscussionsummarybuilder = $this->builderfactory->get_exported_discussion_summaries_builder();
+                return $exportedposts = $exporteddiscussionsummarybuilder->build(
+                    $user,
+                    $forum,
+                    $discussions
+                );
+            }
+        );
+    }
+
+    /**
+     * Create a blog type discussion list renderer.
+     *
+     * @param forum_entity $forum The forum that the discussions belong to
+     * @return discussion_list_renderer
+     */
+    public function get_blog_discussion_list_renderer(
+        forum_entity $forum
+    ) : discussion_list_renderer {
+
+        $capabilitymanager = $this->managerfactory->get_capability_manager($forum);
+        $rendererbase = $this->rendererbase;
+        $notifications = [];
+
+        return new discussion_list_renderer(
+            $forum,
+            $rendererbase,
+            $this->legacydatamapperfactory,
+            $this->exporterfactory,
+            $this->vaultfactory,
+            $this->builderfactory,
+            $capabilitymanager,
+            $this->urlfactory,
+            $notifications,
+            function($discussions, $user, $forum) {
+                $exportedpostsbuilder = $this->builderfactory->get_exported_posts_builder();
+                $discussionentries = [];
+                $postentries = [];
+                foreach($discussions as $discussion) {
+                    $discussionentries[] = $discussion->get_discussion();
+                    $discussionentriesids[] = $discussion->get_discussion()->get_id();
+                    $postentries[] = $discussion->get_first_post();
+                }
+
+                $exportedposts['posts'] = $exportedpostsbuilder->build(
+                    $user,
+                    [$forum],
+                    $discussionentries,
+                    $postentries
+                );
+
+                $postvault = $this->vaultfactory->get_post_vault();
+                $discussionrepliescount = $postvault->get_reply_count_for_discussion_ids($discussionentriesids);
+
+                array_walk($exportedposts['posts'], function($post) use ($discussionrepliescount) {
+                    $post->discussionrepliescount =  $discussionrepliescount[$post->discussionid] ?? 0;
+                    // TODO: Find a better solution due to language differences when defining the singular and plural form.
+                    $post->isreplyplural = $post->discussionrepliescount != 1 ? true : false;
+                });
+
+                $exportedposts['state']['hasdiscussions'] = $exportedposts['posts'] ? true : false;
+
+                return $exportedposts;
+            }
+        );
+    }
+
+    /**
+     * Create a single type discussion list renderer.
+     *
+     * @param forum_entity $forum Forum the discussion belongs to
+     * @param discussion_entity $discussion The discussion entity
+     * @param bool $hasmultiplediscussions Whether the forum has multiple discussions (more than one)
+     * @param int $displaymode How should the posts be formatted?
+     * @return discussion_renderer
+     */
+    public function get_single_discussion_list_renderer(
+        forum_entity $forum,
+        discussion_entity $discussion,
+        bool $hasmultiplediscussions,
+        int $displaymode
+    ) : discussion_renderer {
+
+        $capabilitymanager = $this->managerfactory->get_capability_manager($forum);
+        $ratingmanager = $this->managerfactory->get_rating_manager();
+        $rendererbase = $this->rendererbase;
+
+        $cmid = $forum->get_course_module_record()->id;
+        $baseurl = $this->urlfactory->get_forum_view_url_from_course_module_id($cmid);
+        $notifications = array();
+
+        if ($hasmultiplediscussions) {
+            $notifications[] = (new notification(get_string('warnformorepost', 'forum')))
+                ->set_show_closebutton(true);
+        }
+
+        return new discussion_renderer(
+            $forum,
+            $discussion,
+            $displaymode,
+            $rendererbase,
+            $this->get_single_discussion_posts_renderer($displaymode, false),
+            $this->page,
+            $this->legacydatamapperfactory,
+            $this->exporterfactory,
+            $this->vaultfactory,
+            $capabilitymanager,
+            $ratingmanager,
+            $this->entityfactory->get_exported_posts_sorter(),
+            $baseurl,
             $notifications
         );
     }
index b7c3afd..ff52529 100644 (file)
@@ -109,7 +109,7 @@ class url {
      * @return moodle_url
      */
     public function get_forum_view_url_from_course_module_id(int $coursemoduleid, ?int $pageno = null) : moodle_url {
-        $url = new moodle_url('/mod/forum/discussions.php', [
+        $url = new moodle_url('/mod/forum/view.php', [
             'id' => $coursemoduleid,
         ]);
 
index 7a02f11..5857df8 100644 (file)
@@ -90,6 +90,10 @@ class capability {
      * @return bool
      */
     public function can_subscribe_to_forum(stdClass $user) : bool {
+        if ($this->forum->get_type() == 'single') {
+           return false;
+        }
+
         return !is_guest($this->get_context(), $user) &&
             subscriptions::is_subscribable($this->get_forum_record());
     }
@@ -121,6 +125,12 @@ class capability {
             return false;
         }
 
+        if ($this->forum->get_type() == 'eachuser') {
+            if (forum_user_has_posted_discussion($this->forum->get_id(), $user->id, $groupid)) {
+                return false;
+            }
+        }
+
         if ($this->forum->is_in_group_mode()) {
             return $groupid ? $this->can_access_group($user, $groupid) : $this->can_access_all_groups($user);
         } else {
@@ -175,7 +185,8 @@ class capability {
      */
     public function can_move_discussions(stdClass $user) : bool {
         $forum = $this->get_forum();
-        return $forum->get_type() !== 'single' && has_capability('mod/forum:movediscussions', $this->get_context(), $user);
+        return $forum->get_type() !== 'single' &&
+                has_capability('mod/forum:movediscussions', $this->get_context(), $user);
     }
 
     /**
@@ -185,7 +196,8 @@ class capability {
      * @return bool
      */
     public function can_pin_discussions(stdClass $user) : bool {
-        return has_capability('mod/forum:pindiscussions', $this->get_context(), $user);
+        return $this->forum->get_type() !== 'single' &&
+                has_capability('mod/forum:pindiscussions', $this->get_context(), $user);
     }
 
     /**
index 4d1350a..ca3e7db 100644 (file)
@@ -58,12 +58,12 @@ require_once($CFG->dirroot . '/mod/forum/lib.php');
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class discussion {
-    /** @var discussion_entity $discussion The discussion to render */
+    /** @var forum_entity $forum The forum that the discussion belongs to */
+    private $forum;
+    /** @var discussion_entity $discussion The discussion entity */
     private $discussion;
     /** @var stdClass $discussionrecord Legacy discussion record */
     private $discussionrecord;
-    /** @var forum_entity $forum The forum that the discussion belongs to */
-    private $forum;
     /** @var stdClass $forumrecord Legacy forum record */
     private $forumrecord;
     /** @var int $displaymode The display mode to render the discussion in */
@@ -94,8 +94,8 @@ class discussion {
     /**
      * Constructor.
      *
-     * @param discussion_entity $discussion The discussion to render
      * @param forum_entity $forum The forum that the discussion belongs to
+     * @param discussion_entity $discussion The discussion entity
      * @param int $displaymode The display mode to render the discussion in
      * @param renderer_base $renderer Renderer base
      * @param posts_renderer $postsrenderer A posts renderer
@@ -110,8 +110,8 @@ class discussion {
      * @param array $notifications List of HTML notifications to display
      */
     public function __construct(
-        discussion_entity $discussion,
         forum_entity $forum,
+        discussion_entity $discussion,
         int $displaymode,
         renderer_base $renderer,
         posts_renderer $postsrenderer,
@@ -125,8 +125,8 @@ class discussion {
         moodle_url $baseurl,
         array $notifications = []
     ) {
-        $this->discussion = $discussion;
         $this->forum = $forum;
+        $this->discussion = $discussion;
         $this->displaymode = $displaymode;
         $this->renderer = $renderer;
         $this->postsrenderer = $postsrenderer;
@@ -138,6 +138,7 @@ class discussion {
         $this->capabilitymanager = $capabilitymanager;
         $this->ratingmanager = $ratingmanager;
         $this->notifications = $notifications;
+
         $this->exportedpostsorter = $exportedpostsorter;
 
         $forumdatamapper = $this->legacydatamapperfactory->get_forum_data_mapper();
@@ -164,7 +165,6 @@ class discussion {
 
         $displaymode = $this->displaymode;
         $capabilitymanager = $this->capabilitymanager;
-        $forum = $this->forum;
 
         // Make sure we can render.
         if (!$capabilitymanager->can_view_discussions($user)) {
@@ -175,7 +175,7 @@ class discussion {
 
         $exporteddiscussion = $this->get_exported_discussion($user);
         $exporteddiscussion = array_merge($exporteddiscussion, [
-            'notifications' => $this->get_notifications(),
+            'notifications' => $this->get_notifications($user),
             'html' => [
                 'posts' => $this->postsrenderer->render($user, [$this->forum], [$this->discussion], $posts),
                 'modeselectorform' => $this->get_display_mode_selector_html($displaymode),
@@ -368,9 +368,10 @@ class discussion {
     /**
      * Get a list of notification HTML to render in the page.
      *
+     * @param stdClass $user The user viewing the discussion
      * @return string[]
      */
-    private function get_notifications() : array {
+    private function get_notifications($user) : array {
         $notifications = $this->notifications;
         $discussion = $this->discussion;
         $forum = $this->forum;
@@ -385,6 +386,14 @@ class discussion {
             ->set_show_closebutton();
         }
 
+        if ($forum->get_type() == 'qanda') {
+            if ($this->capabilitymanager->must_post_before_viewing_discussion($user, $discussion)) {
+                $notifications[] = (new notification(
+                    get_string('qandanotify', 'forum')
+                ))->set_show_closebutton(true);
+            }
+        }
+
         if ($forum->has_blocking_enabled()) {
             $notifications[] = (new notification(
                 get_string('thisforumisthrottled', 'forum', [
index 36b27c9..5746754 100644 (file)
@@ -36,6 +36,7 @@ use mod_forum\local\vaults\discussion_list as discussion_list_vault;
 use renderer_base;
 use stdClass;
 use core\output\notification;
+use mod_forum\local\factories\builder as builder_factory;
 
 require_once($CFG->dirroot . '/mod/forum/lib.php');
 
@@ -74,6 +75,12 @@ class discussion_list {
     /** @var array $notifications List of notification HTML */
     private $notifications;
 
+    /** @var builder_factory $builderfactory Builder factory */
+    private $builderfactory;
+
+    /** @var callable $postprocessfortemplate Function to process exported posts before template rendering */
+    private $postprocessfortemplate;
+
     /**
      * Constructor for a new discussion list renderer.
      *
@@ -82,6 +89,7 @@ class discussion_list {
      * @param   legacy_data_mapper_factory $legacydatamapperfactory The factory used to fetch a legacy record
      * @param   exporter_factory    $exporterfactory The factory used to fetch exporter instances
      * @param   vault_factory       $vaultfactory The factory used to fetch the vault instances
+     * @param   builder_factory     $builderfactory The factory used to fetch the builder instances
      * @param   capability_manager  $capabilitymanager The managed used to check capabilities on the forum
      * @param   url_factory         $urlfactory The factory used to create URLs in the forum
      * @param   notification[]      $notifications A list of any notifications to be displayed within the page
@@ -92,18 +100,22 @@ class discussion_list {
         legacy_data_mapper_factory $legacydatamapperfactory,
         exporter_factory $exporterfactory,
         vault_factory $vaultfactory,
+        builder_factory $builderfactory,
         capability_manager $capabilitymanager,
         url_factory $urlfactory,
-        array $notifications = []
+        array $notifications = [],
+        callable $postprocessfortemplate = null
     ) {
         $this->forum = $forum;
         $this->renderer = $renderer;
         $this->legacydatamapperfactory = $legacydatamapperfactory;
         $this->exporterfactory = $exporterfactory;
         $this->vaultfactory = $vaultfactory;
+        $this->builderfactory = $builderfactory;
         $this->capabilitymanager = $capabilitymanager;
         $this->urlfactory = $urlfactory;
         $this->notifications = $notifications;
+        $this->postprocessfortemplate = $postprocessfortemplate;
 
         $forumdatamapper = $this->legacydatamapperfactory->get_forum_data_mapper();
         $this->forumrecord = $forumdatamapper->to_legacy_object($forum);
@@ -121,7 +133,8 @@ class discussion_list {
      * @return  string      The rendered content for display
      */
     public function render(stdClass $user, \cm_info $cm, ?int $groupid, ?int $sortorder, ?int $pageno, ?int $pagesize) : string {
-        $capabilitymanager = $this->capabilitymanager;
+        global $PAGE;
+
         $forum = $this->forum;
 
         $pagesize = $this->get_page_size($pagesize);
@@ -134,18 +147,38 @@ class discussion_list {
             $groupid
         );
 
+        // Count all forum discussion posts.
+        $alldiscussionscount = $this->get_count_all_discussions($user, $groupids);
+
+        // Get all forum discussions posts.
+        $discussions = $this->get_discussions($user, $groupids, $sortorder, $pageno, $pagesize);
+
+        $forumview = [
+            'forum' => (array) $forumexporter->export($this->renderer)
+        ];
+
+        if (!$discussions) {
+            return $this->renderer->render_from_template($this->get_template(), $forumview);
+        }
+
+        if ($this->postprocessfortemplate !== null) {
+            // We've got some post processing to do!
+            $exportedposts = ($this->postprocessfortemplate) ($discussions, $user, $forum);
+        }
+
         $forumview = array_merge(
-                [
-                    'notifications' => $this->get_notifications($user, $groupid),
-                    'forum' => (array) $forumexporter->export($this->renderer),
-                    'groupchangemenu' => groups_print_activity_menu(
-                        $cm,
-                        $this->urlfactory->get_forum_view_url_from_forum($forum),
-                        true
-                    ),
-                ],
-                (array) $this->get_exported_discussions($user, $groupids, $sortorder, $pageno, $pagesize)
-            );
+            $forumview,
+            [
+                'notifications' => $this->get_notifications($user, $groupid),
+                'groupchangemenu' => groups_print_activity_menu(
+                    $cm,
+                    $this->urlfactory->get_forum_view_url_from_forum($forum),
+                    true
+                ),
+                'pagination' => $this->renderer->render(new \paging_bar($alldiscussionscount, $pageno, $pagesize, $PAGE->url, 'p')),
+            ],
+            $exportedposts
+        );
 
         return $this->renderer->render_from_template($this->get_template(), $forumview);
     }
@@ -198,80 +231,50 @@ class discussion_list {
      * @param   int|null    $pagesize The number of discussions to show on the page
      * @return  stdClass    The data to use for display
      */
-    private function get_exported_discussions(stdClass $user, ?array $groupids, ?int $sortorder, ?int $pageno, ?int $pagesize) {
+    private function get_discussions(stdClass $user, ?array $groupids, ?int $sortorder, ?int $pageno, ?int $pagesize) {
         $forum = $this->forum;
         $discussionvault = $this->vaultfactory->get_discussions_in_forum_vault();
         if (null === $groupids) {
-            $discussions = $discussionvault->get_from_forum_id(
+            return $discussions = $discussionvault->get_from_forum_id(
                 $forum->get_id(),
                 $this->capabilitymanager->can_view_hidden_posts($user),
                 $user->id,
                 $sortorder,
                 $this->get_page_size($pagesize),
-                $this->get_page_number($pageno));
+                $this->get_page_number($pageno) * $this->get_page_size($pagesize));
         } else {
-            $discussions = $discussionvault->get_from_forum_id_and_group_id(
+            return $discussions = $discussionvault->get_from_forum_id_and_group_id(
                 $forum->get_id(),
                 $groupids,
                 $this->capabilitymanager->can_view_hidden_posts($user),
                 $user->id,
                 $sortorder,
                 $this->get_page_size($pagesize),
-                $this->get_page_number($pageno));
+                $this->get_page_number($pageno) * $this->get_page_size($pagesize));
         }
+    }
 
-        $discussionids = array_keys($discussions);
-
-        $discussioncount = count($discussionids);
-        if ($discussioncount >= $pagesize) {
-            if (null === $groupids) {
-                $discussioncount = $discussionvault->get_total_discussion_count_from_forum_id(
-                    $forum->get_id(),
-                    $this->capabilitymanager->can_view_hidden_posts($user),
-                    $user->id);
-            } else {
-                $discussioncount = $discussionvault->get_total_discussion_count_from_forum_id_and_group_id(
-                    $forum->get_id(),
-                    $groupids,
-                    $this->capabilitymanager->can_view_hidden_posts($user),
-                    $user->id);
-            }
-        }
-
-        $pagedcontent = new \core\external\paged_content_exporter(
-            $pagesize,
-            $pageno,
-            $discussioncount,
-            function ($pageno, $pagelimit) : \moodle_url {
-                return $this->urlfactory->get_forum_view_url_from_forum($this->forum, $pageno);
-            }
-        );
-
-        $postvault = $this->vaultfactory->get_post_vault();
-        $posts = $postvault->get_from_discussion_ids($discussionids);
-        $groupsbyid = $this->get_groups_available_in_forum();
-        $groupsbyauthorid = $this->get_author_groups_from_posts($posts);
-
-        $replycounts = $postvault->get_reply_count_for_discussion_ids($discussionids);
-        $latestposts = $postvault->get_latest_post_id_for_discussion_ids($discussionids);
-
-        $unreadcounts = [];
-        if (forum_tp_can_track_forums($this->forumrecord)) {
-            $unreadcounts = $postvault->get_unread_count_for_discussion_ids($user, $discussionids);
+    /**
+     * Get a count of all discussions in a forum.
+     *
+     * @param   stdClass    $user The user to render for
+     * @param   array       $groupids The array of groups to render
+     * @return  int         The number of discussions in a forum
+     */
+    public function get_count_all_discussions(stdClass $user, ?array $groupids) {
+        $discussionvault = $this->vaultfactory->get_discussions_in_forum_vault();
+        if (null === $groupids) {
+            return $discussionvault->get_total_discussion_count_from_forum_id(
+                $this->forum->get_id(),
+                $this->capabilitymanager->can_view_hidden_posts($user),
+                $user->id);
+        } else {
+            return $discussionvault->get_total_discussion_count_from_forum_id_and_group_id(
+                $this->forum->get_id(),
+                $groupids,
+                $this->capabilitymanager->can_view_hidden_posts($user),
+                $user->id);
         }
-
-        $summaryexporter = $this->exporterfactory->get_discussion_summaries_exporter(
-            $user,
-            $forum,
-            $discussions,
-            $groupsbyid,
-            $groupsbyauthorid,
-            $replycounts,
-            $unreadcounts,
-            $latestposts
-        );
-
-        return $summaryexporter->export($this->renderer);
     }
 
     /**
@@ -323,63 +326,6 @@ class discussion_list {
         }
     }
 
-    /**
-     * Get the groups details for all groups available to the forum.
-     *
-     * @return  stdClass[]
-     */
-    private function get_groups_available_in_forum() : array {
-        $course = $this->forum->get_course_record();
-        $coursemodule = $this->forum->get_course_module_record();
-
-        return groups_get_all_groups($course->id, 0, $coursemodule->groupingid);
-    }
-
-    /**
-     * Get the author's groups for a list of posts.
-     *
-     * @param post_entity[] $posts The list of posts
-     * @return array Author groups indexed by author id
-     */
-    private function get_author_groups_from_posts(array $posts) : array {
-        $course = $this->forum->get_course_record();
-        $coursemodule = $this->forum->get_course_module_record();
-        $authorids = array_reduce($posts, function($carry, $post) {
-            $carry[$post->get_author_id()] = true;
-            return $carry;
-        }, []);
-        $authorgroups = groups_get_all_groups(
-            $course->id,
-            array_keys($authorids),
-            $coursemodule->groupingid,
-            'g.*, gm.id, gm.groupid, gm.userid'
-        );
-
-        $authorgroups = array_reduce($authorgroups, function($carry, $group) {
-            // Clean up data returned from groups_get_all_groups.
-            $userid = $group->userid;
-            $groupid = $group->groupid;
-
-            unset($group->userid);
-            unset($group->groupid);
-            $group->id = $groupid;
-
-            if (!isset($carry[$userid])) {
-                $carry[$userid] = [$group];
-            } else {
-                $carry[$userid][] = $group;
-            }
-
-            return $carry;
-        }, []);
-
-        foreach (array_diff(array_keys($authorids), array_keys($authorgroups)) as $authorid) {
-            $authorgroups[$authorid] = [];
-        }
-
-        return $authorgroups;
-    }
-
     /**
      * Get the list of notification for display.
      *
index 263486a..43383f9 100644 (file)
@@ -99,4 +99,30 @@ class discussion extends db_table_vault {
         $records = $this->transform_db_records_to_entities($records);
         return count($records) ? array_shift($records) : null;
     }
+
+    /**
+     * Get the last discussion in the specified forum.
+     *
+     * @param   forum_entity $forum
+     * @return  discussion_entity|null
+     */
+    public function get_last_discussion_in_forum(forum_entity $forum) : ?discussion_entity {
+        $records = $this->get_db()->get_records(self::TABLE, [
+            'forum' => $forum->get_id(),
+        ], 'timemodified DESC', '*', 0, 1);
+
+        $records = $this->transform_db_records_to_entities($records);
+        return count($records) ? array_shift($records) : null;
+    }
+
+    /**
+     * Get the count of the discussions in the specified forum.
+     *
+     * @param   forum_entity $forum
+     * @return  int
+     */
+    public function get_count_discussions_in_forum(forum_entity $forum) : ?int {
+        return $this->get_db()->count_records(self::TABLE, [
+            'forum' => $forum->get_id()]);
+    }
 }
index 49da28e..f5ba2c9 100644 (file)
@@ -16,7 +16,7 @@
     along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 }}
 {{!
-    @template mod_forum/qanda_discussion_list
+    @template mod_forum/blog_discussion_list
 
     Template which defines a forum post for sending in a single-post HTML email.
 
     {{$discussion_create_text}}
         {{#str}}addanewtopic, forum{{/str}}
     {{/discussion_create_text}}
+    {{$discussion_list_output}}
+       {{#posts}}
+           {{< mod_forum/forum_discussion_post }}
+               {{$footer}}
+                   <div class="link text-right">
+                       <a href="{{urls.discuss}}">{{#str}}discussthistopic, forum{{/str}}</a>&nbsp;({{discussionrepliescount}}{{#isreplyplural}}{{#str}}repliesmany, forum{{/str}}{{/isreplyplural}}{{^isreplyplural}}{{#str}}repliesone, forum{{/str}}{{/isreplyplural}})
+                   </div>
+               {{/footer}}
+               {{$replyoutput}}{{/replyoutput}}
+           {{/ mod_forum/forum_discussion_post }}
+       {{/posts}}
+    {{/discussion_list_output}}
 {{/ mod_forum/discussion_list }}
index ecf57e0..d6b046c 100644 (file)
     {{/forum.capabilities.create}}
 
     {{#state.hasdiscussions}}
-        <table class="table table-hover table-striped">
-            {{$discussion_list_header}}
-            <thead>
-                <tr>
-                    <th scope="col">&nbsp;</th>
-                    <th scope="col" class="p-l-0">{{#str}}discussion, mod_forum{{/str}}</th>
-                    <th scope="col" class="author">{{#str}}startedby, mod_forum{{/str}}</th>
-                    {{#forum.state.groupmode}}
-                        <th scope="col" class="group">{{#str}}group{{/str}}</th>
-                    {{/forum.state.groupmode}}
-                    {{#forum.capabilities.viewdiscussions}}
-                        <th scope="col" class="text-center">{{#str}}replies, mod_forum{{/str}}</th>
-                        {{#forum.userstate.tracked}}
-                        <th scope="col" class="text-center">
-                            {{#str}}unread, mod_forum{{/str}}
-                            <a href="{{{forum.urls.markasread}}}">{{#pix}}t/markasread, core, {{#str}}markallread, mod_forum{{/str}}{{/pix}}</a>
-                        </th>
-                        {{/forum.userstate.tracked}}
-                    {{/forum.capabilities.viewdiscussions}}
-                    <th scope="col" class="lastpost">{{#str}}lastpost, mod_forum{{/str}}</th>
-                    {{#forum.capabilities.subscribe}}
-                        <th scope="col" class="discussionsubscription"></th>
-                    {{/forum.capabilities.subscribe}}
-                </tr>
-            </thead>
-            {{/discussion_list_header}}
-            {{$discussion_list_body}}
-            <tbody>
-                {{#summaries}}
+        {{{ pagination }}}
+        {{$discussion_list_output}}
+            <table class="table table-hover table-striped">
+                {{$discussion_list_header}}
+                <thead>
                     <tr>
-                        <td scope="col" class="pinned p-0 text-center align-middle">
-                            {{#discussion.pinned}}
-                                {{#pix}}i/pinned, mod_forum, {{#str}}discussionpinned, mod_forum{{/str}}{{/pix}}
-                            {{/discussion.pinned}}
-                        </td>
-                        <td scope="col" class="topic p-0">
-                            <a class="p-3 p-l-0 w-100 h-100 d-block" href="{{discussion.urls.view}}">{{discussion.name}}</a>
-                        </td>
-                        <td scope="col" class="author">
-                            {{#firstpostauthor}}
-                                <a href="{{urls.profile}}">
+                        <th scope="col">&nbsp;</th>
+                        <th scope="col" class="p-l-0">{{#str}}discussion, mod_forum{{/str}}</th>
+                        <th scope="col" class="author">{{#str}}startedby, mod_forum{{/str}}</th>
+                        {{#forum.state.groupmode}}
+                            <th scope="col" class="group">{{#str}}group{{/str}}</th>
+                        {{/forum.state.groupmode}}
+                        {{#forum.capabilities.viewdiscussions}}
+                            <th scope="col" class="text-center">{{#str}}replies, mod_forum{{/str}}</th>
+                            {{#forum.userstate.tracked}}
+                            <th scope="col" class="text-center">
+                                {{#str}}unread, mod_forum{{/str}}
+                                <a href="{{{forum.urls.markasread}}}">{{#pix}}t/markasread, core, {{#str}}markallread, mod_forum{{/str}}{{/pix}}</a>
+                            </th>
+                            {{/forum.userstate.tracked}}
+                        {{/forum.capabilities.viewdiscussions}}
+                        <th scope="col" class="lastpost">{{#str}}lastpost, mod_forum{{/str}}</th>
+                        {{#forum.capabilities.subscribe}}
+                            <th scope="col" class="discussionsubscription"></th>
+                        {{/forum.capabilities.subscribe}}
+                    </tr>
+                </thead>
+                {{/discussion_list_header}}
+                {{$discussion_list_body}}
+                <tbody>
+                    {{#summaries}}
+                        <tr>
+                            <td scope="col" class="pinned p-0 text-center align-middle">
+                                {{#discussion.pinned}}
+                                    {{#pix}}i/pinned, mod_forum, {{#str}}discussionpinned, mod_forum{{/str}}{{/pix}}
+                                {{/discussion.pinned}}
+                            </td>
+                            <td scope="col" class="topic p-0">
+                                <a class="p-3 p-l-0 w-100 h-100 d-block" href="{{discussion.urls.view}}">{{discussion.name}}</a>
+                            </td>
+                            <td scope="col" class="author">
+                                {{#firstpostauthor}}
+                                    <a href="{{urls.profile}}">
+                                        <div class="d-flex flex-row">
+                                            <div class="align-middle p-0">
+                                                <a href="{{urls.profile}}">
+                                                    <img {{!
+                                                        }} class="h-auto rounded-circle userpicture" {{!
+                                                        }} src="{{urls.profileimage}}" {{!
+                                                        }} alt="{{#str}}pictureof, moodle, {{fullname}}{{/str}}" {{!
+                                                        }}>
+                                                </a>
+                                            </div>
+                                            <div class="align-middle p-2">
+                                                <a href="{{urls.profile}}">{{fullname}}</a>
+                                            </div>
+                                        </div>
+                                    </a>
+                                {{/firstpostauthor}}
+                            </td>
+                            {{#forum.state.groupmode}}
+                                <td scope="col" class="group">
+                                    {{#discussion.group}}
+                                        {{#urls.picture}}
+                                            {{#urls.userlist}}
+                                                <a href="{{{urls.userlist}}}">
+                                                    <img class="border rounded h-auto rounded-circle" src="{{{urls.picture}}}">
+                                                </a>
+                                            {{/urls.userlist}}
+                                            {{^urls.userlist}}
+                                                <img class="border rounded h-auto rounded-circle" src="{{{urls.picture}}}">
+                                            {{/urls.userlist}}
+                                        {{/urls.picture}}
+                                        {{^urls.picture}}
+                                            {{#urls.userlist}}
+                                                <a href="{{{urls.userlist}}}">{{name}}</a>
+                                            {{/urls.userlist}}
+                                            {{^urls.userlist}}
+                                                {{name}}
+                                            {{/urls.userlist}}
+                                        {{/urls.picture}}
+                                    {{/discussion.group}}
+                                </td>
+                            {{/forum.state.groupmode}}
+                            {{#forum.capabilities.viewdiscussions}}
+                                <td scope="col" class="p-0 text-center">
+                                    <a href="{{discussion.urls.view}}" class="p-3 w-100 h-100 d-block">
+                                        {{replies}}
+                                    </a>
+                                </td>
+                                {{#forum.userstate.tracked}}
+                                    <td scope="col" class="p-0 text-center">
+                                        {{#unread}}
+                                            {{! TODO Rewrite as AJAX}}
+                                            <div class="p-3 w-100 h-100 d-block">
+                                                <a href="{{{discussion.urls.viewfirstunread}}}">{{unread}}</a>
+                                                <a href="{{{discussion.urls.markasread}}}">{{#pix}}t/markasread, core, {{#str}}markalldread, mod_forum{{/str}}{{/pix}}</a>
+                                            </div>
+                                        {{/unread}}
+                                        {{^unread}}
+                                            <span class="p-3 w-100 h-100 d-block">
+                                                0
+                                            </span>
+                                        {{/unread}}
+                                    </td>
+                                {{/forum.userstate.tracked}}
+                            {{/forum.capabilities.viewdiscussions}}
+                            <td scope="col" class="text-left">
+                                {{! TODO Check q&a, eachuser }}
+                                {{#latestpostid}}
                                     <div class="d-flex flex-row">
                                         <div class="align-middle p-0">
-                                            <a href="{{urls.profile}}">
+                                            <a href="{{latestpostauthor.urls.profile}}">
                                                 <img {{!
                                                     }} class="h-auto rounded-circle userpicture" {{!
-                                                    }} src="{{urls.profileimage}}" {{!
-                                                    }} alt="{{#str}}pictureof, moodle, {{fullname}}{{/str}}" {{!
+                                                    }} src="{{latestpostauthor.urls.profileimage}}" {{!
+                                                    }} alt="{{#str}}pictureof, moodle, {{latestpostauthor.fullname}}{{/str}}" {{!
                                                     }}>
                                             </a>
                                         </div>
-                                        <div class="align-middle p-2">
-                                            <a href="{{urls.profile}}">{{fullname}}</a>
+                                        <div class="p-2 p-t-0 p-b-0 d-inline-flex flex-column">
+                                            <div>
+                                                <a href="{{latestpostauthor.urls.profile}}">{{latestpostauthor.fullname}}</a>
+                                            </div>
+                                            <div>
+                                                <a href="{{{discussion.urls.viewlatest}}}">{{#userdate}}
+                                                    {{discussion.times.modified}}, {{#str}}strftimerecentfull{{/str}}
+                                                {{/userdate}}</a>
+                                            </div>
                                         </div>
                                     </div>
-                                </a>
-                            {{/firstpostauthor}}
-                        </td>
-                        {{#forum.state.groupmode}}
-                            <td scope="col" class="group">
-                                {{#discussion.group}}
-                                    {{#urls.picture}}
-                                        {{#urls.userlist}}
-                                            <a href="{{{urls.userlist}}}">
-                                                <img class="border rounded h-auto rounded-circle" src="{{{urls.picture}}}">
-                                            </a>
-                                        {{/urls.userlist}}
-                                        {{^urls.userlist}}
-                                            <img class="border rounded h-auto rounded-circle" src="{{{urls.picture}}}">
-                                        {{/urls.userlist}}
-                                    {{/urls.picture}}
-                                    {{^urls.picture}}
-                                        {{#urls.userlist}}
-                                            <a href="{{{urls.userlist}}}">{{name}}</a>
-                                        {{/urls.userlist}}
-                                        {{^urls.userlist}}
-                                            {{name}}
-                                        {{/urls.userlist}}
-                                    {{/urls.picture}}
-                                {{/discussion.group}}
+                                {{/latestpostid}}
                             </td>
-                        {{/forum.state.groupmode}}
-                        {{#forum.capabilities.viewdiscussions}}
-                            <td scope="col" class="p-0 text-center">
-                                <a href="{{discussion.urls.view}}" class="p-3 w-100 h-100 d-block">
-                                    {{replies}}
-                                </a>
+                            <td scope="col" class="p-0 align-middle">
+                                {{#discussion}}
+                                    {{> mod_forum/discussion_subscription_toggle}}
+                                {{/discussion}}
                             </td>
-                            {{#forum.userstate.tracked}}
-                                <td scope="col" class="p-0 text-center p-3 w-100 h-100 d-block">
-                                    {{#unread}}
-                                        {{! TODO Rewrite as AJAX}}
-                                        <a href="{{{discussion.urls.viewfirstunread}}}">{{unread}}</a>
-                                        <a href="{{{discussion.urls.markasread}}}">{{#pix}}t/markasread, core, {{#str}}markalldread, mod_forum{{/str}}{{/pix}}</a>
-                                    {{/unread}}
-                                    {{^unread}}
-                                        <span class="p-3 w-100 h-100 d-block">
-                                            0
-                                        </span>
-                                    {{/unread}}
-                                </td>
-                            {{/forum.userstate.tracked}}
-                        {{/forum.capabilities.viewdiscussions}}
-                        <td scope="col" class="text-left">
-                            {{! TODO Check q&a, eachuser }}
-                            {{#latestpostid}}
-                                <div class="d-flex flex-row">
-                                    <div class="align-middle p-0">
-                                        <a href="{{latestpostauthor.urls.profile}}">
-                                            <img {{!
-                                                }} class="h-auto rounded-circle userpicture" {{!
-                                                }} src="{{latestpostauthor.urls.profileimage}}" {{!
-                                                }} alt="{{#str}}pictureof, moodle, {{latestpostauthor.fullname}}{{/str}}" {{!
-                                                }}>
-                                        </a>
-                                    </div>
-                                    <div class="p-2 p-t-0 p-b-0 d-inline-flex flex-column">
-                                        <div>
-                                            <a href="{{latestpostauthor.urls.profile}}">{{latestpostauthor.fullname}}</a>
-                                        </div>
-                                        <div>
-                                            <a href="{{{discussion.urls.viewlatest}}}">{{#userdate}}
-                                                {{discussion.times.modified}}, {{#str}}strftimerecentfull{{/str}}
-                                            {{/userdate}}</a>
-                                        </div>
-                                    </div>
-                                </div>
-                            {{/latestpostid}}
-                        </td>
-                        <td scope="col" class="p-0 align-middle">
-                            {{#discussion}}
-                                {{> mod_forum/discussion_subscription_toggle}}
-                            {{/discussion}}
-                        </td>
-                    </tr>
-                {{/summaries}}
-            </tbody>
-            {{/discussion_list_body}}
-        </table>
-        {{#pageno}}
-            <a href="{{{prev_page_link}}}">Previous</a>
-        {{/pageno}}
-        {{#more_pages}}
-            <a href="{{{next_page_link}}}">Next</a>
-        {{/more_pages}}
+                        </tr>
+                    {{/summaries}}
+                </tbody>
+                {{/discussion_list_body}}
+            </table>
+        {{/discussion_list_output}}
+        {{$pagination_output}}
+            {{{ pagination }}}
+        {{/pagination_output}}
         {{#can_create_discussion}}
             <div class="forumaddnew">
                 <a href="{{create_discussion_link}}" class="btn btn-primary">{{create_discussion_link_text}}</a>
index 5879293..406d929 100644 (file)
                                     </a>
                                 {{/delete}}
                                 {{#reply}}
-                                    <a href="{{{urls.reply}}}" class="btn btn-link">
-                                        {{#str}} reply, mod_forum {{/str}}
-                                    </a>
+                                    {{$replyoutput}}
+                                        <a href="{{{urls.reply}}}" class="btn btn-link">
+                                            {{#str}} reply, mod_forum {{/str}}
+                                        </a>
+                                    {{/replyoutput}}
                                 {{/reply}}
                                 {{#export}}
                                     <a href="{{{urls.export}}}" class="btn btn-link">
diff --git a/mod/forum/templates/single_discussion_list.mustache b/mod/forum/templates/single_discussion_list.mustache
new file mode 100644 (file)
index 0000000..81f2303
--- /dev/null
@@ -0,0 +1,42 @@
+
+{{!
+    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/>.
+}}
+{{!
+    @template mod_forum/qanda_discussion_list
+
+    Template which defines a forum post for sending in a single-post HTML email.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Example context (json):
+    {
+    }
+}}
+{{< mod_forum/discussion_list }}
+    {{$discussion_create_text}}
+        {{#str}}addanewtopic, forum{{/str}}
+    {{/discussion_create_text}}
+    {{$discussion_list_output}}
+       {{#posts}}
+           {{> mod_forum/forum_discussion_post }}
+       {{/posts}}
+    {{/discussion_list_output}}
+{{/ mod_forum/discussion_list }}
index 2e2ce12..9c6d101 100644 (file)
@@ -103,11 +103,17 @@ class mod_forum_vaults_forum_testcase extends advanced_testcase {
         $this->assertEquals([], $entities);
 
         $entities = array_values($vault->get_from_course_module_ids([$coursemodule1->id, $coursemodule2->id]));
+        usort($entities, function($a, $b) {
+            return $a->get_id() <=> $b->get_id();
+        });
         $this->assertCount(2, $entities);
-        $this->assertEquals($forum2->id, $entities[0]->get_id());
-        $this->assertEquals($forum1->id, $entities[1]->get_id());
+        $this->assertEquals($forum1->id, $entities[0]->get_id());
+        $this->assertEquals($forum2->id, $entities[1]->get_id());
 
         $entities = array_values($vault->get_from_course_module_ids([$coursemodule1->id]));
+        usort($entities, function($a, $b) {
+            return $a->get_id() <=> $b->get_id();
+        });
         $this->assertCount(1, $entities);
         $this->assertEquals($forum1->id, $entities[0]->get_id());
     }
index de2f2a8..008d03c 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
+ * Displays the list of discussions in a forum.
+ *
  * @package   mod_forum
- * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
+ * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-    require_once('../../config.php');
-    require_once('lib.php');
-    require_once($CFG->libdir.'/completionlib.php');
-
-    $id          = optional_param('id', 0, PARAM_INT);       // Course Module ID
-    $f           = optional_param('f', 0, PARAM_INT);        // Forum ID
-    $mode        = optional_param('mode', 0, PARAM_INT);     // Display mode (for single forum)
-    $showall     = optional_param('showall', '', PARAM_INT); // show all discussions on one page
-    $changegroup = optional_param('group', -1, PARAM_INT);   // choose the current group
-    $page        = optional_param('page', 0, PARAM_INT);     // which page to show
-    $search      = optional_param('search', '', PARAM_CLEAN);// search string
-
-    $params = array();
-    if ($id) {
-        $params['id'] = $id;
-    } else {
-        $params['f'] = $f;
-    }
-    if ($page) {
-        $params['page'] = $page;
-    }
-    if ($search) {
-        $params['search'] = $search;
-    }
-    $PAGE->set_url('/mod/forum/view.php', $params);
-
-    if ($id) {
-        if (! $cm = get_coursemodule_from_id('forum', $id)) {
-            print_error('invalidcoursemodule');
-        }
-        if (! $course = $DB->get_record("course", array("id" => $cm->course))) {
-            print_error('coursemisconf');
-        }
-        if (! $forum = $DB->get_record("forum", array("id" => $cm->instance))) {
-            print_error('invalidforumid', 'forum');
-        }
-        if ($forum->type == 'single') {
-            $PAGE->set_pagetype('mod-forum-discuss');
-        }
-        // move require_course_login here to use forced language for course
-        // fix for MDL-6926
-        require_course_login($course, true, $cm);
-        $strforums = get_string("modulenameplural", "forum");
-        $strforum = get_string("modulename", "forum");
-    } else if ($f) {
-
-        if (! $forum = $DB->get_record("forum", array("id" => $f))) {
-            print_error('invalidforumid', 'forum');
-        }
-        if (! $course = $DB->get_record("course", array("id" => $forum->course))) {
-            print_error('coursemisconf');
-        }
-
-        if (!$cm = get_coursemodule_from_instance("forum", $forum->id, $course->id)) {
-            print_error('missingparameter');
-        }
-        // move require_course_login here to use forced language for course
-        // fix for MDL-6926
-        require_course_login($course, true, $cm);
-        $strforums = get_string("modulenameplural", "forum");
-        $strforum = get_string("modulename", "forum");
-    } else {
-        print_error('missingparameter');
-    }
-
-    if (!$PAGE->button) {
-        $PAGE->set_button(forum_search_form($course, $search));
-    }
-
-    $context = context_module::instance($cm->id);
-    $PAGE->set_context($context);
-
-    if (!empty($CFG->enablerssfeeds) && !empty($CFG->forum_enablerssfeeds) && $forum->rsstype && $forum->rssarticles) {
-        require_once("$CFG->libdir/rsslib.php");
-
-        $rsstitle = format_string($course->shortname, true, array('context' => context_course::instance($course->id))) . ': ' . format_string($forum->name);
-        rss_add_http_header($context, 'mod_forum', $forum, $rsstitle);
-    }
-
-/// Print header.
-
-    $PAGE->set_title($forum->name);
-    $PAGE->add_body_class('forumtype-'.$forum->type);
-    $PAGE->set_heading($course->fullname);
-
-    // Some capability checks.
-    $courselink = new moodle_url('/course/view.php', ['id' => $cm->course]);
-
-    if (empty($cm->visible) and !has_capability('moodle/course:viewhiddenactivities', $context)) {
-        notice(get_string("activityiscurrentlyhidden"), $courselink);
-    }
-
-    if (!has_capability('mod/forum:viewdiscussion', $context)) {
-        notice(get_string('noviewdiscussionspermission', 'forum'), $courselink);
-    }
-
-    // Mark viewed and trigger the course_module_viewed event.
-    forum_view($forum, $course, $cm, $context);
-
-    echo $OUTPUT->header();
-
-    echo $OUTPUT->heading(format_string($forum->name), 2);
-    if (!empty($forum->intro) && $forum->type != 'single' && $forum->type != 'teacher') {
-        echo $OUTPUT->box(format_module_intro('forum', $forum, $cm->id), 'generalbox', 'intro');
-    }
-
-/// find out current groups mode
-    groups_print_activity_menu($cm, $CFG->wwwroot . '/mod/forum/view.php?id=' . $cm->id);
-
-    $SESSION->fromdiscussion = qualified_me();   // Return here if we post or set subscription etc
-
-
-/// Print settings and things across the top
-
-    // If it's a simple single discussion forum, we need to print the display
-    // mode control.
-    if ($forum->type == 'single') {
-        $discussion = NULL;
-        $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
-        if (!empty($discussions)) {
-            $discussion = array_pop($discussions);
-        }
-        if ($discussion) {
-            if ($mode) {
-                set_user_preference("forum_displaymode", $mode);
-            }
-            $displaymode = get_user_preferences("forum_displaymode", $CFG->forum_displaymode);
-            forum_print_mode_form($forum->id, $displaymode, $forum->type);
-        }
-    }
-
-    if (!empty($forum->blockafter) && !empty($forum->blockperiod)) {
-        $a = new stdClass();
-        $a->blockafter = $forum->blockafter;
-        $a->blockperiod = get_string('secondstotime'.$forum->blockperiod);
-        echo $OUTPUT->notification(get_string('thisforumisthrottled', 'forum', $a));
-    }
-
-    if ($forum->type == 'qanda' && !has_capability('moodle/course:manageactivities', $context)) {
-        echo $OUTPUT->notification(get_string('qandanotify','forum'));
-    }
-
-    switch ($forum->type) {
-        case 'single':
-            if (!empty($discussions) && count($discussions) > 1) {
-                echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
-            }
-            if (! $post = forum_get_post_full($discussion->firstpost)) {
-                print_error('cannotfindfirstpost', 'forum');
-            }
-            if ($mode) {
-                set_user_preference("forum_displaymode", $mode);
-            }
-
-            $canreply    = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $context);
-            $canrate     = has_capability('mod/forum:rate', $context);
-            $displaymode = get_user_preferences("forum_displaymode", $CFG->forum_displaymode);
-
-            echo '&nbsp;'; // this should fix the floating in FF
-            forum_print_discussion($course, $cm, $forum, $discussion, $post, $displaymode, $canreply, $canrate);
-            break;
-
-        case 'eachuser':
-            echo '<p class="mdl-align">';
-            if (forum_user_can_post_discussion($forum, null, -1, $cm)) {
-                print_string("allowsdiscussions", "forum");
-            } else {
-                echo '&nbsp;';
-            }
-            echo '</p>';
-            if (!empty($showall)) {
-                forum_print_latest_discussions($course, $forum, 0, 'header', '', -1, -1, -1, 0, $cm);
-            } else {
-                forum_print_latest_discussions($course, $forum, -1, 'header', '', -1, -1, $page, $CFG->forum_manydiscussions, $cm);
-            }
-            break;
-
-        case 'teacher':
-            if (!empty($showall)) {
-                forum_print_latest_discussions($course, $forum, 0, 'header', '', -1, -1, -1, 0, $cm);
-            } else {
-                forum_print_latest_discussions($course, $forum, -1, 'header', '', -1, -1, $page, $CFG->forum_manydiscussions, $cm);
-            }
-            break;
-
-        case 'blog':
-            echo '<br />';
-            if (!empty($showall)) {
-                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', 'd.pinned DESC, p.created DESC', -1, -1, $page,
-                    $CFG->forum_manydiscussions, $cm);
-            }
-            break;
-
-        default:
-            echo '<br />';
-            if (!empty($showall)) {
-                forum_print_latest_discussions($course, $forum, 0, 'header', '', -1, -1, -1, 0, $cm);
-            } else {
-                forum_print_latest_discussions($course, $forum, -1, 'header', '', -1, -1, $page, $CFG->forum_manydiscussions, $cm);
-            }
-
-
-            break;
-    }
-
-    // Add the subscription toggle JS.
-    $PAGE->requires->yui_module('moodle-mod_forum-subscriptiontoggle', 'Y.M.mod_forum.subscriptiontoggle.init');
-
-    echo $OUTPUT->footer($course);
+require_once('../../config.php');
+
+$managerfactory = mod_forum\local\container::get_manager_factory();
+$legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
+$vaultfactory = mod_forum\local\container::get_vault_factory();
+$forumvault = $vaultfactory->get_forum_vault();
+$discussionvault = $vaultfactory->get_discussion_vault();
+$postvault = $vaultfactory->get_post_vault();
+
+$cmid = required_param('id', PARAM_INT);
+$pageno = optional_param('p', 0, PARAM_INT);
+$pagesize = optional_param('s', 0, PARAM_INT);
+$sortorder = optional_param('o', null, PARAM_INT);
+$mode = optional_param('mode', 0, PARAM_INT);
+
+$forum = $forumvault->get_from_course_module_id($cmid);
+if (!$forum) {
+    throw new \moodle_exception('Unable to find forum with id ' . $forumid);
+}
+
+$urlfactory = mod_forum\local\container::get_url_factory();
+$capabilitymanager = $managerfactory->get_capability_manager($forum);
+
+$url = $urlfactory->get_forum_view_url_from_course_module_id($cmid);
+$PAGE->set_url($url);
+
+$course = $forum->get_course_record();
+$coursemodule = $forum->get_course_module_record();
+$cm = \cm_info::create($coursemodule);
+
+require_course_login($course, true, $cm);
+
+$PAGE->set_context($forum->get_context());
+$PAGE->set_title($forum->get_name());
+$PAGE->add_body_class('forumtype-' . $forum->get_type());
+$PAGE->set_heading($course->fullname);
+$PAGE->set_button(forum_search_form($course));
+
+if (empty($cm->visible) and !has_capability('moodle/course:viewhiddenactivities', $context)) {
+    redirect(
+        $urlfactory->get_course_url_from_forum($forum),
+        get_string('activityiscurrentlyhidden'),
+        \core\output\notification::NOTIFY_WARNING
+    );
+}
+
+if (!$capabilitymanager->can_view_discussions($USER, $forum)) {
+    redirect(
+        $urlfactory->get_course_url_from_forum($forum),
+        get_string('noviewdiscussionspermission', 'fourm'),
+        \core\output\notification::NOTIFY_WARNING
+    );
+}
+
+// Mark viewed and trigger the course_module_viewed event.
+$forumdatamapper = $legacydatamapperfactory->get_forum_data_mapper();
+forum_view(
+    $forumdatamapper->to_legacy_object($forum),
+    $forum->get_course_record(),
+    $forum->get_course_module_record(),
+    $forum->get_context()
+);
+
+if (!empty($CFG->enablerssfeeds) && !empty($CFG->forum_enablerssfeeds) && $forum->get_rss_type() && $forum->get_rss_articles()) {
+    require_once("{$CFG->libdir}/rsslib.php");
+
+    $rsstitle = format_string($course->shortname, true, [
+            'context' => context_course::instance($course->id),
+        ]) . ': ' . format_string($forum->get_name());
+    rss_add_http_header($forum->get_context(), 'mod_forum', $forum, $rsstitle);
+}
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading(format_string($forum->get_name()), 2);
+
+if ('single' !== $forum->get_type() && !empty($forum->get_intro())) {
+    $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
+    $forumdbdatamapper = $legacydatamapperfactory->get_forum_data_mapper();
+    $forumrecord = $forumdbdatamapper->to_legacy_object($forum);
+
+    echo $OUTPUT->box(format_module_intro('forum', $forumrecord, $cm->id), 'generalbox', 'intro');
+}
+
+if ($mode) {
+    set_user_preference('forum_displaymode', $mode);
+}
+
+$displaymode = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
+
+// Fetch the current groupid.
+$groupid = groups_get_activity_group($cm, true) ?: null;
+$rendererfactory = mod_forum\local\container::get_renderer_factory();
+switch ($forum->get_type()) {
+    case 'single':
+        $discussion = $discussionvault->get_last_discussion_in_forum($forum);
+        $discussioncount = $discussionvault->get_count_discussions_in_forum($forum);
+        $hasmultiplediscussions = $discussioncount > 1 ? true : false;
+        $discussionsrenderer = $rendererfactory->get_single_discussion_list_renderer($forum, $discussion,
+            $hasmultiplediscussions, $displaymode);
+        $post = $postvault->get_from_id($discussion->get_first_post_id());
+        $orderpostsby = $displaymode == FORUM_MODE_FLATNEWEST ? 'created DESC' : 'created ASC';
+        $replies = $postvault->get_replies_to_post($post, $orderpostsby);
+        echo $discussionsrenderer->render($USER, $post, $replies);
+        break;
+    case 'blog':
+        $discussionsrenderer = $rendererfactory->get_blog_discussion_list_renderer($forum);
+        echo $discussionsrenderer->render($USER, $cm, $groupid, $sortorder, $pageno, $pagesize);
+        break;
+    default:
+        $discussionsrenderer = $rendererfactory->get_discussion_list_renderer($forum);
+        echo $discussionsrenderer->render($USER, $cm, $groupid, $sortorder, $pageno, $pagesize);
+}
+
+echo $OUTPUT->footer();