/mod/forum/lib.php
PHP | 7037 lines | 6600 code | 185 blank | 252 comment | 133 complexity | 4eae533791952e05c0740e5a66b1c09d MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0
Large files files are truncated, but you can click here to view the full file
- <?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/>.
- /**
- * @package mod_forum
- * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- defined('MOODLE_INTERNAL') || die();
- /** Include required files */
- require_once(__DIR__ . '/deprecatedlib.php');
- require_once($CFG->libdir.'/filelib.php');
- /// CONSTANTS ///////////////////////////////////////////////////////////
- define('FORUM_MODE_FLATOLDEST', 1);
- define('FORUM_MODE_FLATNEWEST', -1);
- define('FORUM_MODE_THREADED', 2);
- define('FORUM_MODE_NESTED', 3);
- define('FORUM_MODE_NESTED_V2', 4);
- define('FORUM_CHOOSESUBSCRIBE', 0);
- define('FORUM_FORCESUBSCRIBE', 1);
- define('FORUM_INITIALSUBSCRIBE', 2);
- define('FORUM_DISALLOWSUBSCRIBE',3);
- /**
- * FORUM_TRACKING_OFF - Tracking is not available for this forum.
- */
- define('FORUM_TRACKING_OFF', 0);
- /**
- * FORUM_TRACKING_OPTIONAL - Tracking is based on user preference.
- */
- define('FORUM_TRACKING_OPTIONAL', 1);
- /**
- * FORUM_TRACKING_FORCED - Tracking is on, regardless of user setting.
- * Treated as FORUM_TRACKING_OPTIONAL if $CFG->forum_allowforcedreadtracking is off.
- */
- define('FORUM_TRACKING_FORCED', 2);
- define('FORUM_MAILED_PENDING', 0);
- define('FORUM_MAILED_SUCCESS', 1);
- define('FORUM_MAILED_ERROR', 2);
- if (!defined('FORUM_CRON_USER_CACHE')) {
- /** Defines how many full user records are cached in forum cron. */
- define('FORUM_CRON_USER_CACHE', 5000);
- }
- /**
- * FORUM_POSTS_ALL_USER_GROUPS - All the posts in groups where the user is enrolled.
- */
- define('FORUM_POSTS_ALL_USER_GROUPS', -2);
- define('FORUM_DISCUSSION_PINNED', 1);
- define('FORUM_DISCUSSION_UNPINNED', 0);
- /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
- /**
- * Given an object containing all the necessary data,
- * (defined by the form in mod_form.php) this function
- * will create a new instance and return the id number
- * of the new instance.
- *
- * @param stdClass $forum add forum instance
- * @param mod_forum_mod_form $mform
- * @return int intance id
- */
- function forum_add_instance($forum, $mform = null) {
- global $CFG, $DB;
- require_once($CFG->dirroot.'/mod/forum/locallib.php');
- $forum->timemodified = time();
- if (empty($forum->assessed)) {
- $forum->assessed = 0;
- }
- if (empty($forum->ratingtime) or empty($forum->assessed)) {
- $forum->assesstimestart = 0;
- $forum->assesstimefinish = 0;
- }
- $forum->id = $DB->insert_record('forum', $forum);
- $modcontext = context_module::instance($forum->coursemodule);
- if ($forum->type == 'single') { // Create related discussion.
- $discussion = new stdClass();
- $discussion->course = $forum->course;
- $discussion->forum = $forum->id;
- $discussion->name = $forum->name;
- $discussion->assessed = $forum->assessed;
- $discussion->message = $forum->intro;
- $discussion->messageformat = $forum->introformat;
- $discussion->messagetrust = trusttext_trusted(context_course::instance($forum->course));
- $discussion->mailnow = false;
- $discussion->groupid = -1;
- $message = '';
- $discussion->id = forum_add_discussion($discussion, null, $message);
- if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
- // Ugly hack - we need to copy the files somehow.
- $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
- $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
- $options = array('subdirs'=>true); // Use the same options as intro field!
- $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
- $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
- }
- }
- forum_update_calendar($forum, $forum->coursemodule);
- forum_grade_item_update($forum);
- $completiontimeexpected = !empty($forum->completionexpected) ? $forum->completionexpected : null;
- \core_completion\api::update_completion_date_event($forum->coursemodule, 'forum', $forum->id, $completiontimeexpected);
- return $forum->id;
- }
- /**
- * Handle changes following the creation of a forum instance.
- * This function is typically called by the course_module_created observer.
- *
- * @param object $context the forum context
- * @param stdClass $forum The forum object
- * @return void
- */
- function forum_instance_created($context, $forum) {
- if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
- $users = \mod_forum\subscriptions::get_potential_subscribers($context, 0, 'u.id, u.email');
- foreach ($users as $user) {
- \mod_forum\subscriptions::subscribe_user($user->id, $forum, $context);
- }
- }
- }
- /**
- * Given an object containing all the necessary data,
- * (defined by the form in mod_form.php) this function
- * will update an existing instance with new data.
- *
- * @global object
- * @param object $forum forum instance (with magic quotes)
- * @return bool success
- */
- function forum_update_instance($forum, $mform) {
- global $CFG, $DB, $OUTPUT, $USER;
- require_once($CFG->dirroot.'/mod/forum/locallib.php');
- $forum->timemodified = time();
- $forum->id = $forum->instance;
- if (empty($forum->assessed)) {
- $forum->assessed = 0;
- }
- if (empty($forum->ratingtime) or empty($forum->assessed)) {
- $forum->assesstimestart = 0;
- $forum->assesstimefinish = 0;
- }
- $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
- // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
- // if scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
- // for count and sum aggregation types the grade we check to make sure they do not exceed the scale (i.e. max score) when calculating the grade
- $updategrades = false;
- if ($oldforum->assessed <> $forum->assessed) {
- // Whether this forum is rated.
- $updategrades = true;
- }
- if ($oldforum->scale <> $forum->scale) {
- // The scale currently in use.
- $updategrades = true;
- }
- if (empty($oldforum->grade_forum) || $oldforum->grade_forum <> $forum->grade_forum) {
- // The whole forum grading.
- $updategrades = true;
- }
- if ($updategrades) {
- forum_update_grades($forum); // Recalculate grades for the forum.
- }
- if ($forum->type == 'single') { // Update related discussion and post.
- $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
- if (!empty($discussions)) {
- if (count($discussions) > 1) {
- echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
- }
- $discussion = array_pop($discussions);
- } else {
- // try to recover by creating initial discussion - MDL-16262
- $discussion = new stdClass();
- $discussion->course = $forum->course;
- $discussion->forum = $forum->id;
- $discussion->name = $forum->name;
- $discussion->assessed = $forum->assessed;
- $discussion->message = $forum->intro;
- $discussion->messageformat = $forum->introformat;
- $discussion->messagetrust = true;
- $discussion->mailnow = false;
- $discussion->groupid = -1;
- $message = '';
- forum_add_discussion($discussion, null, $message);
- if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
- print_error('cannotadd', 'forum');
- }
- }
- if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
- print_error('cannotfindfirstpost', 'forum');
- }
- $cm = get_coursemodule_from_instance('forum', $forum->id);
- $modcontext = context_module::instance($cm->id, MUST_EXIST);
- $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
- $post->subject = $forum->name;
- $post->message = $forum->intro;
- $post->messageformat = $forum->introformat;
- $post->messagetrust = trusttext_trusted($modcontext);
- $post->modified = $forum->timemodified;
- $post->userid = $USER->id; // MDL-18599, so that current teacher can take ownership of activities.
- if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
- // Ugly hack - we need to copy the files somehow.
- $options = array('subdirs'=>true); // Use the same options as intro field!
- $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
- }
- \mod_forum\local\entities\post::add_message_counts($post);
- $DB->update_record('forum_posts', $post);
- $discussion->name = $forum->name;
- $DB->update_record('forum_discussions', $discussion);
- }
- $DB->update_record('forum', $forum);
- $modcontext = context_module::instance($forum->coursemodule);
- if (($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) && ($oldforum->forcesubscribe <> $forum->forcesubscribe)) {
- $users = \mod_forum\subscriptions::get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
- foreach ($users as $user) {
- \mod_forum\subscriptions::subscribe_user($user->id, $forum, $modcontext);
- }
- }
- forum_update_calendar($forum, $forum->coursemodule);
- forum_grade_item_update($forum);
- $completiontimeexpected = !empty($forum->completionexpected) ? $forum->completionexpected : null;
- \core_completion\api::update_completion_date_event($forum->coursemodule, 'forum', $forum->id, $completiontimeexpected);
- return true;
- }
- /**
- * Given an ID of an instance of this module,
- * this function will permanently delete the instance
- * and any data that depends on it.
- *
- * @global object
- * @param int $id forum instance id
- * @return bool success
- */
- function forum_delete_instance($id) {
- global $DB;
- if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
- return false;
- }
- if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
- return false;
- }
- if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
- return false;
- }
- $context = context_module::instance($cm->id);
- // now get rid of all files
- $fs = get_file_storage();
- $fs->delete_area_files($context->id);
- $result = true;
- \core_completion\api::update_completion_date_event($cm->id, 'forum', $forum->id, null);
- // Delete digest and subscription preferences.
- $DB->delete_records('forum_digests', array('forum' => $forum->id));
- $DB->delete_records('forum_subscriptions', array('forum'=>$forum->id));
- $DB->delete_records('forum_discussion_subs', array('forum' => $forum->id));
- if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
- foreach ($discussions as $discussion) {
- if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
- $result = false;
- }
- }
- }
- forum_tp_delete_read_records(-1, -1, -1, $forum->id);
- forum_grade_item_delete($forum);
- // We must delete the module record after we delete the grade item.
- if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
- $result = false;
- }
- return $result;
- }
- /**
- * Indicates API features that the forum supports.
- *
- * @uses FEATURE_GROUPS
- * @uses FEATURE_GROUPINGS
- * @uses FEATURE_MOD_INTRO
- * @uses FEATURE_COMPLETION_TRACKS_VIEWS
- * @uses FEATURE_COMPLETION_HAS_RULES
- * @uses FEATURE_GRADE_HAS_GRADE
- * @uses FEATURE_GRADE_OUTCOMES
- * @param string $feature
- * @return mixed True if yes (some features may use other values)
- */
- function forum_supports($feature) {
- switch($feature) {
- case FEATURE_GROUPS: return true;
- case FEATURE_GROUPINGS: return true;
- case FEATURE_MOD_INTRO: return true;
- case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
- case FEATURE_COMPLETION_HAS_RULES: return true;
- case FEATURE_GRADE_HAS_GRADE: return true;
- case FEATURE_GRADE_OUTCOMES: return true;
- case FEATURE_RATE: return true;
- case FEATURE_BACKUP_MOODLE2: return true;
- case FEATURE_SHOW_DESCRIPTION: return true;
- case FEATURE_PLAGIARISM: return true;
- case FEATURE_ADVANCED_GRADING: return true;
- default: return null;
- }
- }
- /**
- * Create a message-id string to use in the custom headers of forum notification emails
- *
- * message-id is used by email clients to identify emails and to nest conversations
- *
- * @param int $postid The ID of the forum post we are notifying the user about
- * @param int $usertoid The ID of the user being notified
- * @return string A unique message-id
- */
- function forum_get_email_message_id($postid, $usertoid) {
- return generate_email_messageid(hash('sha256', $postid . 'to' . $usertoid));
- }
- /**
- *
- * @param object $course
- * @param object $user
- * @param object $mod TODO this is not used in this function, refactor
- * @param object $forum
- * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
- */
- function forum_user_outline($course, $user, $mod, $forum) {
- global $CFG;
- require_once("$CFG->libdir/gradelib.php");
- $gradeinfo = '';
- $gradetime = 0;
- $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
- if (!empty($grades->items[0]->grades)) {
- // Item 0 is the rating.
- $grade = reset($grades->items[0]->grades);
- $gradetime = max($gradetime, grade_get_date_for_user_grade($grade, $user));
- if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
- $gradeinfo .= get_string('gradeforrating', 'forum', $grade) . html_writer::empty_tag('br');
- } else {
- $gradeinfo .= get_string('gradeforratinghidden', 'forum') . html_writer::empty_tag('br');
- }
- }
- // Item 1 is the whole-forum grade.
- if (!empty($grades->items[1]->grades)) {
- $grade = reset($grades->items[1]->grades);
- $gradetime = max($gradetime, grade_get_date_for_user_grade($grade, $user));
- if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
- $gradeinfo .= get_string('gradeforwholeforum', 'forum', $grade) . html_writer::empty_tag('br');
- } else {
- $gradeinfo .= get_string('gradeforwholeforumhidden', 'forum') . html_writer::empty_tag('br');
- }
- }
- $count = forum_count_user_posts($forum->id, $user->id);
- if ($count && $count->postcount > 0) {
- $info = get_string("numposts", "forum", $count->postcount);
- $time = $count->lastpost;
- if ($gradeinfo) {
- $info .= ', ' . $gradeinfo;
- $time = max($time, $gradetime);
- }
- return (object) [
- 'info' => $info,
- 'time' => $time,
- ];
- } else if ($gradeinfo) {
- return (object) [
- 'info' => $gradeinfo,
- 'time' => $gradetime,
- ];
- }
- return null;
- }
- /**
- * @global object
- * @global object
- * @param object $coure
- * @param object $user
- * @param object $mod
- * @param object $forum
- */
- function forum_user_complete($course, $user, $mod, $forum) {
- global $CFG, $USER;
- require_once("$CFG->libdir/gradelib.php");
- $getgradeinfo = function($grades, string $type) use ($course): string {
- global $OUTPUT;
- if (empty($grades)) {
- return '';
- }
- $result = '';
- $grade = reset($grades);
- if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
- $result .= $OUTPUT->container(get_string("gradefor{$type}", "forum", $grade));
- if ($grade->str_feedback) {
- $result .= $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
- }
- } else {
- $result .= $OUTPUT->container(get_string("gradefor{$type}hidden", "forum"));
- }
- return $result;
- };
- $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
- // Item 0 is the rating.
- if (!empty($grades->items[0]->grades)) {
- echo $getgradeinfo($grades->items[0]->grades, 'rating');
- }
- // Item 1 is the whole-forum grade.
- if (!empty($grades->items[1]->grades)) {
- echo $getgradeinfo($grades->items[1]->grades, 'wholeforum');
- }
- if ($posts = forum_get_user_posts($forum->id, $user->id)) {
- if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
- print_error('invalidcoursemodule');
- }
- $context = context_module::instance($cm->id);
- $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
- $posts = array_filter($posts, function($post) use ($discussions) {
- return isset($discussions[$post->discussion]);
- });
- $entityfactory = mod_forum\local\container::get_entity_factory();
- $rendererfactory = mod_forum\local\container::get_renderer_factory();
- $postrenderer = $rendererfactory->get_posts_renderer();
- echo $postrenderer->render(
- $USER,
- [$forum->id => $entityfactory->get_forum_from_stdclass($forum, $context, $cm, $course)],
- array_map(function($discussion) use ($entityfactory) {
- return $entityfactory->get_discussion_from_stdclass($discussion);
- }, $discussions),
- array_map(function($post) use ($entityfactory) {
- return $entityfactory->get_post_from_stdclass($post);
- }, $posts)
- );
- } else {
- echo "<p>".get_string("noposts", "forum")."</p>";
- }
- }
- /**
- * @deprecated since Moodle 3.3, when the block_course_overview block was removed.
- */
- function forum_filter_user_groups_discussions() {
- throw new coding_exception('forum_filter_user_groups_discussions() can not be used any more and is obsolete.');
- }
- /**
- * Returns whether the discussion group is visible by the current user or not.
- *
- * @since Moodle 2.8, 2.7.1, 2.6.4
- * @param cm_info $cm The discussion course module
- * @param int $discussiongroupid The discussion groupid
- * @return bool
- */
- function forum_is_user_group_discussion(cm_info $cm, $discussiongroupid) {
- if ($discussiongroupid == -1 || $cm->effectivegroupmode != SEPARATEGROUPS) {
- return true;
- }
- if (isguestuser()) {
- return false;
- }
- if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id)) ||
- in_array($discussiongroupid, $cm->get_modinfo()->get_groups($cm->groupingid))) {
- return true;
- }
- return false;
- }
- /**
- * @deprecated since Moodle 3.3, when the block_course_overview block was removed.
- */
- function forum_print_overview() {
- throw new coding_exception('forum_print_overview() can not be used any more and is obsolete.');
- }
- /**
- * Given a course and a date, prints a summary of all the new
- * messages posted in the course since that date
- *
- * @global object
- * @global object
- * @global object
- * @uses CONTEXT_MODULE
- * @uses VISIBLEGROUPS
- * @param object $course
- * @param bool $viewfullnames capability
- * @param int $timestart
- * @return bool success
- */
- function forum_print_recent_activity($course, $viewfullnames, $timestart) {
- global $USER, $DB, $OUTPUT;
- // do not use log table if possible, it may be huge and is expensive to join with other tables
- $userfieldsapi = \core_user\fields::for_userpic();
- $allnamefields = $userfieldsapi->get_sql('u', false, '', 'duserid', false)->selects;
- if (!$posts = $DB->get_records_sql("SELECT p.*,
- f.course, f.type AS forumtype, f.name AS forumname, f.intro, f.introformat, f.duedate,
- f.cutoffdate, f.assessed AS forumassessed, f.assesstimestart, f.assesstimefinish,
- f.scale, f.grade_forum, f.maxbytes, f.maxattachments, f.forcesubscribe,
- f.trackingtype, f.rsstype, f.rssarticles, f.timemodified, f.warnafter, f.blockafter,
- f.blockperiod, f.completiondiscussions, f.completionreplies, f.completionposts,
- f.displaywordcount, f.lockdiscussionafter, f.grade_forum_notify,
- d.name AS discussionname, d.firstpost, d.userid AS discussionstarter,
- d.assessed AS discussionassessed, d.timemodified, d.usermodified, d.forum, d.groupid,
- d.timestart, d.timeend, d.pinned, d.timelocked,
- $allnamefields
- FROM {forum_posts} p
- JOIN {forum_discussions} d ON d.id = p.discussion
- JOIN {forum} f ON f.id = d.forum
- JOIN {user} u ON u.id = p.userid
- WHERE p.created > ? AND f.course = ? AND p.deleted <> 1
- ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
- return false;
- }
- $modinfo = get_fast_modinfo($course);
- $strftimerecent = get_string('strftimerecent');
- $managerfactory = mod_forum\local\container::get_manager_factory();
- $entityfactory = mod_forum\local\container::get_entity_factory();
- $discussions = [];
- $capmanagers = [];
- $printposts = [];
- foreach ($posts as $post) {
- if (!isset($modinfo->instances['forum'][$post->forum])) {
- // not visible
- continue;
- }
- $cm = $modinfo->instances['forum'][$post->forum];
- if (!$cm->uservisible) {
- continue;
- }
- // Get the discussion. Cache if not yet available.
- if (!isset($discussions[$post->discussion])) {
- // Build the discussion record object from the post data.
- $discussionrecord = (object)[
- 'id' => $post->discussion,
- 'course' => $post->course,
- 'forum' => $post->forum,
- 'name' => $post->discussionname,
- 'firstpost' => $post->firstpost,
- 'userid' => $post->discussionstarter,
- 'groupid' => $post->groupid,
- 'assessed' => $post->discussionassessed,
- 'timemodified' => $post->timemodified,
- 'usermodified' => $post->usermodified,
- 'timestart' => $post->timestart,
- 'timeend' => $post->timeend,
- 'pinned' => $post->pinned,
- 'timelocked' => $post->timelocked
- ];
- // Build the discussion entity from the factory and cache it.
- $discussions[$post->discussion] = $entityfactory->get_discussion_from_stdclass($discussionrecord);
- }
- $discussionentity = $discussions[$post->discussion];
- // Get the capability manager. Cache if not yet available.
- if (!isset($capmanagers[$post->forum])) {
- $context = context_module::instance($cm->id);
- $coursemodule = $cm->get_course_module_record();
- // Build the forum record object from the post data.
- $forumrecord = (object)[
- 'id' => $post->forum,
- 'course' => $post->course,
- 'type' => $post->forumtype,
- 'name' => $post->forumname,
- 'intro' => $post->intro,
- 'introformat' => $post->introformat,
- 'duedate' => $post->duedate,
- 'cutoffdate' => $post->cutoffdate,
- 'assessed' => $post->forumassessed,
- 'assesstimestart' => $post->assesstimestart,
- 'assesstimefinish' => $post->assesstimefinish,
- 'scale' => $post->scale,
- 'grade_forum' => $post->grade_forum,
- 'maxbytes' => $post->maxbytes,
- 'maxattachments' => $post->maxattachments,
- 'forcesubscribe' => $post->forcesubscribe,
- 'trackingtype' => $post->trackingtype,
- 'rsstype' => $post->rsstype,
- 'rssarticles' => $post->rssarticles,
- 'timemodified' => $post->timemodified,
- 'warnafter' => $post->warnafter,
- 'blockafter' => $post->blockafter,
- 'blockperiod' => $post->blockperiod,
- 'completiondiscussions' => $post->completiondiscussions,
- 'completionreplies' => $post->completionreplies,
- 'completionposts' => $post->completionposts,
- 'displaywordcount' => $post->displaywordcount,
- 'lockdiscussionafter' => $post->lockdiscussionafter,
- 'grade_forum_notify' => $post->grade_forum_notify
- ];
- // Build the forum entity from the factory.
- $forumentity = $entityfactory->get_forum_from_stdclass($forumrecord, $context, $coursemodule, $course);
- // Get the capability manager of this forum and cache it.
- $capmanagers[$post->forum] = $managerfactory->get_capability_manager($forumentity);
- }
- $capabilitymanager = $capmanagers[$post->forum];
- // Get the post entity.
- $postentity = $entityfactory->get_post_from_stdclass($post);
- // Check if the user can view the post.
- if ($capabilitymanager->can_view_post($USER, $discussionentity, $postentity)) {
- $printposts[] = $post;
- }
- }
- unset($posts);
- if (!$printposts) {
- return false;
- }
- echo $OUTPUT->heading(get_string('newforumposts', 'forum') . ':', 6);
- $list = html_writer::start_tag('ul', ['class' => 'unlist']);
- foreach ($printposts as $post) {
- $subjectclass = empty($post->parent) ? ' bold' : '';
- $authorhidden = forum_is_author_hidden($post, (object) ['type' => $post->forumtype]);
- $list .= html_writer::start_tag('li');
- $list .= html_writer::start_div('head');
- $list .= html_writer::div(userdate_htmltime($post->modified, $strftimerecent), 'date');
- if (!$authorhidden) {
- $list .= html_writer::div(fullname($post, $viewfullnames), 'name');
- }
- $list .= html_writer::end_div(); // Head.
- $list .= html_writer::start_div('info' . $subjectclass);
- $discussionurl = new moodle_url('/mod/forum/discuss.php', ['d' => $post->discussion]);
- if (!empty($post->parent)) {
- $discussionurl->param('parent', $post->parent);
- $discussionurl->set_anchor('p'. $post->id);
- }
- $post->subject = break_up_long_words(format_string($post->subject, true));
- $list .= html_writer::link($discussionurl, $post->subject, ['rel' => 'bookmark']);
- $list .= html_writer::end_div(); // Info.
- $list .= html_writer::end_tag('li');
- }
- $list .= html_writer::end_tag('ul');
- echo $list;
- return true;
- }
- /**
- * Update activity grades.
- *
- * @param object $forum
- * @param int $userid specific user only, 0 means all
- */
- function forum_update_grades($forum, $userid = 0): void {
- global $CFG, $DB;
- require_once($CFG->libdir.'/gradelib.php');
- $ratings = null;
- if ($forum->assessed) {
- require_once($CFG->dirroot.'/rating/lib.php');
- $cm = get_coursemodule_from_instance('forum', $forum->id);
- $rm = new rating_manager();
- $ratings = $rm->get_user_grades((object) [
- 'component' => 'mod_forum',
- 'ratingarea' => 'post',
- 'contextid' => \context_module::instance($cm->id)->id,
- 'modulename' => 'forum',
- 'moduleid ' => $forum->id,
- 'userid' => $userid,
- 'aggregationmethod' => $forum->assessed,
- 'scaleid' => $forum->scale,
- 'itemtable' => 'forum_posts',
- 'itemtableusercolumn' => 'userid',
- ]);
- }
- $forumgrades = null;
- if ($forum->grade_forum) {
- $sql = <<<EOF
- SELECT
- g.userid,
- 0 as datesubmitted,
- g.grade as rawgrade,
- g.timemodified as dategraded
- FROM {forum} f
- JOIN {forum_grades} g ON g.forum = f.id
- WHERE f.id = :forumid
- EOF;
- $params = [
- 'forumid' => $forum->id,
- ];
- if ($userid) {
- $sql .= " AND g.userid = :userid";
- $params['userid'] = $userid;
- }
- $forumgrades = [];
- if ($grades = $DB->get_recordset_sql($sql, $params)) {
- foreach ($grades as $userid => $grade) {
- if ($grade->rawgrade != -1) {
- $forumgrades[$userid] = $grade;
- }
- }
- $grades->close();
- }
- }
- forum_grade_item_update($forum, $ratings, $forumgrades);
- }
- /**
- * Create/update grade items for given forum.
- *
- * @param stdClass $forum Forum object with extra cmidnumber
- * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
- */
- function forum_grade_item_update($forum, $ratings = null, $forumgrades = null): void {
- global $CFG;
- require_once("{$CFG->libdir}/gradelib.php");
- // Update the rating.
- $item = [
- 'itemname' => get_string('gradeitemnameforrating', 'forum', $forum),
- 'idnumber' => $forum->cmidnumber,
- ];
- if (!$forum->assessed || $forum->scale == 0) {
- $item['gradetype'] = GRADE_TYPE_NONE;
- } else if ($forum->scale > 0) {
- $item['gradetype'] = GRADE_TYPE_VALUE;
- $item['grademax'] = $forum->scale;
- $item['grademin'] = 0;
- } else if ($forum->scale < 0) {
- $item['gradetype'] = GRADE_TYPE_SCALE;
- $item['scaleid'] = -$forum->scale;
- }
- if ($ratings === 'reset') {
- $item['reset'] = true;
- $ratings = null;
- }
- // Itemnumber 0 is the rating.
- grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $ratings, $item);
- // Whole forum grade.
- $item = [
- 'itemname' => get_string('gradeitemnameforwholeforum', 'forum', $forum),
- // Note: We do not need to store the idnumber here.
- ];
- if (!$forum->grade_forum) {
- $item['gradetype'] = GRADE_TYPE_NONE;
- } else if ($forum->grade_forum > 0) {
- $item['gradetype'] = GRADE_TYPE_VALUE;
- $item['grademax'] = $forum->grade_forum;
- $item['grademin'] = 0;
- } else if ($forum->grade_forum < 0) {
- $item['gradetype'] = GRADE_TYPE_SCALE;
- $item['scaleid'] = $forum->grade_forum * -1;
- }
- if ($forumgrades === 'reset') {
- $item['reset'] = true;
- $forumgrades = null;
- }
- // Itemnumber 1 is the whole forum grade.
- grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 1, $forumgrades, $item);
- }
- /**
- * Delete grade item for given forum.
- *
- * @param stdClass $forum Forum object
- */
- function forum_grade_item_delete($forum) {
- global $CFG;
- require_once($CFG->libdir.'/gradelib.php');
- grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, null, ['deleted' => 1]);
- grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 1, null, ['deleted' => 1]);
- }
- /**
- * Checks if scale is being used by any instance of forum.
- *
- * This is used to find out if scale used anywhere.
- *
- * @param $scaleid int
- * @return boolean True if the scale is used by any forum
- */
- function forum_scale_used_anywhere(int $scaleid): bool {
- global $DB;
- if (empty($scaleid)) {
- return false;
- }
- return $DB->record_exists_select('forum', "scale = ? and assessed > 0", [$scaleid * -1]);
- }
- // SQL FUNCTIONS ///////////////////////////////////////////////////////////
- /**
- * Gets a post with all info ready for forum_print_post
- * Most of these joins are just to get the forum id
- *
- * @global object
- * @global object
- * @param int $postid
- * @return mixed array of posts or false
- */
- function forum_get_post_full($postid) {
- global $CFG, $DB;
- $userfieldsapi = \core_user\fields::for_name();
- $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
- return $DB->get_record_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
- FROM {forum_posts} p
- JOIN {forum_discussions} d ON p.discussion = d.id
- LEFT JOIN {user} u ON p.userid = u.id
- WHERE p.id = ?", array($postid));
- }
- /**
- * Gets all posts in discussion including top parent.
- *
- * @param int $discussionid The Discussion to fetch.
- * @param string $sort The sorting to apply.
- * @param bool $tracking Whether the user tracks this forum.
- * @return array The posts in the discussion.
- */
- function forum_get_all_discussion_posts($discussionid, $sort, $tracking = false) {
- global $CFG, $DB, $USER;
- $tr_sel = "";
- $tr_join = "";
- $params = array();
- if ($tracking) {
- $tr_sel = ", fr.id AS postread";
- $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
- $params[] = $USER->id;
- }
- $userfieldsapi = \core_user\fields::for_name();
- $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
- $params[] = $discussionid;
- if (!$posts = $DB->get_records_sql("SELECT p.*, $allnames, u.email, u.picture, u.imagealt $tr_sel
- FROM {forum_posts} p
- LEFT JOIN {user} u ON p.userid = u.id
- $tr_join
- WHERE p.discussion = ?
- ORDER BY $sort", $params)) {
- return array();
- }
- foreach ($posts as $pid=>$p) {
- if ($tracking) {
- if (forum_tp_is_post_old($p)) {
- $posts[$pid]->postread = true;
- }
- }
- if (!$p->parent) {
- continue;
- }
- if (!isset($posts[$p->parent])) {
- continue; // parent does not exist??
- }
- if (!isset($posts[$p->parent]->children)) {
- $posts[$p->parent]->children = array();
- }
- $posts[$p->parent]->children[$pid] =& $posts[$pid];
- }
- // Start with the last child of the first post.
- $post = &$posts[reset($posts)->id];
- $lastpost = false;
- while (!$lastpost) {
- if (!isset($post->children)) {
- $post->lastpost = true;
- $lastpost = true;
- } else {
- // Go to the last child of this post.
- $post = &$posts[end($post->children)->id];
- }
- }
- return $posts;
- }
- /**
- * An array of forum objects that the user is allowed to read/search through.
- *
- * @global object
- * @global object
- * @global object
- * @param int $userid
- * @param int $courseid if 0, we look for forums throughout the whole site.
- * @return array of forum objects, or false if no matches
- * Forum objects have the following attributes:
- * id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
- * viewhiddentimedposts
- */
- function forum_get_readable_forums($userid, $courseid=0) {
- global $CFG, $DB, $USER;
- require_once($CFG->dirroot.'/course/lib.php');
- if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
- print_error('notinstalled', 'forum');
- }
- if ($courseid) {
- $courses = $DB->get_records('course', array('id' => $courseid));
- } else {
- // If no course is specified, then the user can see SITE + his courses.
- $courses1 = $DB->get_records('course', array('id' => SITEID));
- $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
- $courses = array_merge($courses1, $courses2);
- }
- if (!$courses) {
- return array();
- }
- $readableforums = array();
- foreach ($courses as $course) {
- $modinfo = get_fast_modinfo($course);
- if (empty($modinfo->instances['forum'])) {
- // hmm, no forums?
- continue;
- }
- $courseforums = $DB->get_records('forum', array('course' => $course->id));
- foreach ($modinfo->instances['forum'] as $forumid => $cm) {
- if (!$cm->uservisible or !isset($courseforums[$forumid])) {
- continue;
- }
- $context = context_module::instance($cm->id);
- $forum = $courseforums[$forumid];
- $forum->context = $context;
- $forum->cm = $cm;
- if (!has_capability('mod/forum:viewdiscussion', $context)) {
- continue;
- }
- /// group access
- if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
- $forum->onlygroups = $modinfo->get_groups($cm->groupingid);
- $forum->onlygroups[] = -1;
- }
- /// hidden timed discussions
- $forum->viewhiddentimedposts = true;
- if (!empty($CFG->forum_enabletimedposts)) {
- if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
- $forum->viewhiddentimedposts = false;
- }
- }
- /// qanda access
- if ($forum->type == 'qanda'
- && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
- // We need to check whether the user has posted in the qanda forum.
- $forum->onlydiscussions = array(); // Holds discussion ids for the discussions
- // the user is allowed to see in this forum.
- if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
- foreach ($discussionspostedin as $d) {
- $forum->onlydiscussions[] = $d->id;
- }
- }
- }
- $readableforums[$forum->id] = $forum;
- }
- unset($modinfo);
- } // End foreach $courses
- return $readableforums;
- }
- /**
- * Returns a list of posts found using an array of search terms.
- *
- * @global object
- * @global object
- * @global object
- * @param array $searchterms array of search terms, e.g. word +word -word
- * @param int $courseid if 0, we search through the whole site
- * @param int $limitfrom
- * @param int $limitnum
- * @param int &$totalcount
- * @param string $extrasql
- * @return array|bool Array of posts found or false
- */
- function forum_search_posts($searchterms, $courseid, $limitfrom, $limitnum,
- &$totalcount, $extrasql='') {
- global $CFG, $DB, $USER;
- require_once($CFG->libdir.'/searchlib.php');
- $forums = forum_get_readable_forums($USER->id, $courseid);
- if (count($forums) == 0) {
- $totalcount = 0;
- return false;
- }
- $now = floor(time() / 60) * 60; // DB Cache Friendly.
- $fullaccess = array();
- $where = array();
- $params = array();
- foreach ($forums as $forumid => $forum) {
- $select = array();
- if (!$forum->viewhiddentimedposts) {
- $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
- $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
- }
- $cm = $forum->cm;
- $context = $forum->context;
- if ($forum->type == 'qanda'
- && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
- if (!empty($forum->onlydiscussions)) {
- list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
- $params = array_merge($params, $discussionid_params);
- $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
- } else {
- $select[] = "p.parent = 0";
- }
- }
- if (!empty($forum->onlygroups)) {
- list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
- $params = array_merge($params, $groupid_params);
- $select[] = "d.groupid $groupid_sql";
- }
- if ($select) {
- $selects = implode(" AND ", $select);
- $where[] = "(d.forum = :forum{$forumid} AND $selects)";
- $params['forum'.$forumid] = $forumid;
- } else {
- $fullaccess[] = $forumid;
- }
- }
- if ($fullaccess) {
- list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
- $params = array_merge($params, $fullid_params);
- $where[] = "(d.forum $fullid_sql)";
- }
- $favjoin = "";
- if (in_array('starredonly:on', $searchterms)) {
- $usercontext = context_user::instance($USER->id);
- $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
- list($favjoin, $favparams) = $ufservice->get_join_sql_by_type('mod_forum', 'discussions',
- "favourited", "d.id");
- $searchterms = array_values(array_diff($searchterms, array('starredonly:on')));
- $params = array_merge($params, $favparams);
- $extrasql .= " AND favourited.itemid IS NOT NULL AND favourited.itemid != 0";
- }
- $selectdiscussion = "(".implode(" OR ", $where).")";
- $messagesearch = '';
- $searchstring = '';
- // Need to concat these back together for parser to work.
- foreach($searchterms as $searchterm){
- if ($searchstring != '') {
- $searchstring .= ' ';
- }
- $searchstring .= $searchterm;
- }
- // We need to allow quoted strings for the search. The quotes *should* be stripped
- // by the parser, but this should be examined carefully for security implications.
- $searchstring = str_replace("\\\"","\"",$searchstring);
- $parser = new search_parser();
- $lexer = new search_lexer($parser);
- if ($lexer->parse($searchstring)) {
- $parsearray = $parser->get_parsed_array();
- $tagjoins = '';
- $tagfields = [];
- $tagfieldcount = 0;
- if ($parsearray) {
- foreach ($parsearray as $token) {
- if ($token->getType() == TOKEN_TAGS) {
- for ($i = 0; $i <= substr_count($token->getValue(), ','); $i++) {
- // Queries can only have a limited number of joins so set a limit sensible users won't exceed.
- if ($tagfieldcount > 10) {
- continue;
- }
- $tagjoins .= " LEFT JOIN {tag_instance} ti_$tagfieldcount
- ON p.id = ti_$tagfieldcount.itemid
- AND ti_$tagfieldcount.component = 'mod_forum'
- AND ti_$tagfieldcount.itemtype = 'forum_posts'";
- $tagjoins .= " LEFT JOIN {tag} t_$tagfieldcount ON t_$tagfieldcount.id = ti_$tagfieldcount.tagid";
- $tagfields[] = "t_$tagfieldcount.rawname";
- $tagfieldcount++;
- }
- }
- }
- list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
- 'p.userid', 'u.id', 'u.firstname',
- 'u.lastname', 'p.modified', 'd.forum',
- $tagfields);
- $params = ($msparams ? array_merge($params, $msparams) : $params);
- }
- }
- $fromsql = "{forum_posts} p
- INNER JOIN {forum_discussions} d ON d.id = p.discussion
- INNER JOIN {user} u ON u.id = p.userid $tagjoins $favjoin";
- $selectsql = ($messagesearch ? $messagesearch . " AND " : "").
- " p.discussion = d.id
- AND p.userid = u.id
- AND $selectdiscussion
- $extrasql";
- $countsql = "SELECT COUNT(*)
- FROM $fromsql
- WHERE $selectsql";
- $userfieldsapi = \core_user\fields::for_name();
- $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
- $searchsql = "SELECT p.*,
- d.forum,
- $allnames,
- u.email,
- u.picture,
- u.imagealt
- FROM $fromsql
- WHERE $selectsql
- ORDER BY p.modified DESC";
- $totalcount = $DB->count_records_sql($countsql, $params);
- return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
- }
- /**
- * Get all the posts for a user in a forum suitable for forum_print_post
- *
- * @global object
- * @global object
- * @uses CONTEXT_MODULE
- * @return array
- */
- function forum_get_user_posts($forumid, $userid) {
- global $CFG, $DB;
- $timedsql = "";
- $params = array($forumid, $userid);
- if (!empty($CFG->forum_enabletimedposts)) {
- $cm = get_coursemodule_from_instance('forum', $forumid);
- if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
- $now = time();
- $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
- $params[] = $now;
- $params[] = $now;
- }
- }
- $userfieldsapi = \core_user\fields::for_name();
- $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
- return $DB->get_records_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
- FROM {forum} f
- JOIN {forum_discussions} d ON d.forum = f.id
- JOIN {forum_posts} p ON p.discussion = d.id
- JOIN {user} u ON u.id = p.userid
- WHERE f.id = ?
- AND p.userid = ?
- $timedsql
- ORDER BY p.modified ASC", $params);
- }
- /**
- * Get all the discussions user participated in
- *
- * @global object
- * @global object
- * @uses CONTEXT_MODULE
- * @param int $forumid
- * @param int $userid
- * @return array Array or false
- */
- function forum_get_user_involved_discussions($forumid, $userid) {
- global $CFG, $DB;
- $timedsql = "";
- $params = array($forumid, $userid);
- if (!empty($CFG->forum_enabletimedposts)) {
- $cm = get_coursemodule_from_instance('forum', $forumid);
- if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
- $now = time();
- $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
- $params[] = $now;
- $params[] = $now;
- }
- }
- return $DB->get_records_sql("SELECT DISTINCT d.*
- FROM {forum} f
- JOIN {forum_discussions} d ON d.forum = f.id
- JOIN {forum_posts} p ON p.discussion = d.id
- WHERE f.id = ?
- AND p.userid = ?
- $timedsql", $params);
- }
- /**
- * Get all the posts for a user in a forum suitable for forum_print_post
- *
- * @global object
- * @global object
- * @param int $forumid
- * @param int $userid
- * @return array of counts or false
- */
- function forum_count_user_posts($forumid, $userid) {
- global $CFG, $DB;
- $timedsql = "";
- $params = array($forumid, $userid);
- if (!empty($CFG->forum_enabletimedposts)) {
- $cm = get_coursemodule_from_instance('forum', $forumid);
- if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
- $now = time();
- $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
- $params[] = $now;
- $params[] = $now;
- }
- }
- return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
- FROM {forum} f
- JOIN {forum_discussions} d ON d.foru…
Large files files are truncated, but you can click here to view the full file