. /** * @package mod * @subpackage 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($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; case FEATURE_SHOW_DESCRIPTION: 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 // Initial topics are still mailed if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) { 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.'" id.'@'.$hostname.'>', 'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id, 'Message-ID: 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: parent.'@'.$hostname.'>'; $userfrom->customheaders[] = 'References: parent.'@'.$hostname.'>'; } $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id))); $postsubject = html_to_text("$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.'&course='.$site->id; $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n"; $headerdata->userprefs = ''.get_string('digestmailprefs', 'forum').''; $posthtml = ""; /* foreach ($CFG->stylesheets as $stylesheet) { //TODO: MDL-21120 $posthtml .= ''."\n"; }*/ $posthtml .= "\n\n"; $posthtml .= '

'.get_string('digestmailheader', 'forum', $headerdata).'



'; 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 .= "

". "wwwroot/course/view.php?id=$course->id\">$shortname -> ". "wwwroot/mod/forum/index.php?id=$course->id\">$strforums -> ". "wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true).""; if ($discussion->name == $forum->name) { $posthtml .= "

"; } else { $posthtml .= " -> wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."

"; } $posthtml .= '

'; $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 = "wwwroot/user/view.php?id=$userfrom->id&course=$course->id\">$by->name"; $posthtml .= '

'.format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by).'
'; } 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"; } else { $posthtml .= "\n
".get_string("everyoneissubscribed", "forum")."
"; } $posthtml .= '

'; } $posthtml .= ''; 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, use the // site shortname as 'from name', the noreply address will be used by email_to_user. $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 = ''; /* foreach ($CFG->stylesheets as $stylesheet) { //TODO: MDL-21120 $posthtml .= ''."\n"; }*/ $posthtml .= ''; $posthtml .= "\n\n\n"; $posthtml .= ''; } else { $posthtml .= ' » '. format_string($discussion->name,true).''; } $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false); if ($canunsubscribe) { $posthtml .= '
'; } $posthtml .= ''; 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 "

".get_string("noposts", "forum")."

