You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
580 lines
24 KiB
580 lines
24 KiB
<?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/>.
|
|
|
|
/**
|
|
* Renderer factory.
|
|
*
|
|
* @package mod_forum
|
|
* @copyright 2019 Ryan Wyllie <ryan@moodle.com>
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
|
|
namespace mod_forum\local\factories;
|
|
|
|
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\factories\vault as vault_factory;
|
|
use mod_forum\local\factories\legacy_data_mapper as legacy_data_mapper_factory;
|
|
use mod_forum\local\factories\entity as entity_factory;
|
|
use mod_forum\local\factories\exporter as exporter_factory;
|
|
use mod_forum\local\factories\manager as manager_factory;
|
|
use mod_forum\local\factories\builder as builder_factory;
|
|
use mod_forum\local\factories\url as url_factory;
|
|
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 core\output\notification;
|
|
|
|
/**
|
|
* Renderer factory.
|
|
*
|
|
* See:
|
|
* https://designpatternsphp.readthedocs.io/en/latest/Creational/SimpleFactory/README.html
|
|
*
|
|
* @copyright 2019 Ryan Wyllie <ryan@moodle.com>
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
class renderer {
|
|
/** @var legacy_data_mapper_factory $legacydatamapperfactory Legacy data mapper factory */
|
|
private $legacydatamapperfactory;
|
|
/** @var exporter_factory $exporterfactory Exporter factory */
|
|
private $exporterfactory;
|
|
/** @var vault_factory $vaultfactory Vault factory */
|
|
private $vaultfactory;
|
|
/** @var manager_factory $managerfactory Manager factory */
|
|
private $managerfactory;
|
|
/** @var entity_factory $entityfactory Entity factory */
|
|
private $entityfactory;
|
|
/** @var builder_factory $builderfactory Builder factory */
|
|
private $builderfactory;
|
|
/** @var url_factory $urlfactory URL factory */
|
|
private $urlfactory;
|
|
/** @var renderer_base $rendererbase Renderer base */
|
|
private $rendererbase;
|
|
/** @var moodle_page $page Moodle page */
|
|
private $page;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param legacy_data_mapper_factory $legacydatamapperfactory Legacy data mapper factory
|
|
* @param exporter_factory $exporterfactory Exporter factory
|
|
* @param vault_factory $vaultfactory Vault factory
|
|
* @param manager_factory $managerfactory Manager factory
|
|
* @param entity_factory $entityfactory Entity factory
|
|
* @param builder_factory $builderfactory Builder factory
|
|
* @param url_factory $urlfactory URL factory
|
|
* @param moodle_page $page Moodle page
|
|
*/
|
|
public function __construct(
|
|
legacy_data_mapper_factory $legacydatamapperfactory,
|
|
exporter_factory $exporterfactory,
|
|
vault_factory $vaultfactory,
|
|
manager_factory $managerfactory,
|
|
entity_factory $entityfactory,
|
|
builder_factory $builderfactory,
|
|
url_factory $urlfactory,
|
|
moodle_page $page
|
|
) {
|
|
$this->legacydatamapperfactory = $legacydatamapperfactory;
|
|
$this->exporterfactory = $exporterfactory;
|
|
$this->vaultfactory = $vaultfactory;
|
|
$this->managerfactory = $managerfactory;
|
|
$this->entityfactory = $entityfactory;
|
|
$this->builderfactory = $builderfactory;
|
|
$this->urlfactory = $urlfactory;
|
|
$this->page = $page;
|
|
$this->rendererbase = $page->get_renderer('mod_forum');
|
|
}
|
|
|
|
/**
|
|
* Create a discussion renderer for the given forum and discussion.
|
|
*
|
|
* @param forum_entity $forum Forum the discussion belongs to
|
|
* @param discussion_entity $discussion Discussion to render
|
|
* @param int $displaymode How should the posts be formatted?
|
|
* @return discussion_renderer
|
|
*/
|
|
public function get_discussion_renderer(
|
|
forum_entity $forum,
|
|
discussion_entity $discussion,
|
|
int $displaymode
|
|
) : discussion_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 = [];
|
|
|
|
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,
|
|
function($discussion, $user, $forum) {
|
|
$exportbuilder = $this->builderfactory->get_exported_discussion_builder();
|
|
return $exportedposts = $exportbuilder->build(
|
|
$user,
|
|
$forum,
|
|
$discussion
|
|
);
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Create a posts renderer to render posts without defined parent/reply relationships.
|
|
*
|
|
* @return posts_renderer
|
|
*/
|
|
public function get_posts_renderer() : posts_renderer {
|
|
return new posts_renderer(
|
|
$this->rendererbase,
|
|
$this->builderfactory->get_exported_posts_builder(),
|
|
'mod_forum/forum_discussion_posts'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Create a posts renderer to render a list of posts in a single discussion.
|
|
*
|
|
* @param int|null $displaymode How should the posts be formatted?
|
|
* @param bool $readonly Should the posts include the actions to reply, delete, etc?
|
|
* @return posts_renderer
|
|
*/
|
|
public function get_single_discussion_posts_renderer(int $displaymode = null, bool $readonly = false) : posts_renderer {
|
|
$exportedpostssorter = $this->entityfactory->get_exported_posts_sorter();
|
|
|
|
switch ($displaymode) {
|
|
case FORUM_MODE_THREADED:
|
|
$template = 'mod_forum/forum_discussion_threaded_posts';
|
|
break;
|
|
case FORUM_MODE_NESTED:
|
|
$template = 'mod_forum/forum_discussion_nested_posts';
|
|
break;
|
|
default;
|
|
$template = 'mod_forum/forum_discussion_posts';
|
|
break;
|
|
}
|
|
|
|
return new posts_renderer(
|
|
$this->rendererbase,
|
|
$this->builderfactory->get_exported_posts_builder(),
|
|
$template,
|
|
// Post process the exported posts for our template. This function will add the "replies"
|
|
// and "hasreplies" properties to the exported posts. It will also sort them into the
|
|
// reply tree structure if the display mode requires it.
|
|
function($exportedposts, $forums) use ($displaymode, $readonly, $exportedpostssorter) {
|
|
$forum = array_shift($forums);
|
|
$seenfirstunread = false;
|
|
$postcount = count($exportedposts);
|
|
$exportedposts = array_map(
|
|
function($exportedpost) use ($forum, $readonly, $seenfirstunread) {
|
|
if ($forum->get_type() == 'single' && !$exportedpost->hasparent) {
|
|
// Remove the author from any posts that don't have a parent.
|
|
unset($exportedpost->author);
|
|
unset($exportedpost->html['authorsubheading']);
|
|
}
|
|
|
|
$exportedpost->firstpost = false;
|
|
$exportedpost->readonly = $readonly;
|
|
$exportedpost->hasreplycount = false;
|
|
$exportedpost->hasreplies = false;
|
|
$exportedpost->replies = [];
|
|
|
|
$exportedpost->isfirstunread = false;
|
|
if (!$seenfirstunread && $exportedpost->unread) {
|
|
$exportedpost->isfirstunread = true;
|
|
$seenfirstunread = true;
|
|
}
|
|
|
|
return $exportedpost;
|
|
},
|
|
$exportedposts
|
|
);
|
|
|
|
if ($displaymode === FORUM_MODE_NESTED || $displaymode === FORUM_MODE_THREADED) {
|
|
$sortedposts = $exportedpostssorter->sort_into_children($exportedposts);
|
|
$sortintoreplies = function($nestedposts) use (&$sortintoreplies) {
|
|
return array_map(function($postdata) use (&$sortintoreplies) {
|
|
[$post, $replies] = $postdata;
|
|
$sortedreplies = $sortintoreplies($replies);
|
|
// Set the parent author name on the replies. This is used for screen
|
|
// readers to help them identify the structure of the discussion.
|
|
$sortedreplies = array_map(function($reply) use ($post) {
|
|
if (isset($post->author)) {
|
|
$reply->parentauthorname = $post->author->fullname;
|
|
} else {
|
|
// The only time the author won't be set is for a single discussion
|
|
// forum. See above for where it gets unset.
|
|
$reply->parentauthorname = get_string('firstpost', 'mod_forum');
|
|
}
|
|
return $reply;
|
|
}, $sortedreplies);
|
|
$post->replies = $sortedreplies;
|
|
$post->hasreplies = !empty($post->replies);
|
|
return $post;
|
|
}, $nestedposts);
|
|
};
|
|
// Set the "replies" property on the exported posts.
|
|
$exportedposts = $sortintoreplies($sortedposts);
|
|
} else if ($displaymode === FORUM_MODE_FLATNEWEST || $displaymode === FORUM_MODE_FLATOLDEST) {
|
|
$exportedfirstpost = array_shift($exportedposts);
|
|
$exportedfirstpost->replies = $exportedposts;
|
|
$exportedfirstpost->hasreplies = true;
|
|
$exportedposts = [$exportedfirstpost];
|
|
}
|
|
|
|
if (!empty($exportedposts)) {
|
|
// Need to identify the first post so that we can use it in behat tests.
|
|
$exportedposts[0]->firstpost = true;
|
|
$exportedposts[0]->hasreplycount = true;
|
|
$exportedposts[0]->replycount = $postcount - 1;
|
|
}
|
|
|
|
return $exportedposts;
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Create a posts renderer to render posts in the forum search results.
|
|
*
|
|
* @param string[] $searchterms The search terms to be highlighted in the posts
|
|
* @return posts_renderer
|
|
*/
|
|
public function get_posts_search_results_renderer(array $searchterms) : posts_renderer {
|
|
$urlfactory = $this->urlfactory;
|
|
|
|
return new posts_renderer(
|
|
$this->rendererbase,
|
|
$this->builderfactory->get_exported_posts_builder(),
|
|
'mod_forum/forum_posts_with_context_links',
|
|
// Post process the exported posts to add the highlighting of the search terms to the post
|
|
// and also the additional context links in the subject.
|
|
function($exportedposts, $forumsbyid, $discussionsbyid) use ($searchterms, $urlfactory) {
|
|
$highlightwords = implode(' ', $searchterms);
|
|
|
|
return array_map(
|
|
function($exportedpost) use (
|
|
$forumsbyid,
|
|
$discussionsbyid,
|
|
$searchterms,
|
|
$highlightwords,
|
|
$urlfactory
|
|
) {
|
|
$discussion = $discussionsbyid[$exportedpost->discussionid];
|
|
$forum = $forumsbyid[$discussion->get_forum_id()];
|
|
|
|
$viewdiscussionurl = $urlfactory->get_discussion_view_url_from_discussion($discussion);
|
|
$exportedpost->urls['viewforum'] = $urlfactory->get_forum_view_url_from_forum($forum)->out(false);
|
|
$exportedpost->urls['viewdiscussion'] = $viewdiscussionurl->out(false);
|
|
$exportedpost->subject = highlight($highlightwords, $exportedpost->subject);
|
|
$exportedpost->forumname = format_string($forum->get_name(), true);
|
|
$exportedpost->discussionname = highlight($highlightwords, format_string($discussion->get_name(), true));
|
|
$exportedpost->showdiscussionname = $forum->get_type() != 'single';
|
|
|
|
// Identify search terms only found in HTML markup, and add a warning about them to
|
|
// the start of the message text. This logic was copied exactly as is from the previous
|
|
// implementation.
|
|
$missingterms = '';
|
|
$exportedpost->message = highlight(
|
|
$highlightwords,
|
|
$exportedpost->message,
|
|
0,
|
|
'<fgw9sdpq4>',
|
|
'</fgw9sdpq4>'
|
|
);
|
|
|
|
foreach ($searchterms as $searchterm) {
|
|
if (
|
|
preg_match("/$searchterm/i", $exportedpost->message) &&
|
|
!preg_match('/<fgw9sdpq4>' . $searchterm . '<\/fgw9sdpq4>/i', $exportedpost->message)
|
|
) {
|
|
$missingterms .= " $searchterm";
|
|
}
|
|
}
|
|
|
|
$exportedpost->message = str_replace('<fgw9sdpq4>', '<span class="highlight">', $exportedpost->message);
|
|
$exportedpost->message = str_replace('</fgw9sdpq4>', '</span>', $exportedpost->message);
|
|
|
|
if ($missingterms) {
|
|
$strmissingsearchterms = get_string('missingsearchterms', 'forum');
|
|
$exportedpost->message = '<p class="highlight2">' . $strmissingsearchterms . ' '
|
|
. $missingterms . '</p>' . $exportedpost->message;
|
|
}
|
|
|
|
return $exportedpost;
|
|
},
|
|
$exportedposts
|
|
);
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Create a posts renderer to render posts in mod/forum/user.php.
|
|
*
|
|
* @param bool $addlinkstocontext Should links to the course, forum, and discussion be included?
|
|
* @return posts_renderer
|
|
*/
|
|
public function get_user_forum_posts_report_renderer(bool $addlinkstocontext) : posts_renderer {
|
|
$urlfactory = $this->urlfactory;
|
|
|
|
return new posts_renderer(
|
|
$this->rendererbase,
|
|
$this->builderfactory->get_exported_posts_builder(),
|
|
'mod_forum/forum_posts_with_context_links',
|
|
function($exportedposts, $forumsbyid, $discussionsbyid) use ($urlfactory, $addlinkstocontext) {
|
|
|
|
return array_map(function($exportedpost) use ($forumsbyid, $discussionsbyid, $addlinkstocontext, $urlfactory) {
|
|
$discussion = $discussionsbyid[$exportedpost->discussionid];
|
|
$forum = $forumsbyid[$discussion->get_forum_id()];
|
|
$courserecord = $forum->get_course_record();
|
|
|
|
if ($addlinkstocontext) {
|
|
$viewdiscussionurl = $urlfactory->get_discussion_view_url_from_discussion($discussion);
|
|
$exportedpost->urls['viewforum'] = $urlfactory->get_forum_view_url_from_forum($forum)->out(false);
|
|
$exportedpost->urls['viewdiscussion'] = $viewdiscussionurl->out(false);
|
|
$exportedpost->urls['viewcourse'] = $urlfactory->get_course_url_from_forum($forum)->out(false);
|
|
}
|
|
|
|
$exportedpost->forumname = format_string($forum->get_name(), true);
|
|
$exportedpost->discussionname = format_string($discussion->get_name(), true);
|
|
$exportedpost->coursename = format_string($courserecord->shortname, true);
|
|
$exportedpost->showdiscussionname = $forum->get_type() != 'single';
|
|
|
|
return $exportedpost;
|
|
}, $exportedposts);
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Create a standard type discussion list renderer.
|
|
*
|
|
* @param forum_entity $forum The forum that the discussions belong to
|
|
* @return discussion_list_renderer
|
|
*/
|
|
public function get_discussion_list_renderer(
|
|
forum_entity $forum
|
|
) : discussion_list_renderer {
|
|
|
|
$capabilitymanager = $this->managerfactory->get_capability_manager($forum);
|
|
$rendererbase = $this->rendererbase;
|
|
$notifications = [];
|
|
|
|
switch ($forum->get_type()) {
|
|
case 'news':
|
|
if (SITEID == $forum->get_course_id()) {
|
|
$template = 'mod_forum/frontpage_news_discussion_list';
|
|
} else {
|
|
$template = 'mod_forum/news_discussion_list';
|
|
}
|
|
break;
|
|
case 'qanda':
|
|
$template = 'mod_forum/qanda_discussion_list';
|
|
break;
|
|
default:
|
|
$template = 'mod_forum/discussion_list';
|
|
}
|
|
|
|
return new discussion_list_renderer(
|
|
$forum,
|
|
$rendererbase,
|
|
$this->legacydatamapperfactory,
|
|
$this->exporterfactory,
|
|
$this->vaultfactory,
|
|
$this->builderfactory,
|
|
$capabilitymanager,
|
|
$this->urlfactory,
|
|
$template,
|
|
$notifications,
|
|
function($discussions, $user, $forum) {
|
|
|
|
$exporteddiscussionsummarybuilder = $this->builderfactory->get_exported_discussion_summaries_builder();
|
|
return $exporteddiscussionsummarybuilder->build(
|
|
$user,
|
|
$forum,
|
|
$discussions
|
|
);
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Create a discussion list renderer which shows more information about the first post.
|
|
*
|
|
* @param forum_entity $forum The forum that the discussions belong to
|
|
* @param string $template The template to use
|
|
* @return discussion_list_renderer
|
|
*/
|
|
private function get_detailed_discussion_list_renderer(
|
|
forum_entity $forum,
|
|
string $template
|
|
) : 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,
|
|
$template,
|
|
$notifications,
|
|
function($discussions, $user, $forum) use ($capabilitymanager) {
|
|
$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();
|
|
$canseeanyprivatereply = $capabilitymanager->can_view_any_private_reply($user);
|
|
$discussionrepliescount = $postvault->get_reply_count_for_discussion_ids(
|
|
$user,
|
|
$discussionentriesids,
|
|
$canseeanyprivatereply
|
|
);
|
|
|
|
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 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 {
|
|
return $this->get_detailed_discussion_list_renderer($forum, 'mod_forum/blog_discussion_list');
|
|
}
|
|
|
|
/**
|
|
* Create a discussion list renderer for the social course format.
|
|
*
|
|
* @param forum_entity $forum The forum that the discussions belong to
|
|
* @return discussion_list_renderer
|
|
*/
|
|
public function get_social_discussion_list_renderer(
|
|
forum_entity $forum
|
|
) : discussion_list_renderer {
|
|
return $this->get_detailed_discussion_list_renderer($forum, 'mod_forum/social_discussion_list');
|
|
}
|
|
|
|
/**
|
|
* Create a discussion list renderer for the social course format.
|
|
*
|
|
* @param forum_entity $forum The forum that the discussions belong to
|
|
* @return discussion_list_renderer
|
|
*/
|
|
public function get_frontpage_news_discussion_list_renderer(
|
|
forum_entity $forum
|
|
) : discussion_list_renderer {
|
|
return $this->get_detailed_discussion_list_renderer($forum, 'mod_forum/frontpage_social_discussion_list');
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
);
|
|
}
|
|
}
|
|
|