Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

8361 lines (7156 sloc) 307.783 kB
<?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
*/
/** Include required files */
require_once($CFG->libdir.'/filelib.php');
require_once($CFG->libdir.'/eventslib.php');
require_once($CFG->dirroot.'/user/selector/lib.php');
/// CONSTANTS ///////////////////////////////////////////////////////////
define('FORUM_MODE_FLATOLDEST', 1);
define('FORUM_MODE_FLATNEWEST', -1);
define('FORUM_MODE_THREADED', 2);
define('FORUM_MODE_NESTED', 3);
define('FORUM_CHOOSESUBSCRIBE', 0);
define('FORUM_FORCESUBSCRIBE', 1);
define('FORUM_INITIALSUBSCRIBE', 2);
define('FORUM_DISALLOWSUBSCRIBE',3);
define('FORUM_TRACKING_OFF', 0);
define('FORUM_TRACKING_OPTIONAL', 1);
define('FORUM_TRACKING_ON', 2);
/// 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.
*
* @global object
* @global object
* @param object $forum add forum instance (with magic quotes)
* @return int intance id
*/
function forum_add_instance($forum, $mform) {
global $CFG, $DB;
$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 = get_context_instance(CONTEXT_MODULE, $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(get_context_instance(CONTEXT_COURSE, $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);
$post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
$DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
}
}
if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
/// all users should be subscribed initially
/// Note: forum_get_potential_subscribers should take the forum context,
/// but that does not exist yet, becuase the forum is only half build at this
/// stage. However, because the forum is brand new, we know that there are
/// no role assignments or overrides in the forum context, so using the
/// course context gives the same list of users.
$users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
foreach ($users as $user) {
forum_subscribe($user->id, $forum->id);
}
}
forum_grade_item_update($forum);
return $forum->id;
}
/**
* 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 $DB, $OUTPUT, $USER;
$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
if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
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 = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
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);
$post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
}
$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
$DB->update_record('forum_posts', $post);
$discussion->name = $forum->name;
$DB->update_record('forum_discussions', $discussion);
}
$DB->update_record('forum', $forum);
forum_grade_item_update($forum);
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 = get_context_instance(CONTEXT_MODULE, $cm->id);
// now get rid of all files
$fs = get_file_storage();
$fs->delete_area_files($context->id);
$result = true;
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;
}
}
}
if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
$result = false;
}
forum_tp_delete_read_records(-1, -1, -1, $forum->id);
if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
$result = false;
}
forum_grade_item_delete($forum);
return $result;
}
/**
* Indicates API features that the forum supports.
*
* @uses FEATURE_GROUPS
* @uses FEATURE_GROUPINGS
* @uses FEATURE_GROUPMEMBERSONLY
* @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_GROUPMEMBERSONLY: 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;
default: return null;
}
}
/**
* Obtains the automatic completion state for this forum based on any conditions
* in forum settings.
*
* @global object
* @global object
* @param object $course Course
* @param object $cm Course-module
* @param int $userid User ID
* @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
* @return bool True if completed, false if not. (If no conditions, then return
* value depends on comparison type)
*/
function forum_get_completion_state($course,$cm,$userid,$type) {
global $CFG,$DB;
// Get forum details
if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
throw new Exception("Can't find forum {$cm->instance}");
}
$result=$type; // Default return value
$postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
$postcountsql="
SELECT
COUNT(1)
FROM
{forum_posts} fp
INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
WHERE
fp.userid=:userid AND fd.forum=:forumid";
if ($forum->completiondiscussions) {
$value = $forum->completiondiscussions <=
$DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
if ($type == COMPLETION_AND) {
$result = $result && $value;
} else {
$result = $result || $value;
}
}
if ($forum->completionreplies) {
$value = $forum->completionreplies <=
$DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
if ($type==COMPLETION_AND) {
$result = $result && $value;
} else {
$result = $result || $value;
}
}
if ($forum->completionposts) {
$value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
if ($type == COMPLETION_AND) {
$result = $result && $value;
} else {
$result = $result || $value;
}
}
return $result;
}
/**
* Function to be run periodically according to the moodle cron
* Finds all posts that have yet to be mailed out, and mails them
* out to all subscribers
*
* @global object
* @global object
* @global object
* @uses CONTEXT_MODULE
* @uses CONTEXT_COURSE
* @uses SITEID
* @uses FORMAT_PLAIN
* @return void
*/
function forum_cron() {
global $CFG, $USER, $DB;
$site = get_site();
// all users that are subscribed to any post that needs sending
$users = array();
// status arrays
$mailcount = array();
$errorcount = array();
// caches
$discussions = array();
$forums = array();
$courses = array();
$coursemodules = array();
$subscribedusers = array();
// Posts older than 2 days will not be mailed. This is to avoid the problem where
// cron has not been running for a long time, and then suddenly people are flooded
// with mail from the past few weeks or months
$timenow = time();
$endtime = $timenow - $CFG->maxeditingtime;
$starttime = $endtime - 48 * 3600; // Two days earlier
if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
// Mark them all now as being mailed. It's unlikely but possible there
// might be an error later so that a post is NOT actually mailed out,
// but since mail isn't crucial, we can accept this risk. Doing it now
// prevents the risk of duplicated mails, which is a worse problem.
if (!forum_mark_old_posts_as_mailed($endtime)) {
mtrace('Errors occurred while trying to mark some posts as being mailed.');
return false; // Don't continue trying to mail them, in case we are in a cron loop
}
// checking post validity, and adding users to loop through later
foreach ($posts as $pid => $post) {
$discussionid = $post->discussion;
if (!isset($discussions[$discussionid])) {
if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
$discussions[$discussionid] = $discussion;
} else {
mtrace('Could not find discussion '.$discussionid);
unset($posts[$pid]);
continue;
}
}
$forumid = $discussions[$discussionid]->forum;
if (!isset($forums[$forumid])) {
if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
$forums[$forumid] = $forum;
} else {
mtrace('Could not find forum '.$forumid);
unset($posts[$pid]);
continue;
}
}
$courseid = $forums[$forumid]->course;
if (!isset($courses[$courseid])) {
if ($course = $DB->get_record('course', array('id' => $courseid))) {
$courses[$courseid] = $course;
} else {
mtrace('Could not find course '.$courseid);
unset($posts[$pid]);
continue;
}
}
if (!isset($coursemodules[$forumid])) {
if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
$coursemodules[$forumid] = $cm;
} else {
mtrace('Could not find course module for forum '.$forumid);
unset($posts[$pid]);
continue;
}
}
// caching subscribed users of each forum
if (!isset($subscribedusers[$forumid])) {
$modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
foreach ($subusers as $postuser) {
unset($postuser->description); // not necessary
// this user is subscribed to this forum
$subscribedusers[$forumid][$postuser->id] = $postuser->id;
// this user is a user we have to process later
$users[$postuser->id] = $postuser;
}
unset($subusers); // release memory
}
}
$mailcount[$pid] = 0;
$errorcount[$pid] = 0;
}
}
if ($users && $posts) {
$urlinfo = parse_url($CFG->wwwroot);
$hostname = $urlinfo['host'];
foreach ($users as $userto) {
@set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
// set this so that the capabilities are cached, and environment matches receiving user
cron_setup_user($userto);
mtrace('Processing user '.$userto->id);
// init caches
$userto->viewfullnames = array();
$userto->canpost = array();
$userto->markposts = array();
// reset the caches
foreach ($coursemodules as $forumid=>$unused) {
$coursemodules[$forumid]->cache = new stdClass();
$coursemodules[$forumid]->cache->caps = array();
unset($coursemodules[$forumid]->uservisible);
}
foreach ($posts as $pid => $post) {
// Set up the environment for the post, discussion, forum, course
$discussion = $discussions[$post->discussion];
$forum = $forums[$discussion->forum];
$course = $courses[$forum->course];
$cm =& $coursemodules[$forum->id];
// Do some checks to see if we can bail out now
// Only active enrolled users are in the list of subscribers
if (!isset($subscribedusers[$forum->id][$userto->id])) {
continue; // user does not subscribe to this forum
}
// Don't send email if the forum is Q&A and the user has not posted
if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id)) {
mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
continue;
}
// Get info about the sending user
if (array_key_exists($post->userid, $users)) { // we might know him/her already
$userfrom = $users[$post->userid];
} else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
unset($userfrom->description); // not necessary
$users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
} else {
mtrace('Could not find user '.$post->userid);
continue;
}
//if we want to check that userto and userfrom are not the same person this is probably the spot to do it
// setup global $COURSE properly - needed for roles and languages
cron_setup_user($userto, $course);
// Fill caches
if (!isset($userto->viewfullnames[$forum->id])) {
$modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
$userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
}
if (!isset($userto->canpost[$discussion->id])) {
$modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
$userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
}
if (!isset($userfrom->groups[$forum->id])) {
if (!isset($userfrom->groups)) {
$userfrom->groups = array();
$users[$userfrom->id]->groups = array();
}
$userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
$users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
}
// Make sure groups allow this user to see this email
if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
if (!groups_group_exists($discussion->groupid)) { // Can't find group
continue; // Be safe and don't send it to anyone
}
if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
// do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
continue;
}
}
// Make sure we're allowed to see it...
if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
mtrace('user '.$userto->id. ' can not see '.$post->id);
continue;
}
// OK so we need to send the email.
// Does the user want this post in a digest? If so postpone it for now.
if ($userto->maildigest > 0) {
// This user wants the mails to be in digest form
$queue = new stdClass();
$queue->userid = $userto->id;
$queue->discussionid = $discussion->id;
$queue->postid = $post->id;
$queue->timemodified = $post->created;
$DB->insert_record('forum_queue', $queue);
continue;
}
// Prepare to actually send the post now, and build up the content
$cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
$userfrom->customheaders = array ( // Headers to make emails easier to track
'Precedence: Bulk',
'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
'X-Course-Id: '.$course->id,
'X-Course-Name: '.format_string($course->fullname, true)
);
if ($post->parent) { // This post is a reply, so add headers for threading (see MDL-22551)
$userfrom->customheaders[] = 'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>';
$userfrom->customheaders[] = 'References: <moodlepost'.$post->parent.'@'.$hostname.'>';
}
$shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
$postsubject = "$shortname: ".format_string($post->subject,true);
$posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
$posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
// Send the post now!
mtrace('Sending ', '');
$eventdata = new stdClass();
$eventdata->component = 'mod_forum';
$eventdata->name = 'posts';
$eventdata->userfrom = $userfrom;
$eventdata->userto = $userto;
$eventdata->subject = $postsubject;
$eventdata->fullmessage = $posttext;
$eventdata->fullmessageformat = FORMAT_PLAIN;
$eventdata->fullmessagehtml = $posthtml;
$eventdata->notification = 1;
$smallmessagestrings = new stdClass();
$smallmessagestrings->user = fullname($userfrom);
$smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
$smallmessagestrings->message = $post->message;
//make sure strings are in message recipients language
$eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
$eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
$eventdata->contexturlname = $discussion->name;
$mailresult = message_send($eventdata);
if (!$mailresult){
mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
" ($userto->email) .. not trying again.");
add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
$errorcount[$post->id]++;
} else {
$mailcount[$post->id]++;
// Mark post as read if forum_usermarksread is set off
if (!$CFG->forum_usermarksread) {
$userto->markposts[$post->id] = $post->id;
}
}
mtrace('post '.$post->id. ': '.$post->subject);
}
// mark processed posts as read
forum_tp_mark_posts_read($userto, $userto->markposts);
}
}
if ($posts) {
foreach ($posts as $post) {
mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
if ($errorcount[$post->id]) {
$DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
}
}
}
// release some memory
unset($subscribedusers);
unset($mailcount);
unset($errorcount);
cron_setup_user();
$sitetimezone = $CFG->timezone;
// Now see if there are any digest mails waiting to be sent, and if we should send them
mtrace('Starting digest processing...');
@set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
if (!isset($CFG->digestmailtimelast)) { // To catch the first time
set_config('digestmailtimelast', 0);
}
$timenow = time();
$digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
// Delete any really old ones (normally there shouldn't be any)
$weekago = $timenow - (7 * 24 * 3600);
$DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
mtrace ('Cleaned old digest records');
if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
$digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
if ($digestposts_rs->valid()) {
// We have work to do
$usermailcount = 0;
//caches - reuse the those filled before too
$discussionposts = array();
$userdiscussions = array();
foreach ($digestposts_rs as $digestpost) {
if (!isset($users[$digestpost->userid])) {
if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
$users[$digestpost->userid] = $user;
} else {
continue;
}
}
$postuser = $users[$digestpost->userid];
if (!isset($posts[$digestpost->postid])) {
if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
$posts[$digestpost->postid] = $post;
} else {
continue;
}
}
$discussionid = $digestpost->discussionid;
if (!isset($discussions[$discussionid])) {
if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
$discussions[$discussionid] = $discussion;
} else {
continue;
}
}
$forumid = $discussions[$discussionid]->forum;
if (!isset($forums[$forumid])) {
if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
$forums[$forumid] = $forum;
} else {
continue;
}
}
$courseid = $forums[$forumid]->course;
if (!isset($courses[$courseid])) {
if ($course = $DB->get_record('course', array('id' => $courseid))) {
$courses[$courseid] = $course;
} else {
continue;
}
}
if (!isset($coursemodules[$forumid])) {
if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
$coursemodules[$forumid] = $cm;
} else {
continue;
}
}
$userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
$discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
}
$digestposts_rs->close(); /// Finished iteration, let's close the resultset
// Data collected, start sending out emails to each user
foreach ($userdiscussions as $userid => $thesediscussions) {
@set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
cron_setup_user();
mtrace(get_string('processingdigest', 'forum', $userid), '... ');
// First of all delete all the queue entries for this user
$DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
$userto = $users[$userid];
// Override the language and timezone of the "current" user, so that
// mail is customised for the receiver.
cron_setup_user($userto);
// init caches
$userto->viewfullnames = array();
$userto->canpost = array();
$userto->markposts = array();
$postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
$headerdata = new stdClass();
$headerdata->sitename = format_string($site->fullname, true);
$headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
$posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
$headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
$posthtml = "<head>";
/* foreach ($CFG->stylesheets as $stylesheet) {
//TODO: MDL-21120
$posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
}*/
$posthtml .= "</head>\n<body id=\"email\">\n";
$posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
foreach ($thesediscussions as $discussionid) {
@set_time_limit(120); // to be reset for each post
$discussion = $discussions[$discussionid];
$forum = $forums[$discussion->forum];
$course = $courses[$forum->course];
$cm = $coursemodules[$forum->id];
//override language
cron_setup_user($userto, $course);
// Fill caches
if (!isset($userto->viewfullnames[$forum->id])) {
$modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
$userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
}
if (!isset($userto->canpost[$discussion->id])) {
$modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
$userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
}
$strforums = get_string('forums', 'forum');
$canunsubscribe = ! forum_is_forcesubscribed($forum);
$canreply = $userto->canpost[$discussion->id];
$shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
$posttext .= "\n \n";
$posttext .= '=====================================================================';
$posttext .= "\n \n";
$posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
if ($discussion->name != $forum->name) {
$posttext .= " -> ".format_string($discussion->name,true);
}
$posttext .= "\n";
$posthtml .= "<p><font face=\"sans-serif\">".
"<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
"<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
"<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
if ($discussion->name == $forum->name) {
$posthtml .= "</font></p>";
} else {
$posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
}
$posthtml .= '<p>';
$postsarray = $discussionposts[$discussionid];
sort($postsarray);
foreach ($postsarray as $postid) {
$post = $posts[$postid];
if (array_key_exists($post->userid, $users)) { // we might know him/her already
$userfrom = $users[$post->userid];
} else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
$users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
} else {
mtrace('Could not find user '.$post->userid);
continue;
}
if (!isset($userfrom->groups[$forum->id])) {
if (!isset($userfrom->groups)) {
$userfrom->groups = array();
$users[$userfrom->id]->groups = array();
}
$userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
$users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
}
$userfrom->customheaders = array ("Precedence: Bulk");
if ($userto->maildigest == 2) {
// Subjects only
$by = new stdClass();
$by->name = fullname($userfrom);
$by->date = userdate($post->modified);
$posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
$posttext .= "\n---------------------------------------------------------------------";
$by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
$posthtml .= '<div><a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'#p'.$post->id.'">'.format_string($post->subject,true).'</a> '.get_string("bynameondate", "forum", $by).'</div>';
} else {
// The full treatment
$posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
$posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
// Create an array of postid's for this user to mark as read.
if (!$CFG->forum_usermarksread) {
$userto->markposts[$post->id] = $post->id;
}
}
}
if ($canunsubscribe) {
$posthtml .= "\n<div class='mdl-right'><font size=\"1\"><a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">".get_string("unsubscribe", "forum")."</a></font></div>";
} else {
$posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
}
$posthtml .= '<hr size="1" noshade="noshade" /></p>';
}
$posthtml .= '</body>';
if (empty($userto->mailformat) || $userto->mailformat != 1) {
// This user DOESN'T want to receive HTML
$posthtml = '';
}
$attachment = $attachname='';
$usetrueaddress = true;
//directly email forum digests rather than sending them via messaging
$mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
if (!$mailresult) {
mtrace("ERROR!");
echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
} else {
mtrace("success.");
$usermailcount++;
// Mark post as read if forum_usermarksread is set off
forum_tp_mark_posts_read($userto, $userto->markposts);
}
}
}
/// We have finishied all digest emails, update $CFG->digestmailtimelast
set_config('digestmailtimelast', $timenow);
}
cron_setup_user();
if (!empty($usermailcount)) {
mtrace(get_string('digestsentusers', 'forum', $usermailcount));
}
if (!empty($CFG->forum_lastreadclean)) {
$timenow = time();
if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
set_config('forum_lastreadclean', $timenow);
mtrace('Removing old forum read tracking info...');
forum_tp_clean_read_records();
}
} else {
set_config('forum_lastreadclean', time());
}
return true;
}
/**
* Builds and returns the body of the email notification in plain text.
*
* @global object
* @global object
* @uses CONTEXT_MODULE
* @param object $course
* @param object $cm
* @param object $forum
* @param object $discussion
* @param object $post
* @param object $userfrom
* @param object $userto
* @param boolean $bare
* @return string The email body in plain text format.
*/
function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
global $CFG, $USER;
$modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
if (!isset($userto->viewfullnames[$forum->id])) {
$viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
} else {
$viewfullnames = $userto->viewfullnames[$forum->id];
}
if (!isset($userto->canpost[$discussion->id])) {
$canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
} else {
$canreply = $userto->canpost[$discussion->id];
}
$by = New stdClass;
$by->name = fullname($userfrom, $viewfullnames);
$by->date = userdate($post->modified, "", $userto->timezone);
$strbynameondate = get_string('bynameondate', 'forum', $by);
$strforums = get_string('forums', 'forum');
$canunsubscribe = ! forum_is_forcesubscribed($forum);
$posttext = '';
if (!$bare) {
$shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
$posttext = "$shortname -> $strforums -> ".format_string($forum->name,true);
if ($discussion->name != $forum->name) {
$posttext .= " -> ".format_string($discussion->name,true);
}
}
// add absolute file links
$post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
$posttext .= "\n---------------------------------------------------------------------\n";
$posttext .= format_string($post->subject,true);
if ($bare) {
$posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
}
$posttext .= "\n".$strbynameondate."\n";
$posttext .= "---------------------------------------------------------------------\n";
$posttext .= format_text_email($post->message, $post->messageformat);
$posttext .= "\n\n";
$posttext .= forum_print_attachments($post, $cm, "text");
if (!$bare && $canreply) {
$posttext .= "---------------------------------------------------------------------\n";
$posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
$posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
}
if (!$bare && $canunsubscribe) {
$posttext .= "\n---------------------------------------------------------------------\n";
$posttext .= get_string("unsubscribe", "forum");
$posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
}
return $posttext;
}
/**
* Builds and returns the body of the email notification in html format.
*
* @global object
* @param object $course
* @param object $cm
* @param object $forum
* @param object $discussion
* @param object $post
* @param object $userfrom
* @param object $userto
* @return string The email text in HTML format
*/
function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
global $CFG;
if ($userto->mailformat != 1) { // Needs to be HTML
return '';
}
if (!isset($userto->canpost[$discussion->id])) {
$canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
} else {
$canreply = $userto->canpost[$discussion->id];
}
$strforums = get_string('forums', 'forum');
$canunsubscribe = ! forum_is_forcesubscribed($forum);
$shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
$posthtml = '<head>';
/* foreach ($CFG->stylesheets as $stylesheet) {
//TODO: MDL-21120
$posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
}*/
$posthtml .= '</head>';
$posthtml .= "\n<body id=\"email\">\n\n";
$posthtml .= '<div class="navbar">'.
'<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
'<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
'<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
if ($discussion->name == $forum->name) {
$posthtml .= '</div>';
} else {
$posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
format_string($discussion->name,true).'</a></div>';
}
$posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
if ($canunsubscribe) {
$posthtml .= '<hr /><div class="mdl-align unsubscribelink">
<a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
<a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
}
$posthtml .= '</body>';
return $posthtml;
}
/**
*
* @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");
$grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
if (empty($grades->items[0]->grades)) {
$grade = false;
} else {
$grade = reset($grades->items[0]->grades);
}
$count = forum_count_user_posts($forum->id, $user->id);
if ($count && $count->postcount > 0) {
$result = new stdClass();
$result->info = get_string("numposts", "forum", $count->postcount);
$result->time = $count->lastpost;
if ($grade) {
$result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
}
return $result;
} else if ($grade) {
$result = new stdClass();
$result->info = get_string('grade') . ': ' . $grade->str_long_grade;
//datesubmitted == time created. dategraded == time modified or time overridden
//if grade was last modified by the user themselves use date graded. Otherwise use date submitted
//TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
$result->time = $grade->dategraded;
} else {
$result->time = $grade->datesubmitted;
}
return $result;
}
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, $OUTPUT;
require_once("$CFG->libdir/gradelib.php");
$grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
if (!empty($grades->items[0]->grades)) {
$grade = reset($grades->items[0]->grades);
echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
if ($grade->str_feedback) {
echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
}
}
if ($posts = forum_get_user_posts($forum->id, $user->id)) {
if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
print_error('invalidcoursemodule');
}
$discussions = forum_get_user_involved_discussions($forum->id, $user->id);
foreach ($posts as $post) {
if (!isset($discussions[$post->discussion])) {
continue;
}
$discussion = $discussions[$post->discussion];
forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
}
} else {
echo "<p>".get_string("noposts", "forum")."</p>";
}
}
/**
* @global object
* @global object
* @global object
* @param array $courses
* @param array $htmlarray
*/
function forum_print_overview($courses,&$htmlarray) {
global $USER, $CFG, $DB, $SESSION;
if (empty($courses) || !is_array($courses) || count($courses) == 0) {
return array();
}
if (!$forums = get_all_instances_in_courses('forum',$courses)) {
return;
}
// get all forum logs in ONE query (much better!)
$params = array();
$sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
." JOIN {course_modules} cm ON cm.id = cmid "
." WHERE (";
foreach ($courses as $course) {
$sql .= '(l.course = ? AND l.time > ?) OR ';
$params[] = $course->id;
$params[] = $course->lastaccess;
}
$sql = substr($sql,0,-3); // take off the last OR
$sql .= ") AND l.module = 'forum' AND action = 'add post' "
." AND userid != ? GROUP BY cmid,l.course,instance";
$params[] = $USER->id;
if (!$new = $DB->get_records_sql($sql, $params)) {
$new = array(); // avoid warnings
}
// also get all forum tracking stuff ONCE.
$trackingforums = array();
foreach ($forums as $forum) {
if (forum_tp_can_track_forums($forum)) {
$trackingforums[$forum->id] = $forum;
}
}
if (count($trackingforums) > 0) {
$cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
$sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
' FROM {forum_posts} p '.
' JOIN {forum_discussions} d ON p.discussion = d.id '.
' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
$params = array($USER->id);
foreach ($trackingforums as $track) {
$sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
$params[] = $track->id;
if (isset($SESSION->currentgroup[$track->course])) {
$groupid = $SESSION->currentgroup[$track->course];
} else {
$groupid = groups_get_all_groups($track->course, $USER->id);
if (is_array($groupid)) {
$groupid = array_shift(array_keys($groupid));
$SESSION->currentgroup[$track->course] = $groupid;
} else {
$groupid = 0;
}
}
$params[] = $groupid;
}
$sql = substr($sql,0,-3); // take off the last OR
$sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
$params[] = $cutoffdate;
if (!$unread = $DB->get_records_sql($sql, $params)) {
$unread = array();
}
} else {
$unread = array();
}
if (empty($unread) and empty($new)) {
return;
}
$strforum = get_string('modulename','forum');
$strnumunread = get_string('overviewnumunread','forum');
$strnumpostssince = get_string('overviewnumpostssince','forum');
foreach ($forums as $forum) {
$str = '';
$count = 0;
$thisunread = 0;
$showunread = false;
// either we have something from logs, or trackposts, or nothing.
if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
$count = $new[$forum->id]->count;
}
if (array_key_exists($forum->id,$unread)) {
$thisunread = $unread[$forum->id]->count;
$showunread = true;
}
if ($count > 0 || $thisunread > 0) {
$str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
$forum->name.'</a></div>';
$str .= '<div class="info"><span class="postsincelogin">';
$str .= $count.' '.$strnumpostssince."</span>";
if (!empty($showunread)) {
$str .= '<div class="unreadposts">'.$thisunread .' '.$strnumunread.'</div>';
}
$str .= '</div></div>';
}
if (!empty($str)) {
if (!array_key_exists($forum->course,$htmlarray)) {
$htmlarray[$forum->course] = array();
}
if (!array_key_exists('forum',$htmlarray[$forum->course])) {
$htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
}
$htmlarray[$forum->course]['forum'] .= $str;
}
}
}
/**
* 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 $CFG, $USER, $DB, $OUTPUT;
// do not use log table if possible, it may be huge and is expensive to join with other tables
if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
d.timestart, d.timeend, d.userid AS duserid,
u.firstname, u.lastname, u.email, u.picture
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 = ?
ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
return false;
}
$modinfo =& get_fast_modinfo($course);
$groupmodes = array();
$cms = array();
$strftimerecent = get_string('strftimerecent');
$printposts = array();
foreach ($posts as $post) {
if (!isset($modinfo->instances['forum'][$post->forum])) {
// not visible
continue;
}
$cm = $modinfo->instances['forum'][$post->forum];
if (!$cm->uservisible) {
continue;
}
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
if (!has_capability('mod/forum:viewdiscussion', $context)) {
continue;
}
if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
continue;
}
}
$groupmode = groups_get_activity_groupmode($cm, $course);
if ($groupmode) {
if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
// oki (Open discussions have groupid -1)
} else {
// separate mode
if (isguestuser()) {
// shortcut
continue;
}
if (is_null($modinfo->groups)) {
$modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
}
if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
continue;
}
}
}
$printposts[] = $post;
}
unset($posts);
if (!$printposts) {
return false;
}
echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
echo "\n<ul class='unlist'>\n";
foreach ($printposts as $post) {
$subjectclass = empty($post->parent) ? ' bold' : '';
echo '<li><div class="head">'.
'<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
'<div class="name">'.fullname($post, $viewfullnames).'</div>'.
'</div>';
echo '<div class="info'.$subjectclass.'">';
if (empty($post->parent)) {
echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
} else {
echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
}
$post->subject = break_up_long_words(format_string($post->subject, true));
echo $post->subject;
echo "</a>\"</div></li>\n";
}
echo "</ul>\n";
return true;
}
/**
* Return grade for given user or all users.
*
* @global object
* @global object
* @param object $forum
* @param int $userid optional user id, 0 means all users
* @return array array of grades, false if none
*/
function forum_get_user_grades($forum, $userid = 0) {
global $CFG;
require_once($CFG->dirroot.'/rating/lib.php');
$ratingoptions = new stdClass;
$ratingoptions->component = 'mod_forum';
$ratingoptions->ratingarea = 'post';
//need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
$ratingoptions->modulename = 'forum';
$ratingoptions->moduleid = $forum->id;
$ratingoptions->userid = $userid;
$ratingoptions->aggregationmethod = $forum->assessed;
$ratingoptions->scaleid = $forum->scale;
$ratingoptions->itemtable = 'forum_posts';
$ratingoptions->itemtableusercolumn = 'userid';
$rm = new rating_manager();
return $rm->get_user_grades($ratingoptions);
}
/**
* Update activity grades
*
* @global object
* @global object
* @param object $forum
* @param int $userid specific user only, 0 means all
* @param boolean $nullifnone return null if grade does not exist
* @return void
*/
function forum_update_grades($forum, $userid=0, $nullifnone=true) {
global $CFG, $DB;
require_once($CFG->libdir.'/gradelib.php');
if (!$forum->assessed) {
forum_grade_item_update($forum);
} else if ($grades = forum_get_user_grades($forum, $userid)) {
forum_grade_item_update($forum, $grades);
} else if ($userid and $nullifnone) {
$grade = new stdClass();
$grade->userid = $userid;
$grade->rawgrade = NULL;
forum_grade_item_update($forum, $grade);
} else {
forum_grade_item_update($forum);
}
}
/**
* Update all grades in gradebook.
* @global object
*/
function forum_upgrade_grades() {
global $DB;
$sql = "SELECT COUNT('x')
FROM {forum} f, {course_modules} cm, {modules} m
WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
$count = $DB->count_records_sql($sql);
$sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
FROM {forum} f, {course_modules} cm, {modules} m
WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
$rs = $DB->get_recordset_sql($sql);
if ($rs->valid()) {
$pbar = new progress_bar('forumupgradegrades', 500, true);
$i=0;
foreach ($rs as $forum) {
$i++;
upgrade_set_timeout(60*5); // set up timeout, may also abort execution
forum_update_grades($forum, 0, false);
$pbar->update($i, $count, "Updating Forum grades ($i/$count).");
}
}
$rs->close();
}
/**
* Create/update grade item for given forum
*
* @global object
* @uses GRADE_TYPE_NONE
* @uses GRADE_TYPE_VALUE
* @uses GRADE_TYPE_SCALE
* @param object $forum object with extra cmidnumber
* @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
* @return int 0 if ok
*/
function forum_grade_item_update($forum, $grades=NULL) {
global $CFG;
if (!function_exists('grade_update')) { //workaround for buggy PHP versions
require_once($CFG->libdir.'/gradelib.php');
}
$params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
if (!$forum->assessed or $forum->scale == 0) {
$params['gradetype'] = GRADE_TYPE_NONE;
} else if ($forum->scale > 0) {
$params['gradetype'] = GRADE_TYPE_VALUE;
$params['grademax'] = $forum->scale;
$params['grademin'] = 0;
} else if ($forum->scale < 0) {
$params['gradetype'] = GRADE_TYPE_SCALE;
$params['scaleid'] = -$forum->scale;
}
if ($grades === 'reset') {
$params['reset'] = true;
$grades = NULL;
}
return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
}
/**
* Delete grade item for given forum
*
* @global object
* @param object $forum object
* @return object grade_item
*/
function forum_grade_item_delete($forum) {
global $CFG;
require_once($CFG->libdir.'/gradelib.php');
return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
}
/**
* Returns the users with data in one forum
* (users with records in forum_subscriptions, forum_posts, students)
*
* @todo: deprecated - to be deleted in 2.2
*
* @param int $forumid
* @return mixed array or false if none
*/
function forum_get_participants($forumid) {
global $CFG, $DB;
$params = array('forumid' => $forumid);
//Get students from forum_subscriptions
$sql = "SELECT DISTINCT u.id, u.id
FROM {user} u,
{forum_subscriptions} s
WHERE s.forum = :forumid AND
u.id = s.userid";
$st_subscriptions = $DB->get_records_sql($sql, $params);
//Get students from forum_posts
$sql = "SELECT DISTINCT u.id, u.id
FROM {user} u,
{forum_discussions} d,
{forum_posts} p
WHERE d.forum = :forumid AND
p.discussion = d.id AND
u.id = p.userid";
$st_posts = $DB->get_records_sql($sql, $params);
//Get students from the ratings table
$sql = "SELECT DISTINCT r.userid, r.userid AS id
FROM {forum_discussions} d
JOIN {forum_posts} p ON p.discussion = d.id
JOIN {rating} r on r.itemid = p.id
WHERE d.forum = :forumid AND
r.component = 'mod_forum' AND
r.ratingarea = 'post'";
$st_ratings = $DB->get_records_sql($sql, $params);
//Add st_posts to st_subscriptions
if ($st_posts) {
foreach ($st_posts as $st_post) {
$st_subscriptions[$st_post->id] = $st_post;
}
}
//Add st_ratings to st_subscriptions
if ($st_ratings) {
foreach ($st_ratings as $st_rating) {
$st_subscriptions[$st_rating->id] = $st_rating;
}
}
//Return st_subscriptions array (it contains an array of unique users)
return ($st_subscriptions);
}
/**
* This function returns if a scale is being used by one forum
*
* @global object
* @param int $forumid
* @param int $scaleid negative number
* @return bool
*/
function forum_scale_used ($forumid,$scaleid) {
global $DB;
$return = false;
$rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
if (!empty($rec) && !empty($scaleid)) {
$return = true;
}
return $return;
}
/**
* Checks if scale is being used by any instance of forum
*
* This is used to find out if scale used anywhere
*
* @global object
* @param $scaleid int
* @return boolean True if the scale is used by any forum
*/
function forum_scale_used_anywhere($scaleid) {
global $DB;
if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
return true;
} else {
return false;
}
}
// 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;
return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, 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 posts with all info ready for forum_print_post
* We pass forumid in because we always know it so no need to make a
* complicated join to find it out.
*
* @global object
* @global object
* @return mixed array of posts or false
*/
function forum_get_discussion_posts($discussion, $sort, $forumid) {
global $CFG, $DB;
return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
FROM {forum_posts} p
LEFT JOIN {user} u ON p.userid = u.id
WHERE p.discussion = ?
AND p.parent > 0 $sort", array($discussion));
}
/**
* Gets all posts in discussion including top parent.
*
* @global object
* @global object
* @global object
* @param int $discussionid
* @param string $sort
* @param bool $tracking does user track the forum?
* @return array of posts
*/
function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
global $CFG, $DB, $USER;
$tr_sel = "";
$tr_join = "";
$params = array();
if ($tracking) {
$now = time();
$cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
$tr_sel = ", fr.id AS postread";
$tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
$params[] = $USER->id;
}
$params[] = $discussionid;
if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, 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];
}
return $posts;
}
/**
* Gets posts with all info ready for forum_print_post
* We pass forumid in because we always know it so no need to make a
* complicated join to find it out.
*
* @global object
* @global object
* @param int $parent
* @param int $forumid
* @return array
*/
function forum_get_child_posts($parent, $forumid) {
global $CFG, $DB;
return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
FROM {forum_posts} p
LEFT JOIN {user} u ON p.userid = u.id
WHERE p.parent = ?
ORDER BY p.created ASC", array($parent));
}
/**
* 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 (is_null($modinfo->groups)) {
$modinfo->groups = groups_get_user_groups($course->id, $userid);
}
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 = get_context_instance(CONTEXT_MODULE, $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)) {
if (is_null($modinfo->groups)) {
$modinfo->groups = groups_get_user_groups($course->id, $USER->id);
}
if (isset($modinfo->groups[$cm->groupingid])) {
$forum->onlygroups = $modinfo->groups[$cm->groupingid];
$forum->onlygroups[] = -1;
} else {
$forum->onlygroups = array(-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=0, $limitfrom=0, $limitnum=50,
&$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 = round(time(), -2); // db 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)";
}
$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();
// Experimental feature under 1.8! MDL-8830
// Use alternative text searches if defined
// This feature only works under mysql until properly implemented for other DBs
// Requires manual creation of text index for forum_posts before enabling it:
// CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
// Experimental feature under 1.8! MDL-8830
if (!empty($CFG->forum_usetextsearches)) {
list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
'p.userid', 'u.id', 'u.firstname',
'u.lastname', 'p.modified', 'd.forum');
} else {
list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
'p.userid', 'u.id', 'u.firstname',
'u.lastname', 'p.modified', 'd.forum');
}
$params = array_merge($params, $msparams);
}
$fromsql = "{forum_posts} p,
{forum_discussions} d,
{user} u";
$selectsql = " $messagesearch
AND p.discussion = d.id
AND p.userid = u.id
AND $selectdiscussion
$extrasql";
$countsql = "SELECT COUNT(*)
FROM $fromsql
WHERE $selectsql";
$searchsql = "SELECT p.*,
d.forum,
u.firstname,
u.lastname,
u.email,
u.picture,
u.imagealt,
u.email
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);
}
/**
* Returns a list of ratings for a particular post - sorted.
*
* TODO: Check if this function is actually used anywhere.
* Up until the fix for MDL-27471 this function wasn't even returning.
*
* @param stdClass $context
* @param int $postid
* @param string $sort
* @return array Array of ratings or false
*/
function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
$options = new stdClass;
$options->context = $context;
$options->component = 'mod_forum';
$options->ratingarea = 'post';
$options->itemid = $postid;
$options->sort = "ORDER BY $sort";
$rm = new rating_manager();
return $rm->get_all_ratings_for_item($options);
}
/**
* Returns a list of all new posts that have not been mailed yet
*
* @param int $starttime posts created after this time
* @param int $endtime posts created before this
* @param int $now used for timed discussions only
* @return array
*/
function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
global $CFG, $DB;
$params = array($starttime, $endtime);
if (!empty($CFG->forum_enabletimedposts)) {
if (empty($now)) {
$now = time();
}
$timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
$params[] = $now;
$params[] = $now;
} else {
$timedsql = "";
}
return $DB->get_records_sql("SELECT p.*, d.course, d.forum
FROM {forum_posts} p
JOIN {forum_discussions} d ON d.id = p.discussion
WHERE p.mailed = 0
AND p.created >= ?
AND (p.created < ? OR p.mailnow = 1)
$timedsql
ORDER BY p.modified ASC", $params);
}
/**
* Marks posts before a certain time as being mailed already
*
* @global object
* @global object
* @param int $endtime
* @param int $now Defaults to time()
* @return bool
*/
function forum_mark_old_posts_as_mailed($endtime, $now=null) {
global $CFG, $DB;
if (empty($now)) {
$now = time();
}
if (empty($CFG->forum_enabletimedposts)) {
return $DB->execute("UPDATE {forum_posts}
SET mailed = '1'
WHERE (created < ? OR mailnow = 1)
AND mailed = 0", array($endtime));
} else {
return $DB->execute("UPDATE {forum_posts}
SET mailed = '1'
WHERE discussion NOT IN (SELECT d.id
FROM {forum_discussions} d
WHERE d.timestart > ?)
AND (created < ? OR mailnow = 1)
AND mailed = 0", array($now, $endtime));
}
}
/**
* 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' , get_context_instance(CONTEXT_MODULE, $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 p.*, d.forum, u.firstname, u.lastname, 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' , get_context_instance(CONTEXT_MODULE, $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' , get_context_instance(CONTEXT_MODULE, $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.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", $params);
}
/**
* Given a log entry, return the forum post details for it.
*
* @global object
* @global object
* @param object $log
* @return array|null
*/
function forum_get_post_from_log($log) {
global $CFG, $DB;
if ($log->action == "add post") {
return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
u.firstname, u.lastname, u.email, u.picture
FROM {forum_discussions} d,
{forum_posts} p,
{forum} f,
{user} u
WHERE p.id = ?
AND d.id = p.discussion
AND p.userid = u.id
AND u.deleted <> '1'
AND f.id = d.forum", array($log->info));
} else if ($log->action == "add discussion") {
return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
u.firstname, u.lastname, u.email, u.picture
FROM {forum_discussions} d,
{forum_posts} p,
{forum} f,
{user} u
WHERE d.id = ?
AND d.firstpost = p.id
AND p.userid = u.id
AND u.deleted <> '1'
AND f.id = d.forum", array($log->info));
}
return NULL;
}
/**
* Given a discussion id, return the first post from the discussion
*
* @global object
* @global object
* @param int $dicsussionid
* @return array
*/
function forum_get_firstpost_from_discussion($discussionid) {
global $CFG, $DB;
return $DB->get_record_sql("SELECT p.*
FROM {forum_discussions} d,
{forum_posts} p
WHERE d.id = ?
AND d.firstpost = p.id ", array($discussionid));
}
/**
* Returns an array of counts of replies to each discussion
*
* @global object
* @global object
* @param int $forumid
* @param string $forumsort
* @param int $limit
* @param int $page
* @param int $perpage
* @return array
*/
function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
global $CFG, $DB;
if ($limit > 0) {
$limitfrom = 0;
$limitnum = $limit;
} else if ($page != -1) {
$limitfrom = $page*$perpage;
$limitnum = $perpage;
} else {
$limitfrom = 0;
$limitnum = 0;
}
if ($forumsort == "") {
$orderby = "";
$groupby = "";
} else {
$orderby = "ORDER BY $forumsort";
$groupby = ", ".strtolower($forumsort);
$groupby = str_replace('desc', '', $groupby);
$groupby = str_replace('asc', '', $groupby);
}
if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
$sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
FROM {forum_posts} p
JOIN {forum_discussions} d ON p.discussion = d.id
WHERE p.parent > 0 AND d.forum = ?
GROUP BY p.discussion";
return $DB->get_records_sql($sql, array($forumid));
} else {
$sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
FROM {forum_posts} p
JOIN {forum_discussions} d ON p.discussion = d.id
WHERE d.forum = ?
GROUP BY p.discussion $groupby
$orderby";
return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
}
}
/**
* @global object
* @global object
* @global object
* @staticvar array $cache
* @param object $forum
* @param object $cm
* @param object $course
* @return mixed
*/
function forum_count_discussions($forum, $cm, $course) {
global $CFG, $DB, $USER;
static $cache = array();
$now = round(time(), -2); // db cache friendliness
$params = array($course->id);
if (!isset($cache[$course->id])) {
if (!empty($CFG->forum_enabletimedposts)) {
$timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
$params[] = $now;
$params[] = $now;
} else {
$timedsql = "";
}
$sql = "SELECT f.id, COUNT(d.id) as dcount
FROM {forum} f
JOIN {forum_discussions} d ON d.forum = f.id
WHERE f.course = ?
$timedsql
GROUP BY f.id";
if ($counts = $DB->get_records_sql($sql, $params)) {
foreach ($counts as $count) {
$counts[$count->id] = $count->dcount;
}
$cache[$course->id] = $counts;
} else {
$cache[$course->id] = array();
}
}
if (empty($cache[$course->id][$forum->id])) {
return 0;
}
$groupmode = groups_get_activity_groupmode($cm, $course);
if ($groupmode != SEPARATEGROUPS) {
return $cache[$course->id][$forum->id];
}
if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
return $cache[$course->id][$forum->id];
}
require_once($CFG->dirroot.'/course/lib.php');
$modinfo =& get_fast_modinfo($course);
if (is_null($modinfo->groups)) {
$modinfo->groups = groups_get_user_groups($course->id, $USER->id);
}
if (array_key_exists($cm->groupingid, $modinfo->groups)) {
$mygroups = $modinfo->groups[$cm->groupingid];
} else {
$mygroups = false; // Will be set below
}
// add all groups posts
if (empty($mygroups)) {
$mygroups = array(-1=>-1);
} else {
$mygroups[-1] = -1;
}
list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
$params[] = $forum->id;
if (!empty($CFG->forum_enabletimedposts)) {
$timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
$params[] = $now;
$params[] = $now;
} else {
$timedsql = "";
}
$sql = "SELECT COUNT(d.id)
FROM {forum_discussions} d
WHERE d.groupid $mygroups_sql AND d.forum = ?
$timedsql";
return $DB->get_field_sql($sql, $params);
}
/**
* How many posts by other users are unrated by a given user in the given discussion?
*
* TODO: Is this function still used anywhere?
*
* @param int $discussionid
* @param int $userid
* @return mixed
*/
function forum_count_unrated_posts($discussionid, $userid) {
global $CFG, $DB;
$sql = "SELECT COUNT(*) as num
FROM {forum_posts}
WHERE parent > 0
AND discussion = :discussionid
AND userid <> :userid";
$params = array('discussionid' => $discussionid, 'userid' => $userid);
$posts = $DB->get_record_sql($sql, $params);
if ($posts) {
$sql = "SELECT count(*) as num
FROM {forum_posts} p,
{rating} r
WHERE p.discussion = :discussionid AND
p.id = r.itemid AND
r.userid = userid AND
r.component = 'mod_forum' AND
r.ratingarea = 'post'";
$rated = $DB->get_record_sql($sql, $params);
if ($rated) {
if ($posts->num > $rated->num) {
return $posts->num - $rated->num;
} else {
return 0; // Just in case there was a counting error
}
} else {
return $posts->num;
}
} else {
return 0;
}
}
/**
* Get all discussions in a forum
*
* @global object
* @global object
* @global object
* @uses CONTEXT_MODULE
* @uses VISIBLEGROUPS
* @param object $cm
* @param string $forumsort
* @param bool $fullpost
* @param int $unused
* @param int $limit
* @param bool $userlastmodified
* @param int $page
* @param int $perpage
* @return array
*/
function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
global $CFG, $DB, $USER;
$timelimit = '';
$now = round(time(), -2);
$params = array($cm->instance);
$modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
return array();
}
if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
$timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
$params[] = $now;
$params[] = $now;
if (isloggedin()) {
$timelimit .= " OR d.userid = ?";
$params[] = $USER->id;
}
$timelimit .= ")";
}
}
if ($limit > 0) {
$limitfrom = 0;
$limitnum = $limit;
} else if ($page != -1) {
$limitfrom = $page*$perpage;
$limitnum = $perpage;
} else {
$limitfrom = 0;
$limitnum = 0;
}
$groupmode = groups_get_activity_groupmode($cm);
$currentgroup = groups_get_activity_group($cm);
if ($groupmode) {
if (empty($modcontext)) {
$modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
}
if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
if ($currentgroup) {
$groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
$params[] = $currentgroup;
} else {
$groupselect = "";
}
} else {
//seprate groups without access all
if ($currentgroup) {
$groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
$params[] = $currentgroup;
} else {
$groupselect = "AND d.groupid = -1";
}
}
} else {
$groupselect = "";
}
if (empty($forumsort)) {
$forumsort = "d.timemodified DESC";
}
if (empty($fullpost)) {
$postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
} else {
$postdata = "p.*";
}
if (empty($userlastmodified)) { // We don't need to know this
$umfields = "";
$umtable = "";
} else {
$umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
$umtable = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
}
$sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
FROM {forum_discussions} d
JOIN {forum_posts} p ON p.discussion = d.id
JOIN {user} u ON p.userid = u.id
$umtable
WHERE d.forum = ? AND p.parent = 0
$timelimit $groupselect
ORDER BY $forumsort";
return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
}
/**
*
* @global object
* @global object
* @global object
* @uses CONTEXT_MODULE
* @uses VISIBLEGROUPS
* @param object $cm
* @return array
*/
function forum_get_discussions_unread($cm) {
global $CFG, $DB, $USER;
$now = round(time(), -2);
$cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
$params = array();
$groupmode = groups_get_activity_groupmode($cm);
$currentgroup = groups_get_activity_group($cm);
if ($groupmode) {
$modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
if ($currentgroup) {
$groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
$params['currentgroup'] = $currentgroup;
} else {
$groupselect = "";
}
} else {
//separate groups without access all
if ($currentgroup) {
$groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
$params['currentgroup'] = $currentgroup;
} else {
$groupselect = "AND d.groupid = -1";
}
}
} else {
$groupselect = "";
}
if (!empty($CFG->forum_enabletimedposts)) {
$timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
$params['now1'] = $now;
$params['now2'] = $now;
} else {
$timedsql = "";
}
$sql = "SELECT d.id, COUNT(p.id) AS unread
FROM {forum_discussions} d
JOIN {forum_posts} p ON p.discussion = d.id
LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
WHERE d.forum = {$cm->instance}
AND p.modified >= :cutoffdate AND r.id is NULL
$groupselect
$timedsql
GROUP BY d.id";
$params['cutoffdate'] = $cutoffdate;
if ($unreads = $DB->get_records_sql($sql, $params)) {
foreach ($unreads as $unread) {
$unreads[$unread->id] = $unread->unread;
}
return $unreads;
} else {
return array();
}
}
/**
* @global object
* @global object
* @global object
* @uses CONEXT_MODULE
* @uses VISIBLEGROUPS
* @param object $cm
* @return array
*/
function forum_get_discussions_count($cm) {
global $CFG, $DB, $USER;
$now = round(time(), -2);
$params = array($cm->instance);
$groupmode = groups_get_activity_groupmode($cm);
$currentgroup = groups_get_activity_group($cm);
if ($groupmode) {
$modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
if ($currentgroup) {
$groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
$params[] = $currentgroup;
} else {
$groupselect = "";
}
} else {
//seprate groups without access all
if ($currentgroup) {
$groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
$params[] = $currentgroup;
} else {
$groupselect = "AND d.groupid = -1";
}
}
} else {
$groupselect = "";
}
$cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
$timelimit = "";
if (!empty($CFG->forum_enabletimedposts)) {
$modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
$timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
$params[] = $now;
$params[] = $now;
if (isloggedin()) {
$timelimit .= " OR d.userid = ?";
$params[] = $USER->id;
}
$timelimit .= ")";
}
}
$sql = "SELECT COUNT(d.id)
FROM {forum_discussions} d
JOIN {forum_posts} p ON p.discussion = d.id
WHERE d.forum = ? AND p.parent = 0
$groupselect $timelimit";
return $DB->get_field_sql($sql, $params);
}