Query and pass the favouriting information into the exporter instead of within the exporter itself
}
$missingdataerr = 'Exporter class is missing required related data: (' . get_called_class() . ') ';
+ $scalartypes = ['string', 'int', 'bool', 'float'];
+ $scalarcheck = 'is_' . $classname;
- if ($nullallowed && array_key_exists($key, $related) && $related[$key] === null) {
- $this->related[$key] = $related[$key];
+ if ($nullallowed && (!array_key_exists($key, $related) || $related[$key] === null)) {
+ $this->related[$key] = null;
} else if ($isarray) {
if (array_key_exists($key, $related) && is_array($related[$key])) {
foreach ($related[$key] as $index => $value) {
- if (!$value instanceof $classname) {
+ if (!$value instanceof $classname && !$scalarcheck($value)) {
throw new coding_exception($missingdataerr . $key . ' => ' . $classname . '[]');
}
}
}
} else {
- $scalartypes = ['string', 'int', 'bool', 'float'];
- $scalarcheck = 'is_' . $classname;
if (array_key_exists($key, $related) &&
((in_array($classname, $scalartypes) && $scalarcheck($related[$key])) ||
($related[$key] instanceof $classname))) {
'core:t/edit' => 'fa-cog',
'core:t/emailno' => 'fa-ban',
'core:t/email' => 'fa-envelope-o',
+ 'core:t/emptystar' => 'fa-star-o',
'core:t/enrolusers' => 'fa-user-plus',
'core:t/expanded' => 'fa-caret-down',
'core:t/go' => 'fa-play',
'context' => null,
'aint' => 5,
'astring' => 'valid string',
- 'abool' => false
+ 'abool' => false,
+ 'ints' => []
);
$this->invalidrelated = array(
'simplestdClass' => 'a string',
'context' => null,
'aint' => false,
'astring' => 4,
- 'abool' => 'not a boolean'
+ 'abool' => 'not a boolean',
+ 'ints' => null
);
$this->validdata = array('stringA' => 'A string', 'stringAformat' => FORMAT_HTML, 'intB' => 4);
$result = $exporter->export($output);
}
+ public function test_invalid_related_all_cases() {
+ global $PAGE;
+
+ foreach ($this->invalidrelated as $key => $value) {
+ $data = $this->validrelated;
+ $data[$key] = $value;
+
+ try {
+ $exporter = new core_testable_exporter($this->validdata, $data);
+ $output = $PAGE->get_renderer('core');
+ $result = $exporter->export($output);
+ } catch (coding_exception $e) {
+ $this->assertNotFalse(strpos($e->getMessage(), $key));
+ }
+ }
+ }
+
public function test_valid_data_and_related() {
global $PAGE;
$output = $PAGE->get_renderer('core');
protected static function define_related() {
// We cache the context so it does not need to be retrieved from the course.
return array('simplestdClass' => 'stdClass', 'arrayofstdClass' => 'stdClass[]', 'context' => 'context?',
- 'astring' => 'string', 'abool' => 'bool', 'aint' => 'int');
+ 'astring' => 'string', 'abool' => 'bool', 'aint' => 'int', 'ints' => 'int[]');
}
protected function get_other_values(renderer_base $output) {
* @param {number} forumid
* @param {number} discussionid
* @param {boolean} targetstate
- * @param includetext
* @return {*|Promise}
*/
var setPinDiscussionState = function(forumid, discussionid, targetstate) {
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Exported discussion builder class.
+ *
+ * @package mod_forum
+ * @copyright 2019 Peter Dias<peter@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\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;
+
+/**
+ * Exported discussion builder class
+ *
+ * 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.
+ *
+ * It encapsulates the complexity involved with exporting discussions. 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
+ *
+ * @copyright 2019 Peter Dias<peter@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class exported_discussion {
+ /** @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 any additional variables for the exported discussion 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 $discussion 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_entity $forum,
+ discussion_entity $discussion
+ ) : array {
+
+ $favouriteids = [];
+ if ($this->is_favourited($discussion, $forum->get_context(), $user)) {
+ $favouriteids[] = $discussion->get_id();
+ }
+
+ $groupsbyid = $this->get_groups_available_in_forum($forum);
+ $discussionexporter = $this->exporterfactory->get_discussion_exporter(
+ $user, $forum, $discussion, $groupsbyid, $favouriteids
+ );
+
+ return (array) $discussionexporter->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);
+ }
+
+ /**
+ * Check whether the provided discussion has been favourited by the user.
+ *
+ * @param discussion_entity $discussion The discussion record
+ * @param \context_module $forumcontext Forum context
+ * @param \stdClass $user The user to check the favourite against
+ *
+ * @return bool Whether or not the user has favourited the discussion
+ */
+ public function is_favourited(discussion_entity $discussion, \context_module $forumcontext, \stdClass $user) {
+ $usercontext = \context_user::instance($user->id);
+ $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
+ return $ufservice->favourite_exists('mod_forum', 'discussions', $discussion->get_id(), $forumcontext);
+ }
+
+
+}
$latestposts = $postvault->get_latest_post_id_for_discussion_ids($user, $discussionids, $canseeanyprivatereply);
$unreadcounts = [];
-
+ $favourites = $this->get_favourites($user);
$forumdatamapper = $this->legacydatamapperfactory->get_forum_data_mapper();
$forumrecord = $forumdatamapper->to_legacy_object($forum);
$groupsbyauthorid,
$replycounts,
$unreadcounts,
- $latestposts
+ $latestposts,
+ $favourites
);
return (array) $summaryexporter->export($this->renderer);
}
+ /**
+ * Get a list of all favourited discussions.
+ *
+ * @param stdClass $user The user we are getting favourites for
+ * @return int[] A list of favourited itemids
+ */
+ private function get_favourites(stdClass $user) : array {
+ $usercontext = \context_user::instance($user->id);
+ $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
+ $favourites = $ufservice->find_favourites_by_type('mod_forum', 'discussions');
+ $ids = [];
+ foreach ($favourites as $favourite) {
+ $ids[] = $favourite->itemid;
+ }
+
+ return $ids;
+ }
+
/**
* Get the groups details for all groups available to the forum.
* @param forum_entity $forum The forum entity
public function is_timed_discussion_visible() : bool {
return !$this->is_timed_discussion() || ($this->has_started() && !$this->has_ended());
}
-
- /**
- * Check whether the provided discussion has been favourited by the user.
- *
- * @param discussion $discussion The discussion record
- * @param context $forumcontext Forum context
- * @param \stdClass $user The user to check the favourite against
- *
- * @return bool Whether or not the user has favourited the discussion
- */
- public static function is_favourited(discussion $discussion, \context_module $forumcontext, \stdClass $user) {
- $usercontext = \context_user::instance($user->id);
- $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
- return $ufservice->favourite_exists('mod_forum', 'discussions', $discussion->get_id(), $forumcontext);
- }
}
$capabilitymanager = $this->related['capabilitymanager'];
$urlfactory = $this->related['urlfactory'];
+ $favouriteids = isset($this->related['favouriteids']) ? $this->related['favouriteids'] : [];
$forum = $this->related['forum'];
$forumrecord = $this->get_forum_record();
],
'userstate' => [
'subscribed' => \mod_forum\subscriptions::is_subscribed($user->id, $forumrecord, $discussion->get_id()),
- 'favourited' => $this->is_favourited($discussion, $forum->get_context(), $user),
+ 'favourited' => in_array($discussion->get_id(), $favouriteids) ? true : false,
],
'capabilities' => [
'subscribe' => $capabilitymanager->can_subscribe_to_discussion($user, $discussion),
return $data;
}
- /**
- * Check whether the provided discussion has been favourited by the user.
- *
- * @param discussion $discussion The discussion record
- * @param context $forumcontext Forum context
- * @param \stdClass $user The user to check the favourite against
- *
- * @return bool Whether or not the user has favourited the discussion
- */
- private function is_favourited(discussion_entity $discussion, \context_module $forumcontext, \stdClass $user) {
- $usercontext = \context_user::instance($user->id);
- $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
- return $ufservice->favourite_exists('mod_forum', 'discussions', $discussion->get_id(), $forumcontext);
- }
-
/**
* Get the legacy forum record from the forum entity.
*
'urlfactory' => 'mod_forum\local\factories\url',
'user' => 'stdClass',
'groupsbyid' => 'stdClass[]',
- 'latestpostid' => 'int?'
+ 'latestpostid' => 'int?',
+ 'favouriteids' => 'int[]?'
];
}
}
'capabilitymanager' => 'mod_forum\local\managers\capability',
'urlfactory' => 'mod_forum\local\factories\url',
'user' => 'stdClass',
+ 'favouriteids' => 'int[]?'
];
}
}
'capabilitymanager' => 'mod_forum\local\managers\capability',
'urlfactory' => 'mod_forum\local\factories\url',
'user' => 'stdClass',
+ 'favouriteids' => 'int[]?'
];
}
}
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\builders\exported_discussion as exported_discussion_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;
$this->managerfactory
);
}
+
+ /**
+ * Get an instance of the exported discussion builder.
+ *
+ * @return exported_discussion_summaries_builder
+ */
+ public function get_exported_discussion_builder() : exported_discussion_builder {
+ return new exported_discussion_builder(
+ $this->rendererbase,
+ $this->legacydatamapperfactory,
+ $this->exporterfactory,
+ $this->vaultfactory,
+ $this->managerfactory->get_rating_manager()
+ );
+ }
}
stdClass $user,
forum_entity $forum,
discussion_entity $discussion,
- array $groupsbyid = []
+ array $groupsbyid = [],
+ array $favouriteids = []
) : discussion_exporter {
return new discussion_exporter($discussion, [
'context' => $forum->get_context(),
'user' => $user,
'legacydatamapperfactory' => $this->legacydatamapperfactory,
'latestpostid' => null,
- 'groupsbyid' => $groupsbyid
+ 'groupsbyid' => $groupsbyid,
+ 'favouriteids' => $favouriteids
]);
}
array $groupsbyauthorid = [],
array $discussionreplycount = [],
array $discussionunreadcount = [],
- array $latestpostid = []
+ array $latestpostid = [],
+ array $favourites = []
) : discussion_summaries_exporter {
return new discussion_summaries_exporter(
$discussions,
'capabilitymanager' => $this->managerfactory->get_capability_manager($forum),
'urlfactory' => $this->urlfactory,
'user' => $user,
+ 'favouriteids' => $favourites
]
);
}
$ratingmanager,
$this->entityfactory->get_exported_posts_sorter(),
$baseurl,
- $notifications
+ $notifications,
+ function($discussion, $user, $forum) {
+ $exportbuilder = $this->builderfactory->get_exported_discussion_builder();
+ return $exportedposts = $exportbuilder->build(
+ $user,
+ $forum,
+ $discussion
+ );
+ }
);
}
private $notifications;
/** @var sorter_entity $exportedpostsorter Sorter for the exported posts */
private $exportedpostsorter;
+ /** @var callable $postprocessfortemplate Function to process exported posts before template rendering */
+ private $postprocessfortemplate;
/**
* Constructor.
rating_manager $ratingmanager,
sorter_entity $exportedpostsorter,
moodle_url $baseurl,
- array $notifications = []
+ array $notifications = [],
+ callable $postprocessfortemplate = null
) {
$this->forum = $forum;
$this->discussion = $discussion;
$this->notifications = $notifications;
$this->exportedpostsorter = $exportedpostsorter;
+ $this->postprocessfortemplate = $postprocessfortemplate;
$forumdatamapper = $this->legacydatamapperfactory->get_forum_data_mapper();
$this->forumrecord = $forumdatamapper->to_legacy_object($forum);
$posts = array_merge([$firstpost], array_values($replies));
- $exporteddiscussion = $this->get_exported_discussion($user);
+ if ($this->postprocessfortemplate !== null) {
+ $exporteddiscussion = ($this->postprocessfortemplate) ($this->discussion, $user, $this->forum);
+ } else {
+ $exporteddiscussion = $this->get_exported_discussion($user);
+ }
+
$exporteddiscussion = array_merge($exporteddiscussion, [
'notifications' => $this->get_notifications($user),
'html' => [
$alias = $this->get_table_alias();
$db = $this->get_db();
- list($favsql, $favparams) = $this->get_favourite_sql($user);
- foreach ($favparams as $key => $param) {
- $favsql = str_replace(":$key", "'$param'", $favsql);
+ if ($user) {
+ list($favsql, $favparams) = $this->get_favourite_sql($user);
+ foreach ($favparams as $key => $param) {
+ $favsql = str_replace(":$key", "'$param'", $favsql);
+ }
}
// Fetch:
/**
* Get the standard favouriting sql.
+ *
+ * @param stdClass $user The user we are getting the sql for
+ * @return [$sql, $params] An array comprising of the sql and any associated params
*/
- private function get_favourite_sql($user): array {
+ private function get_favourite_sql(stdClass $user): array {
$usercontext = \context_user::instance($user->id);
$alias = $this->get_table_alias();
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
$managerfactory = mod_forum\local\container::get_manager_factory();
$capabilitymanager = $managerfactory->get_capability_manager($forum);
-
-
// Does the user have the ability to favourite the discussion?
if (!$capabilitymanager->can_favourite_discussion($USER, $discussion)) {
throw new moodle_exception('cannotfavourite', 'forum');
}
$exporterfactory = mod_forum\local\container::get_exporter_factory();
- $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion);
+ $builder = mod_forum\local\container::get_builder_factory()->get_exported_discussion_builder();
+ $favourited = ($builder->is_favourited($discussion, $forumcontext, $USER) ? [$discussion->get_id()] : []);
+ $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion, [], $favourited);
return $exporter->export($PAGE->get_renderer('mod_forum'));
}
'mod_forum:t/selected' => 'fa-check',
'mod_forum:t/subscribed' => 'fa-envelope-o',
'mod_forum:t/unsubscribed' => 'fa-envelope-open-o',
- 'mod_forum:t/emptystar' => 'fa-star-o',
'mod_forum:t/star' => 'fa-star',
];
}
{{#pix}}t/star, mod_forum, {{#str}}removefromfavourites, mod_forum{{/str}}{{/pix}}
{{/userstate.favourited}}
{{^userstate.favourited}}
- {{#pix}}t/emptystar, mod_forum, {{#str}}addtofavourites, mod_forum{{/str}}{{/pix}}
+ {{#pix}}t/emptystar, core, {{#str}}addtofavourites, mod_forum{{/str}}{{/pix}}
{{/userstate.favourited}}
{{/favouritecontent}}
{{/ mod_forum/discussion_favourite_toggle }}
$this->assertEquals(0, $exporteddiscussion->times['end']);
$this->assertEquals($group->name, $exporteddiscussion->group['name']);
}
-
- public function test_is_favourited() {
- $this->resetAfterTest(true);
-
- $time = time() + 10;
- // Create a user.
- $user = self::getDataGenerator()->create_user(array('trackforums' => 1));
-
- // Set to the user.
- self::setUser($user);
-
- // Create courses to add the modules.
- $course1 = self::getDataGenerator()->create_course();
- $this->getDataGenerator()->enrol_user($user->id, $course1->id);
-
- $record = new stdClass();
- $record->introformat = FORMAT_HTML;
- $record->course = $course1->id;
- $record->trackingtype = FORUM_TRACKING_OFF;
- $forum1 = self::getDataGenerator()->create_module('forum', $record);
-
- $discussion = new discussion_entity(
- 1,
- $course1->id,
- $forum1->id,
- 'test discussion',
- 4,
- 5,
- 6,
- false,
- $time,
- $time,
- 0,
- 0,
- false
- );
- $coursemodule = get_coursemodule_from_instance('forum', $forum1->id);
- $contextmodule = context_module::instance($coursemodule->id);
-
- $this->assertFalse(\mod_forum\local\entities\discussion::is_favourited($discussion, $contextmodule, $user));
-
- // Toggle the favourite for discussion.
- $usercontext = \context_user::instance($user->id);
- $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
- $ufservice->create_favourite('mod_forum', 'discussions', $discussion->get_id(), $contextmodule);
-
- $this->assertTrue(\mod_forum\local\entities\discussion::is_favourited($discussion, $contextmodule, $user));
- }
}
$record->forum = $forum1->id;
$discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
- $response = mod_forum_external::toggle_favourite_state($forum1->id, $discussion1->id, 1);
+ $response = mod_forum_external::toggle_favourite_state($discussion1->id, 1);
$response = external_api::clean_returnvalue(mod_forum_external::toggle_favourite_state_returns(), $response);
$this->assertTrue($response['userstate']['favourited']);
- $response = mod_forum_external::toggle_favourite_state($forum1->id, $discussion1->id, 0);
+ $response = mod_forum_external::toggle_favourite_state($discussion1->id, 0);
$response = external_api::clean_returnvalue(mod_forum_external::toggle_favourite_state_returns(), $response);
$this->assertFalse($response['userstate']['favourited']);
}
$discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
try {
- $response = mod_forum_external::set_pin_state($forum1->id, $discussion1->id, 1);
+ $response = mod_forum_external::set_pin_state($discussion1->id, 1);
} catch (Exception $e) {
$this->assertEquals('cannotpindiscussions', $e->errorcode);
}
self::setAdminUser();
- $response = mod_forum_external::set_pin_state($forum1->id, $discussion1->id, 1);
+ $response = mod_forum_external::set_pin_state($discussion1->id, 1);
$response = external_api::clean_returnvalue(mod_forum_external::set_pin_state_returns(), $response);
$this->assertTrue($response['pinned']);
- $response = mod_forum_external::set_pin_state($forum1->id, $discussion1->id, 0);
+ $response = mod_forum_external::set_pin_state($discussion1->id, 0);
$response = external_api::clean_returnvalue(mod_forum_external::set_pin_state_returns(), $response);
$this->assertFalse($response['pinned']);
}
// Check default values for capabilities.
$enabledcaps = array('canviewdiscussion', 'canstartdiscussion', 'canreplypost', 'canviewrating', 'cancreateattachment',
- 'canexportownpost', 'candeleteownpost', 'canallowforcesubscribe');
+ 'canexportownpost', 'cancantogglefavourite', 'candeleteownpost', 'canallowforcesubscribe');
unset($result['warnings']);
foreach ($result as $capname => $capvalue) {