Merge branch 'MDL-77558-501' of https://github.com/davewoloszyn/moodle into MOODLE_501_STABLE
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
define("mod_forum/grades/expandconversation",["exports","./grader/selectors","mod_forum/repository","core/notification","core/templates","core/modal_cancel","core/modal_events"],(function(_exports,ForumSelectors,_repository,_notification,_templates,_modal_cancel,ModalEvents){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}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}
|
||||
define("mod_forum/grades/expandconversation",["exports","./grader/selectors","mod_forum/repository","core/notification","core/templates","core/modal_cancel","core/modal_events","core/ajax"],(function(_exports,ForumSelectors,_repository,_notification,_templates,_modal_cancel,ModalEvents,_ajax){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}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}
|
||||
/**
|
||||
* This module handles the creation of a Modal that shows the user's post in context of the entire discussion.
|
||||
*
|
||||
* @module mod_forum/grades/expandconversation
|
||||
* @copyright 2019 Mathew May <mathew.solutions>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.registerEventListeners=void 0,ForumSelectors=_interopRequireWildcard(ForumSelectors),_repository=_interopRequireDefault(_repository),_templates=_interopRequireDefault(_templates),_modal_cancel=_interopRequireDefault(_modal_cancel),ModalEvents=_interopRequireWildcard(ModalEvents);_exports.registerEventListeners=rootNode=>{rootNode.addEventListener("click",(e=>{const rootNode=e.target.closest(ForumSelectors.expandConversation);if(rootNode){e.preventDefault();try{!async function(rootNode){let{focusOnClose:focusOnClose=null}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const postId=rootNode.dataset.postid,discussionId=rootNode.dataset.discussionid,discussionName=rootNode.dataset.name,experimentalDisplayMode="1"==rootNode.dataset.experimentalDisplayMode,[allPosts,modal]=await Promise.all([_repository.default.getDiscussionPosts(parseInt(discussionId)),_modal_cancel.default.create({title:discussionName,large:!0,removeOnClose:!0,returnElement:focusOnClose})]),postsById=new Map(allPosts.posts.map((post=>(post.readonly=!0,post.hasreplies=!1,post.replies=[],[post.id,post]))));let posts=[];allPosts.posts.forEach((post=>{if(post.parentid){const parent=postsById.get(post.parentid);parent?(post.parentauthorname=parent.author.fullname,parent.hasreplies=!0,parent.replies.push(post)):posts.push(post)}else posts.push(post)})),modal.getRoot().on(ModalEvents.bodyRendered,(()=>{const relevantPost=modal.getRoot()[0].querySelector("#p".concat(postId));relevantPost&&relevantPost.scrollIntoView({behavior:"smooth"})})),modal.show();const templatePromise=_templates.default.render("mod_forum/grades/grader/discussion/post_modal",{posts:posts,experimentaldisplaymode:experimentalDisplayMode});modal.setBody(templatePromise)}(rootNode,{focusOnClose:e.target})}catch(err){(0,_notification.exception)(err)}}}))}}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.registerEventListeners=void 0,ForumSelectors=_interopRequireWildcard(ForumSelectors),_repository=_interopRequireDefault(_repository),_templates=_interopRequireDefault(_templates),_modal_cancel=_interopRequireDefault(_modal_cancel),ModalEvents=_interopRequireWildcard(ModalEvents),_ajax=_interopRequireDefault(_ajax);_exports.registerEventListeners=rootNode=>{rootNode.addEventListener("click",(e=>{const rootNode=e.target.closest(ForumSelectors.expandConversation);if(rootNode){e.preventDefault();try{!async function(rootNode){let{focusOnClose:focusOnClose=null}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const postId=rootNode.dataset.postid,discussionId=rootNode.dataset.discussionid,discussionName=rootNode.dataset.name,experimentalDisplayMode="1"==rootNode.dataset.experimentalDisplayMode,[allPosts,modal]=await Promise.all([_repository.default.getDiscussionPosts(parseInt(discussionId)),_modal_cancel.default.create({title:discussionName,large:!0,removeOnClose:!0,returnElement:focusOnClose})]),postsById=new Map(allPosts.posts.map((post=>(post.readonly=!0,post.hasreplies=!1,post.replies=[],[post.id,post]))));let posts=[];allPosts.posts.forEach((post=>{if(post.parentid){const parent=postsById.get(post.parentid);parent?(post.parentauthorname=parent.author.fullname,parent.hasreplies=!0,parent.replies.push(post)):posts.push(post)}else posts.push(post)})),modal.getRoot().on(ModalEvents.bodyRendered,(()=>{const relevantPost=modal.getRoot()[0].querySelector("#p".concat(postId));relevantPost&&relevantPost.scrollIntoView({behavior:"smooth"})})),modal.show();const templatePromise=_templates.default.render("mod_forum/grades/grader/discussion/post_modal",{posts:posts,experimentaldisplaymode:experimentalDisplayMode});modal.setBody(templatePromise)}(rootNode,{focusOnClose:e.target})}catch(err){(0,_notification.exception)(err)}}}));document.querySelectorAll(ForumSelectors.showContent).forEach((element=>{element.addEventListener("click",(e=>{const target=e.currentTarget,postId=parseInt(target.dataset.parentPostid,10),discussionId=parseInt(target.dataset.parentDiscussionid,10);markPostsRead([postId],discussionId)}))}))};const markPostsRead=(postIds,discussionId)=>{_ajax.default.call([{methodname:"mod_forum_mark_posts_read",args:{postids:postIds,discussionid:discussionId}}])[0].catch(_notification.exception.exception)}}));
|
||||
|
||||
//# sourceMappingURL=expandconversation.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
|
||||
define("mod_forum/grades/grader/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={launch:'[data-grade-action="launch"]',gradableItem:"[data-gradable-itemtype]",gradableItems:{wholeForum:'[data-gradable-itemtype="forum"]'},expandConversation:'[data-action="view-context"]',posts:'[data-region="posts"]',viewGrade:'[data-grade-action="view"]'},_exports.default}));
|
||||
define("mod_forum/grades/grader/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={launch:'[data-grade-action="launch"]',gradableItem:"[data-gradable-itemtype]",gradableItems:{wholeForum:'[data-gradable-itemtype="forum"]'},expandConversation:'[data-action="view-context"]',posts:'[data-region="posts"]',viewGrade:'[data-grade-action="view"]',showContent:".show-content-button"},_exports.default}));
|
||||
|
||||
//# sourceMappingURL=selectors.min.js.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"selectors.min.js","sources":["../../../src/grades/grader/selectors.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 * This module will tie together all of the different calls the gradable module will make.\n *\n * @module mod_forum/grades/grader/selectors\n * @copyright 2019 Andrew Nicols <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n launch: '[data-grade-action=\"launch\"]',\n gradableItem: '[data-gradable-itemtype]',\n gradableItems: {\n wholeForum: '[data-gradable-itemtype=\"forum\"]',\n },\n expandConversation: '[data-action=\"view-context\"]',\n posts: '[data-region=\"posts\"]',\n viewGrade: '[data-grade-action=\"view\"]',\n};\n"],"names":["launch","gradableItem","gradableItems","wholeForum","expandConversation","posts","viewGrade"],"mappings":"mLAsBe,CACXA,OAAQ,+BACRC,aAAc,2BACdC,cAAe,CACXC,WAAY,oCAEhBC,mBAAoB,+BACpBC,MAAO,wBACPC,UAAW"}
|
||||
{"version":3,"file":"selectors.min.js","sources":["../../../src/grades/grader/selectors.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 * This module will tie together all of the different calls the gradable module will make.\n *\n * @module mod_forum/grades/grader/selectors\n * @copyright 2019 Andrew Nicols <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n launch: '[data-grade-action=\"launch\"]',\n gradableItem: '[data-gradable-itemtype]',\n gradableItems: {\n wholeForum: '[data-gradable-itemtype=\"forum\"]',\n },\n expandConversation: '[data-action=\"view-context\"]',\n posts: '[data-region=\"posts\"]',\n viewGrade: '[data-grade-action=\"view\"]',\n showContent: '.show-content-button',\n};\n"],"names":["launch","gradableItem","gradableItems","wholeForum","expandConversation","posts","viewGrade","showContent"],"mappings":"mLAsBe,CACXA,OAAQ,+BACRC,aAAc,2BACdC,cAAe,CACXC,WAAY,oCAEhBC,mBAAoB,+BACpBC,MAAO,wBACPC,UAAW,6BACXC,YAAa"}
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -26,6 +26,7 @@ import {exception as showException} from "core/notification";
|
||||
import Templates from 'core/templates';
|
||||
import Modal from 'core/modal_cancel';
|
||||
import * as ModalEvents from 'core/modal_events';
|
||||
import Ajax from 'core/ajax';
|
||||
|
||||
/**
|
||||
* Find the Node containing the gradable details from the provided node by searching up the tree.
|
||||
@@ -124,4 +125,31 @@ export const registerEventListeners = (rootNode) => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Each parent post can mark its own read state when it is clicked.
|
||||
const parentContent = document.querySelectorAll(ForumSelectors.showContent);
|
||||
parentContent.forEach(element => {
|
||||
element.addEventListener('click', (e) => {
|
||||
const target = e.currentTarget;
|
||||
const postId = parseInt(target.dataset.parentPostid, 10);
|
||||
const discussionId = parseInt(target.dataset.parentDiscussionid, 10);
|
||||
markPostsRead([postId], discussionId);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark posts as read.
|
||||
*
|
||||
* @param {number[]} postIds The posts to mark as read
|
||||
* @param {number} discussionId The discussion the posts belong to
|
||||
*/
|
||||
const markPostsRead = (postIds, discussionId) => {
|
||||
Ajax.call([{
|
||||
methodname: 'mod_forum_mark_posts_read',
|
||||
args: {
|
||||
postids: postIds,
|
||||
discussionid: discussionId
|
||||
},
|
||||
}])[0].catch(showException.exception);
|
||||
};
|
||||
|
||||
@@ -29,4 +29,5 @@ export default {
|
||||
expandConversation: '[data-action="view-context"]',
|
||||
posts: '[data-region="posts"]',
|
||||
viewGrade: '[data-grade-action="view"]',
|
||||
showContent: '.show-content-button',
|
||||
};
|
||||
|
||||
@@ -257,6 +257,8 @@ const registerEventListeners = (graderLayout, userPicker, saveGradeFunction, use
|
||||
e.preventDefault();
|
||||
|
||||
graderLayout.close();
|
||||
// Keep the underlying forum page up-to-date by forcing a refresh.
|
||||
window.location.reload();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -206,7 +206,6 @@ $functions = array(
|
||||
'ajax' => true,
|
||||
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
|
||||
],
|
||||
|
||||
'mod_forum_set_forum_tracking' => [
|
||||
'classname' => 'mod_forum\external\set_forum_tracking',
|
||||
'methodname' => 'execute',
|
||||
@@ -215,4 +214,14 @@ $functions = array(
|
||||
'ajax' => true,
|
||||
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
|
||||
],
|
||||
'mod_forum_mark_posts_read' => [
|
||||
'classname' => 'mod_forum_external',
|
||||
'methodname' => 'mark_posts_read',
|
||||
'classpath' => 'mod/forum/externallib.php',
|
||||
'description' => 'Mark forum posts as read.',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
'capabilities' => 'mod/forum:viewdiscussion',
|
||||
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
|
||||
],
|
||||
);
|
||||
|
||||
@@ -191,7 +191,7 @@ class mod_forum_external extends external_api {
|
||||
* @return array
|
||||
*/
|
||||
public static function get_discussion_posts(int $discussionid, ?string $sortby, ?string $sortdirection, bool $includeinlineattachments = false) {
|
||||
global $USER;
|
||||
global $USER, $CFG;
|
||||
// Validate the parameter.
|
||||
$params = self::validate_parameters(self::get_discussion_posts_parameters(), [
|
||||
'discussionid' => $discussionid,
|
||||
@@ -247,8 +247,20 @@ class mod_forum_external extends external_api {
|
||||
|
||||
$legacydatamapper = mod_forum\local\container::get_legacy_data_mapper_factory();
|
||||
|
||||
// Export the posts for the template before we mark them as read.
|
||||
// This allows the template to indicate which are the new/unread ones
|
||||
// whilst marking them as read right here and now.
|
||||
$exportedposts = $postbuilder->build($USER, [$forum], [$discussion], $posts, $includeinlineattachments);
|
||||
|
||||
// Mark these posts as read.
|
||||
if (!empty($posts)) {
|
||||
if (!$CFG->forum_usermarksread && forum_tp_is_tracked($forum->get_id())) {
|
||||
forum_tp_mark_posts_read($USER, array_keys($posts));
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'posts' => $postbuilder->build($USER, [$forum], [$discussion], $posts, $includeinlineattachments),
|
||||
'posts' => $exportedposts,
|
||||
'forumid' => $discussion->get_forum_id(),
|
||||
'courseid' => $discussion->get_course_id(),
|
||||
'ratinginfo' => \core_rating\external\util::get_rating_info(
|
||||
@@ -1720,7 +1732,7 @@ class mod_forum_external extends external_api {
|
||||
* @return array
|
||||
*/
|
||||
public static function get_discussion_posts_by_userid(int $userid, int $cmid, ?string $sortby, ?string $sortdirection) {
|
||||
global $USER, $DB;
|
||||
global $USER, $DB, $CFG;
|
||||
// Validate the parameter.
|
||||
$params = self::validate_parameters(self::get_discussion_posts_by_userid_parameters(), [
|
||||
'userid' => $userid,
|
||||
@@ -1817,6 +1829,13 @@ class mod_forum_external extends external_api {
|
||||
'parentposts' => $parentposts,
|
||||
],
|
||||
];
|
||||
|
||||
// Mark these posts as read.
|
||||
if (!empty($posts)) {
|
||||
if (!$CFG->forum_usermarksread && forum_tp_is_tracked($forum->get_id())) {
|
||||
forum_tp_mark_posts_read($USER, array_keys($posts));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -2314,4 +2333,83 @@ class mod_forum_external extends external_api {
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of mark_posts_read parameters.
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function mark_posts_read_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'postids' => new external_multiple_structure(
|
||||
new external_value(PARAM_INT, 'The post ID to mark as read'),
|
||||
'List of post IDs to mark as read',
|
||||
),
|
||||
'discussionid' => new external_value(PARAM_INT, 'The discussion the posts belong to'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger the posts viewed event.
|
||||
*
|
||||
* This method differs from view_forum_discussion in that it is only
|
||||
* interested in marking certain posts as viewed, and not an
|
||||
* entire discussion.
|
||||
*
|
||||
* @param array $postids The post ids
|
||||
* @param int $discussionid The discussion id
|
||||
* @return bool True on success
|
||||
*/
|
||||
public static function mark_posts_read(array $postids, int $discussionid): bool {
|
||||
global $CFG, $DB, $USER;
|
||||
require_once($CFG->dirroot . "/mod/forum/lib.php");
|
||||
|
||||
$params = self::validate_parameters(
|
||||
self::mark_posts_read_parameters(),
|
||||
[
|
||||
'postids' => $postids,
|
||||
'discussionid' => $discussionid,
|
||||
]
|
||||
);
|
||||
|
||||
// Check permissions.
|
||||
$discussion = $DB->get_record('forum_discussions', ['id' => $discussionid], '*', MUST_EXIST);
|
||||
$cm = get_coursemodule_from_instance('forum', $discussion->forum, $discussion->course, false, MUST_EXIST);
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
self::validate_context($modcontext);
|
||||
|
||||
require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
|
||||
|
||||
// Check the posts belong to the discussion.
|
||||
[$insql, $params] = $DB->get_in_or_equal($postids, SQL_PARAMS_NAMED);
|
||||
$params['discussionid'] = $discussionid;
|
||||
$validposts = $DB->get_records_select(
|
||||
'forum_posts',
|
||||
"id $insql AND discussion = :discussionid",
|
||||
$params,
|
||||
'',
|
||||
'id'
|
||||
);
|
||||
|
||||
// Keep only valid posts.
|
||||
$postids = array_intersect($postids, array_keys($validposts));
|
||||
if (empty($postids)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark as read if required.
|
||||
$forum = $DB->get_record('forum', ['id' => $discussion->forum], '*', MUST_EXIST);
|
||||
if (!$CFG->forum_usermarksread && forum_tp_is_tracked($forum)) {
|
||||
return forum_tp_mark_posts_read($USER, $postids);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the data returned from mark_posts_read.
|
||||
*/
|
||||
public static function mark_posts_read_returns(): external_value {
|
||||
return new external_value(PARAM_BOOL, 'Returns true on success');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
aria-expanded="false"
|
||||
aria-controls="parent-post-content-{{id}}"
|
||||
data-bs-toggle="collapse"
|
||||
data-parent-postid="{{id}}"
|
||||
data-parent-discussionid="{{discussionid}}"
|
||||
>
|
||||
{{#str}} viewparentpost, mod_forum {{/str}}
|
||||
</button>
|
||||
|
||||
@@ -6,16 +6,18 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email | trackforums |
|
||||
| student1 | Student | 1 | student1@example.com | 1 |
|
||||
| student2 | Student | 2 | student2@example.com | 0 |
|
||||
| username | firstname | lastname | email | trackforums |
|
||||
| student1 | Student | 1 | student1@example.com | 1 |
|
||||
| student2 | Student | 2 | student2@example.com | 0 |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com | 1 |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| student2 | C1 | student |
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| student2 | C1 | student |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
|
||||
Scenario: Tracking forum posts off
|
||||
Given the following "activity" exists:
|
||||
@@ -185,3 +187,44 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
|
||||
Then I should not see "1 unread post"
|
||||
And I follow "Test forum name"
|
||||
And I should not see "Track unread posts"
|
||||
|
||||
@javascript
|
||||
Scenario: Forum grader panel marks posts as read
|
||||
Given the following "activity" exists:
|
||||
| course | C1 |
|
||||
| activity | forum |
|
||||
| name | Test forum name |
|
||||
| idnumber | forum |
|
||||
| grade_forum | 100 |
|
||||
| scale | 100 |
|
||||
And the following "mod_forum > discussions" exist:
|
||||
| user | forum | name | subject | message |
|
||||
| student1 | forum | Discussion 1 | Discussion 1 | student1's topic |
|
||||
| student2 | forum | Discussion 2 | Discussion 2 | student2's topic |
|
||||
And the following "mod_forum > posts" exist:
|
||||
| user | parentsubject | subject | message |
|
||||
| student2 | Discussion 1 | Reply to discussion 1 | student2's reply |
|
||||
| student1 | Discussion 2 | Reply to discussion 2 | student1's reply |
|
||||
When I am on the "Course 1" course page logged in as teacher1
|
||||
# We have a discussion created by each student, and each has replied to the other.
|
||||
Then I should see "4 unread posts"
|
||||
And I am on the "Test forum name" "forum activity" page
|
||||
And I press "Grade users"
|
||||
And I press "Close grader"
|
||||
And I am on the "Course 1" course page
|
||||
# 4 posts minus the 2 viewed in the grader.
|
||||
And I should see "2 unread posts"
|
||||
And I am on the "Test forum name" "forum activity" page
|
||||
And I press "Grade users"
|
||||
# Let's look at a discussion in student1's grader that features a post from student2.
|
||||
And I press "View discussion"
|
||||
And I click on "Cancel" "button" in the "Discussion 1" "dialogue"
|
||||
And I am on the "Course 1" course page
|
||||
# Remaining 2 unread posts minus the 1 viewed in the discussion context.
|
||||
And I should see "1 unread post"
|
||||
And I am on the "Test forum name" "forum activity" page
|
||||
And I press "Grade users"
|
||||
# Let's look at the parent post that student1 replied to.
|
||||
And I press "View parent post"
|
||||
And I am on the "Course 1" course page
|
||||
And I should not see "1 unread post"
|
||||
|
||||
@@ -3224,4 +3224,54 @@ final class externallib_test extends \core_external\tests\externallib_testcase {
|
||||
'messageformat' => FORMAT_MOODLE,
|
||||
], $updatedpost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test marking individual posts as read.
|
||||
*/
|
||||
public function test_mark_posts_read(): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Setup test data.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
|
||||
$user = $this->getDataGenerator()->create_user(['trackforums' => 1]);
|
||||
$role = $DB->get_record('role', ['shortname' => 'student'], '*', MUST_EXIST);
|
||||
self::getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
|
||||
|
||||
// Add a discussion.
|
||||
$record = new \stdClass();
|
||||
$record->course = $course->id;
|
||||
$record->userid = $user->id;
|
||||
$record->forum = $forum->id;
|
||||
$discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
|
||||
$parentpost = $DB->get_record('forum_posts', ['discussion' => $discussion->id]);
|
||||
|
||||
// Generate some posts.
|
||||
$record = new \stdClass();
|
||||
$record->course = $course->id;
|
||||
$record->userid = $user->id;
|
||||
$record->forum = $forum->id;
|
||||
$record->discussion = $discussion->id;
|
||||
$record->parent = $parentpost->id;
|
||||
$post1 = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
|
||||
$post2 = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
|
||||
$post3 = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
|
||||
|
||||
// Track the forum.
|
||||
$this->setUser($user);
|
||||
forum_tp_start_tracking($forum->id, $user->id);
|
||||
|
||||
// There should be 4 unread posts (1 parent + 3 child posts).
|
||||
$unreadposts = forum_tp_get_course_unread_posts($user->id, $course->id);
|
||||
$this->assertEquals(4, $unreadposts[$forum->id]->unread);
|
||||
|
||||
// Marks some posts as read for this user.
|
||||
$result = mod_forum_external::mark_posts_read([$post1->id, $post2->id], $discussion->id);
|
||||
$this->assertTrue($result);
|
||||
|
||||
// There should be 2 unread posts left (1 parent + 1 child post).
|
||||
$unreadposts = forum_tp_get_course_unread_posts($user->id, $course->id);
|
||||
$this->assertEquals(2, $unreadposts[$forum->id]->unread);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2025100601; // The current module version (Date: YYYYMMDDXX).
|
||||
$plugin->version = 2025100602; // The current module version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2025092600; // Requires this Moodle version.
|
||||
$plugin->component = 'mod_forum'; // Full name of the plugin (used for diagnostics)
|
||||
|
||||
Reference in New Issue
Block a user