first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
define("core_comment/admin",["exports","core/event_dispatcher","core/notification","core/pending","core/prefetch","core/str","core_comment/repository","core_reportbuilder/local/events","core_reportbuilder/local/selectors"],(function(_exports,_event_dispatcher,_notification,_pending,_prefetch,_str,_repository,reportEvents,reportSelectors){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),reportEvents=_interopRequireWildcard(reportEvents),reportSelectors=_interopRequireWildcard(reportSelectors);const Selectors_commentDelete='[data-action="comment-delete"]',Selectors_commentDeleteChecked='[data-togglegroup="report-select-all"][data-toggle="slave"]:checked',Selectors_commentDeleteSelected='[data-action="comment-delete-selected"]';_exports.init=()=>{(0,_prefetch.prefetchStrings)("core_admin",["confirmdeletecomments"]),(0,_prefetch.prefetchStrings)("core",["delete","deleteselected"]),document.addEventListener("click",(event=>{const commentDelete=event.target.closest(Selectors_commentDelete);if(commentDelete){event.preventDefault();const triggerElement=commentDelete.closest(".dropdown").querySelector(".dropdown-toggle");_notification.default.saveCancelPromise((0,_str.getString)("delete","core"),(0,_str.getString)("confirmdeletecomments","core_admin"),(0,_str.getString)("delete","core"),{triggerElement:triggerElement}).then((()=>{const pendingPromise=new _pending.default("core_comment/comment:delete"),reportElement=event.target.closest(reportSelectors.regions.report);return(0,_repository.deleteComment)(commentDelete.dataset.commentId).then((()=>((0,_event_dispatcher.dispatchEvent)(reportEvents.tableReload,{preservePagination:!0},reportElement),pendingPromise.resolve()))).catch(_notification.default.exception)})).catch((()=>{}))}const commentDeleteSelected=event.target.closest(Selectors_commentDeleteSelected);if(commentDeleteSelected){event.preventDefault();const reportElement=document.querySelector(reportSelectors.regions.report),commentDeleteChecked=reportElement.querySelectorAll(Selectors_commentDeleteChecked);if(0===commentDeleteChecked.length)return;_notification.default.saveCancelPromise((0,_str.getString)("deleteselected","core"),(0,_str.getString)("confirmdeletecomments","core_admin"),(0,_str.getString)("delete","core"),{triggerElement:commentDeleteSelected}).then((()=>{const pendingPromise=new _pending.default("core_comment/comments:delete"),deleteCommentIds=[...commentDeleteChecked].map((check=>check.value));return(0,_repository.deleteComments)(deleteCommentIds).then((()=>((0,_event_dispatcher.dispatchEvent)(reportEvents.tableReload,{preservePagination:!0},reportElement),pendingPromise.resolve()))).catch(_notification.default.exception)})).catch((()=>{}))}}))}}));
//# sourceMappingURL=admin.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
define("core_comment/repository",["exports","core/ajax"],(function(_exports,_ajax){var obj;
/**
* Module to handle comment AJAX requests
*
* @module core_comment/repository
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.deleteComments=_exports.deleteComment=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.deleteComment=comment=>deleteComments([comment]);const deleteComments=comments=>{const request={methodname:"core_comment_delete_comments",args:{comments:comments}};return _ajax.default.call([request])[0]};_exports.deleteComments=deleteComments}));
//# sourceMappingURL=repository.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Module to handle comment AJAX requests\n *\n * @module core_comment/repository\n * @copyright 2022 Paul Holden <paulh@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Delete single comment\n *\n * @param {Number} comment Comment ID\n * @return {Promise}\n */\nexport const deleteComment = comment => deleteComments([comment]);\n\n/**\n * Delete multiple comments\n *\n * @param {Number[]} comments Comment IDs\n * @return {Promise}\n */\nexport const deleteComments = comments => {\n const request = {\n methodname: 'core_comment_delete_comments',\n args: {comments}\n };\n\n return Ajax.call([request])[0];\n};\n"],"names":["comment","deleteComments","comments","request","methodname","args","Ajax","call"],"mappings":";;;;;;;8LA+B6BA,SAAWC,eAAe,CAACD,gBAQ3CC,eAAiBC,iBACpBC,QAAU,CACZC,WAAY,+BACZC,KAAM,CAACH,SAAAA,kBAGJI,cAAKC,KAAK,CAACJ,UAAU"}
+111
View File
@@ -0,0 +1,111 @@
// 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/>.
/**
* Comments admin management
*
* @module core_comment/admin
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
"use strict";
import {dispatchEvent} from 'core/event_dispatcher';
import Notification from 'core/notification';
import Pending from 'core/pending';
import {prefetchStrings} from 'core/prefetch';
import {getString} from 'core/str';
import {deleteComment, deleteComments} from 'core_comment/repository';
import * as reportEvents from 'core_reportbuilder/local/events';
import * as reportSelectors from 'core_reportbuilder/local/selectors';
const Selectors = {
commentDelete: '[data-action="comment-delete"]',
commentDeleteChecked: '[data-togglegroup="report-select-all"][data-toggle="slave"]:checked',
commentDeleteSelected: '[data-action="comment-delete-selected"]',
};
/**
* Initialise module
*/
export const init = () => {
prefetchStrings('core_admin', [
'confirmdeletecomments',
]);
prefetchStrings('core', [
'delete',
'deleteselected'
]);
document.addEventListener('click', event => {
const commentDelete = event.target.closest(Selectors.commentDelete);
if (commentDelete) {
event.preventDefault();
// Use triggerElement to return focus to the action menu toggle.
const triggerElement = commentDelete.closest('.dropdown').querySelector('.dropdown-toggle');
Notification.saveCancelPromise(
getString('delete', 'core'),
getString('confirmdeletecomments', 'core_admin'),
getString('delete', 'core'),
{triggerElement}
).then(() => {
const pendingPromise = new Pending('core_comment/comment:delete');
const reportElement = event.target.closest(reportSelectors.regions.report);
return deleteComment(commentDelete.dataset.commentId)
.then(() => {
dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement);
return pendingPromise.resolve();
})
.catch(Notification.exception);
}).catch(() => {
return;
});
}
const commentDeleteSelected = event.target.closest(Selectors.commentDeleteSelected);
if (commentDeleteSelected) {
event.preventDefault();
const reportElement = document.querySelector(reportSelectors.regions.report);
const commentDeleteChecked = reportElement.querySelectorAll(Selectors.commentDeleteChecked);
if (commentDeleteChecked.length === 0) {
return;
}
Notification.saveCancelPromise(
getString('deleteselected', 'core'),
getString('confirmdeletecomments', 'core_admin'),
getString('delete', 'core'),
{triggerElement: commentDeleteSelected}
).then(() => {
const pendingPromise = new Pending('core_comment/comments:delete');
const deleteCommentIds = [...commentDeleteChecked].map(check => check.value);
return deleteComments(deleteCommentIds)
.then(() => {
dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement);
return pendingPromise.resolve();
})
.catch(Notification.exception);
}).catch(() => {
return;
});
}
});
};
+47
View File
@@ -0,0 +1,47 @@
// 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 to handle comment AJAX requests
*
* @module core_comment/repository
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Ajax from 'core/ajax';
/**
* Delete single comment
*
* @param {Number} comment Comment ID
* @return {Promise}
*/
export const deleteComment = comment => deleteComments([comment]);
/**
* Delete multiple comments
*
* @param {Number[]} comments Comment IDs
* @return {Promise}
*/
export const deleteComments = comments => {
const request = {
methodname: 'core_comment_delete_comments',
args: {comments}
};
return Ajax.call([request])[0];
};
+372
View File
@@ -0,0 +1,372 @@
<?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/>.
/**
* External comment API
*
* @package core_comment
* @category external
* @copyright Costantino Cito <ccito@cvaconsulting.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.9
*/
use core_external\external_api;
use core_external\external_format_value;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
defined('MOODLE_INTERNAL') || die();
require_once("$CFG->dirroot/comment/lib.php");
/**
* External comment API functions
*
* @package core_comment
* @category external
* @copyright Costantino Cito <ccito@cvaconsulting.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.9
*/
class core_comment_external extends external_api {
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.9
*/
public static function get_comments_parameters() {
return new external_function_parameters(
array(
'contextlevel' => new external_value(PARAM_ALPHA, 'contextlevel system, course, user...'),
'instanceid' => new external_value(PARAM_INT, 'the Instance id of item associated with the context level'),
'component' => new external_value(PARAM_COMPONENT, 'component'),
'itemid' => new external_value(PARAM_INT, 'associated id'),
'area' => new external_value(PARAM_AREA, 'string comment area', VALUE_DEFAULT, ''),
'page' => new external_value(PARAM_INT, 'page number (0 based)', VALUE_DEFAULT, 0),
'sortdirection' => new external_value(PARAM_ALPHA, 'Sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC'),
)
);
}
/**
* Return a list of comments
*
* @param string $contextlevel ('system, course, user', etc..)
* @param int $instanceid
* @param string $component the name of the component
* @param int $itemid the item id
* @param string $area comment area
* @param int $page page number
* @param string $sortdirection sort direction
* @return array of comments and warnings
* @since Moodle 2.9
*/
public static function get_comments($contextlevel, $instanceid, $component, $itemid, $area = '', $page = 0,
$sortdirection = 'DESC') {
global $CFG;
$warnings = array();
$arrayparams = array(
'contextlevel' => $contextlevel,
'instanceid' => $instanceid,
'component' => $component,
'itemid' => $itemid,
'area' => $area,
'page' => $page,
'sortdirection' => $sortdirection,
);
$params = self::validate_parameters(self::get_comments_parameters(), $arrayparams);
$sortdirection = strtoupper($params['sortdirection']);
$directionallowedvalues = array('ASC', 'DESC');
if (!in_array($sortdirection, $directionallowedvalues)) {
throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
'allowed values are: ' . implode(',', $directionallowedvalues));
}
$context = self::get_context_from_params($params);
self::validate_context($context);
require_capability('moodle/comment:view', $context);
$args = new stdClass;
$args->context = $context;
$args->area = $params['area'];
$args->itemid = $params['itemid'];
$args->component = $params['component'];
$commentobject = new comment($args);
$comments = $commentobject->get_comments($params['page'], $sortdirection);
// False means no permissions to see comments.
if ($comments === false) {
throw new moodle_exception('nopermissions', 'error', '', 'view comments');
}
$options = array('blanktarget' => true);
foreach ($comments as $key => $comment) {
list($comments[$key]->content, $comments[$key]->format) = \core_external\util::format_text($comment->content,
$comment->format,
$context->id,
$params['component'],
'',
0,
$options);
}
$results = array(
'comments' => $comments,
'count' => $commentobject->count(),
'perpage' => (!empty($CFG->commentsperpage)) ? $CFG->commentsperpage : 15,
'canpost' => $commentobject->can_post(),
'warnings' => $warnings
);
return $results;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 2.9
*/
public static function get_comments_returns() {
return new external_single_structure(
array(
'comments' => new external_multiple_structure(
self::get_comment_structure(), 'List of comments'
),
'count' => new external_value(PARAM_INT, 'Total number of comments.', VALUE_OPTIONAL),
'perpage' => new external_value(PARAM_INT, 'Number of comments per page.', VALUE_OPTIONAL),
'canpost' => new external_value(PARAM_BOOL, 'Whether the user can post in this comment area.', VALUE_OPTIONAL),
'warnings' => new external_warnings()
)
);
}
/**
* Helper to get the structure of a single comment.
*
* @return external_single_structure the comment structure.
*/
protected static function get_comment_structure() {
return new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'Comment ID'),
'content' => new external_value(PARAM_RAW, 'The content text formatted'),
'format' => new external_format_value('content'),
'timecreated' => new external_value(PARAM_INT, 'Time created (timestamp)'),
'strftimeformat' => new external_value(PARAM_NOTAGS, 'Time format'),
'profileurl' => new external_value(PARAM_URL, 'URL profile'),
'fullname' => new external_value(PARAM_NOTAGS, 'fullname'),
'time' => new external_value(PARAM_NOTAGS, 'Time in human format'),
'avatar' => new external_value(PARAM_RAW, 'HTML user picture'),
'userid' => new external_value(PARAM_INT, 'User ID'),
'delete' => new external_value(PARAM_BOOL, 'Permission to delete=true/false', VALUE_OPTIONAL)
), 'comment'
);
}
/**
* Returns description of method parameters for the add_comments method.
*
* @return external_function_parameters
* @since Moodle 3.8
*/
public static function add_comments_parameters() {
return new external_function_parameters(
[
'comments' => new external_multiple_structure(
new external_single_structure(
[
'contextlevel' => new external_value(PARAM_ALPHA, 'contextlevel system, course, user...'),
'instanceid' => new external_value(PARAM_INT, 'the id of item associated with the contextlevel'),
'component' => new external_value(PARAM_COMPONENT, 'component'),
'content' => new external_value(PARAM_RAW, 'component'),
'itemid' => new external_value(PARAM_INT, 'associated id'),
'area' => new external_value(PARAM_AREA, 'string comment area', VALUE_DEFAULT, ''),
]
)
)
]
);
}
/**
* Add a comment or comments.
*
* @param array $comments the array of comments to create.
* @return array the array containing those comments created.
* @throws comment_exception
* @since Moodle 3.8
*/
public static function add_comments($comments) {
global $CFG, $SITE;
if (empty($CFG->usecomments)) {
throw new comment_exception('commentsnotenabled', 'moodle');
}
$params = self::validate_parameters(self::add_comments_parameters(), ['comments' => $comments]);
// Validate every intended comment before creating anything, storing the validated comment for use below.
foreach ($params['comments'] as $index => $comment) {
$context = self::get_context_from_params($comment);
self::validate_context($context);
list($context, $course, $cm) = get_context_info_array($context->id);
if ($context->id == SYSCONTEXTID) {
$course = $SITE;
}
// Initialising comment object.
$args = new stdClass();
$args->context = $context;
$args->course = $course;
$args->cm = $cm;
$args->component = $comment['component'];
$args->itemid = $comment['itemid'];
$args->area = $comment['area'];
$manager = new comment($args);
if (!$manager->can_post()) {
throw new comment_exception('nopermissiontocomment');
}
$params['comments'][$index]['preparedcomment'] = $manager;
}
// Create the comments.
$results = [];
foreach ($params['comments'] as $comment) {
$manager = $comment['preparedcomment'];
$newcomment = $manager->add($comment['content']);
$newcomment->delete = true; // USER created the comment, so they can delete it.
$results[] = $newcomment;
}
return $results;
}
/**
* Returns description of method result value for the add_comments method.
*
* @return \core_external\external_description
* @since Moodle 3.8
*/
public static function add_comments_returns() {
return new external_multiple_structure(
self::get_comment_structure()
);
}
/**
* Returns description of method parameters for the delete_comments() method.
*
* @return external_function_parameters
* @since Moodle 3.8
*/
public static function delete_comments_parameters() {
return new external_function_parameters(
[
'comments' => new external_multiple_structure(
new external_value(PARAM_INT, 'id of the comment', VALUE_DEFAULT, 0)
)
]
);
}
/**
* Deletes a comment or comments.
*
* @param array $comments array of comment ids to be deleted
* @return array
* @throws comment_exception
* @since Moodle 3.8
*/
public static function delete_comments(array $comments) {
global $CFG, $DB, $USER, $SITE;
if (empty($CFG->usecomments)) {
throw new comment_exception('commentsnotenabled', 'moodle');
}
$params = self::validate_parameters(self::delete_comments_parameters(), ['comments' => $comments]);
$commentids = $params['comments'];
list($insql, $inparams) = $DB->get_in_or_equal($commentids);
$commentrecords = $DB->get_records_select('comments', "id {$insql}", $inparams);
// If one or more of the records could not be found, report this and fail early.
if (count($commentrecords) != count($comments)) {
$invalidcomments = array_diff($commentids, array_column($commentrecords, 'id'));
$invalidcommentsstr = implode(',', $invalidcomments);
throw new comment_exception("One or more comments could not be found by id: $invalidcommentsstr");
}
// Make sure we can delete every one of the comments before actually doing so.
$comments = []; // Holds the comment objects, for later deletion.
foreach ($commentrecords as $commentrecord) {
// Validate the context.
list($context, $course, $cm) = get_context_info_array($commentrecord->contextid);
if ($context->id == SYSCONTEXTID) {
$course = $SITE;
}
self::validate_context($context);
// Make sure the user is allowed to delete the comment.
$args = new stdClass;
$args->context = $context;
$args->course = $course;
$args->cm = $cm;
$args->component = $commentrecord->component;
$args->itemid = $commentrecord->itemid;
$args->area = $commentrecord->commentarea;
$manager = new comment($args);
if (!$manager->can_delete($commentrecord)) {
throw new comment_exception('nopermissiontodelentry');
}
// User is allowed to delete it, so store the comment object, for use below in final deletion.
$comments[$commentrecord->id] = $manager;
}
// All comments can be deleted by the user. Make it so.
foreach ($comments as $commentid => $comment) {
$comment->delete($commentid);
}
return [];
}
/**
* Returns description of method result value for the delete_comments() method.
*
* @return \core_external\external_description
* @since Moodle 3.8
*/
public static function delete_comments_returns() {
return new external_warnings();
}
}
+140
View File
@@ -0,0 +1,140 @@
<?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/>.
/**
* Exporting a comment area.
*
* A comment area is the set of information about a defined comments area.
*
* @package core_comment
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_comment\external;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/comment/lib.php');
use comment;
use renderer_base;
use stdClass;
/**
* Class for exporting a comment area.
*
* @package core_comment
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class comment_area_exporter extends \core\external\exporter {
/** @var comment The comment instance. */
protected $comment = null;
public function __construct(comment $comment, $related = array()) {
$this->comment = $comment;
$data = new stdClass();
$data->component = $comment->get_component();
$data->commentarea = $comment->get_commentarea();
$data->itemid = $comment->get_itemid();
$data->courseid = $comment->get_courseid();
$data->contextid = $comment->get_context()->id;
$data->cid = $comment->get_cid();
parent::__construct($data, $related);
}
protected static function define_properties() {
return array(
'component' => array(
'type' => PARAM_COMPONENT,
),
'commentarea' => array(
'type' => PARAM_AREA,
),
'itemid' => array(
'type' => PARAM_INT,
),
'courseid' => array(
'type' => PARAM_INT,
),
'contextid' => array(
'type' => PARAM_INT,
),
'cid' => array(
'type' => PARAM_ALPHANUMEXT,
),
);
}
protected static function define_other_properties() {
return array(
'autostart' => array(
'type' => PARAM_BOOL,
),
'canpost' => array(
'type' => PARAM_BOOL,
),
'canview' => array(
'type' => PARAM_BOOL,
),
'count' => array(
'type' => PARAM_INT,
),
'collapsediconkey' => array(
'type' => PARAM_RAW,
),
'displaytotalcount' => array(
'type' => PARAM_BOOL,
),
'displaycancel' => array(
'type' => PARAM_BOOL,
),
'fullwidth' => array(
'type' => PARAM_BOOL,
),
'linktext' => array(
'type' => PARAM_RAW,
),
'notoggle' => array(
'type' => PARAM_BOOL,
),
'template' => array(
'type' => PARAM_RAW,
),
'canpostorhascomments' => array(
'type' => PARAM_BOOL
)
);
}
public function get_other_values(renderer_base $output) {
$values = array();
$values['autostart'] = $this->comment->get_autostart();
$values['canpost'] = $this->comment->can_post();
$values['canview'] = $this->comment->can_view();
$values['collapsediconkey'] = right_to_left() ? 't/collapsed_rtl' : 't/collapsed';
$values['count'] = $this->comment->count();
$values['displaycancel'] = $this->comment->get_displaycancel();
$values['displaytotalcount'] = $this->comment->get_displaytotalcount();
$values['fullwidth'] = $this->comment->get_fullwidth();
$values['linktext'] = $this->comment->get_linktext();
$values['notoggle'] = $this->comment->get_notoggle();
$values['template'] = $this->comment->get_template();
$values['canpostorhascomments'] = $values['canpost'] || ($values['canview'] && $values['count'] > 0);
return $values;
}
}
+271
View File
@@ -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/>.
/**
* Privacy class for requesting user data.
*
* @package core_comment
* @copyright 2018 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_comment\privacy;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\metadata\collection;
use \core_privacy\local\request\transform;
use \core_privacy\local\request\userlist;
/**
* Privacy class for requesting user data.
*
* @package core_comment
* @copyright 2018 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\subsystem\plugin_provider,
\core_privacy\local\request\shared_userlist_provider
{
/**
* Returns meta data about this system.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_database_table('comments', [
'content' => 'privacy:metadata:comment:content',
'timecreated' => 'privacy:metadata:comment:timecreated',
'userid' => 'privacy:metadata:comment:userid',
], 'privacy:metadata:comment');
return $collection;
}
/**
* Writes user data to the writer for the user to download.
*
* @param \context $context The context to export data for.
* @param string $component The component that is calling this function
* @param string $commentarea The comment area related to the component
* @param int $itemid An identifier for a group of comments
* @param array $subcontext The sub-context in which to export this data
* @param bool $onlyforthisuser Only return the comments this user made.
*/
public static function export_comments(\context $context, string $component, string $commentarea, int $itemid,
array $subcontext, bool $onlyforthisuser = true) {
global $USER, $DB;
$params = [
'contextid' => $context->id,
'component' => $component,
'commentarea' => $commentarea,
'itemid' => $itemid
];
$sql = "SELECT c.id, c.content, c.format, c.timecreated, c.userid
FROM {comments} c
WHERE c.contextid = :contextid AND
c.commentarea = :commentarea AND
c.itemid = :itemid AND
(c.component IS NULL OR c.component = :component)";
if ($onlyforthisuser) {
$sql .= " AND c.userid = :userid";
$params['userid'] = $USER->id;
}
$sql .= " ORDER BY c.timecreated DESC";
$rs = $DB->get_recordset_sql($sql, $params);
$comments = [];
foreach ($rs as $record) {
if ($record->userid != $USER->id) {
// Clean HTML in comments that were added by other users.
$comment = ['content' => format_text($record->content, $record->format, ['context' => $context])];
} else {
// Export comments made by this user as they are stored.
$comment = ['content' => $record->content, 'contentformat' => $record->format];
}
$comment += [
'time' => transform::datetime($record->timecreated),
'userid' => transform::user($record->userid),
];
$comments[] = (object)$comment;
}
$rs->close();
if (!empty($comments)) {
$subcontext[] = get_string('commentsubcontext', 'core_comment');
\core_privacy\local\request\writer::with_context($context)
->export_data($subcontext, (object) [
'comments' => $comments,
]);
}
}
/**
* Deletes all comments for a specified context, component, and commentarea.
*
* @param \context $context Details about which context to delete comments for.
* @param string $component Component to delete.
* @param string $commentarea Comment area to delete.
* @param int $itemid The item ID for use with deletion.
*/
public static function delete_comments_for_all_users(\context $context, string $component, string $commentarea = null,
int $itemid = null) {
global $DB;
$params = [
'contextid' => $context->id,
'component' => $component
];
if (isset($commentarea)) {
$params['commentarea'] = $commentarea;
}
if (isset($itemid)) {
$params['itemid'] = $itemid;
}
$DB->delete_records('comments', $params);
}
/**
* Deletes all comments for a specified context, component, and commentarea.
*
* @param \context $context Details about which context to delete comments for.
* @param string $component Component to delete.
* @param string $commentarea Comment area to delete.
* @param string $itemidstest an SQL fragment that the itemid must match. Used
* in the query like WHERE itemid $itemidstest. Must use named parameters,
* and may not use named parameters called contextid, component or commentarea.
* @param array $params any query params used by $itemidstest.
*/
public static function delete_comments_for_all_users_select(\context $context, string $component, string $commentarea,
$itemidstest, $params = []) {
global $DB;
$params += ['contextid' => $context->id, 'component' => $component, 'commentarea' => $commentarea];
$DB->delete_records_select('comments',
'contextid = :contextid AND component = :component AND commentarea = :commentarea AND itemid ' . $itemidstest,
$params);
}
/**
* Deletes all records for a user from a list of approved contexts.
*
* @param \core_privacy\local\request\approved_contextlist $contextlist Contains the user ID and a list of contexts to be
* deleted from.
* @param string $component Component to delete from.
* @param string $commentarea Area to delete from.
* @param int $itemid The item id to delete from.
*/
public static function delete_comments_for_user(\core_privacy\local\request\approved_contextlist $contextlist,
string $component, string $commentarea = null, int $itemid = null) {
global $DB;
$userid = $contextlist->get_user()->id;
$contextids = implode(',', $contextlist->get_contextids());
$params = [
'userid' => $userid,
'component' => $component,
];
$areasql = '';
if (isset($commentarea)) {
$params['commentarea'] = $commentarea;
$areasql = 'AND commentarea = :commentarea';
}
$itemsql = '';
if (isset($itemid)) {
$params['itemid'] = $itemid;
$itemsql = 'AND itemid = :itemid';
}
list($insql, $inparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$params += $inparams;
$select = "userid = :userid AND component = :component $areasql $itemsql AND contextid $insql";
$DB->delete_records_select('comments', $select, $params);
}
/**
* Deletes all records for a context from a list of approved users.
*
* @param \core_privacy\local\request\approved_userlist $userlist Contains the list of users and
* a context to be deleted from.
* @param string $component Component to delete from.
* @param string $commentarea Area to delete from.
* @param int $itemid The item id to delete from.
*/
public static function delete_comments_for_users(\core_privacy\local\request\approved_userlist $userlist,
string $component, string $commentarea = null, int $itemid = null) {
global $DB;
$context = $userlist->get_context();
$params = [
'contextid' => $context->id,
'component' => $component,
];
$areasql = '';
if (isset($commentarea)) {
$params['commentarea'] = $commentarea;
$areasql = 'AND commentarea = :commentarea';
}
$itemsql = '';
if (isset($itemid)) {
$params['itemid'] = $itemid;
$itemsql = 'AND itemid = :itemid';
}
list($insql, $inparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
$params += $inparams;
$select = "contextid = :contextid AND component = :component {$areasql} {$itemsql} AND userid {$insql}";
$DB->delete_records_select('comments', $select, $params);
}
/**
* Add the list of users who have commented in the specified constraints.
*
* @param userlist $userlist The userlist to add the users to.
* @param string $alias An alias prefix to use for comment selects to avoid interference with your own sql.
* @param string $component The component to check.
* @param string $area The comment area to check.
* @param int $contextid The context id.
* @param string $insql The SQL to use in a sub-select for the itemid query.
* @param array $params The params required for the insql.
*/
public static function get_users_in_context_from_sql(
userlist $userlist, string $alias, string $component, string $area, int $contextid = null, string $insql = '',
array $params = []) {
if ($insql != '') {
$insql = "AND {$alias}.itemid {$insql}";
}
$contextsql = '';
if (isset($contextid)) {
$contextsql = "AND {$alias}.contextid = :{$alias}contextid";
$params["{$alias}contextid"] = $contextid;
}
// Comment authors.
$sql = "SELECT {$alias}.userid
FROM {comments} {$alias}
WHERE {$alias}.component = :{$alias}component
AND {$alias}.commentarea = :{$alias}commentarea
$contextsql $insql";
$params["{$alias}component"] = $component;
$params["{$alias}commentarea"] = $area;
$userlist->add_from_sql('userid', $sql, $params);
}
}
@@ -0,0 +1,118 @@
<?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/>.
declare(strict_types=1);
namespace core_comment\reportbuilder\datasource;
use core\reportbuilder\local\entities\context;
use core_reportbuilder\datasource;
use core_reportbuilder\local\entities\user;
use core_comment\reportbuilder\local\entities\comment;
/**
* Comments datasource
*
* @package core_comment
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class comments extends datasource {
/**
* Return user friendly name of the report source
*
* @return string
*/
public static function get_name(): string {
return get_string('comments', 'core_comment');
}
/**
* Initialise report
*/
protected function initialise(): void {
$commententity = new comment();
$commentalias = $commententity->get_table_alias('comments');
$this->set_main_table('comments', $commentalias);
$this->add_entity($commententity);
// Join the context entity.
$contextentity = (new context())
->set_table_alias('context', $commententity->get_table_alias('context'));
$this->add_entity($contextentity
->add_join($commententity->get_context_join())
);
// Join the user entity to the comment userid (author).
$userentity = new user();
$useralias = $userentity->get_table_alias('user');
$this->add_entity($userentity
->add_join("LEFT JOIN {user} {$useralias} ON {$useralias}.id = {$commentalias}.userid"));
// Add report elements from each of the entities we added to the report.
$this->add_all_from_entities();
}
/**
* Return the columns that will be added to the report upon creation
*
* @return string[]
*/
public function get_default_columns(): array {
return [
'user:fullname',
'context:name',
'comment:content',
'comment:timecreated',
];
}
/**
* Return the column sorting that will be added to the report upon creation
*
* @return int[]
*/
public function get_default_column_sorting(): array {
return [
'user:fullname' => SORT_ASC,
'comment:timecreated' => SORT_ASC,
];
}
/**
* Return the filters that will be added to the report upon creation
*
* @return string[]
*/
public function get_default_filters(): array {
return [
'comment:content',
];
}
/**
* Return the conditions that will be added to the report upon creation
*
* @return string[]
*/
public function get_default_conditions(): array {
return [
'user:fullname',
];
}
}
@@ -0,0 +1,266 @@
<?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/>.
declare(strict_types=1);
namespace core_comment\reportbuilder\local\entities;
use context;
use context_helper;
use html_writer;
use lang_string;
use stdClass;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\{date, text};
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\{column, filter};
/**
* Comment entity
*
* @package core_comment
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class comment extends base {
/**
* Database tables that this entity uses
*
* @return string[]
*/
protected function get_default_tables(): array {
return [
'comments',
'context',
];
}
/**
* The default title for this entity
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('comment', 'core_comment');
}
/**
* Initialise the entity
*
* @return base
*/
public function initialise(): base {
$columns = $this->get_all_columns();
foreach ($columns as $column) {
$this->add_column($column);
}
// All the filters defined by the entity can also be used as conditions.
$filters = $this->get_all_filters();
foreach ($filters as $filter) {
$this
->add_filter($filter)
->add_condition($filter);
}
return $this;
}
/**
* Returns list of all available columns
*
* @return column[]
*/
protected function get_all_columns(): array {
global $DB;
$commentalias = $this->get_table_alias('comments');
$contextalias = $this->get_table_alias('context');
// Content.
$contentfieldsql = "{$commentalias}.content";
if ($DB->get_dbfamily() === 'oracle') {
$contentfieldsql = $DB->sql_order_by_text($contentfieldsql, 1024);
}
$columns[] = (new column(
'content',
new lang_string('content'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_LONGTEXT)
->add_join($this->get_context_join())
->add_field($contentfieldsql, 'content')
->add_fields("{$commentalias}.format, {$commentalias}.contextid, " .
context_helper::get_preload_record_columns_sql($contextalias))
->add_callback(static function($content, stdClass $comment): string {
if ($content === null) {
return '';
}
context_helper::preload_from_record($comment);
$context = context::instance_by_id($comment->contextid);
return format_text($content, $comment->format, ['context' => $context]);
});
// Context.
$columns[] = (new column(
'context',
new lang_string('context'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_join($this->get_context_join())
->add_fields("{$commentalias}.contextid, " . context_helper::get_preload_record_columns_sql($contextalias))
// Sorting may not order alphabetically, but will at least group contexts together.
->set_is_sortable(true)
->set_is_deprecated('See \'context:name\' for replacement')
->add_callback(static function($contextid, stdClass $context): string {
if ($contextid === null) {
return '';
}
context_helper::preload_from_record($context);
return context::instance_by_id($contextid)->get_context_name();
});
// Context URL.
$columns[] = (new column(
'contexturl',
new lang_string('contexturl'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_join($this->get_context_join())
->add_fields("{$commentalias}.contextid, " . context_helper::get_preload_record_columns_sql($contextalias))
// Sorting may not order alphabetically, but will at least group contexts together.
->set_is_sortable(true)
->set_is_deprecated('See \'context:link\' for replacement')
->add_callback(static function($contextid, stdClass $context): string {
if ($contextid === null) {
return '';
}
context_helper::preload_from_record($context);
$context = context::instance_by_id($contextid);
return html_writer::link($context->get_url(), $context->get_context_name());
});
// Component.
$columns[] = (new column(
'component',
new lang_string('plugin'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$commentalias}.component")
->set_is_sortable(true);
// Area.
$columns[] = (new column(
'area',
new lang_string('pluginarea'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$commentalias}.commentarea")
->set_is_sortable(true);
// Item ID.
$columns[] = (new column(
'itemid',
new lang_string('pluginitemid'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_INTEGER)
->add_fields("{$commentalias}.itemid")
->set_is_sortable(true)
->set_disabled_aggregation_all();
// Time created.
$columns[] = (new column(
'timecreated',
new lang_string('timecreated', 'core_reportbuilder'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_fields("{$commentalias}.timecreated")
->set_is_sortable(true)
->add_callback([format::class, 'userdate']);
return $columns;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
global $DB;
$commentalias = $this->get_table_alias('comments');
// Content.
$filters[] = (new filter(
text::class,
'content',
new lang_string('content'),
$this->get_entity_name(),
$DB->sql_cast_to_char("{$commentalias}.content")
))
->add_joins($this->get_joins());
// Time created.
$filters[] = (new filter(
date::class,
'timecreated',
new lang_string('timecreated', 'core_reportbuilder'),
$this->get_entity_name(),
"{$commentalias}.timecreated"
))
->add_joins($this->get_joins())
->set_limited_operators([
date::DATE_ANY,
date::DATE_RANGE,
date::DATE_LAST,
date::DATE_CURRENT,
]);
return $filters;
}
/**
* Return syntax for joining on the context table
*
* @return string
*/
public function get_context_join(): string {
$commentalias = $this->get_table_alias('comments');
$contextalias = $this->get_table_alias('context');
return "LEFT JOIN {context} {$contextalias} ON {$contextalias}.id = {$commentalias}.contextid";
}
}
@@ -0,0 +1,124 @@
<?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/>.
declare(strict_types=1);
namespace core_comment\reportbuilder\local\systemreports;
use context_system;
use lang_string;
use moodle_url;
use pix_icon;
use stdClass;
use core\reportbuilder\local\entities\context;
use core_reportbuilder\system_report;
use core_reportbuilder\local\entities\user;
use core_reportbuilder\local\report\action;
use core_comment\reportbuilder\local\entities\comment;
/**
* Comments system report
*
* @package core_comment
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class comments extends system_report {
/**
* Initialise report, we need to set the main table, load our entities and set columns/filters
*/
protected function initialise(): void {
$commententity = new comment();
$commentalias = $commententity->get_table_alias('comments');
$this->set_main_table('comments', $commentalias);
$this->add_entity($commententity);
// Base fields required for action callbacks and checkbox toggle.
$this->add_base_fields("{$commentalias}.id");
$this->set_checkbox_toggleall(static function(stdClass $row): array {
return [$row->id, get_string('select')];
});
// Join the context entity.
$contextentity = (new context())
->set_table_alias('context', $commententity->get_table_alias('context'));
$this->add_entity($contextentity
->add_join($commententity->get_context_join())
);
// Join the user entity to the comment userid (author).
$userentity = new user();
$useralias = $userentity->get_table_alias('user');
$this->add_entity($userentity
->add_join("LEFT JOIN {user} {$useralias} ON {$useralias}.id = {$commentalias}.userid"));
$this->add_columns();
$this->add_filters();
$this->add_actions();
$this->set_downloadable(true, get_string('comments'));
}
/**
* Validates access to view this report
*
* @return bool
*/
protected function can_view(): bool {
return has_capability('moodle/comment:delete', context_system::instance());
}
/**
* Add columns to the report
*/
protected function add_columns(): void {
$this->add_columns_from_entities([
'user:fullnamewithlink',
'comment:content',
'context:link',
'comment:timecreated',
]);
// Default sorting.
$this->set_initial_sort_column('comment:timecreated', SORT_DESC);
}
/**
* Add filters to the report
*/
protected function add_filters(): void {
$this->add_filters_from_entities([
'user:fullname',
'comment:content',
'comment:timecreated',
]);
}
/**
* Add actions to report
*/
protected function add_actions(): void {
$this->add_action(new action(
new moodle_url('#'),
new pix_icon('t/delete', ''),
['data-action' => 'comment-delete', 'data-comment-id' => ':id', 'class' => 'text-danger'],
false,
new lang_string('delete')
));
}
}
+460
View File
@@ -0,0 +1,460 @@
// 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/>.
/**
* Comment Helper
* @author Dongsheng Cai <dongsheng@moodle.com>
*/
M.core_comment = {
/**
* Initialize commenting system
*/
init: function(Y, options) {
var CommentHelper = function(args) {
CommentHelper.superclass.constructor.apply(this, arguments);
};
CommentHelper.NAME = "COMMENT";
CommentHelper.ATTRS = {
options: {},
lang: {}
};
Y.extend(CommentHelper, Y.Base, {
api: M.cfg.wwwroot+'/comment/comment_ajax.php',
initializer: function(args) {
var scope = this;
this.client_id = args.client_id;
this.itemid = args.itemid;
this.commentarea = args.commentarea;
this.component = args.component;
this.courseid = args.courseid;
this.contextid = args.contextid;
this.autostart = (args.autostart);
// Fail fast if the comments element cannot be found, such as in embedded-type views where blocks may be loaded
// then discarded.
if (!Y.one('#comment-ctrl-'+this.client_id)) {
return;
}
// expand comments?
if (this.autostart) {
this.view(args.page);
}
// load comments
var handle = Y.one('#comment-link-'+this.client_id);
// hide toggle link
if (handle) {
if (args.notoggle) {
handle.setStyle('display', 'none');
}
handle.on('click', function(e) {
e.preventDefault();
this.view(0);
return false;
}, this);
// Also handle space/enter key.
handle.on('key', function(e) {
e.preventDefault();
this.view(0);
return false;
}, '13,32', this);
}
scope.toggle_textarea(false);
},
post: function() {
var ta = Y.one('#dlg-content-'+this.client_id);
var scope = this;
var value = ta.get('value');
if (value && value != M.util.get_string('addcomment', 'moodle')) {
ta.set('disabled', true);
ta.setStyles({
'backgroundImage': 'url(' + M.util.image_url('i/loading_small', 'core') + ')',
'backgroundRepeat': 'no-repeat',
'backgroundPosition': 'center center'
});
var params = {'content': value};
this.request({
action: 'add',
scope: scope,
params: params,
callback: async function(id, obj, args) {
var scope = args.scope;
var cid = scope.client_id;
var ta = Y.one('#dlg-content-'+cid);
ta.set('value', '');
ta.set('disabled', false);
ta.setStyle('backgroundImage', 'none');
scope.toggle_textarea(false);
var container = Y.one('#comment-list-'+cid);
var result = await scope.render([obj], true);
var newcomment = Y.Node.create(result.html);
container.appendChild(newcomment);
var ids = result.ids;
var linkTextCount = Y.one('#comment-link-text-' + cid + ' .comment-link-count');
if (linkTextCount) {
linkTextCount.set('innerHTML', obj.count);
}
for(var i in ids) {
var attributes = {
color: { to: '#06e' },
backgroundColor: { to: '#FFE390' }
};
var anim = new Y.YUI2.util.ColorAnim(ids[i], attributes);
anim.animate();
}
scope.register_pagination();
scope.register_delete_buttons();
}
}, true);
} else {
var attributes = {
backgroundColor: { from: '#FFE390', to:'#FFFFFF' }
};
var anim = new Y.YUI2.util.ColorAnim('dlg-content-'+cid, attributes);
anim.animate();
}
},
request: function(args, noloading) {
var params = {};
var scope = this;
if (args['scope']) {
scope = args['scope'];
}
//params['page'] = args.page?args.page:'';
// the form element only accept certain file types
params['sesskey'] = M.cfg.sesskey;
params['action'] = args.action?args.action:'';
params['client_id'] = this.client_id;
params['itemid'] = this.itemid;
params['area'] = this.commentarea;
params['courseid'] = this.courseid;
params['contextid'] = this.contextid;
params['component'] = this.component;
if (args['params']) {
for (i in args['params']) {
params[i] = args['params'][i];
}
}
var cfg = {
method: 'POST',
on: {
complete: function(id,o,p) {
if (!o) {
alert('IO FATAL');
return false;
}
var data = Y.JSON.parse(o.responseText);
if (data.error) {
if (data.error == 'require_login') {
args.callback(id,data,p);
return true;
}
alert(data.error);
return false;
} else {
args.callback(id,data,p);
return true;
}
}
},
arguments: {
scope: scope
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: build_querystring(params)
};
if (args.form) {
cfg.form = args.form;
}
Y.io(this.api, cfg);
if (!noloading) {
this.wait();
}
},
render: async function(list, newcmt) {
var ret = {};
ret.ids = [];
var template = Y.one('#cmt-tmpl');
var html = '';
for(var i in list) {
var htmlid = 'comment-'+list[i].id+'-'+this.client_id;
var val = template.get('innerHTML');
if (list[i].profileurl) {
val = val.replace('___name___', '<a href="'+list[i].profileurl+'">'+list[i].fullname+'</a>');
} else {
val = val.replace('___name___', list[i].fullname);
}
if (list[i].delete || newcmt) {
list[i].clientid = this.client_id;
list[i].content += await this.renderDeleteIcon(list[i]);
}
val = val.replace('___time___', list[i].time);
val = val.replace('___picture___', list[i].avatar);
val = val.replace('___content___', list[i].content);
val = '<li id="'+htmlid+'">'+val+'</li>';
ret.ids.push(htmlid);
html = (val+html);
}
ret.html = html;
return ret;
},
renderDeleteIcon: async function(list) {
return new Promise(function(resolve) {
require(['core/templates', 'core/str'], (Templates, Str) => {
return Str.get_string('deletecommentbyon', 'moodle', {
user: list.fullname,
time: list.time
}).then(function(deleteStr) {
return Templates.renderPix('t/delete', 'core', deleteStr).then(function(deleteIcon) {
var deleteDiv = document.createElement('div');
deleteDiv.className = 'comment-delete';
var deleteLink = document.createElement('a');
deleteLink.href = '#';
deleteLink.role = 'button';
deleteLink.title = deleteStr;
deleteLink.id = `comment-delete-${list.clientid}-${list.id}`;
deleteLink.innerHTML = deleteIcon;
deleteDiv.appendChild(deleteLink);
resolve(deleteDiv.outerHTML);
return true;
});
});
});
});
},
load: function(page) {
var scope = this;
var container = Y.one('#comment-ctrl-'+this.client_id);
var params = {
'action': 'get',
'page': page
};
this.request({
scope: scope,
params: params,
callback: async function(id, ret, args) {
var linkTextCount = Y.one('#comment-link-text-' + scope.client_id + ' .comment-link-count');
if (linkTextCount) {
linkTextCount.set('innerHTML', ret.count);
}
var container = Y.one('#comment-list-'+scope.client_id);
var pagination = Y.one('#comment-pagination-'+scope.client_id);
if (ret.pagination) {
pagination.set('innerHTML', ret.pagination);
} else {
//empty paging bar
pagination.set('innerHTML', '');
}
if (ret.error == 'require_login') {
var result = {};
result.html = M.util.get_string('commentsrequirelogin', 'moodle');
} else {
var result = await scope.render(ret.list);
}
container.set('innerHTML', result.html);
var img = Y.one('#comment-img-'+scope.client_id);
if (img) {
img.set('src', M.util.image_url('t/expanded', 'core'));
}
args.scope.register_pagination();
args.scope.register_delete_buttons();
}
});
},
dodelete: function(id) { // note: delete is a reserved word in javascript, chrome and safary do not like it at all here!
var scope = this,
cid = scope.client_id,
params = {'commentid': id};
function remove_dom(type, anim, cmt) {
cmt.remove();
var linkTextCount = Y.one('#comment-link-text-' + cid + ' .comment-link-count'),
comments = Y.all('#comment-list-' + cid + ' li');
if (linkTextCount) {
linkTextCount.set('innerHTML', comments.size());
}
}
this.request({
action: 'delete',
scope: scope,
params: params,
callback: function(id, resp, args) {
var htmlid= 'comment-'+resp.commentid+'-'+resp.client_id;
var attributes = {
width:{to:0},
height:{to:0}
};
var cmt = Y.one('#'+htmlid);
cmt.setStyle('overflow', 'hidden');
var anim = new Y.YUI2.util.Anim(htmlid, attributes, 1, Y.YUI2.util.Easing.easeOut);
anim.onComplete.subscribe(remove_dom, cmt, this);
anim.animate();
}
}, true);
},
register_actions: function() {
// add new comment
var action_btn = Y.one('#comment-action-post-'+this.client_id);
if (action_btn) {
action_btn.on('click', function(e) {
e.preventDefault();
this.post();
return false;
}, this);
}
// cancel comment box
var cancel = Y.one('#comment-action-cancel-'+this.client_id);
if (cancel) {
cancel.on('click', function(e) {
e.preventDefault();
this.view(0);
return false;
}, this);
}
},
register_delete_buttons: function() {
var scope = this;
// page buttons
Y.all('div.comment-delete a').each(
function(node, id) {
var theid = node.get('id');
var parseid = new RegExp("comment-delete-"+scope.client_id+"-(\\d+)", "i");
var commentid = theid.match(parseid);
if (!commentid) {
return;
}
if (commentid[1]) {
Y.Event.purgeElement('#'+theid, false, 'click');
}
node.on('click', function(e) {
e.preventDefault();
if (commentid[1]) {
scope.dodelete(commentid[1]);
}
});
// Also handle space/enter key.
node.on('key', function(e) {
e.preventDefault();
if (commentid[1]) {
scope.dodelete(commentid[1]);
}
}, '13,32');
// 13 and 32 are the keycodes for space and enter.
}
);
},
register_pagination: function() {
var scope = this;
// page buttons
Y.all('#comment-pagination-'+this.client_id+' a').each(
function(node, id) {
node.on('click', function(e, node) {
e.preventDefault();
var id = node.get('id');
var re = new RegExp("comment-page-"+this.client_id+"-(\\d+)", "i");
var result = id.match(re);
this.load(result[1]);
}, scope, node);
}
);
},
view: function(page) {
var commenttoggler = Y.one('#comment-link-' + this.client_id);
var container = Y.one('#comment-ctrl-'+this.client_id);
var ta = Y.one('#dlg-content-'+this.client_id);
var img = Y.one('#comment-img-'+this.client_id);
var d = container.getStyle('display');
if (d=='none'||d=='') {
// show
if (!this.autostart) {
this.load(page);
} else {
this.register_delete_buttons();
this.register_pagination();
}
container.setStyle('display', 'block');
if (img) {
img.set('src', M.util.image_url('t/expanded', 'core'));
}
if (commenttoggler) {
commenttoggler.setAttribute('aria-expanded', 'true');
}
} else {
// hide
container.setStyle('display', 'none');
var collapsedimage = 't/collapsed'; // ltr mode
if ( Y.one(document.body).hasClass('dir-rtl') ) {
collapsedimage = 't/collapsed_rtl';
} else {
collapsedimage = 't/collapsed';
}
if (img) {
img.set('src', M.util.image_url(collapsedimage, 'core'));
}
if (ta) {
ta.set('value','');
}
if (commenttoggler) {
commenttoggler.setAttribute('aria-expanded', 'false');
}
}
if (ta) {
//toggle_textarea.apply(ta, [false]);
//// reset textarea size
ta.on('focus', function() {
this.toggle_textarea(true);
}, this);
//ta.onkeypress = function() {
//if (this.scrollHeight > this.clientHeight && !window.opera)
//this.rows += 1;
//}
ta.on('blur', function() {
this.toggle_textarea(false);
}, this);
}
this.register_actions();
return false;
},
toggle_textarea: function(focus) {
var t = Y.one('#dlg-content-'+this.client_id);
if (!t) {
return false;
}
if (focus) {
if (t.get('value') == M.util.get_string('addcomment', 'moodle')) {
t.set('value', '');
t.setStyle('color', 'black');
}
}else{
if (t.get('value') == '') {
t.set('value', M.util.get_string('addcomment', 'moodle'));
t.setStyle('color','grey');
t.set('rows', 2);
}
}
},
wait: function() {
var container = Y.one('#comment-list-'+this.client_id);
container.set('innerHTML', '<div class="mdl-align"><img src="'+M.util.image_url('i/loading_small', 'core')+'" /></div>');
}
});
new CommentHelper(options);
}
};
+127
View File
@@ -0,0 +1,127 @@
<?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/>.
/*
* Handling all ajax request for comments API
*
* @package core
* @copyright 2010 Dongsheng Cai {@link http://dongsheng.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('AJAX_SCRIPT', true);
define('NO_DEBUG_DISPLAY', true);
require_once('../config.php');
require_once($CFG->dirroot . '/comment/lib.php');
$contextid = optional_param('contextid', SYSCONTEXTID, PARAM_INT);
$action = optional_param('action', '', PARAM_ALPHA);
if (empty($CFG->usecomments)) {
throw new comment_exception('commentsnotenabled', 'moodle');
}
list($context, $course, $cm) = get_context_info_array($contextid);
if ( $contextid == SYSCONTEXTID ) {
$course = $SITE;
}
$PAGE->set_url('/comment/comment_ajax.php');
// Allow anonymous user to view comments providing forcelogin now enabled
require_course_login($course, true, $cm);
$PAGE->set_context($context);
if (!empty($cm)) {
$PAGE->set_cm($cm, $course);
} else if (!empty($course)) {
$PAGE->set_course($course);
}
if (!confirm_sesskey()) {
$error = array('error'=>get_string('invalidsesskey', 'error'));
die(json_encode($error));
}
$client_id = required_param('client_id', PARAM_ALPHANUM);
$area = optional_param('area', '', PARAM_AREA);
$commentid = optional_param('commentid', -1, PARAM_INT);
$content = optional_param('content', '', PARAM_RAW);
$itemid = optional_param('itemid', '', PARAM_INT);
$page = optional_param('page', 0, PARAM_INT);
$component = optional_param('component', '', PARAM_COMPONENT);
// initilising comment object
$args = new stdClass;
$args->context = $context;
$args->course = $course;
$args->cm = $cm;
$args->area = $area;
$args->itemid = $itemid;
$args->client_id = $client_id;
$args->component = $component;
$manager = new comment($args);
echo $OUTPUT->header(); // send headers
// process ajax request
switch ($action) {
case 'add':
if ($manager->can_post()) {
$result = $manager->add($content);
if (!empty($result) && is_object($result)) {
$result->count = $manager->count();
$result->client_id = $client_id;
echo json_encode($result);
die();
}
}
break;
case 'delete':
$comment = $DB->get_record('comments', ['id' => $commentid]);
if ($manager->can_delete($comment)) {
if ($manager->delete($commentid)) {
$result = array(
'client_id' => $client_id,
'commentid' => $commentid
);
echo json_encode($result);
die();
}
}
break;
case 'get':
default:
if ($manager->can_view()) {
$comments = $manager->get_comments($page);
$result = array(
'list' => $comments,
'count' => $manager->count(),
'pagination' => $manager->get_pagination($page),
'client_id' => $client_id
);
echo json_encode($result);
die();
}
break;
}
if (!isloggedin()) {
// tell user to log in to view comments
echo json_encode(array('error'=>'require_login'));
}
// ignore request
die;
+70
View File
@@ -0,0 +1,70 @@
<?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/>.
/*
* Handling new comments from non-js comments interface
*
* @package core
* @copyright 2010 Dongsheng Cai {@link http://dongsheng.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../config.php');
require_once($CFG->dirroot . '/comment/lib.php');
if (empty($CFG->usecomments)) {
throw new comment_exception('commentsnotenabled', 'moodle');
}
$contextid = optional_param('contextid', SYSCONTEXTID, PARAM_INT);
list($context, $course, $cm) = get_context_info_array($contextid);
require_login($course, true, $cm);
require_sesskey();
if (!$course) {
// Require_login() does not set context if called without a $course, do it manually.
$PAGE->set_context($context);
}
$action = optional_param('action', '', PARAM_ALPHA);
$area = optional_param('area', '', PARAM_AREA);
$content = optional_param('content', '', PARAM_RAW);
$itemid = optional_param('itemid', '', PARAM_INT);
$returnurl = optional_param('returnurl', '/', PARAM_LOCALURL);
$component = optional_param('component', '', PARAM_COMPONENT);
// Currently this script can only add comments
if ($action !== 'add') {
redirect($returnurl);
}
$cmt = new stdClass;
$cmt->contextid = $contextid;
if ($course) {
$cmt->courseid = $course->id;
}
$cmt->cm = $cm;
$cmt->area = $area;
$cmt->itemid = $itemid;
$cmt->component = $component;
$comment = new comment($cmt);
if ($comment->can_post()) {
$cmt = $comment->add($content);
if (!empty($cmt) && is_object($cmt)) {
redirect($returnurl);
}
}
+54
View File
@@ -0,0 +1,54 @@
<?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/>.
/*
* Comments management interface
*
* @package core_comment
* @copyright 2010 Dongsheng Cai {@link http://dongsheng.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../config.php');
require_once($CFG->libdir.'/adminlib.php');
use core_reportbuilder\system_report_factory;
use core_comment\reportbuilder\local\systemreports\comments;
admin_externalpage_setup('comments', '', null, '', array('pagelayout'=>'report'));
$PAGE->requires->js_call_amd('core_comment/admin', 'init');
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('comments'));
$report = system_report_factory::create(comments::class, context_system::instance());
$report->set_default_per_page($CFG->commentsperpage);
echo $report->output();
// Render delete selected button.
if ($DB->record_exists('comments', [])) {
echo $OUTPUT->render(new single_button(
new moodle_url('#'),
get_string('deleteselected'),
'post',
single_button::BUTTON_PRIMARY,
['data-action' => 'comment-delete-selected']
));
}
echo $OUTPUT->footer();
+1182
View File
File diff suppressed because it is too large Load Diff
+283
View File
@@ -0,0 +1,283 @@
<?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/>.
/**
* comment_manager is helper class to manage moodle comments in admin page (Reports->Comments)
*
* @package core_comment
* @copyright 2010 Dongsheng Cai {@link http://dongsheng.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class comment_manager {
/** @var int The number of comments to display per page */
private $perpage;
/** @var stdClass Course data. */
protected $course;
/** @var context|bool To store the context object or false if not found. */
protected $context;
/** @var stdClass Course module. */
protected $cm;
/** @var course_modinfo Module information for course, or null if resetting. */
protected $modinfo;
/** @var string plugin type. */
protected $plugintype;
/** @var string plugin name. */
protected $pluginname;
/**
* Constructs the comment_manage object
*/
public function __construct() {
global $CFG;
$this->perpage = $CFG->commentsperpage;
}
/**
* Return comments by pages
*
* @global moodle_database $DB
* @param int $page
* @return array An array of comments
*/
function get_comments($page) {
global $DB;
if ($page == 0) {
$start = 0;
} else {
$start = $page * $this->perpage;
}
$comments = array();
$userfieldsapi = \core_user\fields::for_name();
$usernamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
$sql = "SELECT c.id, c.contextid, c.itemid, c.component, c.commentarea, c.userid, c.content, $usernamefields, c.timecreated
FROM {comments} c
JOIN {user} u
ON u.id=c.userid
ORDER BY c.timecreated ASC";
$rs = $DB->get_recordset_sql($sql, null, $start, $this->perpage);
$formatoptions = array('overflowdiv' => true, 'blanktarget' => true);
foreach ($rs as $item) {
// Set calculated fields
$item->fullname = fullname($item);
$item->time = userdate($item->timecreated);
$item->content = format_text($item->content, FORMAT_MOODLE, $formatoptions);
// Unset fields not related to the comment
foreach (\core_user\fields::get_name_fields() as $namefield) {
unset($item->$namefield);
}
unset($item->timecreated);
// Record the comment
$comments[] = $item;
}
$rs->close();
return $comments;
}
/**
* Records the course object
*
* @global moodle_page $PAGE
* @global moodle_database $DB
* @param int $courseid
*/
private function setup_course($courseid) {
global $PAGE, $DB;
if (!empty($this->course) && $this->course->id == $courseid) {
// already set, stop
return;
}
if ($courseid == $PAGE->course->id) {
$this->course = $PAGE->course;
} else if (!$this->course = $DB->get_record('course', array('id' => $courseid))) {
$this->course = null;
}
}
/**
* Sets up the module or block information for a comment
*
* @global moodle_database $DB
* @param stdClass $comment
* @return bool
*/
private function setup_plugin($comment) {
global $DB;
$this->context = context::instance_by_id($comment->contextid, IGNORE_MISSING);
if (!$this->context) {
return false;
}
switch ($this->context->contextlevel) {
case CONTEXT_BLOCK:
if ($block = $DB->get_record('block_instances', array('id' => $this->context->instanceid))) {
$this->plugintype = 'block';
$this->pluginname = $block->blockname;
} else {
return false;
}
break;
case CONTEXT_MODULE:
$this->plugintype = 'mod';
$this->cm = get_coursemodule_from_id('', $this->context->instanceid);
$this->setup_course($this->cm->course);
$this->modinfo = get_fast_modinfo($this->course);
$this->pluginname = $this->modinfo->cms[$this->cm->id]->modname;
break;
}
return true;
}
/**
* Print comments
* @param int $page
* @return bool return false if no comments available
*
* @deprecated since Moodle 4.2 - please do not use this function any more
*/
public function print_comments($page = 0) {
global $OUTPUT, $CFG, $OUTPUT, $DB;
debugging('The function ' . __FUNCTION__ . '() is deprecated, please do not use it any more. ' .
'See \'comments\' system report class for replacement', DEBUG_DEVELOPER);
$count = $DB->count_records('comments');
$comments = $this->get_comments($page);
if (count($comments) == 0) {
echo $OUTPUT->notification(get_string('nocomments', 'moodle'));
return false;
}
$table = new html_table();
$table->head = array (
html_writer::checkbox('selectall', '', false, get_string('selectall'), array('id' => 'comment_select_all',
'class' => 'mr-1')),
get_string('author', 'search'),
get_string('content'),
get_string('action')
);
$table->colclasses = array ('leftalign', 'leftalign', 'leftalign', 'leftalign');
$table->attributes = array('class'=>'admintable generaltable');
$table->id = 'commentstable';
$table->data = array();
$link = new moodle_url('/comment/index.php', array('action' => 'delete', 'sesskey' => sesskey()));
foreach ($comments as $c) {
$userdata = html_writer::link(new moodle_url('/user/profile.php', ['id' => $c->userid]), $c->fullname);
$this->setup_plugin($c);
if (!empty($this->plugintype)) {
$context_url = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'url', array($c));
}
$checkbox = html_writer::checkbox('comments', $c->id, false);
$action = html_writer::link(new moodle_url($link, array('commentid' => $c->id)), get_string('delete'));
if (!empty($context_url)) {
$action .= html_writer::empty_tag('br');
$action .= html_writer::link($context_url, get_string('commentincontext'), array('target'=>'_blank'));
}
$table->data[] = array($checkbox, $userdata, $c->content, $action);
}
echo html_writer::table($table);
echo $OUTPUT->paging_bar($count, $page, $this->perpage, $CFG->wwwroot.'/comment/index.php');
return true;
}
/**
* Delete a comment
*
* @param int $commentid
* @return bool
*/
public function delete_comment($commentid) {
global $DB;
if ($DB->record_exists('comments', array('id' => $commentid))) {
$DB->delete_records('comments', array('id' => $commentid));
return true;
}
return false;
}
/**
* Delete comments
*
* @param string $list A list of comment ids separated by hyphens
* @return bool
*/
public function delete_comments($list) {
global $DB;
$ids = explode('-', $list);
foreach ($ids as $id) {
$id = (int)$id;
if ($DB->record_exists('comments', array('id' => $id))) {
$DB->delete_records('comments', array('id' => $id));
}
}
return true;
}
/**
* Get comments created since a given time.
*
* @param stdClass $course course object
* @param stdClass $context context object
* @param string $component component name
* @param int $since the time to check
* @param stdClass|\cm_info|null $cm course module object
* @return array list of comments db records since the given timelimit
* @since Moodle 3.2
*/
public function get_component_comments_since($course, $context, $component, $since, $cm = null) {
global $DB;
$commentssince = array();
$where = 'contextid = ? AND component = ? AND timecreated > ?';
$comments = $DB->get_records_select('comments', $where, array($context->id, $component, $since));
// Check item by item if we have permissions.
$managersviewstatus = array();
foreach ($comments as $comment) {
// Check if the manager for the item is cached.
if (!isset($managersviewstatus[$comment->commentarea]) or
!isset($managersviewstatus[$comment->commentarea][$comment->itemid])) {
$args = new stdClass;
$args->area = $comment->commentarea;
$args->itemid = $comment->itemid;
$args->context = $context;
$args->course = $course;
$args->client_id = 0;
$args->component = $component;
if (!empty($cm)) {
$args->cm = $cm;
}
$manager = new comment($args);
$managersviewstatus[$comment->commentarea][$comment->itemid] = $manager->can_view();
}
if ($managersviewstatus[$comment->commentarea][$comment->itemid]) {
$commentssince[$comment->id] = $comment;
}
}
return $commentssince;
}
}
+49
View File
@@ -0,0 +1,49 @@
@core_comment @javascript
Feature: Manage comments made by users
As an admin
I want to view, filter and delete comments
Background:
Given I log in as "admin"
And the following "course" exists:
| fullname | Course 1 |
| shortname | CS101 |
And the following "core_comment > Comments" exist:
| contextlevel | reference | component | area | content |
| Course | CS101 | block_comments | page_comments | Uno |
| Course | CS101 | block_comments | page_comments | Dos |
| Course | CS101 | block_comments | page_comments | Tres |
Scenario: View and filter site comments
When I navigate to "Reports > Comments" in site administration
And the following should exist in the "reportbuilder-table" table:
| First name | Content | Context URL |
| Admin User | Uno | Course: Course 1 |
| Admin User | Dos | Course: Course 1 |
| Admin User | Tres | Course: Course 1 |
And I click on "Filters" "button"
And I set the following fields in the "Content" "core_reportbuilder > Filter" to these values:
| Content operator | Contains |
| Content value | Uno |
And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element"
Then I should see "Uno" in the "reportbuilder-table" "table"
And I should not see "Dos" in the "reportbuilder-table" "table"
And I should not see "Tres" in the "reportbuilder-table" "table"
Scenario: Delete single comment
When I navigate to "Reports > Comments" in site administration
And I press "Delete" action in the "Uno" report row
And I click on "Delete" "button" in the "Delete" "dialogue"
Then I should not see "Uno" in the "reportbuilder-table" "table"
And I should see "Dos" in the "reportbuilder-table" "table"
And I should see "Tres" in the "reportbuilder-table" "table"
Scenario: Delete multiple comments
When I navigate to "Reports > Comments" in site administration
And I click on "Select" "checkbox" in the "Uno" "table_row"
And I click on "Select" "checkbox" in the "Dos" "table_row"
And I press "Delete selected"
And I click on "Delete" "button" in the "Delete selected" "dialogue"
Then I should not see "Uno" in the "reportbuilder-table" "table"
And I should not see "Dos" in the "reportbuilder-table" "table"
And I should see "Tres" in the "reportbuilder-table" "table"
+161
View File
@@ -0,0 +1,161 @@
<?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/>.
namespace core_comment;
use comment;
use comment_exception;
use core_comment_external;
/**
* Tests for comments when the context is frozen.
*
* @package core_comment
* @copyright 2019 University of Nottingham
* @author Neill Magill <neill.magill@nottingham.ac.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class context_freeze_test extends \advanced_testcase {
/**
* Creates a comment by a student.
*
* Returns:
* - The comment object
* - The sudent that wrote the comment
* - The arguments used to create the comment
*
* @param \stdClass $course Moodle course from the datagenerator
* @return array
*/
protected function create_student_comment_and_freeze_course($course): array {
set_config('contextlocking', 1);
$context = \context_course::instance($course->id);
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$args = new \stdClass;
$args->context = $context;
$args->course = $course;
$args->area = 'page_comments';
$args->itemid = 0;
$args->component = 'block_comments';
$args->linktext = get_string('showcomments');
$args->notoggle = true;
$args->autostart = true;
$args->displaycancel = false;
// Create a comment by the student.
$this->setUser($student);
$comment = new comment($args);
$newcomment = $comment->add('New comment');
// Freeze the context.
$this->setAdminUser();
$context->set_locked(true);
return [$newcomment, $student, $args];
}
/**
* Test that a student cannot delete their own comments in frozen contexts via the external service.
*/
public function test_delete_student_external(): void {
global $CFG;
require_once($CFG->dirroot . '/comment/lib.php');
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
list($newcomment, $student, $args) = $this->create_student_comment_and_freeze_course($course);
// Check that a student cannot delete their own comment.
$this->setUser($student);
$studentcomment = new comment($args);
$this->assertFalse($studentcomment->can_delete($newcomment->id));
$this->assertFalse($studentcomment->can_post());
$this->expectException(comment_exception::class);
$this->expectExceptionMessage(get_string('nopermissiontodelentry', 'error'));
core_comment_external::delete_comments([$newcomment->id]);
}
/**
* Test that a student cannot delete their own comments in frozen contexts.
*/
public function test_delete_student(): void {
global $CFG;
require_once($CFG->dirroot . '/comment/lib.php');
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
list($newcomment, $student, $args) = $this->create_student_comment_and_freeze_course($course);
// Check that a student cannot delete their own comment.
$this->setUser($student);
$studentcomment = new comment($args);
$this->assertFalse($studentcomment->can_delete($newcomment->id));
$this->assertFalse($studentcomment->can_post());
$this->expectException(comment_exception::class);
$this->expectExceptionMessage(get_string('nopermissiontocomment', 'error'));
$studentcomment->delete($newcomment->id);
}
/**
* Test that an admin cannot delete comments in frozen contexts via the external service.
*/
public function test_delete_admin_external(): void {
global $CFG;
require_once($CFG->dirroot . '/comment/lib.php');
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
list($newcomment, $student, $args) = $this->create_student_comment_and_freeze_course($course);
// Check that the admin user cannot delete the comment.
$admincomment = new comment($args);
$this->assertFalse($admincomment->can_delete($newcomment->id));
$this->assertFalse($admincomment->can_post());
$this->expectException(comment_exception::class);
$this->expectExceptionMessage(get_string('nopermissiontodelentry', 'error'));
core_comment_external::delete_comments([$newcomment->id]);
}
/**
* Test that an admin cannot delete comments in frozen contexts.
*/
public function test_delete_admin(): void {
global $CFG;
require_once($CFG->dirroot . '/comment/lib.php');
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
list($newcomment, $student, $args) = $this->create_student_comment_and_freeze_course($course);
// Check that the admin user cannot delete the comment.
$admincomment = new comment($args);
$this->assertFalse($admincomment->can_delete($newcomment->id));
$this->assertFalse($admincomment->can_post());
$this->expectException(comment_exception::class);
$this->expectExceptionMessage(get_string('nopermissiontocomment', 'error'));
$admincomment->delete($newcomment->id);
}
}
+457
View File
@@ -0,0 +1,457 @@
<?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/>.
namespace core_comment;
use comment_exception;
use core_comment_external;
use core_external\external_api;
use externallib_advanced_testcase;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
/**
* External comment functions unit tests
*
* @package core_comment
* @category external
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.9
*/
class externallib_test extends externallib_advanced_testcase {
/**
* Tests set up
*/
protected function setUp(): void {
$this->resetAfterTest();
}
/**
* Helper used to set up a course, with a module, a teacher and two students.
*
* @return array the array of records corresponding to the course, teacher, and students.
*/
protected function setup_course_and_users_basic() {
global $CFG, $DB;
require_once($CFG->dirroot . '/comment/lib.php');
$CFG->usecomments = true;
$student1 = $this->getDataGenerator()->create_user();
$student2 = $this->getDataGenerator()->create_user();
$teacher1 = $this->getDataGenerator()->create_user();
$course1 = $this->getDataGenerator()->create_course(array('enablecomment' => 1));
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
$this->getDataGenerator()->enrol_user($student1->id, $course1->id, $studentrole->id);
$this->getDataGenerator()->enrol_user($student2->id, $course1->id, $studentrole->id);
$this->getDataGenerator()->enrol_user($teacher1->id, $course1->id, $teacherrole->id);
// Create a database module instance.
$record = new \stdClass();
$record->course = $course1->id;
$record->name = "Mod data test";
$record->intro = "Some intro of some sort";
$record->comments = 1;
$module1 = $this->getDataGenerator()->create_module('data', $record);
$field = data_get_field_new('text', $module1);
$fielddetail = new \stdClass();
$fielddetail->name = 'Name';
$fielddetail->description = 'Some name';
$field->define_field($fielddetail);
$field->insert_field();
$recordid = data_add_record($module1);
$datacontent = array();
$datacontent['fieldid'] = $field->field->id;
$datacontent['recordid'] = $recordid;
$datacontent['content'] = 'Asterix';
$DB->insert_record('data_content', $datacontent);
return [$module1, $recordid, $teacher1, $student1, $student2];
}
/**
* Test get_comments
*/
public function test_get_comments(): void {
global $CFG;
[$module1, $recordid, $teacher1, $student1, $student2] = $this->setup_course_and_users_basic();
// Create some comments as student 1.
$this->setUser($student1);
$inputdata = [
[
'contextlevel' => 'module',
'instanceid' => $module1->cmid,
'component' => 'mod_data',
'content' => 'abc',
'itemid' => $recordid,
'area' => 'database_entry'
],
[
'contextlevel' => 'module',
'instanceid' => $module1->cmid,
'component' => 'mod_data',
'content' => 'def',
'itemid' => $recordid,
'area' => 'database_entry'
]
];
$result = core_comment_external::add_comments($inputdata);
$result = external_api::clean_returnvalue(core_comment_external::add_comments_returns(), $result);
$ids = array_column($result, 'id');
// Verify we can get the comments.
$contextlevel = 'module';
$instanceid = $module1->cmid;
$component = 'mod_data';
$itemid = $recordid;
$area = 'database_entry';
$page = 0;
$result = core_comment_external::get_comments($contextlevel, $instanceid, $component, $itemid, $area, $page);
$result = external_api::clean_returnvalue(core_comment_external::get_comments_returns(), $result);
$this->assertCount(0, $result['warnings']);
$this->assertCount(2, $result['comments']);
$this->assertEquals(2, $result['count']);
$this->assertEquals(15, $result['perpage']);
$this->assertTrue($result['canpost']);
$this->assertEquals($student1->id, $result['comments'][0]['userid']);
$this->assertEquals($student1->id, $result['comments'][1]['userid']);
$this->assertEquals($ids[1], $result['comments'][0]['id']); // Default ordering newer first.
$this->assertEquals($ids[0], $result['comments'][1]['id']);
// Test sort direction and pagination.
$CFG->commentsperpage = 1;
$result = core_comment_external::get_comments($contextlevel, $instanceid, $component, $itemid, $area, $page, 'ASC');
$result = external_api::clean_returnvalue(core_comment_external::get_comments_returns(), $result);
$this->assertCount(0, $result['warnings']);
$this->assertCount(1, $result['comments']); // Only one per page.
$this->assertEquals(2, $result['count']);
$this->assertEquals($CFG->commentsperpage, $result['perpage']);
$this->assertEquals($ids[0], $result['comments'][0]['id']); // Comments order older first.
// Next page.
$result = core_comment_external::get_comments($contextlevel, $instanceid, $component, $itemid, $area, $page + 1, 'ASC');
$result = external_api::clean_returnvalue(core_comment_external::get_comments_returns(), $result);
$this->assertCount(0, $result['warnings']);
$this->assertCount(1, $result['comments']);
$this->assertEquals(2, $result['count']);
$this->assertEquals($CFG->commentsperpage, $result['perpage']);
$this->assertEquals($ids[1], $result['comments'][0]['id']);
}
/**
* Test add_comments not enabled site level
*/
public function test_add_comments_not_enabled_site_level(): void {
global $CFG;
[$module1, $recordid, $teacher1, $student1, $student2] = $this->setup_course_and_users_basic();
// Try to add a comment, as student 1, when comments is disabled at site level.
$this->setUser($student1);
$CFG->usecomments = false;
$this->expectException(comment_exception::class);
core_comment_external::add_comments([
[
'contextlevel' => 'module',
'instanceid' => $module1->cmid,
'component' => 'mod_data',
'content' => 'abc',
'itemid' => $recordid,
'area' => 'database_entry'
]
]);
}
/**
* Test add_comments not enabled module level
*/
public function test_add_comments_not_enabled_module_level(): void {
global $DB;
[$module1, $recordid, $teacher1, $student1, $student2] = $this->setup_course_and_users_basic();
// Disable comments for the module.
$DB->set_field('data', 'comments', 0, array('id' => $module1->id));
// Verify we can't add a comment.
$this->setUser($student1);
$this->expectException(comment_exception::class);
core_comment_external::add_comments([
[
'contextlevel' => 'module',
'instanceid' => $module1->cmid,
'component' => 'mod_data',
'content' => 'abc',
'itemid' => $recordid,
'area' => 'database_entry'
]
]);
}
/**
* Test add_comments
*/
public function test_add_comments_single(): void {
[$module1, $recordid, $teacher1, $student1, $student2] = $this->setup_course_and_users_basic();
// Add a comment as student 1.
$this->setUser($student1);
$result = core_comment_external::add_comments([
[
'contextlevel' => 'module',
'instanceid' => $module1->cmid,
'component' => 'mod_data',
'content' => 'abc',
'itemid' => $recordid,
'area' => 'database_entry'
]
]);
$result = external_api::clean_returnvalue(core_comment_external::add_comments_returns(), $result);
// Verify the result contains 1 result having the correct structure.
$this->assertCount(1, $result);
$expectedkeys = [
'id',
'content',
'format',
'timecreated',
'strftimeformat',
'profileurl',
'fullname',
'time',
'avatar',
'userid',
'delete',
];
foreach ($expectedkeys as $key) {
$this->assertArrayHasKey($key, $result[0]);
}
}
/**
* Test add_comments when one of the comments contains invalid data and cannot be created.
*
* This simply verifies that the entire operation fails.
*/
public function test_add_comments_multiple_contains_invalid(): void {
[$module1, $recordid, $teacher1, $student1, $student2] = $this->setup_course_and_users_basic();
// Try to create some comments as student 1, but provide a bad area for the second comment.
$this->setUser($student1);
$this->expectException(comment_exception::class);
core_comment_external::add_comments([
[
'contextlevel' => 'module',
'instanceid' => $module1->cmid,
'component' => 'mod_data',
'content' => 'abc',
'itemid' => $recordid,
'area' => 'database_entry'
],
[
'contextlevel' => 'module',
'instanceid' => $module1->cmid,
'component' => 'mod_data',
'content' => 'def',
'itemid' => $recordid,
'area' => 'badarea'
],
]);
}
/**
* Test add_comments when one of the comments contains invalid data and cannot be created.
*
* This simply verifies that the entire operation fails.
*/
public function test_add_comments_multiple_all_valid(): void {
[$module1, $recordid, $teacher1, $student1, $student2] = $this->setup_course_and_users_basic();
// Try to create some comments as student 1.
$this->setUser($student1);
$inputdata = [
[
'contextlevel' => 'module',
'instanceid' => $module1->cmid,
'component' => 'mod_data',
'content' => 'abc',
'itemid' => $recordid,
'area' => 'database_entry'
],
[
'contextlevel' => 'module',
'instanceid' => $module1->cmid,
'component' => 'mod_data',
'content' => 'def',
'itemid' => $recordid,
'area' => 'database_entry'
]
];
$result = core_comment_external::add_comments($inputdata);
$result = external_api::clean_returnvalue(core_comment_external::add_comments_returns(), $result);
// Two comments should have been created.
$this->assertCount(2, $result);
// The content for each comment should come back formatted.
foreach ($result as $index => $comment) {
$formatoptions = array('overflowdiv' => true, 'blanktarget' => true);
$expectedcontent = format_text($inputdata[$index]['content'], FORMAT_MOODLE, $formatoptions);
$this->assertEquals($expectedcontent, $comment['content']);
}
}
/**
* Test add_comments invalid area
*/
public function test_add_comments_invalid_area(): void {
[$module1, $recordid, $teacher1, $student1, $student2] = $this->setup_course_and_users_basic();
// Try to create a comment with an invalid area, verifying failure.
$this->setUser($student1);
$comments = [
[
'contextlevel' => 'module',
'instanceid' => $module1->cmid,
'component' => 'mod_data',
'content' => 'abc',
'itemid' => $recordid,
'area' => 'spaghetti'
]
];
$this->expectException(comment_exception::class);
core_comment_external::add_comments($comments);
}
/**
* Test delete_comment invalid comment.
*/
public function test_delete_comments_invalid_comment_id(): void {
[$module1, $recordid, $teacher1, $student1, $student2] = $this->setup_course_and_users_basic();
$this->setUser($student1);
$this->expectException(comment_exception::class);
core_comment_external::delete_comments([-1, 0]);
}
/**
* Test delete_comment own user.
*/
public function test_delete_comments_own_user(): void {
[$module1, $recordid, $teacher1, $student1, $student2] = $this->setup_course_and_users_basic();
// Create a few comments as student 1.
$this->setUser($student1);
$result = core_comment_external::add_comments([
[
'contextlevel' => 'module',
'instanceid' => $module1->cmid,
'component' => 'mod_data',
'content' => 'abc',
'itemid' => $recordid,
'area' => 'database_entry'
],
[
'contextlevel' => 'module',
'instanceid' => $module1->cmid,
'component' => 'mod_data',
'content' => 'def',
'itemid' => $recordid,
'area' => 'database_entry'
]
]);
$result = external_api::clean_returnvalue(core_comment_external::add_comments_returns(), $result);
// Delete those comments we just created.
$result = core_comment_external::delete_comments([
$result[0]['id'],
$result[1]['id']
]);
$result = external_api::clean_returnvalue(core_comment_external::delete_comments_returns(), $result);
$this->assertEquals([], $result);
}
/**
* Test delete_comment other student.
*/
public function test_delete_comment_other_student(): void {
[$module1, $recordid, $teacher1, $student1, $student2] = $this->setup_course_and_users_basic();
// Create a comment as the student.
$this->setUser($student1);
$result = core_comment_external::add_comments([
[
'contextlevel' => 'module',
'instanceid' => $module1->cmid,
'component' => 'mod_data',
'content' => 'abc',
'itemid' => $recordid,
'area' => 'database_entry'
]
]);
$result = external_api::clean_returnvalue(core_comment_external::add_comments_returns(), $result);
// Now, as student 2, try to delete the comment made by student 1. Verify we can't.
$this->setUser($student2);
$this->expectException(comment_exception::class);
core_comment_external::delete_comments([$result[0]['id']]);
}
/**
* Test delete_comment as teacher.
*/
public function test_delete_comments_as_teacher(): void {
[$module1, $recordid, $teacher1, $student1, $student2] = $this->setup_course_and_users_basic();
// Create a comment as the student.
$this->setUser($student1);
$result = core_comment_external::add_comments([
[
'contextlevel' => 'module',
'instanceid' => $module1->cmid,
'component' => 'mod_data',
'content' => 'abc',
'itemid' => $recordid,
'area' => 'database_entry'
]
]);
$result = external_api::clean_returnvalue(core_comment_external::add_comments_returns(), $result);
// Verify teachers can delete the comment.
$this->setUser($teacher1);
$result = core_comment_external::delete_comments([$result[0]['id']]);
$result = external_api::clean_returnvalue(core_comment_external::delete_comments_returns(), $result);
$this->assertEquals([], $result);
}
}
@@ -0,0 +1,61 @@
<?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/>.
declare(strict_types=1);
/**
* Behat data generator for comments
*
* @package core_comment
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_core_comment_generator extends behat_generator_base {
/**
* Get a list of the entities that can be created for this component
*
* @return array[]
*/
protected function get_creatable_entities(): array {
return [
'Comments' => [
'singular' => 'Comment',
'datagenerator' => 'comment',
'required' => [
'contextlevel',
'reference',
'component',
'area',
'content',
],
],
];
}
/**
* Pre-process comment, populate context property
*
* @param array $comment
* @return array
*/
protected function preprocess_comment(array $comment): array {
$comment['context'] = $this->get_context($comment['contextlevel'], $comment['reference']);
unset($comment['contextlevel'], $comment['reference']);
return $comment;
}
}
+52
View File
@@ -0,0 +1,52 @@
<?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/>.
declare(strict_types=1);
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/comment/lib.php");
/**
* Comment test generator
*
* @package core_comment
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_comment_generator extends component_generator_base {
/**
* Create comment
*
* @param array|stdClass $record
* @return comment
*/
public function create_comment($record): comment {
$record = (array) $record;
$content = (string) ($record['content'] ?? '');
unset($record['content']);
$comment = new comment((object) $record);
if ($content !== '') {
$comment->add($content);
}
return $comment;
}
}
+436
View File
@@ -0,0 +1,436 @@
<?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/>.
/**
* Privacy tests for core_comment.
*
* @package core_comment
* @category test
* @copyright 2018 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_comment\privacy;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/comment/locallib.php');
require_once($CFG->dirroot . '/comment/lib.php');
use core_privacy\local\request\approved_userlist;
use core_privacy\tests\provider_testcase;
use core_privacy\tests\request\approved_contextlist;
/**
* Unit tests for comment/classes/privacy/policy
*
* @copyright 2018 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends provider_testcase {
protected function setUp(): void {
$this->resetAfterTest();
}
/**
* Check the exporting of comments for a user id in a context.
*/
public function test_export_comments(): void {
$course = $this->getDataGenerator()->create_course();
$context = \context_course::instance($course->id);
$comment = $this->get_comment_object($context, $course);
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
// Add comments.
$comments = [];
$firstcomment = 'This is the first comment';
$this->setUser($user1);
$comment->add($firstcomment);
$comments[$user1->id] = $firstcomment;
$secondcomment = 'From the second user';
$this->setUser($user2);
$comment->add($secondcomment);
$comments[$user2->id] = $secondcomment;
// Retrieve comments only for user1.
$this->setUser($user1);
$writer = \core_privacy\local\request\writer::with_context($context);
provider::export_comments($context, 'block_comments', 'page_comments', 0, []);
$data = $writer->get_data([get_string('commentsubcontext', 'core_comment')]);
$exportedcomments = $data->comments;
// There is only one comment made by this user.
$this->assertCount(1, $exportedcomments);
$comment = reset($exportedcomments);
$this->assertEquals($comments[$user1->id], format_string($comment->content, FORMAT_PLAIN));
// Retrieve comments from any user.
provider::export_comments($context, 'block_comments', 'page_comments', 0, [], false);
$data = $writer->get_data([get_string('commentsubcontext', 'core_comment')]);
$exportedcomments = $data->comments;
// The whole conversation is two comments.
$this->assertCount(2, $exportedcomments);
foreach ($exportedcomments as $comment) {
$this->assertEquals($comments[$comment->userid], format_string($comment->content, FORMAT_PLAIN));
}
}
/**
* Tests the deletion of all comments in a context.
*/
public function test_delete_comments_for_all_users(): void {
global $DB;
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$coursecontext1 = \context_course::instance($course1->id);
$coursecontext2 = \context_course::instance($course2->id);
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$comment1 = $this->get_comment_object($coursecontext1, $course1);
$comment2 = $this->get_comment_object($coursecontext2, $course2);
$this->setUser($user1);
$comment1->add('First comment for user 1 on comment 1');
$comment2->add('First comment for user 1 on comment 2');
$this->setUser($user2);
$comment1->add('First comment for user 2 on comment 1');
$comment2->add('First comment for user 2 on comment 2');
// Because of the way things are set up with validation, creating an entry with the same context in a different component
// or comment area is a huge pain. We're just going to jam entries into the table instead.
$record = (object) [
'contextid' => $coursecontext1->id,
'component' => 'block_comments',
'commentarea' => 'other_comments',
'itemid' => 2,
'content' => 'Comment user 1 different comment area',
'format' => 0,
'userid' => $user1->id,
'timecreated' => time()
];
$DB->insert_record('comments', $record);
$record = (object) [
'contextid' => $coursecontext1->id,
'component' => 'tool_dataprivacy',
'commentarea' => 'page_comments',
'itemid' => 2,
'content' => 'Comment user 1 different component',
'format' => 0,
'userid' => $user1->id,
'timecreated' => time()
];
$DB->insert_record('comments', $record);
// Delete only for the first context. All records in the comments table for this context should be removed.
provider::delete_comments_for_all_users($coursecontext1, 'block_comments', 'page_comments', 0);
// No records left here.
$this->assertCount(0, $comment1->get_comments());
// All of the records are left intact here.
$this->assertCount(2, $comment2->get_comments());
// Check the other comment area.
$result = $DB->get_records('comments', ['commentarea' => 'other_comments']);
$this->assertCount(1, $result);
$data = array_shift($result);
$this->assertEquals('other_comments', $data->commentarea);
// Check the different component, same commentarea.
$result = $DB->get_records('comments', ['component' => 'tool_dataprivacy']);
$this->assertCount(1, $result);
$data = array_shift($result);
$this->assertEquals('tool_dataprivacy', $data->component);
}
/**
* Tests the deletion of all comments in a context.
*/
public function test_delete_comments_for_all_users_select(): void {
global $DB;
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$coursecontext1 = \context_course::instance($course1->id);
$coursecontext2 = \context_course::instance($course2->id);
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$comment1 = $this->get_comment_object($coursecontext1, $course1);
$comment2 = $this->get_comment_object($coursecontext2, $course2);
$this->setUser($user1);
$comment1->add('First comment for user 1 on comment 1');
$comment2->add('First comment for user 1 on comment 2');
$this->setUser($user2);
$comment1->add('First comment for user 2 on comment 1');
$comment2->add('First comment for user 2 on comment 2');
// Because of the way things are set up with validation, creating an entry with the same context in a different component
// or comment area is a huge pain. We're just going to jam entries into the table instead.
$record = (object) [
'contextid' => $coursecontext1->id,
'component' => 'block_comments',
'commentarea' => 'other_comments',
'itemid' => 2,
'content' => 'Comment user 1 different comment area',
'format' => 0,
'userid' => $user1->id,
'timecreated' => time()
];
$DB->insert_record('comments', $record);
$record = (object) [
'contextid' => $coursecontext1->id,
'component' => 'tool_dataprivacy',
'commentarea' => 'page_comments',
'itemid' => 2,
'content' => 'Comment user 1 different component',
'format' => 0,
'userid' => $user1->id,
'timecreated' => time()
];
$DB->insert_record('comments', $record);
// Delete only for the first context. All records in the comments table for this context should be removed.
list($sql, $params) = $DB->get_in_or_equal([0, 1, 2, 3], SQL_PARAMS_NAMED);
provider::delete_comments_for_all_users_select($coursecontext1,
'block_comments', 'page_comments', $sql, $params);
// No records left here.
$this->assertCount(0, $comment1->get_comments());
// All of the records are left intact here.
$this->assertCount(2, $comment2->get_comments());
// Check the other comment area.
$result = $DB->get_records('comments', ['commentarea' => 'other_comments']);
$this->assertCount(1, $result);
$data = array_shift($result);
$this->assertEquals('other_comments', $data->commentarea);
// Check the different component, same commentarea.
$result = $DB->get_records('comments', ['component' => 'tool_dataprivacy']);
$this->assertCount(1, $result);
$data = array_shift($result);
$this->assertEquals('tool_dataprivacy', $data->component);
}
/**
* Tests deletion of comments for a specified user and contexts.
*/
public function test_delete_comments_for_user(): void {
global $DB;
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$course3 = $this->getDataGenerator()->create_course();
$coursecontext1 = \context_course::instance($course1->id);
$coursecontext2 = \context_course::instance($course2->id);
$coursecontext3 = \context_course::instance($course3->id);
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$comment1 = $this->get_comment_object($coursecontext1, $course1);
$comment2 = $this->get_comment_object($coursecontext2, $course2);
$comment3 = $this->get_comment_object($coursecontext3, $course3);
$this->setUser($user1);
$comment1->add('First comment for user 1');
$comment2->add('User 1 comment in second comment');
$this->setUser($user2);
$comment2->add('User two replied in comment two');
$comment3->add('Comment three for user 2.');
// Because of the way things are set up with validation, creating an entry with the same context in a different component
// or comment area is a huge pain. We're just going to jam entries into the table instead.
$record = (object) [
'contextid' => $coursecontext1->id,
'component' => 'block_comments',
'commentarea' => 'other_comments',
'itemid' => 2,
'content' => 'Comment user 1 different comment area',
'format' => 0,
'userid' => $user1->id,
'timecreated' => time()
];
$DB->insert_record('comments', $record);
$record = (object) [
'contextid' => $coursecontext1->id,
'component' => 'tool_dataprivacy',
'commentarea' => 'page_comments',
'itemid' => 2,
'content' => 'Comment user 1 different component',
'format' => 0,
'userid' => $user1->id,
'timecreated' => time()
];
$DB->insert_record('comments', $record);
// Delete the comments for user 1.
$approvedcontextlist = new approved_contextlist($user1, 'block_comments',
[$coursecontext1->id, $coursecontext2->id]);
provider::delete_comments_for_user($approvedcontextlist, 'block_comments', 'page_comments', 0);
// No comments left in comments 1 as only user 1 commented there.
$this->assertCount(0, $comment1->get_comments());
// Only user 2 comments left in comments 2.
$comment2comments = $comment2->get_comments();
$this->assertCount(1, $comment2comments);
$data = array_shift($comment2comments);
$this->assertEquals($user2->id, $data->userid);
// Nothing changed here as user 1 did not leave a comment.
$comment3comments = $comment3->get_comments();
$this->assertCount(1, $comment3comments);
$data = array_shift($comment3comments);
$this->assertEquals($user2->id, $data->userid);
// Check the other comment area.
$result = $DB->get_records('comments', ['commentarea' => 'other_comments']);
$this->assertCount(1, $result);
$data = array_shift($result);
$this->assertEquals('other_comments', $data->commentarea);
// Check the different component, same commentarea.
$result = $DB->get_records('comments', ['component' => 'tool_dataprivacy']);
$this->assertCount(1, $result);
$data = array_shift($result);
$this->assertEquals('tool_dataprivacy', $data->component);
}
/**
* Tests deletion of comments for a specified userlist and context.
*/
public function test_delete_comments_for_users(): void {
global $DB;
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$course3 = $this->getDataGenerator()->create_course();
$coursecontext1 = \context_course::instance($course1->id);
$coursecontext2 = \context_course::instance($course2->id);
$coursecontext3 = \context_course::instance($course3->id);
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
$comment1 = $this->get_comment_object($coursecontext1, $course1);
$comment2 = $this->get_comment_object($coursecontext2, $course2);
$comment3 = $this->get_comment_object($coursecontext3, $course3);
$this->setUser($user1);
$comment1->add('First comment for user 1');
$comment2->add('User 1 comment in second comment');
$this->setUser($user2);
$comment2->add('User two replied in comment two');
$this->setUser($user3);
$comment2->add('User 3 also writing on comment 2, but will not be deleted');
$comment3->add('Only user 3 commenting in comment 3.');
// Because of the way things are set up with validation, creating an entry with the same context in a different component
// or comment area is a huge pain. We're just going to jam entries into the table instead.
$record = (object) [
'contextid' => $coursecontext1->id,
'component' => 'block_comments',
'commentarea' => 'other_comments',
'itemid' => 2,
'content' => 'Comment user 1 different comment area',
'format' => 0,
'userid' => $user1->id,
'timecreated' => time()
];
$DB->insert_record('comments', $record);
$record = (object) [
'contextid' => $coursecontext1->id,
'component' => 'tool_dataprivacy',
'commentarea' => 'page_comments',
'itemid' => 2,
'content' => 'Comment user 1 different component',
'format' => 0,
'userid' => $user1->id,
'timecreated' => time()
];
$DB->insert_record('comments', $record);
// Delete the comments for users 1 and 2 in all 3 contexts.
$approvedusers = [$user1->id, $user2->id];
$approveduserlist = new approved_userlist($coursecontext1, 'block_comments', $approvedusers);
provider::delete_comments_for_users($approveduserlist, 'block_comments', 'page_comments');
$approveduserlist = new approved_userlist($coursecontext2, 'block_comments', $approvedusers);
provider::delete_comments_for_users($approveduserlist, 'block_comments', 'page_comments');
$approveduserlist = new approved_userlist($coursecontext3, 'block_comments', $approvedusers);
provider::delete_comments_for_users($approveduserlist, 'block_comments', 'page_comments');
// No comments left in comments 1 as only user 1 commented there.
$this->assertCount(0, $comment1->get_comments());
// Only user 3's comment left in comments 2 as user 1 and 2 were approved for deletion.
$comment2comments = $comment2->get_comments();
$this->assertCount(1, $comment2comments);
$comment2comment = array_shift($comment2comments);
$this->assertEquals($user3->id, $comment2comment->userid);
// Nothing changed here as user 1 and 2 did not leave a comment.
$comment3comments = $comment3->get_comments();
$this->assertCount(1, $comment3comments);
$data = array_shift($comment3comments);
$this->assertEquals($user3->id, $data->userid);
// Check the other comment area.
$result = $DB->get_records('comments', ['commentarea' => 'other_comments']);
$this->assertCount(1, $result);
$data = array_shift($result);
$this->assertEquals('other_comments', $data->commentarea);
// Check the different component, same commentarea.
$result = $DB->get_records('comments', ['component' => 'tool_dataprivacy']);
$this->assertCount(1, $result);
$data = array_shift($result);
$this->assertEquals('tool_dataprivacy', $data->component);
}
/**
* Creates a comment object
*
* @param context $context A context object.
* @param stdClass $course A course object.
* @return comment The comment object.
*/
protected function get_comment_object($context, $course) {
// Comment on course page.
$args = new \stdClass;
$args->context = $context;
$args->course = $course;
$args->area = 'page_comments';
$args->itemid = 0;
$args->component = 'block_comments';
$comment = new \comment($args);
$comment->set_post_permission(true);
return $comment;
}
}
@@ -0,0 +1,258 @@
<?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/>.
declare(strict_types=1);
namespace core_comment\reportbuilder\datasource;
use context_course;
use core_comment_generator;
use core_reportbuilder_generator;
use core_reportbuilder_testcase;
use core_reportbuilder\local\filters\{date, select, text};
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for comments datasource
*
* @package core_comment
* @covers \core_comment\reportbuilder\datasource\comments
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class comments_test extends core_reportbuilder_testcase {
/**
* Test default datasource
*/
public function test_datasource_default(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$coursecontext = context_course::instance($course->id);
/** @var core_comment_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_comment');
// Our first user will create a single comment.
$userone = $this->getDataGenerator()->create_and_enrol($course, 'student', ['firstname' => 'Zoe']);
$this->setUser($userone);
$useronecomment = $generator->create_comment([
'context' => $coursecontext,
'component' => 'block_comments',
'area' => 'page_comments',
])->add('Cool');
// Our second user will create a couple of comments.
$usertwo = $this->getDataGenerator()->create_and_enrol($course, 'student', ['firstname' => 'Amy']);
$this->setUser($usertwo);
$usertwocommentfirst = $generator->create_comment([
'context' => $coursecontext,
'component' => 'block_comments',
'area' => 'page_comments',
])->add('Super');
$this->waitForSecond(); // For consistent ordering we need distinct time for second user comments.
$usertwocommentsecond = $generator->create_comment([
'context' => $coursecontext,
'component' => 'block_comments',
'area' => 'page_comments',
])->add('Awesome');
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Blogs', 'source' => comments::class, 'default' => 1]);
$content = $this->get_custom_report_content($report->get('id'));
// Default columns are user, context, content, time created. Sorted by user and time created.
$contextname = $coursecontext->get_context_name();
$this->assertEquals([
[fullname($usertwo), $contextname, format_text('Super'), userdate($usertwocommentfirst->timecreated)],
[fullname($usertwo), $contextname, format_text('Awesome'), userdate($usertwocommentsecond->timecreated)],
[fullname($userone), $contextname, format_text('Cool'), userdate($useronecomment->timecreated)],
], array_map('array_values', $content));
}
/**
* Test datasource columns that aren't added by default
*/
public function test_datasource_non_default_columns(): void {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$courseurl = course_get_url($course);
$coursecontext = context_course::instance($course->id);
/** @var core_comment_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_comment');
$generator->create_comment([
'context' => $coursecontext,
'component' => 'block_comments',
'area' => 'page_comments',
'content' => 'Cool',
]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Blogs', 'source' => comments::class, 'default' => 0]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:link']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'comment:component']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'comment:area']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'comment:itemid']);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertCount(1, $content);
$this->assertEquals([
"<a href=\"{$courseurl}\">{$coursecontext->get_context_name()}</a>",
'block_comments',
'page_comments',
0,
], array_values($content[0]));
}
/**
* Data provider for {@see test_datasource_filters}
*
* @return array[]
*/
public function datasource_filters_provider(): array {
return [
// Comment.
'Filter content' => ['comment:content', [
'comment:content_operator' => text::CONTAINS,
'comment:content_value' => 'Cool',
], true],
'Filter content (no match)' => ['comment:content', [
'comment:content_operator' => text::IS_EQUAL_TO,
'comment:content_value' => 'Beans',
], false],
'Filter time created' => ['comment:timecreated', [
'comment:timecreated_operator' => date::DATE_RANGE,
'comment:timecreated_from' => 1622502000,
], true],
'Filter time created (no match)' => ['comment:timecreated', [
'comment:timecreated_operator' => date::DATE_RANGE,
'comment:timecreated_to' => 1622502000,
], false],
// Context.
'Context level' => ['context:level', [
'context:level_operator' => select::EQUAL_TO,
'context:level_value' => CONTEXT_COURSE,
], true],
'Context level (no match)' => ['context:level', [
'context:level_operator' => select::EQUAL_TO,
'context:level_value' => CONTEXT_BLOCK,
], false],
// User.
'Filter user' => ['user:username', [
'user:username_operator' => text::IS_EQUAL_TO,
'user:username_value' => 'admin',
], true],
'Filter user (no match)' => ['user:username', [
'user:username_operator' => text::IS_EQUAL_TO,
'user:username_value' => 'lionel',
], false],
];
}
/**
* Test datasource filters
*
* @param string $filtername
* @param array $filtervalues
* @param bool $expectmatch
*
* @dataProvider datasource_filters_provider
*/
public function test_datasource_filters(
string $filtername,
array $filtervalues,
bool $expectmatch
): void {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$coursecontext = context_course::instance($course->id);
/** @var core_comment_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_comment');
$generator->create_comment([
'context' => $coursecontext,
'component' => 'block_comments',
'area' => 'page_comments',
'content' => 'Cool',
]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
// Create report containing single column, and given filter.
$report = $generator->create_report(['name' => 'Tasks', 'source' => comments::class, 'default' => 0]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'comment:component']);
// Add filter, set it's values.
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]);
$content = $this->get_custom_report_content($report->get('id'), 0, $filtervalues);
if ($expectmatch) {
$this->assertCount(1, $content);
$this->assertEquals('block_comments', reset($content[0]));
} else {
$this->assertEmpty($content);
}
}
/**
* Stress test datasource
*
* In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php
*/
public function test_stress_datasource(): void {
if (!PHPUNIT_LONGTEST) {
$this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
}
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$coursecontext = context_course::instance($course->id);
/** @var core_comment_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_comment');
$generator->create_comment([
'context' => $coursecontext,
'component' => 'block_comments',
'area' => 'page_comments',
'content' => 'Cool',
]);
$this->datasource_stress_test_columns(comments::class);
$this->datasource_stress_test_columns_aggregation(comments::class);
$this->datasource_stress_test_conditions(comments::class, 'comment:component');
}
}
+12
View File
@@ -0,0 +1,12 @@
This files describes API changes in /comment/* ,
information provided here is intended especially for developers.
=== 4.2 ===
* The comment manager `print_comments` method has been deprecated, in favour of new system report class for listing
comment data
=== 3.8 ===
* External function get_comments now returns the total count of comments and the number of comments per page.
It also has a new parameter to indicate the sorting direction (defaulted to DESC).
* The external function core_comment_get_comments now indicates if the current user can post comments in the requested
area.