"; } } /** * @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; } // Courses to search for new posts $coursessqls = array(); $params = array(); foreach ($courses as $course) { // If the user has never entered into the course all posts are pending if ($course->lastaccess == 0) { $coursessqls[] = '(f.course = ?)'; $params[] = $course->id; // Only posts created after the course last access } else { $coursessqls[] = '(f.course = ? AND p.created > ?)'; $params[] = $course->id; $params[] = $course->lastaccess; } } $params[] = $USER->id; $coursessql = implode(' OR ', $coursessqls); $sql = "SELECT f.id, COUNT(*) as count " .'FROM {forum} f ' .'JOIN {forum_discussions} d ON d.forum = f.id ' .'JOIN {forum_posts} p ON p.discussion = d.id ' ."WHERE ($coursessql) " .'AND p.userid != ? ' .'GROUP BY f.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'); 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 .= '
'.$strforum.': '. $forum->name.'
'; $str .= '
'; $str .= get_string('overviewnumpostssince', 'forum', $count).""; if (!empty($showunread)) { $str .= '
'.get_string('overviewnumunread', 'forum', $thisunread).'
'; } $str .= '
'; } 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\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 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); } /** * Get all discussions started by a particular user in a course (or group) * This function no longer used ... * * @todo Remove this function if no longer used * @global object * @global object * @param int $courseid * @param int $userid * @param int $groupid * @return array */ function forum_get_user_discussions($courseid, $userid, $groupid=0) { global $CFG, $DB; $params = array($courseid, $userid); if ($groupid) { $groupselect = " AND d.groupid = ? "; $params[] = $groupid; } else { $groupselect = ""; } return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt, f.type as forumtype, f.name as forumname, f.id as forumid FROM {forum_discussions} d, {forum_posts} p, {user} u, {forum} f WHERE d.course = ? AND p.discussion = d.id AND p.parent = 0 AND p.userid = u.id AND u.id = ? AND d.forum = f.id $groupselect ORDER BY p.created DESC", $params); } /** * Get the list of potential subscribers to a forum. * * @param object $forumcontext the forum context. * @param integer $groupid the id of a group, or 0 for all groups. * @param string $fields the list of fields to return for each user. As for get_users_by_capability. * @param string $sort sort order. As for get_users_by_capability. * @return array list of users. */ function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) { global $DB; // only active enrolled users or everybody on the frontpage list($esql, $params) = get_enrolled_sql($forumcontext, 'mod/forum:allowforcesubscribe', $groupid, true); $sql = "SELECT $fields FROM {user} u JOIN ($esql) je ON je.id = u.id"; if ($sort) { $sql = "$sql ORDER BY $sort"; } else { $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC"; } return $DB->get_records_sql($sql, $params); } /** * Returns list of user objects that are subscribed to this forum * * @global object * @global object * @param object $course the course * @param forum $forum the forum * @param integer $groupid group id, or 0 for all. * @param object $context the forum context, to save re-fetching it where possible. * @param string $fields requested user fields (with "u." table prefix) * @return array list of users. */ function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) { global $CFG, $DB; if (empty($fields)) { $fields ="u.id, u.username, u.firstname, u.lastname, u.maildisplay, u.mailformat, u.maildigest, u.imagealt, u.email, u.emailstop, u.city, u.country, u.lastaccess, u.lastlogin, u.picture, u.timezone, u.theme, u.lang, u.trackforums, u.mnethostid"; } if (empty($context)) { $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id); $context = get_context_instance(CONTEXT_MODULE, $cm->id); } if (forum_is_forcesubscribed($forum)) { $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC"); } else { // only active enrolled users or everybody on the frontpage list($esql, $params) = get_enrolled_sql($context, '', $groupid, true); $params['forumid'] = $forum->id; $results = $DB->get_records_sql("SELECT $fields FROM {user} u JOIN ($esql) je ON je.id = u.id JOIN {forum_subscriptions} s ON s.userid = u.id WHERE s.forum = :forumid ORDER BY u.email ASC", $params); } // Guest user should never be subscribed to a forum. unset($results[$CFG->siteguest]); return $results; } // OTHER FUNCTIONS /////////////////////////////////////////////////////////// /** * @global object * @global object * @param int $courseid * @param string $type */ function forum_get_course_forum($courseid, $type) { // How to set up special 1-per-course forums global $CFG, $DB, $OUTPUT; if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) { // There should always only be ONE, but with the right combination of // errors there might be more. In this case, just return the oldest one (lowest ID). foreach ($forums as $forum) { return $forum; // ie the first one } } // Doesn't exist, so create one now. $forum = new stdClass(); $forum->course = $courseid; $forum->type = "$type"; switch ($forum->type) { case "news": $forum->name = get_string("namenews", "forum"); $forum->intro = get_string("intronews", "forum"); $forum->forcesubscribe = FORUM_FORCESUBSCRIBE; $forum->assessed = 0; if ($courseid == SITEID) { $forum->name = get_string("sitenews"); $forum->forcesubscribe = 0; } break; case "social": $forum->name = get_string("namesocial", "forum"); $forum->intro = get_string("introsocial", "forum"); $forum->assessed = 0; $forum->forcesubscribe = 0; break; case "blog": $forum->name = get_string('blogforum', 'forum'); $forum->intro = get_string('introblog', 'forum'); $forum->assessed = 0; $forum->forcesubscribe = 0; break; default: echo $OUTPUT->notification("That forum type doesn't exist!"); return false; break; } $forum->timemodified = time(); $forum->id = $DB->insert_record("forum", $forum); if (! $module = $DB->get_record("modules", array("name" => "forum"))) { echo $OUTPUT->notification("Could not find forum module!!"); return false; } $mod = new stdClass(); $mod->course = $courseid; $mod->module = $module->id; $mod->instance = $forum->id; $mod->section = 0; if (! $mod->coursemodule = add_course_module($mod) ) { // assumes course/lib.php is loaded echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'"); return false; } if (! $sectionid = add_mod_to_section($mod) ) { // assumes course/lib.php is loaded echo $OUTPUT->notification("Could not add the new course module to that section"); return false; } $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule)); include_once("$CFG->dirroot/course/lib.php"); rebuild_course_cache($courseid); return $DB->get_record("forum", array("id" => "$forum->id")); } /** * Given the data about a posting, builds up the HTML to display it and * returns the HTML in a string. This is designed for sending via HTML email. * * @global object * @param object $course * @param object $cm * @param object $forum * @param object $discussion * @param object $post * @param object $userform * @param object $userto * @param bool $ownpost * @param bool $reply * @param bool $link * @param bool $rate * @param string $footer * @return string */ function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") { global $CFG, $OUTPUT; $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]; } // add absolute file links $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id); // format the post body $options = new stdClass(); $options->para = true; $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id); $output = ''; $output .= ''; if ($post->parent) { $output .= ''; $output .= '
'; $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id)); $output .= ''; } else { $output .= ''; } $output .= '
'.format_string($post->subject).'
'; $fullname = fullname($userfrom, $viewfullnames); $by = new stdClass(); $by->name = ''.$fullname.''; $by->date = userdate($post->modified, '', $userto->timezone); $output .= '
'.get_string('bynameondate', 'forum', $by).'
'; $output .= '
'; if (isset($userfrom->groups)) { $groups = $userfrom->groups[$forum->id]; } else { $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid); } if ($groups) { $output .= print_group_picture($groups, $course->id, false, true, true); } else { $output .= ' '; } $output .= ''; $attachments = forum_print_attachments($post, $cm, 'html'); if ($attachments !== '') { $output .= '
'; $output .= $attachments; $output .= '
'; } $output .= $formattedtext; // Commands $commands = array(); if ($post->parent) { $commands[] = ''.get_string('parent', 'forum').''; } if ($reply) { $commands[] = ''. get_string('reply', 'forum').''; } $output .= '
'; $output .= implode(' | ', $commands); $output .= '
'; // Context link to post if required if ($link) { $output .= ''; } if ($footer) { $output .= ''; } $output .= '
'."\n\n"; return $output; } /** * Print a forum post * * @global object * @global object * @uses FORUM_MODE_THREADED * @uses PORTFOLIO_FORMAT_PLAINHTML * @uses PORTFOLIO_FORMAT_FILE * @uses PORTFOLIO_FORMAT_RICHHTML * @uses PORTFOLIO_ADD_TEXT_LINK * @uses CONTEXT_MODULE * @param object $post The post to print. * @param object $discussion * @param object $forum * @param object $cm * @param object $course * @param boolean $ownpost Whether this post belongs to the current user. * @param boolean $reply Whether to print a 'reply' link at the bottom of the message. * @param boolean $link Just print a shortened version of the post as a link to the full post. * @param string $footer Extra stuff to print after the message. * @param string $highlight Space-separated list of terms to highlight. * @param int $post_read true, false or -99. If we already know whether this user * has read this post, pass that in, otherwise, pass in -99, and this * function will work it out. * @param boolean $dummyifcantsee When forum_user_can_see_post says that * the current user can't see this post, if this argument is true * (the default) then print a dummy 'you can't see this post' post. * If false, don't output anything at all. * @param bool|null $istracked * @return void */ function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false, $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) { global $USER, $CFG, $OUTPUT; require_once($CFG->libdir . '/filelib.php'); // String cache static $str; $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id); $post->course = $course->id; $post->forum = $forum->id; $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id); // caching if (!isset($cm->cache)) { $cm->cache = new stdClass; } if (!isset($cm->cache->caps)) { $cm->cache->caps = array(); $cm->cache->caps['mod/forum:viewdiscussion'] = has_capability('mod/forum:viewdiscussion', $modcontext); $cm->cache->caps['moodle/site:viewfullnames'] = has_capability('moodle/site:viewfullnames', $modcontext); $cm->cache->caps['mod/forum:editanypost'] = has_capability('mod/forum:editanypost', $modcontext); $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext); $cm->cache->caps['mod/forum:deleteownpost'] = has_capability('mod/forum:deleteownpost', $modcontext); $cm->cache->caps['mod/forum:deleteanypost'] = has_capability('mod/forum:deleteanypost', $modcontext); $cm->cache->caps['mod/forum:viewanyrating'] = has_capability('mod/forum:viewanyrating', $modcontext); $cm->cache->caps['mod/forum:exportpost'] = has_capability('mod/forum:exportpost', $modcontext); $cm->cache->caps['mod/forum:exportownpost'] = has_capability('mod/forum:exportownpost', $modcontext); } if (!isset($cm->uservisible)) { $cm->uservisible = coursemodule_visible_for_user($cm); } if ($istracked && is_null($postisread)) { $postisread = forum_tp_is_post_read($USER->id, $post); } if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) { $output = ''; if (!$dummyifcantsee) { if ($return) { return $output; } echo $output; return; } $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id)); $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix')); $output .= html_writer::start_tag('div', array('class'=>'row header')); $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture if ($post->parent) { $output .= html_writer::start_tag('div', array('class'=>'topic')); } else { $output .= html_writer::start_tag('div', array('class'=>'topic starter')); } $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author $output .= html_writer::end_tag('div'); $output .= html_writer::end_tag('div'); // row $output .= html_writer::start_tag('div', array('class'=>'row')); $output .= html_writer::tag('div', ' ', array('class'=>'left side')); // Groups $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content $output .= html_writer::end_tag('div'); // row $output .= html_writer::end_tag('div'); // forumpost if ($return) { return $output; } echo $output; return; } if (empty($str)) { $str = new stdClass; $str->edit = get_string('edit', 'forum'); $str->delete = get_string('delete', 'forum'); $str->reply = get_string('reply', 'forum'); $str->parent = get_string('parent', 'forum'); $str->pruneheading = get_string('pruneheading', 'forum'); $str->prune = get_string('prune', 'forum'); $str->displaymode = get_user_preferences('forum_displaymode', $CFG->forum_displaymode); $str->markread = get_string('markread', 'forum'); $str->markunread = get_string('markunread', 'forum'); } $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion)); // Build an object that represents the posting user $postuser = new stdClass; $postuser->id = $post->userid; $postuser->firstname = $post->firstname; $postuser->lastname = $post->lastname; $postuser->imagealt = $post->imagealt; $postuser->picture = $post->picture; $postuser->email = $post->email; // Some handy things for later on $postuser->fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']); $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id)); // Prepare the groups the posting user belongs to if (isset($cm->cache->usersgroups)) { $groups = array(); if (isset($cm->cache->usersgroups[$post->userid])) { foreach ($cm->cache->usersgroups[$post->userid] as $gid) { $groups[$gid] = $cm->cache->groups[$gid]; } } } else { $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid); } // Prepare the attachements for the post, files then images list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages'); // Determine if we need to shorten this post $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost)); // Prepare an array of commands $commands = array(); // SPECIAL CASE: The front page can display a news item post to non-logged in users. // Don't display the mark read / unread controls in this case. if ($istracked && $CFG->forum_usermarksread && isloggedin()) { $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread')); $text = $str->markunread; if (!$postisread) { $url->param('mark', 'read'); $text = $str->markread; } if ($str->displaymode == FORUM_MODE_THREADED) { $url->param('parent', $post->parent); } else { $url->set_anchor('p'.$post->id); } $commands[] = array('url'=>$url, 'text'=>$text); } // Zoom in to the parent specifically if ($post->parent) { $url = new moodle_url($discussionlink); if ($str->displaymode == FORUM_MODE_THREADED) { $url->param('parent', $post->parent); } else { $url->set_anchor('p'.$post->parent); } $commands[] = array('url'=>$url, 'text'=>$str->parent); } // Hack for allow to edit news posts those are not displayed yet until they are displayed $age = time() - $post->created; if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) { $age = 0; } if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) { $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit); } if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') { $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading); } if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) { $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete); } if ($reply) { $commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mform1', array('reply'=>$post->id)), 'text'=>$str->reply); } if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) { $p = array('postid' => $post->id); require_once($CFG->libdir.'/portfoliolib.php'); $button = new portfolio_add_button(); $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php'); if (empty($attachments)) { $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML); } else { $button->set_formats(PORTFOLIO_FORMAT_RICHHTML); } $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK); if (!empty($porfoliohtml)) { $commands[] = $porfoliohtml; } } // Finished building commands // Begin output $output = ''; if ($istracked) { if ($postisread) { $forumpostclass = ' read'; } else { $forumpostclass = ' unread'; $output .= html_writer::tag('a', '', array('name'=>'unread')); } } else { // ignore trackign status if not tracked or tracked param missing $forumpostclass = ''; } $topicclass = ''; if (empty($post->parent)) { $topicclass = ' firstpost starter'; } $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id)); $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass)); $output .= html_writer::start_tag('div', array('class'=>'row header clearfix')); $output .= html_writer::start_tag('div', array('class'=>'left picture')); $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id)); $output .= html_writer::end_tag('div'); $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass)); $postsubject = $post->subject; if (empty($post->subjectnoformat)) { $postsubject = format_string($postsubject); } $output .= html_writer::tag('div', $postsubject, array('class'=>'subject')); $by = new stdClass(); $by->name = html_writer::link($postuser->profilelink, $postuser->fullname); $by->date = userdate($post->modified); $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author')); $output .= html_writer::end_tag('div'); //topic $output .= html_writer::end_tag('div'); //row $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix')); $output .= html_writer::start_tag('div', array('class'=>'left')); $groupoutput = ''; if ($groups) { $groupoutput = print_group_picture($groups, $course->id, false, true, true); } if (empty($groupoutput)) { $groupoutput = ' '; } $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures')); $output .= html_writer::end_tag('div'); //left side $output .= html_writer::start_tag('div', array('class'=>'no-overflow')); $output .= html_writer::start_tag('div', array('class'=>'content')); if (!empty($attachments)) { $output .= html_writer::tag('div', $attachments, array('class'=>'attachments')); } $options = new stdClass; $options->para = false; $options->trusted = $post->messagetrust; $options->context = $modcontext; if ($shortenpost) { // Prepare shortened version $postclass = 'shortenedpost'; $postcontent = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id); $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum')); $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count')); } else { // Prepare whole post $postclass = 'fullpost'; $postcontent = format_text($post->message, $post->messageformat, $options, $course->id); if (!empty($highlight)) { $postcontent = highlight($highlight, $postcontent); } $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages')); } // Output the post content $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass)); $output .= html_writer::end_tag('div'); // Content $output .= html_writer::end_tag('div'); // Content mask $output .= html_writer::end_tag('div'); // Row $output .= html_writer::start_tag('div', array('class'=>'row side')); $output .= html_writer::tag('div',' ', array('class'=>'left')); $output .= html_writer::start_tag('div', array('class'=>'options clearfix')); // Output ratings if (!empty($post->rating)) { $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating')); } // Output the commands $commandhtml = array(); foreach ($commands as $command) { if (is_array($command)) { $commandhtml[] = html_writer::link($command['url'], $command['text']); } else { $commandhtml[] = $command; } } $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands')); // Output link to post if required if ($link) { if ($post->replies == 1) { $replystring = get_string('repliesone', 'forum', $post->replies); } else { $replystring = get_string('repliesmany', 'forum', $post->replies); } $output .= html_writer::start_tag('div', array('class'=>'link')); $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum')); $output .= ' ('.$replystring.')'; $output .= html_writer::end_tag('div'); // link } // Output footer if required if ($footer) { $output .= html_writer::tag('div', $footer, array('class'=>'footer')); } // Close remaining open divs $output .= html_writer::end_tag('div'); // content $output .= html_writer::end_tag('div'); // row $output .= html_writer::end_tag('div'); // forumpost // Mark the forum post as read if required if ($istracked && !$CFG->forum_usermarksread && !$postisread) { forum_tp_mark_post_read($USER->id, $post, $forum->id); } if ($return) { return $output; } echo $output; return; } /** * Return rating related permissions * * @param string $options the context id * @return array an associative array of the user's rating permissions */ function forum_rating_permissions($contextid, $component, $ratingarea) { $context = get_context_instance_by_id($contextid, MUST_EXIST); if ($component != 'mod_forum' || $ratingarea != 'post') { // We don't know about this component/ratingarea so just return null to get the // default restrictive permissions. return null; } return array( 'view' => has_capability('mod/forum:viewrating', $context), 'viewany' => has_capability('mod/forum:viewanyrating', $context), 'viewall' => has_capability('mod/forum:viewallratings', $context), 'rate' => has_capability('mod/forum:rate', $context) ); } /** * Validates a submitted rating * @param array $params submitted data * context => object the context in which the rated items exists [required] * component => The component for this module - should always be mod_forum [required] * ratingarea => object the context in which the rated items exists [required] * itemid => int the ID of the object being rated [required] * scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required] * rating => int the submitted rating [required] * rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required] * aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required] * @return boolean true if the rating is valid. Will throw rating_exception if not */ function forum_rating_validate($params) { global $DB, $USER; // Check the component is mod_forum if ($params['component'] != 'mod_forum') { throw new rating_exception('invalidcomponent'); } // Check the ratingarea is post (the only rating area in forum) if ($params['ratingarea'] != 'post') { throw new rating_exception('invalidratingarea'); } // Check the rateduserid is not the current user .. you can't rate your own posts if ($params['rateduserid'] == $USER->id) { throw new rating_exception('nopermissiontorate'); } // Fetch all the related records ... we need to do this anyway to call forum_user_can_see_post $post = $DB->get_record('forum_posts', array('id' => $params['itemid'], 'userid' => $params['rateduserid']), '*', MUST_EXIST); $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion), '*', MUST_EXIST); $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST); $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST); $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id , false, MUST_EXIST); $context = get_context_instance(CONTEXT_MODULE, $cm->id); // Make sure the context provided is the context of the forum if ($context->id != $params['context']->id) { throw new rating_exception('invalidcontext'); } if ($forum->scale != $params['scaleid']) { //the scale being submitted doesnt match the one in the database throw new rating_exception('invalidscaleid'); } // check the item we're rating was created in the assessable time window if (!empty($forum->assesstimestart) && !empty($forum->assesstimefinish)) { if ($post->created < $forum->assesstimestart || $post->created > $forum->assesstimefinish) { throw new rating_exception('notavailable'); } } //check that the submitted rating is valid for the scale // lower limit if ($params['rating'] < 0 && $params['rating'] != RATING_UNSET_RATING) { throw new rating_exception('invalidnum'); } // upper limit if ($forum->scale < 0) { //its a custom scale $scalerecord = $DB->get_record('scale', array('id' => -$forum->scale)); if ($scalerecord) { $scalearray = explode(',', $scalerecord->scale); if ($params['rating'] > count($scalearray)) { throw new rating_exception('invalidnum'); } } else { throw new rating_exception('invalidscaleid'); } } else if ($params['rating'] > $forum->scale) { //if its numeric and submitted rating is above maximum throw new rating_exception('invalidnum'); } // Make sure groups allow this user to see the item they're rating 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 throw new rating_exception('cannotfindgroup');//something is wrong } if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) { // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS throw new rating_exception('notmemberofgroup'); } } // perform some final capability checks if (!forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) { throw new rating_exception('nopermissiontorate'); } return true; } /** * This function prints the overview of a discussion in the forum listing. * It needs some discussion information and some post information, these * happen to be combined for efficiency in the $post parameter by the function * that calls this one: forum_print_latest_discussions() * * @global object * @global object * @param object $post The post object (passed by reference for speed). * @param object $forum The forum object. * @param int $group Current group. * @param string $datestring Format to use for the dates. * @param boolean $cantrack Is tracking enabled for this forum. * @param boolean $forumtracked Is the user tracking this forum. * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course */ function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="", $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) { global $USER, $CFG, $OUTPUT; static $rowcount; static $strmarkalldread; if (empty($modcontext)) { if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) { print_error('invalidcoursemodule'); } $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id); } if (!isset($rowcount)) { $rowcount = 0; $strmarkalldread = get_string('markalldread', 'forum'); } else { $rowcount = ($rowcount + 1) % 2; } $post->subject = format_string($post->subject,true); echo "\n\n"; echo ''; // Topic echo ''; echo ''.$post->subject.''; echo "\n"; // Picture $postuser = new stdClass(); $postuser->id = $post->userid; $postuser->firstname = $post->firstname; $postuser->lastname = $post->lastname; $postuser->imagealt = $post->imagealt; $postuser->picture = $post->picture; $postuser->email = $post->email; echo ''; echo $OUTPUT->user_picture($postuser, array('courseid'=>$forum->course)); echo "\n"; // User name $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext)); echo ''; echo ''.$fullname.''; echo "\n"; // Group picture if ($group !== -1) { // Groups are active - group is a group data object or NULL echo ''; if (!empty($group->picture) and empty($group->hidepicture)) { print_group_picture($group, $forum->course, false, false, true); } else if (isset($group->id)) { if($canviewparticipants) { echo ''.$group->name.''; } else { echo $group->name; } } echo "\n"; } if (has_capability('mod/forum:viewdiscussion', $modcontext)) { // Show the column with replies echo ''; echo ''; echo $post->replies.''; echo "\n"; if ($cantrack) { echo ''; if ($forumtracked) { if ($post->unread > 0) { echo ''; echo ''; echo $post->unread; echo ''; echo '' . ''.$strmarkalldread.''; echo ''; } else { echo ''; echo $post->unread; echo ''; } } else { echo ''; echo '-'; echo ''; } echo "\n"; } } echo ''; $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified; // Just in case $parenturl = (empty($post->lastpostid)) ? '' : '&parent='.$post->lastpostid; $usermodified = new stdClass(); $usermodified->id = $post->usermodified; $usermodified->firstname = $post->umfirstname; $usermodified->lastname = $post->umlastname; echo ''. fullname($usermodified).'
'; echo ''. userdate($usedate, $datestring).''; echo "\n"; echo "\n\n"; } /** * Given a post object that we already know has a long message * this function truncates the message nicely to the first * sane place between $CFG->forum_longpost and $CFG->forum_shortpost * * @global object * @param string $message * @return string */ function forum_shorten_post($message) { global $CFG; $i = 0; $tag = false; $length = strlen($message); $count = 0; $stopzone = false; $truncate = 0; for ($i=0; $i<$length; $i++) { $char = $message[$i]; switch ($char) { case "<": $tag = true; break; case ">": $tag = false; break; default: if (!$tag) { if ($stopzone) { if ($char == ".") { $truncate = $i+1; break 2; } } $count++; } break; } if (!$stopzone) { if ($count > $CFG->forum_shortpost) { $stopzone = true; } } } if (!$truncate) { $truncate = $i; } return substr($message, 0, $truncate); } /** * Print the drop down that allows the user to select how they want to have * the discussion displayed. * * @param int $id forum id if $forumtype is 'single', * discussion id for any other forum type * @param mixed $mode forum layout mode * @param string $forumtype optional */ function forum_print_mode_form($id, $mode, $forumtype='') { global $OUTPUT; if ($forumtype == 'single') { $select = new single_select(new moodle_url("/mod/forum/view.php", array('f'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode"); $select->set_label(get_string('displaymode', 'forum'), array('class' => 'accesshide')); $select->class = "forummode"; } else { $select = new single_select(new moodle_url("/mod/forum/discuss.php", array('d'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode"); $select->set_label(get_string('displaymode', 'forum'), array('class' => 'accesshide')); } echo $OUTPUT->render($select); } /** * @global object * @param object $course * @param string $search * @return string */ function forum_search_form($course, $search='') { global $CFG, $OUTPUT; $output = '
'; $output .= '
'; $output .= '
'; $output .= $OUTPUT->help_icon('search'); $output .= ''; $output .= ''; $output .= ''; $output .= ''; $output .= ''; $output .= '
'; $output .= '
'; $output .= '
'; return $output; } /** * @global object * @global object */ function forum_set_return() { global $CFG, $SESSION; if (! isset($SESSION->fromdiscussion)) { if (!empty($_SERVER['HTTP_REFERER'])) { $referer = $_SERVER['HTTP_REFERER']; } else { $referer = ""; } // If the referer is NOT a login screen then save it. if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) { $SESSION->fromdiscussion = $_SERVER["HTTP_REFERER"]; } } } /** * @global object * @param string $default * @return string */ function forum_go_back_to($default) { global $SESSION; if (!empty($SESSION->fromdiscussion)) { $returnto = $SESSION->fromdiscussion; unset($SESSION->fromdiscussion); return $returnto; } else { return $default; } } /** * Given a discussion object that is being moved to $forumto, * this function checks all posts in that discussion * for attachments, and if any are found, these are * moved to the new forum directory. * * @global object * @param object $discussion * @param int $forumfrom source forum id * @param int $forumto target forum id * @return bool success */ function forum_move_attachments($discussion, $forumfrom, $forumto) { global $DB; $fs = get_file_storage(); $newcm = get_coursemodule_from_instance('forum', $forumto); $oldcm = get_coursemodule_from_instance('forum', $forumfrom); $newcontext = get_context_instance(CONTEXT_MODULE, $newcm->id); $oldcontext = get_context_instance(CONTEXT_MODULE, $oldcm->id); // loop through all posts, better not use attachment flag ;-) if ($posts = $DB->get_records('forum_posts', array('discussion'=>$discussion->id), '', 'id, attachment')) { foreach ($posts as $post) { $fs->move_area_files_to_new_context($oldcontext->id, $newcontext->id, 'mod_forum', 'post', $post->id); $attachmentsmoved = $fs->move_area_files_to_new_context($oldcontext->id, $newcontext->id, 'mod_forum', 'attachment', $post->id); if ($attachmentsmoved > 0 && $post->attachment != '1') { // Weird - let's fix it $post->attachment = '1'; $DB->update_record('forum_posts', $post); } else if ($attachmentsmoved == 0 && $post->attachment != '') { // Weird - let's fix it $post->attachment = ''; $DB->update_record('forum_posts', $post); } } } return true; } /** * Returns attachments as formated text/html optionally with separate images * * @global object * @global object * @global object * @param object $post * @param object $cm * @param string $type html/text/separateimages * @return mixed string or array of (html text withouth images and image HTML) */ function forum_print_attachments($post, $cm, $type) { global $CFG, $DB, $USER, $OUTPUT; if (empty($post->attachment)) { return $type !== 'separateimages' ? '' : array('', ''); } if (!in_array($type, array('separateimages', 'html', 'text'))) { return $type !== 'separateimages' ? '' : array('', ''); } if (!$context = get_context_instance(CONTEXT_MODULE, $cm->id)) { return $type !== 'separateimages' ? '' : array('', ''); } $strattachment = get_string('attachment', 'forum'); $fs = get_file_storage(); $imagereturn = ''; $output = ''; $canexport = !empty($CFG->enableportfolios) && (has_capability('mod/forum:exportpost', $context) || ($post->userid == $USER->id && has_capability('mod/forum:exportownpost', $context))); if ($canexport) { require_once($CFG->libdir.'/portfoliolib.php'); } $files = $fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id, "timemodified", false); if ($files) { if ($canexport) { $button = new portfolio_add_button(); } foreach ($files as $file) { $filename = $file->get_filename(); $mimetype = $file->get_mimetype(); $iconimage = ''.$mimetype.''; $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$context->id.'/mod_forum/attachment/'.$post->id.'/'.$filename); if ($type == 'html') { $output .= "$iconimage "; $output .= "".s($filename).""; if ($canexport) { $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php'); $button->set_format_by_file($file); $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK); } $output .= "
"; } else if ($type == 'text') { $output .= "$strattachment ".s($filename).":\n$path\n"; } else { //'returnimages' if (in_array($mimetype, array('image/gif', 'image/jpeg', 'image/png'))) { // Image attachments don't get printed as links $imagereturn .= "
\"\""; if ($canexport) { $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php'); $button->set_format_by_file($file); $imagereturn .= $button->to_html(PORTFOLIO_ADD_ICON_LINK); } } else { $output .= "$iconimage "; $output .= format_text("".s($filename)."", FORMAT_HTML, array('context'=>$context)); if ($canexport) { $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php'); $button->set_format_by_file($file); $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK); } $output .= '
'; } } } } if ($type !== 'separateimages') { return $output; } else { return array($output, $imagereturn); } } /** * Lists all browsable file areas * * @param object $course * @param object $cm * @param object $context * @return array */ function forum_get_file_areas($course, $cm, $context) { $areas = array(); return $areas; } /** * File browsing support for forum module. * * @param object $browser * @param object $areas * @param object $course * @param object $cm * @param object $context * @param string $filearea * @param int $itemid * @param string $filepath * @param string $filename * @return object file_info instance or null if not found */ function forum_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) { global $CFG, $DB; if ($context->contextlevel != CONTEXT_MODULE) { return null; } $fileareas = array('attachment', 'post'); if (!in_array($filearea, $fileareas)) { return null; } if (!$post = $DB->get_record('forum_posts', array('id' => $itemid))) { return null; } if (!$discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion))) { return null; } if (!$forum = $DB->get_record('forum', array('id' => $cm->instance))) { return null; } $fs = get_file_storage(); $filepath = is_null($filepath) ? '/' : $filepath; $filename = is_null($filename) ? '.' : $filename; if (!($storedfile = $fs->get_file($context->id, 'mod_forum', $filearea, $itemid, $filepath, $filename))) { return null; } // Make sure groups allow this user to see this file 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 return null; // Be safe and don't send it to anyone } if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) { // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS return null; } } // Make sure we're allowed to see it... if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) { return null; } $urlbase = $CFG->wwwroot.'/pluginfile.php'; return new file_info_stored($browser, $context, $storedfile, $urlbase, $filearea, $itemid, true, true, false); } /** * Serves the forum attachments. Implements needed access control ;-) * * @param object $course * @param object $cm * @param object $context * @param string $filearea * @param array $args * @param bool $forcedownload * @return bool false if file not found, does not return if found - justsend the file */ function forum_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { global $CFG, $DB; if ($context->contextlevel != CONTEXT_MODULE) { return false; } require_course_login($course, true, $cm); $fileareas = array('attachment', 'post'); if (!in_array($filearea, $fileareas)) { return false; } $postid = (int)array_shift($args); if (!$post = $DB->get_record('forum_posts', array('id'=>$postid))) { return false; } if (!$discussion = $DB->get_record('forum_discussions', array('id'=>$post->discussion))) { return false; } if (!$forum = $DB->get_record('forum', array('id'=>$cm->instance))) { return false; } $fs = get_file_storage(); $relativepath = implode('/', $args); $fullpath = "/$context->id/mod_forum/$filearea/$postid/$relativepath"; if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { return false; } // Make sure groups allow this user to see this file 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 return false; // Be safe and don't send it to anyone } if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) { // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS return false; } } // Make sure we're allowed to see it... if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) { return false; } // finally send the file send_stored_file($file, 0, 0, true); // download MUST be forced - security! } /** * If successful, this function returns the name of the file * * @global object * @param object $post is a full post record, including course and forum * @param object $forum * @param object $cm * @param mixed $mform * @param string $message * @return bool */ function forum_add_attachment($post, $forum, $cm, $mform=null, &$message=null) { global $DB; if (empty($mform)) { return false; } if (empty($post->attachments)) { return true; // Nothing to do } $context = get_context_instance(CONTEXT_MODULE, $cm->id); $info = file_get_draft_area_info($post->attachments); $present = ($info['filecount']>0) ? '1' : ''; file_save_draft_area_files($post->attachments, $context->id, 'mod_forum', 'attachment', $post->id); $DB->set_field('forum_posts', 'attachment', $present, array('id'=>$post->id)); return true; } /** * Add a new post in an existing discussion. * * @global object * @global object * @global object * @param object $post * @param mixed $mform * @param string $message * @return int */ function forum_add_new_post($post, $mform, &$message) { global $USER, $CFG, $DB; $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion)); $forum = $DB->get_record('forum', array('id' => $discussion->forum)); $cm = get_coursemodule_from_instance('forum', $forum->id); $context = get_context_instance(CONTEXT_MODULE, $cm->id); $post->created = $post->modified = time(); $post->mailed = "0"; $post->userid = $USER->id; $post->attachment = ""; $post->id = $DB->insert_record("forum_posts", $post); $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message); $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id)); forum_add_attachment($post, $forum, $cm, $mform, $message); // Update discussion modified date $DB->set_field("forum_discussions", "timemodified", $post->modified, array("id" => $post->discussion)); $DB->set_field("forum_discussions", "usermodified", $post->userid, array("id" => $post->discussion)); if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) { forum_tp_mark_post_read($post->userid, $post, $post->forum); } return $post->id; } /** * Update a post * * @global object * @global object * @global object * @param object $post * @param mixed $mform * @param string $message * @return bool */ function forum_update_post($post, $mform, &$message) { global $USER, $CFG, $DB; $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion)); $forum = $DB->get_record('forum', array('id' => $discussion->forum)); $cm = get_coursemodule_from_instance('forum', $forum->id); $context = get_context_instance(CONTEXT_MODULE, $cm->id); $post->modified = time(); $DB->update_record('forum_posts', $post); $discussion->timemodified = $post->modified; // last modified tracking $discussion->usermodified = $post->userid; // last modified tracking if (!$post->parent) { // Post is a discussion starter - update discussion title and times too $discussion->name = $post->subject; $discussion->timestart = $post->timestart; $discussion->timeend = $post->timeend; } $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message); $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id)); $DB->update_record('forum_discussions', $discussion); forum_add_attachment($post, $forum, $cm, $mform, $message); if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) { forum_tp_mark_post_read($post->userid, $post, $post->forum); } return true; } /** * Given an object containing all the necessary data, * create a new discussion and return the id * * @global object * @global object * @global object * @param object $post * @param mixed $mform * @param string $message * @param int $userid * @return object */ function forum_add_discussion($discussion, $mform=null, &$message=null, $userid=null) { global $USER, $CFG, $DB; $timenow = time(); if (is_null($userid)) { $userid = $USER->id; } // The first post is stored as a real post, and linked // to from the discuss entry. $forum = $DB->get_record('forum', array('id'=>$discussion->forum)); $cm = get_coursemodule_from_instance('forum', $forum->id); $post = new stdClass(); $post->discussion = 0; $post->parent = 0; $post->userid = $userid; $post->created = $timenow; $post->modified = $timenow; $post->mailed = 0; $post->subject = $discussion->name; $post->message = $discussion->message; $post->messageformat = $discussion->messageformat; $post->messagetrust = $discussion->messagetrust; $post->attachments = isset($discussion->attachments) ? $discussion->attachments : null; $post->forum = $forum->id; // speedup $post->course = $forum->course; // speedup $post->mailnow = $discussion->mailnow; $post->id = $DB->insert_record("forum_posts", $post); // TODO: Fix the calling code so that there always is a $cm when this function is called if (!empty($cm->id) && !empty($discussion->itemid)) { // In "single simple discussions" this may not exist yet $context = get_context_instance(CONTEXT_MODULE, $cm->id); $text = file_save_draft_area_files($discussion->itemid, $context->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message); $DB->set_field('forum_posts', 'message', $text, array('id'=>$post->id)); } // Now do the main entry for the discussion, linking to this first post $discussion->firstpost = $post->id; $discussion->timemodified = $timenow; $discussion->usermodified = $post->userid; $discussion->userid = $userid; $post->discussion = $DB->insert_record("forum_discussions", $discussion); // Finally, set the pointer on the post. $DB->set_field("forum_posts", "discussion", $post->discussion, array("id"=>$post->id)); if (!empty($cm->id)) { forum_add_attachment($post, $forum, $cm, $mform, $message); } if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) { forum_tp_mark_post_read($post->userid, $post, $post->forum); } return $post->discussion; } /** * Deletes a discussion and handles all associated cleanup. * * @global object * @param object $discussion Discussion to delete * @param bool $fulldelete True when deleting entire forum * @param object $course Course * @param object $cm Course-module * @param object $forum Forum * @return bool */ function forum_delete_discussion($discussion, $fulldelete, $course, $cm, $forum) { global $DB, $CFG; require_once($CFG->libdir.'/completionlib.php'); $result = true; if ($posts = $DB->get_records("forum_posts", array("discussion" => $discussion->id))) { foreach ($posts as $post) { $post->course = $discussion->course; $post->forum = $discussion->forum; if (!forum_delete_post($post, 'ignore', $course, $cm, $forum, $fulldelete)) { $result = false; } } } forum_tp_delete_read_records(-1, -1, $discussion->id); if (!$DB->delete_records("forum_discussions", array("id"=>$discussion->id))) { $result = false; } // Update completion state if we are tracking completion based on number of posts // But don't bother when deleting whole thing if (!$fulldelete) { $completion = new completion_info($course); if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && ($forum->completiondiscussions || $forum->completionreplies || $forum->completionposts)) { $completion->update_state($cm, COMPLETION_INCOMPLETE, $discussion->userid); } } return $result; } /** * Deletes a single forum post. * * @global object * @param object $post Forum post object * @param mixed $children Whether to delete children. If false, returns false * if there are any children (without deleting the post). If true, * recursively deletes all children. If set to special value 'ignore', deletes * post regardless of children (this is for use only when deleting all posts * in a disussion). * @param object $course Course * @param object $cm Course-module * @param object $forum Forum * @param bool $skipcompletion True to skip updating completion state if it * would otherwise be updated, i.e. when deleting entire forum anyway. * @return bool */ function forum_delete_post($post, $children, $course, $cm, $forum, $skipcompletion=false) { global $DB, $CFG; require_once($CFG->libdir.'/completionlib.php'); $context = get_context_instance(CONTEXT_MODULE, $cm->id); if ($children !== 'ignore' && ($childposts = $DB->get_records('forum_posts', array('parent'=>$post->id)))) { if ($children) { foreach ($childposts as $childpost) { forum_delete_post($childpost, true, $course, $cm, $forum, $skipcompletion); } } else { return false; } } //delete ratings require_once($CFG->dirroot.'/rating/lib.php'); $delopt = new stdClass; $delopt->contextid = $context->id; $delopt->component = 'mod_forum'; $delopt->ratingarea = 'post'; $delopt->itemid = $post->id; $rm = new rating_manager(); $rm->delete_ratings($delopt); //delete attachments $fs = get_file_storage(); $fs->delete_area_files($context->id, 'mod_forum', 'attachment', $post->id); $fs->delete_area_files($context->id, 'mod_forum', 'post', $post->id); if ($DB->delete_records("forum_posts", array("id" => $post->id))) { forum_tp_delete_read_records(-1, $post->id); // Just in case we are deleting the last post forum_discussion_update_last_post($post->discussion); // Update completion state if we are tracking completion based on number of posts // But don't bother when deleting whole thing if (!$skipcompletion) { $completion = new completion_info($course); if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && ($forum->completiondiscussions || $forum->completionreplies || $forum->completionposts)) { $completion->update_state($cm, COMPLETION_INCOMPLETE, $post->userid); } } return true; } return false; } /** * @global object * @param object $post * @param bool $children * @return int */ function forum_count_replies($post, $children=true) { global $DB; $count = 0; if ($children) { if ($childposts = $DB->get_records('forum_posts', array('parent' => $post->id))) { foreach ($childposts as $childpost) { $count ++; // For this child $count += forum_count_replies($childpost, true); } } } else { $count += $DB->count_records('forum_posts', array('parent' => $post->id)); } return $count; } /** * @global object * @param int $forumid * @param mixed $value * @return bool */ function forum_forcesubscribe($forumid, $value=1) { global $DB; return $DB->set_field("forum", "forcesubscribe", $value, array("id" => $forumid)); } /** * @global object * @param object $forum * @return bool */ function forum_is_forcesubscribed($forum) { global $DB; if (isset($forum->forcesubscribe)) { // then we use that return ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE); } else { // Check the database return ($DB->get_field('forum', 'forcesubscribe', array('id' => $forum)) == FORUM_FORCESUBSCRIBE); } } function forum_get_forcesubscribed($forum) { global $DB; if (isset($forum->forcesubscribe)) { // then we use that return $forum->forcesubscribe; } else { // Check the database return $DB->get_field('forum', 'forcesubscribe', array('id' => $forum)); } } /** * @global object * @param int $userid * @param object $forum * @return bool */ function forum_is_subscribed($userid, $forum) { global $DB; if (is_numeric($forum)) { $forum = $DB->get_record('forum', array('id' => $forum)); } // If forum is force subscribed and has allowforcesubscribe, then user is subscribed. $cm = get_coursemodule_from_instance('forum', $forum->id); if (forum_is_forcesubscribed($forum) && $cm && has_capability('mod/forum:allowforcesubscribe', context_module::instance($cm->id), $userid)) { return true; } return $DB->record_exists("forum_subscriptions", array("userid" => $userid, "forum" => $forum->id)); } function forum_get_subscribed_forums($course) { global $USER, $CFG, $DB; $sql = "SELECT f.id FROM {forum} f LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = ?) WHERE f.course = ? AND f.forcesubscribe <> ".FORUM_DISALLOWSUBSCRIBE." AND (f.forcesubscribe = ".FORUM_FORCESUBSCRIBE." OR fs.id IS NOT NULL)"; if ($subscribed = $DB->get_records_sql($sql, array($USER->id, $course->id))) { foreach ($subscribed as $s) { $subscribed[$s->id] = $s->id; } return $subscribed; } else { return array(); } } /** * Returns an array of forums that the current user is subscribed to and is allowed to unsubscribe from * * @return array An array of unsubscribable forums */ function forum_get_optional_subscribed_forums() { global $USER, $DB; // Get courses that $USER is enrolled in and can see $courses = enrol_get_my_courses(); if (empty($courses)) { return array(); } $courseids = array(); foreach($courses as $course) { $courseids[] = $course->id; } list($coursesql, $courseparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'c'); // get all forums from the user's courses that they are subscribed to and which are not set to forced $sql = "SELECT f.id, cm.id as cm, cm.visible FROM {forum} f JOIN {course_modules} cm ON cm.instance = f.id JOIN {modules} m ON m.name = :modulename AND m.id = cm.module LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = :userid) WHERE f.forcesubscribe <> :forcesubscribe AND fs.id IS NOT NULL AND cm.course $coursesql"; $params = array_merge($courseparams, array('modulename'=>'forum', 'userid'=>$USER->id, 'forcesubscribe'=>FORUM_FORCESUBSCRIBE)); if (!$forums = $DB->get_records_sql($sql, $params)) { return array(); } $unsubscribableforums = array(); // Array to return foreach($forums as $forum) { if (empty($forum->visible)) { // the forum is hidden $context = context_module::instance($forum->cm); if (!has_capability('moodle/course:viewhiddenactivities', $context)) { // the user can't see the hidden forum continue; } } // subscribe.php only requires 'mod/forum:managesubscriptions' when // unsubscribing a user other than yourself so we don't require it here either // A check for whether the forum has subscription set to forced is built into the SQL above $unsubscribableforums[] = $forum; } return $unsubscribableforums; } /** * Adds user to the subscriber list * * @global object * @param int $userid * @param int $forumid */ function forum_subscribe($userid, $forumid) { global $DB; if ($DB->record_exists("forum_subscriptions", array("userid"=>$userid, "forum"=>$forumid))) { return true; } $sub = new stdClass(); $sub->userid = $userid; $sub->forum = $forumid; return $DB->insert_record("forum_subscriptions", $sub); } /** * Removes user from the subscriber list * * @global object * @param int $userid * @param int $forumid */ function forum_unsubscribe($userid, $forumid) { global $DB; return $DB->delete_records("forum_subscriptions", array("userid"=>$userid, "forum"=>$forumid)); } /** * Given a new post, subscribes or unsubscribes as appropriate. * Returns some text which describes what happened. * * @global objec * @param object $post * @param object $forum */ function forum_post_subscription($post, $forum) { global $USER; $action = ''; $subscribed = forum_is_subscribed($USER->id, $forum); if ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE) { // database ignored return ""; } elseif (($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE) && !has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $forum->course), $USER->id)) { if ($subscribed) { $action = 'unsubscribe'; // sanity check, following MDL-14558 } else { return ""; } } else { // go with the user's choice if (isset($post->subscribe)) { // no change if ((!empty($post->subscribe) && $subscribed) || (empty($post->subscribe) && !$subscribed)) { return ""; } elseif (!empty($post->subscribe) && !$subscribed) { $action = 'subscribe'; } elseif (empty($post->subscribe) && $subscribed) { $action = 'unsubscribe'; } } } $info = new stdClass(); $info->name = fullname($USER); $info->forum = format_string($forum->name); switch ($action) { case 'subscribe': forum_subscribe($USER->id, $post->forum); return "

".get_string("nowsubscribed", "forum", $info)."

"; case 'unsubscribe': forum_unsubscribe($USER->id, $post->forum); return "

".get_string("nownotsubscribed", "forum", $info)."

"; } } /** * Generate and return the subscribe or unsubscribe link for a forum. * * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe. * @param object $context the context object for this forum. * @param array $messages text used for the link in its various states * (subscribed, unsubscribed, forcesubscribed or cantsubscribe). * Any strings not passed in are taken from the $defaultmessages array * at the top of the function. * @param bool $cantaccessagroup * @param bool $fakelink * @param bool $backtoindex * @param array $subscribed_forums * @return string */ function forum_get_subscribe_link($forum, $context, $messages = array(), $cantaccessagroup = false, $fakelink=true, $backtoindex=false, $subscribed_forums=null) { global $CFG, $USER, $PAGE, $OUTPUT; $defaultmessages = array( 'subscribed' => get_string('unsubscribe', 'forum'), 'unsubscribed' => get_string('subscribe', 'forum'), 'cantaccessgroup' => get_string('no'), 'forcesubscribed' => get_string('everyoneissubscribed', 'forum'), 'cantsubscribe' => get_string('disallowsubscribe','forum') ); $messages = $messages + $defaultmessages; if (forum_is_forcesubscribed($forum)) { return $messages['forcesubscribed']; } else if ($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE && !has_capability('mod/forum:managesubscriptions', $context)) { return $messages['cantsubscribe']; } else if ($cantaccessagroup) { return $messages['cantaccessgroup']; } else { if (!is_enrolled($context, $USER, '', true)) { return ''; } if (is_null($subscribed_forums)) { $subscribed = forum_is_subscribed($USER->id, $forum); } else { $subscribed = !empty($subscribed_forums[$forum->id]); } if ($subscribed) { $linktext = $messages['subscribed']; $linktitle = get_string('subscribestop', 'forum'); } else { $linktext = $messages['unsubscribed']; $linktitle = get_string('subscribestart', 'forum'); } $options = array(); if ($backtoindex) { $backtoindexlink = '&backtoindex=1'; $options['backtoindex'] = 1; } else { $backtoindexlink = ''; } $link = ''; if ($fakelink) { $PAGE->requires->js('/mod/forum/forum.js'); $PAGE->requires->js_function_call('forum_produce_subscribe_link', array($forum->id, $backtoindexlink, $linktext, $linktitle)); $link = "'; } return $link; } } /** * Generate and return the track or no track link for a forum. * * @global object * @global object * @global object * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe. * @param array $messages * @param bool $fakelink * @return string */ function forum_get_tracking_link($forum, $messages=array(), $fakelink=true) { global $CFG, $USER, $PAGE, $OUTPUT; static $strnotrackforum, $strtrackforum; if (isset($messages['trackforum'])) { $strtrackforum = $messages['trackforum']; } if (isset($messages['notrackforum'])) { $strnotrackforum = $messages['notrackforum']; } if (empty($strtrackforum)) { $strtrackforum = get_string('trackforum', 'forum'); } if (empty($strnotrackforum)) { $strnotrackforum = get_string('notrackforum', 'forum'); } if (forum_tp_is_tracked($forum)) { $linktitle = $strnotrackforum; $linktext = $strnotrackforum; } else { $linktitle = $strtrackforum; $linktext = $strtrackforum; } $link = ''; if ($fakelink) { $PAGE->requires->js('/mod/forum/forum.js'); $PAGE->requires->js_function_call('forum_produce_tracking_link', Array($forum->id, $linktext, $linktitle)); // use