Merge branch 'mod_forum-discussion-poc' of https://github.com/ryanwyllie/moodle
This commit is contained in:
@@ -4,122 +4,124 @@ Feature: Change number of discussions displayed
|
||||
As a teacher
|
||||
I need to edit the course and change the number of sections displayed.
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category | format |
|
||||
| Course 1 | C1 | 0 | social |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I press "Add a new discussion topic"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Forum Post 10 |
|
||||
| Message | This is forum post ten |
|
||||
And I press "Post to forum"
|
||||
And I wait to be redirected
|
||||
And I am on "Course 1" course homepage
|
||||
And I wait "1" seconds
|
||||
And I press "Add a new discussion topic"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Forum Post 9 |
|
||||
| Message | This is forum post nine |
|
||||
And I press "Post to forum"
|
||||
And I wait to be redirected
|
||||
And I am on "Course 1" course homepage
|
||||
And I wait "1" seconds
|
||||
And I press "Add a new discussion topic"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Forum Post 8 |
|
||||
| Message | This is forum post eight |
|
||||
And I press "Post to forum"
|
||||
And I wait to be redirected
|
||||
And I am on "Course 1" course homepage
|
||||
And I wait "1" seconds
|
||||
And I press "Add a new discussion topic"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Forum Post 7 |
|
||||
| Message | This is forum post seven |
|
||||
And I press "Post to forum"
|
||||
And I wait to be redirected
|
||||
And I am on "Course 1" course homepage
|
||||
And I wait "1" seconds
|
||||
And I press "Add a new discussion topic"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Forum Post 6 |
|
||||
| Message | This is forum post six |
|
||||
And I press "Post to forum"
|
||||
And I wait to be redirected
|
||||
And I am on "Course 1" course homepage
|
||||
And I wait "1" seconds
|
||||
And I press "Add a new discussion topic"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Forum Post 5 |
|
||||
| Message | This is forum post five |
|
||||
And I press "Post to forum"
|
||||
And I wait to be redirected
|
||||
And I am on "Course 1" course homepage
|
||||
And I wait "1" seconds
|
||||
And I press "Add a new discussion topic"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Forum Post 4 |
|
||||
| Message | This is forum post four |
|
||||
And I press "Post to forum"
|
||||
And I wait to be redirected
|
||||
And I am on "Course 1" course homepage
|
||||
And I wait "1" seconds
|
||||
And I press "Add a new discussion topic"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Forum Post 3 |
|
||||
| Message | This is forum post three |
|
||||
And I press "Post to forum"
|
||||
And I wait to be redirected
|
||||
And I am on "Course 1" course homepage
|
||||
And I wait "1" seconds
|
||||
And I press "Add a new discussion topic"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Forum Post 2 |
|
||||
| Message | This is forum post two |
|
||||
And I press "Post to forum"
|
||||
And I wait to be redirected
|
||||
And I am on "Course 1" course homepage
|
||||
And I wait "1" seconds
|
||||
And I press "Add a new discussion topic"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Forum Post 1 |
|
||||
| Message | This is forum post one |
|
||||
And I press "Post to forum"
|
||||
And I wait to be redirected
|
||||
And I am on "Course 1" course homepage
|
||||
|
||||
Scenario: When number of discussions is decreased fewer discussions appear
|
||||
Given I navigate to "Edit settings" in current page administration
|
||||
And I set the following fields to these values:
|
||||
| numdiscussions | 5 |
|
||||
When I press "Save and display"
|
||||
Then I should see "This is forum post one"
|
||||
And I should see "This is forum post five"
|
||||
And I should not see "This is forum post six"
|
||||
|
||||
Scenario: When number of discussions is decreased to less than 1 only 1 discussion should appear
|
||||
Given I navigate to "Edit settings" in current page administration
|
||||
And I set the following fields to these values:
|
||||
| numdiscussions | -1 |
|
||||
When I press "Save and display"
|
||||
Then I should see "This is forum post one"
|
||||
And I should not see "This is forum post two"
|
||||
And I should not see "This is forum post ten"
|
||||
|
||||
Scenario: When number of discussions is increased more discussions appear
|
||||
Given I navigate to "Edit settings" in current page administration
|
||||
And I set the following fields to these values:
|
||||
| numdiscussions | 9 |
|
||||
When I press "Save and display"
|
||||
Then I should see "This is forum post one"
|
||||
And I should see "This is forum post five"
|
||||
And I should see "This is forum post nine"
|
||||
And I should not see "This is forum post ten"
|
||||
# Uncomment these tests when the forum_print_latest_discussions function is
|
||||
# deprecated in MDL-65082.
|
||||
# Background:
|
||||
# Given the following "users" exist:
|
||||
# | username | firstname | lastname | email |
|
||||
# | teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
# And the following "courses" exist:
|
||||
# | fullname | shortname | category | format |
|
||||
# | Course 1 | C1 | 0 | social |
|
||||
# And the following "course enrolments" exist:
|
||||
# | user | course | role |
|
||||
# | teacher1 | C1 | editingteacher |
|
||||
# And I log in as "teacher1"
|
||||
# And I am on "Course 1" course homepage
|
||||
# And I press "Add a new discussion topic"
|
||||
# And I set the following fields to these values:
|
||||
# | Subject | Forum Post 10 |
|
||||
# | Message | This is forum post ten |
|
||||
# And I press "Post to forum"
|
||||
# And I wait to be redirected
|
||||
# And I am on "Course 1" course homepage
|
||||
# And I wait "1" seconds
|
||||
# And I press "Add a new discussion topic"
|
||||
# And I set the following fields to these values:
|
||||
# | Subject | Forum Post 9 |
|
||||
# | Message | This is forum post nine |
|
||||
# And I press "Post to forum"
|
||||
# And I wait to be redirected
|
||||
# And I am on "Course 1" course homepage
|
||||
# And I wait "1" seconds
|
||||
# And I press "Add a new discussion topic"
|
||||
# And I set the following fields to these values:
|
||||
# | Subject | Forum Post 8 |
|
||||
# | Message | This is forum post eight |
|
||||
# And I press "Post to forum"
|
||||
# And I wait to be redirected
|
||||
# And I am on "Course 1" course homepage
|
||||
# And I wait "1" seconds
|
||||
# And I press "Add a new discussion topic"
|
||||
# And I set the following fields to these values:
|
||||
# | Subject | Forum Post 7 |
|
||||
# | Message | This is forum post seven |
|
||||
# And I press "Post to forum"
|
||||
# And I wait to be redirected
|
||||
# And I am on "Course 1" course homepage
|
||||
# And I wait "1" seconds
|
||||
# And I press "Add a new discussion topic"
|
||||
# And I set the following fields to these values:
|
||||
# | Subject | Forum Post 6 |
|
||||
# | Message | This is forum post six |
|
||||
# And I press "Post to forum"
|
||||
# And I wait to be redirected
|
||||
# And I am on "Course 1" course homepage
|
||||
# And I wait "1" seconds
|
||||
# And I press "Add a new discussion topic"
|
||||
# And I set the following fields to these values:
|
||||
# | Subject | Forum Post 5 |
|
||||
# | Message | This is forum post five |
|
||||
# And I press "Post to forum"
|
||||
# And I wait to be redirected
|
||||
# And I am on "Course 1" course homepage
|
||||
# And I wait "1" seconds
|
||||
# And I press "Add a new discussion topic"
|
||||
# And I set the following fields to these values:
|
||||
# | Subject | Forum Post 4 |
|
||||
# | Message | This is forum post four |
|
||||
# And I press "Post to forum"
|
||||
# And I wait to be redirected
|
||||
# And I am on "Course 1" course homepage
|
||||
# And I wait "1" seconds
|
||||
# And I press "Add a new discussion topic"
|
||||
# And I set the following fields to these values:
|
||||
# | Subject | Forum Post 3 |
|
||||
# | Message | This is forum post three |
|
||||
# And I press "Post to forum"
|
||||
# And I wait to be redirected
|
||||
# And I am on "Course 1" course homepage
|
||||
# And I wait "1" seconds
|
||||
# And I press "Add a new discussion topic"
|
||||
# And I set the following fields to these values:
|
||||
# | Subject | Forum Post 2 |
|
||||
# | Message | This is forum post two |
|
||||
# And I press "Post to forum"
|
||||
# And I wait to be redirected
|
||||
# And I am on "Course 1" course homepage
|
||||
# And I wait "1" seconds
|
||||
# And I press "Add a new discussion topic"
|
||||
# And I set the following fields to these values:
|
||||
# | Subject | Forum Post 1 |
|
||||
# | Message | This is forum post one |
|
||||
# And I press "Post to forum"
|
||||
# And I wait to be redirected
|
||||
# And I am on "Course 1" course homepage
|
||||
#
|
||||
# Scenario: When number of discussions is decreased fewer discussions appear
|
||||
# Given I navigate to "Edit settings" in current page administration
|
||||
# And I set the following fields to these values:
|
||||
# | numdiscussions | 5 |
|
||||
# When I press "Save and display"
|
||||
# Then I should see "This is forum post one"
|
||||
# And I should see "This is forum post five"
|
||||
# And I should not see "This is forum post six"
|
||||
#
|
||||
# Scenario: When number of discussions is decreased to less than 1 only 1 discussion should appear
|
||||
# Given I navigate to "Edit settings" in current page administration
|
||||
# And I set the following fields to these values:
|
||||
# | numdiscussions | -1 |
|
||||
# When I press "Save and display"
|
||||
# Then I should see "This is forum post one"
|
||||
# And I should not see "This is forum post two"
|
||||
# And I should not see "This is forum post ten"
|
||||
#
|
||||
# Scenario: When number of discussions is increased more discussions appear
|
||||
# Given I navigate to "Edit settings" in current page administration
|
||||
# And I set the following fields to these values:
|
||||
# | numdiscussions | 9 |
|
||||
# When I press "Save and display"
|
||||
# Then I should see "This is forum post one"
|
||||
# And I should see "This is forum post five"
|
||||
# And I should see "This is forum post nine"
|
||||
# And I should not see "This is forum post ten"
|
||||
|
||||
@@ -22,14 +22,14 @@ Feature: Managers can create courses
|
||||
And I add the "Latest announcements" block
|
||||
Then "Latest announcements" "block" should exist
|
||||
And I follow "Announcements"
|
||||
And "Add a new topic" "button" should exist
|
||||
And "Add a new topic" "link" should exist
|
||||
And "Subscription mode > Forced subscription" "link" should not exist in current page administration
|
||||
And "Subscription mode > Forced subscription" "text" should exist in current page administration
|
||||
And I log out
|
||||
And I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Announcements"
|
||||
And "Add a new topic" "button" should not exist
|
||||
And "Add a new topic" "link" should not exist
|
||||
And "Forced subscription" "text" should exist in current page administration
|
||||
|
||||
Scenario: Create a course from the management interface and return to it
|
||||
|
||||
@@ -54,6 +54,7 @@ class stored_file_exporter extends \core\external\exporter {
|
||||
$data->filepath = $file->get_filepath();
|
||||
$data->filename = $file->get_filename();
|
||||
$data->isdir = $file->is_directory();
|
||||
$data->isimage = $file->is_valid_image();
|
||||
$data->timemodified = $file->get_timemodified();
|
||||
$data->timecreated = $file->get_timecreated();
|
||||
$data->filesize = $file->get_filesize();
|
||||
@@ -94,6 +95,9 @@ class stored_file_exporter extends \core\external\exporter {
|
||||
'isdir' => array(
|
||||
'type' => PARAM_BOOL
|
||||
),
|
||||
'isimage' => array(
|
||||
'type' => PARAM_BOOL
|
||||
),
|
||||
'timemodified' => array(
|
||||
'type' => PARAM_INT
|
||||
),
|
||||
|
||||
Vendored
+20
-4
@@ -261,16 +261,32 @@ abstract class exporter {
|
||||
final public static function read_properties_definition() {
|
||||
$properties = static::properties_definition();
|
||||
$customprops = static::define_other_properties();
|
||||
foreach ($customprops as $property => $definition) {
|
||||
$customprops = static::format_properties($customprops);
|
||||
$properties += $customprops;
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively formats a given property definition with the default fields required.
|
||||
*
|
||||
* @param array $properties List of properties to format
|
||||
* @return array Formatted array
|
||||
*/
|
||||
final public static function format_properties($properties) {
|
||||
foreach ($properties as $property => $definition) {
|
||||
// Ensures that null is set to its default.
|
||||
if (!isset($definition['null'])) {
|
||||
$customprops[$property]['null'] = NULL_NOT_ALLOWED;
|
||||
$properties[$property]['null'] = NULL_NOT_ALLOWED;
|
||||
}
|
||||
if (!isset($definition['description'])) {
|
||||
$customprops[$property]['description'] = $property;
|
||||
$properties[$property]['description'] = $property;
|
||||
}
|
||||
|
||||
// If an array is provided, it may be a nested array that is unformatted so rinse and repeat.
|
||||
if (is_array($definition['type'])) {
|
||||
$properties[$property]['type'] = static::format_properties($definition['type']);
|
||||
}
|
||||
}
|
||||
$properties += $customprops;
|
||||
return $properties;
|
||||
}
|
||||
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Paged Content exporter.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core\external;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use renderer_base;
|
||||
|
||||
/**
|
||||
* Paged Content exporter.
|
||||
*
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class paged_content_exporter extends exporter {
|
||||
/** @var int pagesize The number of records to show on each page */
|
||||
private $pagesize;
|
||||
|
||||
/** @var int pagenumber The current page number */
|
||||
private $pagenumber;
|
||||
|
||||
/** @var int recordcount The total number of records available */
|
||||
private $recordcount;
|
||||
|
||||
/** @var callable The callback to use to determine a page URL */
|
||||
private $pageurlcallback;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $pagesize The number of records to show on each page
|
||||
* @param int $pagenumber The current page number
|
||||
* @param int $recordcount The total number of records available
|
||||
* @param callable $pageurlcallback The callback to use to determine a page URL
|
||||
* @param array $related List of related elements
|
||||
*/
|
||||
public function __construct(int $pagesize, int $pagenumber, int $recordcount, callable $pageurlcallback, array $related = []) {
|
||||
$this->pagesize = $pagesize;
|
||||
$this->pagenumber = $pagenumber;
|
||||
$this->recordcount = $recordcount;
|
||||
$this->pageurlcallback = $pageurlcallback;
|
||||
|
||||
return parent::__construct([], $related);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of additional properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_other_properties() {
|
||||
return [
|
||||
'itemsperpage' => ['type' => PARAM_INT],
|
||||
'buttons' => [
|
||||
'type' => [
|
||||
'first' => ['type' => PARAM_BOOL],
|
||||
'previous' => ['type' => PARAM_BOOL],
|
||||
'next' => ['type' => PARAM_BOOL],
|
||||
'last' => ['type' => PARAM_BOOL],
|
||||
],
|
||||
],
|
||||
'pages' => [
|
||||
'multiple' => true,
|
||||
'type' => [
|
||||
'page' => ['type' => PARAM_INT],
|
||||
'url' => ['type' => PARAM_URL],
|
||||
'active' => ['type' => PARAM_BOOL],
|
||||
'content' => [
|
||||
'optional' => true,
|
||||
'type' => PARAM_RAW,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the additional values to inject while exporting.
|
||||
*
|
||||
* @param renderer_base $output The renderer.
|
||||
* @return array Keys are the property names, values are their values.
|
||||
*/
|
||||
protected function get_other_values(renderer_base $output) {
|
||||
$pagecount = ceil($this->recordcount / $this->pagesize);
|
||||
|
||||
$pages = [];
|
||||
if ($pagecount > 1) {
|
||||
for ($pageno = 1; $pageno <= $pagecount; $pageno++) {
|
||||
$pages[] = [
|
||||
'page' => $pageno,
|
||||
'url' => call_user_func_array($this->pageurlcallback, [$pageno, $this->pagesize]),
|
||||
'active' => $pageno === $this->pagenumber,
|
||||
'content' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'itemsperpage' => $this->pagesize,
|
||||
'buttons' => [
|
||||
'first' => false,
|
||||
'previous' => false,
|
||||
'next' => false,
|
||||
'last' => false,
|
||||
],
|
||||
'pages' => $pages,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -828,6 +828,65 @@ abstract class moodle_database {
|
||||
return array($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SELECT SQL to preload columns for the specified fieldlist and table alias.
|
||||
*
|
||||
* This function is intended to be used in combination with get_preload_columns_sql and extract_from_fields.
|
||||
*
|
||||
* @param array $fieldlist The list of fields from get_preload_columns
|
||||
* @param string $tablealias The table alias used in the FROM/JOIN field
|
||||
* @return string The SQL to use in the SELECT
|
||||
*/
|
||||
public function get_preload_columns_sql(array $fieldlist, string $tablealias) : string {
|
||||
return implode(', ', array_map(function($fieldname, $alias) use ($tablealias) {
|
||||
return "{$tablealias}.{$fieldname} AS {$alias}";
|
||||
}, $fieldlist, array_keys($fieldlist)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract fields from the specified data.
|
||||
* The fields are removed from the original object.
|
||||
*
|
||||
* This function is intended to be used in combination with get_preload_columns and get_preload_columns_sql.
|
||||
*
|
||||
* @param array $fieldlist The list of fields from get_preload_columns
|
||||
* @param \stdClass $data The data retrieved from the database with fields to be extracted
|
||||
* @return string The SQL to use in the SELECT
|
||||
*/
|
||||
public function extract_fields_from_object(array $fieldlist, \stdClass $data) : \stdClass {
|
||||
$newdata = (object) [];
|
||||
foreach ($fieldlist as $alias => $fieldname) {
|
||||
if (property_exists($data, $alias)) {
|
||||
$newdata->$fieldname = $data->$alias;
|
||||
unset($data->$alias);
|
||||
} else {
|
||||
debugging("Field '{$fieldname}' not found", DEBUG_DEVELOPER);
|
||||
}
|
||||
}
|
||||
|
||||
return $newdata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the preload columns for the specified table and use the specified prefix in the column alias.
|
||||
*
|
||||
* This function is intended to be used in combination with get_preload_columns_sql and extract_from_fields.
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $prefix
|
||||
* @return array The list of columns in a table. The array key is the column name with an applied prefix.
|
||||
*/
|
||||
public function get_preload_columns(string $table, string $prefix) : array {
|
||||
global $DB;
|
||||
|
||||
$fields = [];
|
||||
foreach (array_keys($this->get_columns($table)) as $fieldname) {
|
||||
$fields["{$prefix}{$fieldname}"] = $fieldname;
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts short table name {tablename} to the real prefixed table name in given sql.
|
||||
* @param string $sql The sql to be operated on.
|
||||
|
||||
@@ -794,6 +794,136 @@ class core_dml_testcase extends database_driver_testcase {
|
||||
$this->assertFalse($columns['id']->auto_increment);
|
||||
}
|
||||
|
||||
public function test_get_preload_columns() {
|
||||
$DB = $this->tdb;
|
||||
$dbman = $this->tdb->get_manager();
|
||||
|
||||
$table = $this->get_test_table();
|
||||
$tablename = $table->getName();
|
||||
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, 'lala');
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
$dbman->create_table($table);
|
||||
|
||||
$expected = [
|
||||
'aid' => 'id',
|
||||
'acourse' => 'course',
|
||||
'aname' => 'name',
|
||||
];
|
||||
$columns = $DB->get_preload_columns($tablename, 'a');
|
||||
$this->assertCount(3, $columns);
|
||||
$this->assertEquals($expected, $columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that get_preload_columns_sql works as expected.
|
||||
*
|
||||
* @dataProvider get_preload_columns_sql_provider
|
||||
* @param array $fieldlist The list of fields
|
||||
* @param string $tablealias The alias to use
|
||||
* @param string $expected The string to match
|
||||
*/
|
||||
public function test_get_preload_columns_sql(array $fieldlist, string $tablealias, string $expected) {
|
||||
$this->assertEquals($expected, $this->tdb->get_preload_columns_sql($fieldlist, $tablealias));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for get_preload_columns_sql tests.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_preload_columns_sql_provider() : array {
|
||||
return [
|
||||
'single field' => [
|
||||
[
|
||||
'xid' => 'id',
|
||||
],
|
||||
'x',
|
||||
'x.id AS xid',
|
||||
],
|
||||
'multiple fields' => [
|
||||
[
|
||||
'bananaid' => 'id',
|
||||
'bananacourse' => 'course',
|
||||
'bananafoo' => 'foo',
|
||||
],
|
||||
'banana',
|
||||
'banana.id AS bananaid, banana.course AS bananacourse, banana.foo AS bananafoo',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that extract_fields_from_object works as expected.
|
||||
*
|
||||
* @dataProvider extract_fields_from_object_provider
|
||||
* @param array $fieldlist The list of fields
|
||||
* @param stdClass $in Input values for the test
|
||||
* @param stdClass $out The expected output
|
||||
* @param stdClass $modified Expected value of $in after it's been modified
|
||||
*/
|
||||
public function test_extract_fields_from_object(array $fieldlist, \stdClass $in, \stdClass $out, \stdClass $modified) {
|
||||
$result = $this->tdb->extract_fields_from_object($fieldlist, $in);
|
||||
$this->assertEquals($out, $result);
|
||||
$this->assertEquals($modified, $in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for extract_fields_from_object tests.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function extract_fields_from_object_provider() : array {
|
||||
return [
|
||||
'single table' => [
|
||||
[
|
||||
'sid' => 'id',
|
||||
'scourse' => 'course',
|
||||
'sflag' => 'flag',
|
||||
],
|
||||
(object) [
|
||||
'sid' => 1,
|
||||
'scourse' => 42,
|
||||
'sflag' => 'foo',
|
||||
],
|
||||
(object) [
|
||||
'id' => 1,
|
||||
'course' => 42,
|
||||
'flag' => 'foo',
|
||||
],
|
||||
(object) [
|
||||
],
|
||||
],
|
||||
'single table amongst others' => [
|
||||
[
|
||||
'sid' => 'id',
|
||||
'scourse' => 'course',
|
||||
'sflag' => 'flag',
|
||||
],
|
||||
(object) [
|
||||
'sid' => 1,
|
||||
'scourse' => 42,
|
||||
'sflag' => 'foo',
|
||||
'oid' => 'id',
|
||||
'ocourse' => 'course',
|
||||
'oflag' => 'flag',
|
||||
],
|
||||
(object) [
|
||||
'id' => 1,
|
||||
'course' => 42,
|
||||
'flag' => 'foo',
|
||||
],
|
||||
(object) [
|
||||
'oid' => 'id',
|
||||
'ocourse' => 'course',
|
||||
'oflag' => 'flag',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function test_get_manager() {
|
||||
$DB = $this->tdb;
|
||||
$dbman = $this->tdb->get_manager();
|
||||
|
||||
@@ -598,7 +598,7 @@ class file_storage {
|
||||
* @param int $contextid context ID
|
||||
* @param string $component component
|
||||
* @param mixed $filearea file area/s, you cannot specify multiple fileareas as well as an itemid
|
||||
* @param int $itemid item ID or all files if not specified
|
||||
* @param int|int[]|false $itemid item ID(s) or all files if not specified
|
||||
* @param string $sort A fragment of SQL to use for sorting
|
||||
* @param bool $includedirs whether or not include directories
|
||||
* @param int $updatedsince return files updated since this time
|
||||
@@ -617,8 +617,10 @@ class file_storage {
|
||||
if ($itemid !== false && is_array($filearea)) {
|
||||
throw new coding_exception('You cannot specify multiple fileareas as well as an itemid.');
|
||||
} else if ($itemid !== false) {
|
||||
$itemidsql = ' AND f.itemid = :itemid ';
|
||||
$conditions['itemid'] = $itemid;
|
||||
$itemids = is_array($itemid) ? $itemid : [$itemid];
|
||||
list($itemidinorequalsql, $itemidconditions) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED);
|
||||
$itemidsql = " AND f.itemid {$itemidinorequalsql}";
|
||||
$conditions = array_merge($conditions, $itemidconditions);
|
||||
} else {
|
||||
$itemidsql = '';
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ Feature: Using the activity grade form element
|
||||
And I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test forum name"
|
||||
And I press "Add a new discussion topic"
|
||||
And I click on "Add a new discussion topic" "link"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Discussion subject |
|
||||
| Message | Discussion message |
|
||||
@@ -170,7 +170,7 @@ Feature: Using the activity grade form element
|
||||
And I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test forum name"
|
||||
And I press "Add a new discussion topic"
|
||||
And I click on "Add a new discussion topic" "link"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Discussion subject |
|
||||
| Message | Discussion message |
|
||||
|
||||
@@ -200,3 +200,9 @@ define('PORTFOLIO_ADD_TEXT_LINK', 4);
|
||||
* this replaces the old portfolio_fake_add_url function
|
||||
*/
|
||||
define('PORTFOLIO_ADD_FAKE_URL', 5);
|
||||
|
||||
/**
|
||||
* PORTFOLIO_ADD_MOODULE_URL - hacky way to turn the button class into a moodle_url to redirect to
|
||||
* this replaces the old portfolio_fake_add_url function
|
||||
*/
|
||||
define('PORTFOLIO_ADD_MOODLE_URL', 6);
|
||||
|
||||
@@ -259,7 +259,7 @@ class portfolio_add_button {
|
||||
* Optional, defaults to PORTFOLIO_ADD_FULL_FORM
|
||||
* @param string $addstr string to use for the button or icon alt text or link text.
|
||||
* This is whole string, not key. optional, defaults to 'Add to portfolio';
|
||||
* @return void|string
|
||||
* @return void|string|moodle_url
|
||||
*/
|
||||
public function to_html($format=null, $addstr=null) {
|
||||
global $CFG, $COURSE, $OUTPUT, $USER;
|
||||
@@ -327,6 +327,11 @@ class portfolio_add_button {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If we just want a moodle_url to redirect to, do it now.
|
||||
if ($format == PORTFOLIO_ADD_MOODLE_URL) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
// if we just want a url to redirect to, do it now
|
||||
if ($format == PORTFOLIO_ADD_FAKE_URL) {
|
||||
return $url->out(false);
|
||||
|
||||
@@ -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}}
|
||||
@@ -40,55 +40,55 @@ Feature: Context freezing apply to child contexts
|
||||
Given I log in as "admin"
|
||||
And I am on "courseaa1" course homepage
|
||||
And I follow "faa1"
|
||||
And "Add a new discussion topic" "button" should exist
|
||||
And "Add a new discussion topic" "link" should exist
|
||||
When I follow "Freeze this context"
|
||||
And I click on "Continue" "button"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
When I am on "courseaa1" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "faa1b"
|
||||
Then "Add a new discussion topic" "button" should exist
|
||||
Then "Add a new discussion topic" "link" should exist
|
||||
When I am on "courseaa2" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "faa2"
|
||||
Then "Add a new discussion topic" "button" should exist
|
||||
Then "Add a new discussion topic" "link" should exist
|
||||
When I am on "courseb" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "fb"
|
||||
Then "Add a new discussion topic" "button" should exist
|
||||
Then "Add a new discussion topic" "link" should exist
|
||||
|
||||
And I log out
|
||||
When I log in as "teacher"
|
||||
And I am on "courseaa1" course homepage
|
||||
And I follow "faa1"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
When I am on "courseaa1" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "faa1b"
|
||||
Then "Add a new discussion topic" "button" should exist
|
||||
Then "Add a new discussion topic" "link" should exist
|
||||
When I am on "courseaa2" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "faa2"
|
||||
Then "Add a new discussion topic" "button" should exist
|
||||
Then "Add a new discussion topic" "link" should exist
|
||||
When I am on "courseb" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "fb"
|
||||
And "Add a new discussion topic" "button" should exist
|
||||
And "Add a new discussion topic" "link" should exist
|
||||
|
||||
And I log out
|
||||
When I log in as "student1"
|
||||
And I am on "courseaa1" course homepage
|
||||
And I follow "faa1"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
When I am on "courseaa1" course homepage
|
||||
When I follow "faa1b"
|
||||
Then "Add a new discussion topic" "button" should exist
|
||||
Then "Add a new discussion topic" "link" should exist
|
||||
When I am on "courseaa2" course homepage
|
||||
When I follow "faa2"
|
||||
Then "Add a new discussion topic" "button" should exist
|
||||
Then "Add a new discussion topic" "link" should exist
|
||||
When I am on "courseb" course homepage
|
||||
When I follow "fb"
|
||||
Then "Add a new discussion topic" "button" should exist
|
||||
Then "Add a new discussion topic" "link" should exist
|
||||
|
||||
Scenario: Freeze course should freeze all children
|
||||
Given I log in as "admin"
|
||||
@@ -97,54 +97,54 @@ Feature: Context freezing apply to child contexts
|
||||
When I follow "Freeze this context"
|
||||
And I click on "Continue" "button"
|
||||
Then I should not see "Turn editing on"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
When I am on "courseaa1" course homepage
|
||||
Then I should not see "Turn editing on"
|
||||
And "Unfreeze this context" "link" should exist in current page administration
|
||||
When I follow "faa1b"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
And "Unfreeze this context" "link" should not exist in current page administration
|
||||
When I am on "courseaa2" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "faa2"
|
||||
Then "Add a new discussion topic" "button" should exist
|
||||
Then "Add a new discussion topic" "link" should exist
|
||||
When I am on "courseb" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "fb"
|
||||
Then "Add a new discussion topic" "button" should exist
|
||||
Then "Add a new discussion topic" "link" should exist
|
||||
|
||||
And I log out
|
||||
When I log in as "teacher"
|
||||
And I am on "courseaa1" course homepage
|
||||
And I follow "faa1"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
When I am on "courseaa1" course homepage
|
||||
Then I should not see "Turn editing on"
|
||||
When I follow "faa1b"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
When I am on "courseaa2" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "faa2"
|
||||
Then "Add a new discussion topic" "button" should exist
|
||||
Then "Add a new discussion topic" "link" should exist
|
||||
When I am on "courseb" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "fb"
|
||||
Then "Add a new discussion topic" "button" should exist
|
||||
Then "Add a new discussion topic" "link" should exist
|
||||
|
||||
And I log out
|
||||
When I log in as "student1"
|
||||
And I am on "courseaa1" course homepage
|
||||
And I follow "faa1"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
When I am on "courseaa1" course homepage
|
||||
When I follow "faa1b"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
When I am on "courseaa2" course homepage
|
||||
When I follow "faa2"
|
||||
Then "Add a new discussion topic" "button" should exist
|
||||
Then "Add a new discussion topic" "link" should exist
|
||||
When I am on "courseb" course homepage
|
||||
When I follow "fb"
|
||||
Then "Add a new discussion topic" "button" should exist
|
||||
Then "Add a new discussion topic" "link" should exist
|
||||
|
||||
Scenario: Freeze course category should freeze all children
|
||||
Given I log in as "admin"
|
||||
@@ -153,53 +153,53 @@ Feature: Context freezing apply to child contexts
|
||||
And I click on "Continue" "button"
|
||||
And I am on "courseaa1" course homepage
|
||||
And I should not see "Turn editing on"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
When I am on "courseaa1" course homepage
|
||||
Then I should not see "Turn editing on"
|
||||
And "Unfreeze this context" "link" should not exist in current page administration
|
||||
When I follow "faa1b"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
And "Unfreeze this context" "link" should not exist in current page administration
|
||||
When I am on "courseaa2" course homepage
|
||||
Then I should not see "Turn editing on"
|
||||
When I follow "faa2"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
And "Unfreeze this context" "link" should not exist in current page administration
|
||||
When I am on "courseb" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "fb"
|
||||
Then "Add a new discussion topic" "button" should exist
|
||||
Then "Add a new discussion topic" "link" should exist
|
||||
|
||||
And I log out
|
||||
When I log in as "teacher"
|
||||
And I am on "courseaa1" course homepage
|
||||
Then I should not see "Turn editing on"
|
||||
And I follow "faa1"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
When I am on "courseaa1" course homepage
|
||||
Then I should not see "Turn editing on"
|
||||
When I follow "faa1b"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
When I am on "courseaa2" course homepage
|
||||
Then I should not see "Turn editing on"
|
||||
When I follow "faa2"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
When I am on "courseb" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "fb"
|
||||
Then "Add a new discussion topic" "button" should exist
|
||||
Then "Add a new discussion topic" "link" should exist
|
||||
|
||||
And I log out
|
||||
When I log in as "student1"
|
||||
And I am on "courseaa1" course homepage
|
||||
And I follow "faa1"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
When I am on "courseaa1" course homepage
|
||||
When I follow "faa1b"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
When I am on "courseaa2" course homepage
|
||||
When I follow "faa2"
|
||||
Then "Add a new discussion topic" "button" should not exist
|
||||
Then "Add a new discussion topic" "link" should not exist
|
||||
When I am on "courseb" course homepage
|
||||
When I follow "fb"
|
||||
Then "Add a new discussion topic" "button" should exist
|
||||
Then "Add a new discussion topic" "link" should exist
|
||||
|
||||
@@ -179,6 +179,8 @@ class core_exporter_testcase extends advanced_testcase {
|
||||
$this->assertEquals('otherstring description', $properties['otherstring']['description']);
|
||||
// Other properties default description.
|
||||
$this->assertEquals('otherstrings', $properties['otherstrings']['description']);
|
||||
// Assert nested elements are formatted correctly.
|
||||
$this->assertEquals('id', $properties['nestedarray']['type']['id']['description']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +230,13 @@ class core_testable_exporter extends \core\external\exporter {
|
||||
'otherstrings' => array(
|
||||
'type' => PARAM_TEXT,
|
||||
'multiple' => true
|
||||
),
|
||||
'nestedarray' => array(
|
||||
'multiple' => true,
|
||||
'optional' => true,
|
||||
'type' => [
|
||||
'id' => ['type' => PARAM_INT]
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -591,6 +591,11 @@ class core_externallib_testcase extends advanced_testcase {
|
||||
$files = external_util::get_area_files($context, $component, $filearea, false);
|
||||
$this->assertEquals($expectedfiles, $files);
|
||||
|
||||
$DB->method('get_in_or_equal')->willReturn([
|
||||
'= :mock1',
|
||||
['mock1' => $itemid]
|
||||
]);
|
||||
|
||||
// Get just the file indicated by $itemid.
|
||||
$files = external_util::get_area_files($context, $component, $filearea, $itemid);
|
||||
$this->assertEquals($expectedfiles, $files);
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
define(["mod_forum/subscription_toggle"],function(a){return{init:function(b){a.init(b)}}});
|
||||
+1
@@ -0,0 +1 @@
|
||||
define(["core/ajax"],function(a){var b=function(b,c,d){var e={methodname:"mod_forum_set_subscription_state",args:{forumid:b,discussionid:c,targetstate:d}};return a.call([e])[0]};return{setDiscussionSubscriptionState:b}});
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
define([],function(){return{subscription:{toggle:"[data-type='subscription-toggle'][data-action='toggle']"}}});
|
||||
@@ -0,0 +1 @@
|
||||
define(["jquery","core/templates","core/notification","mod_forum/repository","mod_forum/selectors"],function(a,b,c,d,e){var f=function(f){f.on("click",e.subscription.toggle,function(e){var f=a(this),g=f.data("forumid"),h=f.data("discussionid"),i=f.data("targetstate");d.setDiscussionSubscriptionState(g,h,i).then(function(a){return b.render("mod_forum/discussion_subscription_toggle",a)}).then(function(a,c){return b.replaceNode(f,a,c)})["catch"](c.exception),e.preventDefault()})};return{init:function(a){f(a)}}});
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Module for the list of discussions on when viewing a forum.
|
||||
*
|
||||
* @module mod_forum/discussion_list
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['mod_forum/subscription_toggle'], function(SubscriptionToggle) {
|
||||
return {
|
||||
init: function(root) {
|
||||
SubscriptionToggle.init(root);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Forum repository class to encapsulate all of the AJAX requests that
|
||||
* can be sent for forum.
|
||||
*
|
||||
* @module mod_forum/repository
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['core/ajax'], function(Ajax) {
|
||||
/**
|
||||
* Set the subscription state for a discussion in a forum.
|
||||
*
|
||||
* @param {number} forumId ID of the forum the discussion belongs to
|
||||
* @param {number} discussionId ID of the discussion with the subscription state
|
||||
* @param {boolean} targetState Set the subscribed state. True == subscribed; false == unsubscribed.
|
||||
* @return {object} jQuery promise
|
||||
*/
|
||||
var setDiscussionSubscriptionState = function(forumId, discussionId, targetState) {
|
||||
var request = {
|
||||
methodname: 'mod_forum_set_subscription_state',
|
||||
args: {
|
||||
forumid: forumId,
|
||||
discussionid: discussionId,
|
||||
targetstate: targetState
|
||||
}
|
||||
};
|
||||
return Ajax.call([request])[0];
|
||||
};
|
||||
|
||||
return {
|
||||
setDiscussionSubscriptionState: setDiscussionSubscriptionState,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Common CSS selectors for the forum UI.
|
||||
*
|
||||
* @module mod_forum/selectors
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define([], function() {
|
||||
return {
|
||||
subscription: {
|
||||
toggle: "[data-type='subscription-toggle'][data-action='toggle']",
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Handle discussion subscription toggling on a discussion list in
|
||||
* the forum view.
|
||||
*
|
||||
* @module mod_forum/subscription_toggle
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define([
|
||||
'jquery',
|
||||
'core/templates',
|
||||
'core/notification',
|
||||
'mod_forum/repository',
|
||||
'mod_forum/selectors',
|
||||
], function(
|
||||
$,
|
||||
Templates,
|
||||
Notification,
|
||||
Repository,
|
||||
Selectors
|
||||
) {
|
||||
|
||||
/**
|
||||
* Register event listeners for the subscription toggle.
|
||||
*
|
||||
* @param {object} root The discussion list root element
|
||||
*/
|
||||
var registerEventListeners = function(root) {
|
||||
root.on('click', Selectors.subscription.toggle, function(e) {
|
||||
var toggleElement = $(this);
|
||||
var forumId = toggleElement.data('forumid');
|
||||
var discussionId = toggleElement.data('discussionid');
|
||||
var subscriptionState = toggleElement.data('targetstate');
|
||||
|
||||
Repository.setDiscussionSubscriptionState(forumId, discussionId, subscriptionState)
|
||||
.then(function(context) {
|
||||
return Templates.render('mod_forum/discussion_subscription_toggle', context);
|
||||
})
|
||||
.then(function(html, js) {
|
||||
return Templates.replaceNode(toggleElement, html, js);
|
||||
})
|
||||
.catch(Notification.exception);
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
init: function(root) {
|
||||
registerEventListeners(root);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,198 @@
|
||||
<?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 <[email protected]>
|
||||
* @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\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;
|
||||
|
||||
/**
|
||||
* Exported discussion summaries 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 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
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Mihail Geshoski <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
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_entity $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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
<?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 post builder class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @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\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 context;
|
||||
use core_tag_tag;
|
||||
use moodle_exception;
|
||||
use rating_manager;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Exported post 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 posts.
|
||||
*
|
||||
* It encapsulates the complexity involved with exporting posts. 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 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class exported_posts {
|
||||
/** @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 posts for a given set of forums, discussions, and posts.
|
||||
*
|
||||
* This will typically be used for a list of posts in the same discussion/forum however
|
||||
* it does support exporting any arbitrary list of posts as long as the caller also provides
|
||||
* a unique list of all discussions for the list of posts and all forums for the list of discussions.
|
||||
*
|
||||
* Increasing the number of different forums being processed will increase the processing time
|
||||
* due to processing multiple contexts (for things like capabilities, files, etc). The code attempts
|
||||
* to load the additional resources as efficiently as possible but there is no way around some of
|
||||
* the additional overhead.
|
||||
*
|
||||
* @param stdClass $user The user to export the posts for.
|
||||
* @param forum_entity[] $forums A list of all forums that each of the $discussions belong to
|
||||
* @param discussion_entity[] $discussions A list of all discussions that each of the $posts belong to
|
||||
* @param post_entity[] $posts The list of posts to export.
|
||||
* @return stdClass[] List of exported posts in the same order as the $posts array.
|
||||
*/
|
||||
public function build(
|
||||
stdClass $user,
|
||||
array $forums,
|
||||
array $discussions,
|
||||
array $posts
|
||||
) : array {
|
||||
// Format the forums and discussion to make them more easily accessed later.
|
||||
$forums = array_reduce($forums, function($carry, $forum) {
|
||||
$carry[$forum->get_id()] = $forum;
|
||||
return $carry;
|
||||
}, []);
|
||||
$discussions = array_reduce($discussions, function($carry, $discussion) {
|
||||
$carry[$discussion->get_id()] = $discussion;
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
// Group the posts by discussion and forum so that we can load the resources in
|
||||
// batches to improve performance.
|
||||
$groupedposts = $this->group_posts_by_discussion($forums, $discussions, $posts);
|
||||
// Load all of the resources we need in order to export the posts.
|
||||
$authorsbyid = $this->get_authors_for_posts($posts);
|
||||
$attachmentsbypostid = $this->get_attachments_for_posts($groupedposts);
|
||||
$groupsbycourseandauthorid = $this->get_author_groups_from_posts($groupedposts);
|
||||
$tagsbypostid = $this->get_tags_from_posts($posts);
|
||||
$ratingbypostid = $this->get_ratings_from_posts($user, $groupedposts);
|
||||
$readreceiptcollectionbyforumid = $this->get_read_receipts_from_posts($user, $groupedposts);
|
||||
$exportedposts = [];
|
||||
|
||||
// Export each set of posts per discussion because it's the largest chunks we can
|
||||
// break them into due to constraints on capability checks.
|
||||
foreach ($groupedposts as $grouping) {
|
||||
[
|
||||
'forum' => $forum,
|
||||
'discussion' => $discussion,
|
||||
'posts' => $groupedposts
|
||||
] = $grouping;
|
||||
|
||||
$forumid = $forum->get_id();
|
||||
$courseid = $forum->get_course_record()->id;
|
||||
$postsexporter = $this->exporterfactory->get_posts_exporter(
|
||||
$user,
|
||||
$forum,
|
||||
$discussion,
|
||||
$groupedposts,
|
||||
$authorsbyid,
|
||||
$attachmentsbypostid,
|
||||
$groupsbycourseandauthorid[$courseid],
|
||||
$readreceiptcollectionbyforumid[$forumid] ?? null,
|
||||
$tagsbypostid,
|
||||
$ratingbypostid,
|
||||
true
|
||||
);
|
||||
['posts' => $exportedgroupedposts] = (array) $postsexporter->export($this->renderer);
|
||||
$exportedposts = array_merge($exportedposts, $exportedgroupedposts);
|
||||
}
|
||||
|
||||
if (count($forums) == 1 && count($discussions) == 1) {
|
||||
// All of the posts belong to a single discussion in a single forum so
|
||||
// the exported order will match the given $posts array.
|
||||
return $exportedposts;
|
||||
} else {
|
||||
// Since we grouped the posts by discussion and forum the ordering of the
|
||||
// exported posts may be different to the given $posts array so we should
|
||||
// sort it back into the correct order for the caller.
|
||||
return $this->sort_exported_posts($posts, $exportedposts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Group the posts by which discussion they belong to in order for them to be processed
|
||||
* in chunks by the exporting.
|
||||
*
|
||||
* Returns a list of groups where each group has a forum, discussion, and list of posts.
|
||||
* E.g.
|
||||
* [
|
||||
* [
|
||||
* 'forum' => <forum_entity>,
|
||||
* 'discussion' => <discussion_entity>,
|
||||
* 'posts' => [
|
||||
* <post_entity in discussion>,
|
||||
* <post_entity in discussion>,
|
||||
* <post_entity in discussion>
|
||||
* ]
|
||||
* ]
|
||||
* ]
|
||||
*
|
||||
* @param forum_entity[] $forums A list of all forums that each of the $discussions belong to, indexed by id.
|
||||
* @param discussion_entity[] $discussions A list of all discussions that each of the $posts belong to, indexed by id.
|
||||
* @param post_entity[] $posts The list of posts to process.
|
||||
* @return array List of grouped posts. Each group has a discussion, forum, and posts.
|
||||
*/
|
||||
private function group_posts_by_discussion(array $forums, array $discussions, array $posts) : array {
|
||||
return array_reduce($posts, function($carry, $post) use ($forums, $discussions) {
|
||||
$discussionid = $post->get_discussion_id();
|
||||
if (!isset($discussions[$discussionid])) {
|
||||
throw new moodle_exception('Unable to find discussion with id ' . $discussionid);
|
||||
}
|
||||
|
||||
if (isset($carry[$discussionid])) {
|
||||
$carry[$discussionid]['posts'][] = $post;
|
||||
} else {
|
||||
$discussion = $discussions[$discussionid];
|
||||
$forumid = $discussion->get_forum_id();
|
||||
|
||||
if (!isset($forums[$forumid])) {
|
||||
throw new moodle_exception('Unable to find forum with id ' . $forumid);
|
||||
}
|
||||
|
||||
$carry[$discussionid] = [
|
||||
'forum' => $forums[$forumid],
|
||||
'discussion' => $discussions[$discussionid],
|
||||
'posts' => [$post]
|
||||
];
|
||||
}
|
||||
|
||||
return $carry;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the list of authors for the given posts.
|
||||
*
|
||||
* The list of authors will be indexed by the author id.
|
||||
*
|
||||
* @param post_entity[] $posts The list of posts to process.
|
||||
* @return author_entity[]
|
||||
*/
|
||||
private function get_authors_for_posts(array $posts) : array {
|
||||
$authorvault = $this->vaultfactory->get_author_vault();
|
||||
return $authorvault->get_authors_for_posts($posts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the list of all attachments for the posts. The list of attachments will be
|
||||
* indexed by the post id.
|
||||
*
|
||||
* @param array $groupedposts List of posts grouped by discussions.
|
||||
* @return stored_file[]
|
||||
*/
|
||||
private function get_attachments_for_posts(array $groupedposts) : array {
|
||||
$attachmentsbypostid = [];
|
||||
$postattachmentvault = $this->vaultfactory->get_post_attachment_vault();
|
||||
$postsbyforum = array_reduce($groupedposts, function($carry, $grouping) {
|
||||
['forum' => $forum, 'posts' => $posts] = $grouping;
|
||||
|
||||
$forumid = $forum->get_id();
|
||||
if (!isset($carry[$forumid])) {
|
||||
$carry[$forumid] = [
|
||||
'forum' => $forum,
|
||||
'posts' => []
|
||||
];
|
||||
}
|
||||
|
||||
$carry[$forumid]['posts'] = array_merge($carry[$forumid]['posts'], $posts);
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
foreach ($postsbyforum as $grouping) {
|
||||
['forum' => $forum, 'posts' => $posts] = $grouping;
|
||||
$attachments = $postattachmentvault->get_attachments_for_posts($forum->get_context(), $posts);
|
||||
|
||||
// Have to loop in order to maintain the correct indexes since they are numeric.
|
||||
foreach ($attachments as $postid => $attachment) {
|
||||
$attachmentsbypostid[$postid] = $attachment;
|
||||
}
|
||||
}
|
||||
|
||||
return $attachmentsbypostid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the groups for each author of the given posts.
|
||||
*
|
||||
* The results are grouped by course and then author id because the groups are
|
||||
* contextually related to the course, e.g. a single author can be part of two different
|
||||
* sets of groups in two different courses.
|
||||
*
|
||||
* @param array $groupedposts List of posts grouped by discussions.
|
||||
* @return array List of groups indexed by forum id and then author id.
|
||||
*/
|
||||
private function get_author_groups_from_posts(array $groupedposts) : array {
|
||||
$groupsbyauthorid = [];
|
||||
$authoridsbycourseid = [];
|
||||
|
||||
// Get the unique list of author ids for each course in the grouped
|
||||
// posts. Grouping by course is the largest grouping we can achieve.
|
||||
foreach ($groupedposts as $grouping) {
|
||||
['forum' => $forum, 'posts' => $posts] = $grouping;
|
||||
$course = $forum->get_course_record();
|
||||
$courseid = $course->id;
|
||||
|
||||
if (!isset($authoridsbycourseid[$courseid])) {
|
||||
$coursemodule = $forum->get_course_module_record();
|
||||
$authoridsbycourseid[$courseid] = [
|
||||
'groupingid' => $coursemodule->groupingid,
|
||||
'authorids' => []
|
||||
];
|
||||
}
|
||||
|
||||
$authorids = array_map(function($post) {
|
||||
return $post->get_author_id();
|
||||
}, $posts);
|
||||
|
||||
foreach ($authorids as $authorid) {
|
||||
$authoridsbycourseid[$courseid]['authorids'][$authorid] = $authorid;
|
||||
}
|
||||
}
|
||||
|
||||
// Load each set of groups per course.
|
||||
foreach ($authoridsbycourseid as $courseid => $values) {
|
||||
['groupingid' => $groupingid, 'authorids' => $authorids] = $values;
|
||||
$authorgroups = groups_get_all_groups(
|
||||
$courseid,
|
||||
array_keys($authorids),
|
||||
$groupingid,
|
||||
'g.*, gm.id, gm.groupid, gm.userid'
|
||||
);
|
||||
|
||||
if (!isset($groupsbyauthorid[$courseid])) {
|
||||
$groupsbyauthorid[$courseid] = [];
|
||||
}
|
||||
|
||||
foreach ($authorgroups as $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($groupsbyauthorid[$courseid][$userid])) {
|
||||
$groupsbyauthorid[$courseid][$userid] = [];
|
||||
}
|
||||
|
||||
$groupsbyauthorid[$courseid][$userid][] = $group;
|
||||
}
|
||||
}
|
||||
|
||||
return $groupsbyauthorid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of tags for each of the posts. The tags will be returned in an
|
||||
* array indexed by the post id.
|
||||
*
|
||||
* @param post_entity[] $posts The list of posts to load tags for.
|
||||
* @return array Sets of tags indexed by post id.
|
||||
*/
|
||||
private function get_tags_from_posts(array $posts) : array {
|
||||
$postids = array_map(function($post) {
|
||||
return $post->get_id();
|
||||
}, $posts);
|
||||
return core_tag_tag::get_items_tags('mod_forum', 'forum_posts', $postids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of ratings for each post. The ratings are returned in an array
|
||||
* indexed by the post id.
|
||||
*
|
||||
* @param stdClass $user The user viewing the ratings.
|
||||
* @param array $groupedposts List of posts grouped by discussions.
|
||||
* @return array Sets of ratings indexed by post id.
|
||||
*/
|
||||
private function get_ratings_from_posts(stdClass $user, array $groupedposts) {
|
||||
$ratingsbypostid = [];
|
||||
$postsdatamapper = $this->legacydatamapperfactory->get_post_data_mapper();
|
||||
$postsbyforum = array_reduce($groupedposts, function($carry, $grouping) {
|
||||
['forum' => $forum, 'posts' => $posts] = $grouping;
|
||||
|
||||
$forumid = $forum->get_id();
|
||||
if (!isset($carry[$forumid])) {
|
||||
$carry[$forumid] = [
|
||||
'forum' => $forum,
|
||||
'posts' => []
|
||||
];
|
||||
}
|
||||
|
||||
$carry[$forumid]['posts'] = array_merge($carry[$forumid]['posts'], $posts);
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
foreach ($postsbyforum as $grouping) {
|
||||
['forum' => $forum, 'posts' => $posts] = $grouping;
|
||||
|
||||
if (!$forum->has_rating_aggregate()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$items = $postsdatamapper->to_legacy_objects($posts);
|
||||
$ratingoptions = (object) [
|
||||
'context' => $forum->get_context(),
|
||||
'component' => 'mod_forum',
|
||||
'ratingarea' => 'post',
|
||||
'items' => $items,
|
||||
'aggregate' => $forum->get_rating_aggregate(),
|
||||
'scaleid' => $forum->get_scale(),
|
||||
'userid' => $user->id,
|
||||
'assesstimestart' => $forum->get_assess_time_start(),
|
||||
'assesstimefinish' => $forum->get_assess_time_finish()
|
||||
];
|
||||
|
||||
$rm = $this->ratingmanager;
|
||||
$items = $rm->get_ratings($ratingoptions);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$ratingsbypostid[$item->id] = empty($item->rating) ? null : $item->rating;
|
||||
}
|
||||
}
|
||||
|
||||
return $ratingsbypostid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the read receipt collections for the given viewing user and each forum. The
|
||||
* receipt collections will only be loaded for posts in forums that the user is tracking.
|
||||
*
|
||||
* The receipt collections are returned in an array indexed by the forum ids.
|
||||
*
|
||||
* @param stdClass $user The user viewing the posts.
|
||||
* @param array $groupedposts List of posts grouped by discussions.
|
||||
*/
|
||||
private function get_read_receipts_from_posts(stdClass $user, array $groupedposts) {
|
||||
$forumdatamapper = $this->legacydatamapperfactory->get_forum_data_mapper();
|
||||
$trackedforums = [];
|
||||
$trackedpostids = [];
|
||||
|
||||
foreach ($groupedposts as $group) {
|
||||
['forum' => $forum, 'posts' => $posts] = $group;
|
||||
$forumid = $forum->get_id();
|
||||
|
||||
if (!isset($trackedforums[$forumid])) {
|
||||
$forumrecord = $forumdatamapper->to_legacy_object($forum);
|
||||
$trackedforums[$forumid] = forum_tp_is_tracked($forumrecord, $user);
|
||||
}
|
||||
|
||||
if ($trackedforums[$forumid]) {
|
||||
foreach ($posts as $post) {
|
||||
$trackedpostids[] = $post->get_id();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($trackedpostids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// We can just load a single receipt collection for all tracked posts.
|
||||
$receiptvault = $this->vaultfactory->get_post_read_receipt_collection_vault();
|
||||
$readreceiptcollection = $receiptvault->get_from_user_id_and_post_ids($user->id, $trackedpostids);
|
||||
$receiptsbyforumid = [];
|
||||
|
||||
// Assign the collection to all forums that are tracked.
|
||||
foreach ($trackedforums as $forumid => $tracked) {
|
||||
if ($tracked) {
|
||||
$receiptsbyforumid[$forumid] = $readreceiptcollection;
|
||||
}
|
||||
}
|
||||
|
||||
return $receiptsbyforumid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the list of exported posts back into the same order as the given posts.
|
||||
* The ordering of the exported posts can often deviate from the given posts due
|
||||
* to the process of exporting them so we need to sort them back into the order
|
||||
* that the calling code expected.
|
||||
*
|
||||
* @param post_entity[] $posts The posts in the expected order.
|
||||
* @param stdClass[] $exportedposts The list of exported posts in any order.
|
||||
* @return stdClass[] Sorted exported posts.
|
||||
*/
|
||||
private function sort_exported_posts(array $posts, array $exportedposts) {
|
||||
$postindexes = [];
|
||||
foreach (array_values($posts) as $index => $post) {
|
||||
$postindexes[$post->get_id()] = $index;
|
||||
}
|
||||
|
||||
$sortedexportedposts = [];
|
||||
|
||||
foreach ($exportedposts as $exportedpost) {
|
||||
$index = $postindexes[$exportedpost->id];
|
||||
$sortedexportedposts[$index] = $exportedpost;
|
||||
}
|
||||
|
||||
return $sortedexportedposts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Container class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2018 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\factories\renderer as renderer_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\vault as vault_factory;
|
||||
use mod_forum\local\factories\builder as builder_factory;
|
||||
use mod_forum\local\factories\url as url_factory;
|
||||
|
||||
/**
|
||||
* Container class.
|
||||
*
|
||||
* This class provides helper methods with static configurations to get any
|
||||
* of the factories from the "local" namespace.
|
||||
*
|
||||
* @copyright 2018 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class container {
|
||||
/**
|
||||
* Create the renderer factory.
|
||||
*
|
||||
* @return renderer_factory
|
||||
*/
|
||||
public static function get_renderer_factory() : renderer_factory {
|
||||
global $PAGE;
|
||||
|
||||
return new renderer_factory(
|
||||
self::get_legacy_data_mapper_factory(),
|
||||
self::get_exporter_factory(),
|
||||
self::get_vault_factory(),
|
||||
self::get_manager_factory(),
|
||||
self::get_entity_factory(),
|
||||
self::get_builder_factory(),
|
||||
self::get_url_factory(),
|
||||
$PAGE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the legacy data mapper factory.
|
||||
*
|
||||
* @return legacy_data_mapper_factory
|
||||
*/
|
||||
public static function get_legacy_data_mapper_factory() : legacy_data_mapper_factory {
|
||||
return new legacy_data_mapper_factory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the exporter factory.
|
||||
*
|
||||
* @return exporter_factory
|
||||
*/
|
||||
public static function get_exporter_factory() : exporter_factory {
|
||||
return new exporter_factory(
|
||||
self::get_legacy_data_mapper_factory(),
|
||||
self::get_manager_factory(),
|
||||
self::get_url_factory()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the vault factory.
|
||||
*
|
||||
* @return vault_factory
|
||||
*/
|
||||
public static function get_vault_factory() : vault_factory {
|
||||
global $DB;
|
||||
|
||||
return new vault_factory(
|
||||
$DB,
|
||||
self::get_entity_factory(),
|
||||
get_file_storage()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the manager factory.
|
||||
*
|
||||
* @return manager_factory
|
||||
*/
|
||||
public static function get_manager_factory() : manager_factory {
|
||||
return new manager_factory(
|
||||
self::get_legacy_data_mapper_factory()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the entity factory.
|
||||
*
|
||||
* @return entity_factory
|
||||
*/
|
||||
public static function get_entity_factory() : entity_factory {
|
||||
return new entity_factory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the builder factory.
|
||||
*
|
||||
* @return builder_factory
|
||||
*/
|
||||
public static function get_builder_factory() : builder_factory {
|
||||
global $PAGE;
|
||||
|
||||
return new builder_factory(
|
||||
self::get_legacy_data_mapper_factory(),
|
||||
self::get_exporter_factory(),
|
||||
self::get_vault_factory(),
|
||||
self::get_manager_factory(),
|
||||
$PAGE->get_renderer('mod_forum')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the URL factory.
|
||||
*
|
||||
* @return url_factory
|
||||
*/
|
||||
public static function get_url_factory() : url_factory {
|
||||
return new url_factory(
|
||||
self::get_legacy_data_mapper_factory()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Author data mapper.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\data_mappers\legacy;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\author as author_entity;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Convert an author entity into an stdClass.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class author {
|
||||
/**
|
||||
* Convert a list of author entities into stdClasses.
|
||||
*
|
||||
* @param author_entity[] $authors The authors to convert.
|
||||
* @return stdClass[]
|
||||
*/
|
||||
public function to_legacy_objects(array $authors) : array {
|
||||
return array_map(function(author_entity $author) {
|
||||
return (object) [
|
||||
'id' => $author->get_id(),
|
||||
'picture' => $author->get_picture_item_id(),
|
||||
'firstname' => $author->get_first_name(),
|
||||
'lastname' => $author->get_last_name(),
|
||||
'fullname' => $author->get_full_name(),
|
||||
'email' => $author->get_email(),
|
||||
'middlename' => $author->get_middle_name(),
|
||||
'firstnamephonetic' => $author->get_first_name_phonetic(),
|
||||
'lastnamephonetic' => $author->get_last_name_phonetic(),
|
||||
'alternatename' => $author->get_alternate_name(),
|
||||
'imagealt' => $author->get_image_alt()
|
||||
];
|
||||
}, $authors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an author entity into an stdClass.
|
||||
*
|
||||
* @param author_entity $author The author to convert.
|
||||
* @return stdClass
|
||||
*/
|
||||
public function to_legacy_object(author_entity $author) : stdClass {
|
||||
return $this->to_legacy_objects([$author])[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Discussion data mapper.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\data_mappers\legacy;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\discussion as discussion_entity;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Convert a discussion entity into an stdClass.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class discussion {
|
||||
/**
|
||||
* Convert a list of discussion entities into stdClasses.
|
||||
*
|
||||
* @param discussion_entity[] $authors The authors to convert.
|
||||
* @return stdClass[]
|
||||
*/
|
||||
public function to_legacy_objects(array $discussions) : array {
|
||||
return array_map(function(discussion_entity $discussion) {
|
||||
return (object) [
|
||||
'id' => $discussion->get_id(),
|
||||
'course' => $discussion->get_course_id(),
|
||||
'forum' => $discussion->get_forum_id(),
|
||||
'name' => $discussion->get_name(),
|
||||
'firstpost' => $discussion->get_first_post_id(),
|
||||
'userid' => $discussion->get_user_id(),
|
||||
'groupid' => $discussion->get_group_id(),
|
||||
'assessed' => $discussion->is_assessed(),
|
||||
'timemodified' => $discussion->get_time_modified(),
|
||||
'usermodified' => $discussion->get_user_modified(),
|
||||
'timestart' => $discussion->get_time_start(),
|
||||
'timeend' => $discussion->get_time_end(),
|
||||
'pinned' => $discussion->is_pinned()
|
||||
];
|
||||
}, $discussions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a discussion entity into an stdClass.
|
||||
*
|
||||
* @param discussion_entity $discussion The discussion to convert.
|
||||
* @return stdClass
|
||||
*/
|
||||
public function to_legacy_object(discussion_entity $discussion) : stdClass {
|
||||
return $this->to_legacy_objects([$discussion])[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Forum data mapper.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\data_mappers\legacy;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\forum as forum_entity;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Convert a forum entity into an stdClass.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class forum {
|
||||
/**
|
||||
* Convert a list of forum entities into stdClasses.
|
||||
*
|
||||
* @param forum_entity[] $forums The forums to convert.
|
||||
* @return stdClass[]
|
||||
*/
|
||||
public function to_legacy_objects(array $forums) : array {
|
||||
return array_map(function(forum_entity $forum) {
|
||||
return (object) [
|
||||
'id' => $forum->get_id(),
|
||||
'course' => $forum->get_course_id(),
|
||||
'type' => $forum->get_type(),
|
||||
'name' => $forum->get_name(),
|
||||
'intro' => $forum->get_intro(),
|
||||
'introformat' => $forum->get_intro_format(),
|
||||
'assessed' => $forum->get_rating_aggregate(),
|
||||
'assesstimestart' => $forum->get_assess_time_start(),
|
||||
'assesstimefinish' => $forum->get_assess_time_finish(),
|
||||
'scale' => $forum->get_scale(),
|
||||
'maxbytes' => $forum->get_max_bytes(),
|
||||
'maxattachments' => $forum->get_max_attachments(),
|
||||
'forcesubscribe' => $forum->get_subscription_mode(),
|
||||
'trackingtype' => $forum->get_tracking_type(),
|
||||
'rsstype' => $forum->get_rss_type(),
|
||||
'rssarticles' => $forum->get_rss_articles(),
|
||||
'timemodified' => $forum->get_time_modified(),
|
||||
'warnafter' => $forum->get_warn_after(),
|
||||
'blockafter' => $forum->get_block_after(),
|
||||
'blockperiod' => $forum->get_block_period(),
|
||||
'completiondiscussions' => $forum->get_completion_discussions(),
|
||||
'completionreplies' => $forum->get_completion_replies(),
|
||||
'completionposts' => $forum->get_completion_posts(),
|
||||
'displaywordcount' => $forum->should_display_word_count(),
|
||||
'lockdiscussionafter' => $forum->get_lock_discussions_after()
|
||||
];
|
||||
}, $forums);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a forum entity into an stdClass.
|
||||
*
|
||||
* @param forum_entity $forum The forum to convert.
|
||||
* @return stdClass
|
||||
*/
|
||||
public function to_legacy_object(forum_entity $forum) : stdClass {
|
||||
return $this->to_legacy_objects([$forum])[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Post data mapper.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\data_mappers\legacy;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\post as post_entity;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Convert a post entity into an stdClass.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class post {
|
||||
/**
|
||||
* Convert a list of post entities into stdClasses.
|
||||
*
|
||||
* @param post_entity[] $posts The posts to convert.
|
||||
* @return stdClass[]
|
||||
*/
|
||||
public function to_legacy_objects(array $posts) : array {
|
||||
return array_map(function(post_entity $post) {
|
||||
return (object) [
|
||||
'id' => $post->get_id(),
|
||||
'discussion' => $post->get_discussion_id(),
|
||||
'parent' => $post->get_parent_id(),
|
||||
'userid' => $post->get_author_id(),
|
||||
'created' => $post->get_time_created(),
|
||||
'modified' => $post->get_time_modified(),
|
||||
'mailed' => $post->has_been_mailed(),
|
||||
'subject' => $post->get_subject(),
|
||||
'message' => $post->get_message(),
|
||||
'messageformat' => $post->get_message_format(),
|
||||
'messagetrust' => $post->is_message_trusted(),
|
||||
'attachment' => $post->has_attachments(),
|
||||
'totalscore' => $post->get_total_score(),
|
||||
'mailnow' => $post->should_mail_now(),
|
||||
'deleted' => $post->is_deleted()
|
||||
];
|
||||
}, $posts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a post entity into an stdClass.
|
||||
*
|
||||
* @param post_entity $post The post to convert.
|
||||
* @return stdClass
|
||||
*/
|
||||
public function to_legacy_object(post_entity $post) : stdClass {
|
||||
return $this->to_legacy_objects([$post])[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Author class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\entities;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Author class.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class author {
|
||||
/** @var int $id ID */
|
||||
private $id;
|
||||
/** @var int $pictureitemid Picture item id */
|
||||
private $pictureitemid;
|
||||
/** @var string $firstname First name */
|
||||
private $firstname;
|
||||
/** @var string $lastname Last name */
|
||||
private $lastname;
|
||||
/** @var string $fullname Full name */
|
||||
private $fullname;
|
||||
/** @var string $email Email */
|
||||
private $email;
|
||||
/** @var string $middlename Middle name */
|
||||
private $middlename;
|
||||
/** @var string $firstnamephonetic Phonetic spelling of first name */
|
||||
private $firstnamephonetic;
|
||||
/** @var string $lastnamephonetic Phonetic spelling of last name */
|
||||
private $lastnamephonetic;
|
||||
/** @var string $alternatename Altername name */
|
||||
private $alternatename;
|
||||
/** @var string $imagealt Image alt */
|
||||
private $imagealt;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $id ID
|
||||
* @param int $pictureitemid Picture item id
|
||||
* @param string $firstname First name
|
||||
* @param string $lastname Last name
|
||||
* @param string $fullname Full name
|
||||
* @param string $email Email
|
||||
* @param string|null $middlename Middle name
|
||||
* @param string|null $firstnamephonetic Phonetic spelling of first name
|
||||
* @param string|null $lastnamephonetic Phonetic spelling of last name
|
||||
* @param string|null $alternatename Altername name
|
||||
* @param string|null $imagealt Image alt
|
||||
*/
|
||||
public function __construct(
|
||||
int $id,
|
||||
int $pictureitemid,
|
||||
string $firstname,
|
||||
string $lastname,
|
||||
string $fullname,
|
||||
string $email,
|
||||
string $middlename = null,
|
||||
string $firstnamephonetic = null,
|
||||
string $lastnamephonetic = null,
|
||||
string $alternatename = null,
|
||||
string $imagealt = null
|
||||
) {
|
||||
$this->id = $id;
|
||||
$this->pictureitemid = $pictureitemid;
|
||||
$this->firstname = $firstname;
|
||||
$this->lastname = $lastname;
|
||||
$this->fullname = $fullname;
|
||||
$this->email = $email;
|
||||
$this->middlename = $middlename;
|
||||
$this->firstnamephonetic = $firstnamephonetic;
|
||||
$this->lastnamephonetic = $lastnamephonetic;
|
||||
$this->alternatename = $alternatename;
|
||||
$this->imagealt = $imagealt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_id() : int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the picture item id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_picture_item_id() : int {
|
||||
return $this->pictureitemid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_first_name() : string {
|
||||
return $this->firstname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_last_name() : string {
|
||||
return $this->lastname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_full_name() : string {
|
||||
return $this->fullname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the email.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_email() : string {
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the middle name.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_middle_name() : ?string {
|
||||
return $this->middlename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first name phonetic.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_first_name_phonetic() : ?string {
|
||||
return $this->firstnamephonetic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last name phonetic.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_last_name_phonetic() : ?string {
|
||||
return $this->lastnamephonetic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the alternate name.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_alternate_name() : ?string {
|
||||
return $this->alternatename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the image alt.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_image_alt() : ?string {
|
||||
return $this->imagealt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Discussion class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\entities;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\post as post_entity;
|
||||
|
||||
/**
|
||||
* Discussion class.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class discussion {
|
||||
/** @var int $id ID */
|
||||
private $id;
|
||||
/** @var int $courseid Course id */
|
||||
private $courseid;
|
||||
/** @var int $forumid Forum id */
|
||||
private $forumid;
|
||||
/** @var string $name Discussion name */
|
||||
private $name;
|
||||
/** @var int $firstpostid Id of the first post in the discussion */
|
||||
private $firstpostid;
|
||||
/** @var int $userid Id of the user that created the discussion */
|
||||
private $userid;
|
||||
/** @var int $groupid Group id if it's a group dicussion */
|
||||
private $groupid;
|
||||
/** @var bool $assessed Is the discussion assessed? */
|
||||
private $assessed;
|
||||
/** @var int $timemodified Timestamp for last modification to the discussion */
|
||||
private $timemodified;
|
||||
/** @var int $usermodified Id of user that last modified the discussion */
|
||||
private $usermodified;
|
||||
/** @var int $timestart Start time for the discussion */
|
||||
private $timestart;
|
||||
/** @var int $timeend End time for the discussion */
|
||||
private $timeend;
|
||||
/** @var bool $pinned Is the discussion pinned? */
|
||||
private $pinned;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $id ID
|
||||
* @param int $courseid Course id
|
||||
* @param int $forumid Forum id
|
||||
* @param string $name Discussion name
|
||||
* @param int $firstpostid Id of the first post in the discussion
|
||||
* @param int $userid Id of the user that created the discussion
|
||||
* @param int $groupid Group id if it's a group dicussion
|
||||
* @param bool $assessed Is the discussion assessed?
|
||||
* @param int $timemodified Timestamp for last modification to the discussion
|
||||
* @param int $usermodified Id of user that last modified the discussion
|
||||
* @param int $timestart Start time for the discussion
|
||||
* @param int $timeend End time for the discussion
|
||||
* @param bool $pinned Is the discussion pinned?
|
||||
*/
|
||||
public function __construct(
|
||||
int $id,
|
||||
int $courseid,
|
||||
int $forumid,
|
||||
string $name,
|
||||
int $firstpostid,
|
||||
int $userid,
|
||||
int $groupid,
|
||||
bool $assessed,
|
||||
int $timemodified,
|
||||
int $usermodified,
|
||||
int $timestart,
|
||||
int $timeend,
|
||||
bool $pinned
|
||||
) {
|
||||
$this->id = $id;
|
||||
$this->courseid = $courseid;
|
||||
$this->forumid = $forumid;
|
||||
$this->name = $name;
|
||||
$this->firstpostid = $firstpostid;
|
||||
$this->userid = $userid;
|
||||
$this->groupid = $groupid;
|
||||
$this->assessed = $assessed;
|
||||
$this->timemodified = $timemodified;
|
||||
$this->usermodified = $usermodified;
|
||||
$this->timestart = $timestart;
|
||||
$this->timeend = $timeend;
|
||||
$this->pinned = $pinned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the discussion id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_id() : int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the course id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_course_id() : int {
|
||||
return $this->courseid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the forum id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_forum_id() : int {
|
||||
return $this->forumid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the discussion.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() : string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the fist post in the discussion.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_first_post_id() : int {
|
||||
return $this->firstpostid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the user that created the discussion.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_user_id() : int {
|
||||
return $this->userid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the group that this discussion belongs to.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_group_id() : int {
|
||||
return $this->groupid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this discussion is assessed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_assessed() : bool {
|
||||
return $this->assessed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp for when this discussion was last modified.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_time_modified() : int {
|
||||
return $this->timemodified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the user that last modified this discussion.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_user_modified() : int {
|
||||
return $this->usermodified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the start time of this discussion. Returns zero if the discussion
|
||||
* has no designated start time.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_time_start() : int {
|
||||
return $this->timestart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time of this discussion. Returns zero if the discussion
|
||||
* has no designated end time.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_time_end() : int {
|
||||
return $this->timeend;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this discussion is pinned.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_pinned() : bool {
|
||||
return $this->pinned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given post is the first post in this discussion.
|
||||
*
|
||||
* @param post_entity $post The post to check
|
||||
* @return bool
|
||||
*/
|
||||
public function is_first_post(post_entity $post) : bool {
|
||||
return $this->get_first_post_id() === $post->get_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the discussion has started yet.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_started() : bool {
|
||||
$startime = $this->get_time_start();
|
||||
return empty($startime) || $startime < time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the discussion has ended.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_ended() : bool {
|
||||
$endtime = $this->get_time_end();
|
||||
return !empty($endtime) && $endtime >= time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the discussion belongs to a group.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_group() : bool {
|
||||
return $this->get_group_id() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the discussion is timed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_timed_discussion() : bool {
|
||||
global $CFG;
|
||||
|
||||
return !empty($CFG->forum_enabletimedposts) &&
|
||||
($this->get_time_start() || $this->get_time_end());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the timed discussion is visible.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_timed_discussion_visible() : bool {
|
||||
return !$this->is_timed_discussion() || ($this->has_started() && !$this->has_ended());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Discussion summary class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\entities;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\discussion as discussion_entity;
|
||||
use mod_forum\local\entities\post as post_entity;
|
||||
use mod_forum\local\entities\author as author_entity;
|
||||
|
||||
/**
|
||||
* Discussion summary class.
|
||||
*
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class discussion_summary {
|
||||
/** @var discussion_entity $discussion The discussion being summarised */
|
||||
private $discussion;
|
||||
/** @var author_entity $firstpostauthor Author of the first post in the discussion */
|
||||
private $firstpostauthor;
|
||||
/** @var post_entity $firstpost First post in the discussion */
|
||||
private $firstpost;
|
||||
/** @var author_entity $latestpostauthor Author of the last post in the discussion */
|
||||
private $latestpostauthor;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param discussion_entity $discussion The discussion being summarised
|
||||
* @param post_entity $firstpost First post in the discussion
|
||||
* @param author_entity $firstpostauthor Author of the first post in the discussion
|
||||
* @param author_entity $latestpostauthor Author of the last post in the discussion
|
||||
*/
|
||||
public function __construct(
|
||||
discussion_entity $discussion,
|
||||
post_entity $firstpost,
|
||||
author_entity $firstpostauthor,
|
||||
author_entity $latestpostauthor
|
||||
) {
|
||||
$this->discussion = $discussion;
|
||||
$this->firstpostauthor = $firstpostauthor;
|
||||
$this->firstpost = $firstpost;
|
||||
$this->latestpostauthor = $latestpostauthor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the discussion entity.
|
||||
*
|
||||
* @return discussion_entity
|
||||
*/
|
||||
public function get_discussion() : discussion_entity {
|
||||
return $this->discussion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the author entity for the first post.
|
||||
*
|
||||
* @return author_entity
|
||||
*/
|
||||
public function get_first_post_author() : author_entity {
|
||||
return $this->firstpostauthor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the author entity for the last post.
|
||||
*
|
||||
* @return author_entity
|
||||
*/
|
||||
public function get_latest_post_author() : author_entity {
|
||||
return $this->latestpostauthor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the post entity for the first post.
|
||||
*
|
||||
* @return post_entity
|
||||
*/
|
||||
public function get_first_post() : post_entity {
|
||||
return $this->firstpost;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Forum class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\entities;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/mod/forum/lib.php');
|
||||
require_once($CFG->dirroot . '/rating/lib.php');
|
||||
|
||||
use mod_forum\local\entities\discussion as discussion_entity;
|
||||
use context;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Forum class.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class forum {
|
||||
/** @var context $context The forum module context */
|
||||
private $context;
|
||||
/** @var stdClass $coursemodule The forum course module record */
|
||||
private $coursemodule;
|
||||
/** @var stdClass $course The forum course record */
|
||||
private $course;
|
||||
/** @var int $effectivegroupmode The effective group mode */
|
||||
private $effectivegroupmode;
|
||||
/** @var int $id ID */
|
||||
private $id;
|
||||
/** @var int $courseid Id of the course this forum is in */
|
||||
private $courseid;
|
||||
/** @var string $type The forum type, e.g. single, qanda, etc */
|
||||
private $type;
|
||||
/** @var string $name Name of the forum */
|
||||
private $name;
|
||||
/** @var string $intro Intro text */
|
||||
private $intro;
|
||||
/** @var int $introformat Format of the intro text */
|
||||
private $introformat;
|
||||
/** @var int $assessed The forum rating aggregate */
|
||||
private $assessed;
|
||||
/** @var int $assesstimestart Timestamp to begin assessment */
|
||||
private $assesstimestart;
|
||||
/** @var int $assesstimefinish Timestamp to end assessment */
|
||||
private $assesstimefinish;
|
||||
/** @var int $scale The rating scale */
|
||||
private $scale;
|
||||
/** @var int $maxbytes Maximum attachment size */
|
||||
private $maxbytes;
|
||||
/** @var int $maxattachments Maximum number of attachments */
|
||||
private $maxattachments;
|
||||
/** @var int $forcesubscribe Does the forum force users to subscribe? */
|
||||
private $forcesubscribe;
|
||||
/** @var int $trackingtype Tracking type */
|
||||
private $trackingtype;
|
||||
/** @var int $rsstype RSS type */
|
||||
private $rsstype;
|
||||
/** @var int $rssarticles RSS articles */
|
||||
private $rssarticles;
|
||||
/** @var int $timemodified Timestamp when the forum was last modified */
|
||||
private $timemodified;
|
||||
/** @var int $warnafter Warn after */
|
||||
private $warnafter;
|
||||
/** @var int $blockafter Block after */
|
||||
private $blockafter;
|
||||
/** @var int $blockperiod Block period */
|
||||
private $blockperiod;
|
||||
/** @var int $completiondiscussions Completion discussions */
|
||||
private $completiondiscussions;
|
||||
/** @var int $completionreplies Completion replies */
|
||||
private $completionreplies;
|
||||
/** @var int $completionposts Completion posts */
|
||||
private $completionposts;
|
||||
/** @var bool $displaywordcounts Should display word counts in posts */
|
||||
private $displaywordcounts;
|
||||
/** @var bool $lockdiscussionafter Timestamp after which discussions should be locked */
|
||||
private $lockdiscussionafter;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param context $context The forum module context
|
||||
* @param stdClass $coursemodule The forum course module record
|
||||
* @param stdClass $course The forum course record
|
||||
* @param int $effectivegroupmode The effective group mode
|
||||
* @param int $id ID
|
||||
* @param int $courseid Id of the course this forum is in
|
||||
* @param string $type The forum type, e.g. single, qanda, etc
|
||||
* @param string $name Name of the forum
|
||||
* @param string $intro Intro text
|
||||
* @param int $introformat Format of the intro text
|
||||
* @param int $assessed The forum rating aggregate
|
||||
* @param int $assesstimestart Timestamp to begin assessment
|
||||
* @param int $assesstimefinish Timestamp to end assessment
|
||||
* @param int $scale The rating scale
|
||||
* @param int $maxbytes Maximum attachment size
|
||||
* @param int $maxattachments Maximum number of attachments
|
||||
* @param int $forcesubscribe Does the forum force users to subscribe?
|
||||
* @param int $trackingtype Tracking type
|
||||
* @param int $rsstype RSS type
|
||||
* @param int $rssarticles RSS articles
|
||||
* @param int $timemodified Timestamp when the forum was last modified
|
||||
* @param int $warnafter Warn after
|
||||
* @param int $blockafter Block after
|
||||
* @param int $blockperiod Block period
|
||||
* @param int $completiondiscussions Completion discussions
|
||||
* @param int $completionreplies Completion replies
|
||||
* @param int $completionposts Completion posts
|
||||
* @param bool $displaywordcount Should display word counts in posts
|
||||
* @param int $lockdiscussionafter Timestamp after which discussions should be locked
|
||||
*/
|
||||
public function __construct(
|
||||
context $context,
|
||||
stdClass $coursemodule,
|
||||
stdClass $course,
|
||||
int $effectivegroupmode,
|
||||
int $id,
|
||||
int $courseid,
|
||||
string $type,
|
||||
string $name,
|
||||
string $intro,
|
||||
int $introformat,
|
||||
int $assessed,
|
||||
int $assesstimestart,
|
||||
int $assesstimefinish,
|
||||
int $scale,
|
||||
int $maxbytes,
|
||||
int $maxattachments,
|
||||
int $forcesubscribe,
|
||||
int $trackingtype,
|
||||
int $rsstype,
|
||||
int $rssarticles,
|
||||
int $timemodified,
|
||||
int $warnafter,
|
||||
int $blockafter,
|
||||
int $blockperiod,
|
||||
int $completiondiscussions,
|
||||
int $completionreplies,
|
||||
int $completionposts,
|
||||
bool $displaywordcount,
|
||||
int $lockdiscussionafter
|
||||
) {
|
||||
$this->context = $context;
|
||||
$this->coursemodule = $coursemodule;
|
||||
$this->course = $course;
|
||||
$this->effectivegroupmode = $effectivegroupmode;
|
||||
$this->id = $id;
|
||||
$this->courseid = $courseid;
|
||||
$this->type = $type;
|
||||
$this->name = $name;
|
||||
$this->intro = $intro;
|
||||
$this->introformat = $introformat;
|
||||
$this->assessed = $assessed;
|
||||
$this->assesstimestart = $assesstimestart;
|
||||
$this->assesstimefinish = $assesstimefinish;
|
||||
$this->scale = $scale;
|
||||
$this->maxbytes = $maxbytes;
|
||||
$this->maxattachments = $maxattachments;
|
||||
$this->forcesubscribe = $forcesubscribe;
|
||||
$this->trackingtype = $trackingtype;
|
||||
$this->rsstype = $rsstype;
|
||||
$this->rssarticles = $rssarticles;
|
||||
$this->timemodified = $timemodified;
|
||||
$this->warnafter = $warnafter;
|
||||
$this->blockafter = $blockafter;
|
||||
$this->blockperiod = $blockperiod;
|
||||
$this->completiondiscussions = $completiondiscussions;
|
||||
$this->completionreplies = $completionreplies;
|
||||
$this->completionposts = $completionposts;
|
||||
$this->displaywordcount = $displaywordcount;
|
||||
$this->lockdiscussionafter = $lockdiscussionafter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the forum module context.
|
||||
*
|
||||
* @return context
|
||||
*/
|
||||
public function get_context() : context {
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the forum course module record
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
public function get_course_module_record() : stdClass {
|
||||
return $this->coursemodule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the effective group mode.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_effective_group_mode() : int {
|
||||
return $this->effectivegroupmode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the forum is set to group mode.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_in_group_mode() : bool {
|
||||
return $this->get_effective_group_mode() !== NOGROUPS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the course record.
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
public function get_course_record() : stdClass {
|
||||
return $this->course;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the forum id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_id() : int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the course that the forum belongs to.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_course_id() : int {
|
||||
return $this->courseid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the forum type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_type() : string {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the forum name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() : string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the forum intro text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_intro() : string {
|
||||
return $this->intro;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the forum intro text format.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_intro_format() : int {
|
||||
return $this->introformat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rating aggregate.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_rating_aggregate() : int {
|
||||
return $this->assessed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the forum have a rating aggregate?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_rating_aggregate() : bool {
|
||||
return $this->get_rating_aggregate() != RATING_AGGREGATE_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp for when the assessment period begins.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_assess_time_start() : int {
|
||||
return $this->assesstimestart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp for when the assessment period ends.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_assess_time_finish() : int {
|
||||
return $this->assesstimefinish;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rating scale.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_scale() : int {
|
||||
return $this->scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum bytes.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_max_bytes() : int {
|
||||
return $this->maxbytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum number of attachments.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_max_attachments() : int {
|
||||
return $this->maxattachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subscription mode.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_subscription_mode() : int {
|
||||
return $this->forcesubscribe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the subscription mode set to optional.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_subscription_optional() : bool {
|
||||
return $this->get_subscription_mode() === FORUM_CHOOSESUBSCRIBE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the subscription mode set to forced.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_subscription_forced() : bool {
|
||||
return $this->get_subscription_mode() === FORUM_FORCESUBSCRIBE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the subscription mode set to automatic.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_subscription_automatic() : bool {
|
||||
return $this->get_subscription_mode() === FORUM_INITIALSUBSCRIBE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the subscription mode set to disabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_subscription_disabled() : bool {
|
||||
return $this->get_subscription_mode() === FORUM_DISALLOWSUBSCRIBE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tracking type.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_tracking_type() : int {
|
||||
return $this->trackingtype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the RSS type.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_rss_type() : int {
|
||||
return $this->rsstype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the RSS articles.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_rss_articles() : int {
|
||||
return $this->rssarticles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp for when the forum was last modified.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_time_modified() : int {
|
||||
return $this->timemodified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get warn after.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_warn_after() : int {
|
||||
return $this->warnafter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get block after.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_block_after() : int {
|
||||
return $this->blockafter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block period.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_block_period() : int {
|
||||
return $this->blockperiod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the forum have blocking enabled?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_blocking_enabled() : bool {
|
||||
return !empty($this->get_block_after()) && !empty($this->get_block_period());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the completion discussions.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_completion_discussions() : int {
|
||||
return $this->completiondiscussions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the completion replies.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_completion_replies() : int {
|
||||
return $this->completionreplies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the completion posts.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_completion_posts() : int {
|
||||
return $this->completionposts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the word counts be shown in the posts?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_display_word_count() : bool {
|
||||
return $this->displaywordcount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp after which the discussion should be locked.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_lock_discussions_after() : int {
|
||||
return $this->lockdiscussionafter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the forum have a discussion locking timestamp?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_lock_discussions_after() : bool {
|
||||
return !empty($this->get_lock_discussions_after());
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the discussion locked?
|
||||
*
|
||||
* @param discussion_entity $discussion The discussion to check
|
||||
* @return bool
|
||||
*/
|
||||
public function is_discussion_locked(discussion_entity $discussion) : bool {
|
||||
if (!$this->has_lock_discussions_after()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->get_type() === 'single') {
|
||||
// It does not make sense to lock a single discussion forum.
|
||||
return false;
|
||||
}
|
||||
|
||||
return (($discussion->get_time_modified() + $this->get_lock_discussions_after()) < time());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Post class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\entities;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Post class.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class post {
|
||||
/** @var int $id ID */
|
||||
private $id;
|
||||
/** @var int $discussionid The id of the discussion this post belongs to */
|
||||
private $discussionid;
|
||||
/** @var int $parentid The id of the post that this post is replying to. Zero if it isn't a reply. */
|
||||
private $parentid;
|
||||
/** @var int $authorid The id of user who authored the post */
|
||||
private $authorid;
|
||||
/** @var int $timecreated Timestamp for when the post was created */
|
||||
private $timecreated;
|
||||
/** @var int $timemodified Timestamp for when the post last modified */
|
||||
private $timemodified;
|
||||
/** @var bool $mailed If the post has been mailed */
|
||||
private $mailed;
|
||||
/** @var string $subject Post subject */
|
||||
private $subject;
|
||||
/** @var string $message Post message */
|
||||
private $message;
|
||||
/** @var int $messageformat Format of the post message */
|
||||
private $messageformat;
|
||||
/** @var bool $messagetrust Is this a trusted message, i.e. created by a trusted user. */
|
||||
private $messagetrust;
|
||||
/** @var bool $hasattachments Does the post have attachments */
|
||||
private $hasattachments;
|
||||
/** @var int $totalscore Total score */
|
||||
private $totalscore;
|
||||
/** @var bool $mailnow Should this post be mailed immediately */
|
||||
private $mailnow;
|
||||
/** @var bool $deleted Is the post deleted */
|
||||
private $deleted;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $id ID
|
||||
* @param int $discussionid The id of the discussion this post belongs to
|
||||
* @param int $parentid The id of the post that this post is replying to. Zero if it isn't a reply.
|
||||
* @param int $authorid The id of user who authored the post
|
||||
* @param int $timecreated Timestamp for when the post was created
|
||||
* @param int $timemodified Timestamp for when the post last modified
|
||||
* @param bool $mailed If the post has been mailed
|
||||
* @param string $subject Post subject
|
||||
* @param string $message Post message
|
||||
* @param int $messageformat Format of the post message
|
||||
* @param bool $messagetrust Is this a trusted message, i.e. created by a trusted user.
|
||||
* @param bool $hasattachments Does the post have attachments
|
||||
* @param int $totalscore Total score
|
||||
* @param bool $mailnow Should this post be mailed immediately
|
||||
* @param bool $deleted Is the post deleted
|
||||
*/
|
||||
public function __construct(
|
||||
int $id,
|
||||
int $discussionid,
|
||||
int $parentid,
|
||||
int $authorid,
|
||||
int $timecreated,
|
||||
int $timemodified,
|
||||
bool $mailed,
|
||||
string $subject,
|
||||
string $message,
|
||||
int $messageformat,
|
||||
bool $messagetrust,
|
||||
bool $hasattachments,
|
||||
int $totalscore,
|
||||
bool $mailnow,
|
||||
bool $deleted
|
||||
) {
|
||||
$this->id = $id;
|
||||
$this->discussionid = $discussionid;
|
||||
$this->parentid = $parentid;
|
||||
$this->authorid = $authorid;
|
||||
$this->timecreated = $timecreated;
|
||||
$this->timemodified = $timemodified;
|
||||
$this->mailed = $mailed;
|
||||
$this->subject = $subject;
|
||||
$this->message = $message;
|
||||
$this->messageformat = $messageformat;
|
||||
$this->messagetrust = $messagetrust;
|
||||
$this->hasattachments = $hasattachments;
|
||||
$this->totalscore = $totalscore;
|
||||
$this->mailnow = $mailnow;
|
||||
$this->deleted = $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the post id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_id() : int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the discussion id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_discussion_id() : int {
|
||||
return $this->discussionid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the parent post. Returns zero if this post is not a reply.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_parent_id() : int {
|
||||
return $this->parentid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this post have a parent? I.e. is it a reply?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_parent() : bool {
|
||||
return $this->get_parent_id() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the user that authored the post.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_author_id() : int {
|
||||
return $this->authorid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp for when this post was created.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_time_created() : int {
|
||||
return $this->timecreated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp for when this post was last modified.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_time_modified() : int {
|
||||
return $this->timemodified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has this post been mailed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_been_mailed() : bool {
|
||||
return $this->mailed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the post subject.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_subject() : string {
|
||||
return $this->subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the post message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_message() : string {
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the post message format.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_message_format() : int {
|
||||
return $this->messageformat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a trusted message? I.e. was it authored by a trusted user?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_message_trusted() : bool {
|
||||
return $this->messagetrust;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this post have attachments?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_attachments() : bool {
|
||||
return $this->hasattachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total score.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_total_score() : int {
|
||||
return $this->totalscore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this post be mailed now?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_mail_now() : bool {
|
||||
return $this->mailnow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this post deleted?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_deleted() : bool {
|
||||
return $this->deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the post's age in seconds.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_age() : int {
|
||||
return time() - $this->get_time_created();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given user authored this post.
|
||||
*
|
||||
* @param stdClass $user The user to check.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_owned_by_user(stdClass $user) : bool {
|
||||
return $this->get_author_id() == $user->id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Post read receipt collection class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\entities;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\post as post_entity;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Post read receipt collection class.
|
||||
*
|
||||
* Contains the list of read receipts for posts.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class post_read_receipt_collection {
|
||||
/** @var stdClass[] $receiptsbypostid Receipt records indexed by post id */
|
||||
private $receiptsbypostid = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $records The list of post read receipt records.
|
||||
*/
|
||||
public function __construct(array $records) {
|
||||
foreach ($records as $record) {
|
||||
$postid = $record->postid;
|
||||
|
||||
if (isset($this->receiptsbypostid[$postid])) {
|
||||
$this->receiptsbypostid[$postid][] = $record;
|
||||
} else {
|
||||
$this->receiptsbypostid[$postid] = [$record];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a user has read a post.
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @param post_entity $post The post to check
|
||||
* @return bool
|
||||
*/
|
||||
public function has_user_read_post(stdClass $user, post_entity $post) : bool {
|
||||
global $CFG;
|
||||
$isoldpost = ($post->get_time_modified() < (time() - ($CFG->forum_oldpostdays * 24 * 3600)));
|
||||
|
||||
if ($isoldpost) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$receipts = isset($this->receiptsbypostid[$post->get_id()]) ? $this->receiptsbypostid[$post->get_id()] : [];
|
||||
|
||||
foreach ($receipts as $receipt) {
|
||||
if ($receipt->userid == $user->id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Class to sort items.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\entities;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class to sort lists of items.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class sorter {
|
||||
/** @var callable $getid Function used to get the id from an item */
|
||||
private $getid;
|
||||
/** @var callable $getparentid Function used to get the parent id from an item */
|
||||
private $getparentid;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Allows the calling code to provide 2 functions to get the id and parent id from
|
||||
* the list of items it is intended to process.
|
||||
*
|
||||
* This allows this class to be composed in numerous different ways to support various
|
||||
* types of items while keeping the underlying sorting algorithm consistent.
|
||||
*
|
||||
* @param callable $getid Function used to get the id from an item
|
||||
* @param callable $getparentid Function used to get the parent id from an item
|
||||
*/
|
||||
public function __construct(callable $getid, callable $getparentid) {
|
||||
$this->getid = $getid;
|
||||
$this->getparentid = $getparentid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort a list of items into a parent/child data structure. The resulting data structure
|
||||
* is a recursive array of arrays where the first element is the parent and the second is
|
||||
* an array of it's children.
|
||||
*
|
||||
* For example
|
||||
* If we have an array of items A, B, C, and D where D is a child of C, B and C are children
|
||||
* of A.
|
||||
*
|
||||
* This function would sort them into the following:
|
||||
* [
|
||||
* [
|
||||
* A,
|
||||
* [
|
||||
* [
|
||||
* B,
|
||||
* []
|
||||
* ],
|
||||
* [
|
||||
* C,
|
||||
* [
|
||||
* [
|
||||
* D,
|
||||
* []
|
||||
* ]
|
||||
* ]
|
||||
* ]
|
||||
* ]
|
||||
* ]
|
||||
* ]
|
||||
*
|
||||
* @param array $items The list of items to sort.
|
||||
* @return array
|
||||
*/
|
||||
public function sort_into_children(array $items) : array {
|
||||
$ids = array_reduce($items, function($carry, $item) {
|
||||
$carry[($this->getid)($item)] = true;
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
// Split out the items into "parents" and "replies" (children). These are unsorted
|
||||
// at this point.
|
||||
[$parents, $replies] = array_reduce($items, function($carry, $item) use ($ids) {
|
||||
$parentid = ($this->getparentid)($item);
|
||||
|
||||
if (!empty($ids[$parentid])) {
|
||||
// This is a child to another item in the list so add it to the children list.
|
||||
$carry[1][] = $item;
|
||||
} else {
|
||||
// This isn't a child to anything in our list so it's a parent.
|
||||
$carry[0][] = $item;
|
||||
}
|
||||
|
||||
return $carry;
|
||||
}, [[], []]);
|
||||
|
||||
if (empty($replies)) {
|
||||
return array_map(function($parent) {
|
||||
return [$parent, []];
|
||||
}, $parents);
|
||||
}
|
||||
|
||||
// Recurse to sort the replies into the correct nesting.
|
||||
$sortedreplies = $this->sort_into_children($replies);
|
||||
|
||||
// Sort the parents and sorted replies into their matching pairs.
|
||||
return array_map(function($parent) use ($sortedreplies) {
|
||||
$parentid = ($this->getid)($parent);
|
||||
return [
|
||||
$parent,
|
||||
array_values(array_filter($sortedreplies, function($replydata) use ($parentid) {
|
||||
return ($this->getparentid)($replydata[0]) == $parentid;
|
||||
}))
|
||||
];
|
||||
}, $parents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the data structure returned from "sort_into_children" and flatten it back
|
||||
* into an array. It does a depth first flatten which maintains the reply ordering.
|
||||
*
|
||||
* @param array $items Items in the data structure returned by "sort_into_children"
|
||||
* @return array A flat array.
|
||||
*/
|
||||
public function flatten_children(array $items) : array {
|
||||
$result = [];
|
||||
|
||||
foreach ($items as [$item, $children]) {
|
||||
$result[] = $item;
|
||||
$result = array_merge($result, $this->flatten_children($children));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Author exporter.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\exporters;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\author as author_entity;
|
||||
use core\external\exporter;
|
||||
use renderer_base;
|
||||
|
||||
require_once($CFG->dirroot . '/mod/forum/lib.php');
|
||||
|
||||
/**
|
||||
* Author exporter.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class author extends exporter {
|
||||
/** @var author_entity $author Author entity */
|
||||
private $author;
|
||||
/** @var array $authorgroups List of groups that the author belongs to */
|
||||
private $authorgroups;
|
||||
/** @var bool $canview Should the author be anonymised? */
|
||||
private $canview;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param author_entity $author The author entity to export
|
||||
* @param stdClass[] $authorgroups The list of groups that the author belongs to
|
||||
* @param bool $canview Can the requesting user view this author or should it be anonymised?
|
||||
* @param array $related The related data for the export.
|
||||
*/
|
||||
public function __construct(author_entity $author, array $authorgroups = [], bool $canview = true, array $related = []) {
|
||||
$this->author = $author;
|
||||
$this->authorgroups = $authorgroups;
|
||||
$this->canview = $canview;
|
||||
return parent::__construct([], $related);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of additional properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_other_properties() {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => PARAM_INT,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'fullname' => [
|
||||
'type' => PARAM_TEXT,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'groups' => [
|
||||
'multiple' => true,
|
||||
'optional' => true,
|
||||
'type' => [
|
||||
'id' => ['type' => PARAM_INT],
|
||||
'urls' => [
|
||||
'type' => [
|
||||
'image' => [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'urls' => [
|
||||
'type' => [
|
||||
'profile' => [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'profileimage' => [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the additional values to inject while exporting.
|
||||
*
|
||||
* @param renderer_base $output The renderer.
|
||||
* @return array Keys are the property names, values are their values.
|
||||
*/
|
||||
protected function get_other_values(renderer_base $output) {
|
||||
$author = $this->author;
|
||||
$urlfactory = $this->related['urlfactory'];
|
||||
|
||||
if ($this->canview) {
|
||||
$groups = array_map(function($group) {
|
||||
$imageurl = get_group_picture_url($group, $group->courseid);
|
||||
return [
|
||||
'id' => $group->id,
|
||||
'urls' => [
|
||||
'image' => $imageurl ? $imageurl->out(false) : null
|
||||
]
|
||||
];
|
||||
}, $this->authorgroups);
|
||||
|
||||
return [
|
||||
'id' => $author->get_id(),
|
||||
'fullname' => $author->get_full_name(),
|
||||
'groups' => $groups,
|
||||
'urls' => [
|
||||
'profile' => ($urlfactory->get_author_profile_url($author))->out(false),
|
||||
'profileimage' => ($urlfactory->get_author_profile_image_url($author))->out(false)
|
||||
]
|
||||
];
|
||||
} else {
|
||||
// The author should be anonymised.
|
||||
return [
|
||||
'id' => null,
|
||||
'fullname' => get_string('forumauthorhidden', 'mod_forum'),
|
||||
'groups' => [],
|
||||
'urls' => [
|
||||
'profile' => null,
|
||||
'profileimage' => null
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of objects that are related.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_related() {
|
||||
return [
|
||||
'urlfactory' => 'mod_forum\local\factories\url',
|
||||
'context' => 'context'
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Discussion exporter class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\exporters;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\discussion as discussion_entity;
|
||||
use mod_forum\local\exporters\post as post_exporter;
|
||||
use mod_forum\local\factories\exporter as exporter_factory;
|
||||
use core\external\exporter;
|
||||
use renderer_base;
|
||||
|
||||
/**
|
||||
* Discussion exporter class.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class discussion extends exporter {
|
||||
/** @var discussion_entity $discussion Discussion to export */
|
||||
private $discussion;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param discussion_entity $discussion Discussion to export
|
||||
* @param array $related The related export data
|
||||
*/
|
||||
public function __construct(discussion_entity $discussion, array $related = []) {
|
||||
$this->discussion = $discussion;
|
||||
|
||||
return parent::__construct([], $related);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of additional properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_other_properties() {
|
||||
return [
|
||||
'id' => ['type' => PARAM_INT],
|
||||
'forumid' => ['type' => PARAM_INT],
|
||||
'pinned' => ['type' => PARAM_BOOL],
|
||||
'name' => ['type' => PARAM_TEXT],
|
||||
'group' => [
|
||||
'optional' => true,
|
||||
'type' => [
|
||||
'name' => ['type' => PARAM_TEXT],
|
||||
'urls' => [
|
||||
'type' => [
|
||||
'picture' => [
|
||||
'optional' => true,
|
||||
'type' => PARAM_URL,
|
||||
],
|
||||
'userlist' => [
|
||||
'optional' => true,
|
||||
'type' => PARAM_URL,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'times' => [
|
||||
'type' => [
|
||||
'modified' => ['type' => PARAM_INT],
|
||||
'start' => ['type' => PARAM_INT],
|
||||
'end' => ['type' => PARAM_INT],
|
||||
],
|
||||
],
|
||||
'userstate' => [
|
||||
'type' => [
|
||||
'subscribed' => ['type' => PARAM_BOOL],
|
||||
],
|
||||
],
|
||||
'capabilities' => [
|
||||
'type' => [
|
||||
'subscribe' => ['type' => PARAM_BOOL],
|
||||
'move' => ['type' => PARAM_BOOL],
|
||||
'pin' => ['type' => PARAM_BOOL],
|
||||
'post' => ['type' => PARAM_BOOL]
|
||||
]
|
||||
],
|
||||
'urls' => [
|
||||
'type' => [
|
||||
'view' => ['type' => PARAM_URL],
|
||||
'viewlatest' => [
|
||||
'optional' => true,
|
||||
'type' => PARAM_URL
|
||||
],
|
||||
'viewfirstunread' => [
|
||||
'optional' => true,
|
||||
'type' => PARAM_URL,
|
||||
],
|
||||
'markasread' => ['type' => PARAM_URL],
|
||||
'subscribe' => ['type' => PARAM_URL]
|
||||
],
|
||||
],
|
||||
'timed' => [
|
||||
'type' => [
|
||||
'istimed' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'visible' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the additional values to inject while exporting.
|
||||
*
|
||||
* @param renderer_base $output The renderer.
|
||||
* @return array Keys are the property names, values are their values.
|
||||
*/
|
||||
protected function get_other_values(renderer_base $output) {
|
||||
|
||||
$capabilitymanager = $this->related['capabilitymanager'];
|
||||
$urlfactory = $this->related['urlfactory'];
|
||||
|
||||
$forum = $this->related['forum'];
|
||||
$forumrecord = $this->get_forum_record();
|
||||
$user = $this->related['user'];
|
||||
$discussion = $this->discussion;
|
||||
|
||||
$groupdata = null;
|
||||
if ($discussion->has_group() && $group = $this->related['groupsbyid'][$discussion->get_group_id()]) {
|
||||
$groupdata = [
|
||||
'name' => $group->name,
|
||||
'urls' => [],
|
||||
];
|
||||
$canviewparticipants = $capabilitymanager->can_view_participants($user, $discussion);
|
||||
if (!$group->hidepicture) {
|
||||
$url = get_group_picture_url($group, $forum->get_course_id());
|
||||
if (!empty($url)) {
|
||||
$groupdata['urls']['picture'] = $url;
|
||||
}
|
||||
}
|
||||
if ($canviewparticipants) {
|
||||
$groupdata['urls']['userlist'] = (new \moodle_url('/user/index.php', [
|
||||
'id' => $forum->get_course_id(),
|
||||
'group' => $group->id,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
$viewfirstunreadurl = $urlfactory->get_discussion_view_first_unread_post_url_from_discussion($discussion);
|
||||
$data = [
|
||||
'id' => $discussion->get_id(),
|
||||
'forumid' => $forum->get_id(),
|
||||
'pinned' => $discussion->is_pinned(),
|
||||
'name' => format_string($discussion->get_name(), true, [
|
||||
'context' => $this->related['context']
|
||||
]),
|
||||
'times' => [
|
||||
'modified' => $discussion->get_time_modified(),
|
||||
'start' => $discussion->get_time_start(),
|
||||
'end' => $discussion->get_time_end(),
|
||||
],
|
||||
'userstate' => [
|
||||
'subscribed' => \mod_forum\subscriptions::is_subscribed($user->id, $forumrecord, $discussion->get_id()),
|
||||
],
|
||||
'capabilities' => [
|
||||
'subscribe' => $capabilitymanager->can_subscribe_to_discussion($user, $discussion),
|
||||
'move' => $capabilitymanager->can_move_discussion($user, $discussion),
|
||||
'pin' => $capabilitymanager->can_pin_discussion($user, $discussion),
|
||||
'post' => $capabilitymanager->can_post_in_discussion($user, $discussion)
|
||||
],
|
||||
'urls' => [
|
||||
'view' => $urlfactory->get_discussion_view_url_from_discussion($discussion)->out(false),
|
||||
'viewfirstunread' => $viewfirstunreadurl->out(false),
|
||||
'markasread' => $urlfactory->get_mark_discussion_as_read_url_from_discussion($forum, $discussion)->out(false),
|
||||
'subscribe' => $urlfactory->get_discussion_subscribe_url($discussion)->out(false)
|
||||
]
|
||||
];
|
||||
|
||||
if (!empty($this->related['latestpostid'])) {
|
||||
$data['urls']['viewlatest'] = $urlfactory->get_discussion_view_latest_post_url_from_discussion(
|
||||
$discussion,
|
||||
$this->related['latestpostid']
|
||||
)->out(false);
|
||||
}
|
||||
|
||||
if ($groupdata) {
|
||||
$data['group'] = $groupdata;
|
||||
}
|
||||
|
||||
$canviewhiddentimedposts = $capabilitymanager->can_view_hidden_posts($user);
|
||||
$canalwaysseetimedpost = $user->id == $discussion->get_user_id() || $canviewhiddentimedposts;
|
||||
$data['timed']['istimed'] = $canalwaysseetimedpost ? $discussion->is_timed_discussion() : null;
|
||||
$data['timed']['visible'] = $canalwaysseetimedpost ? $discussion->is_timed_discussion_visible() : null;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the legacy forum record from the forum entity.
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
private function get_forum_record() {
|
||||
$forumdbdatamapper = $this->related['legacydatamapperfactory']->get_forum_data_mapper();
|
||||
return $forumdbdatamapper->to_legacy_object($this->related['forum']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of objects that are related.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_related() {
|
||||
return [
|
||||
'legacydatamapperfactory' => 'mod_forum\local\factories\legacy_data_mapper',
|
||||
'context' => 'context',
|
||||
'forum' => 'mod_forum\local\entities\forum',
|
||||
'capabilitymanager' => 'mod_forum\local\managers\capability',
|
||||
'urlfactory' => 'mod_forum\local\factories\url',
|
||||
'user' => 'stdClass',
|
||||
'groupsbyid' => 'stdClass[]',
|
||||
'latestpostid' => 'int?'
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Discussion summaries exporter.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\exporters;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\discussion as discussion_entity;
|
||||
use mod_forum\local\exporters\post as post_exporter;
|
||||
use core\external\exporter;
|
||||
use renderer_base;
|
||||
|
||||
/**
|
||||
* Discussion summaries exporter.
|
||||
*
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class discussion_summaries extends exporter {
|
||||
/** @var discussion_summary_entity[] The list of discussion summaries to export */
|
||||
private $discussions;
|
||||
|
||||
/** @var stdClass[] The group information for each author */
|
||||
private $groupsbyid;
|
||||
|
||||
/** @var stdClass[] The group information for each author */
|
||||
private $groupsbyauthorid;
|
||||
|
||||
/** @var int[] Discussion reply counts indexed by dicussion id */
|
||||
private $discussionreplycount;
|
||||
|
||||
/** @var int[] Discussion unread counts indexed by dicussion id */
|
||||
private $discussionunreadcount;
|
||||
|
||||
/** @var array The latest post in each discussion */
|
||||
private $latestpostids;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param discussion_summary_entity[] $discussion The list of discussion summaries to export
|
||||
* @param stdClass[] $groupsbyid The group information for each author
|
||||
* @param stdClass[] $groupsbyauthorid The group information for each author
|
||||
* @param int[] $discussionreplycount Discussion reply counts indexed by dicussion id
|
||||
* @param int[] $discussionunreadcount Discussion unread counts indexed by dicussion id
|
||||
* @param int[] $latestpostids List of latest post ids indexed by discussion id
|
||||
* @param array $related The related
|
||||
*/
|
||||
public function __construct(
|
||||
array $discussions,
|
||||
array $groupsbyid,
|
||||
array $groupsbyauthorid,
|
||||
array $discussionreplycount,
|
||||
array $discussionunreadcount,
|
||||
array $latestpostids,
|
||||
array $related = []
|
||||
) {
|
||||
$this->discussions = $discussions;
|
||||
$this->groupsbyid = $groupsbyid;
|
||||
$this->groupsbyauthorid = $groupsbyauthorid;
|
||||
$this->discussionreplycount = $discussionreplycount;
|
||||
$this->discussionunreadcount = $discussionunreadcount;
|
||||
$this->latestpostids = $latestpostids;
|
||||
return parent::__construct([], $related);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of additional properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_other_properties() {
|
||||
return [
|
||||
'summaries' => [
|
||||
'type' => discussion_summary::read_properties_definition(),
|
||||
'multiple' => true
|
||||
],
|
||||
'state' => [
|
||||
'type' => [
|
||||
'hasdiscussions' => ['type' => PARAM_BOOL],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the additional values to inject while exporting.
|
||||
*
|
||||
* @param renderer_base $output The renderer.
|
||||
* @return array Keys are the property names, values are their values.
|
||||
*/
|
||||
protected function get_other_values(renderer_base $output) {
|
||||
$exporteddiscussions = [];
|
||||
$related = $this->related;
|
||||
|
||||
foreach ($this->discussions as $discussion) {
|
||||
$discussionid = $discussion->get_discussion()->get_id();
|
||||
$replycount = isset($this->discussionreplycount[$discussionid]) ? $this->discussionreplycount[$discussionid] : 0;
|
||||
$unreadcount = isset($this->discussionunreadcount[$discussionid]) ? $this->discussionunreadcount[$discussionid] : 0;
|
||||
$latestpostid = isset($this->latestpostids[$discussionid]) ? $this->latestpostids[$discussionid] : 0;
|
||||
$exporter = new discussion_summary(
|
||||
$discussion,
|
||||
$this->groupsbyid,
|
||||
$this->groupsbyauthorid,
|
||||
$replycount,
|
||||
$unreadcount,
|
||||
$latestpostid,
|
||||
$related
|
||||
);
|
||||
$exporteddiscussions[] = $exporter->export($output);
|
||||
}
|
||||
|
||||
return [
|
||||
'summaries' => $exporteddiscussions,
|
||||
'state' => [
|
||||
'hasdiscussions' => !empty($exporteddiscussions),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of objects that are related.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_related() {
|
||||
return [
|
||||
'legacydatamapperfactory' => 'mod_forum\local\factories\legacy_data_mapper',
|
||||
'context' => 'context',
|
||||
'forum' => 'mod_forum\local\entities\forum',
|
||||
'capabilitymanager' => 'mod_forum\local\managers\capability',
|
||||
'urlfactory' => 'mod_forum\local\factories\url',
|
||||
'user' => 'stdClass',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Discussion summary exporter class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\exporters;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\discussion_summary as discussion_summary_entity;
|
||||
use core\external\exporter;
|
||||
use renderer_base;
|
||||
|
||||
/**
|
||||
* Discussion summary exporter class.
|
||||
*
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class discussion_summary extends exporter {
|
||||
/** @var discussion_summary_entity The discussion summary information */
|
||||
private $summary;
|
||||
|
||||
/** @var stdClass[] The group information for each author */
|
||||
private $groupsbyid;
|
||||
|
||||
/** @var stdClass[] The group information for each author */
|
||||
private $groupsbyauthorid;
|
||||
|
||||
/** @var int The number of replies to the discussion */
|
||||
private $replycount;
|
||||
|
||||
/** @var int number of unread posts if the user is tracking these */
|
||||
private $unreadcount;
|
||||
|
||||
/** @var int The latest post id in the discussion */
|
||||
private $latestpostid;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param discussion_summary_entity $summary The discussion summary information
|
||||
* @param stdClass[] $groupsbyid The group information for each author
|
||||
* @param stdClass[] $groupsbyauthorid The group information for each author
|
||||
* @param int $replycount The number of replies to the discussion
|
||||
* @param int $unreadcount number of unread posts if the user is tracking these
|
||||
* @param int $latestpostid The latest post id in the discussion
|
||||
* @param array $related The related objects
|
||||
*/
|
||||
public function __construct(
|
||||
discussion_summary_entity $summary,
|
||||
array $groupsbyid,
|
||||
array $groupsbyauthorid,
|
||||
int $replycount,
|
||||
int $unreadcount,
|
||||
int $latestpostid,
|
||||
array $related = []
|
||||
) {
|
||||
$this->summary = $summary;
|
||||
$this->groupsbyid = $groupsbyid;
|
||||
$this->groupsbyauthorid = $groupsbyauthorid;
|
||||
$this->replycount = $replycount;
|
||||
$this->unreadcount = $unreadcount;
|
||||
$this->latestpostid = $latestpostid;
|
||||
return parent::__construct([], $related);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of additional properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_other_properties() {
|
||||
return [
|
||||
'id' => ['type' => PARAM_INT],
|
||||
'discussion' => [
|
||||
'type' => discussion::read_properties_definition(),
|
||||
],
|
||||
'replies' => ['type' => PARAM_INT],
|
||||
'unread' => ['type' => PARAM_INT],
|
||||
'firstpostauthor' => [
|
||||
'type' => author::read_properties_definition(),
|
||||
],
|
||||
'latestpostauthor' => [
|
||||
'type' => author::read_properties_definition(),
|
||||
],
|
||||
'latestpostid' => ['type' => PARAM_INT],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the additional values to inject while exporting.
|
||||
*
|
||||
* @param renderer_base $output The renderer.
|
||||
* @return array Keys are the property names, values are their values.
|
||||
*/
|
||||
protected function get_other_values(renderer_base $output) {
|
||||
$capabilitymanager = $this->related['capabilitymanager'];
|
||||
$forum = $this->related['forum'];
|
||||
$user = $this->related['user'];
|
||||
$discussion = $this->summary->get_discussion();
|
||||
|
||||
$related = (array) (object) $this->related;
|
||||
$related['latestpostid'] = $this->latestpostid;
|
||||
$related['groupsbyid'] = $this->groupsbyid;
|
||||
$discussionexporter = new discussion($discussion, $related);
|
||||
|
||||
$related = [
|
||||
'urlfactory' => $this->related['urlfactory'],
|
||||
'context' => $this->related['forum']->get_context(),
|
||||
];
|
||||
|
||||
$firstpostauthor = new author(
|
||||
$this->summary->get_first_post_author(),
|
||||
$this->groupsbyauthorid[$this->summary->get_first_post_author()->get_id()],
|
||||
$capabilitymanager->can_view_post(
|
||||
$user,
|
||||
$discussion,
|
||||
$this->summary->get_first_post()
|
||||
),
|
||||
$related
|
||||
);
|
||||
|
||||
$latestpostauthor = new author(
|
||||
$this->summary->get_latest_post_author(),
|
||||
[],
|
||||
$capabilitymanager->can_view_post(
|
||||
$user,
|
||||
$discussion,
|
||||
$this->summary->get_first_post()
|
||||
),
|
||||
$related
|
||||
);
|
||||
|
||||
return [
|
||||
'id' => $discussion->get_id(),
|
||||
'discussion' => $discussionexporter->export($output),
|
||||
'replies' => $this->replycount,
|
||||
'unread' => $this->unreadcount,
|
||||
'firstpostauthor' => $firstpostauthor->export($output),
|
||||
'latestpostauthor' => $latestpostauthor->export($output),
|
||||
'latestpostid' => $this->latestpostid,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of objects that are related.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_related() {
|
||||
return [
|
||||
'legacydatamapperfactory' => 'mod_forum\local\factories\legacy_data_mapper',
|
||||
'context' => 'context',
|
||||
'forum' => 'mod_forum\local\entities\forum',
|
||||
'capabilitymanager' => 'mod_forum\local\managers\capability',
|
||||
'urlfactory' => 'mod_forum\local\factories\url',
|
||||
'user' => 'stdClass',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Forum Exporter.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\exporters;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\forum as forum_entity;
|
||||
use mod_forum\local\exporters\post as post_exporter;
|
||||
use core\external\exporter;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Forum class.
|
||||
*
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class forum extends exporter {
|
||||
/** @var forum_entity The entity relating to the forum being displayed */
|
||||
private $forum;
|
||||
|
||||
/**
|
||||
* Constructor for the forum exporter.
|
||||
*
|
||||
* @param forum_entity $forum The forum being displayed
|
||||
* @param array $related The related objects
|
||||
*/
|
||||
public function __construct(forum_entity $forum, $related = []) {
|
||||
$this->forum = $forum;
|
||||
return parent::__construct([], $related);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of additional properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_other_properties() {
|
||||
return [
|
||||
'id' => ['type' => PARAM_INT],
|
||||
'state' => [
|
||||
'type' => [
|
||||
'groupmode' => ['type' => PARAM_INT],
|
||||
],
|
||||
],
|
||||
'userstate' => [
|
||||
'type' => [
|
||||
'tracked' => ['type' => PARAM_INT],
|
||||
],
|
||||
],
|
||||
'capabilities' => [
|
||||
'type' => [
|
||||
'viewdiscussions' => ['type' => PARAM_BOOL],
|
||||
'create' => ['type' => PARAM_BOOL],
|
||||
'subscribe' => ['type' => PARAM_BOOL],
|
||||
]
|
||||
],
|
||||
'urls' => [
|
||||
'type' => [
|
||||
'create' => ['type' => PARAM_URL],
|
||||
'markasread' => ['type' => PARAM_URL],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the additional values to inject while exporting.
|
||||
*
|
||||
* @param renderer_base $output The renderer.
|
||||
* @return array Keys are the property names, values are their values.
|
||||
*/
|
||||
protected function get_other_values(renderer_base $output) {
|
||||
$capabilitymanager = $this->related['capabilitymanager'];
|
||||
$urlfactory = $this->related['urlfactory'];
|
||||
$user = $this->related['user'];
|
||||
$currentgroup = $this->related['currentgroup'];
|
||||
|
||||
return [
|
||||
'id' => $this->forum->get_id(),
|
||||
'state' => [
|
||||
'groupmode' => $this->forum->get_effective_group_mode(),
|
||||
],
|
||||
'userstate' => [
|
||||
'tracked' => forum_tp_is_tracked($this->get_forum_record(), $this->related['user']),
|
||||
],
|
||||
'capabilities' => [
|
||||
'viewdiscussions' => $capabilitymanager->can_view_discussions($user),
|
||||
'create' => $capabilitymanager->can_create_discussions($user, $currentgroup),
|
||||
'subscribe' => $capabilitymanager->can_subscribe_to_forum($user),
|
||||
],
|
||||
'urls' => [
|
||||
'create' => $urlfactory->get_discussion_create_url($this->forum)->out(false),
|
||||
'markasread' => $urlfactory->get_mark_all_discussions_as_read_url($this->forum)->out(false),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of objects that are related.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_related() {
|
||||
return [
|
||||
'legacydatamapperfactory' => 'mod_forum\local\factories\legacy_data_mapper',
|
||||
'capabilitymanager' => 'mod_forum\local\managers\capability',
|
||||
'urlfactory' => 'mod_forum\local\factories\url',
|
||||
'user' => 'stdClass',
|
||||
'currentgroup' => 'int?',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the legacy forum record for this forum.
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
private function get_forum_record() : stdClass {
|
||||
$forumdbdatamapper = $this->related['legacydatamapperfactory']->get_forum_data_mapper();
|
||||
return $forumdbdatamapper->to_legacy_object($this->forum);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Post exporter class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\exporters;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\post as post_entity;
|
||||
use mod_forum\local\exporters\author as author_exporter;
|
||||
use mod_forum\local\factories\exporter as exporter_factory;
|
||||
use core\external\exporter;
|
||||
use core_files\external\stored_file_exporter;
|
||||
use context;
|
||||
use core_tag_tag;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
|
||||
require_once($CFG->dirroot . '/mod/forum/lib.php');
|
||||
|
||||
/**
|
||||
* Post exporter class.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class post extends exporter {
|
||||
/** @var post_entity $post The post to export */
|
||||
private $post;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param post_entity $post The post to export
|
||||
* @param array $related List of related data
|
||||
*/
|
||||
public function __construct(post_entity $post, array $related = []) {
|
||||
$this->post = $post;
|
||||
return parent::__construct([], $related);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of additional properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_other_properties() {
|
||||
$attachmentdefinition = stored_file_exporter::read_properties_definition();
|
||||
$attachmentdefinition['urls'] = [
|
||||
'type' => [
|
||||
'export' => [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
]
|
||||
]
|
||||
];
|
||||
$attachmentdefinition['html'] = [
|
||||
'type' => [
|
||||
'plagiarism' => [
|
||||
'type' => PARAM_RAW,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
return [
|
||||
'id' => ['type' => PARAM_INT],
|
||||
'subject' => ['type' => PARAM_TEXT],
|
||||
'message' => ['type' => PARAM_RAW],
|
||||
'messageformat' => ['type' => PARAM_INT],
|
||||
'author' => ['type' => author_exporter::read_properties_definition()],
|
||||
'discussionid' => ['type' => PARAM_INT],
|
||||
'hasparent' => ['type' => PARAM_BOOL],
|
||||
'parentid' => [
|
||||
'type' => PARAM_INT,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'timecreated' => ['type' => PARAM_INT],
|
||||
'unread' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'isdeleted' => ['type' => PARAM_BOOL],
|
||||
'haswordcount' => ['type' => PARAM_BOOL],
|
||||
'wordcount' => [
|
||||
'type' => PARAM_INT,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'capabilities' => [
|
||||
'type' => [
|
||||
'view' => ['type' => PARAM_BOOL],
|
||||
'edit' => ['type' => PARAM_BOOL],
|
||||
'delete' => ['type' => PARAM_BOOL],
|
||||
'split' => ['type' => PARAM_BOOL],
|
||||
'reply' => ['type' => PARAM_BOOL],
|
||||
'export' => ['type' => PARAM_BOOL],
|
||||
'controlreadstatus' => ['type' => PARAM_BOOL]
|
||||
]
|
||||
],
|
||||
'urls' => [
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED,
|
||||
'type' => [
|
||||
'view' => [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'viewisolated' => [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'viewparent' => [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'edit' => [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'delete' => [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'split' => [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'reply' => [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'export' => [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'markasread' => [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'markasunread' => [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'discuss' => [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
]
|
||||
]
|
||||
],
|
||||
'attachments' => [
|
||||
'multiple' => true,
|
||||
'type' => $attachmentdefinition
|
||||
],
|
||||
'tags' => [
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED,
|
||||
'multiple' => true,
|
||||
'type' => [
|
||||
'id' => ['type' => PARAM_INT],
|
||||
'tagid' => ['type' => PARAM_INT],
|
||||
'isstandard' => ['type' => PARAM_BOOL],
|
||||
'displayname' => ['type' => PARAM_TEXT],
|
||||
'flag' => ['type' => PARAM_BOOL],
|
||||
'urls' => [
|
||||
'type' => [
|
||||
'view' => ['type' => PARAM_URL]
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'html' => [
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED,
|
||||
'type' => [
|
||||
'rating' => [
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED,
|
||||
'type' => PARAM_RAW
|
||||
],
|
||||
'taglist' => [
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED,
|
||||
'type' => PARAM_RAW
|
||||
],
|
||||
'authorsubheading' => [
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED,
|
||||
'type' => PARAM_RAW
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the additional values to inject while exporting.
|
||||
*
|
||||
* @param renderer_base $output The renderer.
|
||||
* @return array Keys are the property names, values are their values.
|
||||
*/
|
||||
protected function get_other_values(renderer_base $output) {
|
||||
$post = $this->post;
|
||||
$authorgroups = $this->related['authorgroups'];
|
||||
$forum = $this->related['forum'];
|
||||
$discussion = $this->related['discussion'];
|
||||
$author = $this->related['author'];
|
||||
$user = $this->related['user'];
|
||||
$readreceiptcollection = $this->related['readreceiptcollection'];
|
||||
$rating = $this->related['rating'];
|
||||
$tags = $this->related['tags'];
|
||||
$attachments = $this->related['attachments'];
|
||||
$includehtml = $this->related['includehtml'];
|
||||
$isdeleted = $post->is_deleted();
|
||||
$hasrating = $rating != null;
|
||||
$hastags = !empty($tags);
|
||||
$discussionid = $post->get_discussion_id();
|
||||
$parentid = $post->get_parent_id();
|
||||
|
||||
$capabilitymanager = $this->related['capabilitymanager'];
|
||||
$canview = $capabilitymanager->can_view_post($user, $discussion, $post);
|
||||
$canedit = $capabilitymanager->can_edit_post($user, $discussion, $post);
|
||||
$candelete = $capabilitymanager->can_delete_post($user, $discussion, $post);
|
||||
$cansplit = $capabilitymanager->can_split_post($user, $discussion, $post);
|
||||
$canreply = $capabilitymanager->can_reply_to_post($user, $discussion, $post);
|
||||
$canexport = $capabilitymanager->can_export_post($user, $post);
|
||||
$cancontrolreadstatus = $capabilitymanager->can_manually_control_post_read_status($user);
|
||||
|
||||
$urlfactory = $this->related['urlfactory'];
|
||||
$viewurl = $canview ? $urlfactory->get_view_post_url_from_post($post) : null;
|
||||
$viewisolatedurl = $canview ? $urlfactory->get_view_isolated_post_url_from_post($post) : null;
|
||||
$viewparenturl = $post->has_parent() ? $urlfactory->get_view_post_url_from_post_id($discussionid, $parentid) : null;
|
||||
$editurl = $canedit ? $urlfactory->get_edit_post_url_from_post($forum, $post) : null;
|
||||
$deleteurl = $candelete ? $urlfactory->get_delete_post_url_from_post($post) : null;
|
||||
$spliturl = $cansplit ? $urlfactory->get_split_discussion_at_post_url_from_post($post) : null;
|
||||
$replyurl = $canreply ? $urlfactory->get_reply_to_post_url_from_post($post) : null;
|
||||
$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);
|
||||
// Only bother loading the content if the user can see it.
|
||||
$loadcontent = $canview && !$isdeleted;
|
||||
$exportattachments = $loadcontent && !empty($attachments);
|
||||
|
||||
if ($loadcontent) {
|
||||
$subject = $post->get_subject();
|
||||
$timecreated = $post->get_time_created();
|
||||
$message = $this->get_message($post);
|
||||
} else {
|
||||
$subject = $isdeleted ? get_string('forumsubjectdeleted', 'forum') : get_string('forumsubjecthidden', 'forum');
|
||||
$message = $isdeleted ? get_string('forumbodydeleted', 'forum') : get_string('forumbodyhidden', 'forum');
|
||||
$timecreated = null;
|
||||
|
||||
if ($isdeleted) {
|
||||
$exportedauthor->fullname = null;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $post->get_id(),
|
||||
'subject' => $subject,
|
||||
'message' => $message,
|
||||
'messageformat' => $post->get_message_format(),
|
||||
'author' => $exportedauthor,
|
||||
'discussionid' => $post->get_discussion_id(),
|
||||
'hasparent' => $post->has_parent(),
|
||||
'parentid' => $post->has_parent() ? $post->get_parent_id() : null,
|
||||
'timecreated' => $timecreated,
|
||||
'unread' => ($loadcontent && $readreceiptcollection) ? !$readreceiptcollection->has_user_read_post($user, $post) : null,
|
||||
'isdeleted' => $isdeleted,
|
||||
'haswordcount' => $forum->should_display_word_count(),
|
||||
'wordcount' => $forum->should_display_word_count() ? count_words($message) : null,
|
||||
'capabilities' => [
|
||||
'view' => $canview,
|
||||
'edit' => $canedit,
|
||||
'delete' => $candelete,
|
||||
'split' => $cansplit,
|
||||
'reply' => $canreply,
|
||||
'export' => $canexport,
|
||||
'controlreadstatus' => $cancontrolreadstatus
|
||||
],
|
||||
'urls' => [
|
||||
'view' => $viewurl ? $viewurl->out(false) : null,
|
||||
'viewisolated' => $viewisolatedurl ? $viewisolatedurl->out(false) : null,
|
||||
'viewparent' => $viewparenturl ? $viewparenturl->out(false) : null,
|
||||
'edit' => $editurl ? $editurl->out(false) : null,
|
||||
'delete' => $deleteurl ? $deleteurl->out(false) : null,
|
||||
'split' => $spliturl ? $spliturl->out(false) : null,
|
||||
'reply' => $replyurl ? $replyurl->out(false) : null,
|
||||
'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) : [],
|
||||
'html' => $includehtml ? [
|
||||
'rating' => ($loadcontent && $hasrating) ? $output->render($rating) : null,
|
||||
'taglist' => ($loadcontent && $hastags) ? $output->tag_list($tags) : null,
|
||||
'authorsubheading' => ($loadcontent) ? $this->get_author_subheading_html($exportedauthor, $timecreated) : null
|
||||
] : null
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of objects that are related.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_related() {
|
||||
return [
|
||||
'capabilitymanager' => 'mod_forum\local\managers\capability',
|
||||
'readreceiptcollection' => 'mod_forum\local\entities\post_read_receipt_collection?',
|
||||
'urlfactory' => 'mod_forum\local\factories\url',
|
||||
'forum' => 'mod_forum\local\entities\forum',
|
||||
'discussion' => 'mod_forum\local\entities\discussion',
|
||||
'author' => 'mod_forum\local\entities\author',
|
||||
'user' => 'stdClass',
|
||||
'context' => 'context',
|
||||
'authorgroups' => 'stdClass[]',
|
||||
'attachments' => '\stored_file[]?',
|
||||
'tags' => '\core_tag_tag[]?',
|
||||
'rating' => 'rating?',
|
||||
'includehtml' => 'bool'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message text from a post.
|
||||
*
|
||||
* @param post_entity $post The post
|
||||
* @return string
|
||||
*/
|
||||
private function get_message(post_entity $post) : string {
|
||||
global $CFG;
|
||||
|
||||
$context = $this->related['context'];
|
||||
$message = file_rewrite_pluginfile_urls(
|
||||
$post->get_message(),
|
||||
'pluginfile.php',
|
||||
$context->id,
|
||||
'mod_forum',
|
||||
'post',
|
||||
$post->get_id()
|
||||
);
|
||||
|
||||
if (!empty($CFG->enableplagiarism)) {
|
||||
require_once($CFG->libdir . '/plagiarismlib.php');
|
||||
$forum = $this->related['forum'];
|
||||
$message .= plagiarism_get_links([
|
||||
'userid' => $post->get_author_id(),
|
||||
'content' => $message,
|
||||
'cmid' => $forum->get_course_module_record()->id,
|
||||
'course' => $forum->get_course_id(),
|
||||
'forum' => $forum->get_id()
|
||||
]);
|
||||
}
|
||||
|
||||
$message = format_text(
|
||||
$message,
|
||||
$post->get_message_format(),
|
||||
(object) [
|
||||
'para' => false,
|
||||
'trusted' => $post->is_message_trusted(),
|
||||
'context' => $context
|
||||
]
|
||||
);
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exported attachments for a post.
|
||||
*
|
||||
* @param stored_file[] $attachments The list of attachments for the post
|
||||
* @param post_entity $post The post being exported
|
||||
* @param renderer_base $output Renderer base
|
||||
* @param bool $canexport If the user can export the post (relates to portfolios not exporters like this class)
|
||||
* @return array
|
||||
*/
|
||||
private function export_attachments(array $attachments, post_entity $post, renderer_base $output, bool $canexport) : array {
|
||||
global $CFG;
|
||||
|
||||
$urlfactory = $this->related['urlfactory'];
|
||||
$enableplagiarism = $CFG->enableplagiarism;
|
||||
$forum = $this->related['forum'];
|
||||
$context = $this->related['context'];
|
||||
|
||||
if ($enableplagiarism) {
|
||||
require_once($CFG->libdir . '/plagiarismlib.php' );
|
||||
}
|
||||
|
||||
return array_map(function($attachment) use (
|
||||
$output,
|
||||
$enableplagiarism,
|
||||
$canexport,
|
||||
$context,
|
||||
$forum,
|
||||
$post,
|
||||
$urlfactory
|
||||
) {
|
||||
$exporter = new stored_file_exporter($attachment, ['context' => $context]);
|
||||
$exportedattachment = $exporter->export($output);
|
||||
$exporturl = $canexport ? $urlfactory->get_export_attachment_url_from_post_and_attachment($post, $attachment) : null;
|
||||
|
||||
if ($enableplagiarism) {
|
||||
$plagiarismhtml = plagiarism_get_links([
|
||||
'userid' => $post->get_author_id(),
|
||||
'file' => $attachment,
|
||||
'cmid' => $forum->get_course_module_record()->id,
|
||||
'course' => $forum->get_course_id(),
|
||||
'forum' => $forum->get_id()
|
||||
]);
|
||||
} else {
|
||||
$plagiarismhtml = null;
|
||||
}
|
||||
|
||||
$exportedattachment->urls = [
|
||||
'export' => $exporturl ? $exporturl->out(false) : null
|
||||
];
|
||||
$exportedattachment->html = [
|
||||
'plagiarism' => $plagiarismhtml
|
||||
];
|
||||
|
||||
return $exportedattachment;
|
||||
}, $attachments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the list of tags.
|
||||
*
|
||||
* @param core_tag_tag[] $tags List of tags to export
|
||||
* @return array
|
||||
*/
|
||||
private function export_tags(array $tags) : array {
|
||||
$user = $this->related['user'];
|
||||
$context = $this->related['context'];
|
||||
$capabilitymanager = $this->related['capabilitymanager'];
|
||||
$canmanagetags = $capabilitymanager->can_manage_tags($user);
|
||||
|
||||
return array_values(array_map(function($tag) use ($context, $canmanagetags) {
|
||||
$viewurl = core_tag_tag::make_url($tag->tagcollid, $tag->rawname, 0, $context->id);
|
||||
return [
|
||||
'id' => $tag->taginstanceid,
|
||||
'tagid' => $tag->id,
|
||||
'isstandard' => $tag->isstandard,
|
||||
'displayname' => $tag->get_display_name(),
|
||||
'flag' => $canmanagetags && !empty($tag->flag),
|
||||
'urls' => [
|
||||
'view' => $viewurl->out(false)
|
||||
]
|
||||
];
|
||||
}, $tags));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTML to display as a subheading in a post.
|
||||
*
|
||||
* @param stdClass $exportedauthor The exported author object
|
||||
* @param int $timecreated The post time created timestamp if it's to be displayed
|
||||
* @return string
|
||||
*/
|
||||
private function get_author_subheading_html(stdClass $exportedauthor, int $timecreated) : string {
|
||||
$fullname = $exportedauthor->fullname;
|
||||
$profileurl = $exportedauthor->urls['profile'] ?? null;
|
||||
$formatteddate = userdate($timecreated, get_string('strftimedaydatetime', 'core_langconfig'));
|
||||
$name = $profileurl ? "<a href=\"{$profileurl}\">{$fullname}</a>" : $fullname;
|
||||
$date = "<time>{$formatteddate}</time>";
|
||||
return get_string('bynameondate', 'mod_forum', ['name' => $name, 'date' => $date]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Posts exporter class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\exporters;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\author as author_entity;
|
||||
use mod_forum\local\entities\post as post_entity;
|
||||
use mod_forum\local\exporters\post as post_exporter;
|
||||
use core\external\exporter;
|
||||
use renderer_base;
|
||||
|
||||
require_once($CFG->dirroot . '/mod/forum/lib.php');
|
||||
|
||||
/**
|
||||
* Posts exporter class.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class posts extends exporter {
|
||||
/** @var post_entity[] $posts List of posts to export */
|
||||
private $posts;
|
||||
/** @var author_entity[] $authorsbyid List of authors for the posts indexed by author id */
|
||||
private $authorsbyid;
|
||||
/** @var array $attachmentsbypostid List of attachments indexed by post id */
|
||||
private $attachmentsbypostid;
|
||||
/** @var array $groupsbyauthorid List of author's groups indexed by author id */
|
||||
private $groupsbyauthorid;
|
||||
/** @var array $tagsbypostid List of tags indexed by post id */
|
||||
private $tagsbypostid;
|
||||
/** @var array $ratingbypostid List of ratings indexed by post id */
|
||||
private $ratingbypostid;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param post_entity[] $posts List of posts to export
|
||||
* @param author_entity[] $authorsbyid List of authors for the posts indexed by author id
|
||||
* @param array $attachmentsbypostid List of attachments indexed by post id
|
||||
* @param array $groupsbyauthorid List of author's groups indexed by author id
|
||||
* @param array $tagsbypostid List of tags indexed by post id
|
||||
* @param array $ratingbypostid List of ratings indexed by post id
|
||||
* @param array $related The related objects for exporting
|
||||
*/
|
||||
public function __construct(
|
||||
array $posts,
|
||||
array $authorsbyid = [],
|
||||
array $attachmentsbypostid = [],
|
||||
array $groupsbyauthorid = [],
|
||||
array $tagsbypostid = [],
|
||||
array $ratingbypostid = [],
|
||||
array $related = []
|
||||
) {
|
||||
$this->posts = $posts;
|
||||
$this->authorsbyid = $authorsbyid;
|
||||
$this->attachmentsbypostid = $attachmentsbypostid;
|
||||
$this->groupsbyauthorid = $groupsbyauthorid;
|
||||
$this->tagsbypostid = $tagsbypostid;
|
||||
$this->ratingbypostid = $ratingbypostid;
|
||||
return parent::__construct([], $related);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of additional properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_other_properties() {
|
||||
return [
|
||||
'posts' => [
|
||||
'type' => post_exporter::read_properties_definition(),
|
||||
'multiple' => true
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the additional values to inject while exporting.
|
||||
*
|
||||
* @param renderer_base $output The renderer.
|
||||
* @return array Keys are the property names, values are their values.
|
||||
*/
|
||||
protected function get_other_values(renderer_base $output) {
|
||||
$related = $this->related;
|
||||
$authorsbyid = $this->authorsbyid;
|
||||
$attachmentsbypostid = $this->attachmentsbypostid;
|
||||
$groupsbyauthorid = $this->groupsbyauthorid;
|
||||
$tagsbypostid = $this->tagsbypostid;
|
||||
$ratingbypostid = $this->ratingbypostid;
|
||||
$exportedposts = array_map(
|
||||
function($post) use (
|
||||
$related,
|
||||
$authorsbyid,
|
||||
$attachmentsbypostid,
|
||||
$groupsbyauthorid,
|
||||
$tagsbypostid,
|
||||
$ratingbypostid,
|
||||
$output
|
||||
) {
|
||||
$authorid = $post->get_author_id();
|
||||
$postid = $post->get_id();
|
||||
$author = isset($authorsbyid[$authorid]) ? $authorsbyid[$authorid] : [];
|
||||
$attachments = isset($attachmentsbypostid[$postid]) ? $attachmentsbypostid[$postid] : [];
|
||||
$authorgroups = isset($groupsbyauthorid[$authorid]) ? $groupsbyauthorid[$authorid] : [];
|
||||
$tags = isset($tagsbypostid[$postid]) ? $tagsbypostid[$postid] : [];
|
||||
$rating = isset($ratingbypostid[$postid]) ? $ratingbypostid[$postid] : null;
|
||||
$exporter = new post_exporter($post, array_merge($related, [
|
||||
'author' => $author,
|
||||
'attachments' => $attachments,
|
||||
'authorgroups' => $authorgroups,
|
||||
'tags' => $tags,
|
||||
'rating' => $rating
|
||||
]));
|
||||
return $exporter->export($output);
|
||||
},
|
||||
$this->posts
|
||||
);
|
||||
|
||||
return [
|
||||
'posts' => $exportedposts
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of objects that are related.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_related() {
|
||||
return [
|
||||
'capabilitymanager' => 'mod_forum\local\managers\capability',
|
||||
'urlfactory' => 'mod_forum\local\factories\url',
|
||||
'forum' => 'mod_forum\local\entities\forum',
|
||||
'discussion' => 'mod_forum\local\entities\discussion',
|
||||
'readreceiptcollection' => 'mod_forum\local\entities\post_read_receipt_collection?',
|
||||
'user' => 'stdClass',
|
||||
'context' => 'context',
|
||||
'includehtml' => 'bool'
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Builder factory.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @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\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;
|
||||
use mod_forum\local\factories\manager as manager_factory;
|
||||
use renderer_base;
|
||||
|
||||
/**
|
||||
* Builder factory to construct any builders for forum.
|
||||
*
|
||||
* See:
|
||||
* https://designpatternsphp.readthedocs.io/en/latest/Creational/SimpleFactory/README.html
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class builder {
|
||||
/** @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 renderer_base $rendererbase Renderer base */
|
||||
private $rendererbase;
|
||||
|
||||
/**
|
||||
* 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 renderer_base $rendererbase Renderer base
|
||||
*/
|
||||
public function __construct(
|
||||
legacy_data_mapper_factory $legacydatamapperfactory,
|
||||
exporter_factory $exporterfactory,
|
||||
vault_factory $vaultfactory,
|
||||
manager_factory $managerfactory,
|
||||
renderer_base $rendererbase
|
||||
) {
|
||||
$this->legacydatamapperfactory = $legacydatamapperfactory;
|
||||
$this->exporterfactory = $exporterfactory;
|
||||
$this->vaultfactory = $vaultfactory;
|
||||
$this->managerfactory = $managerfactory;
|
||||
$this->rendererbase = $rendererbase;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get an instance of the exported posts builder.
|
||||
*
|
||||
* @return exported_posts_builder
|
||||
*/
|
||||
public function get_exported_posts_builder() : exported_posts_builder {
|
||||
return new exported_posts_builder(
|
||||
$this->rendererbase,
|
||||
$this->legacydatamapperfactory,
|
||||
$this->exporterfactory,
|
||||
$this->vaultfactory,
|
||||
$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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Entity factory.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @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\author as author_entity;
|
||||
use mod_forum\local\entities\discussion as discussion_entity;
|
||||
use mod_forum\local\entities\discussion_summary as discussion_summary_entity;
|
||||
use mod_forum\local\entities\forum as forum_entity;
|
||||
use mod_forum\local\entities\post as post_entity;
|
||||
use mod_forum\local\entities\post_read_receipt_collection as post_read_receipt_collection_entity;
|
||||
use mod_forum\local\entities\sorter as sorter_entity;
|
||||
use stdClass;
|
||||
use context;
|
||||
use cm_info;
|
||||
use user_picture;
|
||||
use moodle_url;
|
||||
|
||||
/**
|
||||
* Entity factory to create the forum entities.
|
||||
*
|
||||
* See:
|
||||
* https://designpatternsphp.readthedocs.io/en/latest/Creational/SimpleFactory/README.html
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class entity {
|
||||
/**
|
||||
* Create a forum entity from a stdClass (legacy forum object).
|
||||
*
|
||||
* @param stdClass $record The forum record
|
||||
* @param context $context The forum module context
|
||||
* @param stdClass $coursemodule Course module record for the forum
|
||||
* @param stdClass $course Course the forum belongs to
|
||||
* @return forum_entity
|
||||
*/
|
||||
public function get_forum_from_stdclass(
|
||||
stdClass $record,
|
||||
context $context,
|
||||
stdClass $coursemodule,
|
||||
stdClass $course
|
||||
) : forum_entity {
|
||||
// Note: cm_info::create loads a cm_info in the context of the current user which
|
||||
// creates hidden dependency on the logged in user (very bad) however it's the best
|
||||
// option to load some data we need which doesn't require the logged in user.
|
||||
// Only use properties which do not require the logged in user.
|
||||
$cm = \cm_info::create($coursemodule);
|
||||
|
||||
return new forum_entity(
|
||||
$context,
|
||||
$coursemodule,
|
||||
$course,
|
||||
// This property is a general module property that isn't affected by the logged in user.
|
||||
$cm->effectivegroupmode,
|
||||
$record->id,
|
||||
$record->course,
|
||||
$record->type,
|
||||
$record->name,
|
||||
$record->intro,
|
||||
$record->introformat,
|
||||
$record->assessed,
|
||||
$record->assesstimestart,
|
||||
$record->assesstimefinish,
|
||||
$record->scale,
|
||||
$record->maxbytes,
|
||||
$record->maxattachments,
|
||||
$record->forcesubscribe,
|
||||
$record->trackingtype,
|
||||
$record->rsstype,
|
||||
$record->rssarticles,
|
||||
$record->timemodified,
|
||||
$record->warnafter,
|
||||
$record->blockafter,
|
||||
$record->blockperiod,
|
||||
$record->completiondiscussions,
|
||||
$record->completionreplies,
|
||||
$record->completionposts,
|
||||
$record->displaywordcount,
|
||||
$record->lockdiscussionafter
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a discussion entity from an stdClass (legacy dicussion object).
|
||||
*
|
||||
* @param stdClass $record Discussion record
|
||||
* @return discussion_entity
|
||||
*/
|
||||
public function get_discussion_from_stdclass(stdClass $record) : discussion_entity {
|
||||
return new discussion_entity(
|
||||
$record->id,
|
||||
$record->course,
|
||||
$record->forum,
|
||||
$record->name,
|
||||
$record->firstpost,
|
||||
$record->userid,
|
||||
$record->groupid,
|
||||
$record->assessed,
|
||||
$record->timemodified,
|
||||
$record->usermodified,
|
||||
$record->timestart,
|
||||
$record->timeend,
|
||||
$record->pinned
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a post entity from an stdClass (legacy post object).
|
||||
*
|
||||
* @param stdClass $record The post record
|
||||
* @return post_entity
|
||||
*/
|
||||
public function get_post_from_stdclass(stdClass $record) : post_entity {
|
||||
return new post_entity(
|
||||
$record->id,
|
||||
$record->discussion,
|
||||
$record->parent,
|
||||
$record->userid,
|
||||
$record->created,
|
||||
$record->modified,
|
||||
$record->mailed,
|
||||
$record->subject,
|
||||
$record->message,
|
||||
$record->messageformat,
|
||||
$record->messagetrust,
|
||||
$record->attachment,
|
||||
$record->totalscore,
|
||||
$record->mailnow,
|
||||
$record->deleted
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an author entity from a user record.
|
||||
*
|
||||
* @param stdClass $record The user record
|
||||
* @return author_entity
|
||||
*/
|
||||
public function get_author_from_stdclass(stdClass $record) : author_entity {
|
||||
return new author_entity(
|
||||
$record->id,
|
||||
$record->picture,
|
||||
$record->firstname,
|
||||
$record->lastname,
|
||||
fullname($record),
|
||||
$record->email,
|
||||
$record->middlename,
|
||||
$record->firstnamephonetic,
|
||||
$record->lastnamephonetic,
|
||||
$record->alternatename,
|
||||
$record->imagealt
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a discussion summary enttiy from stdClasses.
|
||||
*
|
||||
* @param stdClass $discussion The discussion record
|
||||
* @param stdClass $firstpost A post record for the first post in the discussion
|
||||
* @param stdClass $firstpostauthor A user record for the author of the first post
|
||||
* @param stdClass $latestpostauthor A user record for the author of the latest post in the discussion
|
||||
* @return discussion_summary_entity
|
||||
*/
|
||||
public function get_discussion_summary_from_stdclass(
|
||||
stdClass $discussion,
|
||||
stdClass $firstpost,
|
||||
stdClass $firstpostauthor,
|
||||
stdClass $latestpostauthor
|
||||
) : discussion_summary_entity {
|
||||
|
||||
$firstpostauthorentity = $this->get_author_from_stdclass($firstpostauthor);
|
||||
return new discussion_summary_entity(
|
||||
$this->get_discussion_from_stdclass($discussion),
|
||||
$this->get_post_from_stdclass($firstpost, $firstpostauthorentity),
|
||||
$firstpostauthorentity,
|
||||
$this->get_author_from_stdclass($latestpostauthor)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a post read receipt collection entity from a list of read receipt records.
|
||||
*
|
||||
* @param array $records A list of read receipt records.
|
||||
* @return post_read_receipt_collection_entity
|
||||
*/
|
||||
public function get_post_read_receipt_collection_from_stdclasses(array $records) : post_read_receipt_collection_entity {
|
||||
return new post_read_receipt_collection_entity($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sorter entity to sort post entities.
|
||||
*
|
||||
* @return sorter_entity
|
||||
*/
|
||||
public function get_posts_sorter() : sorter_entity {
|
||||
return new sorter_entity(
|
||||
// Get id function for a post_entity.
|
||||
function(post_entity $post) {
|
||||
return $post->get_id();
|
||||
},
|
||||
// Get parent id function for a post_entity.
|
||||
function(post_entity $post) {
|
||||
return $post->get_parent_id();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sorter entity to sort exported posts.
|
||||
*
|
||||
* @return sorter_entity
|
||||
*/
|
||||
public function get_exported_posts_sorter() : sorter_entity {
|
||||
return new sorter_entity(
|
||||
// Get id function for an exported post.
|
||||
function(stdClass $post) {
|
||||
return $post->id;
|
||||
},
|
||||
// Get parent id function for an exported post.
|
||||
function(stdClass $post) {
|
||||
return $post->parentid;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Forum Exporter factory.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @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\entities\post as post_entity;
|
||||
use mod_forum\local\entities\post_read_receipt_collection as post_read_receipt_collection_entity;
|
||||
use mod_forum\local\factories\legacy_data_mapper as legacy_data_mapper_factory;
|
||||
use mod_forum\local\factories\manager as manager_factory;
|
||||
use mod_forum\local\factories\url as url_factory;
|
||||
use mod_forum\local\exporters\forum as forum_exporter;
|
||||
use mod_forum\local\exporters\discussion as discussion_exporter;
|
||||
use mod_forum\local\exporters\discussion_summaries as discussion_summaries_exporter;
|
||||
use mod_forum\local\exporters\post as post_exporter;
|
||||
use mod_forum\local\exporters\posts as posts_exporter;
|
||||
use context;
|
||||
use rating;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* The exporter factory class used to fetch an instance of the different exporter types.
|
||||
*
|
||||
* See:
|
||||
* https://designpatternsphp.readthedocs.io/en/latest/Creational/SimpleFactory/README.html
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class exporter {
|
||||
/** @var legacy_data_mapper_factory The factory to fetch a legacy data mapper */
|
||||
private $legacydatamapperfactory;
|
||||
|
||||
/** @var manager_factory The factory to fetch a new manager */
|
||||
private $managerfactory;
|
||||
|
||||
/** @var url_factory The factory to create urls */
|
||||
private $urlfactory;
|
||||
|
||||
/**
|
||||
* Constructor for the exporter factory.
|
||||
*
|
||||
* @param legacy_data_mapper_factory $legacydatamapperfactory The factory to fetch a legacy data mapper instance
|
||||
* @param manager_factory $managerfactory The factory fo fetch a manager instance
|
||||
* @param url_factory $urlfactory The factory to create urls
|
||||
*/
|
||||
public function __construct(
|
||||
legacy_data_mapper_factory $legacydatamapperfactory,
|
||||
manager_factory $managerfactory,
|
||||
url_factory $urlfactory
|
||||
) {
|
||||
$this->legacydatamapperfactory = $legacydatamapperfactory;
|
||||
$this->managerfactory = $managerfactory;
|
||||
$this->urlfactory = $urlfactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new forum exporter for the specified user and forum.
|
||||
*
|
||||
* @param stdClass $user The user viewing the forum
|
||||
* @param forum_entity $forum The forum being viewed
|
||||
* @param int $currentgroup The group currently being viewed
|
||||
* @return forum_exporter
|
||||
*/
|
||||
public function get_forum_exporter(
|
||||
stdClass $user,
|
||||
forum_entity $forum,
|
||||
?int $currentgroup
|
||||
) : forum_exporter {
|
||||
return new forum_exporter($forum, [
|
||||
'legacydatamapperfactory' => $this->legacydatamapperfactory,
|
||||
'capabilitymanager' => $this->managerfactory->get_capability_manager($forum),
|
||||
'urlfactory' => $this->urlfactory,
|
||||
'user' => $user,
|
||||
'currentgroup' => $currentgroup,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the structure of the forum exporter.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_forum_export_structure() : array {
|
||||
return forum_exporter::read_properties_definition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new discussion exporter for the specified user and forum discussion.
|
||||
*
|
||||
* @param stdClass $user The user viewing the forum
|
||||
* @param forum_entity $forum The forum being viewed
|
||||
* @param discussion_entity $discussion The discussion being viewed
|
||||
* @param stdClass[] $groupsbyid The list of groups in the forum
|
||||
* @return discussion_exporter
|
||||
*/
|
||||
public function get_discussion_exporter(
|
||||
stdClass $user,
|
||||
forum_entity $forum,
|
||||
discussion_entity $discussion,
|
||||
array $groupsbyid = []
|
||||
) : discussion_exporter {
|
||||
return new discussion_exporter($discussion, [
|
||||
'context' => $forum->get_context(),
|
||||
'forum' => $forum,
|
||||
'capabilitymanager' => $this->managerfactory->get_capability_manager($forum),
|
||||
'urlfactory' => $this->urlfactory,
|
||||
'user' => $user,
|
||||
'legacydatamapperfactory' => $this->legacydatamapperfactory,
|
||||
'latestpostid' => null,
|
||||
'groupsbyid' => $groupsbyid
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the structure of the discussion exporter.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_discussion_export_structure() {
|
||||
return discussion_exporter::read_properties_definition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new discussion summaries exporter for the specified user and set of discussions.
|
||||
*
|
||||
* @param stdClass $user The user viewing the forum
|
||||
* @param forum_entity $forum The forum being viewed
|
||||
* @param discussion_entity[] $discussions The set of discussions to be shown
|
||||
* @param stdClass[] $groupsbyauthorid The set of groups in an associative array for each author
|
||||
* @param stdClass[] $groupsbyid The set of groups in the forum in an associative array for each group
|
||||
* @param int[] $discussionreplycount The number of replies for each discussion
|
||||
* @param int[] $discussionunreadcount The number of unread posts for each discussion
|
||||
* @param int[] $latestpostids The latest post id for each discussion
|
||||
* @return discussion_summaries_exporter
|
||||
*/
|
||||
public function get_discussion_summaries_exporter(
|
||||
stdClass $user,
|
||||
forum_entity $forum,
|
||||
array $discussions,
|
||||
array $groupsbyid = [],
|
||||
array $groupsbyauthorid = [],
|
||||
array $discussionreplycount = [],
|
||||
array $discussionunreadcount = [],
|
||||
array $latestpostid = []
|
||||
) : discussion_summaries_exporter {
|
||||
return new discussion_summaries_exporter(
|
||||
$discussions,
|
||||
$groupsbyid,
|
||||
$groupsbyauthorid,
|
||||
$discussionreplycount,
|
||||
$discussionunreadcount,
|
||||
$latestpostid,
|
||||
[
|
||||
'legacydatamapperfactory' => $this->legacydatamapperfactory,
|
||||
'context' => $forum->get_context(),
|
||||
'forum' => $forum,
|
||||
'capabilitymanager' => $this->managerfactory->get_capability_manager($forum),
|
||||
'urlfactory' => $this->urlfactory,
|
||||
'user' => $user,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the structure of the discussion summaries exporter.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_discussion_summaries_export_structure() {
|
||||
return discussion_summaries_exporter::read_properties_definition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new post exporter for the specified user and set of post.
|
||||
*
|
||||
* @param stdClass $user The user viewing the forum
|
||||
* @param forum_entity $forum The forum being viewed
|
||||
* @param discussion_entity $discussion The discussion that the post is in
|
||||
* @param post_entity[] $posts The set of posts to be exported
|
||||
* @param author_entity[] $authorsbyid List of authors indexed by author id
|
||||
* @param array $attachmentsbypostid List of attachments for each post indexed by post id
|
||||
* @param array $groupsbyauthorid List of groups for the post authors indexed by author id
|
||||
* @param post_read_receipt_collection_entity|null $readreceiptcollection Details of read receipts for each post
|
||||
* @param array $tagsbypostid List of tags for each post indexed by post id
|
||||
* @param rating[] $ratingbypostid List of ratings for each post indexed by post id
|
||||
* @param bool $includehtml Include some pre-constructed HTML in the export
|
||||
* @return post_exporter
|
||||
*/
|
||||
public function get_posts_exporter(
|
||||
stdClass $user,
|
||||
forum_entity $forum,
|
||||
discussion_entity $discussion,
|
||||
array $posts,
|
||||
array $authorsbyid = [],
|
||||
array $attachmentsbypostid = [],
|
||||
array $groupsbyauthorid = [],
|
||||
post_read_receipt_collection_entity $readreceiptcollection = null,
|
||||
array $tagsbypostid = [],
|
||||
array $ratingbypostid = [],
|
||||
bool $includehtml = false
|
||||
) : posts_exporter {
|
||||
return new posts_exporter($posts, $authorsbyid, $attachmentsbypostid, $groupsbyauthorid, $tagsbypostid, $ratingbypostid, [
|
||||
'capabilitymanager' => $this->managerfactory->get_capability_manager($forum),
|
||||
'urlfactory' => $this->urlfactory,
|
||||
'forum' => $forum,
|
||||
'discussion' => $discussion,
|
||||
'user' => $user,
|
||||
'context' => $forum->get_context(),
|
||||
'readreceiptcollection' => $readreceiptcollection,
|
||||
'includehtml' => $includehtml
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the structure of the posts exporter.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_posts_export_structure() {
|
||||
return posts_exporter::read_properties_definition();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Legacy data mapper factory.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @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\data_mappers\legacy\author as author_data_mapper;
|
||||
use mod_forum\local\data_mappers\legacy\discussion as discussion_data_mapper;
|
||||
use mod_forum\local\data_mappers\legacy\forum as forum_data_mapper;
|
||||
use mod_forum\local\data_mappers\legacy\post as post_data_mapper;
|
||||
|
||||
/**
|
||||
* Legacy data mapper factory.
|
||||
*
|
||||
* See:
|
||||
* https://designpatternsphp.readthedocs.io/en/latest/Creational/SimpleFactory/README.html
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class legacy_data_mapper {
|
||||
/**
|
||||
* Create a legacy forum data mapper.
|
||||
*
|
||||
* @return forum_data_mapper
|
||||
*/
|
||||
public function get_forum_data_mapper() : forum_data_mapper {
|
||||
return new forum_data_mapper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a legacy discussion data mapper.
|
||||
*
|
||||
* @return discussion_data_mapper
|
||||
*/
|
||||
public function get_discussion_data_mapper() : discussion_data_mapper {
|
||||
return new discussion_data_mapper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a legacy post data mapper.
|
||||
*
|
||||
* @return post_data_mapper
|
||||
*/
|
||||
public function get_post_data_mapper() : post_data_mapper {
|
||||
return new post_data_mapper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a legacy author data mapper.
|
||||
*
|
||||
* @return author_data_mapper
|
||||
*/
|
||||
public function get_author_data_mapper() : author_data_mapper {
|
||||
return new author_data_mapper();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Managers factory.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\factories;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/rating/lib.php');
|
||||
|
||||
use mod_forum\local\entities\forum as forum_entity;
|
||||
use mod_forum\local\managers\capability as capability_manager;
|
||||
use rating_manager;
|
||||
|
||||
/**
|
||||
* Managers factory.
|
||||
*
|
||||
* See:
|
||||
* https://designpatternsphp.readthedocs.io/en/latest/Creational/SimpleFactory/README.html
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class manager {
|
||||
/** @var legacy_data_mapper $legacydatamapperfactory Legacy data mapper factory */
|
||||
private $legacydatamapperfactory;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param legacy_data_mapper $legacydatamapperfactory Legacy data mapper factory
|
||||
*/
|
||||
public function __construct(legacy_data_mapper $legacydatamapperfactory) {
|
||||
$this->legacydatamapperfactory = $legacydatamapperfactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a capability manager for the given forum.
|
||||
*
|
||||
* @param forum_entity $forum The forum to manage capabilities for
|
||||
* @return capability_manager
|
||||
*/
|
||||
public function get_capability_manager(forum_entity $forum) {
|
||||
return new capability_manager(
|
||||
$forum,
|
||||
$this->legacydatamapperfactory->get_forum_data_mapper(),
|
||||
$this->legacydatamapperfactory->get_discussion_data_mapper(),
|
||||
$this->legacydatamapperfactory->get_post_data_mapper()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a rating manager.
|
||||
*
|
||||
* @return rating_manager
|
||||
*/
|
||||
public function get_rating_manager() : rating_manager {
|
||||
return new rating_manager();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
<?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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
$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);
|
||||
}
|
||||
|
||||
$exportedpost->firstpost = false;
|
||||
$exportedpost->readonly = $readonly;
|
||||
$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;
|
||||
$post->replies = $sortintoreplies($replies);
|
||||
$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;
|
||||
}
|
||||
|
||||
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 = [];
|
||||
|
||||
return new discussion_list_renderer(
|
||||
$forum,
|
||||
$rendererbase,
|
||||
$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 $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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* A URL factory for the forum.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @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\author as author_entity;
|
||||
use mod_forum\local\entities\forum as forum_entity;
|
||||
use mod_forum\local\entities\discussion as discussion_entity;
|
||||
use mod_forum\local\entities\post as post_entity;
|
||||
use mod_forum\local\factories\legacy_data_mapper as legacy_data_mapper_factory;
|
||||
use moodle_url;
|
||||
use stored_file;
|
||||
use user_picture;
|
||||
|
||||
require_once($CFG->dirroot . '/mod/forum/lib.php');
|
||||
|
||||
/**
|
||||
* A URL factory for the forum.
|
||||
*
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class url {
|
||||
/** @var legacy_data_mapper_factory $legacydatamapperfactory Legacy data mapper factory */
|
||||
private $legacydatamapperfactory;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param legacy_data_mapper_factory $legacydatamapperfactory Legacy data mapper factory
|
||||
*/
|
||||
public function __construct(legacy_data_mapper_factory $legacydatamapperfactory) {
|
||||
$this->legacydatamapperfactory = $legacydatamapperfactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the course url from the given course id.
|
||||
*
|
||||
* @param int $courseid The course id
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_course_url_from_courseid(int $courseid) : moodle_url {
|
||||
return new moodle_url('/course/view.php', [
|
||||
'id' => $courseid,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the course url from the given forum entity.
|
||||
*
|
||||
* @param forum_entity $forum The forum entity
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_course_url_from_forum(forum_entity $forum) : moodle_url {
|
||||
return $this->get_course_url_from_courseid($forum->get_course_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the create discussion url for the given forum.
|
||||
*
|
||||
* @param forum_entity $forum The forum entity
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_discussion_create_url(forum_entity $forum) : moodle_url {
|
||||
return new moodle_url('/mod/forum/post.php', [
|
||||
'forum' => $forum->get_id(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view forum url for the given forum and optionally a page number.
|
||||
*
|
||||
* @param forum_entity $forum The forum entity
|
||||
* @param int|null $pageno The page number
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_forum_view_url_from_forum(forum_entity $forum, ?int $pageno = null) : moodle_url {
|
||||
return $this->get_forum_view_url_from_course_module_id($forum->get_course_module_record()->id, $pageno);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view forum url for the given course module id and optionally a page number.
|
||||
*
|
||||
* @param int $coursemoduleid The course module id
|
||||
* @param int|null $pageno The page number
|
||||
* @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/view.php', [
|
||||
'id' => $coursemoduleid,
|
||||
]);
|
||||
|
||||
if (null !== $pageno) {
|
||||
$url->param('page', $pageno);
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view discussion url from the given discussion id.
|
||||
*
|
||||
* @param int $discussionid The discussion id
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_discussion_view_url_from_discussion_id(int $discussionid) : moodle_url {
|
||||
return new moodle_url('/mod/forum/discuss.php', [
|
||||
'd' => $discussionid
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view discussion url from the given discussion.
|
||||
*
|
||||
* @param discussion_entity $discussion The discussion
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_discussion_view_url_from_discussion(discussion_entity $discussion) : moodle_url {
|
||||
return $this->get_discussion_view_url_from_discussion_id($discussion->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to view the first unread post in a discussion.
|
||||
*
|
||||
* @param discussion_entity $discussion The discussion
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_discussion_view_first_unread_post_url_from_discussion(discussion_entity $discussion) {
|
||||
$viewurl = $this->get_discussion_view_url_from_discussion_id($discussion->get_id());
|
||||
$viewurl->set_anchor('unread');
|
||||
|
||||
return $viewurl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to view the latest post in a discussion.
|
||||
*
|
||||
* @param discussion_entity $discussion The discussion
|
||||
* @param int|null $latestpost The id of the latest post
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_discussion_view_latest_post_url_from_discussion(discussion_entity $discussion, ?int $latestpost) {
|
||||
$viewurl = $this->get_discussion_view_url_from_discussion_id($discussion->get_id());
|
||||
if (null === $latestpost) {
|
||||
return $viewurl;
|
||||
} else {
|
||||
return new moodle_url($viewurl, ['parent' => $latestpost]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to view a discussion from a post.
|
||||
*
|
||||
* @param post_entity $post The post
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_discussion_view_url_from_post(post_entity $post) : moodle_url {
|
||||
return $this->get_discussion_view_url_from_discussion_id($post->get_discussion_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to view a discussion from a discussion id and post id.
|
||||
*
|
||||
* @param int $discussionid The discussion id
|
||||
* @param int $postid The post id
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_view_post_url_from_post_id(int $discussionid, int $postid) : moodle_url {
|
||||
$url = $this->get_discussion_view_url_from_discussion_id($discussionid);
|
||||
$url->set_anchor('p' . $postid);
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to view a post in the context of the rest of the discussion.
|
||||
*
|
||||
* @param post_entity $post The post
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_view_post_url_from_post(post_entity $post) : moodle_url {
|
||||
return $this->get_view_post_url_from_post_id($post->get_discussion_id(), $post->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to view a post and it's replies in isolation without the rest of the
|
||||
* discussion.
|
||||
*
|
||||
* @param int $discussionid The discussion id
|
||||
* @param int $postid The post id
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_view_isolated_post_url_from_post_id(int $discussionid, int $postid) : moodle_url {
|
||||
$url = $this->get_discussion_view_url_from_discussion_id($discussionid);
|
||||
$url->params(['parent' => $postid]);
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to view a post and it's replies in isolation without the rest of the
|
||||
* discussion.
|
||||
*
|
||||
* @param post_entity $post The post
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_view_isolated_post_url_from_post(post_entity $post) : moodle_url {
|
||||
return $this->get_view_isolated_post_url_from_post_id($post->get_discussion_id(), $post->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to edit a post.
|
||||
*
|
||||
* @param forum_entity $forum The forum the post belongs to
|
||||
* @param post_entity $post The post
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_edit_post_url_from_post(forum_entity $forum, post_entity $post) : moodle_url {
|
||||
if ($forum->get_type() == 'single') {
|
||||
return new moodle_url('/course/modedit.php', [
|
||||
'update' => $forum->get_course_module_record()->id,
|
||||
'sesskey' => sesskey(),
|
||||
'return' => 1
|
||||
]);
|
||||
} else {
|
||||
return new moodle_url('/mod/forum/post.php', [
|
||||
'edit' => $post->get_id()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to split a discussion at a post.
|
||||
*
|
||||
* @param post_entity $post The post
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_split_discussion_at_post_url_from_post(post_entity $post) : moodle_url {
|
||||
return new moodle_url('/mod/forum/post.php', [
|
||||
'prune' => $post->get_id()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to delete a post.
|
||||
*
|
||||
* @param post_entity $post The post
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_delete_post_url_from_post(post_entity $post) : moodle_url {
|
||||
return new moodle_url('/mod/forum/post.php', [
|
||||
'delete' => $post->get_id()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to reply to a post.
|
||||
*
|
||||
* @param post_entity $post The post
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_reply_to_post_url_from_post(post_entity $post) : moodle_url {
|
||||
return new moodle_url('/mod/forum/post.php#mformforum', [
|
||||
'reply' => $post->get_id()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to export (see portfolios) a post.
|
||||
*
|
||||
* @param post_entity $post The post
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_export_post_url_from_post(post_entity $post) : ?moodle_url {
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->libdir . '/portfoliolib.php');
|
||||
$button = new \portfolio_add_button();
|
||||
$button->set_callback_options('forum_portfolio_caller', ['postid' => $post->get_id()], 'mod_forum');
|
||||
if ($post->has_attachments()) {
|
||||
$button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
|
||||
} else {
|
||||
$button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
|
||||
}
|
||||
|
||||
$url = $button->to_html(PORTFOLIO_ADD_MOODLE_URL);
|
||||
return $url ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to mark a post as read.
|
||||
*
|
||||
* @param post_entity $post The post
|
||||
* @param int $displaymode The display mode to show the forum in after marking as read
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_mark_post_as_read_url_from_post(post_entity $post, int $displaymode = FORUM_MODE_THREADED) : moodle_url {
|
||||
$params = [
|
||||
'd' => $post->get_discussion_id(),
|
||||
'postid' => $post->get_id(),
|
||||
'mark' => 'read'
|
||||
];
|
||||
|
||||
$url = new moodle_url('/mod/forum/discuss.php', $params);
|
||||
|
||||
if ($displaymode == FORUM_MODE_THREADED) {
|
||||
$url->param('parent', $post->get_parent_id());
|
||||
} else {
|
||||
$url->set_anchor('p' . $post->get_id());
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to mark a post as unread.
|
||||
*
|
||||
* @param post_entity $post The post
|
||||
* @param int $displaymode The display mode to show the forum in after marking as unread
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_mark_post_as_unread_url_from_post(post_entity $post, int $displaymode = FORUM_MODE_THREADED) : moodle_url {
|
||||
$params = [
|
||||
'd' => $post->get_discussion_id(),
|
||||
'postid' => $post->get_id(),
|
||||
'mark' => 'unread'
|
||||
];
|
||||
|
||||
$url = new moodle_url('/mod/forum/discuss.php', $params);
|
||||
|
||||
if ($displaymode == FORUM_MODE_THREADED) {
|
||||
$url->param('parent', $post->get_parent_id());
|
||||
} else {
|
||||
$url->set_anchor('p' . $post->get_id());
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to export attachments for a post.
|
||||
*
|
||||
* @param post_entity $post The post
|
||||
* @param stored_file $attachment
|
||||
* @return moodle_url|null
|
||||
*/
|
||||
public function get_export_attachment_url_from_post_and_attachment(post_entity $post, stored_file $attachment) : ?moodle_url {
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->libdir . '/portfoliolib.php');
|
||||
$button = new \portfolio_add_button();
|
||||
$button->set_callback_options(
|
||||
'forum_portfolio_caller',
|
||||
['postid' => $post->get_id(), 'attachment' => $attachment->get_id()],
|
||||
'mod_forum'
|
||||
);
|
||||
$button->set_format_by_file($attachment);
|
||||
$url = $button->to_html(PORTFOLIO_ADD_MOODLE_URL);
|
||||
return $url ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to view an author's profile.
|
||||
*
|
||||
* @param author_entity $author The author
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_author_profile_url(author_entity $author) : moodle_url {
|
||||
return new moodle_url('/user/view.php', [
|
||||
'id' => $author->get_id()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to view the author's profile image.
|
||||
*
|
||||
* @param author_entity $author The author
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_author_profile_image_url(author_entity $author) : moodle_url {
|
||||
global $PAGE;
|
||||
|
||||
$datamapper = $this->legacydatamapperfactory->get_author_data_mapper();
|
||||
$record = $datamapper->to_legacy_object($author);
|
||||
$userpicture = new user_picture($record);
|
||||
$userpicture->size = 2;
|
||||
|
||||
return $userpicture->get_url($PAGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to mark a discussion as read.
|
||||
*
|
||||
* @param forum_entity $forum The forum that the discussion belongs to
|
||||
* @param discussion_entity $discussion The discussion
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_mark_discussion_as_read_url_from_discussion(
|
||||
forum_entity $forum,
|
||||
discussion_entity $discussion
|
||||
) : moodle_url {
|
||||
return new moodle_url('/mod/forum/markposts.php', [
|
||||
'f' => $discussion->get_forum_id(),
|
||||
'd' => $discussion->get_id(),
|
||||
'mark' => 'read',
|
||||
'sesskey' => sesskey(),
|
||||
'return' => $this->get_forum_view_url_from_forum($forum)->out(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to mark all discussions as read.
|
||||
*
|
||||
* @param forum_entity $forum The forum that the discussions belong to
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_mark_all_discussions_as_read_url(forum_entity $forum) : moodle_url {
|
||||
return new moodle_url('/mod/forum/markposts.php', [
|
||||
'f' => $forum->get_id(),
|
||||
'mark' => 'read',
|
||||
'sesskey' => sesskey(),
|
||||
'return' => $this->get_forum_view_url_from_forum($forum)->out(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to subscribe to a discussion.
|
||||
*
|
||||
* @param discussion_entity $discussion The discussion
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_discussion_subscribe_url(discussion_entity $discussion) : moodle_url {
|
||||
return new moodle_url('/mod/forum/subscribe.php', [
|
||||
'sesskey' => sesskey(),
|
||||
'id' => $discussion->get_forum_id(),
|
||||
'd' => $discussion->get_id()
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Vault factory.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @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\factories\entity as entity_factory;
|
||||
use mod_forum\local\vaults\author as author_vault;
|
||||
use mod_forum\local\vaults\discussion as discussion_vault;
|
||||
use mod_forum\local\vaults\discussion_list as discussion_list_vault;
|
||||
use mod_forum\local\vaults\forum as forum_vault;
|
||||
use mod_forum\local\vaults\post as post_vault;
|
||||
use mod_forum\local\vaults\post_attachment as post_attachment_vault;
|
||||
use mod_forum\local\vaults\post_read_receipt_collection as post_read_receipt_collection_vault;
|
||||
use file_storage;
|
||||
use moodle_database;
|
||||
|
||||
/**
|
||||
* Vault factory.
|
||||
*
|
||||
* See:
|
||||
* https://designpatternsphp.readthedocs.io/en/latest/Creational/SimpleFactory/README.html
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class vault {
|
||||
/** @var entity_factory $entityfactory Entity factory */
|
||||
private $entityfactory;
|
||||
/** @var moodle_database $db A moodle database */
|
||||
private $db;
|
||||
/** @var file_storage $filestorage A file storage instance */
|
||||
private $filestorage;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param moodle_database $db A moodle database
|
||||
* @param entity_factory $entityfactory Entity factory
|
||||
* @param file_storage $filestorage A file storage instance
|
||||
*/
|
||||
public function __construct(moodle_database $db, entity_factory $entityfactory, file_storage $filestorage) {
|
||||
$this->db = $db;
|
||||
$this->entityfactory = $entityfactory;
|
||||
$this->filestorage = $filestorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a forum vault.
|
||||
*
|
||||
* @return forum_vault
|
||||
*/
|
||||
public function get_forum_vault() : forum_vault {
|
||||
return new forum_vault(
|
||||
$this->db,
|
||||
$this->entityfactory
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a discussion vault.
|
||||
*
|
||||
* @return discussion_vault
|
||||
*/
|
||||
public function get_discussion_vault() : discussion_vault {
|
||||
return new discussion_vault(
|
||||
$this->db,
|
||||
$this->entityfactory
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a discussion list vault.
|
||||
*
|
||||
* @return discussion_list_vault
|
||||
*/
|
||||
public function get_discussions_in_forum_vault() : discussion_list_vault {
|
||||
return new discussion_list_vault(
|
||||
$this->db,
|
||||
$this->entityfactory
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a post vault.
|
||||
*
|
||||
* @return post_vault
|
||||
*/
|
||||
public function get_post_vault() : post_vault {
|
||||
return new post_vault(
|
||||
$this->db,
|
||||
$this->entityfactory
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an author vault.
|
||||
*
|
||||
* @return author_vault
|
||||
*/
|
||||
public function get_author_vault() : author_vault {
|
||||
return new author_vault(
|
||||
$this->db,
|
||||
$this->entityfactory
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a post read receipt collection vault.
|
||||
*
|
||||
* @return post_read_receipt_collection_vault
|
||||
*/
|
||||
public function get_post_read_receipt_collection_vault() : post_read_receipt_collection_vault {
|
||||
return new post_read_receipt_collection_vault(
|
||||
$this->db,
|
||||
$this->entityfactory
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a post attachment vault.
|
||||
*
|
||||
* @return post_attachment_vault
|
||||
*/
|
||||
public function get_post_attachment_vault() : post_attachment_vault {
|
||||
return new post_attachment_vault(
|
||||
$this->filestorage
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,514 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Capability manager for the forum.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\managers;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\data_mappers\legacy\forum as legacy_forum_data_mapper;
|
||||
use mod_forum\local\data_mappers\legacy\discussion as legacy_discussion_data_mapper;
|
||||
use mod_forum\local\data_mappers\legacy\post as legacy_post_data_mapper;
|
||||
use mod_forum\local\entities\discussion as discussion_entity;
|
||||
use mod_forum\local\entities\forum as forum_entity;
|
||||
use mod_forum\local\entities\post as post_entity;
|
||||
use mod_forum\subscriptions;
|
||||
use context;
|
||||
use context_system;
|
||||
use stdClass;
|
||||
|
||||
require_once($CFG->dirroot . '/mod/forum/lib.php');
|
||||
|
||||
/**
|
||||
* Capability manager for the forum.
|
||||
*
|
||||
* Defines all the business rules for what a user can and can't do in the forum.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class capability {
|
||||
/** @var legacy_forum_data_mapper $forumdatamapper Legacy forum data mapper */
|
||||
private $forumdatamapper;
|
||||
/** @var legacy_discussion_data_mapper $discussiondatamapper Legacy discussion data mapper */
|
||||
private $discussiondatamapper;
|
||||
/** @var legacy_post_data_mapper $postdatamapper Legacy post data mapper */
|
||||
private $postdatamapper;
|
||||
/** @var forum_entity $forum Forum entity */
|
||||
private $forum;
|
||||
/** @var stdClass $forumrecord Legacy forum record */
|
||||
private $forumrecord;
|
||||
/** @var context $context Module context for the forum */
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param forum_entity $forum The forum entity to manage capabilities for.
|
||||
* @param legacy_forum_data_mapper $forumdatamapper Legacy forum data mapper
|
||||
* @param legacy_discussion_data_mapper $discussiondatamapper Legacy discussion data mapper
|
||||
* @param legacy_post_data_mapper $postdatamapper Legacy post data mapper
|
||||
*/
|
||||
public function __construct(
|
||||
forum_entity $forum,
|
||||
legacy_forum_data_mapper $forumdatamapper,
|
||||
legacy_discussion_data_mapper $discussiondatamapper,
|
||||
legacy_post_data_mapper $postdatamapper
|
||||
) {
|
||||
$this->forumdatamapper = $forumdatamapper;
|
||||
$this->discussiondatamapper = $discussiondatamapper;
|
||||
$this->postdatamapper = $postdatamapper;
|
||||
$this->forum = $forum;
|
||||
$this->forumrecord = $forumdatamapper->to_legacy_object($forum);
|
||||
$this->context = $forum->get_context();
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user subscribe to this forum?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user create discussions in this forum?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @param int|null $groupid The current activity group id
|
||||
* @return bool
|
||||
*/
|
||||
public function can_create_discussions(stdClass $user, int $groupid = null) : bool {
|
||||
if (isguestuser($user) or !isloggedin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ($this->forum->get_type()) {
|
||||
case 'news':
|
||||
$capability = 'mod/forum:addnews';
|
||||
break;
|
||||
case 'qanda':
|
||||
$capability = 'mod/forum:addquestion';
|
||||
break;
|
||||
default:
|
||||
$capability = 'mod/forum:startdiscussion';
|
||||
}
|
||||
|
||||
if (!has_capability($capability, $this->forum->get_context(), $user)) {
|
||||
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 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user access all groups?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @return bool
|
||||
*/
|
||||
public function can_access_all_groups(stdClass $user) : bool {
|
||||
return has_capability('moodle/site:accessallgroups', $this->get_context(), $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user access the given group?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @param int $groupid The id of the group that the forum is set to
|
||||
* @return bool
|
||||
*/
|
||||
public function can_access_group(stdClass $user, int $groupid) : bool {
|
||||
if ($this->can_access_all_groups($user)) {
|
||||
// This user has access to all groups.
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is a group discussion for a forum in separate groups mode.
|
||||
// Check if the user is a member.
|
||||
// This is the most expensive check.
|
||||
return groups_is_member($groupid, $user->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user post to their groups?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @return bool
|
||||
*/
|
||||
public function can_post_to_my_groups(stdClass $user) : bool {
|
||||
return has_capability('mod/forum:canposttomygroups', $this->get_context(), $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user view discussions in this forum?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view_discussions(stdClass $user) : bool {
|
||||
return has_capability('mod/forum:viewdiscussion', $this->get_context(), $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user move discussions in this forum?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @return bool
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user pin discussions in this forum?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @return bool
|
||||
*/
|
||||
public function can_pin_discussions(stdClass $user) : bool {
|
||||
return $this->forum->get_type() !== 'single' &&
|
||||
has_capability('mod/forum:pindiscussions', $this->get_context(), $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user split discussions in this forum?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @return bool
|
||||
*/
|
||||
public function can_split_discussions(stdClass $user) : bool {
|
||||
$forum = $this->get_forum();
|
||||
return $forum->get_type() !== 'single' && has_capability('mod/forum:splitdiscussions', $this->get_context(), $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user export (see portfolios) discussions in this forum?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @return bool
|
||||
*/
|
||||
public function can_export_discussions(stdClass $user) : bool {
|
||||
global $CFG;
|
||||
return $CFG->enableportfolios && has_capability('mod/forum:exportdiscussion', $this->get_context(), $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user manually mark posts as read/unread in this forum?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @return bool
|
||||
*/
|
||||
public function can_manually_control_post_read_status(stdClass $user) : bool {
|
||||
global $CFG;
|
||||
return $CFG->forum_usermarksread && isloggedin() && forum_tp_is_tracked($this->get_forum_record(), $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the user required to post in the discussion before they can view it?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @param discussion_entity $discussion The discussion to check
|
||||
* @return bool
|
||||
*/
|
||||
public function must_post_before_viewing_discussion(stdClass $user, discussion_entity $discussion) : bool {
|
||||
$forum = $this->get_forum();
|
||||
|
||||
if ($forum->get_type() === 'qanda') {
|
||||
// If it's a Q and A forum then the user must either have the capability to view without
|
||||
// posting or the user must have posted before they can view the discussion.
|
||||
return !has_capability('mod/forum:viewqandawithoutposting', $this->get_context(), $user) &&
|
||||
!forum_user_has_posted($forum->get_id(), $discussion->get_id(), $user->id);
|
||||
} else {
|
||||
// No other forum types require posting before viewing.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user subscribe to the give discussion?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @param discussion_entity $discussion The discussion to check
|
||||
* @return bool
|
||||
*/
|
||||
public function can_subscribe_to_discussion(stdClass $user, discussion_entity $discussion) : bool {
|
||||
return $this->can_subscribe_to_forum($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user move the discussion in this forum?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @param discussion_entity $discussion The discussion to check
|
||||
* @return bool
|
||||
*/
|
||||
public function can_move_discussion(stdClass $user, discussion_entity $discussion) : bool {
|
||||
return $this->can_move_discussions($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the user pin the discussion?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @param discussion_entity $discussion The discussion to check
|
||||
* @return bool
|
||||
*/
|
||||
public function can_pin_discussion(stdClass $user, discussion_entity $discussion) : bool {
|
||||
return $this->can_pin_discussions($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user post in this discussion?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @param discussion_entity $discussion The discussion to check
|
||||
* @return bool
|
||||
*/
|
||||
public function can_post_in_discussion(stdClass $user, discussion_entity $discussion) : bool {
|
||||
$forum = $this->get_forum();
|
||||
$forumrecord = $this->get_forum_record();
|
||||
$discussionrecord = $this->get_discussion_record($discussion);
|
||||
$context = $this->get_context();
|
||||
$coursemodule = $forum->get_course_module_record();
|
||||
$course = $forum->get_course_record();
|
||||
|
||||
return forum_user_can_post($forumrecord, $discussionrecord, $user, $coursemodule, $course, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user view the post in this discussion?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @param discussion_entity $discussion The discussion to check
|
||||
* @param post_entity $post The post the user wants to view
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view_post(stdClass $user, discussion_entity $discussion, post_entity $post) : bool {
|
||||
$forum = $this->get_forum();
|
||||
$forumrecord = $this->get_forum_record();
|
||||
$discussionrecord = $this->get_discussion_record($discussion);
|
||||
$postrecord = $this->get_post_record($post);
|
||||
$coursemodule = $forum->get_course_module_record();
|
||||
return forum_user_can_see_post($forumrecord, $discussionrecord, $postrecord, $user, $coursemodule, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user edit the post in this discussion?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @param discussion_entity $discussion The discussion to check
|
||||
* @param post_entity $post The post the user wants to edit
|
||||
* @return bool
|
||||
*/
|
||||
public function can_edit_post(stdClass $user, discussion_entity $discussion, post_entity $post) : bool {
|
||||
global $CFG;
|
||||
|
||||
$context = $this->get_context();
|
||||
$ownpost = $post->is_owned_by_user($user);
|
||||
$ineditingtime = $post->get_age() < $CFG->maxeditingtime;
|
||||
|
||||
switch ($this->forum->get_type()) {
|
||||
case 'news':
|
||||
// Allow editing of news posts once the discussion has started.
|
||||
$ineditingtime = !$post->has_parent() && $discussion->has_started();
|
||||
break;
|
||||
case 'single':
|
||||
return $discussion->is_first_post($post) && has_capability('moodle/course:manageactivities', $context, $user);
|
||||
}
|
||||
|
||||
return ($ownpost && $ineditingtime) || has_capability('mod/forum:editanypost', $context, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user delete the post in this discussion?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @param discussion_entity $discussion The discussion to check
|
||||
* @param post_entity $post The post the user wants to delete
|
||||
* @return bool
|
||||
*/
|
||||
public function can_delete_post(stdClass $user, discussion_entity $discussion, post_entity $post) : bool {
|
||||
global $CFG;
|
||||
|
||||
$forum = $this->get_forum();
|
||||
|
||||
if ($forum->get_type() == 'single' && $discussion->is_first_post($post)) {
|
||||
// Do not allow deleting of first post in single simple type.
|
||||
return false;
|
||||
} else {
|
||||
$context = $this->get_context();
|
||||
$ownpost = $post->is_owned_by_user($user);
|
||||
$ineditingtime = $post->get_age() < $CFG->maxeditingtime;
|
||||
|
||||
return ($ownpost && $ineditingtime && has_capability('mod/forum:deleteownpost', $context, $user)) ||
|
||||
has_capability('mod/forum:deleteanypost', $context, $user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user split the post in this discussion?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @param discussion_entity $discussion The discussion to check
|
||||
* @param post_entity $post The post the user wants to split
|
||||
* @return bool
|
||||
*/
|
||||
public function can_split_post(stdClass $user, discussion_entity $discussion, post_entity $post) : bool {
|
||||
return $this->can_split_discussions($user) && $post->has_parent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user reply to the post in this discussion?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @param discussion_entity $discussion The discussion to check
|
||||
* @param post_entity $post The post the user wants to reply to
|
||||
* @return bool
|
||||
*/
|
||||
public function can_reply_to_post(stdClass $user, discussion_entity $discussion, post_entity $post) : bool {
|
||||
return $this->can_post_in_discussion($user, $discussion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user export (see portfolios) the post in this discussion?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @param post_entity $post The post the user wants to export
|
||||
* @return bool
|
||||
*/
|
||||
public function can_export_post(stdClass $user, post_entity $post) : bool {
|
||||
global $CFG;
|
||||
$context = $this->get_context();
|
||||
return $CFG->enableportfolios && (has_capability('mod/forum:exportpost', $context, $user) ||
|
||||
($post->is_owned_by_user($user) && has_capability('mod/forum:exportownpost', $context, $user)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the forum entity for this capability manager.
|
||||
*
|
||||
* @return forum_entity
|
||||
*/
|
||||
protected function get_forum() : forum_entity {
|
||||
return $this->forum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the legacy forum record for this forum.
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
protected function get_forum_record() : stdClass {
|
||||
return $this->forumrecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the context for this capability manager.
|
||||
*
|
||||
* @return context
|
||||
*/
|
||||
protected function get_context() : context {
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the legacy discussion record for the given discussion entity.
|
||||
*
|
||||
* @param discussion_entity $discussion The discussion to convert
|
||||
* @return stdClass
|
||||
*/
|
||||
protected function get_discussion_record(discussion_entity $discussion) : stdClass {
|
||||
return $this->discussiondatamapper->to_legacy_object($discussion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the legacy post record for the given post entity.
|
||||
*
|
||||
* @param post_entity $post The post to convert
|
||||
* @return stdClass
|
||||
*/
|
||||
protected function get_post_record(post_entity $post) : stdClass {
|
||||
return $this->postdatamapper->to_legacy_object($post);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user view the participants of this discussion?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @param discussion_entity $discussion The discussion to check
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view_participants(stdClass $user, discussion_entity $discussion) : bool {
|
||||
return course_can_view_participants($this->get_context()) &&
|
||||
!$this->must_post_before_viewing_discussion($user, $discussion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user view hidden posts in this forum?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view_hidden_posts(stdClass $user) : bool {
|
||||
return has_capability('mod/forum:viewhiddentimedposts', $this->get_context(), $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user manage this forum?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @return bool
|
||||
*/
|
||||
public function can_manage_forum(stdClass $user) {
|
||||
return has_capability('moodle/course:manageactivities', $this->get_context(), $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user manage tags on the site?
|
||||
*
|
||||
* @param stdClass $user The user to check
|
||||
* @return bool
|
||||
*/
|
||||
public function can_manage_tags(stdClass $user) : bool {
|
||||
return has_capability('moodle/tag:manage', context_system::instance(), $user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Discussion renderer.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\renderers;
|
||||
|
||||
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\post as post_entity;
|
||||
use mod_forum\local\entities\sorter as sorter_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 mod_forum\local\managers\capability as capability_manager;
|
||||
use mod_forum\local\renderers\posts as posts_renderer;
|
||||
use core\output\notification;
|
||||
use context;
|
||||
use context_module;
|
||||
use html_writer;
|
||||
use moodle_exception;
|
||||
use moodle_page;
|
||||
use moodle_url;
|
||||
use rating_manager;
|
||||
use renderer_base;
|
||||
use single_button;
|
||||
use single_select;
|
||||
use stdClass;
|
||||
use url_select;
|
||||
|
||||
require_once($CFG->dirroot . '/mod/forum/lib.php');
|
||||
|
||||
/**
|
||||
* Discussion renderer class.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class discussion {
|
||||
/** @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 stdClass $forumrecord Legacy forum record */
|
||||
private $forumrecord;
|
||||
/** @var int $displaymode The display mode to render the discussion in */
|
||||
private $displaymode;
|
||||
/** @var renderer_base $renderer Renderer base */
|
||||
private $renderer;
|
||||
/** @var posts_renderer $postsrenderer A posts renderer */
|
||||
private $postsrenderer;
|
||||
/** @var moodle_page $page The page this discussion is being rendered for */
|
||||
private $page;
|
||||
/** @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 capability_manager $capabilitymanager Capability manager */
|
||||
private $capabilitymanager;
|
||||
/** @var rating_manager $ratingmanager Rating manager */
|
||||
private $ratingmanager;
|
||||
/** @var moodle_url $baseurl The base URL for the discussion */
|
||||
private $baseurl;
|
||||
/** @var array $notifications List of HTML notifications to display */
|
||||
private $notifications;
|
||||
/** @var sorter_entity $exportedpostsorter Sorter for the exported posts */
|
||||
private $exportedpostsorter;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @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
|
||||
* @param moodle_page $page The page this discussion is being rendered for
|
||||
* @param legacy_data_mapper_factory $legacydatamapperfactory Legacy data mapper factory
|
||||
* @param exporter_factory $exporterfactory Exporter factory
|
||||
* @param vault_factory $vaultfactory Vault factory
|
||||
* @param capability_manager $capabilitymanager Capability manager
|
||||
* @param rating_manager $ratingmanager Rating manager
|
||||
* @param sorter_entity $exportedpostsorter Sorter for the exported posts
|
||||
* @param moodle_url $baseurl The base URL for the discussion
|
||||
* @param array $notifications List of HTML notifications to display
|
||||
*/
|
||||
public function __construct(
|
||||
forum_entity $forum,
|
||||
discussion_entity $discussion,
|
||||
int $displaymode,
|
||||
renderer_base $renderer,
|
||||
posts_renderer $postsrenderer,
|
||||
moodle_page $page,
|
||||
legacy_data_mapper_factory $legacydatamapperfactory,
|
||||
exporter_factory $exporterfactory,
|
||||
vault_factory $vaultfactory,
|
||||
capability_manager $capabilitymanager,
|
||||
rating_manager $ratingmanager,
|
||||
sorter_entity $exportedpostsorter,
|
||||
moodle_url $baseurl,
|
||||
array $notifications = []
|
||||
) {
|
||||
$this->forum = $forum;
|
||||
$this->discussion = $discussion;
|
||||
$this->displaymode = $displaymode;
|
||||
$this->renderer = $renderer;
|
||||
$this->postsrenderer = $postsrenderer;
|
||||
$this->page = $page;
|
||||
$this->baseurl = $baseurl;
|
||||
$this->legacydatamapperfactory = $legacydatamapperfactory;
|
||||
$this->exporterfactory = $exporterfactory;
|
||||
$this->vaultfactory = $vaultfactory;
|
||||
$this->capabilitymanager = $capabilitymanager;
|
||||
$this->ratingmanager = $ratingmanager;
|
||||
$this->notifications = $notifications;
|
||||
|
||||
$this->exportedpostsorter = $exportedpostsorter;
|
||||
|
||||
$forumdatamapper = $this->legacydatamapperfactory->get_forum_data_mapper();
|
||||
$this->forumrecord = $forumdatamapper->to_legacy_object($forum);
|
||||
|
||||
$discussiondatamapper = $this->legacydatamapperfactory->get_discussion_data_mapper();
|
||||
$this->discussionrecord = $discussiondatamapper->to_legacy_object($discussion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the discussion for the given user in the specified display mode.
|
||||
*
|
||||
* @param stdClass $user The user viewing the discussion
|
||||
* @param post_entity $firstpost The first post in the discussion
|
||||
* @param array $replies List of replies to the first post
|
||||
* @return string HTML for the discussion
|
||||
*/
|
||||
public function render(
|
||||
stdClass $user,
|
||||
post_entity $firstpost,
|
||||
array $replies
|
||||
) : string {
|
||||
global $CFG;
|
||||
|
||||
$displaymode = $this->displaymode;
|
||||
$capabilitymanager = $this->capabilitymanager;
|
||||
|
||||
// Make sure we can render.
|
||||
if (!$capabilitymanager->can_view_discussions($user)) {
|
||||
throw new moodle_exception('noviewdiscussionspermission', 'mod_forum');
|
||||
}
|
||||
|
||||
$posts = array_merge([$firstpost], array_values($replies));
|
||||
|
||||
$exporteddiscussion = $this->get_exported_discussion($user);
|
||||
$exporteddiscussion = array_merge($exporteddiscussion, [
|
||||
'notifications' => $this->get_notifications($user),
|
||||
'html' => [
|
||||
'posts' => $this->postsrenderer->render($user, [$this->forum], [$this->discussion], $posts),
|
||||
'modeselectorform' => $this->get_display_mode_selector_html($displaymode),
|
||||
'subscribe' => null,
|
||||
'movediscussion' => null,
|
||||
'pindiscussion' => null,
|
||||
'neighbourlinks' => $this->get_neighbour_links_html(),
|
||||
'exportdiscussion' => !empty($CFG->enableportfolios) ? $this->get_export_discussion_html() : null
|
||||
]
|
||||
]);
|
||||
|
||||
$capabilities = (array) $exporteddiscussion['capabilities'];
|
||||
|
||||
if ($capabilities['subscribe']) {
|
||||
$exporteddiscussion['html']['subscribe'] = $this->get_subscription_button_html();
|
||||
}
|
||||
|
||||
if ($capabilities['move']) {
|
||||
$exporteddiscussion['html']['movediscussion'] = $this->get_move_discussion_html();
|
||||
}
|
||||
|
||||
if ($capabilities['pin']) {
|
||||
$exporteddiscussion['html']['pindiscussion'] = $this->get_pin_discussion_html();
|
||||
}
|
||||
|
||||
return $this->renderer->render_from_template('mod_forum/forum_discussion', $exporteddiscussion);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 exported discussion.
|
||||
*
|
||||
* @param stdClass $user The user viewing the discussion
|
||||
* @return array
|
||||
*/
|
||||
private function get_exported_discussion(stdClass $user) : array {
|
||||
$discussionexporter = $this->exporterfactory->get_discussion_exporter(
|
||||
$user,
|
||||
$this->forum,
|
||||
$this->discussion,
|
||||
$this->get_groups_available_in_forum()
|
||||
);
|
||||
|
||||
return (array) $discussionexporter->export($this->renderer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTML for the display mode selector.
|
||||
*
|
||||
* @param int $displaymode The current display mode
|
||||
* @return string
|
||||
*/
|
||||
private function get_display_mode_selector_html(int $displaymode) : string {
|
||||
$baseurl = $this->baseurl;
|
||||
$select = new single_select(
|
||||
$baseurl,
|
||||
'mode',
|
||||
forum_get_layout_modes(),
|
||||
$displaymode,
|
||||
null,
|
||||
'mode'
|
||||
);
|
||||
$select->set_label(get_string('displaymode', 'forum'), ['class' => 'accesshide']);
|
||||
|
||||
return $this->renderer->render($select);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTML to render the subscription button.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_subscription_button_html() : string {
|
||||
global $PAGE;
|
||||
|
||||
$forumrecord = $this->forumrecord;
|
||||
$discussion = $this->discussion;
|
||||
$html = html_writer::div(
|
||||
forum_get_discussion_subscription_icon($forumrecord, $discussion->get_id(), null, true),
|
||||
'discussionsubscription'
|
||||
);
|
||||
$html .= forum_get_discussion_subscription_icon_preloaders();
|
||||
// Add the subscription toggle JS.
|
||||
$PAGE->requires->yui_module('moodle-mod_forum-subscriptiontoggle', 'Y.M.mod_forum.subscriptiontoggle.init');
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTML to render the move discussion selector and button.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_move_discussion_html() : string {
|
||||
global $DB;
|
||||
|
||||
$forum = $this->forum;
|
||||
$discussion = $this->discussion;
|
||||
$courseid = $forum->get_course_id();
|
||||
|
||||
// Popup menu to move discussions to other forums. The discussion in a
|
||||
// single discussion forum can't be moved.
|
||||
$modinfo = get_fast_modinfo($courseid);
|
||||
if (isset($modinfo->instances['forum'])) {
|
||||
$forummenu = [];
|
||||
// Check forum types and eliminate simple discussions.
|
||||
$forumcheck = $DB->get_records('forum', ['course' => $courseid], '', 'id, type');
|
||||
foreach ($modinfo->instances['forum'] as $forumcm) {
|
||||
if (!$forumcm->uservisible || !has_capability('mod/forum:startdiscussion',
|
||||
context_module::instance($forumcm->id))) {
|
||||
continue;
|
||||
}
|
||||
$section = $forumcm->sectionnum;
|
||||
$sectionname = get_section_name($courseid, $section);
|
||||
if (empty($forummenu[$section])) {
|
||||
$forummenu[$section] = [$sectionname => []];
|
||||
}
|
||||
$forumidcompare = $forumcm->instance != $forum->get_id();
|
||||
$forumtypecheck = $forumcheck[$forumcm->instance]->type !== 'single';
|
||||
|
||||
if ($forumidcompare and $forumtypecheck) {
|
||||
$url = "/mod/forum/discuss.php?d={$discussion->get_id()}&move=$forumcm->instance&sesskey=".sesskey();
|
||||
$forummenu[$section][$sectionname][$url] = format_string($forumcm->name);
|
||||
}
|
||||
}
|
||||
if (!empty($forummenu)) {
|
||||
$html = '<div class="movediscussionoption">';
|
||||
$select = new url_select($forummenu, '',
|
||||
['/mod/forum/discuss.php?d=' . $discussion->get_id() => get_string("movethisdiscussionto", "forum")],
|
||||
'forummenu', get_string('move'));
|
||||
$html .= $this->renderer->render($select);
|
||||
$html .= "</div>";
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTML to render the pin discussion button.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_pin_discussion_html() : string {
|
||||
$discussion = $this->discussion;
|
||||
|
||||
if ($discussion->is_pinned()) {
|
||||
$pinlink = FORUM_DISCUSSION_UNPINNED;
|
||||
$pintext = get_string('discussionunpin', 'forum');
|
||||
} else {
|
||||
$pinlink = FORUM_DISCUSSION_PINNED;
|
||||
$pintext = get_string('discussionpin', 'forum');
|
||||
}
|
||||
|
||||
$button = new single_button(
|
||||
new moodle_url('discuss.php', ['pin' => $pinlink, 'd' => $discussion->get_id()]),
|
||||
$pintext,
|
||||
'post'
|
||||
);
|
||||
return $this->renderer->render($button);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTML to render the export discussion button.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function get_export_discussion_html() : ?string {
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->libdir . '/portfoliolib.php');
|
||||
$discussion = $this->discussion;
|
||||
$button = new \portfolio_add_button();
|
||||
$button->set_callback_options('forum_portfolio_caller', ['discussionid' => $discussion->get_id()], 'mod_forum');
|
||||
$button = $button->to_html(PORTFOLIO_ADD_FULL_FORM, get_string('exportdiscussion', 'mod_forum'));
|
||||
return $button ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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($user) : array {
|
||||
$notifications = $this->notifications;
|
||||
$discussion = $this->discussion;
|
||||
$forum = $this->forum;
|
||||
$renderer = $this->renderer;
|
||||
|
||||
if ($forum->is_discussion_locked($discussion)) {
|
||||
$notifications[] = (new notification(
|
||||
get_string('discussionlocked', 'forum'),
|
||||
notification::NOTIFY_INFO
|
||||
))
|
||||
->set_extra_classes(['discussionlocked'])
|
||||
->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', [
|
||||
'blockafter' => $forum->get_block_after(),
|
||||
'blockperiod' => get_string('secondstotime' . $forum->get_block_period())
|
||||
])
|
||||
))->set_show_closebutton();
|
||||
}
|
||||
|
||||
return array_map(function($notification) {
|
||||
return $notification->export_for_template($this->renderer);
|
||||
}, $notifications);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTML to display the neighbour links.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_neighbour_links_html() : string {
|
||||
$forum = $this->forum;
|
||||
$coursemodule = $forum->get_course_module_record();
|
||||
$neighbours = forum_get_discussion_neighbours($coursemodule, $this->discussionrecord, $this->forumrecord);
|
||||
return $this->renderer->neighbouring_discussion_navigation($neighbours['prev'], $neighbours['next']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Discussion list renderer.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\renderers;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
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 mod_forum\local\factories\url as url_factory;
|
||||
use mod_forum\local\managers\capability as capability_manager;
|
||||
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');
|
||||
|
||||
/**
|
||||
* The discussion list renderer.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class discussion_list {
|
||||
/** @var forum_entity The forum being rendered */
|
||||
private $forum;
|
||||
|
||||
/** @var stdClass The DB record for the forum being rendered */
|
||||
private $forumrecord;
|
||||
|
||||
/** @var renderer_base The renderer used to render the view */
|
||||
private $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 capability_manager $capabilitymanager Capability manager */
|
||||
private $capabilitymanager;
|
||||
|
||||
/** @var url_factory $urlfactory URL factory */
|
||||
private $urlfactory;
|
||||
|
||||
/** @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.
|
||||
*
|
||||
* @param forum_entity $forum The forum entity to be rendered
|
||||
* @param renderer_base $renderer The renderer used to render the view
|
||||
* @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
|
||||
* @param callable|null $postprocessfortemplate Callback function to process discussion lists for templates
|
||||
*/
|
||||
public function __construct(
|
||||
forum_entity $forum,
|
||||
renderer_base $renderer,
|
||||
legacy_data_mapper_factory $legacydatamapperfactory,
|
||||
exporter_factory $exporterfactory,
|
||||
vault_factory $vaultfactory,
|
||||
builder_factory $builderfactory,
|
||||
capability_manager $capabilitymanager,
|
||||
url_factory $urlfactory,
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render for the specified user.
|
||||
*
|
||||
* @param stdClass $user The user to render for
|
||||
* @param cm_info $cm The course module info for this discussion list
|
||||
* @param int $groupid The group to render
|
||||
* @param int $sortorder The sort order to use when selecting the discussions in the list
|
||||
* @param int $pageno The zero-indexed page number to use
|
||||
* @param int $pagesize The number of discussions to show on the page
|
||||
* @return string The rendered content for display
|
||||
*/
|
||||
public function render(stdClass $user, \cm_info $cm, ?int $groupid, ?int $sortorder, ?int $pageno, ?int $pagesize) : string {
|
||||
global $PAGE;
|
||||
|
||||
$forum = $this->forum;
|
||||
|
||||
$pagesize = $this->get_page_size($pagesize);
|
||||
$pageno = $this->get_page_number($pageno);
|
||||
|
||||
$groupids = $this->get_groups_from_groupid($user, $groupid);
|
||||
$forumexporter = $this->exporterfactory->get_forum_exporter(
|
||||
$user,
|
||||
$this->forum,
|
||||
$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),
|
||||
'groupchangemenu' => groups_print_activity_menu(
|
||||
$cm,
|
||||
$this->urlfactory->get_forum_view_url_from_forum($forum),
|
||||
true
|
||||
),
|
||||
'notifications' => $this->get_notifications($user, $groupid)
|
||||
];
|
||||
|
||||
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(
|
||||
$forumview,
|
||||
[
|
||||
'pagination' => $this->renderer->render(new \paging_bar($alldiscussionscount, $pageno, $pagesize, $PAGE->url, 'p')),
|
||||
],
|
||||
$exportedposts
|
||||
);
|
||||
|
||||
return $this->renderer->render_from_template($this->get_template(), $forumview);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of groups to show based on the current user and requested groupid.
|
||||
*
|
||||
* @param stdClass $user The user viewing
|
||||
* @param int $groupid The groupid requested
|
||||
* @return array The list of groups to show
|
||||
*/
|
||||
private function get_groups_from_groupid(stdClass $user, ?int $groupid) : ?array {
|
||||
$forum = $this->forum;
|
||||
$effectivegroupmode = $forum->get_effective_group_mode();
|
||||
if (empty($effectivegroupmode)) {
|
||||
// This forum is not in a group mode. Show all posts always.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (null == $groupid) {
|
||||
// No group was specified.
|
||||
$showallgroups = (VISIBLEGROUPS == $effectivegroupmode);
|
||||
$showallgroups = $showallgroups || $this->capabilitymanager->can_access_all_groups($user);
|
||||
if ($showallgroups) {
|
||||
// Return null to show all groups.
|
||||
return null;
|
||||
} else {
|
||||
// No group was specified. Only show the users current groups.
|
||||
return array_keys(
|
||||
groups_get_all_groups(
|
||||
$forum->get_course_id(),
|
||||
$user->id,
|
||||
$forum->get_course_module_record()->groupingid
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// A group was specified. Just show that group.
|
||||
return [$groupid];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the data used to display the discussions on the current page.
|
||||
*
|
||||
* @param stdClass $user The user to render for
|
||||
* @param int[]|null $groupids The group ids for this list of discussions
|
||||
* @param int|null $sortorder The sort order to use when selecting the discussions in the list
|
||||
* @param int|null $pageno The zero-indexed page number to use
|
||||
* @param int|null $pagesize The number of discussions to show on the page
|
||||
* @return stdClass The data to use for display
|
||||
*/
|
||||
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) {
|
||||
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_size($pagesize));
|
||||
} else {
|
||||
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_size($pagesize));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the page size to use when displaying the page.
|
||||
*
|
||||
* @param int $pagesize The number of discussions to show on the page
|
||||
* @return int The normalised page size
|
||||
*/
|
||||
private function get_page_size(?int $pagesize) : int {
|
||||
if (null === $pagesize || $pagesize <= 0) {
|
||||
$pagesize = discussion_list_vault::PAGESIZE_DEFAULT;
|
||||
}
|
||||
|
||||
return $pagesize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the current page number (zero-indexed).
|
||||
*
|
||||
* @param int $pageno The zero-indexed page number to use
|
||||
* @return int The normalised page number
|
||||
*/
|
||||
private function get_page_number(?int $pageno) : int {
|
||||
if (null === $pageno || $pageno < 0) {
|
||||
$pageno = 0;
|
||||
}
|
||||
|
||||
return $pageno;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the name of the template to use for the current forum and view modes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_template() : string {
|
||||
switch ($this->forum->get_type()) {
|
||||
case 'news':
|
||||
return 'mod_forum/news_discussion_list';
|
||||
break;
|
||||
case 'blog':
|
||||
return 'mod_forum/blog_discussion_list';
|
||||
break;
|
||||
case 'qanda':
|
||||
return 'mod_forum/qanda_discussion_list';
|
||||
break;
|
||||
default:
|
||||
return 'mod_forum/discussion_list';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of notification for display.
|
||||
*
|
||||
* @param stdClass $user The viewing user
|
||||
* @param int|null $groupid The forum's group id
|
||||
* @return array
|
||||
*/
|
||||
private function get_notifications(stdClass $user, ?int $groupid) : array {
|
||||
$notifications = $this->notifications;
|
||||
$forum = $this->forum;
|
||||
$renderer = $this->renderer;
|
||||
$capabilitymanager = $this->capabilitymanager;
|
||||
|
||||
if ($forum->has_blocking_enabled()) {
|
||||
$notifications[] = (new notification(
|
||||
get_string('thisforumisthrottled', 'forum', [
|
||||
'blockafter' => $forum->get_block_after(),
|
||||
'blockperiod' => get_string('secondstotime' . $forum->get_block_period())
|
||||
])
|
||||
))->set_show_closebutton();
|
||||
}
|
||||
|
||||
if ($forum->is_in_group_mode() && !$capabilitymanager->can_access_all_groups($user)) {
|
||||
if ($groupid === null) {
|
||||
if (!$capabilitymanager->can_post_to_my_groups($user)) {
|
||||
$notifications[] = (new notification(
|
||||
get_string('cannotadddiscussiongroup', 'mod_forum'),
|
||||
\core\output\notification::NOTIFY_WARNING
|
||||
))->set_show_closebutton();
|
||||
} else {
|
||||
$notifications[] = (new notification(
|
||||
get_string('cannotadddiscussionall', 'mod_forum'),
|
||||
\core\output\notification::NOTIFY_WARNING
|
||||
))->set_show_closebutton();
|
||||
}
|
||||
} else if (!$capabilitymanager->can_access_group($user, $groupid)) {
|
||||
$notifications[] = (new notification(
|
||||
get_string('cannotadddiscussion', 'mod_forum'),
|
||||
\core\output\notification::NOTIFY_WARNING
|
||||
))->set_show_closebutton();
|
||||
}
|
||||
}
|
||||
|
||||
if ('qanda' === $forum->get_type() && !$capabilitymanager->can_manage_forum($user)) {
|
||||
$notifications[] = (new notification(
|
||||
get_string('qandanotify', 'forum'),
|
||||
notification::NOTIFY_INFO
|
||||
))->set_show_closebutton();
|
||||
}
|
||||
|
||||
if ('eachuser' === $forum->get_type()) {
|
||||
$notifications[] = (new notification(
|
||||
get_string('allowsdiscussions', 'forum'),
|
||||
notification::NOTIFY_INFO)
|
||||
)->set_show_closebutton();
|
||||
}
|
||||
|
||||
return array_map(function($notification) {
|
||||
return $notification->export_for_template($this->renderer);
|
||||
}, $notifications);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Posts renderer.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\renderers;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\builders\exported_posts as exported_posts_builder;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Posts renderer class.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class posts {
|
||||
/** @var renderer_base $renderer Renderer base */
|
||||
private $renderer;
|
||||
/** @var exported_posts_builder $exportedpostsbuilder Builder for building exported posts */
|
||||
private $exportedpostsbuilder;
|
||||
/** @var string $template The template to render */
|
||||
private $template;
|
||||
/** @var callable $postprocessfortemplate Function to process exported posts before template rendering */
|
||||
private $postprocessfortemplate;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param renderer_base $renderer Renderer base
|
||||
* @param exported_posts_builder $exportedpostsbuilder Builder for building exported posts
|
||||
* @param string $template The template to render
|
||||
* @param callable $postprocessfortemplate Function to process exported posts before template rendering
|
||||
*/
|
||||
public function __construct(
|
||||
renderer_base $renderer,
|
||||
exported_posts_builder $exportedpostsbuilder,
|
||||
string $template,
|
||||
callable $postprocessfortemplate = null
|
||||
) {
|
||||
$this->renderer = $renderer;
|
||||
$this->exportedpostsbuilder = $exportedpostsbuilder;
|
||||
$this->template = $template;
|
||||
$this->postprocessfortemplate = $postprocessfortemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the given posts for the forums and discussions.
|
||||
*
|
||||
* @param stdClass $user The user viewing the posts
|
||||
* @param forum_entity[] $forums A list of all forums for these posts
|
||||
* @param discussion_entity[] $discussions A list of all discussions for these posts
|
||||
* @param post_entity[] $posts The posts to render
|
||||
* @return string
|
||||
*/
|
||||
public function render(
|
||||
stdClass $user,
|
||||
array $forums,
|
||||
array $discussions,
|
||||
array $posts
|
||||
) : string {
|
||||
// Format the forums and discussion to make them more easily accessed later.
|
||||
$forums = array_reduce($forums, function($carry, $forum) {
|
||||
$carry[$forum->get_id()] = $forum;
|
||||
return $carry;
|
||||
}, []);
|
||||
$discussions = array_reduce($discussions, function($carry, $discussion) {
|
||||
$carry[$discussion->get_id()] = $discussion;
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
$exportedposts = $this->exportedpostsbuilder->build(
|
||||
$user,
|
||||
$forums,
|
||||
$discussions,
|
||||
$posts
|
||||
);
|
||||
|
||||
if ($this->postprocessfortemplate !== null) {
|
||||
// We've got some post processing to do!
|
||||
$exportedposts = ($this->postprocessfortemplate)($exportedposts, $forums, $discussions, $user);
|
||||
}
|
||||
|
||||
return $this->renderer->render_from_template(
|
||||
$this->template,
|
||||
['posts' => array_values($exportedposts)]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Author vault class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\vaults;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Author vault class.
|
||||
*
|
||||
* This should be the only place that accessed the database.
|
||||
*
|
||||
* This uses the repository pattern. See:
|
||||
* https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class author extends db_table_vault {
|
||||
/** The table for this vault */
|
||||
private const TABLE = 'user';
|
||||
|
||||
/**
|
||||
* Get the table alias.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_table_alias() : string {
|
||||
return 'a';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the SQL to be used in get_records_sql.
|
||||
*
|
||||
* @param string|null $wheresql Where conditions for the SQL
|
||||
* @param string|null $sortsql Order by conditions for the SQL
|
||||
* @return string
|
||||
*/
|
||||
protected function generate_get_records_sql(string $wheresql = null, string $sortsql = null) : string {
|
||||
$selectsql = 'SELECT * FROM {' . self::TABLE . '} ' . $this->get_table_alias();
|
||||
$selectsql .= $wheresql ? ' WHERE ' . $wheresql : '';
|
||||
$selectsql .= $sortsql ? ' ORDER BY ' . $sortsql : '';
|
||||
|
||||
return $selectsql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the DB records into author entities.
|
||||
*
|
||||
* @param array $results The DB records
|
||||
* @return author_entity[]
|
||||
*/
|
||||
protected function from_db_records(array $results) {
|
||||
$entityfactory = $this->get_entity_factory();
|
||||
|
||||
return array_map(function(array $result) use ($entityfactory) {
|
||||
[
|
||||
'record' => $record,
|
||||
] = $result;
|
||||
return $entityfactory->get_author_from_stdclass($record);
|
||||
}, $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the authors for the given posts.
|
||||
*
|
||||
* Returns a distinct list of authors indexed by author id.
|
||||
*
|
||||
* @param post_entity[] $posts The list of posts
|
||||
* @return author_entity[]
|
||||
*/
|
||||
public function get_authors_for_posts(array $posts) : array {
|
||||
$authorids = array_reduce($posts, function($carry, $post) {
|
||||
$carry[$post->get_author_id()] = true;
|
||||
return $carry;
|
||||
}, []);
|
||||
$authorids = array_keys($authorids);
|
||||
return $this->get_from_ids($authorids);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Abstract class for loading records from the DB.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\vaults;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\factories\entity as entity_factory;
|
||||
use moodle_database;
|
||||
|
||||
/**
|
||||
* Abstract class for loading records from the DB.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class db_table_vault {
|
||||
/** @var moodle_database $db A moodle database */
|
||||
private $db;
|
||||
/** @var entity_factory $entityfactory Entity factory */
|
||||
private $entityfactory;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param moodle_database $db A moodle database
|
||||
* @param entity_factory $entityfactory Entity factory
|
||||
*/
|
||||
public function __construct(
|
||||
moodle_database $db,
|
||||
entity_factory $entityfactory
|
||||
) {
|
||||
$this->db = $db;
|
||||
$this->entityfactory = $entityfactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the table alias.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function get_table_alias() : string;
|
||||
|
||||
/**
|
||||
* Build the SQL to be used in get_records_sql.
|
||||
*
|
||||
* @param string|null $wheresql Where conditions for the SQL
|
||||
* @param string|null $sortsql Order by conditions for the SQL
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function generate_get_records_sql(string $wheresql = null, string $sortsql = null) : string;
|
||||
|
||||
/**
|
||||
* Convert the DB records into entities. The list of records will have been
|
||||
* passed through any preprocessors that may be defined before being given to
|
||||
* this function.
|
||||
*
|
||||
* Each result will from the list will be an associative array where the key
|
||||
* is set to the identifier given to the preprocessor and the result is the value
|
||||
* generated by the preprocessor.
|
||||
*
|
||||
* All results will have a 'record' key who's value is the original DB record.
|
||||
*
|
||||
* @param array $results The DB records
|
||||
*/
|
||||
abstract protected function from_db_records(array $results);
|
||||
|
||||
/**
|
||||
* Get the list of preprocessors to run on the DB record results. The preprocessors
|
||||
* should be defined using an associative array. The key used to identify the
|
||||
* preprocessor in this list will be used to identify the value of that preprocessor
|
||||
* in the list of results when passed to the from_db_records function.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_preprocessors() : array {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the moodle database.
|
||||
*
|
||||
* @return moodle_database
|
||||
*/
|
||||
protected function get_db() : moodle_database {
|
||||
return $this->db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity factory.
|
||||
*
|
||||
* @return entity_factory
|
||||
*/
|
||||
protected function get_entity_factory() : entity_factory {
|
||||
return $this->entityfactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the defined preprocessors on the DB record results and then convert
|
||||
* them into entities.
|
||||
*
|
||||
* @param stdClass[] $records List of DB results
|
||||
* @return array
|
||||
*/
|
||||
protected function transform_db_records_to_entities(array $records) {
|
||||
$preprocessors = $this->get_preprocessors();
|
||||
$result = array_map(function($record) {
|
||||
return ['record' => $record];
|
||||
}, $records);
|
||||
|
||||
$result = array_reduce(array_keys($preprocessors), function($carry, $preprocessor) use ($records, $preprocessors) {
|
||||
$step = $preprocessors[$preprocessor];
|
||||
$dependencies = $step->execute($records);
|
||||
|
||||
foreach ($dependencies as $index => $dependency) {
|
||||
// Add the new dependency to the list.
|
||||
$carry[$index] = array_merge($carry[$index], [$preprocessor => $dependency]);
|
||||
}
|
||||
|
||||
return $carry;
|
||||
}, $result);
|
||||
|
||||
return $this->from_db_records($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity for the given id.
|
||||
*
|
||||
* @param int $id Identifier for the entity
|
||||
* @return object|null
|
||||
*/
|
||||
public function get_from_id(int $id) {
|
||||
$records = $this->get_from_ids([$id]);
|
||||
return count($records) ? array_shift($records) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of entities for the given ids.
|
||||
*
|
||||
* @param int[] $ids Identifiers
|
||||
* @return array
|
||||
*/
|
||||
public function get_from_ids(array $ids) {
|
||||
$alias = $this->get_table_alias();
|
||||
list($insql, $params) = $this->get_db()->get_in_or_equal($ids);
|
||||
$wheresql = $alias . '.id ' . $insql;
|
||||
$sql = $this->generate_get_records_sql($wheresql);
|
||||
$records = $this->get_db()->get_records_sql($sql, $params);
|
||||
|
||||
return $this->transform_db_records_to_entities($records);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Discussion vault class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\vaults;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\forum as forum_entity;
|
||||
use mod_forum\local\entities\discussion as discussion_entity;
|
||||
|
||||
/**
|
||||
* Discussion vault class.
|
||||
*
|
||||
* This should be the only place that accessed the database.
|
||||
*
|
||||
* This uses the repository pattern. See:
|
||||
* https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class discussion extends db_table_vault {
|
||||
/** The table for this vault */
|
||||
private const TABLE = 'forum_discussions';
|
||||
|
||||
/**
|
||||
* Get the table alias.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_table_alias() : string {
|
||||
return 'd';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the SQL to be used in get_records_sql.
|
||||
*
|
||||
* @param string|null $wheresql Where conditions for the SQL
|
||||
* @param string|null $sortsql Order by conditions for the SQL
|
||||
* @return string
|
||||
*/
|
||||
protected function generate_get_records_sql(string $wheresql = null, string $sortsql = null) : string {
|
||||
$selectsql = 'SELECT * FROM {' . self::TABLE . '} ' . $this->get_table_alias();
|
||||
$selectsql .= $wheresql ? ' WHERE ' . $wheresql : '';
|
||||
$selectsql .= $sortsql ? ' ORDER BY ' . $sortsql : '';
|
||||
|
||||
return $selectsql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the DB records into discussion entities.
|
||||
*
|
||||
* @param array $results The DB records
|
||||
* @return discussion_entity[]
|
||||
*/
|
||||
protected function from_db_records(array $results) {
|
||||
$entityfactory = $this->get_entity_factory();
|
||||
|
||||
return array_map(function(array $result) use ($entityfactory) {
|
||||
[
|
||||
'record' => $record,
|
||||
] = $result;
|
||||
return $entityfactory->get_discussion_from_stdclass($record);
|
||||
}, $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first discussion in the specified forum.
|
||||
*
|
||||
* @param forum_entity $forum
|
||||
* @return discussion_entity|null
|
||||
*/
|
||||
public function get_first_discussion_in_forum(forum_entity $forum) : ?discussion_entity {
|
||||
$records = $this->get_db()->get_records(self::TABLE, [
|
||||
'forum' => $forum->get_id(),
|
||||
], 'timemodified ASC', '*', 0, 1);
|
||||
|
||||
$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()]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Vault class for a discussion list.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\vaults;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\vaults\preprocessors\extract_record as extract_record_preprocessor;
|
||||
use mod_forum\local\vaults\preprocessors\extract_user as extract_user_preprocessor;
|
||||
use mod_forum\local\renderers\discussion_list as discussion_list_renderer;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Discussion list vault.
|
||||
*
|
||||
* This should be the only place that accessed the database.
|
||||
*
|
||||
* This uses the repository pattern. See:
|
||||
* https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class discussion_list extends db_table_vault {
|
||||
/** The table for this vault */
|
||||
private const TABLE = 'forum_discussions';
|
||||
/** Alias for first author id */
|
||||
private const FIRST_AUTHOR_ID_ALIAS = 'userpictureid';
|
||||
/** Alias for author fields */
|
||||
private const FIRST_AUTHOR_ALIAS = 'userrecord';
|
||||
/** Alias for last author id */
|
||||
private const LATEST_AUTHOR_ID_ALIAS = 'userpictureid';
|
||||
/** Alias for last author fields */
|
||||
private const LATEST_AUTHOR_ALIAS = 'userrecord';
|
||||
/** Default limit */
|
||||
public const PAGESIZE_DEFAULT = 100;
|
||||
|
||||
/** Sort by newest first */
|
||||
public const SORTORDER_NEWEST_FIRST = 1;
|
||||
/** Sort by oldest first */
|
||||
public const SORTORDER_OLDEST_FIRST = 2;
|
||||
/** Sort by created desc */
|
||||
public const SORTORDER_CREATED_DESC = 3;
|
||||
|
||||
/**
|
||||
* Get the table alias.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_table_alias() : string {
|
||||
return 'd';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the SQL to be used in get_records_sql.
|
||||
*
|
||||
* @param string|null $wheresql Where conditions for the SQL
|
||||
* @param string|null $sortsql Order by conditions for the SQL
|
||||
* @return string
|
||||
*/
|
||||
protected function generate_get_records_sql(string $wheresql = null, ?string $sortsql = null) : string {
|
||||
$alias = $this->get_table_alias();
|
||||
$db = $this->get_db();
|
||||
|
||||
// Fetch:
|
||||
// - Discussion
|
||||
// - First post
|
||||
// - Author
|
||||
// - Most recent editor.
|
||||
$tablefields = $db->get_preload_columns(self::TABLE, $alias);
|
||||
$postfields = $db->get_preload_columns('forum_posts', 'p_');
|
||||
$firstauthorfields = \user_picture::fields('fa', null, self::FIRST_AUTHOR_ID_ALIAS, self::FIRST_AUTHOR_ALIAS);
|
||||
$latestuserfields = \user_picture::fields('la', null, self::LATEST_AUTHOR_ID_ALIAS, self::LATEST_AUTHOR_ALIAS);
|
||||
|
||||
$fields = implode(', ', [
|
||||
$db->get_preload_columns_sql($tablefields, $alias),
|
||||
$db->get_preload_columns_sql($postfields, 'fp'),
|
||||
$firstauthorfields,
|
||||
$latestuserfields,
|
||||
]);
|
||||
|
||||
$tables = '{' . self::TABLE . '} ' . $alias;
|
||||
$tables .= ' JOIN {user} fa ON fa.id = ' . $alias . '.userid';
|
||||
$tables .= ' JOIN {user} la ON la.id = ' . $alias . '.usermodified';
|
||||
$tables .= ' JOIN {forum_posts} fp ON fp.id = ' . $alias . '.firstpost';
|
||||
|
||||
$selectsql = 'SELECT ' . $fields . ' FROM ' . $tables;
|
||||
$selectsql .= $wheresql ? ' WHERE ' . $wheresql : '';
|
||||
$selectsql .= $sortsql ? ' ORDER BY ' . $sortsql : '';
|
||||
|
||||
return $selectsql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the SQL to be used in count_records_sql.
|
||||
*
|
||||
* @param string|null $wheresql Where conditions for the SQL
|
||||
* @return string
|
||||
*/
|
||||
protected function generate_count_records_sql(string $wheresql = null) : string {
|
||||
$alias = $this->get_table_alias();
|
||||
$db = $this->get_db();
|
||||
|
||||
$selectsql = "SELECT COUNT(1) FROM {" . self::TABLE . "} {$alias}";
|
||||
$selectsql .= $wheresql ? ' WHERE ' . $wheresql : '';
|
||||
|
||||
return $selectsql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of preprocessors to execute on the DB results before being converted
|
||||
* into entities.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_preprocessors() : array {
|
||||
return array_merge(
|
||||
parent::get_preprocessors(),
|
||||
[
|
||||
'discussion' => new extract_record_preprocessor($this->get_db(), self::TABLE, $this->get_table_alias()),
|
||||
'firstpost' => new extract_record_preprocessor($this->get_db(), 'forum_posts', 'p_'),
|
||||
'firstpostauthor' => new extract_user_preprocessor(self::FIRST_AUTHOR_ID_ALIAS, self::FIRST_AUTHOR_ALIAS),
|
||||
'latestpostauthor' => new extract_user_preprocessor(self::LATEST_AUTHOR_ID_ALIAS, self::LATEST_AUTHOR_ALIAS),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the DB records into discussion list entities.
|
||||
*
|
||||
* @param array $results The DB records
|
||||
* @return discussion_list[]
|
||||
*/
|
||||
protected function from_db_records(array $results) {
|
||||
$entityfactory = $this->get_entity_factory();
|
||||
|
||||
return array_map(function(array $result) use ($entityfactory) {
|
||||
[
|
||||
'discussion' => $discussion,
|
||||
'firstpost' => $firstpost,
|
||||
'firstpostauthor' => $firstpostauthor,
|
||||
'latestpostauthor' => $latestpostauthor,
|
||||
] = $result;
|
||||
return $entityfactory->get_discussion_summary_from_stdclass(
|
||||
$discussion,
|
||||
$firstpost,
|
||||
$firstpostauthor,
|
||||
$latestpostauthor
|
||||
);
|
||||
}, $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sort order SQL for a sort method.
|
||||
*
|
||||
* @param int|null $sortmethod
|
||||
*/
|
||||
public function get_sort_order(?int $sortmethod) : string {
|
||||
global $CFG;
|
||||
|
||||
$alias = $this->get_table_alias();
|
||||
|
||||
if ($sortmethod == self::SORTORDER_CREATED_DESC) {
|
||||
$keyfield = "fp.created";
|
||||
$direction = "DESC";
|
||||
} else {
|
||||
// TODO consider user favourites...
|
||||
$keyfield = "{$alias}.timemodified";
|
||||
$direction = "DESC";
|
||||
|
||||
if ($sortmethod == self::SORTORDER_OLDEST_FIRST) {
|
||||
$direction = "ASC";
|
||||
}
|
||||
|
||||
if (!empty($CFG->forum_enabletimedposts)) {
|
||||
$keyfield = "CASE WHEN {$keyfield} < {$alias}.timestart THEN {$alias}.timestart ELSE {$keyfield} END";
|
||||
}
|
||||
}
|
||||
|
||||
return "{$alias}.pinned DESC, {$keyfield} {$direction}, {$alias}.id DESC";
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch any required SQL to respect timed posts.
|
||||
*
|
||||
* @param bool $includehiddendiscussions Whether to include hidden discussions or not
|
||||
* @param int $includepostsforuser Which user to include posts for, if any
|
||||
* @return array The SQL and parameters to include
|
||||
*/
|
||||
protected function get_hidden_post_sql(bool $includehiddendiscussions, ?int $includepostsforuser) {
|
||||
$wheresql = '';
|
||||
$params = [];
|
||||
if (!$includehiddendiscussions) {
|
||||
$now = time();
|
||||
$wheresql = " AND ((d.timestart <= :timestart AND (d.timeend = 0 OR d.timeend > :timeend))";
|
||||
$params['timestart'] = $now;
|
||||
$params['timeend'] = $now;
|
||||
if (null !== $includepostsforuser) {
|
||||
$wheresql .= " OR d.userid = :byuser";
|
||||
$params['byuser'] = $includepostsforuser;
|
||||
}
|
||||
$wheresql .= ")";
|
||||
}
|
||||
|
||||
return [
|
||||
'wheresql' => $wheresql,
|
||||
'params' => $params,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get each discussion, first post, first and last post author for the given forum, considering timed posts, and
|
||||
* pagination.
|
||||
*
|
||||
* @param int $forumid The forum to fetch the discussion set for
|
||||
* @param bool $includehiddendiscussions Whether to include hidden discussions or not
|
||||
* @param int|null $includepostsforuser Which user to include posts for, if any
|
||||
* @param int $sortorder The sort order to use
|
||||
* @param int $limit The number of discussions to fetch
|
||||
* @param int $offset The record offset
|
||||
* @return array The set of data fetched
|
||||
*/
|
||||
public function get_from_forum_id(
|
||||
int $forumid,
|
||||
bool $includehiddendiscussions,
|
||||
?int $includepostsforuser,
|
||||
?int $sortorder,
|
||||
int $limit,
|
||||
int $offset
|
||||
) {
|
||||
$alias = $this->get_table_alias();
|
||||
$wheresql = "{$alias}.forum = :forumid";
|
||||
[
|
||||
'wheresql' => $hiddensql,
|
||||
'params' => $hiddenparams
|
||||
] = $this->get_hidden_post_sql($includehiddendiscussions, $includepostsforuser);
|
||||
$wheresql .= $hiddensql;
|
||||
|
||||
$params = array_merge($hiddenparams, [
|
||||
'forumid' => $forumid,
|
||||
]);
|
||||
|
||||
$sql = $this->generate_get_records_sql($wheresql, $this->get_sort_order($sortorder));
|
||||
$records = $this->get_db()->get_records_sql($sql, $params, $offset, $limit);
|
||||
|
||||
return $this->transform_db_records_to_entities($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get each discussion, first post, first and last post author for the given forum, and the set of groups to display
|
||||
* considering timed posts, and pagination.
|
||||
*
|
||||
* @param int $forumid The forum to fetch the discussion set for
|
||||
* @param int[] $groupids The list of real groups to filter on
|
||||
* @param bool $includehiddendiscussions Whether to include hidden discussions or not
|
||||
* @param int|null $includepostsforuser Which user to include posts for, if any
|
||||
* @param int $sortorder The sort order to use
|
||||
* @param int $limit The number of discussions to fetch
|
||||
* @param int $offset The record offset
|
||||
* @return array The set of data fetched
|
||||
*/
|
||||
public function get_from_forum_id_and_group_id(
|
||||
int $forumid,
|
||||
array $groupids,
|
||||
bool $includehiddendiscussions,
|
||||
?int $includepostsforuser,
|
||||
?int $sortorder,
|
||||
int $limit,
|
||||
int $offset
|
||||
) {
|
||||
$alias = $this->get_table_alias();
|
||||
|
||||
$wheresql = "{$alias}.forum = :forumid AND ";
|
||||
$groupparams = [];
|
||||
if (empty($groupids)) {
|
||||
$wheresql .= "{$alias}.groupid = :allgroupsid";
|
||||
} else {
|
||||
list($insql, $groupparams) = $this->get_db()->get_in_or_equal($groupids, SQL_PARAMS_NAMED, 'gid');
|
||||
$wheresql .= "({$alias}.groupid = :allgroupsid OR {$alias}.groupid {$insql})";
|
||||
}
|
||||
|
||||
[
|
||||
'wheresql' => $hiddensql,
|
||||
'params' => $hiddenparams
|
||||
] = $this->get_hidden_post_sql($includehiddendiscussions, $includepostsforuser);
|
||||
$wheresql .= $hiddensql;
|
||||
|
||||
$params = array_merge($hiddenparams, $groupparams, [
|
||||
'forumid' => $forumid,
|
||||
'allgroupsid' => -1,
|
||||
]);
|
||||
|
||||
$sql = $this->generate_get_records_sql($wheresql, $this->get_sort_order($sortorder));
|
||||
$records = $this->get_db()->get_records_sql($sql, $params, $offset, $limit);
|
||||
|
||||
return $this->transform_db_records_to_entities($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of discussions in the forum.
|
||||
*
|
||||
* @param int $forumid Id of the forum to count discussions in
|
||||
* @param bool $includehiddendiscussions Include hidden dicussions in the count?
|
||||
* @param int|null $includepostsforuser Include discussions created by this user in the count
|
||||
* (only works if not including hidden discussions).
|
||||
* @return int
|
||||
*/
|
||||
public function get_total_discussion_count_from_forum_id(
|
||||
int $forumid,
|
||||
bool $includehiddendiscussions,
|
||||
?int $includepostsforuser
|
||||
) {
|
||||
$alias = $this->get_table_alias();
|
||||
|
||||
$wheresql = "{$alias}.forum = :forumid";
|
||||
|
||||
[
|
||||
'wheresql' => $hiddensql,
|
||||
'params' => $hiddenparams
|
||||
] = $this->get_hidden_post_sql($includehiddendiscussions, $includepostsforuser);
|
||||
$wheresql .= $hiddensql;
|
||||
|
||||
$params = array_merge($hiddenparams, [
|
||||
'forumid' => $forumid,
|
||||
]);
|
||||
|
||||
return $this->get_db()->count_records_sql($this->generate_count_records_sql($wheresql), $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of discussions in all groups and the list of groups provided.
|
||||
*
|
||||
* @param int $forumid Id of the forum to count discussions in
|
||||
* @param int[] $groupids List of group ids to include in the count (discussions in all groups will always be counted)
|
||||
* @param bool $includehiddendiscussions Include hidden dicussions in the count?
|
||||
* @param int|null $includepostsforuser Include discussions created by this user in the count
|
||||
* (only works if not including hidden discussions).
|
||||
* @return int
|
||||
*/
|
||||
public function get_total_discussion_count_from_forum_id_and_group_id(
|
||||
int $forumid,
|
||||
array $groupids,
|
||||
bool $includehiddendiscussions,
|
||||
?int $includepostsforuser
|
||||
) {
|
||||
$alias = $this->get_table_alias();
|
||||
|
||||
$wheresql = "{$alias}.forum = :forumid AND ";
|
||||
$groupparams = [];
|
||||
if (empty($groupids)) {
|
||||
$wheresql .= "{$alias}.groupid = :allgroupsid";
|
||||
} else {
|
||||
list($insql, $groupparams) = $this->get_db()->get_in_or_equal($groupids, SQL_PARAMS_NAMED, 'gid');
|
||||
$wheresql .= "({$alias}.groupid = :allgroupsid OR {$alias}.groupid {$insql})";
|
||||
}
|
||||
|
||||
[
|
||||
'wheresql' => $hiddensql,
|
||||
'params' => $hiddenparams
|
||||
] = $this->get_hidden_post_sql($includehiddendiscussions, $includepostsforuser);
|
||||
$wheresql .= $hiddensql;
|
||||
|
||||
$params = array_merge($hiddenparams, $groupparams, [
|
||||
'forumid' => $forumid,
|
||||
'allgroupsid' => -1,
|
||||
]);
|
||||
|
||||
return $this->get_db()->count_records_sql($this->generate_count_records_sql($wheresql), $params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Forum vault class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\vaults;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\forum as forum_entity;
|
||||
use mod_forum\local\vaults\preprocessors\extract_context as extract_context_preprocessor;
|
||||
use mod_forum\local\vaults\preprocessors\extract_record as extract_record_preprocessor;
|
||||
use context_helper;
|
||||
|
||||
/**
|
||||
* Forum vault class.
|
||||
*
|
||||
* This should be the only place that accessed the database.
|
||||
*
|
||||
* This uses the repository pattern. See:
|
||||
* https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class forum extends db_table_vault {
|
||||
/** The table for this vault */
|
||||
private const TABLE = 'forum';
|
||||
|
||||
/**
|
||||
* Get the table alias.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_table_alias() : string {
|
||||
return 'f';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the SQL to be used in get_records_sql.
|
||||
*
|
||||
* @param string|null $wheresql Where conditions for the SQL
|
||||
* @param string|null $sortsql Order by conditions for the SQL
|
||||
* @return string
|
||||
*/
|
||||
protected function generate_get_records_sql(string $wheresql = null, string $sortsql = null) : string {
|
||||
$db = $this->get_db();
|
||||
$alias = $this->get_table_alias();
|
||||
$tablefields = $db->get_preload_columns(self::TABLE, $alias);
|
||||
$coursemodulefields = $db->get_preload_columns('course_modules', 'cm_');
|
||||
$coursefields = $db->get_preload_columns('course', 'c_');
|
||||
|
||||
$fields = implode(', ', [
|
||||
$db->get_preload_columns_sql($tablefields, $alias),
|
||||
context_helper::get_preload_record_columns_sql('ctx'),
|
||||
$db->get_preload_columns_sql($coursemodulefields, 'cm'),
|
||||
$db->get_preload_columns_sql($coursefields, 'c'),
|
||||
]);
|
||||
|
||||
$tables = '{' . self::TABLE . '} ' . $alias;
|
||||
$tables .= " JOIN {modules} m ON m.name = 'forum'";
|
||||
$tables .= " JOIN {course_modules} cm ON cm.module = m.id AND cm.instance = {$alias}.id";
|
||||
$tables .= ' JOIN {context} ctx ON ctx.contextlevel = ' . CONTEXT_MODULE . ' AND ctx.instanceid = cm.id';
|
||||
$tables .= " JOIN {course} c ON c.id = {$alias}.course";
|
||||
|
||||
$selectsql = 'SELECT ' . $fields . ' FROM ' . $tables;
|
||||
$selectsql .= $wheresql ? ' WHERE ' . $wheresql : '';
|
||||
$selectsql .= $sortsql ? ' ORDER BY ' . $sortsql : '';
|
||||
|
||||
return $selectsql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of preprocessors to execute on the DB results before being converted
|
||||
* into entities.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_preprocessors() : array {
|
||||
return array_merge(
|
||||
parent::get_preprocessors(),
|
||||
[
|
||||
'forum' => new extract_record_preprocessor($this->get_db(), self::TABLE, $this->get_table_alias()),
|
||||
'course_module' => new extract_record_preprocessor($this->get_db(), 'course_modules', 'cm_'),
|
||||
'course' => new extract_record_preprocessor($this->get_db(), 'course', 'c_'),
|
||||
'context' => new extract_context_preprocessor(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the DB records into forum entities.
|
||||
*
|
||||
* @param array $results The DB records
|
||||
* @return forum_entity[]
|
||||
*/
|
||||
protected function from_db_records(array $results) : array {
|
||||
$entityfactory = $this->get_entity_factory();
|
||||
|
||||
return array_map(function(array $result) use ($entityfactory) {
|
||||
[
|
||||
'forum' => $forumrecord,
|
||||
'course_module' => $coursemodule,
|
||||
'course' => $course,
|
||||
'context' => $context,
|
||||
] = $result;
|
||||
return $entityfactory->get_forum_from_stdclass($forumrecord, $context, $coursemodule, $course);
|
||||
}, $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the forum for the given course module id.
|
||||
*
|
||||
* @param int $id The course module id
|
||||
* @return forum_entity|null
|
||||
*/
|
||||
public function get_from_course_module_id(int $id) : ?forum_entity {
|
||||
$records = $this->get_from_course_module_ids([$id]);
|
||||
return count($records) ? array_shift($records) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the forums for the given course module ids
|
||||
*
|
||||
* @param int[] $ids The course module ids
|
||||
* @return forum_entity[]
|
||||
*/
|
||||
public function get_from_course_module_ids(array $ids) : array {
|
||||
$alias = $this->get_table_alias();
|
||||
list($insql, $params) = $this->get_db()->get_in_or_equal($ids);
|
||||
$wheresql = 'cm.id ' . $insql;
|
||||
$sql = $this->generate_get_records_sql($wheresql);
|
||||
$records = $this->get_db()->get_records_sql($sql, $params);
|
||||
|
||||
return $this->transform_db_records_to_entities($records);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Post vault class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\vaults;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\post as post_entity;
|
||||
use mod_forum\local\factories\entity as entity_factory;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Post vault class.
|
||||
*
|
||||
* This should be the only place that accessed the database.
|
||||
*
|
||||
* This uses the repository pattern. See:
|
||||
* https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class post extends db_table_vault {
|
||||
/** The table for this vault */
|
||||
private const TABLE = 'forum_posts';
|
||||
/** Alias for user id */
|
||||
private const USER_ID_ALIAS = 'userpictureid';
|
||||
/** Alias for user fields */
|
||||
private const USER_ALIAS = 'userrecord';
|
||||
|
||||
/**
|
||||
* Get the table alias.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_table_alias() : string {
|
||||
return 'p';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the SQL to be used in get_records_sql.
|
||||
*
|
||||
* @param string|null $wheresql Where conditions for the SQL
|
||||
* @param string|null $sortsql Order by conditions for the SQL
|
||||
* @return string
|
||||
*/
|
||||
protected function generate_get_records_sql(string $wheresql = null, string $sortsql = null) : string {
|
||||
$table = self::TABLE;
|
||||
$alias = $this->get_table_alias();
|
||||
$fields = $alias . '.*';
|
||||
$tables = "{{$table}} {$alias}";
|
||||
|
||||
$selectsql = "SELECT {$fields} FROM {$tables}";
|
||||
$selectsql .= $wheresql ? ' WHERE ' . $wheresql : '';
|
||||
$selectsql .= $sortsql ? ' ORDER BY ' . $sortsql : '';
|
||||
|
||||
return $selectsql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the DB records into post entities.
|
||||
*
|
||||
* @param array $results The DB records
|
||||
* @return post_entity[]
|
||||
*/
|
||||
protected function from_db_records(array $results) {
|
||||
$entityfactory = $this->get_entity_factory();
|
||||
|
||||
return array_map(function(array $result) use ($entityfactory) {
|
||||
['record' => $record] = $result;
|
||||
return $entityfactory->get_post_from_stdclass($record);
|
||||
}, $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the post ids for the given discussion.
|
||||
*
|
||||
* @param int $discussionid The discussion to load posts for
|
||||
* @param string $orderby Order the results
|
||||
* @return post_entity[]
|
||||
*/
|
||||
public function get_from_discussion_id(int $discussionid, string $orderby = 'created ASC') : array {
|
||||
$alias = $this->get_table_alias();
|
||||
$wheresql = $alias . '.discussion = ?';
|
||||
$orderbysql = $alias . '.' . $orderby;
|
||||
$sql = $this->generate_get_records_sql($wheresql, $orderbysql);
|
||||
$records = $this->get_db()->get_records_sql($sql, [$discussionid]);
|
||||
|
||||
return $this->transform_db_records_to_entities($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of posts for the given discussions.
|
||||
*
|
||||
* @param int[] $discussionids The list of discussion ids to load posts for
|
||||
* @return post_entity[]
|
||||
*/
|
||||
public function get_from_discussion_ids(array $discussionids) : array {
|
||||
if (empty($discussionids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$alias = $this->get_table_alias();
|
||||
|
||||
list($insql, $params) = $this->get_db()->get_in_or_equal($discussionids);
|
||||
|
||||
$wheresql = "{$alias}.discussion {$insql}";
|
||||
|
||||
$sql = $this->generate_get_records_sql($wheresql, '');
|
||||
$records = $this->get_db()->get_records_sql($sql, $params);
|
||||
|
||||
return $this->transform_db_records_to_entities($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a list of replies to the given post. This will load all descendants of the post.
|
||||
* That is, all direct replies and replies to those replies etc.
|
||||
*
|
||||
* The return value will be a flat array of posts in the requested order.
|
||||
*
|
||||
* @param post_entity $post The post to load replies for
|
||||
* @param string $orderby How to order the replies
|
||||
* @return post_entity[]
|
||||
*/
|
||||
public function get_replies_to_post(post_entity $post, string $orderby = 'created ASC') : array {
|
||||
$alias = $this->get_table_alias();
|
||||
$params = [$post->get_discussion_id(), $post->get_time_created(), $post->get_id()];
|
||||
// Unfortunately the best we can do to filter down the query is ignore all posts
|
||||
// that were created before the given post (since they can't be replies).
|
||||
$wheresql = "{$alias}.discussion = ? and {$alias}.created >= ? and {$alias}.id != ?";
|
||||
$orderbysql = $alias . '.' . $orderby;
|
||||
$sql = $this->generate_get_records_sql($wheresql, $orderbysql);
|
||||
$records = $this->get_db()->get_records_sql($sql, $params);
|
||||
$posts = $this->transform_db_records_to_entities($records);
|
||||
$sorter = $this->get_entity_factory()->get_posts_sorter();
|
||||
// We need to sort all of the values into the replies tree in order to capture
|
||||
// the full list of descendants.
|
||||
$sortedposts = $sorter->sort_into_children($posts);
|
||||
$replies = [];
|
||||
|
||||
// From the sorted list we can grab the first elements and check if they are replies
|
||||
// to the post we care about. If so we keep them.
|
||||
foreach ($sortedposts as $candidate) {
|
||||
[$candidatepost, $candidatereplies] = $candidate;
|
||||
if ($candidatepost->has_parent() && $candidatepost->get_parent_id() == $post->get_id()) {
|
||||
$replies[] = $candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($replies)) {
|
||||
return $replies;
|
||||
}
|
||||
|
||||
$getreplypostids = function($candidates) use (&$getreplypostids) {
|
||||
$ids = [];
|
||||
|
||||
foreach ($candidates as $candidate) {
|
||||
[$reply, $replies] = $candidate;
|
||||
$ids = array_merge($ids, [$reply->get_id()], $getreplypostids($replies));
|
||||
}
|
||||
|
||||
return $ids;
|
||||
};
|
||||
// Recursively build a list of the ids of all posts in the full reply tree.
|
||||
$replypostids = $getreplypostids($replies);
|
||||
|
||||
// Now go back and filter the original result set down to just the posts that
|
||||
// we've flagged as in the reply tree. We need to filter the original set of values
|
||||
// so that we can maintain the requested sort order.
|
||||
return array_values(array_filter($posts, function($post) use ($replypostids) {
|
||||
return in_array($post->get_id(), $replypostids);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mapping of replies to the specified discussions.
|
||||
*
|
||||
* @param int[] $discussionids The list of discussions to fetch counts for
|
||||
* @return int[] The number of replies for each discussion returned in an associative array
|
||||
*/
|
||||
public function get_reply_count_for_discussion_ids(array $discussionids) : array {
|
||||
if (empty($discussionids)) {
|
||||
return [];
|
||||
}
|
||||
list($insql, $params) = $this->get_db()->get_in_or_equal($discussionids);
|
||||
$sql = "SELECT discussion, COUNT(1) FROM {" . self::TABLE . "} p " .
|
||||
"WHERE p.discussion {$insql} AND p.parent > 0 GROUP BY discussion";
|
||||
|
||||
return $this->get_db()->get_records_sql_menu($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mapping of unread post counts for the specified discussions.
|
||||
*
|
||||
* @param stdClass $user The user to fetch counts for
|
||||
* @param int[] $discussionids The list of discussions to fetch counts for
|
||||
* @return int[] The count of unread posts for each discussion returned in an associative array
|
||||
*/
|
||||
public function get_unread_count_for_discussion_ids(stdClass $user, array $discussionids) : array {
|
||||
global $CFG;
|
||||
|
||||
if (empty($discussionids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$alias = $this->get_table_alias();
|
||||
list($insql, $params) = $this->get_db()->get_in_or_equal($discussionids, SQL_PARAMS_NAMED);
|
||||
$sql = "SELECT p.discussion, COUNT(p.id) FROM {" . self::TABLE . "} p
|
||||
LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = :userid
|
||||
WHERE p.discussion {$insql} AND p.modified > :cutofftime AND r.id IS NULL
|
||||
GROUP BY p.discussion";
|
||||
|
||||
$params['userid'] = $user->id;
|
||||
$params['cutofftime'] = floor((new \DateTime())
|
||||
->sub(new \DateInterval("P{$CFG->forum_oldpostdays}D"))
|
||||
->format('U') / 60) * 60;
|
||||
|
||||
return $this->get_db()->get_records_sql_menu($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mapping of the most recent post in each discussion based on post creation time.
|
||||
*
|
||||
* @param int[] $discussionids The list of discussions to fetch counts for
|
||||
* @return int[] The post id of the most recent post for each discussions returned in an associative array
|
||||
*/
|
||||
public function get_latest_post_id_for_discussion_ids(array $discussionids) : array {
|
||||
global $CFG;
|
||||
|
||||
if (empty($discussionids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$alias = $this->get_table_alias();
|
||||
list($insql, $params) = $this->get_db()->get_in_or_equal($discussionids, SQL_PARAMS_NAMED);
|
||||
|
||||
$sql = "
|
||||
SELECT p.discussion, MAX(p.id)
|
||||
FROM {" . self::TABLE . "} p
|
||||
JOIN (
|
||||
SELECT mp.discussion, MAX(mp.created) AS created
|
||||
FROM {" . self::TABLE . "} mp
|
||||
WHERE mp.discussion {$insql}
|
||||
GROUP BY mp.discussion
|
||||
) lp ON lp.discussion = p.discussion AND lp.created = p.created
|
||||
GROUP BY p.discussion";
|
||||
|
||||
return $this->get_db()->get_records_sql_menu($sql, $params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Post attachment vault class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2018 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\vaults;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\post as post_entity;
|
||||
use context;
|
||||
use file_storage;
|
||||
|
||||
/**
|
||||
* Post attachment vault class.
|
||||
*
|
||||
* This should be the only place that accessed the database.
|
||||
*
|
||||
* This uses the repository pattern. See:
|
||||
* https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html
|
||||
*
|
||||
* @copyright 2018 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class post_attachment {
|
||||
/** The component for attachments */
|
||||
private const COMPONENT = 'mod_forum';
|
||||
/** File area for attachments */
|
||||
private const FILE_AREA = 'attachment';
|
||||
/** Sort the attachments by filename */
|
||||
private const SORT = 'filename';
|
||||
/** Don't include directories */
|
||||
private const INCLUDE_DIRECTORIES = false;
|
||||
/** @var file_storage $filestorage File storage */
|
||||
private $filestorage;
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param file_storage $filestorage File storage
|
||||
*/
|
||||
public function __construct(file_storage $filestorage) {
|
||||
$this->filestorage = $filestorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the given posts. The results are indexed by
|
||||
* post id.
|
||||
*
|
||||
* @param context $context The (forum) context that the posts are in
|
||||
* @param post_entity[] $posts The list of posts to load attachments for
|
||||
* @return array Post attachments indexed by post id
|
||||
*/
|
||||
public function get_attachments_for_posts(context $context, array $posts) {
|
||||
$itemids = array_map(function($post) {
|
||||
return $post->get_id();
|
||||
}, $posts);
|
||||
|
||||
$files = $this->filestorage->get_area_files(
|
||||
$context->id,
|
||||
self::COMPONENT,
|
||||
self::FILE_AREA,
|
||||
$itemids,
|
||||
self::SORT,
|
||||
self::INCLUDE_DIRECTORIES
|
||||
);
|
||||
|
||||
$filesbyid = array_reduce($posts, function($carry, $post) {
|
||||
$carry[$post->get_id()] = [];
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
return array_reduce($files, function($carry, $file) {
|
||||
$itemid = $file->get_itemid();
|
||||
$carry[$itemid] = array_merge($carry[$itemid], [$file]);
|
||||
return $carry;
|
||||
}, $filesbyid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Post read receipt collection class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2018 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\vaults;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Post read receipt collection class.
|
||||
*
|
||||
* This should be the only place that accessed the database.
|
||||
*
|
||||
* This uses the repository pattern. See:
|
||||
* https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html
|
||||
*
|
||||
* @copyright 2018 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class post_read_receipt_collection extends db_table_vault {
|
||||
/** The table for this vault */
|
||||
private const TABLE = 'forum_read';
|
||||
|
||||
/**
|
||||
* Get the table alias.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_table_alias() : string {
|
||||
return 'fr';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the SQL to be used in get_records_sql.
|
||||
*
|
||||
* @param string|null $wheresql Where conditions for the SQL
|
||||
* @param string|null $sortsql Order by conditions for the SQL
|
||||
* @return string
|
||||
*/
|
||||
protected function generate_get_records_sql(string $wheresql = null, string $sortsql = null) : string {
|
||||
$selectsql = 'SELECT * FROM {' . self::TABLE . '} ' . $this->get_table_alias();
|
||||
$selectsql .= $wheresql ? ' WHERE ' . $wheresql : '';
|
||||
$selectsql .= $sortsql ? ' ORDER BY ' . $sortsql : '';
|
||||
|
||||
return $selectsql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the DB records into post_read_receipt_collection entities.
|
||||
*
|
||||
* @param array $results The DB records
|
||||
* @return post_read_receipt_collection
|
||||
*/
|
||||
protected function from_db_records(array $results) {
|
||||
$entityfactory = $this->get_entity_factory();
|
||||
$records = array_map(function($result) {
|
||||
return $result['record'];
|
||||
}, $results);
|
||||
|
||||
return $entityfactory->get_post_read_receipt_collection_from_stdclasses($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the post_read_receipt_collection for the given user and set
|
||||
* of posts.
|
||||
*
|
||||
* @param int $userid Id of the user to load receipts for
|
||||
* @param int[] $postids List of post ids to load receipts for
|
||||
* @return post_read_receipt_collection
|
||||
*/
|
||||
public function get_from_user_id_and_post_ids(int $userid, array $postids) {
|
||||
$alias = $this->get_table_alias();
|
||||
[$postidinsql, $params] = $this->get_db()->get_in_or_equal($postids);
|
||||
$params[] = $userid;
|
||||
|
||||
$wheresql = "{$alias}.postid {$postidinsql}";
|
||||
$wheresql .= " AND {$alias}.userid = ?";
|
||||
$sql = $this->generate_get_records_sql($wheresql);
|
||||
$records = $this->get_db()->get_records_sql($sql, $params);
|
||||
|
||||
return $this->transform_db_records_to_entities($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the post_read_receipt_collection for the given user and set
|
||||
* of posts.
|
||||
*
|
||||
* @param stdClass $user The user to load receipts for
|
||||
* @param post_entity[] $posts List of posts to load receipts for
|
||||
* @return post_read_receipt_collection
|
||||
*/
|
||||
public function get_from_user_and_posts(stdClass $user, array $posts) {
|
||||
$postids = array_map(function($post) {
|
||||
return $post->get_id();
|
||||
}, $posts);
|
||||
return $this->get_from_user_id_and_post_ids($user->id, $postids);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Extract context vault preprocessor.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\vaults\preprocessors;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use context;
|
||||
use context_helper;
|
||||
|
||||
/**
|
||||
* Extract context vault preprocessor.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class extract_context {
|
||||
/**
|
||||
* Extract the contexts from a list of records.
|
||||
*
|
||||
* @param stdClass[] $records The list of records which have context properties
|
||||
* @return context[] List of contexts matching the records.
|
||||
*/
|
||||
public function execute(array $records) : array {
|
||||
return array_map(function($record) {
|
||||
$contextid = $record->ctxid;
|
||||
context_helper::preload_from_record($record);
|
||||
$context = context::instance_by_id($contextid);
|
||||
return $context;
|
||||
}, $records);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Extract record vault preprocessor.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\vaults\preprocessors;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use moodle_database;
|
||||
|
||||
/**
|
||||
* Extract record vault preprocessor.
|
||||
*
|
||||
* Extract record vault preprocessor. Typically used to separate out records
|
||||
* when two tables have been joined together in a query.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class extract_record {
|
||||
/** @var moodle_database $db A moodle database */
|
||||
private $db;
|
||||
/** @var string $table The table name where the records were loaded from */
|
||||
private $table;
|
||||
/** @var string $alias The table alias used as the record property prefix */
|
||||
private $alias;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param moodle_database $db A moodle database
|
||||
* @param string $table The table name where the records were loaded from
|
||||
* @param string $alias The table alias used as the record property prefix
|
||||
*/
|
||||
public function __construct(moodle_database $db, string $table, string $alias) {
|
||||
$this->db = $db;
|
||||
$this->table = $table;
|
||||
$this->alias = $alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a record embedded in the properties of another record out into a
|
||||
* separate record.
|
||||
*
|
||||
* @param stdClass[] $records The list of records to process
|
||||
* @return stdClass[] The extracted records
|
||||
*/
|
||||
public function execute(array $records) : array {
|
||||
$db = $this->db;
|
||||
$fields = $this->db->get_preload_columns($this->table, $this->alias);
|
||||
|
||||
return array_map(function($record) use ($db, $fields) {
|
||||
return $db->extract_fields_from_object($fields, $record);
|
||||
}, $records);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Extract user vault preprocessor.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2018 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\vaults\preprocessors;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use user_picture;
|
||||
|
||||
/**
|
||||
* Extract user vault preprocessor.
|
||||
*
|
||||
* Used to separate out the user record
|
||||
* from a list of DB records that have been joined on the user table.
|
||||
*
|
||||
* @copyright 2018 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class extract_user {
|
||||
/** @var string $idalias The alias for the id property of the user */
|
||||
private $idalias;
|
||||
/** @var string $alias The prefix used for each of the user properties */
|
||||
private $alias;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $idalias The alias for the id property of the user
|
||||
* @param string $alias The prefix used for each of the user properties
|
||||
*/
|
||||
public function __construct(string $idalias, string $alias) {
|
||||
$this->idalias = $idalias;
|
||||
$this->alias = $alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the user records from a list of DB records.
|
||||
*
|
||||
* @param stdClass[] $records The DB records
|
||||
* @return stdClass[] The list of extracted users
|
||||
*/
|
||||
public function execute(array $records) : array {
|
||||
$idalias = $this->idalias;
|
||||
$alias = $this->alias;
|
||||
|
||||
return array_map(function($record) use ($idalias, $alias) {
|
||||
return user_picture::unalias($record, null, $idalias, $alias);
|
||||
}, $records);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
// This file is part of Moodle - https://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/>.
|
||||
|
||||
/**
|
||||
* Defined caches used internally by the plugin.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @category cache
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$definitions = [
|
||||
'forum_is_tracked' => [
|
||||
'mode' => cache_store::MODE_REQUEST,
|
||||
'simplekeys' => true,
|
||||
'simpledata' => true,
|
||||
'staticacceleration' => true
|
||||
],
|
||||
];
|
||||
@@ -113,4 +113,14 @@ $functions = array(
|
||||
'type' => 'read',
|
||||
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
|
||||
),
|
||||
|
||||
'mod_forum_set_subscription_state' => array(
|
||||
'classname' => 'mod_forum_external',
|
||||
'methodname' => 'set_subscription_state',
|
||||
'classpath' => 'mod/forum/externallib.php',
|
||||
'description' => 'Set the subscription state',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -410,3 +410,905 @@ function forum_cron() {
|
||||
debugging("forum_cron() has been deprecated and replaced with new tasks. Please uses these instead.",
|
||||
DEBUG_DEVELOPER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a forum discussion
|
||||
*
|
||||
* @uses CONTEXT_MODULE
|
||||
* @uses FORUM_MODE_FLATNEWEST
|
||||
* @uses FORUM_MODE_FLATOLDEST
|
||||
* @uses FORUM_MODE_THREADED
|
||||
* @uses FORUM_MODE_NESTED
|
||||
* @param stdClass $course
|
||||
* @param stdClass $cm
|
||||
* @param stdClass $forum
|
||||
* @param stdClass $discussion
|
||||
* @param stdClass $post
|
||||
* @param int $mode
|
||||
* @param mixed $canreply
|
||||
* @param bool $canrate
|
||||
* @deprecated since Moodle 3.7
|
||||
*/
|
||||
function forum_print_discussion($course, $cm, $forum, $discussion, $post, $mode, $canreply=NULL, $canrate=false) {
|
||||
debugging('forum_print_discussion() has been deprecated, ' .
|
||||
'please use \mod_forum\local\renderers\discussion instead.', DEBUG_DEVELOPER);
|
||||
|
||||
global $USER, $CFG;
|
||||
|
||||
require_once($CFG->dirroot.'/rating/lib.php');
|
||||
|
||||
$ownpost = (isloggedin() && $USER->id == $post->userid);
|
||||
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
if ($canreply === NULL) {
|
||||
$reply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
|
||||
} else {
|
||||
$reply = $canreply;
|
||||
}
|
||||
|
||||
// $cm holds general cache for forum functions
|
||||
$cm->cache = new stdClass;
|
||||
$cm->cache->groups = groups_get_all_groups($course->id, 0, $cm->groupingid);
|
||||
$cm->cache->usersgroups = array();
|
||||
|
||||
$posters = array();
|
||||
|
||||
// preload all posts - TODO: improve...
|
||||
if ($mode == FORUM_MODE_FLATNEWEST) {
|
||||
$sort = "p.created DESC";
|
||||
} else {
|
||||
$sort = "p.created ASC";
|
||||
}
|
||||
|
||||
$forumtracked = forum_tp_is_tracked($forum);
|
||||
$posts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
|
||||
$post = $posts[$post->id];
|
||||
|
||||
foreach ($posts as $pid=>$p) {
|
||||
$posters[$p->userid] = $p->userid;
|
||||
}
|
||||
|
||||
// preload all groups of ppl that posted in this discussion
|
||||
if ($postersgroups = groups_get_all_groups($course->id, $posters, $cm->groupingid, 'gm.id, gm.groupid, gm.userid')) {
|
||||
foreach($postersgroups as $pg) {
|
||||
if (!isset($cm->cache->usersgroups[$pg->userid])) {
|
||||
$cm->cache->usersgroups[$pg->userid] = array();
|
||||
}
|
||||
$cm->cache->usersgroups[$pg->userid][$pg->groupid] = $pg->groupid;
|
||||
}
|
||||
unset($postersgroups);
|
||||
}
|
||||
|
||||
//load ratings
|
||||
if ($forum->assessed != RATING_AGGREGATE_NONE) {
|
||||
$ratingoptions = new stdClass;
|
||||
$ratingoptions->context = $modcontext;
|
||||
$ratingoptions->component = 'mod_forum';
|
||||
$ratingoptions->ratingarea = 'post';
|
||||
$ratingoptions->items = $posts;
|
||||
$ratingoptions->aggregate = $forum->assessed;//the aggregation method
|
||||
$ratingoptions->scaleid = $forum->scale;
|
||||
$ratingoptions->userid = $USER->id;
|
||||
if ($forum->type == 'single' or !$discussion->id) {
|
||||
$ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/view.php?id=$cm->id";
|
||||
} else {
|
||||
$ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id";
|
||||
}
|
||||
$ratingoptions->assesstimestart = $forum->assesstimestart;
|
||||
$ratingoptions->assesstimefinish = $forum->assesstimefinish;
|
||||
|
||||
$rm = new rating_manager();
|
||||
$posts = $rm->get_ratings($ratingoptions);
|
||||
}
|
||||
|
||||
|
||||
$post->forum = $forum->id; // Add the forum id to the post object, later used by forum_print_post
|
||||
$post->forumtype = $forum->type;
|
||||
|
||||
$post->subject = format_string($post->subject);
|
||||
|
||||
$postread = !empty($post->postread);
|
||||
|
||||
forum_print_post_start($post);
|
||||
forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, false,
|
||||
'', '', $postread, true, $forumtracked);
|
||||
|
||||
switch ($mode) {
|
||||
case FORUM_MODE_FLATOLDEST :
|
||||
case FORUM_MODE_FLATNEWEST :
|
||||
default:
|
||||
forum_print_posts_flat($course, $cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts);
|
||||
break;
|
||||
|
||||
case FORUM_MODE_THREADED :
|
||||
forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, 0, $reply, $forumtracked, $posts);
|
||||
break;
|
||||
|
||||
case FORUM_MODE_NESTED :
|
||||
forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
|
||||
break;
|
||||
}
|
||||
forum_print_post_end($post);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a static array of posts that are open.
|
||||
*
|
||||
* @return array
|
||||
* @deprecated since Moodle 3.7
|
||||
*/
|
||||
function forum_post_nesting_cache() {
|
||||
debugging('forum_post_nesting_cache() has been deprecated, ' .
|
||||
'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
|
||||
static $nesting = array();
|
||||
return $nesting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true for the first time this post was started
|
||||
*
|
||||
* @param int $id The id of the post to start
|
||||
* @return bool
|
||||
* @deprecated since Moodle 3.7
|
||||
*/
|
||||
function forum_should_start_post_nesting($id) {
|
||||
debugging('forum_should_start_post_nesting() has been deprecated, ' .
|
||||
'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
|
||||
$cache = forum_post_nesting_cache();
|
||||
if (!array_key_exists($id, $cache)) {
|
||||
$cache[$id] = 1;
|
||||
return true;
|
||||
} else {
|
||||
$cache[$id]++;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true when all the opens are nested with a close.
|
||||
*
|
||||
* @param int $id The id of the post to end
|
||||
* @return bool
|
||||
* @deprecated since Moodle 3.7
|
||||
*/
|
||||
function forum_should_end_post_nesting($id) {
|
||||
debugging('forum_should_end_post_nesting() has been deprecated, ' .
|
||||
'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
|
||||
$cache = forum_post_nesting_cache();
|
||||
if (!array_key_exists($id, $cache)) {
|
||||
return true;
|
||||
} else {
|
||||
$cache[$id]--;
|
||||
if ($cache[$id] == 0) {
|
||||
unset($cache[$id]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a forum post container
|
||||
*
|
||||
* @param object $post The post to print.
|
||||
* @param bool $return Return the string or print it
|
||||
* @return string
|
||||
* @deprecated since Moodle 3.7
|
||||
*/
|
||||
function forum_print_post_start($post, $return = false) {
|
||||
debugging('forum_print_post_start() has been deprecated, ' .
|
||||
'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
|
||||
$output = '';
|
||||
|
||||
if (forum_should_start_post_nesting($post->id)) {
|
||||
$attributes = [
|
||||
'id' => 'p'.$post->id,
|
||||
'tabindex' => -1,
|
||||
'class' => 'relativelink'
|
||||
];
|
||||
$output .= html_writer::start_tag('article', $attributes);
|
||||
}
|
||||
if ($return) {
|
||||
return $output;
|
||||
}
|
||||
echo $output;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* End a forum post container
|
||||
*
|
||||
* @param object $post The post to print.
|
||||
* @param bool $return Return the string or print it
|
||||
* @return string
|
||||
* @deprecated since Moodle 3.7
|
||||
*/
|
||||
function forum_print_post_end($post, $return = false) {
|
||||
debugging('forum_print_post_end() has been deprecated, ' .
|
||||
'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
|
||||
$output = '';
|
||||
|
||||
if (forum_should_end_post_nesting($post->id)) {
|
||||
$output .= html_writer::end_tag('article');
|
||||
}
|
||||
if ($return) {
|
||||
return $output;
|
||||
}
|
||||
echo $output;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a forum post
|
||||
* This function should always be surrounded with calls to forum_print_post_start
|
||||
* and forum_print_post_end to create the surrounding container for the post.
|
||||
* Replies can be nested before forum_print_post_end and should reflect the structure of
|
||||
* thread.
|
||||
*
|
||||
* @global object
|
||||
* @global object
|
||||
* @uses FORUM_MODE_THREADED
|
||||
* @uses PORTFOLIO_FORMAT_PLAINHTML
|
||||
* @uses PORTFOLIO_FORMAT_FILE
|
||||
* @uses PORTFOLIO_FORMAT_RICHHTML
|
||||
* @uses PORTFOLIO_ADD_TEXT_LINK
|
||||
* @uses CONTEXT_MODULE
|
||||
* @param object $post The post to print.
|
||||
* @param object $discussion
|
||||
* @param object $forum
|
||||
* @param object $cm
|
||||
* @param object $course
|
||||
* @param boolean $ownpost Whether this post belongs to the current user.
|
||||
* @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
|
||||
* @param boolean $link Just print a shortened version of the post as a link to the full post.
|
||||
* @param string $footer Extra stuff to print after the message.
|
||||
* @param string $highlight Space-separated list of terms to highlight.
|
||||
* @param int $post_read true, false or -99. If we already know whether this user
|
||||
* has read this post, pass that in, otherwise, pass in -99, and this
|
||||
* function will work it out.
|
||||
* @param boolean $dummyifcantsee When forum_user_can_see_post says that
|
||||
* the current user can't see this post, if this argument is true
|
||||
* (the default) then print a dummy 'you can't see this post' post.
|
||||
* If false, don't output anything at all.
|
||||
* @param bool|null $istracked
|
||||
* @return void
|
||||
* @deprecated since Moodle 3.7
|
||||
*/
|
||||
function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
|
||||
$footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
|
||||
debugging('forum_print_post() has been deprecated, ' .
|
||||
'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
|
||||
global $USER, $CFG, $OUTPUT;
|
||||
|
||||
require_once($CFG->libdir . '/filelib.php');
|
||||
|
||||
// String cache
|
||||
static $str;
|
||||
// This is an extremely hacky way to ensure we only print the 'unread' anchor
|
||||
// the first time we encounter an unread post on a page. Ideally this would
|
||||
// be moved into the caller somehow, and be better testable. But at the time
|
||||
// of dealing with this bug, this static workaround was the most surgical and
|
||||
// it fits together with only printing th unread anchor id once on a given page.
|
||||
static $firstunreadanchorprinted = false;
|
||||
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
|
||||
$post->course = $course->id;
|
||||
$post->forum = $forum->id;
|
||||
$post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
|
||||
if (!empty($CFG->enableplagiarism)) {
|
||||
require_once($CFG->libdir.'/plagiarismlib.php');
|
||||
$post->message .= plagiarism_get_links(array('userid' => $post->userid,
|
||||
'content' => $post->message,
|
||||
'cmid' => $cm->id,
|
||||
'course' => $post->course,
|
||||
'forum' => $post->forum));
|
||||
}
|
||||
|
||||
// caching
|
||||
if (!isset($cm->cache)) {
|
||||
$cm->cache = new stdClass;
|
||||
}
|
||||
|
||||
if (!isset($cm->cache->caps)) {
|
||||
$cm->cache->caps = array();
|
||||
$cm->cache->caps['mod/forum:viewdiscussion'] = has_capability('mod/forum:viewdiscussion', $modcontext);
|
||||
$cm->cache->caps['moodle/site:viewfullnames'] = has_capability('moodle/site:viewfullnames', $modcontext);
|
||||
$cm->cache->caps['mod/forum:editanypost'] = has_capability('mod/forum:editanypost', $modcontext);
|
||||
$cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
|
||||
$cm->cache->caps['mod/forum:deleteownpost'] = has_capability('mod/forum:deleteownpost', $modcontext);
|
||||
$cm->cache->caps['mod/forum:deleteanypost'] = has_capability('mod/forum:deleteanypost', $modcontext);
|
||||
$cm->cache->caps['mod/forum:viewanyrating'] = has_capability('mod/forum:viewanyrating', $modcontext);
|
||||
$cm->cache->caps['mod/forum:exportpost'] = has_capability('mod/forum:exportpost', $modcontext);
|
||||
$cm->cache->caps['mod/forum:exportownpost'] = has_capability('mod/forum:exportownpost', $modcontext);
|
||||
}
|
||||
|
||||
if (!isset($cm->uservisible)) {
|
||||
$cm->uservisible = \core_availability\info_module::is_user_visible($cm, 0, false);
|
||||
}
|
||||
|
||||
if ($istracked && is_null($postisread)) {
|
||||
$postisread = forum_tp_is_post_read($USER->id, $post);
|
||||
}
|
||||
|
||||
if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm, false)) {
|
||||
// Do _not_ check the deleted flag - we need to display a different UI.
|
||||
$output = '';
|
||||
if (!$dummyifcantsee) {
|
||||
if ($return) {
|
||||
return $output;
|
||||
}
|
||||
echo $output;
|
||||
return;
|
||||
}
|
||||
|
||||
$output .= html_writer::start_tag('div', array('class' => 'forumpost clearfix',
|
||||
'aria-label' => get_string('hiddenforumpost', 'forum')));
|
||||
$output .= html_writer::start_tag('header', array('class' => 'row header'));
|
||||
$output .= html_writer::tag('div', '', array('class' => 'left picture', 'role' => 'presentation')); // Picture.
|
||||
if ($post->parent) {
|
||||
$output .= html_writer::start_tag('div', array('class' => 'topic'));
|
||||
} else {
|
||||
$output .= html_writer::start_tag('div', array('class' => 'topic starter'));
|
||||
}
|
||||
$output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class' => 'subject',
|
||||
'role' => 'header',
|
||||
'id' => ('headp' . $post->id))); // Subject.
|
||||
$authorclasses = array('class' => 'author');
|
||||
$output .= html_writer::tag('address', get_string('forumauthorhidden', 'forum'), $authorclasses); // Author.
|
||||
$output .= html_writer::end_tag('div');
|
||||
$output .= html_writer::end_tag('header'); // Header.
|
||||
$output .= html_writer::start_tag('div', array('class'=>'row'));
|
||||
$output .= html_writer::tag('div', ' ', array('class'=>'left side')); // Groups
|
||||
$output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
|
||||
$output .= html_writer::end_tag('div'); // row
|
||||
$output .= html_writer::end_tag('div'); // forumpost
|
||||
|
||||
if ($return) {
|
||||
return $output;
|
||||
}
|
||||
echo $output;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($post->deleted)) {
|
||||
// Note: Posts marked as deleted are still returned by the above forum_user_can_post because it is required for
|
||||
// nesting of posts.
|
||||
$output = '';
|
||||
if (!$dummyifcantsee) {
|
||||
if ($return) {
|
||||
return $output;
|
||||
}
|
||||
echo $output;
|
||||
return;
|
||||
}
|
||||
$output .= html_writer::start_tag('div', [
|
||||
'class' => 'forumpost clearfix',
|
||||
'aria-label' => get_string('forumbodydeleted', 'forum'),
|
||||
]);
|
||||
|
||||
$output .= html_writer::start_tag('header', array('class' => 'row header'));
|
||||
$output .= html_writer::tag('div', '', array('class' => 'left picture', 'role' => 'presentation'));
|
||||
|
||||
$classes = ['topic'];
|
||||
if (!empty($post->parent)) {
|
||||
$classes[] = 'starter';
|
||||
}
|
||||
$output .= html_writer::start_tag('div', ['class' => implode(' ', $classes)]);
|
||||
|
||||
// Subject.
|
||||
$output .= html_writer::tag('div', get_string('forumsubjectdeleted', 'forum'), [
|
||||
'class' => 'subject',
|
||||
'role' => 'header',
|
||||
'id' => ('headp' . $post->id)
|
||||
]);
|
||||
|
||||
// Author.
|
||||
$output .= html_writer::tag('address', '', ['class' => 'author']);
|
||||
|
||||
$output .= html_writer::end_tag('div');
|
||||
$output .= html_writer::end_tag('header'); // End header.
|
||||
$output .= html_writer::start_tag('div', ['class' => 'row']);
|
||||
$output .= html_writer::tag('div', ' ', ['class' => 'left side']); // Groups.
|
||||
$output .= html_writer::tag('div', get_string('forumbodydeleted', 'forum'), ['class' => 'content']); // Content.
|
||||
$output .= html_writer::end_tag('div'); // End row.
|
||||
$output .= html_writer::end_tag('div'); // End forumpost.
|
||||
|
||||
if ($return) {
|
||||
return $output;
|
||||
}
|
||||
echo $output;
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($str)) {
|
||||
$str = new stdClass;
|
||||
$str->edit = get_string('edit', 'forum');
|
||||
$str->delete = get_string('delete', 'forum');
|
||||
$str->reply = get_string('reply', 'forum');
|
||||
$str->parent = get_string('parent', 'forum');
|
||||
$str->pruneheading = get_string('pruneheading', 'forum');
|
||||
$str->prune = get_string('prune', 'forum');
|
||||
$str->displaymode = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
|
||||
$str->markread = get_string('markread', 'forum');
|
||||
$str->markunread = get_string('markunread', 'forum');
|
||||
}
|
||||
|
||||
$discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
|
||||
|
||||
// Build an object that represents the posting user
|
||||
$postuser = new stdClass;
|
||||
$postuserfields = explode(',', user_picture::fields());
|
||||
$postuser = username_load_fields_from_object($postuser, $post, null, $postuserfields);
|
||||
$postuser->id = $post->userid;
|
||||
$postuser->fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
|
||||
$postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
|
||||
|
||||
// Prepare the groups the posting user belongs to
|
||||
if (isset($cm->cache->usersgroups)) {
|
||||
$groups = array();
|
||||
if (isset($cm->cache->usersgroups[$post->userid])) {
|
||||
foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
|
||||
$groups[$gid] = $cm->cache->groups[$gid];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
|
||||
}
|
||||
|
||||
// Prepare the attachements for the post, files then images
|
||||
list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
|
||||
|
||||
// Determine if we need to shorten this post
|
||||
$shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
|
||||
|
||||
// Prepare an array of commands
|
||||
$commands = array();
|
||||
|
||||
// Add a permalink.
|
||||
$permalink = new moodle_url($discussionlink);
|
||||
$permalink->set_anchor('p' . $post->id);
|
||||
$commands[] = array('url' => $permalink, 'text' => get_string('permalink', 'forum'), 'attributes' => ['rel' => 'bookmark']);
|
||||
|
||||
// SPECIAL CASE: The front page can display a news item post to non-logged in users.
|
||||
// Don't display the mark read / unread controls in this case.
|
||||
if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
|
||||
$url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
|
||||
$text = $str->markunread;
|
||||
if (!$postisread) {
|
||||
$url->param('mark', 'read');
|
||||
$text = $str->markread;
|
||||
}
|
||||
if ($str->displaymode == FORUM_MODE_THREADED) {
|
||||
$url->param('parent', $post->parent);
|
||||
} else {
|
||||
$url->set_anchor('p'.$post->id);
|
||||
}
|
||||
$commands[] = array('url'=>$url, 'text'=>$text, 'attributes' => ['rel' => 'bookmark']);
|
||||
}
|
||||
|
||||
// Zoom in to the parent specifically
|
||||
if ($post->parent) {
|
||||
$url = new moodle_url($discussionlink);
|
||||
if ($str->displaymode == FORUM_MODE_THREADED) {
|
||||
$url->param('parent', $post->parent);
|
||||
} else {
|
||||
$url->set_anchor('p'.$post->parent);
|
||||
}
|
||||
$commands[] = array('url'=>$url, 'text'=>$str->parent, 'attributes' => ['rel' => 'bookmark']);
|
||||
}
|
||||
|
||||
// Hack for allow to edit news posts those are not displayed yet until they are displayed
|
||||
$age = time() - $post->created;
|
||||
if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
|
||||
$age = 0;
|
||||
}
|
||||
|
||||
if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
|
||||
if (has_capability('moodle/course:manageactivities', $modcontext)) {
|
||||
// The first post in single simple is the forum description.
|
||||
$commands[] = array('url'=>new moodle_url('/course/modedit.php', array('update'=>$cm->id, 'sesskey'=>sesskey(), 'return'=>1)), 'text'=>$str->edit);
|
||||
}
|
||||
} else if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
|
||||
$commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
|
||||
}
|
||||
|
||||
if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
|
||||
$commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
|
||||
}
|
||||
|
||||
if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
|
||||
// Do not allow deleting of first post in single simple type.
|
||||
} else if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
|
||||
$commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
|
||||
}
|
||||
|
||||
if ($reply) {
|
||||
$commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mformforum', array('reply'=>$post->id)), 'text'=>$str->reply);
|
||||
}
|
||||
|
||||
if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
|
||||
$p = array('postid' => $post->id);
|
||||
require_once($CFG->libdir.'/portfoliolib.php');
|
||||
$button = new portfolio_add_button();
|
||||
$button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), 'mod_forum');
|
||||
if (empty($attachments)) {
|
||||
$button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
|
||||
} else {
|
||||
$button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
|
||||
}
|
||||
|
||||
$porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
|
||||
if (!empty($porfoliohtml)) {
|
||||
$commands[] = $porfoliohtml;
|
||||
}
|
||||
}
|
||||
// Finished building commands
|
||||
|
||||
|
||||
// Begin output
|
||||
|
||||
$output = '';
|
||||
|
||||
if ($istracked) {
|
||||
if ($postisread) {
|
||||
$forumpostclass = ' read';
|
||||
} else {
|
||||
$forumpostclass = ' unread';
|
||||
// If this is the first unread post printed then give it an anchor and id of unread.
|
||||
if (!$firstunreadanchorprinted) {
|
||||
$output .= html_writer::tag('a', '', array('id' => 'unread'));
|
||||
$firstunreadanchorprinted = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ignore trackign status if not tracked or tracked param missing
|
||||
$forumpostclass = '';
|
||||
}
|
||||
|
||||
$topicclass = '';
|
||||
if (empty($post->parent)) {
|
||||
$topicclass = ' firstpost starter';
|
||||
}
|
||||
|
||||
if (!empty($post->lastpost)) {
|
||||
$forumpostclass .= ' lastpost';
|
||||
}
|
||||
|
||||
// Flag to indicate whether we should hide the author or not.
|
||||
$authorhidden = forum_is_author_hidden($post, $forum);
|
||||
$postbyuser = new stdClass;
|
||||
$postbyuser->post = $post->subject;
|
||||
$postbyuser->user = $postuser->fullname;
|
||||
$discussionbyuser = get_string('postbyuser', 'forum', $postbyuser);
|
||||
// Begin forum post.
|
||||
$output .= html_writer::start_div('forumpost clearfix' . $forumpostclass . $topicclass,
|
||||
['aria-label' => $discussionbyuser]);
|
||||
// Begin header row.
|
||||
$output .= html_writer::start_tag('header', ['class' => 'row header clearfix']);
|
||||
|
||||
// User picture.
|
||||
if (!$authorhidden) {
|
||||
$picture = $OUTPUT->user_picture($postuser, ['courseid' => $course->id]);
|
||||
$output .= html_writer::div($picture, 'left picture', ['role' => 'presentation']);
|
||||
$topicclass = 'topic' . $topicclass;
|
||||
}
|
||||
|
||||
// Begin topic column.
|
||||
$output .= html_writer::start_div($topicclass);
|
||||
$postsubject = $post->subject;
|
||||
if (empty($post->subjectnoformat)) {
|
||||
$postsubject = format_string($postsubject);
|
||||
}
|
||||
$output .= html_writer::div($postsubject, 'subject', ['role' => 'heading', 'aria-level' => '1', 'id' => ('headp' . $post->id)]);
|
||||
|
||||
if ($authorhidden) {
|
||||
$bytext = userdate_htmltime($post->created);
|
||||
} else {
|
||||
$by = new stdClass();
|
||||
$by->date = userdate_htmltime($post->created);
|
||||
$by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
|
||||
$bytext = get_string('bynameondate', 'forum', $by);
|
||||
}
|
||||
$bytextoptions = [
|
||||
'class' => 'author'
|
||||
];
|
||||
$output .= html_writer::tag('address', $bytext, $bytextoptions);
|
||||
// End topic column.
|
||||
$output .= html_writer::end_div();
|
||||
|
||||
// End header row.
|
||||
$output .= html_writer::end_tag('header');
|
||||
|
||||
// Row with the forum post content.
|
||||
$output .= html_writer::start_div('row maincontent clearfix');
|
||||
// Show if author is not hidden or we have groups.
|
||||
if (!$authorhidden || $groups) {
|
||||
$output .= html_writer::start_div('left');
|
||||
$groupoutput = '';
|
||||
if ($groups) {
|
||||
$groupoutput = print_group_picture($groups, $course->id, false, true, true);
|
||||
}
|
||||
if (empty($groupoutput)) {
|
||||
$groupoutput = ' ';
|
||||
}
|
||||
$output .= html_writer::div($groupoutput, 'grouppictures');
|
||||
$output .= html_writer::end_div(); // Left side.
|
||||
}
|
||||
|
||||
$output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
|
||||
$output .= html_writer::start_tag('div', array('class'=>'content'));
|
||||
|
||||
$options = new stdClass;
|
||||
$options->para = false;
|
||||
$options->trusted = $post->messagetrust;
|
||||
$options->context = $modcontext;
|
||||
if ($shortenpost) {
|
||||
// Prepare shortened version by filtering the text then shortening it.
|
||||
$postclass = 'shortenedpost';
|
||||
$postcontent = format_text($post->message, $post->messageformat, $options);
|
||||
$postcontent = shorten_text($postcontent, $CFG->forum_shortpost);
|
||||
$postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
|
||||
$postcontent .= html_writer::tag('div', '('.get_string('numwords', 'moodle', count_words($post->message)).')',
|
||||
array('class'=>'post-word-count'));
|
||||
} else {
|
||||
// Prepare whole post
|
||||
$postclass = 'fullpost';
|
||||
$postcontent = format_text($post->message, $post->messageformat, $options, $course->id);
|
||||
if (!empty($highlight)) {
|
||||
$postcontent = highlight($highlight, $postcontent);
|
||||
}
|
||||
if (!empty($forum->displaywordcount)) {
|
||||
$postcontent .= html_writer::tag('div', get_string('numwords', 'moodle', count_words($postcontent)),
|
||||
array('class'=>'post-word-count'));
|
||||
}
|
||||
$postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
|
||||
}
|
||||
|
||||
if (\core_tag_tag::is_enabled('mod_forum', 'forum_posts')) {
|
||||
$postcontent .= $OUTPUT->tag_list(core_tag_tag::get_item_tags('mod_forum', 'forum_posts', $post->id), null, 'forum-tags');
|
||||
}
|
||||
|
||||
// Output the post content
|
||||
$output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
|
||||
$output .= html_writer::end_tag('div'); // Content
|
||||
$output .= html_writer::end_tag('div'); // Content mask
|
||||
$output .= html_writer::end_tag('div'); // Row
|
||||
|
||||
$output .= html_writer::start_tag('nav', array('class' => 'row side'));
|
||||
$output .= html_writer::tag('div',' ', array('class'=>'left'));
|
||||
$output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
|
||||
|
||||
if (!empty($attachments)) {
|
||||
$output .= html_writer::tag('div', $attachments, array('class' => 'attachments'));
|
||||
}
|
||||
|
||||
// Output ratings
|
||||
if (!empty($post->rating)) {
|
||||
$output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
|
||||
}
|
||||
|
||||
// Output the commands
|
||||
$commandhtml = array();
|
||||
foreach ($commands as $command) {
|
||||
if (is_array($command)) {
|
||||
$attributes = ['class' => 'nav-item nav-link'];
|
||||
if (isset($command['attributes'])) {
|
||||
$attributes = array_merge($attributes, $command['attributes']);
|
||||
}
|
||||
$commandhtml[] = html_writer::link($command['url'], $command['text'], $attributes);
|
||||
} else {
|
||||
$commandhtml[] = $command;
|
||||
}
|
||||
}
|
||||
$output .= html_writer::tag('div', implode(' ', $commandhtml), array('class' => 'commands nav'));
|
||||
|
||||
// Output link to post if required
|
||||
if ($link) {
|
||||
if (forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext)) {
|
||||
$langstring = 'discussthistopic';
|
||||
} else {
|
||||
$langstring = 'viewthediscussion';
|
||||
}
|
||||
if ($post->replies == 1) {
|
||||
$replystring = get_string('repliesone', 'forum', $post->replies);
|
||||
} else {
|
||||
$replystring = get_string('repliesmany', 'forum', $post->replies);
|
||||
}
|
||||
if (!empty($discussion->unread) && $discussion->unread !== '-') {
|
||||
$replystring .= ' <span class="sep">/</span> <span class="unread">';
|
||||
$unreadlink = new moodle_url($discussionlink, null, 'unread');
|
||||
if ($discussion->unread == 1) {
|
||||
$replystring .= html_writer::link($unreadlink, get_string('unreadpostsone', 'forum'));
|
||||
} else {
|
||||
$replystring .= html_writer::link($unreadlink, get_string('unreadpostsnumber', 'forum', $discussion->unread));
|
||||
}
|
||||
$replystring .= '</span>';
|
||||
}
|
||||
|
||||
$output .= html_writer::start_tag('div', array('class'=>'link'));
|
||||
$output .= html_writer::link($discussionlink, get_string($langstring, 'forum'));
|
||||
$output .= ' ('.$replystring.')';
|
||||
$output .= html_writer::end_tag('div'); // link
|
||||
}
|
||||
|
||||
// Output footer if required
|
||||
if ($footer) {
|
||||
$output .= html_writer::tag('div', $footer, array('class'=>'footer'));
|
||||
}
|
||||
|
||||
// Close remaining open divs
|
||||
$output .= html_writer::end_tag('div'); // content
|
||||
$output .= html_writer::end_tag('nav'); // row
|
||||
$output .= html_writer::end_tag('div'); // forumpost
|
||||
|
||||
// Mark the forum post as read if required
|
||||
if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
|
||||
forum_tp_mark_post_read($USER->id, $post);
|
||||
}
|
||||
|
||||
if ($return) {
|
||||
return $output;
|
||||
}
|
||||
echo $output;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @global object
|
||||
* @global object
|
||||
* @uses FORUM_MODE_FLATNEWEST
|
||||
* @param object $course
|
||||
* @param object $cm
|
||||
* @param object $forum
|
||||
* @param object $discussion
|
||||
* @param object $post
|
||||
* @param object $mode
|
||||
* @param bool $reply
|
||||
* @param bool $forumtracked
|
||||
* @param array $posts
|
||||
* @return void
|
||||
* @deprecated since Moodle 3.7
|
||||
*/
|
||||
function forum_print_posts_flat($course, &$cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts) {
|
||||
debugging('forum_print_posts_flat() has been deprecated, ' .
|
||||
'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
|
||||
global $USER, $CFG;
|
||||
|
||||
$link = false;
|
||||
|
||||
foreach ($posts as $post) {
|
||||
if (!$post->parent) {
|
||||
continue;
|
||||
}
|
||||
$post->subject = format_string($post->subject);
|
||||
$ownpost = ($USER->id == $post->userid);
|
||||
|
||||
$postread = !empty($post->postread);
|
||||
|
||||
forum_print_post_start($post);
|
||||
forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
|
||||
'', '', $postread, true, $forumtracked);
|
||||
forum_print_post_end($post);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Document this function
|
||||
*
|
||||
* @global object
|
||||
* @global object
|
||||
* @uses CONTEXT_MODULE
|
||||
* @return void
|
||||
* @deprecated since Moodle 3.7
|
||||
*/
|
||||
function forum_print_posts_threaded($course, &$cm, $forum, $discussion, $parent, $depth, $reply, $forumtracked, $posts) {
|
||||
debugging('forum_print_posts_threaded() has been deprecated, ' .
|
||||
'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
|
||||
global $USER, $CFG;
|
||||
|
||||
$link = false;
|
||||
|
||||
if (!empty($posts[$parent->id]->children)) {
|
||||
$posts = $posts[$parent->id]->children;
|
||||
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
$canviewfullnames = has_capability('moodle/site:viewfullnames', $modcontext);
|
||||
|
||||
foreach ($posts as $post) {
|
||||
|
||||
echo '<div class="indent">';
|
||||
if ($depth > 0) {
|
||||
$ownpost = ($USER->id == $post->userid);
|
||||
$post->subject = format_string($post->subject);
|
||||
|
||||
$postread = !empty($post->postread);
|
||||
|
||||
forum_print_post_start($post);
|
||||
forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
|
||||
'', '', $postread, true, $forumtracked);
|
||||
forum_print_post_end($post);
|
||||
} else {
|
||||
if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm, true)) {
|
||||
if (forum_user_can_see_post($forum, $discussion, $post, null, $cm, false)) {
|
||||
// This post has been deleted but still exists and may have children.
|
||||
$subject = get_string('privacy:request:delete:post:subject', 'mod_forum');
|
||||
$byline = '';
|
||||
} else {
|
||||
// The user can't see this post at all.
|
||||
echo "</div>\n";
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
$by = new stdClass();
|
||||
$by->name = fullname($post, $canviewfullnames);
|
||||
$by->date = userdate_htmltime($post->modified);
|
||||
$byline = ' ' . get_string("bynameondate", "forum", $by);
|
||||
$subject = format_string($post->subject, true);
|
||||
}
|
||||
|
||||
if ($forumtracked) {
|
||||
if (!empty($post->postread)) {
|
||||
$style = '<span class="forumthread read">';
|
||||
} else {
|
||||
$style = '<span class="forumthread unread">';
|
||||
}
|
||||
} else {
|
||||
$style = '<span class="forumthread">';
|
||||
}
|
||||
|
||||
echo $style;
|
||||
echo "<a name='{$post->id}'></a>";
|
||||
echo html_writer::link(new moodle_url('/mod/forum/discuss.php', [
|
||||
'd' => $post->discussion,
|
||||
'parent' => $post->id,
|
||||
]), $subject);
|
||||
echo $byline;
|
||||
echo "</span>";
|
||||
}
|
||||
|
||||
forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, $depth-1, $reply, $forumtracked, $posts);
|
||||
echo "</div>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Document this function
|
||||
* @global object
|
||||
* @global object
|
||||
* @return void
|
||||
* @deprecated since Moodle 3.7
|
||||
*/
|
||||
function forum_print_posts_nested($course, &$cm, $forum, $discussion, $parent, $reply, $forumtracked, $posts) {
|
||||
debugging('forum_print_posts_nested() has been deprecated, ' .
|
||||
'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
|
||||
global $USER, $CFG;
|
||||
|
||||
$link = false;
|
||||
|
||||
if (!empty($posts[$parent->id]->children)) {
|
||||
$posts = $posts[$parent->id]->children;
|
||||
|
||||
foreach ($posts as $post) {
|
||||
|
||||
echo '<div class="indent">';
|
||||
if (!isloggedin()) {
|
||||
$ownpost = false;
|
||||
} else {
|
||||
$ownpost = ($USER->id == $post->userid);
|
||||
}
|
||||
|
||||
$post->subject = format_string($post->subject);
|
||||
$postread = !empty($post->postread);
|
||||
|
||||
forum_print_post_start($post);
|
||||
forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
|
||||
'', '', $postread, true, $forumtracked);
|
||||
forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
|
||||
forum_print_post_end($post);
|
||||
echo "</div>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+120
-204
@@ -40,42 +40,80 @@ if ($parent !== 0) {
|
||||
}
|
||||
$PAGE->set_url($url);
|
||||
|
||||
$discussion = $DB->get_record('forum_discussions', array('id' => $d), '*', MUST_EXIST);
|
||||
$course = $DB->get_record('course', array('id' => $discussion->course), '*', MUST_EXIST);
|
||||
$forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
|
||||
$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
|
||||
$vaultfactory = mod_forum\local\container::get_vault_factory();
|
||||
$discussionvault = $vaultfactory->get_discussion_vault();
|
||||
$discussion = $discussionvault->get_from_id($d);
|
||||
|
||||
if (!$discussion) {
|
||||
throw new \moodle_exception('Unable to find discussion with id ' . $discussionid);
|
||||
}
|
||||
|
||||
$forumvault = $vaultfactory->get_forum_vault();
|
||||
$forum = $forumvault->get_from_id($discussion->get_forum_id());
|
||||
|
||||
if (!$forum) {
|
||||
throw new \moodle_exception('Unable to find forum with id ' . $discussion->get_forum_id());
|
||||
}
|
||||
|
||||
$managerfactory = mod_forum\local\container::get_manager_factory();
|
||||
$capabilitymanager = $managerfactory->get_capability_manager($forum);
|
||||
$urlfactory = mod_forum\local\container::get_url_factory();
|
||||
|
||||
// Make sure we can render.
|
||||
if (!$capabilitymanager->can_view_discussions($USER)) {
|
||||
throw new moodle_exception('noviewdiscussionspermission', 'mod_forum');
|
||||
}
|
||||
|
||||
$datamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
|
||||
$forumdatamapper = $datamapperfactory->get_forum_data_mapper();
|
||||
$forumrecord = $forumdatamapper->to_legacy_object($forum);
|
||||
$discussiondatamapper = $datamapperfactory->get_discussion_data_mapper();
|
||||
$discussionrecord = $discussiondatamapper->to_legacy_object($discussion);
|
||||
$discussionviewurl = $urlfactory->get_discussion_view_url_from_discussion($discussion);
|
||||
|
||||
$course = $forum->get_course_record();
|
||||
$cm = $forum->get_course_module_record();
|
||||
|
||||
require_course_login($course, true, $cm);
|
||||
|
||||
// move this down fix for MDL-6926
|
||||
require_once($CFG->dirroot.'/mod/forum/lib.php');
|
||||
require_once($CFG->dirroot . '/mod/forum/lib.php');
|
||||
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
require_capability('mod/forum:viewdiscussion', $modcontext, NULL, true, 'noviewdiscussionspermission', 'forum');
|
||||
$modcontext = $forum->get_context();
|
||||
|
||||
if (!empty($CFG->enablerssfeeds) && !empty($CFG->forum_enablerssfeeds) && $forum->rsstype && $forum->rssarticles) {
|
||||
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, array('context' => context_course::instance($course->id))) . ': ' . format_string($forum->name);
|
||||
rss_add_http_header($modcontext, 'mod_forum', $forum, $rsstitle);
|
||||
$rsstitle = format_string(
|
||||
$course->shortname,
|
||||
true,
|
||||
['context' => context_course::instance($course->id)]
|
||||
);
|
||||
$rsstitle .= ': ' . format_string($forum->get_name());
|
||||
rss_add_http_header($modcontext, 'mod_forum', $forumrecord, $rsstitle);
|
||||
}
|
||||
|
||||
// Move discussion if requested.
|
||||
if ($move > 0 and confirm_sesskey()) {
|
||||
$return = $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
|
||||
if ($move > 0 && confirm_sesskey()) {
|
||||
$forumid = $forum->get_id();
|
||||
$discussionid = $discussion->get_id();
|
||||
$return = $discussionviewurl->out(false);
|
||||
|
||||
if (!$forumto = $DB->get_record('forum', array('id' => $move))) {
|
||||
if (!$forumto = $DB->get_record('forum', ['id' => $move])) {
|
||||
print_error('cannotmovetonotexist', 'forum', $return);
|
||||
}
|
||||
|
||||
require_capability('mod/forum:movediscussions', $modcontext);
|
||||
|
||||
if ($forum->type == 'single') {
|
||||
print_error('cannotmovefromsingleforum', 'forum', $return);
|
||||
}
|
||||
|
||||
if (!$forumto = $DB->get_record('forum', array('id' => $move))) {
|
||||
print_error('cannotmovetonotexist', 'forum', $return);
|
||||
if (!$capabilitymanager->can_move_discussions($USER)) {
|
||||
if ($forum->get_type() == 'single') {
|
||||
print_error('cannotmovefromsingleforum', 'forum', $return);
|
||||
} else {
|
||||
print_error('nopermissions', 'error', $return, get_capability_string('mod/forum:movediscussions'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($forumto->type == 'single') {
|
||||
@@ -88,6 +126,7 @@ if ($move > 0 and confirm_sesskey()) {
|
||||
if (!array_key_exists($forumto->id, $forums)) {
|
||||
print_error('cannotmovetonotfound', 'forum', $return);
|
||||
}
|
||||
|
||||
$cmto = $forums[$forumto->id];
|
||||
if (!$cmto->uservisible) {
|
||||
print_error('cannotmovenotvisible', 'forum', $return);
|
||||
@@ -96,13 +135,13 @@ if ($move > 0 and confirm_sesskey()) {
|
||||
$destinationctx = context_module::instance($cmto->id);
|
||||
require_capability('mod/forum:startdiscussion', $destinationctx);
|
||||
|
||||
if (!forum_move_attachments($discussion, $forum->id, $forumto->id)) {
|
||||
if (!forum_move_attachments($discussionrecord, $forumid, $forumto->id)) {
|
||||
echo $OUTPUT->notification("Errors occurred while moving attachment directories - check your file permissions");
|
||||
}
|
||||
// For each subscribed user in this forum and discussion, copy over per-discussion subscriptions if required.
|
||||
$discussiongroup = $discussion->groupid == -1 ? 0 : $discussion->groupid;
|
||||
$discussiongroup = $discussion->get_group_id() == -1 ? 0 : $discussion->get_group_id();
|
||||
$potentialsubscribers = \mod_forum\subscriptions::fetch_subscribed_users(
|
||||
$forum,
|
||||
$forumrecord,
|
||||
$discussiongroup,
|
||||
$modcontext,
|
||||
'u.id',
|
||||
@@ -113,14 +152,14 @@ if ($move > 0 and confirm_sesskey()) {
|
||||
// Firstly for the forum being moved to.
|
||||
\mod_forum\subscriptions::fill_subscription_cache($forumto->id);
|
||||
// And also for the discussion being moved.
|
||||
\mod_forum\subscriptions::fill_subscription_cache($forum->id);
|
||||
$subscriptionchanges = array();
|
||||
\mod_forum\subscriptions::fill_subscription_cache($forumid);
|
||||
$subscriptionchanges = [];
|
||||
$subscriptiontime = time();
|
||||
foreach ($potentialsubscribers as $subuser) {
|
||||
$userid = $subuser->id;
|
||||
$targetsubscription = \mod_forum\subscriptions::is_subscribed($userid, $forumto, null, $cmto);
|
||||
$discussionsubscribed = \mod_forum\subscriptions::is_subscribed($userid, $forum, $discussion->id);
|
||||
$forumsubscribed = \mod_forum\subscriptions::is_subscribed($userid, $forum);
|
||||
$discussionsubscribed = \mod_forum\subscriptions::is_subscribed($userid, $forumrecord, $discussionid);
|
||||
$forumsubscribed = \mod_forum\subscriptions::is_subscribed($userid, $forumrecord);
|
||||
|
||||
if ($forumsubscribed && !$discussionsubscribed && $targetsubscription) {
|
||||
// The user has opted out of this discussion and the move would cause them to receive notifications again.
|
||||
@@ -133,12 +172,12 @@ if ($move > 0 and confirm_sesskey()) {
|
||||
}
|
||||
}
|
||||
|
||||
$DB->set_field('forum_discussions', 'forum', $forumto->id, array('id' => $discussion->id));
|
||||
$DB->set_field('forum_read', 'forumid', $forumto->id, array('discussionid' => $discussion->id));
|
||||
$DB->set_field('forum_discussions', 'forum', $forumto->id, ['id' => $discussionid]);
|
||||
$DB->set_field('forum_read', 'forumid', $forumto->id, ['discussionid' => $discussionid]);
|
||||
|
||||
// Delete the existing per-discussion subscriptions and replace them with the newly calculated ones.
|
||||
$DB->delete_records('forum_discussion_subs', array('discussion' => $discussion->id));
|
||||
$newdiscussion = clone $discussion;
|
||||
$DB->delete_records('forum_discussion_subs', ['discussion' => $discussionid]);
|
||||
$newdiscussion = clone $discussionrecord;
|
||||
$newdiscussion->forum = $forumto->id;
|
||||
foreach ($subscriptionchanges as $userid => $preference) {
|
||||
if ($preference != \mod_forum\subscriptions::FORUM_DISCUSSION_UNSUBSCRIBED) {
|
||||
@@ -151,52 +190,54 @@ if ($move > 0 and confirm_sesskey()) {
|
||||
}
|
||||
}
|
||||
|
||||
$params = array(
|
||||
$params = [
|
||||
'context' => $destinationctx,
|
||||
'objectid' => $discussion->id,
|
||||
'other' => array(
|
||||
'fromforumid' => $forum->id,
|
||||
'objectid' => $discussionid,
|
||||
'other' => [
|
||||
'fromforumid' => $forumid,
|
||||
'toforumid' => $forumto->id,
|
||||
)
|
||||
);
|
||||
]
|
||||
];
|
||||
$event = \mod_forum\event\discussion_moved::create($params);
|
||||
$event->add_record_snapshot('forum_discussions', $discussion);
|
||||
$event->add_record_snapshot('forum', $forum);
|
||||
$event->add_record_snapshot('forum_discussions', $discussionrecord);
|
||||
$event->add_record_snapshot('forum', $forumrecord);
|
||||
$event->add_record_snapshot('forum', $forumto);
|
||||
$event->trigger();
|
||||
|
||||
// Delete the RSS files for the 2 forums to force regeneration of the feeds
|
||||
require_once($CFG->dirroot.'/mod/forum/rsslib.php');
|
||||
forum_rss_delete_file($forum);
|
||||
require_once($CFG->dirroot . '/mod/forum/rsslib.php');
|
||||
forum_rss_delete_file($forumrecord);
|
||||
forum_rss_delete_file($forumto);
|
||||
|
||||
redirect($return.'&move=-1&sesskey='.sesskey());
|
||||
redirect($return . '&move=-1&sesskey=' . sesskey());
|
||||
}
|
||||
// Pin or unpin discussion if requested.
|
||||
if ($pin !== -1 && confirm_sesskey()) {
|
||||
require_capability('mod/forum:pindiscussions', $modcontext);
|
||||
if (!$capabilitymanager->can_pin_discussions($USER)) {
|
||||
print_error('nopermissions', 'error', $return, get_capability_string('mod/forum:pindiscussions'));
|
||||
}
|
||||
|
||||
$params = array('context' => $modcontext, 'objectid' => $discussion->id, 'other' => array('forumid' => $forum->id));
|
||||
$params = ['context' => $modcontext, 'objectid' => $discussion->get_id(), 'other' => ['forumid' => $forum->get_id()]];
|
||||
|
||||
switch ($pin) {
|
||||
case FORUM_DISCUSSION_PINNED:
|
||||
// Pin the discussion and trigger discussion pinned event.
|
||||
forum_discussion_pin($modcontext, $forum, $discussion);
|
||||
forum_discussion_pin($modcontext, $forumrecord, $discussionrecord);
|
||||
break;
|
||||
case FORUM_DISCUSSION_UNPINNED:
|
||||
// Unpin the discussion and trigger discussion unpinned event.
|
||||
forum_discussion_unpin($modcontext, $forum, $discussion);
|
||||
forum_discussion_unpin($modcontext, $forumrecord, $discussionrecord);
|
||||
break;
|
||||
default:
|
||||
echo $OUTPUT->notification("Invalid value when attempting to pin/unpin discussion");
|
||||
break;
|
||||
}
|
||||
|
||||
redirect(new moodle_url('/mod/forum/discuss.php', array('d' => $discussion->id)));
|
||||
redirect($discussionviewurl->out(false));
|
||||
}
|
||||
|
||||
// Trigger discussion viewed event.
|
||||
forum_discussion_view($modcontext, $forum, $discussion);
|
||||
forum_discussion_view($modcontext, $forumrecord, $discussionrecord);
|
||||
|
||||
unset($SESSION->fromdiscussion);
|
||||
|
||||
@@ -212,19 +253,21 @@ if ($parent) {
|
||||
$displaymode = FORUM_MODE_NESTED;
|
||||
}
|
||||
} else {
|
||||
$parent = $discussion->firstpost;
|
||||
$parent = $discussion->get_first_post_id();
|
||||
}
|
||||
|
||||
if (! $post = forum_get_post_full($parent)) {
|
||||
print_error("notexists", 'forum', "$CFG->wwwroot/mod/forum/view.php?f=$forum->id");
|
||||
$postvault = $vaultfactory->get_post_vault();
|
||||
if (!$post = $postvault->get_from_id($parent)) {
|
||||
print_error("notexists", 'forum', "$CFG->wwwroot/mod/forum/view.php?f={$forum->get_id()}");
|
||||
}
|
||||
|
||||
if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm, false)) {
|
||||
print_error('noviewdiscussionspermission', 'forum', "$CFG->wwwroot/mod/forum/view.php?id=$forum->id");
|
||||
if (!$capabilitymanager->can_view_post($USER, $discussion, $post)) {
|
||||
print_error('noviewdiscussionspermission', 'forum', "$CFG->wwwroot/mod/forum/view.php?id={$forum->get_id()}");
|
||||
}
|
||||
|
||||
if ($mark == 'read' or $mark == 'unread') {
|
||||
if ($CFG->forum_usermarksread && forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
|
||||
$istracked = forum_tp_is_tracked($forumrecord, $USER);
|
||||
if ($mark == 'read'|| $mark == 'unread') {
|
||||
if ($CFG->forum_usermarksread && forum_tp_can_track_forums($forumrecord) && $istracked) {
|
||||
if ($mark == 'read') {
|
||||
forum_tp_add_read_record($USER->id, $postid);
|
||||
} else {
|
||||
@@ -242,163 +285,36 @@ if (empty($forumnode)) {
|
||||
} else {
|
||||
$forumnode->make_active();
|
||||
}
|
||||
$node = $forumnode->add(format_string($discussion->name), new moodle_url('/mod/forum/discuss.php', array('d'=>$discussion->id)));
|
||||
$node = $forumnode->add(format_string($discussion->get_name()), $discussionviewurl);
|
||||
$node->display = false;
|
||||
if ($node && $post->id != $discussion->firstpost) {
|
||||
$node->add(format_string($post->subject), $PAGE->url);
|
||||
if ($node && $post->get_id() != $discussion->get_first_post_id()) {
|
||||
$node->add(format_string($post->get_subject()), $PAGE->url);
|
||||
}
|
||||
|
||||
$PAGE->set_title("$course->shortname: ".format_string($discussion->name));
|
||||
$PAGE->set_title("$course->shortname: " . format_string($discussion->get_name()));
|
||||
$PAGE->set_heading($course->fullname);
|
||||
$PAGE->set_button($searchform);
|
||||
$renderer = $PAGE->get_renderer('mod_forum');
|
||||
$PAGE->set_button(forum_search_form($course));
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(format_string($forum->get_name()), 2);
|
||||
echo $OUTPUT->heading(format_string($discussion->get_name()), 3, 'discussionname');
|
||||
|
||||
echo $OUTPUT->heading(format_string($forum->name), 2);
|
||||
echo $OUTPUT->heading(format_string($discussion->name), 3, 'discussionname');
|
||||
|
||||
// is_guest should be used here as this also checks whether the user is a guest in the current course.
|
||||
// Guests and visitors cannot subscribe - only enrolled users.
|
||||
if ((!is_guest($modcontext, $USER) && isloggedin()) && has_capability('mod/forum:viewdiscussion', $modcontext)) {
|
||||
// Discussion subscription.
|
||||
if (\mod_forum\subscriptions::is_subscribable($forum)) {
|
||||
echo html_writer::div(
|
||||
forum_get_discussion_subscription_icon($forum, $post->discussion, null, true),
|
||||
'discussionsubscription'
|
||||
);
|
||||
echo forum_get_discussion_subscription_icon_preloaders();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Check to see if groups are being used in this forum
|
||||
/// If so, make sure the current person is allowed to see this discussion
|
||||
/// Also, if we know they should be able to reply, then explicitly set $canreply for performance reasons
|
||||
|
||||
$canreply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
|
||||
if (!$canreply and $forum->type !== 'news') {
|
||||
if (isguestuser() or !isloggedin()) {
|
||||
$canreply = true;
|
||||
}
|
||||
if (!is_enrolled($modcontext) and !is_viewing($modcontext)) {
|
||||
// allow guests and not-logged-in to see the link - they are prompted to log in after clicking the link
|
||||
// normal users with temporary guest access see this link too, they are asked to enrol instead
|
||||
$canreply = enrol_selfenrol_available($course->id);
|
||||
}
|
||||
}
|
||||
|
||||
// Output the links to neighbour discussions.
|
||||
$neighbours = forum_get_discussion_neighbours($cm, $discussion, $forum);
|
||||
$neighbourlinks = $renderer->neighbouring_discussion_navigation($neighbours['prev'], $neighbours['next']);
|
||||
echo $neighbourlinks;
|
||||
|
||||
/// Print the controls across the top
|
||||
echo '<div class="discussioncontrols clearfix"><div class="controlscontainer m-b-1">';
|
||||
|
||||
if (!empty($CFG->enableportfolios) && has_capability('mod/forum:exportdiscussion', $modcontext)) {
|
||||
require_once($CFG->libdir.'/portfoliolib.php');
|
||||
$button = new portfolio_add_button();
|
||||
$button->set_callback_options('forum_portfolio_caller', array('discussionid' => $discussion->id), 'mod_forum');
|
||||
$button = $button->to_html(PORTFOLIO_ADD_FULL_FORM, get_string('exportdiscussion', 'mod_forum'));
|
||||
$buttonextraclass = '';
|
||||
if (empty($button)) {
|
||||
// no portfolio plugin available.
|
||||
$button = ' ';
|
||||
$buttonextraclass = ' noavailable';
|
||||
}
|
||||
echo html_writer::tag('div', $button, array('class' => 'discussioncontrol exporttoportfolio'.$buttonextraclass));
|
||||
} else {
|
||||
echo html_writer::tag('div', ' ', array('class'=>'discussioncontrol nullcontrol'));
|
||||
}
|
||||
|
||||
// groups selector not needed here
|
||||
echo '<div class="discussioncontrol displaymode">';
|
||||
forum_print_mode_form($discussion->id, $displaymode);
|
||||
echo "</div>";
|
||||
|
||||
if ($forum->type != 'single'
|
||||
&& has_capability('mod/forum:movediscussions', $modcontext)) {
|
||||
|
||||
echo '<div class="discussioncontrol movediscussion">';
|
||||
// Popup menu to move discussions to other forums. The discussion in a
|
||||
// single discussion forum can't be moved.
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
if (isset($modinfo->instances['forum'])) {
|
||||
$forummenu = array();
|
||||
// Check forum types and eliminate simple discussions.
|
||||
$forumcheck = $DB->get_records('forum', array('course' => $course->id),'', 'id, type');
|
||||
foreach ($modinfo->instances['forum'] as $forumcm) {
|
||||
if (!$forumcm->uservisible || !has_capability('mod/forum:startdiscussion',
|
||||
context_module::instance($forumcm->id))) {
|
||||
continue;
|
||||
}
|
||||
$section = $forumcm->sectionnum;
|
||||
$sectionname = get_section_name($course, $section);
|
||||
if (empty($forummenu[$section])) {
|
||||
$forummenu[$section] = array($sectionname => array());
|
||||
}
|
||||
$forumidcompare = $forumcm->instance != $forum->id;
|
||||
$forumtypecheck = $forumcheck[$forumcm->instance]->type !== 'single';
|
||||
if ($forumidcompare and $forumtypecheck) {
|
||||
$url = "/mod/forum/discuss.php?d=$discussion->id&move=$forumcm->instance&sesskey=".sesskey();
|
||||
$forummenu[$section][$sectionname][$url] = format_string($forumcm->name);
|
||||
}
|
||||
}
|
||||
if (!empty($forummenu)) {
|
||||
echo '<div class="movediscussionoption">';
|
||||
$select = new url_select($forummenu, '',
|
||||
array('/mod/forum/discuss.php?d=' . $discussion->id => get_string("movethisdiscussionto", "forum")),
|
||||
'forummenu', get_string('move'));
|
||||
echo $OUTPUT->render($select);
|
||||
echo "</div>";
|
||||
}
|
||||
}
|
||||
echo "</div>";
|
||||
}
|
||||
|
||||
if (has_capability('mod/forum:pindiscussions', $modcontext)) {
|
||||
if ($discussion->pinned == FORUM_DISCUSSION_PINNED) {
|
||||
$pinlink = FORUM_DISCUSSION_UNPINNED;
|
||||
$pintext = get_string('discussionunpin', 'forum');
|
||||
} else {
|
||||
$pinlink = FORUM_DISCUSSION_PINNED;
|
||||
$pintext = get_string('discussionpin', 'forum');
|
||||
}
|
||||
$button = new single_button(new moodle_url('discuss.php', array('pin' => $pinlink, 'd' => $discussion->id)), $pintext, 'post');
|
||||
echo html_writer::tag('div', $OUTPUT->render($button), array('class' => 'discussioncontrol pindiscussion'));
|
||||
}
|
||||
|
||||
|
||||
echo "</div></div>";
|
||||
|
||||
if (forum_discussion_is_locked($forum, $discussion)) {
|
||||
echo $OUTPUT->notification(get_string('discussionlocked', 'forum'),
|
||||
\core\output\notification::NOTIFY_INFO . ' discussionlocked');
|
||||
}
|
||||
|
||||
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('mod/forum:viewqandawithoutposting', $modcontext) &&
|
||||
!forum_user_has_posted($forum->id,$discussion->id,$USER->id)) {
|
||||
echo $OUTPUT->notification(get_string('qandanotify', 'forum'));
|
||||
}
|
||||
$rendererfactory = mod_forum\local\container::get_renderer_factory();
|
||||
$discussionrenderer = $rendererfactory->get_discussion_renderer($forum, $discussion, $displaymode);
|
||||
$orderpostsby = $displaymode == FORUM_MODE_FLATNEWEST ? 'created DESC' : 'created ASC';
|
||||
$replies = $postvault->get_replies_to_post($post, $orderpostsby);
|
||||
$postids = array_map(function($post) {
|
||||
return $post->get_id();
|
||||
}, array_merge([$post], array_values($replies)));
|
||||
|
||||
if ($move == -1 and confirm_sesskey()) {
|
||||
echo $OUTPUT->notification(get_string('discussionmoved', 'forum', format_string($forum->name,true)), 'notifysuccess');
|
||||
$forumname = format_string($forum->get_name(), true);
|
||||
echo $OUTPUT->notification(get_string('discussionmoved', 'forum', $forumname), 'notifysuccess');
|
||||
}
|
||||
|
||||
$canrate = has_capability('mod/forum:rate', $modcontext);
|
||||
forum_print_discussion($course, $cm, $forum, $discussion, $post, $displaymode, $canreply, $canrate);
|
||||
|
||||
echo $neighbourlinks;
|
||||
|
||||
// Add the subscription toggle JS.
|
||||
$PAGE->requires->yui_module('moodle-mod_forum-subscriptiontoggle', 'Y.M.mod_forum.subscriptiontoggle.init');
|
||||
|
||||
echo $discussionrenderer->render($USER, $post, $replies);
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
if ($istracked && !$CFG->forum_usermarksread) {
|
||||
forum_tp_mark_posts_read($USER, $postids);
|
||||
}
|
||||
|
||||
@@ -1258,4 +1258,87 @@ class mod_forum_external extends external_api {
|
||||
|
||||
return new external_single_structure($structure);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the subscription state.
|
||||
*
|
||||
* @param int $forumid
|
||||
* @param int $discussionid
|
||||
* @param bool $targetstate
|
||||
* @return \stdClass
|
||||
*/
|
||||
public static function set_subscription_state($forumid, $discussionid, $targetstate) {
|
||||
global $PAGE, $USER;
|
||||
|
||||
$params = self::validate_parameters(self::set_subscription_state_parameters(), [
|
||||
'forumid' => $forumid,
|
||||
'discussionid' => $discussionid,
|
||||
'targetstate' => $targetstate
|
||||
]);
|
||||
|
||||
$vaultfactory = mod_forum\local\container::get_vault_factory();
|
||||
$forumvault = $vaultfactory->get_forum_vault();
|
||||
$forum = $forumvault->get_from_id($params['forumid']);
|
||||
$coursemodule = $forum->get_course_module_record();
|
||||
$context = $forum->get_context();
|
||||
|
||||
self::validate_context($context);
|
||||
|
||||
$discussionvault = $vaultfactory->get_discussion_vault();
|
||||
$discussion = $discussionvault->get_from_id($params['discussionid']);
|
||||
$legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
|
||||
|
||||
$forumrecord = $legacydatamapperfactory->get_forum_data_mapper()->to_legacy_object($forum);
|
||||
$discussionrecord = $legacydatamapperfactory->get_discussion_data_mapper()->to_legacy_object($discussion);
|
||||
|
||||
if (!\mod_forum\subscriptions::is_subscribable($forumrecord)) {
|
||||
// Nothing to do. We won't actually output any content here though.
|
||||
throw new \moodle_exception('cannotsubscribe', 'mod_forum');
|
||||
}
|
||||
|
||||
$issubscribed = \mod_forum\subscriptions::is_subscribed(
|
||||
$USER->id,
|
||||
$forumrecord,
|
||||
$discussion->get_id(),
|
||||
$coursemodule
|
||||
);
|
||||
|
||||
// If the current state doesn't equal the desired state then update the current
|
||||
// state to the desired state.
|
||||
if ($issubscribed != (bool) $params['targetstate']) {
|
||||
if ($params['targetstate']) {
|
||||
\mod_forum\subscriptions::subscribe_user_to_discussion($USER->id, $discussionrecord, $context);
|
||||
} else {
|
||||
\mod_forum\subscriptions::unsubscribe_user_from_discussion($USER->id, $discussionrecord, $context);
|
||||
}
|
||||
}
|
||||
|
||||
$exporterfactory = mod_forum\local\container::get_exporter_factory();
|
||||
$exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion);
|
||||
return $exporter->export($PAGE->get_renderer('mod_forum'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of method parameters.
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function set_subscription_state_parameters() {
|
||||
return new external_function_parameters(
|
||||
[
|
||||
'forumid' => new external_value(PARAM_INT, 'Forum that the discussion is in'),
|
||||
'discussionid' => new external_value(PARAM_INT, 'The discussion to subscribe or unsubscribe'),
|
||||
'targetstate' => new external_value(PARAM_BOOL, 'The target state')
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of method result value.
|
||||
*
|
||||
* @return external_description
|
||||
*/
|
||||
public static function set_subscription_state_returns() {
|
||||
return \mod_forum\local\exporters\discussion::get_read_structure();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ $string['blockperiod_help'] = 'Students can be blocked from posting more than a
|
||||
$string['blockperioddisabled'] = 'Don\'t block';
|
||||
$string['blogforum'] = 'Standard forum displayed in a blog-like format';
|
||||
$string['bynameondate'] = 'by {$a->name} - {$a->date}';
|
||||
$string['cachedef_forum_is_tracked'] = 'Forum tracking status for user';
|
||||
$string['cannotadd'] = 'Could not add the discussion for this forum';
|
||||
$string['cannotadddiscussion'] = 'Adding discussions to this forum requires group membership.';
|
||||
$string['cannotadddiscussionall'] = 'You do not have permission to add a new discussion topic for all participants.';
|
||||
|
||||
+50
-901
File diff suppressed because it is too large
Load Diff
+6
-14
@@ -29,14 +29,14 @@ require_once("lib.php");
|
||||
$f = required_param('f',PARAM_INT); // The forum to mark
|
||||
$mark = required_param('mark',PARAM_ALPHA); // Read or unread?
|
||||
$d = optional_param('d',0,PARAM_INT); // Discussion to mark.
|
||||
$returnpage = optional_param('returnpage', 'index.php', PARAM_FILE); // Page to return to.
|
||||
$return = optional_param('return', null, PARAM_LOCALURL); // Page to return to.
|
||||
|
||||
$url = new moodle_url('/mod/forum/markposts.php', array('f'=>$f, 'mark'=>$mark));
|
||||
if ($d !== 0) {
|
||||
$url->param('d', $d);
|
||||
}
|
||||
if ($returnpage !== 'index.php') {
|
||||
$url->param('returnpage', $returnpage);
|
||||
if (null !== $return) {
|
||||
$url->param('return', $return);
|
||||
}
|
||||
$PAGE->set_url($url);
|
||||
|
||||
@@ -57,10 +57,10 @@ $user = $USER;
|
||||
require_login($course, false, $cm);
|
||||
require_sesskey();
|
||||
|
||||
if ($returnpage == 'index.php') {
|
||||
$returnto = new moodle_url("/mod/forum/$returnpage", array('id' => $course->id));
|
||||
if (null === $return) {
|
||||
$returnto = new moodle_url("/mod/forum/index.php", ['id' => $course->id]);
|
||||
} else {
|
||||
$returnto = new moodle_url("/mod/forum/$returnpage", array('f' => $forum->id));
|
||||
$returnto = new moodle_url($return);
|
||||
}
|
||||
|
||||
if (isguestuser()) { // Guests can't change forum
|
||||
@@ -93,14 +93,6 @@ if ($mark == 'read') {
|
||||
}
|
||||
forum_tp_mark_forum_read($user, $forum->id, $currentgroup);
|
||||
}
|
||||
|
||||
/// FUTURE - Add ability to mark them as unread.
|
||||
// } else { // subscribe
|
||||
// if (forum_tp_start_tracking($forum->id, $user->id)) {
|
||||
// redirect($returnto, get_string("nowtracking", "forum", $info), 1);
|
||||
// } else {
|
||||
// print_error("Could not start tracking that forum", get_local_referer());
|
||||
// }
|
||||
}
|
||||
|
||||
redirect($returnto);
|
||||
|
||||
+37
-11
@@ -96,6 +96,9 @@ if (!isloggedin() or isguestuser()) {
|
||||
|
||||
require_login(0, false); // Script is useless unless they're logged in.
|
||||
|
||||
$entityfactory = mod_forum\local\container::get_entity_factory();
|
||||
$vaultfactory = mod_forum\local\container::get_vault_factory();
|
||||
|
||||
if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
if (! $forum = $DB->get_record("forum", array("id" => $forum))) {
|
||||
print_error('invalidforumid', 'forum');
|
||||
@@ -305,6 +308,10 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
require_login($course, false, $cm);
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
|
||||
$postentity = $entityfactory->get_post_from_stdclass($post);
|
||||
$discussionentity = $entityfactory->get_discussion_from_stdclass($discussion);
|
||||
$forumentity = $entityfactory->get_forum_from_stdclass($forum, $modcontext, $cm, $course);
|
||||
|
||||
if ( !(($post->userid == $USER->id && has_capability('mod/forum:deleteownpost', $modcontext))
|
||||
|| has_capability('mod/forum:deleteanypost', $modcontext)) ) {
|
||||
print_error('cannotdeletepost', 'forum');
|
||||
@@ -400,20 +407,27 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
"post.php?delete=$delete&confirm=$delete",
|
||||
$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id);
|
||||
|
||||
forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
|
||||
$postentities = [$postentity];
|
||||
|
||||
if (empty($post->edit)) {
|
||||
$forumtracked = forum_tp_is_tracked($forum);
|
||||
$posts = forum_get_all_discussion_posts($discussion->id, "created ASC", $forumtracked);
|
||||
forum_print_posts_nested($course, $cm, $forum, $discussion, $post, false, false, $forumtracked, $posts);
|
||||
$postvault = $vaultfactory->get_post_vault();
|
||||
$replies = $postvault->get_replies_to_post($postentity, 'created ASC');
|
||||
$postentities = array_merge($postentities, $replies);
|
||||
}
|
||||
|
||||
$rendererfactory = mod_forum\local\container::get_renderer_factory();
|
||||
$postsrenderer = $rendererfactory->get_single_discussion_posts_renderer(FORUM_MODE_NESTED, true);
|
||||
echo $postsrenderer->render($USER, [$forumentity], [$discussionentity], [$postentity]);
|
||||
} else {
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(format_string($forum->name), 2);
|
||||
echo $OUTPUT->confirm(get_string("deletesure", "forum", $replycount),
|
||||
"post.php?delete=$delete&confirm=$delete",
|
||||
$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id);
|
||||
forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
|
||||
|
||||
$rendererfactory = mod_forum\local\container::get_renderer_factory();
|
||||
$postsrenderer = $rendererfactory->get_single_discussion_posts_renderer(null, true);
|
||||
echo $postsrenderer->render($USER, [$forumentity], [$discussionentity], [$postentity]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -452,7 +466,6 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
|
||||
$prunemform = new mod_forum_prune_form(null, array('prune' => $prune, 'confirm' => $prune));
|
||||
|
||||
|
||||
if ($prunemform->is_cancelled()) {
|
||||
redirect(forum_go_back_to(new moodle_url("/mod/forum/discuss.php", array('d' => $post->discussion))));
|
||||
} else if ($fromform = $prunemform->get_data()) {
|
||||
@@ -539,7 +552,12 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
|
||||
$prunemform->display();
|
||||
|
||||
forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
|
||||
$postentity = $entityfactory->get_post_from_stdclass($post);
|
||||
$discussionentity = $entityfactory->get_discussion_from_stdclass($discussion);
|
||||
$forumentity = $entityfactory->get_forum_from_stdclass($forum, $modcontext, $cm, $course);
|
||||
$rendererfactory = mod_forum\local\container::get_renderer_factory();
|
||||
$postsrenderer = $rendererfactory->get_single_discussion_posts_renderer(null, true);
|
||||
echo $postsrenderer->render($USER, [$forumentity], [$discussionentity], [$postentity]);
|
||||
}
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
@@ -1048,14 +1066,22 @@ if (!empty($parent)) {
|
||||
print_error('notpartofdiscussion', 'forum');
|
||||
}
|
||||
|
||||
forum_print_post($parent, $discussion, $forum, $cm, $course, false, false, false);
|
||||
$postentity = $entityfactory->get_post_from_stdclass($parent);
|
||||
$discussionentity = $entityfactory->get_discussion_from_stdclass($discussion);
|
||||
$forumentity = $entityfactory->get_forum_from_stdclass($forum, $modcontext, $cm, $course);
|
||||
$postentities = [$postentity];
|
||||
|
||||
if (empty($post->edit)) {
|
||||
if ($forum->type != 'qanda' || forum_user_can_see_discussion($forum, $discussion, $modcontext)) {
|
||||
$forumtracked = forum_tp_is_tracked($forum);
|
||||
$posts = forum_get_all_discussion_posts($discussion->id, "created ASC", $forumtracked);
|
||||
forum_print_posts_threaded($course, $cm, $forum, $discussion, $parent, 0, false, $forumtracked, $posts);
|
||||
$postvault = $vaultfactory->get_post_vault();
|
||||
$replies = $postvault->get_replies_to_post($postentity, 'created ASC');
|
||||
$postentities = array_merge($postentities, $replies);
|
||||
}
|
||||
}
|
||||
|
||||
$rendererfactory = mod_forum\local\container::get_renderer_factory();
|
||||
$postsrenderer = $rendererfactory->get_single_discussion_posts_renderer(FORUM_MODE_THREADED, true);
|
||||
echo $postsrenderer->render($USER, [$forumentity], [$discussionentity], [$postentity]);
|
||||
} else {
|
||||
if (!empty($forum->intro)) {
|
||||
echo $OUTPUT->box(format_module_intro('forum', $forum, $cm->id), 'generalbox', 'intro');
|
||||
|
||||
+57
-69
@@ -233,91 +233,79 @@ foreach ($searchterms as $key => $searchterm) {
|
||||
}
|
||||
}
|
||||
$strippedsearch = implode(' ', $searchterms); // Rebuild the string
|
||||
$entityfactory = mod_forum\local\container::get_entity_factory();
|
||||
$vaultfactory = mod_forum\local\container::get_vault_factory();
|
||||
$rendererfactory = mod_forum\local\container::get_renderer_factory();
|
||||
$managerfactory = mod_forum\local\container::get_manager_factory();
|
||||
$legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
|
||||
$forumdatamapper = $legacydatamapperfactory->get_forum_data_mapper();
|
||||
|
||||
$discussionvault = $vaultfactory->get_discussion_vault();
|
||||
$discussionids = array_keys(array_reduce($posts, function($carry, $post) {
|
||||
$carry[$post->discussion] = true;
|
||||
return $carry;
|
||||
}, []));
|
||||
$discussions = $discussionvault->get_from_ids($discussionids);
|
||||
$discussionsbyid = array_reduce($discussions, function($carry, $discussion) {
|
||||
$carry[$discussion->get_id()] = $discussion;
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
$forumvault = $vaultfactory->get_forum_vault();
|
||||
$forumids = array_keys(array_reduce($discussions, function($carry, $discussion) {
|
||||
$carry[$discussion->get_forum_id()] = true;
|
||||
return $carry;
|
||||
}, []));
|
||||
$forums = $forumvault->get_from_ids($forumids);
|
||||
$forumsbyid = array_reduce($forums, function($carry, $forum) {
|
||||
$carry[$forum->get_id()] = $forum;
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
$postids = array_map(function($post) {
|
||||
return $post->id;
|
||||
}, $posts);
|
||||
|
||||
$poststorender = [];
|
||||
|
||||
foreach ($posts as $post) {
|
||||
|
||||
// Replace the simple subject with the three items forum name -> thread name -> subject
|
||||
// (if all three are appropriate) each as a link.
|
||||
if (! $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion))) {
|
||||
if (!isset($discussionsbyid[$post->discussion])) {
|
||||
print_error('invaliddiscussionid', 'forum');
|
||||
}
|
||||
if (! $forum = $DB->get_record('forum', array('id' => "$discussion->forum"))) {
|
||||
|
||||
$discussion = $discussionsbyid[$post->discussion];
|
||||
if (!isset($forumsbyid[$discussion->get_forum_id()])) {
|
||||
print_error('invalidforumid', 'forum');
|
||||
}
|
||||
|
||||
if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
|
||||
print_error('invalidcoursemodule');
|
||||
$forum = $forumsbyid[$discussion->get_forum_id()];
|
||||
$capabilitymanager = $managerfactory->get_capability_manager($forum);
|
||||
$postentity = $entityfactory->get_post_from_stdclass($post);
|
||||
|
||||
if (!$capabilitymanager->can_view_post($USER, $discussion, $postentity)) {
|
||||
// Don't render posts that the user can't view.
|
||||
continue;
|
||||
}
|
||||
|
||||
$post->subject = highlight($strippedsearch, $post->subject);
|
||||
$discussion->name = highlight($strippedsearch, $discussion->name);
|
||||
|
||||
$fullsubject = "<a href=\"view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
|
||||
if ($forum->type != 'single') {
|
||||
$fullsubject .= " -> <a href=\"discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a>";
|
||||
if ($post->parent != 0) {
|
||||
$fullsubject .= " -> <a href=\"discuss.php?d=$post->discussion&parent=$post->id\">".format_string($post->subject,true)."</a>";
|
||||
}
|
||||
if ($postentity->is_deleted()) {
|
||||
// Don't render deleted posts.
|
||||
continue;
|
||||
}
|
||||
|
||||
$post->subject = $fullsubject;
|
||||
$post->subjectnoformat = true;
|
||||
|
||||
//add the ratings information to the post
|
||||
//Unfortunately seem to have do this individually as posts may be from different forums
|
||||
if ($forum->assessed != RATING_AGGREGATE_NONE) {
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
$ratingoptions->context = $modcontext;
|
||||
$ratingoptions->items = array($post);
|
||||
$ratingoptions->aggregate = $forum->assessed;//the aggregation method
|
||||
$ratingoptions->scaleid = $forum->scale;
|
||||
$ratingoptions->assesstimestart = $forum->assesstimestart;
|
||||
$ratingoptions->assesstimefinish = $forum->assesstimefinish;
|
||||
$postswithratings = $rm->get_ratings($ratingoptions);
|
||||
|
||||
if ($postswithratings && count($postswithratings)==1) {
|
||||
$post = $postswithratings[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Identify search terms only found in HTML markup, and add a warning about them to
|
||||
// the start of the message text. However, do not do the highlighting here. forum_print_post
|
||||
// will do it for us later.
|
||||
$missing_terms = "";
|
||||
|
||||
$options = new stdClass();
|
||||
$options->trusted = $post->messagetrust;
|
||||
$post->message = highlight($strippedsearch,
|
||||
format_text($post->message, $post->messageformat, $options, $course->id),
|
||||
0, '<fgw9sdpq4>', '</fgw9sdpq4>');
|
||||
|
||||
foreach ($searchterms as $searchterm) {
|
||||
if (preg_match("/$searchterm/i",$post->message) && !preg_match('/<fgw9sdpq4>'.$searchterm.'<\/fgw9sdpq4>/i',$post->message)) {
|
||||
$missing_terms .= " $searchterm";
|
||||
}
|
||||
}
|
||||
|
||||
$post->message = str_replace('<fgw9sdpq4>', '<span class="highlight">', $post->message);
|
||||
$post->message = str_replace('</fgw9sdpq4>', '</span>', $post->message);
|
||||
|
||||
if ($missing_terms) {
|
||||
$strmissingsearchterms = get_string('missingsearchterms','forum');
|
||||
$post->message = '<p class="highlight2">'.$strmissingsearchterms.' '.$missing_terms.'</p>'.$post->message;
|
||||
}
|
||||
|
||||
// Prepare a link to the post in context, to be displayed after the forum post.
|
||||
$fulllink = "<a href=\"discuss.php?d=$post->discussion#p$post->id\">".get_string("postincontext", "forum")."</a>";
|
||||
|
||||
// Message is now html format.
|
||||
if ($post->messageformat != FORMAT_HTML) {
|
||||
$post->messageformat = FORMAT_HTML;
|
||||
}
|
||||
|
||||
// Now pring the post.
|
||||
forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false,
|
||||
$fulllink, '', -99, false);
|
||||
$poststorender[] = $postentity;
|
||||
}
|
||||
|
||||
$renderer = $rendererfactory->get_posts_search_results_renderer($searchterms);
|
||||
echo $renderer->render(
|
||||
$USER,
|
||||
$forumsbyid,
|
||||
$discussionsbyid,
|
||||
$poststorender
|
||||
);
|
||||
|
||||
echo $OUTPUT->paging_bar($totalcount, $page, $perpage, $url);
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
{{!
|
||||
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/blog_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 }}
|
||||
{{$footer}}
|
||||
<div class="link text-right">
|
||||
<a href="{{urls.discuss}}">{{#str}}discussthistopic, forum{{/str}}</a> ({{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 }}
|
||||
@@ -0,0 +1,243 @@
|
||||
{{!
|
||||
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/discussion_list
|
||||
|
||||
Template to display the list of forum discussions.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Context variables required for this template:
|
||||
* TODO
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
<div id="discussion-list-{{uniqid}}">
|
||||
{{{groupchangemenu}}}
|
||||
|
||||
{{#notifications}}
|
||||
{{> core/notification}}
|
||||
{{/notifications}}
|
||||
|
||||
{{#forum.capabilities.create}}
|
||||
<div class="p-t-1 p-b-1">
|
||||
<a href="{{forum.urls.create}}" class="btn btn-primary">
|
||||
{{$discussion_create_text}}
|
||||
{{#str}}addanewdiscussion, forum{{/str}}
|
||||
{{/discussion_create_text}}
|
||||
</a>
|
||||
</div>
|
||||
{{/forum.capabilities.create}}
|
||||
|
||||
{{#state.hasdiscussions}}
|
||||
{{{ pagination }}}
|
||||
{{$discussion_list_output}}
|
||||
<table class="table table-hover table-striped">
|
||||
{{$discussion_list_header}}
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"> </th>
|
||||
<th scope="col"> </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}}
|
||||
<!-- The discussion class is only required for behat tests to identify the row -->
|
||||
<tr class="discussion {{#discussion.timed}}{{#istimed}}{{^visible}}dimmed_text{{/visible}}{{/istimed}}{{/discussion.timed}}">
|
||||
<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="timed p-0 text-center align-middle">
|
||||
{{#discussion.timed.istimed}}
|
||||
<div class="timedpost">
|
||||
{{#pix}}
|
||||
i/calendar, moodle,
|
||||
{{#discussion.times.start}}
|
||||
{{! }}{{#str}} displaystart, mod_forum {{/str}}: {{#userdate}}{{.}}, {{#str}}strftimerecentfull {{/str}}{{/userdate}}
|
||||
{{/discussion.times.start}}
|
||||
{{#discussion.times.end}}
|
||||
{{! }}{{#str}} displayend, mod_forum {{/str}}: {{#userdate}}{{.}}, {{#str}} strftimerecentfull {{/str}}{{/userdate}}
|
||||
{{/discussion.times.end}}
|
||||
{{#discussion.timed.visible}}
|
||||
{{! }}{{#str}} timedvisible, mod_forum {{/str}}
|
||||
{{/discussion.timed.visible}}
|
||||
{{^discussion.timed.visible}}
|
||||
{{! }}{{#str}} timedhidden, mod_forum {{/str}}
|
||||
{{/discussion.timed.visible}}
|
||||
{{/pix}}
|
||||
</div>
|
||||
{{/discussion.timed.istimed}}
|
||||
</td>
|
||||
<td scope="col" class="topic p-0 align-middle">
|
||||
<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 align-middle">
|
||||
{{#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 align-middle">
|
||||
<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 align-middle">
|
||||
{{#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="{{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>
|
||||
{{/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>
|
||||
</div>
|
||||
{{/can_create_discussion}}
|
||||
{{/state.hasdiscussions}}
|
||||
{{^state.hasdiscussions}}
|
||||
<div class="forumnodiscuss">
|
||||
{{$no_discussions_text}}
|
||||
({{#str}}nodiscussions, forum{{/str}})
|
||||
{{/no_discussions_text}}
|
||||
</div>
|
||||
{{/state.hasdiscussions}}
|
||||
|
||||
</div>
|
||||
{{#js}}
|
||||
require(['jquery', 'mod_forum/discussion_list'], function($, View) {
|
||||
var root = $('#discussion-list-{{uniqid}}');
|
||||
View.init(root);
|
||||
});
|
||||
{{/js}}
|
||||
@@ -0,0 +1,59 @@
|
||||
{{!
|
||||
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/discussion_subscription_toggle
|
||||
|
||||
Template to display the discussion subscription toggle.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Context variables required for this template:
|
||||
* TODO
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
{{#capabilities.subscribe}}
|
||||
<a
|
||||
href="{{{urls.subscribe}}}"
|
||||
class="p-3 btn btn-link"
|
||||
data-type="subscription-toggle"
|
||||
data-action="toggle"
|
||||
data-discussionid="{{id}}"
|
||||
data-forumid="{{forumid}}"
|
||||
{{#userstate.subscribed}}
|
||||
title="{{#str}}clicktounsubscribe, mod_forum{{/str}}"
|
||||
data-targetstate="0"
|
||||
{{/userstate.subscribed}}
|
||||
{{^userstate.subscribed}}
|
||||
title="{{#str}}clicktosubscribe, mod_forum{{/str}}"
|
||||
data-targetstate="1"
|
||||
{{/userstate.subscribed}}
|
||||
>
|
||||
{{#userstate.subscribed}}
|
||||
{{#pix}}t/subscribed, mod_forum, {{#str}}clicktounsubscribe, mod_forum{{/str}}{{/pix}}
|
||||
{{/userstate.subscribed}}
|
||||
{{^userstate.subscribed}}
|
||||
{{#pix}}t/unsubscribed, mod_forum, {{#str}}clicktosubscribe, mod_forum{{/str}}{{/pix}}
|
||||
{{/userstate.subscribed}}
|
||||
</a>
|
||||
{{/capabilities.subscribe}}
|
||||
@@ -0,0 +1,51 @@
|
||||
{{!
|
||||
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/forum_discussion
|
||||
|
||||
Template for displaying a single forum discussion.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
|
||||
{{#html}}
|
||||
{{{subscribe}}}
|
||||
{{{neighbourlinks}}}
|
||||
|
||||
<div class="d-flex flex-wrap m-b-1">
|
||||
<div>{{{exportdiscussion}}}</div>
|
||||
<div class="{{#exportdiscussion}}ml-2{{/exportdiscussion}}">{{{modeselectorform}}}</div>
|
||||
<div class="ml-2">{{{movediscussion}}}</div>
|
||||
<div class="ml-2">{{{pindiscussion}}}</div>
|
||||
</div>
|
||||
{{/html}}
|
||||
|
||||
{{#notifications}}
|
||||
{{> core/notification}}
|
||||
{{/notifications}}
|
||||
|
||||
{{{html.posts}}}
|
||||
|
||||
{{#html.neighbourlinks}}{{{.}}}{{/html.neighbourlinks}}
|
||||
@@ -0,0 +1,40 @@
|
||||
{{!
|
||||
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/forum_discussion_nested_post
|
||||
|
||||
Template to render a post in a nested format.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
{{< mod_forum/forum_discussion_post }}
|
||||
{{$replies}}
|
||||
<div class="indent">
|
||||
{{#replies}}
|
||||
{{> mod_forum/forum_discussion_nested_post }}
|
||||
{{/replies}}
|
||||
</div>
|
||||
{{/replies}}
|
||||
{{/ mod_forum/forum_discussion_post }}
|
||||
@@ -0,0 +1,34 @@
|
||||
{{!
|
||||
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/forum_discussion_nested_posts
|
||||
|
||||
Template to render a list of posts in a nested format.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
{{#posts}}
|
||||
{{> mod_forum/forum_discussion_nested_post }}
|
||||
{{/posts}}
|
||||
@@ -0,0 +1,181 @@
|
||||
{{!
|
||||
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/forum_discussion_post
|
||||
|
||||
Template to render a single post from a discussion.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
<article id="p{{id}}" class="relativelink mb-2" tabindex="-1" data-post-id="{{id}}">
|
||||
|
||||
<!-- The firstpost and starter classes below aren't used for anything other than to identify the first post in behat -->
|
||||
<div
|
||||
class="d-flex border p-2 mb-2 forumpost {{#unread}}unread{{/unread}} {{#firstpost}}firstpost starter{{/firstpost}}"
|
||||
aria-label='{{#str}} postbyuser, mod_forum, {"post": "{{subject}}", "user": "{{author.fullname}}"} {{/str}}'
|
||||
>
|
||||
{{#isfirstunread}}<a id="unread"></a>{{/isfirstunread}}
|
||||
<div style="width: 45px;">
|
||||
{{^isdeleted}}
|
||||
{{#author}}
|
||||
{{#urls.profileimage}}
|
||||
<img class="rounded-circle w-100" src="{{{.}}}">
|
||||
{{/urls.profileimage}}
|
||||
{{#groups}}
|
||||
<img class="rounded-circle w-100" src="{{{urls.image}}}">
|
||||
{{/groups}}
|
||||
{{/author}}
|
||||
{{/isdeleted}}
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-column ml-2 w-100">
|
||||
<header class="mb-2 header row">
|
||||
<h3 class="h6 font-weight-bold mb-0">{{$subject}}{{{subject}}}{{/subject}}</h3>
|
||||
{{^isdeleted}}
|
||||
<address>
|
||||
{{{html.authorsubheading}}}
|
||||
</address>
|
||||
{{/isdeleted}}
|
||||
</header>
|
||||
<div>
|
||||
{{{message}}}
|
||||
</div>
|
||||
|
||||
{{^isdeleted}}
|
||||
{{#haswordcount}}
|
||||
<p class="font-italic mb-0"><small>{{#str}} numwords, core, {{wordcount}} {{/str}}</small></p>
|
||||
{{/haswordcount}}
|
||||
|
||||
{{#attachments}}
|
||||
{{#isimage}}
|
||||
<div>
|
||||
<img src="{{{url}}}" alt="{{filename}}" style="max-width: 100%">
|
||||
{{#urls.export}}
|
||||
<a href="{{{.}}}" title="{{#str}} addtoportfolio, core_portfolio {{/str}}">
|
||||
{{#pix}} t/portfolioadd, core {{/pix}}
|
||||
</a>
|
||||
{{/urls.export}}
|
||||
{{#html.plagiarism}}
|
||||
<div>{{{.}}}</div>
|
||||
{{/html.plagiarism}}
|
||||
</div>
|
||||
{{/isimage}}
|
||||
{{/attachments}}
|
||||
|
||||
{{{html.taglist}}}
|
||||
|
||||
{{#attachments}}
|
||||
{{^isimage}}
|
||||
<div>
|
||||
<a href="{{{url}}}">{{#pix}} {{icon}}, core {{/pix}} {{filename}}</a>
|
||||
{{#urls.export}}
|
||||
<a href="{{{.}}}" title="{{#str}} addtoportfolio, core_portfolio {{/str}}">
|
||||
{{#pix}} t/portfolioadd, core {{/pix}}
|
||||
</a>
|
||||
{{/urls.export}}
|
||||
{{#html.plagiarism}}
|
||||
<div>{{{.}}}</div>
|
||||
{{/html.plagiarism}}
|
||||
</div>
|
||||
{{/isimage}}
|
||||
{{/attachments}}
|
||||
|
||||
{{#html.rating}}
|
||||
<div class="mt-2">{{{.}}}</div>
|
||||
{{/html.rating}}
|
||||
|
||||
{{$actions}}
|
||||
{{^readonly}}
|
||||
<div class="d-flex justify-content-end">
|
||||
{{#capabilities}}
|
||||
{{#view}}
|
||||
<a href="{{{urls.view}}}" class="btn btn-link">
|
||||
{{#str}} permalink, mod_forum {{/str}}
|
||||
</a>
|
||||
{{/view}}
|
||||
{{#controlreadstatus}}
|
||||
{{#unread}}
|
||||
<a href="{{{urls.markasread}}}" class="btn btn-link">
|
||||
{{#str}} markread, mod_forum {{/str}}
|
||||
</a>
|
||||
{{/unread}}
|
||||
{{^unread}}
|
||||
<a href="{{{urls.markasunread}}}" class="btn btn-link">
|
||||
{{#str}} markunread, mod_forum {{/str}}
|
||||
</a>
|
||||
{{/unread}}
|
||||
{{/controlreadstatus}}
|
||||
{{#urls.viewparent}}
|
||||
<a href="{{{.}}}" class="btn btn-link">
|
||||
{{#str}} parent, mod_forum {{/str}}
|
||||
</a>
|
||||
{{/urls.viewparent}}
|
||||
{{#edit}}
|
||||
<a href="{{{urls.edit}}}" class="btn btn-link">
|
||||
{{#str}} edit, mod_forum {{/str}}
|
||||
</a>
|
||||
{{/edit}}
|
||||
{{#split}}
|
||||
<a href="{{{urls.split}}}" class="btn btn-link">
|
||||
{{#str}} prune, mod_forum {{/str}}
|
||||
</a>
|
||||
{{/split}}
|
||||
{{#delete}}
|
||||
<a href="{{{urls.delete}}}" class="btn btn-link">
|
||||
{{#str}} delete, mod_forum {{/str}}
|
||||
</a>
|
||||
{{/delete}}
|
||||
{{#reply}}
|
||||
{{$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">
|
||||
{{#str}} addtoportfolio, core_portfolio {{/str}}
|
||||
</a>
|
||||
{{/export}}
|
||||
{{/capabilities}}
|
||||
</div>
|
||||
{{/readonly}}
|
||||
{{/actions}}
|
||||
|
||||
{{$footer}}{{/footer}}
|
||||
{{/isdeleted}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{$replies}}
|
||||
{{#hasreplies}}
|
||||
<div>
|
||||
{{#replies}}
|
||||
{{> mod_forum/forum_discussion_post }}
|
||||
{{/replies}}
|
||||
</div>
|
||||
{{/hasreplies}}
|
||||
{{/replies}}
|
||||
</article>
|
||||
@@ -0,0 +1,34 @@
|
||||
{{!
|
||||
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/forum_discussion_posts
|
||||
|
||||
Template to render a list of posts for a discussion.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
{{#posts}}
|
||||
{{> mod_forum/forum_discussion_post }}
|
||||
{{/posts}}
|
||||
@@ -0,0 +1,45 @@
|
||||
{{!
|
||||
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/forum_discussion_threaded_post
|
||||
|
||||
Template to render a single post from a discussion in a threaded format.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
<div class="mb-2" data-post-id="{{id}}">
|
||||
<a href="{{{urls.viewisolated}}}">{{subject}}</a>
|
||||
{{^isdeleted}}
|
||||
<address class="d-inline-block mb-0">
|
||||
{{{html.authorsubheading}}}
|
||||
</address>
|
||||
{{/isdeleted}}
|
||||
|
||||
<div class="indent">
|
||||
{{#replies}}
|
||||
{{> mod_forum/forum_discussion_threaded_post }}
|
||||
{{/replies}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,44 @@
|
||||
{{!
|
||||
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/forum_discussion_threaded_posts
|
||||
|
||||
Template to render a list of posts in a threaded format.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
|
||||
{{#posts}}
|
||||
{{< mod_forum/forum_discussion_post }}
|
||||
{{$replies}}
|
||||
<!-- The forumthread class is only added for behat -->
|
||||
<div class="indent forumthread">
|
||||
{{#replies}}
|
||||
{{> mod_forum/forum_discussion_threaded_post }}
|
||||
{{/replies}}
|
||||
</div>
|
||||
{{/replies}}
|
||||
{{/ mod_forum/forum_discussion_post }}
|
||||
{{/posts}}
|
||||
@@ -0,0 +1,72 @@
|
||||
{{!
|
||||
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/forum_posts_with_context_links
|
||||
|
||||
Template to render a list of posts with breadcrumb style subject and a link
|
||||
in the footer to the full discussion.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
{{#posts}}
|
||||
{{< mod_forum/forum_discussion_post }}
|
||||
{{$subject}}
|
||||
{{#coursename}}
|
||||
{{#urls.viewcourse}}
|
||||
<a href="{{{urls.viewcourse}}}">{{{coursename}}}</a> ->
|
||||
{{/urls.viewcourse}}
|
||||
{{^urls.viewcourse}}
|
||||
{{coursename}} ->
|
||||
{{/urls.viewcourse}}
|
||||
{{/coursename}}
|
||||
{{#urls.viewforum}}
|
||||
<a href="{{{urls.viewforum}}}">{{{forumname}}}</a>
|
||||
{{/urls.viewforum}}
|
||||
{{^urls.viewforum}}
|
||||
{{{forumname}}}
|
||||
{{/urls.viewforum}}
|
||||
{{#showdiscussionname}}
|
||||
{{#urls.viewdiscussion}}
|
||||
-> <a href="{{{urls.viewdiscussion}}}">{{{discussionname}}}</a>
|
||||
{{/urls.viewdiscussion}}
|
||||
{{^urls.viewdiscussion}}
|
||||
{{{discussionname}}}
|
||||
{{/urls.viewdiscussion}}
|
||||
{{#hasparent}}
|
||||
{{#urls.viewparent}}
|
||||
-> <a href="{{{urls.viewparent}}}">{{{subject}}}</a>
|
||||
{{/urls.viewparent}}
|
||||
{{^urls.viewparent}}
|
||||
{{{subject}}}
|
||||
{{/urls.viewparent}}
|
||||
{{/hasparent}}
|
||||
{{/showdiscussionname}}
|
||||
{{/subject}}
|
||||
{{$replies}}{{/replies}}
|
||||
{{$footer}}
|
||||
<div><a href="{{{urls.view}}}">{{#str}} postincontext, mod_forum {{/str}}</a></div>
|
||||
{{/footer}}
|
||||
{{/ mod_forum/forum_discussion_post }}
|
||||
{{/posts}}
|
||||
@@ -0,0 +1,39 @@
|
||||
{{!
|
||||
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/news_discussion_list
|
||||
|
||||
Template to render a list of discussions for a news forum
|
||||
|
||||
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}}
|
||||
{{$no_discussions_text}}
|
||||
({{#str}}nonews, forum{{/str}})
|
||||
{{/no_discussions_text}}
|
||||
{{/ mod_forum/discussion_list }}
|
||||
@@ -0,0 +1,39 @@
|
||||
{{!
|
||||
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}}addanewquestion, forum{{/str}}
|
||||
{{/discussion_create_text}}
|
||||
{{$no_discussions_text}}
|
||||
({{#str}}noquestions, forum{{/str}})
|
||||
{{/no_discussions_text}}
|
||||
{{/ mod_forum/discussion_list }}
|
||||
@@ -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/single_discussion_list
|
||||
|
||||
Template to render a discussion for a single discussion forum.
|
||||
|
||||
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 }}
|
||||
@@ -96,7 +96,7 @@ class behat_mod_forum extends behat_base {
|
||||
|
||||
// Navigate to forum.
|
||||
$this->execute('behat_general::click_link', $this->escape($forumname));
|
||||
$this->execute('behat_forms::press_button', $buttonstr);
|
||||
$this->execute('behat_general::click_link', $buttonstr);
|
||||
|
||||
// Fill form and post.
|
||||
$this->execute('behat_forms::i_set_the_following_fields_to_these_values', $table);
|
||||
|
||||
@@ -68,12 +68,12 @@ Feature: Students can choose from 4 discussion display options and their choice
|
||||
And I follow "Discussion 1"
|
||||
When I select "Display replies in threaded form" from the "mode" singleselect
|
||||
Then I should see "Discussion contents 1, first message"
|
||||
And I should see "Reply 1 to discussion 1" in the "span.forumthread" "css_element"
|
||||
And I should see "Reply 1 to discussion 1" in the "div.forumthread" "css_element"
|
||||
And I follow "Test forum name"
|
||||
And I follow "Discussion 2"
|
||||
And the field "Display mode" matches value "Display replies in threaded form"
|
||||
And I should see "Discussion contents 2, first message"
|
||||
And I should see "Reply 1 to discussion 2" in the "span.forumthread" "css_element"
|
||||
And I should see "Reply 1 to discussion 2" in the "div.forumthread" "css_element"
|
||||
|
||||
Scenario: Display replies in nested form
|
||||
Given I follow "Test forum name"
|
||||
|
||||
@@ -34,7 +34,7 @@ Feature: Edited forum posts handle tags correctly
|
||||
| Subject | Student post subject |
|
||||
| Message | Student post message |
|
||||
| Tags | Tag1 |
|
||||
Then I should see "Tag1" in the ".forum-tags" "css_element"
|
||||
Then I should see "Tag1" in the ".tag_list" "css_element"
|
||||
And I click on "Edit" "link" in the "//div[@aria-label='Student post subject by Student 1']" "xpath_element"
|
||||
Then I should see "Tag1" in the ".form-autocomplete-selection" "css_element"
|
||||
|
||||
@@ -50,7 +50,7 @@ Feature: Edited forum posts handle tags correctly
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test forum"
|
||||
And I click on "Add a new discussion topic" "button"
|
||||
And I click on "Add a new discussion topic" "link"
|
||||
And I expand all fieldsets
|
||||
And I open the autocomplete suggestions list
|
||||
And I should see "OT1" in the ".form-autocomplete-suggestions" "css_element"
|
||||
@@ -60,9 +60,9 @@ Feature: Edited forum posts handle tags correctly
|
||||
| Subject | Student post subject |
|
||||
| Message | Student post message |
|
||||
| Tags | OT1, OT3 |
|
||||
Then I should see "OT1" in the ".forum-tags" "css_element"
|
||||
And I should see "OT3" in the ".forum-tags" "css_element"
|
||||
And I should not see "OT2" in the ".forum-tags" "css_element"
|
||||
Then I should see "OT1" in the ".tag_list" "css_element"
|
||||
And I should see "OT3" in the ".tag_list" "css_element"
|
||||
And I should not see "OT2" in the ".tag_list" "css_element"
|
||||
And I click on "Edit" "link" in the "//div[@aria-label='Student post subject by Teacher 1']" "xpath_element"
|
||||
And I should see "OT1" in the ".form-autocomplete-selection" "css_element"
|
||||
And I should see "OT3" in the ".form-autocomplete-selection" "css_element"
|
||||
|
||||
@@ -29,13 +29,13 @@ Feature: A user can control their default discussion subscription settings
|
||||
And I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test forum name"
|
||||
When I press "Add a new discussion topic"
|
||||
When I click on "Add a new discussion topic" "link"
|
||||
Then "input[name=discussionsubscribe][checked=checked]" "css_element" should exist
|
||||
And I log out
|
||||
And I log in as "student2"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test forum name"
|
||||
And I press "Add a new discussion topic"
|
||||
And I click on "Add a new discussion topic" "link"
|
||||
And "input[name=discussionsubscribe]:not([checked=checked])" "css_element" should exist
|
||||
|
||||
Scenario: Replying to an existing discussion in an optional forum follows user preferences
|
||||
@@ -72,13 +72,13 @@ Feature: A user can control their default discussion subscription settings
|
||||
And I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test forum name"
|
||||
When I press "Add a new discussion topic"
|
||||
When I click on "Add a new discussion topic" "link"
|
||||
Then "input[name=discussionsubscribe][checked=checked]" "css_element" should exist
|
||||
And I log out
|
||||
And I log in as "student2"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test forum name"
|
||||
And I press "Add a new discussion topic"
|
||||
And I click on "Add a new discussion topic" "link"
|
||||
And "input[name=discussionsubscribe][checked=checked]" "css_element" should exist
|
||||
|
||||
Scenario: Replying to an existing discussion in an automatic forum follows forum subscription
|
||||
|
||||
@@ -35,7 +35,7 @@ Feature: Forums in 'No groups' mode allow posting to All participants for all us
|
||||
And I follow "Standard forum name"
|
||||
And I should not see "Group A"
|
||||
And I should not see "Group B"
|
||||
When I click on "Add a new discussion topic" "button"
|
||||
When I click on "Add a new discussion topic" "link"
|
||||
Then I should not see "Post a copy to all groups"
|
||||
And I should not see "Group" in the "form" "css_element"
|
||||
And I set the following fields to these values:
|
||||
@@ -51,7 +51,7 @@ Feature: Forums in 'No groups' mode allow posting to All participants for all us
|
||||
And I follow "Standard forum name"
|
||||
And I should not see "Group A"
|
||||
And I should not see "Group B"
|
||||
When I click on "Add a new discussion topic" "button"
|
||||
When I click on "Add a new discussion topic" "link"
|
||||
Then I should not see "Post a copy to all groups"
|
||||
And I should not see "Group" in the "form" "css_element"
|
||||
And I set the following fields to these values:
|
||||
|
||||
@@ -23,7 +23,7 @@ Feature: Posting to forums in a course with no groups behaves correctly
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Standard forum"
|
||||
When I click on "Add a new discussion topic" "button"
|
||||
When I click on "Add a new discussion topic" "link"
|
||||
Then I should not see "Post a copy to all groups"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Teacher -> All participants |
|
||||
@@ -36,7 +36,7 @@ Feature: Posting to forums in a course with no groups behaves correctly
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Separate forum"
|
||||
When I click on "Add a new discussion topic" "button"
|
||||
When I click on "Add a new discussion topic" "link"
|
||||
Then I should not see "Post a copy to all groups"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Teacher -> All participants |
|
||||
@@ -49,7 +49,7 @@ Feature: Posting to forums in a course with no groups behaves correctly
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Visible forum"
|
||||
When I click on "Add a new discussion topic" "button"
|
||||
When I click on "Add a new discussion topic" "link"
|
||||
Then I should not see "Post a copy to all groups"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Teacher -> All participants |
|
||||
@@ -62,7 +62,7 @@ Feature: Posting to forums in a course with no groups behaves correctly
|
||||
Given I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Standard forum"
|
||||
When I click on "Add a new discussion topic" "button"
|
||||
When I click on "Add a new discussion topic" "link"
|
||||
Then I should not see "Post a copy to all groups"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Student -> All participants |
|
||||
|
||||
@@ -112,7 +112,7 @@ Feature: A user with access to multiple groups should be able to post a copy of
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "No group forum"
|
||||
And I press "Add a new discussion topic"
|
||||
And I click on "Add a new discussion topic" "link"
|
||||
Then I should not see "Post a copy to all groups"
|
||||
|
||||
Scenario: Posts to all groups that have groupings should only display within the grouping and not to other groups
|
||||
|
||||
@@ -56,7 +56,7 @@ Feature: Posting to all groups in a separate group discussion is restricted to u
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Standard forum name"
|
||||
When I click on "Add a new discussion topic" "button"
|
||||
When I click on "Add a new discussion topic" "link"
|
||||
Then the "Group" select box should contain "All participants"
|
||||
And the "Group" select box should contain "Group A"
|
||||
And the "Group" select box should contain "Group B"
|
||||
@@ -68,7 +68,7 @@ Feature: Posting to all groups in a separate group discussion is restricted to u
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Standard forum name"
|
||||
And I select "Group A" from the "Separate groups" singleselect
|
||||
When I click on "Add a new discussion topic" "button"
|
||||
When I click on "Add a new discussion topic" "link"
|
||||
Then I should see "Post a copy to all groups"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Teacher 1 -> Group B |
|
||||
@@ -98,7 +98,7 @@ Feature: Posting to all groups in a separate group discussion is restricted to u
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Standard forum name"
|
||||
And I select "Group A" from the "Separate groups" singleselect
|
||||
When I click on "Add a new discussion topic" "button"
|
||||
When I click on "Add a new discussion topic" "link"
|
||||
Then I should see "Post a copy to all groups"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Teacher 1 -> Group C |
|
||||
@@ -127,7 +127,7 @@ Feature: Posting to all groups in a separate group discussion is restricted to u
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Standard forum name"
|
||||
When I click on "Add a new discussion topic" "button"
|
||||
When I click on "Add a new discussion topic" "link"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Teacher 1 -> Post to all |
|
||||
| Message | Teacher 1 -> Post to all |
|
||||
@@ -155,7 +155,7 @@ Feature: Posting to all groups in a separate group discussion is restricted to u
|
||||
And I am on "Course 1" course homepage
|
||||
When I follow "Standard forum name"
|
||||
Then I should see "Group A"
|
||||
And I click on "Add a new discussion topic" "button"
|
||||
And I click on "Add a new discussion topic" "link"
|
||||
And I should see "Group A"
|
||||
And I should not see "Group B"
|
||||
And I should not see "Group C"
|
||||
@@ -173,7 +173,7 @@ Feature: Posting to all groups in a separate group discussion is restricted to u
|
||||
And I am on "Course 1" course homepage
|
||||
When I follow "Standard forum name"
|
||||
And I select "Group A" from the "Separate groups" singleselect
|
||||
And I click on "Add a new discussion topic" "button"
|
||||
And I click on "Add a new discussion topic" "link"
|
||||
And the "Group" select box should not contain "All participants"
|
||||
And the "Group" select box should contain "Group A"
|
||||
And the "Group" select box should contain "Group B"
|
||||
@@ -193,7 +193,7 @@ Feature: Posting to all groups in a separate group discussion is restricted to u
|
||||
And I should not see "Student -> B"
|
||||
# Now try posting in Group A (starting at Group B)
|
||||
And I select "Group B" from the "Separate groups" singleselect
|
||||
And I click on "Add a new discussion topic" "button"
|
||||
And I click on "Add a new discussion topic" "link"
|
||||
And the "Group" select box should not contain "All participants"
|
||||
And the "Group" select box should contain "Group A"
|
||||
And the "Group" select box should contain "Group B"
|
||||
@@ -220,7 +220,7 @@ Feature: Posting to all groups in a separate group discussion is restricted to u
|
||||
Given I log in as "noneditor1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Standard forum name"
|
||||
When I click on "Add a new discussion topic" "button"
|
||||
When I click on "Add a new discussion topic" "link"
|
||||
Then the "Group" select box should not contain "All participants"
|
||||
And the "Group" select box should contain "Group A"
|
||||
And the "Group" select box should contain "Group B"
|
||||
@@ -234,7 +234,7 @@ Feature: Posting to all groups in a separate group discussion is restricted to u
|
||||
Given I log in as "noneditor1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Standard forum name"
|
||||
When I click on "Add a new discussion topic" "button"
|
||||
When I click on "Add a new discussion topic" "link"
|
||||
Then the "Group" select box should not contain "All participants"
|
||||
And the "Group" select box should contain "Group A"
|
||||
And the "Group" select box should contain "Group B"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user