MDL-37111 copy first post files in single type forum using intro options
[moodle.git] / mod / forum / lib.php
blobb515244f3b7ea91a309a8ae0784ca2a84877a35c
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * @package mod
19 * @subpackage forum
20 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 defined('MOODLE_INTERNAL') || die();
26 /** Include required files */
27 require_once($CFG->libdir.'/filelib.php');
28 require_once($CFG->libdir.'/eventslib.php');
29 require_once($CFG->dirroot.'/user/selector/lib.php');
30 require_once($CFG->dirroot.'/mod/forum/post_form.php');
32 /// CONSTANTS ///////////////////////////////////////////////////////////
34 define('FORUM_MODE_FLATOLDEST', 1);
35 define('FORUM_MODE_FLATNEWEST', -1);
36 define('FORUM_MODE_THREADED', 2);
37 define('FORUM_MODE_NESTED', 3);
39 define('FORUM_CHOOSESUBSCRIBE', 0);
40 define('FORUM_FORCESUBSCRIBE', 1);
41 define('FORUM_INITIALSUBSCRIBE', 2);
42 define('FORUM_DISALLOWSUBSCRIBE',3);
44 define('FORUM_TRACKING_OFF', 0);
45 define('FORUM_TRACKING_OPTIONAL', 1);
46 define('FORUM_TRACKING_ON', 2);
48 if (!defined('FORUM_CRON_USER_CACHE')) {
49 /** Defines how many full user records are cached in forum cron. */
50 define('FORUM_CRON_USER_CACHE', 5000);
53 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
55 /**
56 * Given an object containing all the necessary data,
57 * (defined by the form in mod_form.php) this function
58 * will create a new instance and return the id number
59 * of the new instance.
61 * @param stdClass $forum add forum instance
62 * @param mod_forum_mod_form $mform
63 * @return int intance id
65 function forum_add_instance($forum, $mform = null) {
66 global $CFG, $DB;
68 $forum->timemodified = time();
70 if (empty($forum->assessed)) {
71 $forum->assessed = 0;
74 if (empty($forum->ratingtime) or empty($forum->assessed)) {
75 $forum->assesstimestart = 0;
76 $forum->assesstimefinish = 0;
79 $forum->id = $DB->insert_record('forum', $forum);
80 $modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule);
82 if ($forum->type == 'single') { // Create related discussion.
83 $discussion = new stdClass();
84 $discussion->course = $forum->course;
85 $discussion->forum = $forum->id;
86 $discussion->name = $forum->name;
87 $discussion->assessed = $forum->assessed;
88 $discussion->message = $forum->intro;
89 $discussion->messageformat = $forum->introformat;
90 $discussion->messagetrust = trusttext_trusted(get_context_instance(CONTEXT_COURSE, $forum->course));
91 $discussion->mailnow = false;
92 $discussion->groupid = -1;
94 $message = '';
96 $discussion->id = forum_add_discussion($discussion, null, $message);
98 if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
99 // Ugly hack - we need to copy the files somehow.
100 $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
101 $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
103 $options = array('subdirs'=>true); // Use the same options as intro field!
104 $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
105 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
109 if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
110 /// all users should be subscribed initially
111 /// Note: forum_get_potential_subscribers should take the forum context,
112 /// but that does not exist yet, becuase the forum is only half build at this
113 /// stage. However, because the forum is brand new, we know that there are
114 /// no role assignments or overrides in the forum context, so using the
115 /// course context gives the same list of users.
116 $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
117 foreach ($users as $user) {
118 forum_subscribe($user->id, $forum->id);
122 forum_grade_item_update($forum);
124 return $forum->id;
129 * Given an object containing all the necessary data,
130 * (defined by the form in mod_form.php) this function
131 * will update an existing instance with new data.
133 * @global object
134 * @param object $forum forum instance (with magic quotes)
135 * @return bool success
137 function forum_update_instance($forum, $mform) {
138 global $DB, $OUTPUT, $USER;
140 $forum->timemodified = time();
141 $forum->id = $forum->instance;
143 if (empty($forum->assessed)) {
144 $forum->assessed = 0;
147 if (empty($forum->ratingtime) or empty($forum->assessed)) {
148 $forum->assesstimestart = 0;
149 $forum->assesstimefinish = 0;
152 $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
154 // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
155 // if scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
156 // 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
157 if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
158 forum_update_grades($forum); // recalculate grades for the forum
161 if ($forum->type == 'single') { // Update related discussion and post.
162 $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
163 if (!empty($discussions)) {
164 if (count($discussions) > 1) {
165 echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
167 $discussion = array_pop($discussions);
168 } else {
169 // try to recover by creating initial discussion - MDL-16262
170 $discussion = new stdClass();
171 $discussion->course = $forum->course;
172 $discussion->forum = $forum->id;
173 $discussion->name = $forum->name;
174 $discussion->assessed = $forum->assessed;
175 $discussion->message = $forum->intro;
176 $discussion->messageformat = $forum->introformat;
177 $discussion->messagetrust = true;
178 $discussion->mailnow = false;
179 $discussion->groupid = -1;
181 $message = '';
183 forum_add_discussion($discussion, null, $message);
185 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
186 print_error('cannotadd', 'forum');
189 if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
190 print_error('cannotfindfirstpost', 'forum');
193 $cm = get_coursemodule_from_instance('forum', $forum->id);
194 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
196 $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
197 $post->subject = $forum->name;
198 $post->message = $forum->intro;
199 $post->messageformat = $forum->introformat;
200 $post->messagetrust = trusttext_trusted($modcontext);
201 $post->modified = $forum->timemodified;
202 $post->userid = $USER->id; // MDL-18599, so that current teacher can take ownership of activities.
204 if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
205 // Ugly hack - we need to copy the files somehow.
206 $options = array('subdirs'=>true); // Use the same options as intro field!
207 $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
210 $DB->update_record('forum_posts', $post);
211 $discussion->name = $forum->name;
212 $DB->update_record('forum_discussions', $discussion);
215 $DB->update_record('forum', $forum);
217 forum_grade_item_update($forum);
219 return true;
224 * Given an ID of an instance of this module,
225 * this function will permanently delete the instance
226 * and any data that depends on it.
228 * @global object
229 * @param int $id forum instance id
230 * @return bool success
232 function forum_delete_instance($id) {
233 global $DB;
235 if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
236 return false;
238 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
239 return false;
241 if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
242 return false;
245 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
247 // now get rid of all files
248 $fs = get_file_storage();
249 $fs->delete_area_files($context->id);
251 $result = true;
253 if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
254 foreach ($discussions as $discussion) {
255 if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
256 $result = false;
261 if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
262 $result = false;
265 forum_tp_delete_read_records(-1, -1, -1, $forum->id);
267 if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
268 $result = false;
271 forum_grade_item_delete($forum);
273 return $result;
278 * Indicates API features that the forum supports.
280 * @uses FEATURE_GROUPS
281 * @uses FEATURE_GROUPINGS
282 * @uses FEATURE_GROUPMEMBERSONLY
283 * @uses FEATURE_MOD_INTRO
284 * @uses FEATURE_COMPLETION_TRACKS_VIEWS
285 * @uses FEATURE_COMPLETION_HAS_RULES
286 * @uses FEATURE_GRADE_HAS_GRADE
287 * @uses FEATURE_GRADE_OUTCOMES
288 * @param string $feature
289 * @return mixed True if yes (some features may use other values)
291 function forum_supports($feature) {
292 switch($feature) {
293 case FEATURE_GROUPS: return true;
294 case FEATURE_GROUPINGS: return true;
295 case FEATURE_GROUPMEMBERSONLY: return true;
296 case FEATURE_MOD_INTRO: return true;
297 case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
298 case FEATURE_COMPLETION_HAS_RULES: return true;
299 case FEATURE_GRADE_HAS_GRADE: return true;
300 case FEATURE_GRADE_OUTCOMES: return true;
301 case FEATURE_RATE: return true;
302 case FEATURE_BACKUP_MOODLE2: return true;
303 case FEATURE_SHOW_DESCRIPTION: return true;
305 default: return null;
311 * Obtains the automatic completion state for this forum based on any conditions
312 * in forum settings.
314 * @global object
315 * @global object
316 * @param object $course Course
317 * @param object $cm Course-module
318 * @param int $userid User ID
319 * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
320 * @return bool True if completed, false if not. (If no conditions, then return
321 * value depends on comparison type)
323 function forum_get_completion_state($course,$cm,$userid,$type) {
324 global $CFG,$DB;
326 // Get forum details
327 if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
328 throw new Exception("Can't find forum {$cm->instance}");
331 $result=$type; // Default return value
333 $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
334 $postcountsql="
335 SELECT
336 COUNT(1)
337 FROM
338 {forum_posts} fp
339 INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
340 WHERE
341 fp.userid=:userid AND fd.forum=:forumid";
343 if ($forum->completiondiscussions) {
344 $value = $forum->completiondiscussions <=
345 $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
346 if ($type == COMPLETION_AND) {
347 $result = $result && $value;
348 } else {
349 $result = $result || $value;
352 if ($forum->completionreplies) {
353 $value = $forum->completionreplies <=
354 $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
355 if ($type==COMPLETION_AND) {
356 $result = $result && $value;
357 } else {
358 $result = $result || $value;
361 if ($forum->completionposts) {
362 $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
363 if ($type == COMPLETION_AND) {
364 $result = $result && $value;
365 } else {
366 $result = $result || $value;
370 return $result;
374 * Create a message-id string to use in the custom headers of forum notification emails
376 * message-id is used by email clients to identify emails and to nest conversations
378 * @param int $postid The ID of the forum post we are notifying the user about
379 * @param int $usertoid The ID of the user being notified
380 * @param string $hostname The server's hostname
381 * @return string A unique message-id
383 function forum_get_email_message_id($postid, $usertoid, $hostname) {
384 return '<'.hash('sha256',$postid.'to'.$usertoid).'@'.$hostname.'>';
388 * Removes properties from user record that are not necessary
389 * for sending post notifications.
390 * @param stdClass $user
391 * @return void, $user parameter is modified
393 function forum_cron_minimise_user_record(stdClass $user) {
395 // We store large amount of users in one huge array,
396 // make sure we do not store info there we do not actually need
397 // in mail generation code or messaging.
399 unset($user->institution);
400 unset($user->department);
401 unset($user->address);
402 unset($user->city);
403 unset($user->url);
404 unset($user->currentlogin);
405 unset($user->description);
406 unset($user->descriptionformat);
410 * Function to be run periodically according to the moodle cron
411 * Finds all posts that have yet to be mailed out, and mails them
412 * out to all subscribers
414 * @global object
415 * @global object
416 * @global object
417 * @uses CONTEXT_MODULE
418 * @uses CONTEXT_COURSE
419 * @uses SITEID
420 * @uses FORMAT_PLAIN
421 * @return void
423 function forum_cron() {
424 global $CFG, $USER, $DB;
426 $site = get_site();
428 // All users that are subscribed to any post that needs sending,
429 // please increase $CFG->extramemorylimit on large sites that
430 // send notifications to a large number of users.
431 $users = array();
432 $userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!!
434 // status arrays
435 $mailcount = array();
436 $errorcount = array();
438 // caches
439 $discussions = array();
440 $forums = array();
441 $courses = array();
442 $coursemodules = array();
443 $subscribedusers = array();
446 // Posts older than 2 days will not be mailed. This is to avoid the problem where
447 // cron has not been running for a long time, and then suddenly people are flooded
448 // with mail from the past few weeks or months
449 $timenow = time();
450 $endtime = $timenow - $CFG->maxeditingtime;
451 $starttime = $endtime - 48 * 3600; // Two days earlier
453 if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
454 // Mark them all now as being mailed. It's unlikely but possible there
455 // might be an error later so that a post is NOT actually mailed out,
456 // but since mail isn't crucial, we can accept this risk. Doing it now
457 // prevents the risk of duplicated mails, which is a worse problem.
459 if (!forum_mark_old_posts_as_mailed($endtime)) {
460 mtrace('Errors occurred while trying to mark some posts as being mailed.');
461 return false; // Don't continue trying to mail them, in case we are in a cron loop
464 // checking post validity, and adding users to loop through later
465 foreach ($posts as $pid => $post) {
467 $discussionid = $post->discussion;
468 if (!isset($discussions[$discussionid])) {
469 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
470 $discussions[$discussionid] = $discussion;
471 } else {
472 mtrace('Could not find discussion '.$discussionid);
473 unset($posts[$pid]);
474 continue;
477 $forumid = $discussions[$discussionid]->forum;
478 if (!isset($forums[$forumid])) {
479 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
480 $forums[$forumid] = $forum;
481 } else {
482 mtrace('Could not find forum '.$forumid);
483 unset($posts[$pid]);
484 continue;
487 $courseid = $forums[$forumid]->course;
488 if (!isset($courses[$courseid])) {
489 if ($course = $DB->get_record('course', array('id' => $courseid))) {
490 $courses[$courseid] = $course;
491 } else {
492 mtrace('Could not find course '.$courseid);
493 unset($posts[$pid]);
494 continue;
497 if (!isset($coursemodules[$forumid])) {
498 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
499 $coursemodules[$forumid] = $cm;
500 } else {
501 mtrace('Could not find course module for forum '.$forumid);
502 unset($posts[$pid]);
503 continue;
508 // caching subscribed users of each forum
509 if (!isset($subscribedusers[$forumid])) {
510 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
511 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
512 foreach ($subusers as $postuser) {
513 // this user is subscribed to this forum
514 $subscribedusers[$forumid][$postuser->id] = $postuser->id;
515 $userscount++;
516 if ($userscount > FORUM_CRON_USER_CACHE) {
517 // Store minimal user info.
518 $minuser = new stdClass();
519 $minuser->id = $postuser->id;
520 $users[$postuser->id] = $minuser;
521 } else {
522 // Cache full user record.
523 forum_cron_minimise_user_record($postuser);
524 $users[$postuser->id] = $postuser;
527 // Release memory.
528 unset($subusers);
529 unset($postuser);
533 $mailcount[$pid] = 0;
534 $errorcount[$pid] = 0;
538 if ($users && $posts) {
540 $urlinfo = parse_url($CFG->wwwroot);
541 $hostname = $urlinfo['host'];
543 foreach ($users as $userto) {
545 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
547 mtrace('Processing user '.$userto->id);
549 // Init user caches - we keep the cache for one cycle only,
550 // otherwise it could consume too much memory.
551 if (isset($userto->username)) {
552 $userto = clone($userto);
553 } else {
554 $userto = $DB->get_record('user', array('id' => $userto->id));
555 forum_cron_minimise_user_record($userto);
557 $userto->viewfullnames = array();
558 $userto->canpost = array();
559 $userto->markposts = array();
561 // set this so that the capabilities are cached, and environment matches receiving user
562 cron_setup_user($userto);
564 // reset the caches
565 foreach ($coursemodules as $forumid=>$unused) {
566 $coursemodules[$forumid]->cache = new stdClass();
567 $coursemodules[$forumid]->cache->caps = array();
568 unset($coursemodules[$forumid]->uservisible);
571 foreach ($posts as $pid => $post) {
573 // Set up the environment for the post, discussion, forum, course
574 $discussion = $discussions[$post->discussion];
575 $forum = $forums[$discussion->forum];
576 $course = $courses[$forum->course];
577 $cm =& $coursemodules[$forum->id];
579 // Do some checks to see if we can bail out now
580 // Only active enrolled users are in the list of subscribers
581 if (!isset($subscribedusers[$forum->id][$userto->id])) {
582 continue; // user does not subscribe to this forum
585 // Don't send email if the forum is Q&A and the user has not posted
586 // Initial topics are still mailed
587 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
588 mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
589 continue;
592 // Get info about the sending user
593 if (array_key_exists($post->userid, $users)) { // we might know him/her already
594 $userfrom = $users[$post->userid];
595 if (!isset($userfrom->idnumber)) {
596 // Minimalised user info, fetch full record.
597 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
598 forum_cron_minimise_user_record($userfrom);
601 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
602 forum_cron_minimise_user_record($userfrom);
603 // Fetch only once if possible, we can add it to user list, it will be skipped anyway.
604 if ($userscount <= FORUM_CRON_USER_CACHE) {
605 $userscount++;
606 $users[$userfrom->id] = $userfrom;
609 } else {
610 mtrace('Could not find user '.$post->userid);
611 continue;
614 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
616 // setup global $COURSE properly - needed for roles and languages
617 cron_setup_user($userto, $course);
619 // Fill caches
620 if (!isset($userto->viewfullnames[$forum->id])) {
621 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
622 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
624 if (!isset($userto->canpost[$discussion->id])) {
625 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
626 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
628 if (!isset($userfrom->groups[$forum->id])) {
629 if (!isset($userfrom->groups)) {
630 $userfrom->groups = array();
631 if (isset($users[$userfrom->id])) {
632 $users[$userfrom->id]->groups = array();
635 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
636 if (isset($users[$userfrom->id])) {
637 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
641 // Make sure groups allow this user to see this email
642 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
643 if (!groups_group_exists($discussion->groupid)) { // Can't find group
644 continue; // Be safe and don't send it to anyone
647 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
648 // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
649 continue;
653 // Make sure we're allowed to see it...
654 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
655 mtrace('user '.$userto->id. ' can not see '.$post->id);
656 continue;
659 // OK so we need to send the email.
661 // Does the user want this post in a digest? If so postpone it for now.
662 if ($userto->maildigest > 0) {
663 // This user wants the mails to be in digest form
664 $queue = new stdClass();
665 $queue->userid = $userto->id;
666 $queue->discussionid = $discussion->id;
667 $queue->postid = $post->id;
668 $queue->timemodified = $post->created;
669 $DB->insert_record('forum_queue', $queue);
670 continue;
674 // Prepare to actually send the post now, and build up the content
676 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
678 $userfrom->customheaders = array ( // Headers to make emails easier to track
679 'Precedence: Bulk',
680 'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
681 'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
682 'Message-ID: '.forum_get_email_message_id($post->id, $userto->id, $hostname),
683 'X-Course-Id: '.$course->id,
684 'X-Course-Name: '.format_string($course->fullname, true)
687 if ($post->parent) { // This post is a reply, so add headers for threading (see MDL-22551)
688 $userfrom->customheaders[] = 'In-Reply-To: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
689 $userfrom->customheaders[] = 'References: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
692 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
694 $postsubject = html_to_text("$shortname: ".format_string($post->subject, true));
695 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
696 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
698 // Send the post now!
700 mtrace('Sending ', '');
702 $eventdata = new stdClass();
703 $eventdata->component = 'mod_forum';
704 $eventdata->name = 'posts';
705 $eventdata->userfrom = $userfrom;
706 $eventdata->userto = $userto;
707 $eventdata->subject = $postsubject;
708 $eventdata->fullmessage = $posttext;
709 $eventdata->fullmessageformat = FORMAT_PLAIN;
710 $eventdata->fullmessagehtml = $posthtml;
711 $eventdata->notification = 1;
713 $smallmessagestrings = new stdClass();
714 $smallmessagestrings->user = fullname($userfrom);
715 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
716 $smallmessagestrings->message = $post->message;
717 //make sure strings are in message recipients language
718 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
720 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
721 $eventdata->contexturlname = $discussion->name;
723 $mailresult = message_send($eventdata);
724 if (!$mailresult){
725 mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
726 " ($userto->email) .. not trying again.");
727 add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
728 substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
729 $errorcount[$post->id]++;
730 } else {
731 $mailcount[$post->id]++;
733 // Mark post as read if forum_usermarksread is set off
734 if (!$CFG->forum_usermarksread) {
735 $userto->markposts[$post->id] = $post->id;
739 mtrace('post '.$post->id. ': '.$post->subject);
742 // mark processed posts as read
743 forum_tp_mark_posts_read($userto, $userto->markposts);
744 unset($userto);
748 if ($posts) {
749 foreach ($posts as $post) {
750 mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
751 if ($errorcount[$post->id]) {
752 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
757 // release some memory
758 unset($subscribedusers);
759 unset($mailcount);
760 unset($errorcount);
762 cron_setup_user();
764 $sitetimezone = $CFG->timezone;
766 // Now see if there are any digest mails waiting to be sent, and if we should send them
768 mtrace('Starting digest processing...');
770 @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
772 if (!isset($CFG->digestmailtimelast)) { // To catch the first time
773 set_config('digestmailtimelast', 0);
776 $timenow = time();
777 $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
779 // Delete any really old ones (normally there shouldn't be any)
780 $weekago = $timenow - (7 * 24 * 3600);
781 $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
782 mtrace ('Cleaned old digest records');
784 if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
786 mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
788 $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
790 if ($digestposts_rs->valid()) {
792 // We have work to do
793 $usermailcount = 0;
795 //caches - reuse the those filled before too
796 $discussionposts = array();
797 $userdiscussions = array();
799 foreach ($digestposts_rs as $digestpost) {
800 if (!isset($posts[$digestpost->postid])) {
801 if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
802 $posts[$digestpost->postid] = $post;
803 } else {
804 continue;
807 $discussionid = $digestpost->discussionid;
808 if (!isset($discussions[$discussionid])) {
809 if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
810 $discussions[$discussionid] = $discussion;
811 } else {
812 continue;
815 $forumid = $discussions[$discussionid]->forum;
816 if (!isset($forums[$forumid])) {
817 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
818 $forums[$forumid] = $forum;
819 } else {
820 continue;
824 $courseid = $forums[$forumid]->course;
825 if (!isset($courses[$courseid])) {
826 if ($course = $DB->get_record('course', array('id' => $courseid))) {
827 $courses[$courseid] = $course;
828 } else {
829 continue;
833 if (!isset($coursemodules[$forumid])) {
834 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
835 $coursemodules[$forumid] = $cm;
836 } else {
837 continue;
840 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
841 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
843 $digestposts_rs->close(); /// Finished iteration, let's close the resultset
845 // Data collected, start sending out emails to each user
846 foreach ($userdiscussions as $userid => $thesediscussions) {
848 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
850 cron_setup_user();
852 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
854 // First of all delete all the queue entries for this user
855 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
857 // Init user caches - we keep the cache for one cycle only,
858 // otherwise it would unnecessarily consume memory.
859 if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
860 $userto = clone($users[$userid]);
861 } else {
862 $userto = $DB->get_record('user', array('id' => $userid));
863 forum_cron_minimise_user_record($userto);
865 $userto->viewfullnames = array();
866 $userto->canpost = array();
867 $userto->markposts = array();
869 // Override the language and timezone of the "current" user, so that
870 // mail is customised for the receiver.
871 cron_setup_user($userto);
873 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
875 $headerdata = new stdClass();
876 $headerdata->sitename = format_string($site->fullname, true);
877 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
879 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
880 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
882 $posthtml = "<head>";
883 /* foreach ($CFG->stylesheets as $stylesheet) {
884 //TODO: MDL-21120
885 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
887 $posthtml .= "</head>\n<body id=\"email\">\n";
888 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
890 foreach ($thesediscussions as $discussionid) {
892 @set_time_limit(120); // to be reset for each post
894 $discussion = $discussions[$discussionid];
895 $forum = $forums[$discussion->forum];
896 $course = $courses[$forum->course];
897 $cm = $coursemodules[$forum->id];
899 //override language
900 cron_setup_user($userto, $course);
902 // Fill caches
903 if (!isset($userto->viewfullnames[$forum->id])) {
904 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
905 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
907 if (!isset($userto->canpost[$discussion->id])) {
908 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
909 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
912 $strforums = get_string('forums', 'forum');
913 $canunsubscribe = ! forum_is_forcesubscribed($forum);
914 $canreply = $userto->canpost[$discussion->id];
915 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
917 $posttext .= "\n \n";
918 $posttext .= '=====================================================================';
919 $posttext .= "\n \n";
920 $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
921 if ($discussion->name != $forum->name) {
922 $posttext .= " -> ".format_string($discussion->name,true);
924 $posttext .= "\n";
926 $posthtml .= "<p><font face=\"sans-serif\">".
927 "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
928 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
929 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
930 if ($discussion->name == $forum->name) {
931 $posthtml .= "</font></p>";
932 } else {
933 $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
935 $posthtml .= '<p>';
937 $postsarray = $discussionposts[$discussionid];
938 sort($postsarray);
940 foreach ($postsarray as $postid) {
941 $post = $posts[$postid];
943 if (array_key_exists($post->userid, $users)) { // we might know him/her already
944 $userfrom = $users[$post->userid];
945 if (!isset($userfrom->idnumber)) {
946 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
947 forum_cron_minimise_user_record($userfrom);
950 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
951 forum_cron_minimise_user_record($userfrom);
952 if ($userscount <= FORUM_CRON_USER_CACHE) {
953 $userscount++;
954 $users[$userfrom->id] = $userfrom;
957 } else {
958 mtrace('Could not find user '.$post->userid);
959 continue;
962 if (!isset($userfrom->groups[$forum->id])) {
963 if (!isset($userfrom->groups)) {
964 $userfrom->groups = array();
965 if (isset($users[$userfrom->id])) {
966 $users[$userfrom->id]->groups = array();
969 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
970 if (isset($users[$userfrom->id])) {
971 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
975 $userfrom->customheaders = array ("Precedence: Bulk");
977 if ($userto->maildigest == 2) {
978 // Subjects only
979 $by = new stdClass();
980 $by->name = fullname($userfrom);
981 $by->date = userdate($post->modified);
982 $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
983 $posttext .= "\n---------------------------------------------------------------------";
985 $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
986 $posthtml .= '<div><a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'#p'.$post->id.'">'.format_string($post->subject,true).'</a> '.get_string("bynameondate", "forum", $by).'</div>';
988 } else {
989 // The full treatment
990 $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
991 $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
993 // Create an array of postid's for this user to mark as read.
994 if (!$CFG->forum_usermarksread) {
995 $userto->markposts[$post->id] = $post->id;
999 if ($canunsubscribe) {
1000 $posthtml .= "\n<div class='mdl-right'><font size=\"1\"><a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">".get_string("unsubscribe", "forum")."</a></font></div>";
1001 } else {
1002 $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
1004 $posthtml .= '<hr size="1" noshade="noshade" /></p>';
1006 $posthtml .= '</body>';
1008 if (empty($userto->mailformat) || $userto->mailformat != 1) {
1009 // This user DOESN'T want to receive HTML
1010 $posthtml = '';
1013 $attachment = $attachname='';
1014 $usetrueaddress = true;
1015 // Directly email forum digests rather than sending them via messaging, use the
1016 // site shortname as 'from name', the noreply address will be used by email_to_user.
1017 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
1019 if (!$mailresult) {
1020 mtrace("ERROR!");
1021 echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
1022 add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
1023 } else {
1024 mtrace("success.");
1025 $usermailcount++;
1027 // Mark post as read if forum_usermarksread is set off
1028 forum_tp_mark_posts_read($userto, $userto->markposts);
1032 /// We have finishied all digest emails, update $CFG->digestmailtimelast
1033 set_config('digestmailtimelast', $timenow);
1036 cron_setup_user();
1038 if (!empty($usermailcount)) {
1039 mtrace(get_string('digestsentusers', 'forum', $usermailcount));
1042 if (!empty($CFG->forum_lastreadclean)) {
1043 $timenow = time();
1044 if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
1045 set_config('forum_lastreadclean', $timenow);
1046 mtrace('Removing old forum read tracking info...');
1047 forum_tp_clean_read_records();
1049 } else {
1050 set_config('forum_lastreadclean', time());
1054 return true;
1058 * Builds and returns the body of the email notification in plain text.
1060 * @global object
1061 * @global object
1062 * @uses CONTEXT_MODULE
1063 * @param object $course
1064 * @param object $cm
1065 * @param object $forum
1066 * @param object $discussion
1067 * @param object $post
1068 * @param object $userfrom
1069 * @param object $userto
1070 * @param boolean $bare
1071 * @return string The email body in plain text format.
1073 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
1074 global $CFG, $USER;
1076 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1078 if (!isset($userto->viewfullnames[$forum->id])) {
1079 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1080 } else {
1081 $viewfullnames = $userto->viewfullnames[$forum->id];
1084 if (!isset($userto->canpost[$discussion->id])) {
1085 $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1086 } else {
1087 $canreply = $userto->canpost[$discussion->id];
1090 $by = New stdClass;
1091 $by->name = fullname($userfrom, $viewfullnames);
1092 $by->date = userdate($post->modified, "", $userto->timezone);
1094 $strbynameondate = get_string('bynameondate', 'forum', $by);
1096 $strforums = get_string('forums', 'forum');
1098 $canunsubscribe = ! forum_is_forcesubscribed($forum);
1100 $posttext = '';
1102 if (!$bare) {
1103 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1104 $posttext = "$shortname -> $strforums -> ".format_string($forum->name,true);
1106 if ($discussion->name != $forum->name) {
1107 $posttext .= " -> ".format_string($discussion->name,true);
1111 // add absolute file links
1112 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1114 $posttext .= "\n---------------------------------------------------------------------\n";
1115 $posttext .= format_string($post->subject,true);
1116 if ($bare) {
1117 $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1119 $posttext .= "\n".$strbynameondate."\n";
1120 $posttext .= "---------------------------------------------------------------------\n";
1121 $posttext .= format_text_email($post->message, $post->messageformat);
1122 $posttext .= "\n\n";
1123 $posttext .= forum_print_attachments($post, $cm, "text");
1125 if (!$bare && $canreply) {
1126 $posttext .= "---------------------------------------------------------------------\n";
1127 $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1128 $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1130 if (!$bare && $canunsubscribe) {
1131 $posttext .= "\n---------------------------------------------------------------------\n";
1132 $posttext .= get_string("unsubscribe", "forum");
1133 $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1136 return $posttext;
1140 * Builds and returns the body of the email notification in html format.
1142 * @global object
1143 * @param object $course
1144 * @param object $cm
1145 * @param object $forum
1146 * @param object $discussion
1147 * @param object $post
1148 * @param object $userfrom
1149 * @param object $userto
1150 * @return string The email text in HTML format
1152 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1153 global $CFG;
1155 if ($userto->mailformat != 1) { // Needs to be HTML
1156 return '';
1159 if (!isset($userto->canpost[$discussion->id])) {
1160 $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1161 } else {
1162 $canreply = $userto->canpost[$discussion->id];
1165 $strforums = get_string('forums', 'forum');
1166 $canunsubscribe = ! forum_is_forcesubscribed($forum);
1167 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1169 $posthtml = '<head>';
1170 /* foreach ($CFG->stylesheets as $stylesheet) {
1171 //TODO: MDL-21120
1172 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1174 $posthtml .= '</head>';
1175 $posthtml .= "\n<body id=\"email\">\n\n";
1177 $posthtml .= '<div class="navbar">'.
1178 '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1179 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1180 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1181 if ($discussion->name == $forum->name) {
1182 $posthtml .= '</div>';
1183 } else {
1184 $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1185 format_string($discussion->name,true).'</a></div>';
1187 $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1189 if ($canunsubscribe) {
1190 $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1191 <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1192 <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1195 $posthtml .= '</body>';
1197 return $posthtml;
1203 * @param object $course
1204 * @param object $user
1205 * @param object $mod TODO this is not used in this function, refactor
1206 * @param object $forum
1207 * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1209 function forum_user_outline($course, $user, $mod, $forum) {
1210 global $CFG;
1211 require_once("$CFG->libdir/gradelib.php");
1212 $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1213 if (empty($grades->items[0]->grades)) {
1214 $grade = false;
1215 } else {
1216 $grade = reset($grades->items[0]->grades);
1219 $count = forum_count_user_posts($forum->id, $user->id);
1221 if ($count && $count->postcount > 0) {
1222 $result = new stdClass();
1223 $result->info = get_string("numposts", "forum", $count->postcount);
1224 $result->time = $count->lastpost;
1225 if ($grade) {
1226 $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1228 return $result;
1229 } else if ($grade) {
1230 $result = new stdClass();
1231 $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1233 //datesubmitted == time created. dategraded == time modified or time overridden
1234 //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1235 //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1236 if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1237 $result->time = $grade->dategraded;
1238 } else {
1239 $result->time = $grade->datesubmitted;
1242 return $result;
1244 return NULL;
1249 * @global object
1250 * @global object
1251 * @param object $coure
1252 * @param object $user
1253 * @param object $mod
1254 * @param object $forum
1256 function forum_user_complete($course, $user, $mod, $forum) {
1257 global $CFG,$USER, $OUTPUT;
1258 require_once("$CFG->libdir/gradelib.php");
1260 $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1261 if (!empty($grades->items[0]->grades)) {
1262 $grade = reset($grades->items[0]->grades);
1263 echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1264 if ($grade->str_feedback) {
1265 echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1269 if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1271 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1272 print_error('invalidcoursemodule');
1274 $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1276 foreach ($posts as $post) {
1277 if (!isset($discussions[$post->discussion])) {
1278 continue;
1280 $discussion = $discussions[$post->discussion];
1282 forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1284 } else {
1285 echo "<p>".get_string("noposts", "forum")."</p>";
1295 * @global object
1296 * @global object
1297 * @global object
1298 * @param array $courses
1299 * @param array $htmlarray
1301 function forum_print_overview($courses,&$htmlarray) {
1302 global $USER, $CFG, $DB, $SESSION;
1304 if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1305 return array();
1308 if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1309 return;
1312 // Courses to search for new posts
1313 $coursessqls = array();
1314 $params = array();
1315 foreach ($courses as $course) {
1317 // If the user has never entered into the course all posts are pending
1318 if ($course->lastaccess == 0) {
1319 $coursessqls[] = '(f.course = ?)';
1320 $params[] = $course->id;
1322 // Only posts created after the course last access
1323 } else {
1324 $coursessqls[] = '(f.course = ? AND p.created > ?)';
1325 $params[] = $course->id;
1326 $params[] = $course->lastaccess;
1329 $params[] = $USER->id;
1330 $coursessql = implode(' OR ', $coursessqls);
1332 $sql = "SELECT f.id, COUNT(*) as count "
1333 .'FROM {forum} f '
1334 .'JOIN {forum_discussions} d ON d.forum = f.id '
1335 .'JOIN {forum_posts} p ON p.discussion = d.id '
1336 ."WHERE ($coursessql) "
1337 .'AND p.userid != ? '
1338 .'GROUP BY f.id';
1340 if (!$new = $DB->get_records_sql($sql, $params)) {
1341 $new = array(); // avoid warnings
1344 // also get all forum tracking stuff ONCE.
1345 $trackingforums = array();
1346 foreach ($forums as $forum) {
1347 if (forum_tp_can_track_forums($forum)) {
1348 $trackingforums[$forum->id] = $forum;
1352 if (count($trackingforums) > 0) {
1353 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1354 $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1355 ' FROM {forum_posts} p '.
1356 ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1357 ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1358 $params = array($USER->id);
1360 foreach ($trackingforums as $track) {
1361 $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1362 $params[] = $track->id;
1363 if (isset($SESSION->currentgroup[$track->course])) {
1364 $groupid = $SESSION->currentgroup[$track->course];
1365 } else {
1366 // get first groupid
1367 $groupids = groups_get_all_groups($track->course, $USER->id);
1368 if ($groupids) {
1369 reset($groupids);
1370 $groupid = key($groupids);
1371 $SESSION->currentgroup[$track->course] = $groupid;
1372 } else {
1373 $groupid = 0;
1375 unset($groupids);
1377 $params[] = $groupid;
1379 $sql = substr($sql,0,-3); // take off the last OR
1380 $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1381 $params[] = $cutoffdate;
1383 if (!$unread = $DB->get_records_sql($sql, $params)) {
1384 $unread = array();
1386 } else {
1387 $unread = array();
1390 if (empty($unread) and empty($new)) {
1391 return;
1394 $strforum = get_string('modulename','forum');
1396 foreach ($forums as $forum) {
1397 $str = '';
1398 $count = 0;
1399 $thisunread = 0;
1400 $showunread = false;
1401 // either we have something from logs, or trackposts, or nothing.
1402 if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1403 $count = $new[$forum->id]->count;
1405 if (array_key_exists($forum->id,$unread)) {
1406 $thisunread = $unread[$forum->id]->count;
1407 $showunread = true;
1409 if ($count > 0 || $thisunread > 0) {
1410 $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1411 $forum->name.'</a></div>';
1412 $str .= '<div class="info"><span class="postsincelogin">';
1413 $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
1414 if (!empty($showunread)) {
1415 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
1417 $str .= '</div></div>';
1419 if (!empty($str)) {
1420 if (!array_key_exists($forum->course,$htmlarray)) {
1421 $htmlarray[$forum->course] = array();
1423 if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1424 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1426 $htmlarray[$forum->course]['forum'] .= $str;
1432 * Given a course and a date, prints a summary of all the new
1433 * messages posted in the course since that date
1435 * @global object
1436 * @global object
1437 * @global object
1438 * @uses CONTEXT_MODULE
1439 * @uses VISIBLEGROUPS
1440 * @param object $course
1441 * @param bool $viewfullnames capability
1442 * @param int $timestart
1443 * @return bool success
1445 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1446 global $CFG, $USER, $DB, $OUTPUT;
1448 // do not use log table if possible, it may be huge and is expensive to join with other tables
1450 if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1451 d.timestart, d.timeend, d.userid AS duserid,
1452 u.firstname, u.lastname, u.email, u.picture
1453 FROM {forum_posts} p
1454 JOIN {forum_discussions} d ON d.id = p.discussion
1455 JOIN {forum} f ON f.id = d.forum
1456 JOIN {user} u ON u.id = p.userid
1457 WHERE p.created > ? AND f.course = ?
1458 ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1459 return false;
1462 $modinfo = get_fast_modinfo($course);
1464 $groupmodes = array();
1465 $cms = array();
1467 $strftimerecent = get_string('strftimerecent');
1469 $printposts = array();
1470 foreach ($posts as $post) {
1471 if (!isset($modinfo->instances['forum'][$post->forum])) {
1472 // not visible
1473 continue;
1475 $cm = $modinfo->instances['forum'][$post->forum];
1476 if (!$cm->uservisible) {
1477 continue;
1479 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1481 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1482 continue;
1485 if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1486 and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1487 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1488 continue;
1492 $groupmode = groups_get_activity_groupmode($cm, $course);
1494 if ($groupmode) {
1495 if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1496 // oki (Open discussions have groupid -1)
1497 } else {
1498 // separate mode
1499 if (isguestuser()) {
1500 // shortcut
1501 continue;
1504 if (is_null($modinfo->groups)) {
1505 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1508 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1509 continue;
1514 $printposts[] = $post;
1516 unset($posts);
1518 if (!$printposts) {
1519 return false;
1522 echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1523 echo "\n<ul class='unlist'>\n";
1525 foreach ($printposts as $post) {
1526 $subjectclass = empty($post->parent) ? ' bold' : '';
1528 echo '<li><div class="head">'.
1529 '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1530 '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1531 '</div>';
1532 echo '<div class="info'.$subjectclass.'">';
1533 if (empty($post->parent)) {
1534 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1535 } else {
1536 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1538 $post->subject = break_up_long_words(format_string($post->subject, true));
1539 echo $post->subject;
1540 echo "</a>\"</div></li>\n";
1543 echo "</ul>\n";
1545 return true;
1549 * Return grade for given user or all users.
1551 * @global object
1552 * @global object
1553 * @param object $forum
1554 * @param int $userid optional user id, 0 means all users
1555 * @return array array of grades, false if none
1557 function forum_get_user_grades($forum, $userid = 0) {
1558 global $CFG;
1560 require_once($CFG->dirroot.'/rating/lib.php');
1562 $ratingoptions = new stdClass;
1563 $ratingoptions->component = 'mod_forum';
1564 $ratingoptions->ratingarea = 'post';
1566 //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1567 $ratingoptions->modulename = 'forum';
1568 $ratingoptions->moduleid = $forum->id;
1569 $ratingoptions->userid = $userid;
1570 $ratingoptions->aggregationmethod = $forum->assessed;
1571 $ratingoptions->scaleid = $forum->scale;
1572 $ratingoptions->itemtable = 'forum_posts';
1573 $ratingoptions->itemtableusercolumn = 'userid';
1575 $rm = new rating_manager();
1576 return $rm->get_user_grades($ratingoptions);
1580 * Update activity grades
1582 * @category grade
1583 * @param object $forum
1584 * @param int $userid specific user only, 0 means all
1585 * @param boolean $nullifnone return null if grade does not exist
1586 * @return void
1588 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1589 global $CFG, $DB;
1590 require_once($CFG->libdir.'/gradelib.php');
1592 if (!$forum->assessed) {
1593 forum_grade_item_update($forum);
1595 } else if ($grades = forum_get_user_grades($forum, $userid)) {
1596 forum_grade_item_update($forum, $grades);
1598 } else if ($userid and $nullifnone) {
1599 $grade = new stdClass();
1600 $grade->userid = $userid;
1601 $grade->rawgrade = NULL;
1602 forum_grade_item_update($forum, $grade);
1604 } else {
1605 forum_grade_item_update($forum);
1610 * Update all grades in gradebook.
1611 * @global object
1613 function forum_upgrade_grades() {
1614 global $DB;
1616 $sql = "SELECT COUNT('x')
1617 FROM {forum} f, {course_modules} cm, {modules} m
1618 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1619 $count = $DB->count_records_sql($sql);
1621 $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1622 FROM {forum} f, {course_modules} cm, {modules} m
1623 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1624 $rs = $DB->get_recordset_sql($sql);
1625 if ($rs->valid()) {
1626 $pbar = new progress_bar('forumupgradegrades', 500, true);
1627 $i=0;
1628 foreach ($rs as $forum) {
1629 $i++;
1630 upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1631 forum_update_grades($forum, 0, false);
1632 $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1635 $rs->close();
1639 * Create/update grade item for given forum
1641 * @category grade
1642 * @uses GRADE_TYPE_NONE
1643 * @uses GRADE_TYPE_VALUE
1644 * @uses GRADE_TYPE_SCALE
1645 * @param stdClass $forum Forum object with extra cmidnumber
1646 * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1647 * @return int 0 if ok
1649 function forum_grade_item_update($forum, $grades=NULL) {
1650 global $CFG;
1651 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1652 require_once($CFG->libdir.'/gradelib.php');
1655 $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1657 if (!$forum->assessed or $forum->scale == 0) {
1658 $params['gradetype'] = GRADE_TYPE_NONE;
1660 } else if ($forum->scale > 0) {
1661 $params['gradetype'] = GRADE_TYPE_VALUE;
1662 $params['grademax'] = $forum->scale;
1663 $params['grademin'] = 0;
1665 } else if ($forum->scale < 0) {
1666 $params['gradetype'] = GRADE_TYPE_SCALE;
1667 $params['scaleid'] = -$forum->scale;
1670 if ($grades === 'reset') {
1671 $params['reset'] = true;
1672 $grades = NULL;
1675 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1679 * Delete grade item for given forum
1681 * @category grade
1682 * @param stdClass $forum Forum object
1683 * @return grade_item
1685 function forum_grade_item_delete($forum) {
1686 global $CFG;
1687 require_once($CFG->libdir.'/gradelib.php');
1689 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1694 * This function returns if a scale is being used by one forum
1696 * @global object
1697 * @param int $forumid
1698 * @param int $scaleid negative number
1699 * @return bool
1701 function forum_scale_used ($forumid,$scaleid) {
1702 global $DB;
1703 $return = false;
1705 $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1707 if (!empty($rec) && !empty($scaleid)) {
1708 $return = true;
1711 return $return;
1715 * Checks if scale is being used by any instance of forum
1717 * This is used to find out if scale used anywhere
1719 * @global object
1720 * @param $scaleid int
1721 * @return boolean True if the scale is used by any forum
1723 function forum_scale_used_anywhere($scaleid) {
1724 global $DB;
1725 if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1726 return true;
1727 } else {
1728 return false;
1732 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1735 * Gets a post with all info ready for forum_print_post
1736 * Most of these joins are just to get the forum id
1738 * @global object
1739 * @global object
1740 * @param int $postid
1741 * @return mixed array of posts or false
1743 function forum_get_post_full($postid) {
1744 global $CFG, $DB;
1746 return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1747 FROM {forum_posts} p
1748 JOIN {forum_discussions} d ON p.discussion = d.id
1749 LEFT JOIN {user} u ON p.userid = u.id
1750 WHERE p.id = ?", array($postid));
1754 * Gets posts with all info ready for forum_print_post
1755 * We pass forumid in because we always know it so no need to make a
1756 * complicated join to find it out.
1758 * @global object
1759 * @global object
1760 * @return mixed array of posts or false
1762 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1763 global $CFG, $DB;
1765 return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1766 FROM {forum_posts} p
1767 LEFT JOIN {user} u ON p.userid = u.id
1768 WHERE p.discussion = ?
1769 AND p.parent > 0 $sort", array($discussion));
1773 * Gets all posts in discussion including top parent.
1775 * @global object
1776 * @global object
1777 * @global object
1778 * @param int $discussionid
1779 * @param string $sort
1780 * @param bool $tracking does user track the forum?
1781 * @return array of posts
1783 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1784 global $CFG, $DB, $USER;
1786 $tr_sel = "";
1787 $tr_join = "";
1788 $params = array();
1790 if ($tracking) {
1791 $now = time();
1792 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1793 $tr_sel = ", fr.id AS postread";
1794 $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1795 $params[] = $USER->id;
1798 $params[] = $discussionid;
1799 if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1800 FROM {forum_posts} p
1801 LEFT JOIN {user} u ON p.userid = u.id
1802 $tr_join
1803 WHERE p.discussion = ?
1804 ORDER BY $sort", $params)) {
1805 return array();
1808 foreach ($posts as $pid=>$p) {
1809 if ($tracking) {
1810 if (forum_tp_is_post_old($p)) {
1811 $posts[$pid]->postread = true;
1814 if (!$p->parent) {
1815 continue;
1817 if (!isset($posts[$p->parent])) {
1818 continue; // parent does not exist??
1820 if (!isset($posts[$p->parent]->children)) {
1821 $posts[$p->parent]->children = array();
1823 $posts[$p->parent]->children[$pid] =& $posts[$pid];
1826 return $posts;
1830 * Gets posts with all info ready for forum_print_post
1831 * We pass forumid in because we always know it so no need to make a
1832 * complicated join to find it out.
1834 * @global object
1835 * @global object
1836 * @param int $parent
1837 * @param int $forumid
1838 * @return array
1840 function forum_get_child_posts($parent, $forumid) {
1841 global $CFG, $DB;
1843 return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1844 FROM {forum_posts} p
1845 LEFT JOIN {user} u ON p.userid = u.id
1846 WHERE p.parent = ?
1847 ORDER BY p.created ASC", array($parent));
1851 * An array of forum objects that the user is allowed to read/search through.
1853 * @global object
1854 * @global object
1855 * @global object
1856 * @param int $userid
1857 * @param int $courseid if 0, we look for forums throughout the whole site.
1858 * @return array of forum objects, or false if no matches
1859 * Forum objects have the following attributes:
1860 * id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1861 * viewhiddentimedposts
1863 function forum_get_readable_forums($userid, $courseid=0) {
1865 global $CFG, $DB, $USER;
1866 require_once($CFG->dirroot.'/course/lib.php');
1868 if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1869 print_error('notinstalled', 'forum');
1872 if ($courseid) {
1873 $courses = $DB->get_records('course', array('id' => $courseid));
1874 } else {
1875 // If no course is specified, then the user can see SITE + his courses.
1876 $courses1 = $DB->get_records('course', array('id' => SITEID));
1877 $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1878 $courses = array_merge($courses1, $courses2);
1880 if (!$courses) {
1881 return array();
1884 $readableforums = array();
1886 foreach ($courses as $course) {
1888 $modinfo = get_fast_modinfo($course);
1889 if (is_null($modinfo->groups)) {
1890 $modinfo->groups = groups_get_user_groups($course->id, $userid);
1893 if (empty($modinfo->instances['forum'])) {
1894 // hmm, no forums?
1895 continue;
1898 $courseforums = $DB->get_records('forum', array('course' => $course->id));
1900 foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1901 if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1902 continue;
1904 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1905 $forum = $courseforums[$forumid];
1906 $forum->context = $context;
1907 $forum->cm = $cm;
1909 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1910 continue;
1913 /// group access
1914 if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1915 if (is_null($modinfo->groups)) {
1916 $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1918 if (isset($modinfo->groups[$cm->groupingid])) {
1919 $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1920 $forum->onlygroups[] = -1;
1921 } else {
1922 $forum->onlygroups = array(-1);
1926 /// hidden timed discussions
1927 $forum->viewhiddentimedposts = true;
1928 if (!empty($CFG->forum_enabletimedposts)) {
1929 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1930 $forum->viewhiddentimedposts = false;
1934 /// qanda access
1935 if ($forum->type == 'qanda'
1936 && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1938 // We need to check whether the user has posted in the qanda forum.
1939 $forum->onlydiscussions = array(); // Holds discussion ids for the discussions
1940 // the user is allowed to see in this forum.
1941 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1942 foreach ($discussionspostedin as $d) {
1943 $forum->onlydiscussions[] = $d->id;
1948 $readableforums[$forum->id] = $forum;
1951 unset($modinfo);
1953 } // End foreach $courses
1955 return $readableforums;
1959 * Returns a list of posts found using an array of search terms.
1961 * @global object
1962 * @global object
1963 * @global object
1964 * @param array $searchterms array of search terms, e.g. word +word -word
1965 * @param int $courseid if 0, we search through the whole site
1966 * @param int $limitfrom
1967 * @param int $limitnum
1968 * @param int &$totalcount
1969 * @param string $extrasql
1970 * @return array|bool Array of posts found or false
1972 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1973 &$totalcount, $extrasql='') {
1974 global $CFG, $DB, $USER;
1975 require_once($CFG->libdir.'/searchlib.php');
1977 $forums = forum_get_readable_forums($USER->id, $courseid);
1979 if (count($forums) == 0) {
1980 $totalcount = 0;
1981 return false;
1984 $now = round(time(), -2); // db friendly
1986 $fullaccess = array();
1987 $where = array();
1988 $params = array();
1990 foreach ($forums as $forumid => $forum) {
1991 $select = array();
1993 if (!$forum->viewhiddentimedposts) {
1994 $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
1995 $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
1998 $cm = $forum->cm;
1999 $context = $forum->context;
2001 if ($forum->type == 'qanda'
2002 && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2003 if (!empty($forum->onlydiscussions)) {
2004 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
2005 $params = array_merge($params, $discussionid_params);
2006 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
2007 } else {
2008 $select[] = "p.parent = 0";
2012 if (!empty($forum->onlygroups)) {
2013 list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
2014 $params = array_merge($params, $groupid_params);
2015 $select[] = "d.groupid $groupid_sql";
2018 if ($select) {
2019 $selects = implode(" AND ", $select);
2020 $where[] = "(d.forum = :forum{$forumid} AND $selects)";
2021 $params['forum'.$forumid] = $forumid;
2022 } else {
2023 $fullaccess[] = $forumid;
2027 if ($fullaccess) {
2028 list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
2029 $params = array_merge($params, $fullid_params);
2030 $where[] = "(d.forum $fullid_sql)";
2033 $selectdiscussion = "(".implode(" OR ", $where).")";
2035 $messagesearch = '';
2036 $searchstring = '';
2038 // Need to concat these back together for parser to work.
2039 foreach($searchterms as $searchterm){
2040 if ($searchstring != '') {
2041 $searchstring .= ' ';
2043 $searchstring .= $searchterm;
2046 // We need to allow quoted strings for the search. The quotes *should* be stripped
2047 // by the parser, but this should be examined carefully for security implications.
2048 $searchstring = str_replace("\\\"","\"",$searchstring);
2049 $parser = new search_parser();
2050 $lexer = new search_lexer($parser);
2052 if ($lexer->parse($searchstring)) {
2053 $parsearray = $parser->get_parsed_array();
2054 // Experimental feature under 1.8! MDL-8830
2055 // Use alternative text searches if defined
2056 // This feature only works under mysql until properly implemented for other DBs
2057 // Requires manual creation of text index for forum_posts before enabling it:
2058 // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2059 // Experimental feature under 1.8! MDL-8830
2060 if (!empty($CFG->forum_usetextsearches)) {
2061 list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2062 'p.userid', 'u.id', 'u.firstname',
2063 'u.lastname', 'p.modified', 'd.forum');
2064 } else {
2065 list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2066 'p.userid', 'u.id', 'u.firstname',
2067 'u.lastname', 'p.modified', 'd.forum');
2069 $params = array_merge($params, $msparams);
2072 $fromsql = "{forum_posts} p,
2073 {forum_discussions} d,
2074 {user} u";
2076 $selectsql = " $messagesearch
2077 AND p.discussion = d.id
2078 AND p.userid = u.id
2079 AND $selectdiscussion
2080 $extrasql";
2082 $countsql = "SELECT COUNT(*)
2083 FROM $fromsql
2084 WHERE $selectsql";
2086 $searchsql = "SELECT p.*,
2087 d.forum,
2088 u.firstname,
2089 u.lastname,
2090 u.email,
2091 u.picture,
2092 u.imagealt
2093 FROM $fromsql
2094 WHERE $selectsql
2095 ORDER BY p.modified DESC";
2097 $totalcount = $DB->count_records_sql($countsql, $params);
2099 return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2103 * Returns a list of ratings for a particular post - sorted.
2105 * TODO: Check if this function is actually used anywhere.
2106 * Up until the fix for MDL-27471 this function wasn't even returning.
2108 * @param stdClass $context
2109 * @param int $postid
2110 * @param string $sort
2111 * @return array Array of ratings or false
2113 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2114 $options = new stdClass;
2115 $options->context = $context;
2116 $options->component = 'mod_forum';
2117 $options->ratingarea = 'post';
2118 $options->itemid = $postid;
2119 $options->sort = "ORDER BY $sort";
2121 $rm = new rating_manager();
2122 return $rm->get_all_ratings_for_item($options);
2126 * Returns a list of all new posts that have not been mailed yet
2128 * @param int $starttime posts created after this time
2129 * @param int $endtime posts created before this
2130 * @param int $now used for timed discussions only
2131 * @return array
2133 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2134 global $CFG, $DB;
2136 $params = array($starttime, $endtime);
2137 if (!empty($CFG->forum_enabletimedposts)) {
2138 if (empty($now)) {
2139 $now = time();
2141 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2142 $params[] = $now;
2143 $params[] = $now;
2144 } else {
2145 $timedsql = "";
2148 return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2149 FROM {forum_posts} p
2150 JOIN {forum_discussions} d ON d.id = p.discussion
2151 WHERE p.mailed = 0
2152 AND p.created >= ?
2153 AND (p.created < ? OR p.mailnow = 1)
2154 $timedsql
2155 ORDER BY p.modified ASC", $params);
2159 * Marks posts before a certain time as being mailed already
2161 * @global object
2162 * @global object
2163 * @param int $endtime
2164 * @param int $now Defaults to time()
2165 * @return bool
2167 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2168 global $CFG, $DB;
2169 if (empty($now)) {
2170 $now = time();
2173 if (empty($CFG->forum_enabletimedposts)) {
2174 return $DB->execute("UPDATE {forum_posts}
2175 SET mailed = '1'
2176 WHERE (created < ? OR mailnow = 1)
2177 AND mailed = 0", array($endtime));
2179 } else {
2180 return $DB->execute("UPDATE {forum_posts}
2181 SET mailed = '1'
2182 WHERE discussion NOT IN (SELECT d.id
2183 FROM {forum_discussions} d
2184 WHERE d.timestart > ?)
2185 AND (created < ? OR mailnow = 1)
2186 AND mailed = 0", array($now, $endtime));
2191 * Get all the posts for a user in a forum suitable for forum_print_post
2193 * @global object
2194 * @global object
2195 * @uses CONTEXT_MODULE
2196 * @return array
2198 function forum_get_user_posts($forumid, $userid) {
2199 global $CFG, $DB;
2201 $timedsql = "";
2202 $params = array($forumid, $userid);
2204 if (!empty($CFG->forum_enabletimedposts)) {
2205 $cm = get_coursemodule_from_instance('forum', $forumid);
2206 if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2207 $now = time();
2208 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2209 $params[] = $now;
2210 $params[] = $now;
2214 return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2215 FROM {forum} f
2216 JOIN {forum_discussions} d ON d.forum = f.id
2217 JOIN {forum_posts} p ON p.discussion = d.id
2218 JOIN {user} u ON u.id = p.userid
2219 WHERE f.id = ?
2220 AND p.userid = ?
2221 $timedsql
2222 ORDER BY p.modified ASC", $params);
2226 * Get all the discussions user participated in
2228 * @global object
2229 * @global object
2230 * @uses CONTEXT_MODULE
2231 * @param int $forumid
2232 * @param int $userid
2233 * @return array Array or false
2235 function forum_get_user_involved_discussions($forumid, $userid) {
2236 global $CFG, $DB;
2238 $timedsql = "";
2239 $params = array($forumid, $userid);
2240 if (!empty($CFG->forum_enabletimedposts)) {
2241 $cm = get_coursemodule_from_instance('forum', $forumid);
2242 if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2243 $now = time();
2244 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2245 $params[] = $now;
2246 $params[] = $now;
2250 return $DB->get_records_sql("SELECT DISTINCT d.*
2251 FROM {forum} f
2252 JOIN {forum_discussions} d ON d.forum = f.id
2253 JOIN {forum_posts} p ON p.discussion = d.id
2254 WHERE f.id = ?
2255 AND p.userid = ?
2256 $timedsql", $params);
2260 * Get all the posts for a user in a forum suitable for forum_print_post
2262 * @global object
2263 * @global object
2264 * @param int $forumid
2265 * @param int $userid
2266 * @return array of counts or false
2268 function forum_count_user_posts($forumid, $userid) {
2269 global $CFG, $DB;
2271 $timedsql = "";
2272 $params = array($forumid, $userid);
2273 if (!empty($CFG->forum_enabletimedposts)) {
2274 $cm = get_coursemodule_from_instance('forum', $forumid);
2275 if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2276 $now = time();
2277 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2278 $params[] = $now;
2279 $params[] = $now;
2283 return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2284 FROM {forum} f
2285 JOIN {forum_discussions} d ON d.forum = f.id
2286 JOIN {forum_posts} p ON p.discussion = d.id
2287 JOIN {user} u ON u.id = p.userid
2288 WHERE f.id = ?
2289 AND p.userid = ?
2290 $timedsql", $params);
2294 * Given a log entry, return the forum post details for it.
2296 * @global object
2297 * @global object
2298 * @param object $log
2299 * @return array|null
2301 function forum_get_post_from_log($log) {
2302 global $CFG, $DB;
2304 if ($log->action == "add post") {
2306 return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2307 u.firstname, u.lastname, u.email, u.picture
2308 FROM {forum_discussions} d,
2309 {forum_posts} p,
2310 {forum} f,
2311 {user} u
2312 WHERE p.id = ?
2313 AND d.id = p.discussion
2314 AND p.userid = u.id
2315 AND u.deleted <> '1'
2316 AND f.id = d.forum", array($log->info));
2319 } else if ($log->action == "add discussion") {
2321 return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2322 u.firstname, u.lastname, u.email, u.picture
2323 FROM {forum_discussions} d,
2324 {forum_posts} p,
2325 {forum} f,
2326 {user} u
2327 WHERE d.id = ?
2328 AND d.firstpost = p.id
2329 AND p.userid = u.id
2330 AND u.deleted <> '1'
2331 AND f.id = d.forum", array($log->info));
2333 return NULL;
2337 * Given a discussion id, return the first post from the discussion
2339 * @global object
2340 * @global object
2341 * @param int $dicsussionid
2342 * @return array
2344 function forum_get_firstpost_from_discussion($discussionid) {
2345 global $CFG, $DB;
2347 return $DB->get_record_sql("SELECT p.*
2348 FROM {forum_discussions} d,
2349 {forum_posts} p
2350 WHERE d.id = ?
2351 AND d.firstpost = p.id ", array($discussionid));
2355 * Returns an array of counts of replies to each discussion
2357 * @global object
2358 * @global object
2359 * @param int $forumid
2360 * @param string $forumsort
2361 * @param int $limit
2362 * @param int $page
2363 * @param int $perpage
2364 * @return array
2366 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2367 global $CFG, $DB;
2369 if ($limit > 0) {
2370 $limitfrom = 0;
2371 $limitnum = $limit;
2372 } else if ($page != -1) {
2373 $limitfrom = $page*$perpage;
2374 $limitnum = $perpage;
2375 } else {
2376 $limitfrom = 0;
2377 $limitnum = 0;
2380 if ($forumsort == "") {
2381 $orderby = "";
2382 $groupby = "";
2384 } else {
2385 $orderby = "ORDER BY $forumsort";
2386 $groupby = ", ".strtolower($forumsort);
2387 $groupby = str_replace('desc', '', $groupby);
2388 $groupby = str_replace('asc', '', $groupby);
2391 if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2392 $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2393 FROM {forum_posts} p
2394 JOIN {forum_discussions} d ON p.discussion = d.id
2395 WHERE p.parent > 0 AND d.forum = ?
2396 GROUP BY p.discussion";
2397 return $DB->get_records_sql($sql, array($forumid));
2399 } else {
2400 $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2401 FROM {forum_posts} p
2402 JOIN {forum_discussions} d ON p.discussion = d.id
2403 WHERE d.forum = ?
2404 GROUP BY p.discussion $groupby
2405 $orderby";
2406 return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2411 * @global object
2412 * @global object
2413 * @global object
2414 * @staticvar array $cache
2415 * @param object $forum
2416 * @param object $cm
2417 * @param object $course
2418 * @return mixed
2420 function forum_count_discussions($forum, $cm, $course) {
2421 global $CFG, $DB, $USER;
2423 static $cache = array();
2425 $now = round(time(), -2); // db cache friendliness
2427 $params = array($course->id);
2429 if (!isset($cache[$course->id])) {
2430 if (!empty($CFG->forum_enabletimedposts)) {
2431 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2432 $params[] = $now;
2433 $params[] = $now;
2434 } else {
2435 $timedsql = "";
2438 $sql = "SELECT f.id, COUNT(d.id) as dcount
2439 FROM {forum} f
2440 JOIN {forum_discussions} d ON d.forum = f.id
2441 WHERE f.course = ?
2442 $timedsql
2443 GROUP BY f.id";
2445 if ($counts = $DB->get_records_sql($sql, $params)) {
2446 foreach ($counts as $count) {
2447 $counts[$count->id] = $count->dcount;
2449 $cache[$course->id] = $counts;
2450 } else {
2451 $cache[$course->id] = array();
2455 if (empty($cache[$course->id][$forum->id])) {
2456 return 0;
2459 $groupmode = groups_get_activity_groupmode($cm, $course);
2461 if ($groupmode != SEPARATEGROUPS) {
2462 return $cache[$course->id][$forum->id];
2465 if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2466 return $cache[$course->id][$forum->id];
2469 require_once($CFG->dirroot.'/course/lib.php');
2471 $modinfo = get_fast_modinfo($course);
2472 if (is_null($modinfo->groups)) {
2473 $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2476 if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2477 $mygroups = $modinfo->groups[$cm->groupingid];
2478 } else {
2479 $mygroups = false; // Will be set below
2482 // add all groups posts
2483 if (empty($mygroups)) {
2484 $mygroups = array(-1=>-1);
2485 } else {
2486 $mygroups[-1] = -1;
2489 list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2490 $params[] = $forum->id;
2492 if (!empty($CFG->forum_enabletimedposts)) {
2493 $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2494 $params[] = $now;
2495 $params[] = $now;
2496 } else {
2497 $timedsql = "";
2500 $sql = "SELECT COUNT(d.id)
2501 FROM {forum_discussions} d
2502 WHERE d.groupid $mygroups_sql AND d.forum = ?
2503 $timedsql";
2505 return $DB->get_field_sql($sql, $params);
2509 * How many posts by other users are unrated by a given user in the given discussion?
2511 * TODO: Is this function still used anywhere?
2513 * @param int $discussionid
2514 * @param int $userid
2515 * @return mixed
2517 function forum_count_unrated_posts($discussionid, $userid) {
2518 global $CFG, $DB;
2520 $sql = "SELECT COUNT(*) as num
2521 FROM {forum_posts}
2522 WHERE parent > 0
2523 AND discussion = :discussionid
2524 AND userid <> :userid";
2525 $params = array('discussionid' => $discussionid, 'userid' => $userid);
2526 $posts = $DB->get_record_sql($sql, $params);
2527 if ($posts) {
2528 $sql = "SELECT count(*) as num
2529 FROM {forum_posts} p,
2530 {rating} r
2531 WHERE p.discussion = :discussionid AND
2532 p.id = r.itemid AND
2533 r.userid = userid AND
2534 r.component = 'mod_forum' AND
2535 r.ratingarea = 'post'";
2536 $rated = $DB->get_record_sql($sql, $params);
2537 if ($rated) {
2538 if ($posts->num > $rated->num) {
2539 return $posts->num - $rated->num;
2540 } else {
2541 return 0; // Just in case there was a counting error
2543 } else {
2544 return $posts->num;
2546 } else {
2547 return 0;
2552 * Get all discussions in a forum
2554 * @global object
2555 * @global object
2556 * @global object
2557 * @uses CONTEXT_MODULE
2558 * @uses VISIBLEGROUPS
2559 * @param object $cm
2560 * @param string $forumsort
2561 * @param bool $fullpost
2562 * @param int $unused
2563 * @param int $limit
2564 * @param bool $userlastmodified
2565 * @param int $page
2566 * @param int $perpage
2567 * @return array
2569 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2570 global $CFG, $DB, $USER;
2572 $timelimit = '';
2574 $now = round(time(), -2);
2575 $params = array($cm->instance);
2577 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2579 if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2580 return array();
2583 if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2585 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2586 $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2587 $params[] = $now;
2588 $params[] = $now;
2589 if (isloggedin()) {
2590 $timelimit .= " OR d.userid = ?";
2591 $params[] = $USER->id;
2593 $timelimit .= ")";
2597 if ($limit > 0) {
2598 $limitfrom = 0;
2599 $limitnum = $limit;
2600 } else if ($page != -1) {
2601 $limitfrom = $page*$perpage;
2602 $limitnum = $perpage;
2603 } else {
2604 $limitfrom = 0;
2605 $limitnum = 0;
2608 $groupmode = groups_get_activity_groupmode($cm);
2609 $currentgroup = groups_get_activity_group($cm);
2611 if ($groupmode) {
2612 if (empty($modcontext)) {
2613 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2616 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2617 if ($currentgroup) {
2618 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2619 $params[] = $currentgroup;
2620 } else {
2621 $groupselect = "";
2624 } else {
2625 //seprate groups without access all
2626 if ($currentgroup) {
2627 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2628 $params[] = $currentgroup;
2629 } else {
2630 $groupselect = "AND d.groupid = -1";
2633 } else {
2634 $groupselect = "";
2638 if (empty($forumsort)) {
2639 $forumsort = "d.timemodified DESC";
2641 if (empty($fullpost)) {
2642 $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2643 } else {
2644 $postdata = "p.*";
2647 if (empty($userlastmodified)) { // We don't need to know this
2648 $umfields = "";
2649 $umtable = "";
2650 } else {
2651 $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2652 $umtable = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2655 $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2656 u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2657 FROM {forum_discussions} d
2658 JOIN {forum_posts} p ON p.discussion = d.id
2659 JOIN {user} u ON p.userid = u.id
2660 $umtable
2661 WHERE d.forum = ? AND p.parent = 0
2662 $timelimit $groupselect
2663 ORDER BY $forumsort";
2664 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2669 * @global object
2670 * @global object
2671 * @global object
2672 * @uses CONTEXT_MODULE
2673 * @uses VISIBLEGROUPS
2674 * @param object $cm
2675 * @return array
2677 function forum_get_discussions_unread($cm) {
2678 global $CFG, $DB, $USER;
2680 $now = round(time(), -2);
2681 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2683 $params = array();
2684 $groupmode = groups_get_activity_groupmode($cm);
2685 $currentgroup = groups_get_activity_group($cm);
2687 if ($groupmode) {
2688 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2690 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2691 if ($currentgroup) {
2692 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2693 $params['currentgroup'] = $currentgroup;
2694 } else {
2695 $groupselect = "";
2698 } else {
2699 //separate groups without access all
2700 if ($currentgroup) {
2701 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2702 $params['currentgroup'] = $currentgroup;
2703 } else {
2704 $groupselect = "AND d.groupid = -1";
2707 } else {
2708 $groupselect = "";
2711 if (!empty($CFG->forum_enabletimedposts)) {
2712 $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2713 $params['now1'] = $now;
2714 $params['now2'] = $now;
2715 } else {
2716 $timedsql = "";
2719 $sql = "SELECT d.id, COUNT(p.id) AS unread
2720 FROM {forum_discussions} d
2721 JOIN {forum_posts} p ON p.discussion = d.id
2722 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2723 WHERE d.forum = {$cm->instance}
2724 AND p.modified >= :cutoffdate AND r.id is NULL
2725 $groupselect
2726 $timedsql
2727 GROUP BY d.id";
2728 $params['cutoffdate'] = $cutoffdate;
2730 if ($unreads = $DB->get_records_sql($sql, $params)) {
2731 foreach ($unreads as $unread) {
2732 $unreads[$unread->id] = $unread->unread;
2734 return $unreads;
2735 } else {
2736 return array();
2741 * @global object
2742 * @global object
2743 * @global object
2744 * @uses CONEXT_MODULE
2745 * @uses VISIBLEGROUPS
2746 * @param object $cm
2747 * @return array
2749 function forum_get_discussions_count($cm) {
2750 global $CFG, $DB, $USER;
2752 $now = round(time(), -2);
2753 $params = array($cm->instance);
2754 $groupmode = groups_get_activity_groupmode($cm);
2755 $currentgroup = groups_get_activity_group($cm);
2757 if ($groupmode) {
2758 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2760 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2761 if ($currentgroup) {
2762 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2763 $params[] = $currentgroup;
2764 } else {
2765 $groupselect = "";
2768 } else {
2769 //seprate groups without access all
2770 if ($currentgroup) {
2771 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2772 $params[] = $currentgroup;
2773 } else {
2774 $groupselect = "AND d.groupid = -1";
2777 } else {
2778 $groupselect = "";
2781 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2783 $timelimit = "";
2785 if (!empty($CFG->forum_enabletimedposts)) {
2787 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2789 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2790 $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2791 $params[] = $now;
2792 $params[] = $now;
2793 if (isloggedin()) {
2794 $timelimit .= " OR d.userid = ?";
2795 $params[] = $USER->id;
2797 $timelimit .= ")";
2801 $sql = "SELECT COUNT(d.id)
2802 FROM {forum_discussions} d
2803 JOIN {forum_posts} p ON p.discussion = d.id
2804 WHERE d.forum = ? AND p.parent = 0
2805 $groupselect $timelimit";
2807 return $DB->get_field_sql($sql, $params);
2812 * Get all discussions started by a particular user in a course (or group)
2813 * This function no longer used ...
2815 * @todo Remove this function if no longer used
2816 * @global object
2817 * @global object
2818 * @param int $courseid
2819 * @param int $userid
2820 * @param int $groupid
2821 * @return array
2823 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2824 global $CFG, $DB;
2825 $params = array($courseid, $userid);
2826 if ($groupid) {
2827 $groupselect = " AND d.groupid = ? ";
2828 $params[] = $groupid;
2829 } else {
2830 $groupselect = "";
2833 return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2834 f.type as forumtype, f.name as forumname, f.id as forumid
2835 FROM {forum_discussions} d,
2836 {forum_posts} p,
2837 {user} u,
2838 {forum} f
2839 WHERE d.course = ?
2840 AND p.discussion = d.id
2841 AND p.parent = 0
2842 AND p.userid = u.id
2843 AND u.id = ?
2844 AND d.forum = f.id $groupselect
2845 ORDER BY p.created DESC", $params);
2849 * Get the list of potential subscribers to a forum.
2851 * @param object $forumcontext the forum context.
2852 * @param integer $groupid the id of a group, or 0 for all groups.
2853 * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2854 * @param string $sort sort order. As for get_users_by_capability.
2855 * @return array list of users.
2857 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2858 global $DB;
2860 // only active enrolled users or everybody on the frontpage
2861 list($esql, $params) = get_enrolled_sql($forumcontext, 'mod/forum:allowforcesubscribe', $groupid, true);
2863 $sql = "SELECT $fields
2864 FROM {user} u
2865 JOIN ($esql) je ON je.id = u.id";
2866 if ($sort) {
2867 $sql = "$sql ORDER BY $sort";
2868 } else {
2869 $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
2872 return $DB->get_records_sql($sql, $params);
2876 * Returns list of user objects that are subscribed to this forum
2878 * @global object
2879 * @global object
2880 * @param object $course the course
2881 * @param forum $forum the forum
2882 * @param integer $groupid group id, or 0 for all.
2883 * @param object $context the forum context, to save re-fetching it where possible.
2884 * @param string $fields requested user fields (with "u." table prefix)
2885 * @return array list of users.
2887 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2888 global $CFG, $DB;
2890 if (empty($fields)) {
2891 $fields ="u.id,
2892 u.username,
2893 u.firstname,
2894 u.lastname,
2895 u.maildisplay,
2896 u.mailformat,
2897 u.maildigest,
2898 u.imagealt,
2899 u.email,
2900 u.emailstop,
2901 u.city,
2902 u.country,
2903 u.lastaccess,
2904 u.lastlogin,
2905 u.picture,
2906 u.timezone,
2907 u.theme,
2908 u.lang,
2909 u.trackforums,
2910 u.mnethostid";
2913 if (empty($context)) {
2914 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2915 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2918 if (forum_is_forcesubscribed($forum)) {
2919 $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2921 } else {
2922 // only active enrolled users or everybody on the frontpage
2923 list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2924 $params['forumid'] = $forum->id;
2925 $results = $DB->get_records_sql("SELECT $fields
2926 FROM {user} u
2927 JOIN ($esql) je ON je.id = u.id
2928 JOIN {forum_subscriptions} s ON s.userid = u.id
2929 WHERE s.forum = :forumid
2930 ORDER BY u.email ASC", $params);
2933 // Guest user should never be subscribed to a forum.
2934 unset($results[$CFG->siteguest]);
2936 return $results;
2941 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2945 * @global object
2946 * @global object
2947 * @param int $courseid
2948 * @param string $type
2950 function forum_get_course_forum($courseid, $type) {
2951 // How to set up special 1-per-course forums
2952 global $CFG, $DB, $OUTPUT;
2954 if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2955 // There should always only be ONE, but with the right combination of
2956 // errors there might be more. In this case, just return the oldest one (lowest ID).
2957 foreach ($forums as $forum) {
2958 return $forum; // ie the first one
2962 // Doesn't exist, so create one now.
2963 $forum = new stdClass();
2964 $forum->course = $courseid;
2965 $forum->type = "$type";
2966 switch ($forum->type) {
2967 case "news":
2968 $forum->name = get_string("namenews", "forum");
2969 $forum->intro = get_string("intronews", "forum");
2970 $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2971 $forum->assessed = 0;
2972 if ($courseid == SITEID) {
2973 $forum->name = get_string("sitenews");
2974 $forum->forcesubscribe = 0;
2976 break;
2977 case "social":
2978 $forum->name = get_string("namesocial", "forum");
2979 $forum->intro = get_string("introsocial", "forum");
2980 $forum->assessed = 0;
2981 $forum->forcesubscribe = 0;
2982 break;
2983 case "blog":
2984 $forum->name = get_string('blogforum', 'forum');
2985 $forum->intro = get_string('introblog', 'forum');
2986 $forum->assessed = 0;
2987 $forum->forcesubscribe = 0;
2988 break;
2989 default:
2990 echo $OUTPUT->notification("That forum type doesn't exist!");
2991 return false;
2992 break;
2995 $forum->timemodified = time();
2996 $forum->id = $DB->insert_record("forum", $forum);
2998 if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2999 echo $OUTPUT->notification("Could not find forum module!!");
3000 return false;
3002 $mod = new stdClass();
3003 $mod->course = $courseid;
3004 $mod->module = $module->id;
3005 $mod->instance = $forum->id;
3006 $mod->section = 0;
3007 if (! $mod->coursemodule = add_course_module($mod) ) { // assumes course/lib.php is loaded
3008 echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
3009 return false;
3011 if (! $sectionid = add_mod_to_section($mod) ) { // assumes course/lib.php is loaded
3012 echo $OUTPUT->notification("Could not add the new course module to that section");
3013 return false;
3015 $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
3017 include_once("$CFG->dirroot/course/lib.php");
3018 rebuild_course_cache($courseid);
3020 return $DB->get_record("forum", array("id" => "$forum->id"));
3025 * Given the data about a posting, builds up the HTML to display it and
3026 * returns the HTML in a string. This is designed for sending via HTML email.
3028 * @global object
3029 * @param object $course
3030 * @param object $cm
3031 * @param object $forum
3032 * @param object $discussion
3033 * @param object $post
3034 * @param object $userform
3035 * @param object $userto
3036 * @param bool $ownpost
3037 * @param bool $reply
3038 * @param bool $link
3039 * @param bool $rate
3040 * @param string $footer
3041 * @return string
3043 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3044 $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3046 global $CFG, $OUTPUT;
3048 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3050 if (!isset($userto->viewfullnames[$forum->id])) {
3051 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3052 } else {
3053 $viewfullnames = $userto->viewfullnames[$forum->id];
3056 // add absolute file links
3057 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3059 // format the post body
3060 $options = new stdClass();
3061 $options->para = true;
3062 $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3064 $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3066 $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3067 $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3068 $output .= '</td>';
3070 if ($post->parent) {
3071 $output .= '<td class="topic">';
3072 } else {
3073 $output .= '<td class="topic starter">';
3075 $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3077 $fullname = fullname($userfrom, $viewfullnames);
3078 $by = new stdClass();
3079 $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3080 $by->date = userdate($post->modified, '', $userto->timezone);
3081 $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3083 $output .= '</td></tr>';
3085 $output .= '<tr><td class="left side" valign="top">';
3087 if (isset($userfrom->groups)) {
3088 $groups = $userfrom->groups[$forum->id];
3089 } else {
3090 $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3093 if ($groups) {
3094 $output .= print_group_picture($groups, $course->id, false, true, true);
3095 } else {
3096 $output .= '&nbsp;';
3099 $output .= '</td><td class="content">';
3101 $attachments = forum_print_attachments($post, $cm, 'html');
3102 if ($attachments !== '') {
3103 $output .= '<div class="attachments">';
3104 $output .= $attachments;
3105 $output .= '</div>';
3108 $output .= $formattedtext;
3110 // Commands
3111 $commands = array();
3113 if ($post->parent) {
3114 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3115 $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3118 if ($reply) {
3119 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3120 get_string('reply', 'forum').'</a>';
3123 $output .= '<div class="commands">';
3124 $output .= implode(' | ', $commands);
3125 $output .= '</div>';
3127 // Context link to post if required
3128 if ($link) {
3129 $output .= '<div class="link">';
3130 $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3131 get_string('postincontext', 'forum').'</a>';
3132 $output .= '</div>';
3135 if ($footer) {
3136 $output .= '<div class="footer">'.$footer.'</div>';
3138 $output .= '</td></tr></table>'."\n\n";
3140 return $output;
3144 * Print a forum post
3146 * @global object
3147 * @global object
3148 * @uses FORUM_MODE_THREADED
3149 * @uses PORTFOLIO_FORMAT_PLAINHTML
3150 * @uses PORTFOLIO_FORMAT_FILE
3151 * @uses PORTFOLIO_FORMAT_RICHHTML
3152 * @uses PORTFOLIO_ADD_TEXT_LINK
3153 * @uses CONTEXT_MODULE
3154 * @param object $post The post to print.
3155 * @param object $discussion
3156 * @param object $forum
3157 * @param object $cm
3158 * @param object $course
3159 * @param boolean $ownpost Whether this post belongs to the current user.
3160 * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3161 * @param boolean $link Just print a shortened version of the post as a link to the full post.
3162 * @param string $footer Extra stuff to print after the message.
3163 * @param string $highlight Space-separated list of terms to highlight.
3164 * @param int $post_read true, false or -99. If we already know whether this user
3165 * has read this post, pass that in, otherwise, pass in -99, and this
3166 * function will work it out.
3167 * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3168 * the current user can't see this post, if this argument is true
3169 * (the default) then print a dummy 'you can't see this post' post.
3170 * If false, don't output anything at all.
3171 * @param bool|null $istracked
3172 * @return void
3174 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3175 $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3176 global $USER, $CFG, $OUTPUT;
3178 require_once($CFG->libdir . '/filelib.php');
3180 // String cache
3181 static $str;
3183 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3185 $post->course = $course->id;
3186 $post->forum = $forum->id;
3187 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3189 // caching
3190 if (!isset($cm->cache)) {
3191 $cm->cache = new stdClass;
3194 if (!isset($cm->cache->caps)) {
3195 $cm->cache->caps = array();
3196 $cm->cache->caps['mod/forum:viewdiscussion'] = has_capability('mod/forum:viewdiscussion', $modcontext);
3197 $cm->cache->caps['moodle/site:viewfullnames'] = has_capability('moodle/site:viewfullnames', $modcontext);
3198 $cm->cache->caps['mod/forum:editanypost'] = has_capability('mod/forum:editanypost', $modcontext);
3199 $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3200 $cm->cache->caps['mod/forum:deleteownpost'] = has_capability('mod/forum:deleteownpost', $modcontext);
3201 $cm->cache->caps['mod/forum:deleteanypost'] = has_capability('mod/forum:deleteanypost', $modcontext);
3202 $cm->cache->caps['mod/forum:viewanyrating'] = has_capability('mod/forum:viewanyrating', $modcontext);
3203 $cm->cache->caps['mod/forum:exportpost'] = has_capability('mod/forum:exportpost', $modcontext);
3204 $cm->cache->caps['mod/forum:exportownpost'] = has_capability('mod/forum:exportownpost', $modcontext);
3207 if (!isset($cm->uservisible)) {
3208 $cm->uservisible = coursemodule_visible_for_user($cm);
3211 if ($istracked && is_null($postisread)) {
3212 $postisread = forum_tp_is_post_read($USER->id, $post);
3215 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3216 $output = '';
3217 if (!$dummyifcantsee) {
3218 if ($return) {
3219 return $output;
3221 echo $output;
3222 return;
3224 $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3225 $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3226 $output .= html_writer::start_tag('div', array('class'=>'row header'));
3227 $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3228 if ($post->parent) {
3229 $output .= html_writer::start_tag('div', array('class'=>'topic'));
3230 } else {
3231 $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3233 $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3234 $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3235 $output .= html_writer::end_tag('div');
3236 $output .= html_writer::end_tag('div'); // row
3237 $output .= html_writer::start_tag('div', array('class'=>'row'));
3238 $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3239 $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3240 $output .= html_writer::end_tag('div'); // row
3241 $output .= html_writer::end_tag('div'); // forumpost
3243 if ($return) {
3244 return $output;
3246 echo $output;
3247 return;
3250 if (empty($str)) {
3251 $str = new stdClass;
3252 $str->edit = get_string('edit', 'forum');
3253 $str->delete = get_string('delete', 'forum');
3254 $str->reply = get_string('reply', 'forum');
3255 $str->parent = get_string('parent', 'forum');
3256 $str->pruneheading = get_string('pruneheading', 'forum');
3257 $str->prune = get_string('prune', 'forum');
3258 $str->displaymode = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3259 $str->markread = get_string('markread', 'forum');
3260 $str->markunread = get_string('markunread', 'forum');
3263 $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3265 // Build an object that represents the posting user
3266 $postuser = new stdClass;
3267 $postuser->id = $post->userid;
3268 $postuser->firstname = $post->firstname;
3269 $postuser->lastname = $post->lastname;
3270 $postuser->imagealt = $post->imagealt;
3271 $postuser->picture = $post->picture;
3272 $postuser->email = $post->email;
3273 // Some handy things for later on
3274 $postuser->fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3275 $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3277 // Prepare the groups the posting user belongs to
3278 if (isset($cm->cache->usersgroups)) {
3279 $groups = array();
3280 if (isset($cm->cache->usersgroups[$post->userid])) {
3281 foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3282 $groups[$gid] = $cm->cache->groups[$gid];
3285 } else {
3286 $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3289 // Prepare the attachements for the post, files then images
3290 list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3292 // Determine if we need to shorten this post
3293 $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3296 // Prepare an array of commands
3297 $commands = array();
3299 // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3300 // Don't display the mark read / unread controls in this case.
3301 if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3302 $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3303 $text = $str->markunread;
3304 if (!$postisread) {
3305 $url->param('mark', 'read');
3306 $text = $str->markread;
3308 if ($str->displaymode == FORUM_MODE_THREADED) {
3309 $url->param('parent', $post->parent);
3310 } else {
3311 $url->set_anchor('p'.$post->id);
3313 $commands[] = array('url'=>$url, 'text'=>$text);
3316 // Zoom in to the parent specifically
3317 if ($post->parent) {
3318 $url = new moodle_url($discussionlink);
3319 if ($str->displaymode == FORUM_MODE_THREADED) {
3320 $url->param('parent', $post->parent);
3321 } else {
3322 $url->set_anchor('p'.$post->parent);
3324 $commands[] = array('url'=>$url, 'text'=>$str->parent);
3327 // Hack for allow to edit news posts those are not displayed yet until they are displayed
3328 $age = time() - $post->created;
3329 if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3330 $age = 0;
3333 if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3334 if (has_capability('moodle/course:manageactivities', $modcontext)) {
3335 // The first post in single simple is the forum description.
3336 $commands[] = array('url'=>new moodle_url('/course/modedit.php', array('update'=>$cm->id, 'sesskey'=>sesskey(), 'return'=>1)), 'text'=>$str->edit);
3338 } else if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3339 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3342 if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3343 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3346 if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3347 // Do not allow deleting of first post in single simple type.
3348 } else if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3349 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3352 if ($reply) {
3353 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mform1', array('reply'=>$post->id)), 'text'=>$str->reply);
3356 if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3357 $p = array('postid' => $post->id);
3358 require_once($CFG->libdir.'/portfoliolib.php');
3359 $button = new portfolio_add_button();
3360 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3361 if (empty($attachments)) {
3362 $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3363 } else {
3364 $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3367 $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3368 if (!empty($porfoliohtml)) {
3369 $commands[] = $porfoliohtml;
3372 // Finished building commands
3375 // Begin output
3377 $output = '';
3379 if ($istracked) {
3380 if ($postisread) {
3381 $forumpostclass = ' read';
3382 } else {
3383 $forumpostclass = ' unread';
3384 $output .= html_writer::tag('a', '', array('name'=>'unread'));
3386 } else {
3387 // ignore trackign status if not tracked or tracked param missing
3388 $forumpostclass = '';
3391 $topicclass = '';
3392 if (empty($post->parent)) {
3393 $topicclass = ' firstpost starter';
3396 $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3397 $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3398 $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3399 $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3400 $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3401 $output .= html_writer::end_tag('div');
3404 $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3406 $postsubject = $post->subject;
3407 if (empty($post->subjectnoformat)) {
3408 $postsubject = format_string($postsubject);
3410 $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3412 $by = new stdClass();
3413 $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3414 $by->date = userdate($post->modified);
3415 $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3417 $output .= html_writer::end_tag('div'); //topic
3418 $output .= html_writer::end_tag('div'); //row
3420 $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3421 $output .= html_writer::start_tag('div', array('class'=>'left'));
3423 $groupoutput = '';
3424 if ($groups) {
3425 $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3427 if (empty($groupoutput)) {
3428 $groupoutput = '&nbsp;';
3430 $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3432 $output .= html_writer::end_tag('div'); //left side
3433 $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3434 $output .= html_writer::start_tag('div', array('class'=>'content'));
3435 if (!empty($attachments)) {
3436 $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3439 $options = new stdClass;
3440 $options->para = false;
3441 $options->trusted = $post->messagetrust;
3442 $options->context = $modcontext;
3443 if ($shortenpost) {
3444 // Prepare shortened version
3445 $postclass = 'shortenedpost';
3446 $postcontent = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3447 $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3448 $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3449 } else {
3450 // Prepare whole post
3451 $postclass = 'fullpost';
3452 $postcontent = format_text($post->message, $post->messageformat, $options, $course->id);
3453 if (!empty($highlight)) {
3454 $postcontent = highlight($highlight, $postcontent);
3456 $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3458 // Output the post content
3459 $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3460 $output .= html_writer::end_tag('div'); // Content
3461 $output .= html_writer::end_tag('div'); // Content mask
3462 $output .= html_writer::end_tag('div'); // Row
3464 $output .= html_writer::start_tag('div', array('class'=>'row side'));
3465 $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3466 $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3468 // Output ratings
3469 if (!empty($post->rating)) {
3470 $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3473 // Output the commands
3474 $commandhtml = array();
3475 foreach ($commands as $command) {
3476 if (is_array($command)) {
3477 $commandhtml[] = html_writer::link($command['url'], $command['text']);
3478 } else {
3479 $commandhtml[] = $command;
3482 $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3484 // Output link to post if required
3485 if ($link) {
3486 if ($post->replies == 1) {
3487 $replystring = get_string('repliesone', 'forum', $post->replies);
3488 } else {
3489 $replystring = get_string('repliesmany', 'forum', $post->replies);
3492 $output .= html_writer::start_tag('div', array('class'=>'link'));
3493 $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3494 $output .= '&nbsp;('.$replystring.')';
3495 $output .= html_writer::end_tag('div'); // link
3498 // Output footer if required
3499 if ($footer) {
3500 $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3503 // Close remaining open divs
3504 $output .= html_writer::end_tag('div'); // content
3505 $output .= html_writer::end_tag('div'); // row
3506 $output .= html_writer::end_tag('div'); // forumpost
3508 // Mark the forum post as read if required
3509 if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3510 forum_tp_mark_post_read($USER->id, $post, $forum->id);
3513 if ($return) {
3514 return $output;
3516 echo $output;
3517 return;
3521 * Return rating related permissions
3523 * @param string $options the context id
3524 * @return array an associative array of the user's rating permissions
3526 function forum_rating_permissions($contextid, $component, $ratingarea) {
3527 $context = get_context_instance_by_id($contextid, MUST_EXIST);
3528 if ($component != 'mod_forum' || $ratingarea != 'post') {
3529 // We don't know about this component/ratingarea so just return null to get the
3530 // default restrictive permissions.
3531 return null;
3533 return array(
3534 'view' => has_capability('mod/forum:viewrating', $context),
3535 'viewany' => has_capability('mod/forum:viewanyrating', $context),
3536 'viewall' => has_capability('mod/forum:viewallratings', $context),
3537 'rate' => has_capability('mod/forum:rate', $context)
3542 * Validates a submitted rating
3543 * @param array $params submitted data
3544 * context => object the context in which the rated items exists [required]
3545 * component => The component for this module - should always be mod_forum [required]
3546 * ratingarea => object the context in which the rated items exists [required]
3547 * itemid => int the ID of the object being rated [required]
3548 * scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
3549 * rating => int the submitted rating [required]
3550 * rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
3551 * aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
3552 * @return boolean true if the rating is valid. Will throw rating_exception if not
3554 function forum_rating_validate($params) {
3555 global $DB, $USER;
3557 // Check the component is mod_forum
3558 if ($params['component'] != 'mod_forum') {
3559 throw new rating_exception('invalidcomponent');
3562 // Check the ratingarea is post (the only rating area in forum)
3563 if ($params['ratingarea'] != 'post') {
3564 throw new rating_exception('invalidratingarea');
3567 // Check the rateduserid is not the current user .. you can't rate your own posts
3568 if ($params['rateduserid'] == $USER->id) {
3569 throw new rating_exception('nopermissiontorate');
3572 // Fetch all the related records ... we need to do this anyway to call forum_user_can_see_post
3573 $post = $DB->get_record('forum_posts', array('id' => $params['itemid'], 'userid' => $params['rateduserid']), '*', MUST_EXIST);
3574 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion), '*', MUST_EXIST);
3575 $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
3576 $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
3577 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id , false, MUST_EXIST);
3578 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3580 // Make sure the context provided is the context of the forum
3581 if ($context->id != $params['context']->id) {
3582 throw new rating_exception('invalidcontext');
3585 if ($forum->scale != $params['scaleid']) {
3586 //the scale being submitted doesnt match the one in the database
3587 throw new rating_exception('invalidscaleid');
3590 // check the item we're rating was created in the assessable time window
3591 if (!empty($forum->assesstimestart) && !empty($forum->assesstimefinish)) {
3592 if ($post->created < $forum->assesstimestart || $post->created > $forum->assesstimefinish) {
3593 throw new rating_exception('notavailable');
3597 //check that the submitted rating is valid for the scale
3599 // lower limit
3600 if ($params['rating'] < 0 && $params['rating'] != RATING_UNSET_RATING) {
3601 throw new rating_exception('invalidnum');
3604 // upper limit
3605 if ($forum->scale < 0) {
3606 //its a custom scale
3607 $scalerecord = $DB->get_record('scale', array('id' => -$forum->scale));
3608 if ($scalerecord) {
3609 $scalearray = explode(',', $scalerecord->scale);
3610 if ($params['rating'] > count($scalearray)) {
3611 throw new rating_exception('invalidnum');
3613 } else {
3614 throw new rating_exception('invalidscaleid');
3616 } else if ($params['rating'] > $forum->scale) {
3617 //if its numeric and submitted rating is above maximum
3618 throw new rating_exception('invalidnum');
3621 // Make sure groups allow this user to see the item they're rating
3622 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
3623 if (!groups_group_exists($discussion->groupid)) { // Can't find group
3624 throw new rating_exception('cannotfindgroup');//something is wrong
3627 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
3628 // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
3629 throw new rating_exception('notmemberofgroup');
3633 // perform some final capability checks
3634 if (!forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) {
3635 throw new rating_exception('nopermissiontorate');
3638 return true;
3643 * This function prints the overview of a discussion in the forum listing.
3644 * It needs some discussion information and some post information, these
3645 * happen to be combined for efficiency in the $post parameter by the function
3646 * that calls this one: forum_print_latest_discussions()
3648 * @global object
3649 * @global object
3650 * @param object $post The post object (passed by reference for speed).
3651 * @param object $forum The forum object.
3652 * @param int $group Current group.
3653 * @param string $datestring Format to use for the dates.
3654 * @param boolean $cantrack Is tracking enabled for this forum.
3655 * @param boolean $forumtracked Is the user tracking this forum.
3656 * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3658 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3659 $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3661 global $USER, $CFG, $OUTPUT;
3663 static $rowcount;
3664 static $strmarkalldread;
3666 if (empty($modcontext)) {
3667 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3668 print_error('invalidcoursemodule');
3670 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3673 if (!isset($rowcount)) {
3674 $rowcount = 0;
3675 $strmarkalldread = get_string('markalldread', 'forum');
3676 } else {
3677 $rowcount = ($rowcount + 1) % 2;
3680 $post->subject = format_string($post->subject,true);
3682 echo "\n\n";
3683 echo '<tr class="discussion r'.$rowcount.'">';
3685 // Topic
3686 echo '<td class="topic starter">';
3687 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3688 echo "</td>\n";
3690 // Picture
3691 $postuser = new stdClass();
3692 $postuser->id = $post->userid;
3693 $postuser->firstname = $post->firstname;
3694 $postuser->lastname = $post->lastname;
3695 $postuser->imagealt = $post->imagealt;
3696 $postuser->picture = $post->picture;
3697 $postuser->email = $post->email;
3699 echo '<td class="picture">';
3700 echo $OUTPUT->user_picture($postuser, array('courseid'=>$forum->course));
3701 echo "</td>\n";
3703 // User name
3704 $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
3705 echo '<td class="author">';
3706 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
3707 echo "</td>\n";
3709 // Group picture
3710 if ($group !== -1) { // Groups are active - group is a group data object or NULL
3711 echo '<td class="picture group">';
3712 if (!empty($group->picture) and empty($group->hidepicture)) {
3713 print_group_picture($group, $forum->course, false, false, true);
3714 } else if (isset($group->id)) {
3715 if($canviewparticipants) {
3716 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
3717 } else {
3718 echo $group->name;
3721 echo "</td>\n";
3724 if (has_capability('mod/forum:viewdiscussion', $modcontext)) { // Show the column with replies
3725 echo '<td class="replies">';
3726 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3727 echo $post->replies.'</a>';
3728 echo "</td>\n";
3730 if ($cantrack) {
3731 echo '<td class="replies">';
3732 if ($forumtracked) {
3733 if ($post->unread > 0) {
3734 echo '<span class="unread">';
3735 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
3736 echo $post->unread;
3737 echo '</a>';
3738 echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3739 $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
3740 '<img src="'.$OUTPUT->pix_url('t/clear') . '" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
3741 echo '</span>';
3742 } else {
3743 echo '<span class="read">';
3744 echo $post->unread;
3745 echo '</span>';
3747 } else {
3748 echo '<span class="read">';
3749 echo '-';
3750 echo '</span>';
3752 echo "</td>\n";
3756 echo '<td class="lastpost">';
3757 $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified; // Just in case
3758 $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
3759 $usermodified = new stdClass();
3760 $usermodified->id = $post->usermodified;
3761 $usermodified->firstname = $post->umfirstname;
3762 $usermodified->lastname = $post->umlastname;
3763 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
3764 fullname($usermodified).'</a><br />';
3765 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
3766 userdate($usedate, $datestring).'</a>';
3767 echo "</td>\n";
3769 echo "</tr>\n\n";
3775 * Given a post object that we already know has a long message
3776 * this function truncates the message nicely to the first
3777 * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
3779 * @global object
3780 * @param string $message
3781 * @return string
3783 function forum_shorten_post($message) {
3785 global $CFG;
3787 $i = 0;
3788 $tag = false;
3789 $length = strlen($message);
3790 $count = 0;
3791 $stopzone = false;
3792 $truncate = 0;
3794 for ($i=0; $i<$length; $i++) {
3795 $char = $message[$i];
3797 switch ($char) {
3798 case "<":
3799 $tag = true;
3800 break;
3801 case ">":
3802 $tag = false;
3803 break;
3804 default:
3805 if (!$tag) {
3806 if ($stopzone) {
3807 if ($char == ".") {
3808 $truncate = $i+1;
3809 break 2;
3812 $count++;
3814 break;
3816 if (!$stopzone) {
3817 if ($count > $CFG->forum_shortpost) {
3818 $stopzone = true;
3823 if (!$truncate) {
3824 $truncate = $i;
3827 return substr($message, 0, $truncate);
3831 * Print the drop down that allows the user to select how they want to have
3832 * the discussion displayed.
3834 * @param int $id forum id if $forumtype is 'single',
3835 * discussion id for any other forum type
3836 * @param mixed $mode forum layout mode
3837 * @param string $forumtype optional
3839 function forum_print_mode_form($id, $mode, $forumtype='') {
3840 global $OUTPUT;
3841 if ($forumtype == 'single') {
3842 $select = new single_select(new moodle_url("/mod/forum/view.php", array('f'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3843 $select->set_label(get_string('displaymode', 'forum'), array('class' => 'accesshide'));
3844 $select->class = "forummode";
3845 } else {
3846 $select = new single_select(new moodle_url("/mod/forum/discuss.php", array('d'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3847 $select->set_label(get_string('displaymode', 'forum'), array('class' => 'accesshide'));
3849 echo $OUTPUT->render($select);
3853 * @global object
3854 * @param object $course
3855 * @param string $search
3856 * @return string
3858 function forum_search_form($course, $search='') {
3859 global $CFG, $OUTPUT;
3861 $output = '<div class="forumsearch">';
3862 $output .= '<form action="'.$CFG->wwwroot.'/mod/forum/search.php" style="display:inline">';
3863 $output .= '<fieldset class="invisiblefieldset">';
3864 $output .= $OUTPUT->help_icon('search');
3865 $output .= '<label class="accesshide" for="search" >'.get_string('search', 'forum').'</label>';
3866 $output .= '<input id="search" name="search" type="text" size="18" value="'.s($search, true).'" alt="search" />';
3867 $output .= '<label class="accesshide" for="searchforums" >'.get_string('searchforums', 'forum').'</label>';
3868 $output .= '<input id="searchforums" value="'.get_string('searchforums', 'forum').'" type="submit" />';
3869 $output .= '<input name="id" type="hidden" value="'.$course->id.'" />';
3870 $output .= '</fieldset>';
3871 $output .= '</form>';
3872 $output .= '</div>';
3874 return $output;
3879 * @global object
3880 * @global object
3882 function forum_set_return() {
3883 global $CFG, $SESSION;
3885 if (! isset($SESSION->fromdiscussion)) {
3886 if (!empty($_SERVER['HTTP_REFERER'])) {
3887 $referer = $_SERVER['HTTP_REFERER'];
3888 } else {
3889 $referer = "";
3891 // If the referer is NOT a login screen then save it.
3892 if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) {
3893 $SESSION->fromdiscussion = $_SERVER["HTTP_REFERER"];
3900 * @global object
3901 * @param string $default
3902 * @return string
3904 function forum_go_back_to($default) {
3905 global $SESSION;
3907 if (!empty($SESSION->fromdiscussion)) {
3908 $returnto = $SESSION->fromdiscussion;
3909 unset($SESSION->fromdiscussion);
3910 return $returnto;
3911 } else {
3912 return $default;
3917 * Given a discussion object that is being moved to $forumto,
3918 * this function checks all posts in that discussion
3919 * for attachments, and if any are found, these are
3920 * moved to the new forum directory.
3922 * @global object
3923 * @param object $discussion
3924 * @param int $forumfrom source forum id
3925 * @param int $forumto target forum id
3926 * @return bool success
3928 function forum_move_attachments($discussion, $forumfrom, $forumto) {
3929 global $DB;
3931 $fs = get_file_storage();
3933 $newcm = get_coursemodule_from_instance('forum', $forumto);
3934 $oldcm = get_coursemodule_from_instance('forum', $forumfrom);
3936 $newcontext = get_context_instance(CONTEXT_MODULE, $newcm->id);
3937 $oldcontext = get_context_instance(CONTEXT_MODULE, $oldcm->id);
3939 // loop through all posts, better not use attachment flag ;-)
3940 if ($posts = $DB->get_records('forum_posts', array('discussion'=>$discussion->id), '', 'id, attachment')) {
3941 foreach ($posts as $post) {
3942 $fs->move_area_files_to_new_context($oldcontext->id,
3943 $newcontext->id, 'mod_forum', 'post', $post->id);
3944 $attachmentsmoved = $fs->move_area_files_to_new_context($oldcontext->id,
3945 $newcontext->id, 'mod_forum', 'attachment', $post->id);
3946 if ($attachmentsmoved > 0 && $post->attachment != '1') {
3947 // Weird - let's fix it
3948 $post->attachment = '1';
3949 $DB->update_record('forum_posts', $post);
3950 } else if ($attachmentsmoved == 0 && $post->attachment != '') {
3951 // Weird - let's fix it
3952 $post->attachment = '';
3953 $DB->update_record('forum_posts', $post);
3958 return true;
3962 * Returns attachments as formated text/html optionally with separate images
3964 * @global object
3965 * @global object
3966 * @global object
3967 * @param object $post
3968 * @param object $cm
3969 * @param string $type html/text/separateimages
3970 * @return mixed string or array of (html text withouth images and image HTML)
3972 function forum_print_attachments($post, $cm, $type) {
3973 global $CFG, $DB, $USER, $OUTPUT;
3975 if (empty($post->attachment)) {
3976 return $type !== 'separateimages' ? '' : array('', '');
3979 if (!in_array($type, array('separateimages', 'html', 'text'))) {
3980 return $type !== 'separateimages' ? '' : array('', '');
3983 if (!$context = get_context_instance(CONTEXT_MODULE, $cm->id)) {
3984 return $type !== 'separateimages' ? '' : array('', '');
3986 $strattachment = get_string('attachment', 'forum');
3988 $fs = get_file_storage();
3990 $imagereturn = '';
3991 $output = '';
3993 $canexport = !empty($CFG->enableportfolios) && (has_capability('mod/forum:exportpost', $context) || ($post->userid == $USER->id && has_capability('mod/forum:exportownpost', $context)));
3995 if ($canexport) {
3996 require_once($CFG->libdir.'/portfoliolib.php');
3999 $files = $fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id, "timemodified", false);
4000 if ($files) {
4001 if ($canexport) {
4002 $button = new portfolio_add_button();
4004 foreach ($files as $file) {
4005 $filename = $file->get_filename();
4006 $mimetype = $file->get_mimetype();
4007 $iconimage = $OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file), 'moodle', array('class' => 'icon'));
4008 $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$context->id.'/mod_forum/attachment/'.$post->id.'/'.$filename);
4010 if ($type == 'html') {
4011 $output .= "<a href=\"$path\">$iconimage</a> ";
4012 $output .= "<a href=\"$path\">".s($filename)."</a>";
4013 if ($canexport) {
4014 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
4015 $button->set_format_by_file($file);
4016 $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
4018 $output .= "<br />";
4020 } else if ($type == 'text') {
4021 $output .= "$strattachment ".s($filename).":\n$path\n";
4023 } else { //'returnimages'
4024 if (in_array($mimetype, array('image/gif', 'image/jpeg', 'image/png'))) {
4025 // Image attachments don't get printed as links
4026 $imagereturn .= "<br /><img src=\"$path\" alt=\"\" />";
4027 if ($canexport) {
4028 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
4029 $button->set_format_by_file($file);
4030 $imagereturn .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
4032 } else {
4033 $output .= "<a href=\"$path\">$iconimage</a> ";
4034 $output .= format_text("<a href=\"$path\">".s($filename)."</a>", FORMAT_HTML, array('context'=>$context));
4035 if ($canexport) {
4036 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
4037 $button->set_format_by_file($file);
4038 $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
4040 $output .= '<br />';
4046 if ($type !== 'separateimages') {
4047 return $output;
4049 } else {
4050 return array($output, $imagereturn);
4054 ////////////////////////////////////////////////////////////////////////////////
4055 // File API //
4056 ////////////////////////////////////////////////////////////////////////////////
4059 * Lists all browsable file areas
4061 * @package mod_forum
4062 * @category files
4063 * @param stdClass $course course object
4064 * @param stdClass $cm course module object
4065 * @param stdClass $context context object
4066 * @return array
4068 function forum_get_file_areas($course, $cm, $context) {
4069 return array(
4070 'attachment' => get_string('areaattachment', 'mod_forum'),
4071 'post' => get_string('areapost', 'mod_forum'),
4076 * File browsing support for forum module.
4078 * @package mod_forum
4079 * @category files
4080 * @param stdClass $browser file browser object
4081 * @param stdClass $areas file areas
4082 * @param stdClass $course course object
4083 * @param stdClass $cm course module
4084 * @param stdClass $context context module
4085 * @param string $filearea file area
4086 * @param int $itemid item ID
4087 * @param string $filepath file path
4088 * @param string $filename file name
4089 * @return file_info instance or null if not found
4091 function forum_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
4092 global $CFG, $DB, $USER;
4094 if ($context->contextlevel != CONTEXT_MODULE) {
4095 return null;
4098 // filearea must contain a real area
4099 if (!isset($areas[$filearea])) {
4100 return null;
4103 // Note that forum_user_can_see_post() additionally allows access for parent roles
4104 // and it explicitly checks qanda forum type, too. One day, when we stop requiring
4105 // course:managefiles, we will need to extend this.
4106 if (!has_capability('mod/forum:viewdiscussion', $context)) {
4107 return null;
4110 if (is_null($itemid)) {
4111 require_once($CFG->dirroot.'/mod/forum/locallib.php');
4112 return new forum_file_info_container($browser, $course, $cm, $context, $areas, $filearea);
4115 static $cached = array();
4116 // $cached will store last retrieved post, discussion and forum. To make sure that the cache
4117 // is cleared between unit tests we check if this is the same session
4118 if (!isset($cached['sesskey']) || $cached['sesskey'] != sesskey()) {
4119 $cached = array('sesskey' => sesskey());
4122 if (isset($cached['post']) && $cached['post']->id == $itemid) {
4123 $post = $cached['post'];
4124 } else if ($post = $DB->get_record('forum_posts', array('id' => $itemid))) {
4125 $cached['post'] = $post;
4126 } else {
4127 return null;
4130 if (isset($cached['discussion']) && $cached['discussion']->id == $post->discussion) {
4131 $discussion = $cached['discussion'];
4132 } else if ($discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion))) {
4133 $cached['discussion'] = $discussion;
4134 } else {
4135 return null;
4138 if (isset($cached['forum']) && $cached['forum']->id == $cm->instance) {
4139 $forum = $cached['forum'];
4140 } else if ($forum = $DB->get_record('forum', array('id' => $cm->instance))) {
4141 $cached['forum'] = $forum;
4142 } else {
4143 return null;
4146 $fs = get_file_storage();
4147 $filepath = is_null($filepath) ? '/' : $filepath;
4148 $filename = is_null($filename) ? '.' : $filename;
4149 if (!($storedfile = $fs->get_file($context->id, 'mod_forum', $filearea, $itemid, $filepath, $filename))) {
4150 return null;
4153 // Checks to see if the user can manage files or is the owner.
4154 // TODO MDL-33805 - Do not use userid here and move the capability check above.
4155 if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
4156 return null;
4158 // Make sure groups allow this user to see this file
4159 if ($discussion->groupid > 0 && !has_capability('moodle/site:accessallgroups', $context)) {
4160 $groupmode = groups_get_activity_groupmode($cm, $course);
4161 if ($groupmode == SEPARATEGROUPS && !groups_is_member($discussion->groupid)) {
4162 return null;
4166 // Make sure we're allowed to see it...
4167 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4168 return null;
4171 $urlbase = $CFG->wwwroot.'/pluginfile.php';
4172 return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
4176 * Serves the forum attachments. Implements needed access control ;-)
4178 * @package mod_forum
4179 * @category files
4180 * @param stdClass $course course object
4181 * @param stdClass $cm course module object
4182 * @param stdClass $context context object
4183 * @param string $filearea file area
4184 * @param array $args extra arguments
4185 * @param bool $forcedownload whether or not force download
4186 * @param array $options additional options affecting the file serving
4187 * @return bool false if file not found, does not return if found - justsend the file
4189 function forum_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
4190 global $CFG, $DB;
4192 if ($context->contextlevel != CONTEXT_MODULE) {
4193 return false;
4196 require_course_login($course, true, $cm);
4198 $areas = forum_get_file_areas($course, $cm, $context);
4200 // filearea must contain a real area
4201 if (!isset($areas[$filearea])) {
4202 return false;
4205 $postid = (int)array_shift($args);
4207 if (!$post = $DB->get_record('forum_posts', array('id'=>$postid))) {
4208 return false;
4211 if (!$discussion = $DB->get_record('forum_discussions', array('id'=>$post->discussion))) {
4212 return false;
4215 if (!$forum = $DB->get_record('forum', array('id'=>$cm->instance))) {
4216 return false;
4219 $fs = get_file_storage();
4220 $relativepath = implode('/', $args);
4221 $fullpath = "/$context->id/mod_forum/$filearea/$postid/$relativepath";
4222 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
4223 return false;
4226 // Make sure groups allow this user to see this file
4227 if ($discussion->groupid > 0) {
4228 $groupmode = groups_get_activity_groupmode($cm, $course);
4229 if ($groupmode == SEPARATEGROUPS) {
4230 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
4231 return false;
4236 // Make sure we're allowed to see it...
4237 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4238 return false;
4241 // finally send the file
4242 send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security!
4246 * If successful, this function returns the name of the file
4248 * @global object
4249 * @param object $post is a full post record, including course and forum
4250 * @param object $forum
4251 * @param object $cm
4252 * @param mixed $mform
4253 * @param string $message
4254 * @return bool
4256 function forum_add_attachment($post, $forum, $cm, $mform=null, &$message=null) {
4257 global $DB;
4259 if (empty($mform)) {
4260 return false;
4263 if (empty($post->attachments)) {
4264 return true; // Nothing to do
4267 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4269 $info = file_get_draft_area_info($post->attachments);
4270 $present = ($info['filecount']>0) ? '1' : '';
4271 file_save_draft_area_files($post->attachments, $context->id, 'mod_forum', 'attachment', $post->id,
4272 mod_forum_post_form::attachment_options($forum));
4274 $DB->set_field('forum_posts', 'attachment', $present, array('id'=>$post->id));
4276 return true;
4280 * Add a new post in an existing discussion.
4282 * @global object
4283 * @global object
4284 * @global object
4285 * @param object $post
4286 * @param mixed $mform
4287 * @param string $message
4288 * @return int
4290 function forum_add_new_post($post, $mform, &$message) {
4291 global $USER, $CFG, $DB;
4293 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
4294 $forum = $DB->get_record('forum', array('id' => $discussion->forum));
4295 $cm = get_coursemodule_from_instance('forum', $forum->id);
4296 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4298 $post->created = $post->modified = time();
4299 $post->mailed = "0";
4300 $post->userid = $USER->id;
4301 $post->attachment = "";
4303 $post->id = $DB->insert_record("forum_posts", $post);
4304 $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id,
4305 mod_forum_post_form::editor_options(), $post->message);
4306 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
4307 forum_add_attachment($post, $forum, $cm, $mform, $message);
4309 // Update discussion modified date
4310 $DB->set_field("forum_discussions", "timemodified", $post->modified, array("id" => $post->discussion));
4311 $DB->set_field("forum_discussions", "usermodified", $post->userid, array("id" => $post->discussion));
4313 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4314 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4317 return $post->id;
4321 * Update a post
4323 * @global object
4324 * @global object
4325 * @global object
4326 * @param object $post
4327 * @param mixed $mform
4328 * @param string $message
4329 * @return bool
4331 function forum_update_post($post, $mform, &$message) {
4332 global $USER, $CFG, $DB;
4334 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
4335 $forum = $DB->get_record('forum', array('id' => $discussion->forum));
4336 $cm = get_coursemodule_from_instance('forum', $forum->id);
4337 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4339 $post->modified = time();
4341 $DB->update_record('forum_posts', $post);
4343 $discussion->timemodified = $post->modified; // last modified tracking
4344 $discussion->usermodified = $post->userid; // last modified tracking
4346 if (!$post->parent) { // Post is a discussion starter - update discussion title and times too
4347 $discussion->name = $post->subject;
4348 $discussion->timestart = $post->timestart;
4349 $discussion->timeend = $post->timeend;
4351 $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id,
4352 mod_forum_post_form::editor_options(), $post->message);
4353 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
4355 $DB->update_record('forum_discussions', $discussion);
4357 forum_add_attachment($post, $forum, $cm, $mform, $message);
4359 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4360 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4363 return true;
4367 * Given an object containing all the necessary data,
4368 * create a new discussion and return the id
4370 * @global object
4371 * @global object
4372 * @global object
4373 * @param object $post
4374 * @param mixed $mform
4375 * @param string $message
4376 * @param int $userid
4377 * @return object
4379 function forum_add_discussion($discussion, $mform=null, &$message=null, $userid=null) {
4380 global $USER, $CFG, $DB;
4382 $timenow = time();
4384 if (is_null($userid)) {
4385 $userid = $USER->id;
4388 // The first post is stored as a real post, and linked
4389 // to from the discuss entry.
4391 $forum = $DB->get_record('forum', array('id'=>$discussion->forum));
4392 $cm = get_coursemodule_from_instance('forum', $forum->id);
4394 $post = new stdClass();
4395 $post->discussion = 0;
4396 $post->parent = 0;
4397 $post->userid = $userid;
4398 $post->created = $timenow;
4399 $post->modified = $timenow;
4400 $post->mailed = 0;
4401 $post->subject = $discussion->name;
4402 $post->message = $discussion->message;
4403 $post->messageformat = $discussion->messageformat;
4404 $post->messagetrust = $discussion->messagetrust;
4405 $post->attachments = isset($discussion->attachments) ? $discussion->attachments : null;
4406 $post->forum = $forum->id; // speedup
4407 $post->course = $forum->course; // speedup
4408 $post->mailnow = $discussion->mailnow;
4410 $post->id = $DB->insert_record("forum_posts", $post);
4412 // TODO: Fix the calling code so that there always is a $cm when this function is called
4413 if (!empty($cm->id) && !empty($discussion->itemid)) { // In "single simple discussions" this may not exist yet
4414 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4415 $text = file_save_draft_area_files($discussion->itemid, $context->id, 'mod_forum', 'post', $post->id,
4416 mod_forum_post_form::editor_options(), $post->message);
4417 $DB->set_field('forum_posts', 'message', $text, array('id'=>$post->id));
4420 // Now do the main entry for the discussion, linking to this first post
4422 $discussion->firstpost = $post->id;
4423 $discussion->timemodified = $timenow;
4424 $discussion->usermodified = $post->userid;
4425 $discussion->userid = $userid;
4427 $post->discussion = $DB->insert_record("forum_discussions", $discussion);
4429 // Finally, set the pointer on the post.
4430 $DB->set_field("forum_posts", "discussion", $post->discussion, array("id"=>$post->id));
4432 if (!empty($cm->id)) {
4433 forum_add_attachment($post, $forum, $cm, $mform, $message);
4436 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4437 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4440 return $post->discussion;
4445 * Deletes a discussion and handles all associated cleanup.
4447 * @global object
4448 * @param object $discussion Discussion to delete
4449 * @param bool $fulldelete True when deleting entire forum
4450 * @param object $course Course
4451 * @param object $cm Course-module
4452 * @param object $forum Forum
4453 * @return bool
4455 function forum_delete_discussion($discussion, $fulldelete, $course, $cm, $forum) {
4456 global $DB, $CFG;
4457 require_once($CFG->libdir.'/completionlib.php');
4459 $result = true;
4461 if ($posts = $DB->get_records("forum_posts", array("discussion" => $discussion->id))) {
4462 foreach ($posts as $post) {
4463 $post->course = $discussion->course;
4464 $post->forum = $discussion->forum;
4465 if (!forum_delete_post($post, 'ignore', $course, $cm, $forum, $fulldelete)) {
4466 $result = false;
4471 forum_tp_delete_read_records(-1, -1, $discussion->id);
4473 if (!$DB->delete_records("forum_discussions", array("id"=>$discussion->id))) {
4474 $result = false;
4477 // Update completion state if we are tracking completion based on number of posts
4478 // But don't bother when deleting whole thing
4479 if (!$fulldelete) {
4480 $completion = new completion_info($course);
4481 if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC &&
4482 ($forum->completiondiscussions || $forum->completionreplies || $forum->completionposts)) {
4483 $completion->update_state($cm, COMPLETION_INCOMPLETE, $discussion->userid);
4487 return $result;
4492 * Deletes a single forum post.
4494 * @global object
4495 * @param object $post Forum post object
4496 * @param mixed $children Whether to delete children. If false, returns false
4497 * if there are any children (without deleting the post). If true,
4498 * recursively deletes all children. If set to special value 'ignore', deletes
4499 * post regardless of children (this is for use only when deleting all posts
4500 * in a disussion).
4501 * @param object $course Course
4502 * @param object $cm Course-module
4503 * @param object $forum Forum
4504 * @param bool $skipcompletion True to skip updating completion state if it
4505 * would otherwise be updated, i.e. when deleting entire forum anyway.
4506 * @return bool
4508 function forum_delete_post($post, $children, $course, $cm, $forum, $skipcompletion=false) {
4509 global $DB, $CFG;
4510 require_once($CFG->libdir.'/completionlib.php');
4512 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4514 if ($children !== 'ignore' && ($childposts = $DB->get_records('forum_posts', array('parent'=>$post->id)))) {
4515 if ($children) {
4516 foreach ($childposts as $childpost) {
4517 forum_delete_post($childpost, true, $course, $cm, $forum, $skipcompletion);
4519 } else {
4520 return false;
4524 //delete ratings
4525 require_once($CFG->dirroot.'/rating/lib.php');
4526 $delopt = new stdClass;
4527 $delopt->contextid = $context->id;
4528 $delopt->component = 'mod_forum';
4529 $delopt->ratingarea = 'post';
4530 $delopt->itemid = $post->id;
4531 $rm = new rating_manager();
4532 $rm->delete_ratings($delopt);
4534 //delete attachments
4535 $fs = get_file_storage();
4536 $fs->delete_area_files($context->id, 'mod_forum', 'attachment', $post->id);
4537 $fs->delete_area_files($context->id, 'mod_forum', 'post', $post->id);
4539 if ($DB->delete_records("forum_posts", array("id" => $post->id))) {
4541 forum_tp_delete_read_records(-1, $post->id);
4543 // Just in case we are deleting the last post
4544 forum_discussion_update_last_post($post->discussion);
4546 // Update completion state if we are tracking completion based on number of posts
4547 // But don't bother when deleting whole thing
4549 if (!$skipcompletion) {
4550 $completion = new completion_info($course);
4551 if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC &&
4552 ($forum->completiondiscussions || $forum->completionreplies || $forum->completionposts)) {
4553 $completion->update_state($cm, COMPLETION_INCOMPLETE, $post->userid);
4557 return true;
4559 return false;
4563 * @global object
4564 * @param object $post
4565 * @param bool $children
4566 * @return int
4568 function forum_count_replies($post, $children=true) {
4569 global $DB;
4570 $count = 0;
4572 if ($children) {
4573 if ($childposts = $DB->get_records('forum_posts', array('parent' => $post->id))) {
4574 foreach ($childposts as $childpost) {
4575 $count ++; // For this child
4576 $count += forum_count_replies($childpost, true);
4579 } else {
4580 $count += $DB->count_records('forum_posts', array('parent' => $post->id));
4583 return $count;
4588 * @global object
4589 * @param int $forumid
4590 * @param mixed $value
4591 * @return bool
4593 function forum_forcesubscribe($forumid, $value=1) {
4594 global $DB;
4595 return $DB->set_field("forum", "forcesubscribe", $value, array("id" => $forumid));
4599 * @global object
4600 * @param object $forum
4601 * @return bool
4603 function forum_is_forcesubscribed($forum) {
4604 global $DB;
4605 if (isset($forum->forcesubscribe)) { // then we use that
4606 return ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE);
4607 } else { // Check the database
4608 return ($DB->get_field('forum', 'forcesubscribe', array('id' => $forum)) == FORUM_FORCESUBSCRIBE);
4612 function forum_get_forcesubscribed($forum) {
4613 global $DB;
4614 if (isset($forum->forcesubscribe)) { // then we use that
4615 return $forum->forcesubscribe;
4616 } else { // Check the database
4617 return $DB->get_field('forum', 'forcesubscribe', array('id' => $forum));
4622 * @global object
4623 * @param int $userid
4624 * @param object $forum
4625 * @return bool
4627 function forum_is_subscribed($userid, $forum) {
4628 global $DB;
4629 if (is_numeric($forum)) {
4630 $forum = $DB->get_record('forum', array('id' => $forum));
4632 // If forum is force subscribed and has allowforcesubscribe, then user is subscribed.
4633 $cm = get_coursemodule_from_instance('forum', $forum->id);
4634 if (forum_is_forcesubscribed($forum) && $cm &&
4635 has_capability('mod/forum:allowforcesubscribe', context_module::instance($cm->id), $userid)) {
4636 return true;
4638 return $DB->record_exists("forum_subscriptions", array("userid" => $userid, "forum" => $forum->id));
4641 function forum_get_subscribed_forums($course) {
4642 global $USER, $CFG, $DB;
4643 $sql = "SELECT f.id
4644 FROM {forum} f
4645 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = ?)
4646 WHERE f.course = ?
4647 AND f.forcesubscribe <> ".FORUM_DISALLOWSUBSCRIBE."
4648 AND (f.forcesubscribe = ".FORUM_FORCESUBSCRIBE." OR fs.id IS NOT NULL)";
4649 if ($subscribed = $DB->get_records_sql($sql, array($USER->id, $course->id))) {
4650 foreach ($subscribed as $s) {
4651 $subscribed[$s->id] = $s->id;
4653 return $subscribed;
4654 } else {
4655 return array();
4660 * Returns an array of forums that the current user is subscribed to and is allowed to unsubscribe from
4662 * @return array An array of unsubscribable forums
4664 function forum_get_optional_subscribed_forums() {
4665 global $USER, $DB;
4667 // Get courses that $USER is enrolled in and can see
4668 $courses = enrol_get_my_courses();
4669 if (empty($courses)) {
4670 return array();
4673 $courseids = array();
4674 foreach($courses as $course) {
4675 $courseids[] = $course->id;
4677 list($coursesql, $courseparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'c');
4679 // get all forums from the user's courses that they are subscribed to and which are not set to forced
4680 $sql = "SELECT f.id, cm.id as cm, cm.visible
4681 FROM {forum} f
4682 JOIN {course_modules} cm ON cm.instance = f.id
4683 JOIN {modules} m ON m.name = :modulename AND m.id = cm.module
4684 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = :userid)
4685 WHERE f.forcesubscribe <> :forcesubscribe AND fs.id IS NOT NULL
4686 AND cm.course $coursesql";
4687 $params = array_merge($courseparams, array('modulename'=>'forum', 'userid'=>$USER->id, 'forcesubscribe'=>FORUM_FORCESUBSCRIBE));
4688 if (!$forums = $DB->get_records_sql($sql, $params)) {
4689 return array();
4692 $unsubscribableforums = array(); // Array to return
4694 foreach($forums as $forum) {
4696 if (empty($forum->visible)) {
4697 // the forum is hidden
4698 $context = context_module::instance($forum->cm);
4699 if (!has_capability('moodle/course:viewhiddenactivities', $context)) {
4700 // the user can't see the hidden forum
4701 continue;
4705 // subscribe.php only requires 'mod/forum:managesubscriptions' when
4706 // unsubscribing a user other than yourself so we don't require it here either
4708 // A check for whether the forum has subscription set to forced is built into the SQL above
4710 $unsubscribableforums[] = $forum;
4713 return $unsubscribableforums;
4717 * Adds user to the subscriber list
4719 * @global object
4720 * @param int $userid
4721 * @param int $forumid
4723 function forum_subscribe($userid, $forumid) {
4724 global $DB;
4726 if ($DB->record_exists("forum_subscriptions", array("userid"=>$userid, "forum"=>$forumid))) {
4727 return true;
4730 $sub = new stdClass();
4731 $sub->userid = $userid;
4732 $sub->forum = $forumid;
4734 return $DB->insert_record("forum_subscriptions", $sub);
4738 * Removes user from the subscriber list
4740 * @global object
4741 * @param int $userid
4742 * @param int $forumid
4744 function forum_unsubscribe($userid, $forumid) {
4745 global $DB;
4746 return $DB->delete_records("forum_subscriptions", array("userid"=>$userid, "forum"=>$forumid));
4750 * Given a new post, subscribes or unsubscribes as appropriate.
4751 * Returns some text which describes what happened.
4753 * @global objec
4754 * @param object $post
4755 * @param object $forum
4757 function forum_post_subscription($post, $forum) {
4759 global $USER;
4761 $action = '';
4762 $subscribed = forum_is_subscribed($USER->id, $forum);
4764 if ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE) { // database ignored
4765 return "";
4767 } elseif (($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE)
4768 && !has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $forum->course), $USER->id)) {
4769 if ($subscribed) {
4770 $action = 'unsubscribe'; // sanity check, following MDL-14558
4771 } else {
4772 return "";
4775 } else { // go with the user's choice
4776 if (isset($post->subscribe)) {
4777 // no change
4778 if ((!empty($post->subscribe) && $subscribed)
4779 || (empty($post->subscribe) && !$subscribed)) {
4780 return "";
4782 } elseif (!empty($post->subscribe) && !$subscribed) {
4783 $action = 'subscribe';
4785 } elseif (empty($post->subscribe) && $subscribed) {
4786 $action = 'unsubscribe';
4791 $info = new stdClass();
4792 $info->name = fullname($USER);
4793 $info->forum = format_string($forum->name);
4795 switch ($action) {
4796 case 'subscribe':
4797 forum_subscribe($USER->id, $post->forum);
4798 return "<p>".get_string("nowsubscribed", "forum", $info)."</p>";
4799 case 'unsubscribe':
4800 forum_unsubscribe($USER->id, $post->forum);
4801 return "<p>".get_string("nownotsubscribed", "forum", $info)."</p>";
4806 * Generate and return the subscribe or unsubscribe link for a forum.
4808 * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
4809 * @param object $context the context object for this forum.
4810 * @param array $messages text used for the link in its various states
4811 * (subscribed, unsubscribed, forcesubscribed or cantsubscribe).
4812 * Any strings not passed in are taken from the $defaultmessages array
4813 * at the top of the function.
4814 * @param bool $cantaccessagroup
4815 * @param bool $fakelink
4816 * @param bool $backtoindex
4817 * @param array $subscribed_forums
4818 * @return string
4820 function forum_get_subscribe_link($forum, $context, $messages = array(), $cantaccessagroup = false, $fakelink=true, $backtoindex=false, $subscribed_forums=null) {
4821 global $CFG, $USER, $PAGE, $OUTPUT;
4822 $defaultmessages = array(
4823 'subscribed' => get_string('unsubscribe', 'forum'),
4824 'unsubscribed' => get_string('subscribe', 'forum'),
4825 'cantaccessgroup' => get_string('no'),
4826 'forcesubscribed' => get_string('everyoneissubscribed', 'forum'),
4827 'cantsubscribe' => get_string('disallowsubscribe','forum')
4829 $messages = $messages + $defaultmessages;
4831 if (forum_is_forcesubscribed($forum)) {
4832 return $messages['forcesubscribed'];
4833 } else if ($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE && !has_capability('mod/forum:managesubscriptions', $context)) {
4834 return $messages['cantsubscribe'];
4835 } else if ($cantaccessagroup) {
4836 return $messages['cantaccessgroup'];
4837 } else {
4838 if (!is_enrolled($context, $USER, '', true)) {
4839 return '';
4841 if (is_null($subscribed_forums)) {
4842 $subscribed = forum_is_subscribed($USER->id, $forum);
4843 } else {
4844 $subscribed = !empty($subscribed_forums[$forum->id]);
4846 if ($subscribed) {
4847 $linktext = $messages['subscribed'];
4848 $linktitle = get_string('subscribestop', 'forum');
4849 } else {
4850 $linktext = $messages['unsubscribed'];
4851 $linktitle = get_string('subscribestart', 'forum');
4854 $options = array();
4855 if ($backtoindex) {
4856 $backtoindexlink = '&amp;backtoindex=1';
4857 $options['backtoindex'] = 1;
4858 } else {
4859 $backtoindexlink = '';
4861 $link = '';
4863 if ($fakelink) {
4864 $PAGE->requires->js('/mod/forum/forum.js');
4865 $PAGE->requires->js_function_call('forum_produce_subscribe_link', array($forum->id, $backtoindexlink, $linktext, $linktitle));
4866 $link = "<noscript>";
4868 $options['id'] = $forum->id;
4869 $options['sesskey'] = sesskey();
4870 $url = new moodle_url('/mod/forum/subscribe.php', $options);
4871 $link .= $OUTPUT->single_button($url, $linktext, 'get', array('title'=>$linktitle));
4872 if ($fakelink) {
4873 $link .= '</noscript>';
4876 return $link;
4882 * Generate and return the track or no track link for a forum.
4884 * @global object
4885 * @global object
4886 * @global object
4887 * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
4888 * @param array $messages
4889 * @param bool $fakelink
4890 * @return string
4892 function forum_get_tracking_link($forum, $messages=array(), $fakelink=true) {
4893 global $CFG, $USER, $PAGE, $OUTPUT;
4895 static $strnotrackforum, $strtrackforum;
4897 if (isset($messages['trackforum'])) {
4898 $strtrackforum = $messages['trackforum'];
4900 if (isset($messages['notrackforum'])) {
4901 $strnotrackforum = $messages['notrackforum'];
4903 if (empty($strtrackforum)) {
4904 $strtrackforum = get_string('trackforum', 'forum');
4906 if (empty($strnotrackforum)) {
4907 $strnotrackforum = get_string('notrackforum', 'forum');
4910 if (forum_tp_is_tracked($forum)) {
4911 $linktitle = $strnotrackforum;
4912 $linktext = $strnotrackforum;
4913 } else {
4914 $linktitle = $strtrackforum;
4915 $linktext = $strtrackforum;
4918 $link = '';
4919 if ($fakelink) {
4920 $PAGE->requires->js('/mod/forum/forum.js');
4921 $PAGE->requires->js_function_call('forum_produce_tracking_link', Array($forum->id, $linktext, $linktitle));
4922 // use <noscript> to print button in case javascript is not enabled
4923 $link .= '<noscript>';
4925 $url = new moodle_url('/mod/forum/settracking.php', array('id'=>$forum->id));
4926 $link .= $OUTPUT->single_button($url, $linktext, 'get', array('title'=>$linktitle));
4928 if ($fakelink) {
4929 $link .= '</noscript>';
4932 return $link;
4938 * Returns true if user created new discussion already
4940 * @global object
4941 * @global object
4942 * @param int $forumid
4943 * @param int $userid
4944 * @return bool
4946 function forum_user_has_posted_discussion($forumid, $userid) {
4947 global $CFG, $DB;
4949 $sql = "SELECT 'x'
4950 FROM {forum_discussions} d, {forum_posts} p
4951 WHERE d.forum = ? AND p.discussion = d.id AND p.parent = 0 and p.userid = ?";
4953 return $DB->record_exists_sql($sql, array($forumid, $userid));
4957 * @global object
4958 * @global object
4959 * @param int $forumid
4960 * @param int $userid
4961 * @return array
4963 function forum_discussions_user_has_posted_in($forumid, $userid) {
4964 global $CFG, $DB;
4966 $haspostedsql = "SELECT d.id AS id,
4968 FROM {forum_posts} p,
4969 {forum_discussions} d
4970 WHERE p.discussion = d.id
4971 AND d.forum = ?
4972 AND p.userid = ?";
4974 return $DB->get_records_sql($haspostedsql, array($forumid, $userid));
4978 * @global object
4979 * @global object
4980 * @param int $forumid
4981 * @param int $did
4982 * @param int $userid
4983 * @return bool
4985 function forum_user_has_posted($forumid, $did, $userid) {
4986 global $DB;
4988 if (empty($did)) {
4989 // posted in any forum discussion?
4990 $sql = "SELECT 'x'
4991 FROM {forum_posts} p
4992 JOIN {forum_discussions} d ON d.id = p.discussion
4993 WHERE p.userid = :userid AND d.forum = :forumid";
4994 return $DB->record_exists_sql($sql, array('forumid'=>$forumid,'userid'=>$userid));
4995 } else {
4996 return $DB->record_exists('forum_posts', array('discussion'=>$did,'userid'=>$userid));
5001 * Returns creation time of the first user's post in given discussion
5002 * @global object $DB
5003 * @param int $did Discussion id
5004 * @param int $userid User id
5005 * @return int|bool post creation time stamp or return false
5007 function forum_get_user_posted_time($did, $userid) {
5008 global $DB;
5010 $posttime = $DB->get_field('forum_posts', 'MIN(created)', array('userid'=>$userid, 'discussion'=>$did));
5011 if (empty($posttime)) {
5012 return false;
5014 return $posttime;
5018 * @global object
5019 * @param object $forum
5020 * @param object $currentgroup
5021 * @param int $unused
5022 * @param object $cm
5023 * @param object $context
5024 * @return bool
5026 function forum_user_can_post_discussion($forum, $currentgroup=null, $unused=-1, $cm=NULL, $context=NULL) {
5027 // $forum is an object
5028 global $USER;
5030 // shortcut - guest and not-logged-in users can not post
5031 if (isguestuser() or !isloggedin()) {
5032 return false;
5035 if (!$cm) {
5036 debugging('missing cm', DEBUG_DEVELOPER);
5037 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5038 print_error('invalidcoursemodule');
5042 if (!$context) {
5043 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
5046 if ($currentgroup === null) {
5047 $currentgroup = groups_get_activity_group($cm);
5050 $groupmode = groups_get_activity_groupmode($cm);
5052 if ($forum->type == 'news') {
5053 $capname = 'mod/forum:addnews';
5054 } else if ($forum->type == 'qanda') {
5055 $capname = 'mod/forum:addquestion';
5056 } else {
5057 $capname = 'mod/forum:startdiscussion';
5060 if (!has_capability($capname, $context)) {
5061 return false;
5064 if ($forum->type == 'single') {
5065 return false;
5068 if ($forum->type == 'eachuser') {
5069 if (forum_user_has_posted_discussion($forum->id, $USER->id)) {
5070 return false;
5074 if (!$groupmode or has_capability('moodle/site:accessallgroups', $context)) {
5075 return true;
5078 if ($currentgroup) {
5079 return groups_is_member($currentgroup);
5080 } else {
5081 // no group membership and no accessallgroups means no new discussions
5082 // reverted to 1.7 behaviour in 1.9+, buggy in 1.8.0-1.9.0
5083 return false;
5088 * This function checks whether the user can reply to posts in a forum
5089 * discussion. Use forum_user_can_post_discussion() to check whether the user
5090 * can start discussions.
5092 * @global object
5093 * @global object
5094 * @uses DEBUG_DEVELOPER
5095 * @uses CONTEXT_MODULE
5096 * @uses VISIBLEGROUPS
5097 * @param object $forum forum object
5098 * @param object $discussion
5099 * @param object $user
5100 * @param object $cm
5101 * @param object $course
5102 * @param object $context
5103 * @return bool
5105 function forum_user_can_post($forum, $discussion, $user=NULL, $cm=NULL, $course=NULL, $context=NULL) {
5106 global $USER, $DB;
5107 if (empty($user)) {
5108 $user = $USER;
5111 // shortcut - guest and not-logged-in users can not post
5112 if (isguestuser($user) or empty($user->id)) {
5113 return false;
5116 if (!isset($discussion->groupid)) {
5117 debugging('incorrect discussion parameter', DEBUG_DEVELOPER);
5118 return false;
5121 if (!$cm) {
5122 debugging('missing cm', DEBUG_DEVELOPER);
5123 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5124 print_error('invalidcoursemodule');
5128 if (!$course) {
5129 debugging('missing course', DEBUG_DEVELOPER);
5130 if (!$course = $DB->get_record('course', array('id' => $forum->course))) {
5131 print_error('invalidcourseid');
5135 if (!$context) {
5136 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
5139 // normal users with temporary guest access can not post, suspended users can not post either
5140 if (!is_viewing($context, $user->id) and !is_enrolled($context, $user->id, '', true)) {
5141 return false;
5144 if ($forum->type == 'news') {
5145 $capname = 'mod/forum:replynews';
5146 } else {
5147 $capname = 'mod/forum:replypost';
5150 if (!has_capability($capname, $context, $user->id)) {
5151 return false;
5154 if (!$groupmode = groups_get_activity_groupmode($cm, $course)) {
5155 return true;
5158 if (has_capability('moodle/site:accessallgroups', $context)) {
5159 return true;
5162 if ($groupmode == VISIBLEGROUPS) {
5163 if ($discussion->groupid == -1) {
5164 // allow students to reply to all participants discussions - this was not possible in Moodle <1.8
5165 return true;
5167 return groups_is_member($discussion->groupid);
5169 } else {
5170 //separate groups
5171 if ($discussion->groupid == -1) {
5172 return false;
5174 return groups_is_member($discussion->groupid);
5179 * checks to see if a user can view a particular post
5181 * @global object
5182 * @global object
5183 * @uses CONTEXT_MODULE
5184 * @uses SEPARATEGROUPS
5185 * @param object $post
5186 * @param object $course
5187 * @param object $cm
5188 * @param object $forum
5189 * @param object $discussion
5190 * @param object $user
5192 function forum_user_can_view_post($post, $course, $cm, $forum, $discussion, $user=NULL){
5194 global $CFG, $USER;
5196 if (!$user){
5197 $user = $USER;
5200 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5201 if (!has_capability('mod/forum:viewdiscussion', $modcontext)) {
5202 return false;
5205 // If it's a grouped discussion, make sure the user is a member
5206 if ($discussion->groupid > 0) {
5207 $groupmode = groups_get_activity_groupmode($cm);
5208 if ($groupmode == SEPARATEGROUPS) {
5209 return groups_is_member($discussion->groupid) || has_capability('moodle/site:accessallgroups', $modcontext);
5212 return true;
5216 * Check to ensure a user can view a timed discussion.
5218 * @param object $discussion
5219 * @param object $user
5220 * @param object $context
5221 * @return boolean returns true if they can view post, false otherwise
5223 function forum_user_can_see_timed_discussion($discussion, $user, $context) {
5224 global $CFG;
5226 // Check that the user can view a discussion that is normally hidden due to access times.
5227 if (!empty($CFG->forum_enabletimedposts)) {
5228 $time = time();
5229 if (($discussion->timestart != 0 && $discussion->timestart > $time)
5230 || ($discussion->timeend != 0 && $discussion->timeend < $time)) {
5231 if (!has_capability('mod/forum:viewhiddentimedposts', $context, $user->id)) {
5232 return false;
5237 return true;
5241 * Check to ensure a user can view a group discussion.
5243 * @param object $discussion
5244 * @param object $cm
5245 * @param object $context
5246 * @return boolean returns true if they can view post, false otherwise
5248 function forum_user_can_see_group_discussion($discussion, $cm, $context) {
5250 // If it's a grouped discussion, make sure the user is a member.
5251 if ($discussion->groupid > 0) {
5252 $groupmode = groups_get_activity_groupmode($cm);
5253 if ($groupmode == SEPARATEGROUPS) {
5254 return groups_is_member($discussion->groupid) || has_capability('moodle/site:accessallgroups', $context);
5258 return true;
5262 * @global object
5263 * @global object
5264 * @uses DEBUG_DEVELOPER
5265 * @param object $forum
5266 * @param object $discussion
5267 * @param object $context
5268 * @param object $user
5269 * @return bool
5271 function forum_user_can_see_discussion($forum, $discussion, $context, $user=NULL) {
5272 global $USER, $DB;
5274 if (empty($user) || empty($user->id)) {
5275 $user = $USER;
5278 // retrieve objects (yuk)
5279 if (is_numeric($forum)) {
5280 debugging('missing full forum', DEBUG_DEVELOPER);
5281 if (!$forum = $DB->get_record('forum',array('id'=>$forum))) {
5282 return false;
5285 if (is_numeric($discussion)) {
5286 debugging('missing full discussion', DEBUG_DEVELOPER);
5287 if (!$discussion = $DB->get_record('forum_discussions',array('id'=>$discussion))) {
5288 return false;
5291 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5292 print_error('invalidcoursemodule');
5295 if (!has_capability('mod/forum:viewdiscussion', $context)) {
5296 return false;
5299 if (!forum_user_can_see_timed_discussion($discussion, $user, $context)) {
5300 return false;
5303 if (!forum_user_can_see_group_discussion($discussion, $cm, $context)) {
5304 return false;
5307 if ($forum->type == 'qanda' &&
5308 !forum_user_has_posted($forum->id, $discussion->id, $user->id) &&
5309 !has_capability('mod/forum:viewqandawithoutposting', $context)) {
5310 return false;
5312 return true;
5317 * @global object
5318 * @global object
5319 * @param object $forum
5320 * @param object $discussion
5321 * @param object $post
5322 * @param object $user
5323 * @param object $cm
5324 * @return bool
5326 function forum_user_can_see_post($forum, $discussion, $post, $user=NULL, $cm=NULL) {
5327 global $CFG, $USER, $DB;
5329 // Context used throughout function.
5330 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5332 // retrieve objects (yuk)
5333 if (is_numeric($forum)) {
5334 debugging('missing full forum', DEBUG_DEVELOPER);
5335 if (!$forum = $DB->get_record('forum',array('id'=>$forum))) {
5336 return false;
5340 if (is_numeric($discussion)) {
5341 debugging('missing full discussion', DEBUG_DEVELOPER);
5342 if (!$discussion = $DB->get_record('forum_discussions',array('id'=>$discussion))) {
5343 return false;
5346 if (is_numeric($post)) {
5347 debugging('missing full post', DEBUG_DEVELOPER);
5348 if (!$post = $DB->get_record('forum_posts',array('id'=>$post))) {
5349 return false;
5353 if (!isset($post->id) && isset($post->parent)) {
5354 $post->id = $post->parent;
5357 if (!$cm) {
5358 debugging('missing cm', DEBUG_DEVELOPER);
5359 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5360 print_error('invalidcoursemodule');
5364 if (empty($user) || empty($user->id)) {
5365 $user = $USER;
5368 $canviewdiscussion = !empty($cm->cache->caps['mod/forum:viewdiscussion']) || has_capability('mod/forum:viewdiscussion', $modcontext, $user->id);
5369 if (!$canviewdiscussion && !has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), get_context_instance(CONTEXT_USER, $post->userid))) {
5370 return false;
5373 if (isset($cm->uservisible)) {
5374 if (!$cm->uservisible) {
5375 return false;
5377 } else {
5378 if (!coursemodule_visible_for_user($cm, $user->id)) {
5379 return false;
5383 if (!forum_user_can_see_timed_discussion($discussion, $user, $modcontext)) {
5384 return false;
5387 if (!forum_user_can_see_group_discussion($discussion, $cm, $modcontext)) {
5388 return false;
5391 if ($forum->type == 'qanda') {
5392 $firstpost = forum_get_firstpost_from_discussion($discussion->id);
5393 $userfirstpost = forum_get_user_posted_time($discussion->id, $user->id);
5395 return (($userfirstpost !== false && (time() - $userfirstpost >= $CFG->maxeditingtime)) ||
5396 $firstpost->id == $post->id || $post->userid == $user->id || $firstpost->userid == $user->id ||
5397 has_capability('mod/forum:viewqandawithoutposting', $modcontext, $user->id));
5399 return true;
5404 * Prints the discussion view screen for a forum.
5406 * @global object
5407 * @global object
5408 * @param object $course The current course object.
5409 * @param object $forum Forum to be printed.
5410 * @param int $maxdiscussions .
5411 * @param string $displayformat The display format to use (optional).
5412 * @param string $sort Sort arguments for database query (optional).
5413 * @param int $groupmode Group mode of the forum (optional).
5414 * @param void $unused (originally current group)
5415 * @param int $page Page mode, page to display (optional).
5416 * @param int $perpage The maximum number of discussions per page(optional)
5419 function forum_print_latest_discussions($course, $forum, $maxdiscussions=-1, $displayformat='plain', $sort='',
5420 $currentgroup=-1, $groupmode=-1, $page=-1, $perpage=100, $cm=NULL) {
5421 global $CFG, $USER, $OUTPUT;
5423 if (!$cm) {
5424 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5425 print_error('invalidcoursemodule');
5428 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
5430 if (empty($sort)) {
5431 $sort = "d.timemodified DESC";
5434 $olddiscussionlink = false;
5436 // Sort out some defaults
5437 if ($perpage <= 0) {
5438 $perpage = 0;
5439 $page = -1;
5442 if ($maxdiscussions == 0) {
5443 // all discussions - backwards compatibility
5444 $page = -1;
5445 $perpage = 0;
5446 if ($displayformat == 'plain') {
5447 $displayformat = 'header'; // Abbreviate display by default
5450 } else if ($maxdiscussions > 0) {
5451 $page = -1;
5452 $perpage = $maxdiscussions;
5455 $fullpost = false;
5456 if ($displayformat == 'plain') {
5457 $fullpost = true;
5461 // Decide if current user is allowed to see ALL the current discussions or not
5463 // First check the group stuff
5464 if ($currentgroup == -1 or $groupmode == -1) {
5465 $groupmode = groups_get_activity_groupmode($cm, $course);
5466 $currentgroup = groups_get_activity_group($cm);
5469 $groups = array(); //cache
5471 // If the user can post discussions, then this is a good place to put the
5472 // button for it. We do not show the button if we are showing site news
5473 // and the current user is a guest.
5475 $canstart = forum_user_can_post_discussion($forum, $currentgroup, $groupmode, $cm, $context);
5476 if (!$canstart and $forum->type !== 'news') {
5477 if (isguestuser() or !isloggedin()) {
5478 $canstart = true;
5480 if (!is_enrolled($context) and !is_viewing($context)) {
5481 // allow guests and not-logged-in to see the button - they are prompted to log in after clicking the link
5482 // normal users with temporary guest access see this button too, they are asked to enrol instead
5483 // do not show the button to users with suspended enrolments here
5484 $canstart = enrol_selfenrol_available($course->id);
5488 if ($canstart) {
5489 echo '<div class="singlebutton forumaddnew">';
5490 echo "<form id=\"newdiscussionform\" method=\"get\" action=\"$CFG->wwwroot/mod/forum/post.php\">";
5491 echo '<div>';
5492 echo "<input type=\"hidden\" name=\"forum\" value=\"$forum->id\" />";
5493 switch ($forum->type) {
5494 case 'news':
5495 case 'blog':
5496 $buttonadd = get_string('addanewtopic', 'forum');
5497 break;
5498 case 'qanda':
5499 $buttonadd = get_string('addanewquestion', 'forum');
5500 break;
5501 default:
5502 $buttonadd = get_string('addanewdiscussion', 'forum');
5503 break;
5505 echo '<input type="submit" value="'.$buttonadd.'" />';
5506 echo '</div>';
5507 echo '</form>';
5508 echo "</div>\n";
5510 } else if (isguestuser() or !isloggedin() or $forum->type == 'news') {
5511 // no button and no info
5513 } else if ($groupmode and has_capability('mod/forum:startdiscussion', $context)) {
5514 // inform users why they can not post new discussion
5515 if ($currentgroup) {
5516 echo $OUTPUT->notification(get_string('cannotadddiscussion', 'forum'));
5517 } else {
5518 echo $OUTPUT->notification(get_string('cannotadddiscussionall', 'forum'));
5522 // Get all the recent discussions we're allowed to see
5524 $getuserlastmodified = ($displayformat == 'header');
5526 if (! $discussions = forum_get_discussions($cm, $sort, $fullpost, null, $maxdiscussions, $getuserlastmodified, $page, $perpage) ) {
5527 echo '<div class="forumnodiscuss">';
5528 if ($forum->type == 'news') {
5529 echo '('.get_string('nonews', 'forum').')';
5530 } else if ($forum->type == 'qanda') {
5531 echo '('.get_string('noquestions','forum').')';
5532 } else {
5533 echo '('.get_string('nodiscussions', 'forum').')';
5535 echo "</div>\n";
5536 return;
5539 // If we want paging
5540 if ($page != -1) {
5541 ///Get the number of discussions found
5542 $numdiscussions = forum_get_discussions_count($cm);
5544 ///Show the paging bar
5545 echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
5546 if ($numdiscussions > 1000) {
5547 // saves some memory on sites with very large forums
5548 $replies = forum_count_discussion_replies($forum->id, $sort, $maxdiscussions, $page, $perpage);
5549 } else {
5550 $replies = forum_count_discussion_replies($forum->id);
5553 } else {
5554 $replies = forum_count_discussion_replies($forum->id);
5556 if ($maxdiscussions > 0 and $maxdiscussions <= count($discussions)) {
5557 $olddiscussionlink = true;
5561 $canviewparticipants = has_capability('moodle/course:viewparticipants',$context);
5563 $strdatestring = get_string('strftimerecentfull');
5565 // Check if the forum is tracked.
5566 if ($cantrack = forum_tp_can_track_forums($forum)) {
5567 $forumtracked = forum_tp_is_tracked($forum);
5568 } else {
5569 $forumtracked = false;
5572 if ($forumtracked) {
5573 $unreads = forum_get_discussions_unread($cm);
5574 } else {
5575 $unreads = array();
5578 if ($displayformat == 'header') {
5579 echo '<table cellspacing="0" class="forumheaderlist">';
5580 echo '<thead>';
5581 echo '<tr>';
5582 echo '<th class="header topic" scope="col">'.get_string('discussion', 'forum').'</th>';
5583 echo '<th class="header author" colspan="2" scope="col">'.get_string('startedby', 'forum').'</th>';
5584 if ($groupmode > 0) {
5585 echo '<th class="header group" scope="col">'.get_string('group').'</th>';
5587 if (has_capability('mod/forum:viewdiscussion', $context)) {
5588 echo '<th class="header replies" scope="col">'.get_string('replies', 'forum').'</th>';
5589 // If the forum can be tracked, display the unread column.
5590 if ($cantrack) {
5591 echo '<th class="header replies" scope="col">'.get_string('unread', 'forum');
5592 if ($forumtracked) {
5593 echo '&nbsp;<a title="'.get_string('markallread', 'forum').
5594 '" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
5595 $forum->id.'&amp;mark=read&amp;returnpage=view.php">'.
5596 '<img src="'.$OUTPUT->pix_url('t/clear') . '" class="iconsmall" alt="'.get_string('markallread', 'forum').'" /></a>';
5598 echo '</th>';
5601 echo '<th class="header lastpost" scope="col">'.get_string('lastpost', 'forum').'</th>';
5602 echo '</tr>';
5603 echo '</thead>';
5604 echo '<tbody>';
5607 foreach ($discussions as $discussion) {
5608 if (!empty($replies[$discussion->discussion])) {
5609 $discussion->replies = $replies[$discussion->discussion]->replies;
5610 $discussion->lastpostid = $replies[$discussion->discussion]->lastpostid;
5611 } else {
5612 $discussion->replies = 0;
5615 // SPECIAL CASE: The front page can display a news item post to non-logged in users.
5616 // All posts are read in this case.
5617 if (!$forumtracked) {
5618 $discussion->unread = '-';
5619 } else if (empty($USER)) {
5620 $discussion->unread = 0;
5621 } else {
5622 if (empty($unreads[$discussion->discussion])) {
5623 $discussion->unread = 0;
5624 } else {
5625 $discussion->unread = $unreads[$discussion->discussion];
5629 if (isloggedin()) {
5630 $ownpost = ($discussion->userid == $USER->id);
5631 } else {
5632 $ownpost=false;
5634 // Use discussion name instead of subject of first post
5635 $discussion->subject = $discussion->name;
5637 switch ($displayformat) {
5638 case 'header':
5639 if ($groupmode > 0) {
5640 if (isset($groups[$discussion->groupid])) {
5641 $group = $groups[$discussion->groupid];
5642 } else {
5643 $group = $groups[$discussion->groupid] = groups_get_group($discussion->groupid);
5645 } else {
5646 $group = -1;
5648 forum_print_discussion_header($discussion, $forum, $group, $strdatestring, $cantrack, $forumtracked,
5649 $canviewparticipants, $context);
5650 break;
5651 default:
5652 $link = false;
5654 if ($discussion->replies) {
5655 $link = true;
5656 } else {
5657 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5658 $link = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
5661 $discussion->forum = $forum->id;
5663 forum_print_post($discussion, $discussion, $forum, $cm, $course, $ownpost, 0, $link, false,
5664 '', null, true, $forumtracked);
5665 break;
5669 if ($displayformat == "header") {
5670 echo '</tbody>';
5671 echo '</table>';
5674 if ($olddiscussionlink) {
5675 if ($forum->type == 'news') {
5676 $strolder = get_string('oldertopics', 'forum');
5677 } else {
5678 $strolder = get_string('olderdiscussions', 'forum');
5680 echo '<div class="forumolddiscuss">';
5681 echo '<a href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'&amp;showall=1">';
5682 echo $strolder.'</a> ...</div>';
5685 if ($page != -1) { ///Show the paging bar
5686 echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
5692 * Prints a forum discussion
5694 * @uses CONTEXT_MODULE
5695 * @uses FORUM_MODE_FLATNEWEST
5696 * @uses FORUM_MODE_FLATOLDEST
5697 * @uses FORUM_MODE_THREADED
5698 * @uses FORUM_MODE_NESTED
5699 * @param stdClass $course
5700 * @param stdClass $cm
5701 * @param stdClass $forum
5702 * @param stdClass $discussion
5703 * @param stdClass $post
5704 * @param int $mode
5705 * @param mixed $canreply
5706 * @param bool $canrate
5708 function forum_print_discussion($course, $cm, $forum, $discussion, $post, $mode, $canreply=NULL, $canrate=false) {
5709 global $USER, $CFG;
5711 require_once($CFG->dirroot.'/rating/lib.php');
5713 $ownpost = (isloggedin() && $USER->id == $post->userid);
5715 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5716 if ($canreply === NULL) {
5717 $reply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
5718 } else {
5719 $reply = $canreply;
5722 // $cm holds general cache for forum functions
5723 $cm->cache = new stdClass;
5724 $cm->cache->groups = groups_get_all_groups($course->id, 0, $cm->groupingid);
5725 $cm->cache->usersgroups = array();
5727 $posters = array();
5729 // preload all posts - TODO: improve...
5730 if ($mode == FORUM_MODE_FLATNEWEST) {
5731 $sort = "p.created DESC";
5732 } else {
5733 $sort = "p.created ASC";
5736 $forumtracked = forum_tp_is_tracked($forum);
5737 $posts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
5738 $post = $posts[$post->id];
5740 foreach ($posts as $pid=>$p) {
5741 $posters[$p->userid] = $p->userid;
5744 // preload all groups of ppl that posted in this discussion
5745 if ($postersgroups = groups_get_all_groups($course->id, $posters, $cm->groupingid, 'gm.id, gm.groupid, gm.userid')) {
5746 foreach($postersgroups as $pg) {
5747 if (!isset($cm->cache->usersgroups[$pg->userid])) {
5748 $cm->cache->usersgroups[$pg->userid] = array();
5750 $cm->cache->usersgroups[$pg->userid][$pg->groupid] = $pg->groupid;
5752 unset($postersgroups);
5755 //load ratings
5756 if ($forum->assessed != RATING_AGGREGATE_NONE) {
5757 $ratingoptions = new stdClass;
5758 $ratingoptions->context = $modcontext;
5759 $ratingoptions->component = 'mod_forum';
5760 $ratingoptions->ratingarea = 'post';
5761 $ratingoptions->items = $posts;
5762 $ratingoptions->aggregate = $forum->assessed;//the aggregation method
5763 $ratingoptions->scaleid = $forum->scale;
5764 $ratingoptions->userid = $USER->id;
5765 if ($forum->type == 'single' or !$discussion->id) {
5766 $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/view.php?id=$cm->id";
5767 } else {
5768 $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id";
5770 $ratingoptions->assesstimestart = $forum->assesstimestart;
5771 $ratingoptions->assesstimefinish = $forum->assesstimefinish;
5773 $rm = new rating_manager();
5774 $posts = $rm->get_ratings($ratingoptions);
5778 $post->forum = $forum->id; // Add the forum id to the post object, later used by forum_print_post
5779 $post->forumtype = $forum->type;
5781 $post->subject = format_string($post->subject);
5783 $postread = !empty($post->postread);
5785 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, false,
5786 '', '', $postread, true, $forumtracked);
5788 switch ($mode) {
5789 case FORUM_MODE_FLATOLDEST :
5790 case FORUM_MODE_FLATNEWEST :
5791 default:
5792 forum_print_posts_flat($course, $cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts);
5793 break;
5795 case FORUM_MODE_THREADED :
5796 forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, 0, $reply, $forumtracked, $posts);
5797 break;
5799 case FORUM_MODE_NESTED :
5800 forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
5801 break;
5807 * @global object
5808 * @global object
5809 * @uses FORUM_MODE_FLATNEWEST
5810 * @param object $course
5811 * @param object $cm
5812 * @param object $forum
5813 * @param object $discussion
5814 * @param object $post
5815 * @param object $mode
5816 * @param bool $reply
5817 * @param bool $forumtracked
5818 * @param array $posts
5819 * @return void
5821 function forum_print_posts_flat($course, &$cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts) {
5822 global $USER, $CFG;
5824 $link = false;
5826 if ($mode == FORUM_MODE_FLATNEWEST) {
5827 $sort = "ORDER BY created DESC";
5828 } else {
5829 $sort = "ORDER BY created ASC";
5832 foreach ($posts as $post) {
5833 if (!$post->parent) {
5834 continue;
5836 $post->subject = format_string($post->subject);
5837 $ownpost = ($USER->id == $post->userid);
5839 $postread = !empty($post->postread);
5841 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5842 '', '', $postread, true, $forumtracked);
5847 * @todo Document this function
5849 * @global object
5850 * @global object
5851 * @uses CONTEXT_MODULE
5852 * @return void
5854 function forum_print_posts_threaded($course, &$cm, $forum, $discussion, $parent, $depth, $reply, $forumtracked, $posts) {
5855 global $USER, $CFG;
5857 $link = false;
5859 if (!empty($posts[$parent->id]->children)) {
5860 $posts = $posts[$parent->id]->children;
5862 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5863 $canviewfullnames = has_capability('moodle/site:viewfullnames', $modcontext);
5865 foreach ($posts as $post) {
5867 echo '<div class="indent">';
5868 if ($depth > 0) {
5869 $ownpost = ($USER->id == $post->userid);
5870 $post->subject = format_string($post->subject);
5872 $postread = !empty($post->postread);
5874 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5875 '', '', $postread, true, $forumtracked);
5876 } else {
5877 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
5878 echo "</div>\n";
5879 continue;
5881 $by = new stdClass();
5882 $by->name = fullname($post, $canviewfullnames);
5883 $by->date = userdate($post->modified);
5885 if ($forumtracked) {
5886 if (!empty($post->postread)) {
5887 $style = '<span class="forumthread read">';
5888 } else {
5889 $style = '<span class="forumthread unread">';
5891 } else {
5892 $style = '<span class="forumthread">';
5894 echo $style."<a name=\"$post->id\"></a>".
5895 "<a href=\"discuss.php?d=$post->discussion&amp;parent=$post->id\">".format_string($post->subject,true)."</a> ";
5896 print_string("bynameondate", "forum", $by);
5897 echo "</span>";
5900 forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, $depth-1, $reply, $forumtracked, $posts);
5901 echo "</div>\n";
5907 * @todo Document this function
5908 * @global object
5909 * @global object
5910 * @return void
5912 function forum_print_posts_nested($course, &$cm, $forum, $discussion, $parent, $reply, $forumtracked, $posts) {
5913 global $USER, $CFG;
5915 $link = false;
5917 if (!empty($posts[$parent->id]->children)) {
5918 $posts = $posts[$parent->id]->children;
5920 foreach ($posts as $post) {
5922 echo '<div class="indent">';
5923 if (!isloggedin()) {
5924 $ownpost = false;
5925 } else {
5926 $ownpost = ($USER->id == $post->userid);
5929 $post->subject = format_string($post->subject);
5930 $postread = !empty($post->postread);
5932 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5933 '', '', $postread, true, $forumtracked);
5934 forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
5935 echo "</div>\n";
5941 * Returns all forum posts since a given time in specified forum.
5943 * @todo Document this functions args
5944 * @global object
5945 * @global object
5946 * @global object
5947 * @global object
5949 function forum_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
5950 global $CFG, $COURSE, $USER, $DB;
5952 if ($COURSE->id == $courseid) {
5953 $course = $COURSE;
5954 } else {
5955 $course = $DB->get_record('course', array('id' => $courseid));
5958 $modinfo = get_fast_modinfo($course);
5960 $cm = $modinfo->cms[$cmid];
5961 $params = array($timestart, $cm->instance);
5963 if ($userid) {
5964 $userselect = "AND u.id = ?";
5965 $params[] = $userid;
5966 } else {
5967 $userselect = "";
5970 if ($groupid) {
5971 $groupselect = "AND gm.groupid = ?";
5972 $groupjoin = "JOIN {groups_members} gm ON gm.userid=u.id";
5973 $params[] = $groupid;
5974 } else {
5975 $groupselect = "";
5976 $groupjoin = "";
5979 if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
5980 d.timestart, d.timeend, d.userid AS duserid,
5981 u.firstname, u.lastname, u.email, u.picture, u.imagealt, u.email
5982 FROM {forum_posts} p
5983 JOIN {forum_discussions} d ON d.id = p.discussion
5984 JOIN {forum} f ON f.id = d.forum
5985 JOIN {user} u ON u.id = p.userid
5986 $groupjoin
5987 WHERE p.created > ? AND f.id = ?
5988 $userselect $groupselect
5989 ORDER BY p.id ASC", $params)) { // order by initial posting date
5990 return;
5993 $groupmode = groups_get_activity_groupmode($cm, $course);
5994 $cm_context = get_context_instance(CONTEXT_MODULE, $cm->id);
5995 $viewhiddentimed = has_capability('mod/forum:viewhiddentimedposts', $cm_context);
5996 $accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context);
5998 if (is_null($modinfo->groups)) {
5999 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
6002 $printposts = array();
6003 foreach ($posts as $post) {
6005 if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
6006 and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
6007 if (!$viewhiddentimed) {
6008 continue;
6012 if ($groupmode) {
6013 if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or $accessallgroups) {
6014 // oki (Open discussions have groupid -1)
6015 } else {
6016 // separate mode
6017 if (isguestuser()) {
6018 // shortcut
6019 continue;
6022 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
6023 continue;
6028 $printposts[] = $post;
6031 if (!$printposts) {
6032 return;
6035 $aname = format_string($cm->name,true);
6037 foreach ($printposts as $post) {
6038 $tmpactivity = new stdClass();
6040 $tmpactivity->type = 'forum';
6041 $tmpactivity->cmid = $cm->id;
6042 $tmpactivity->name = $aname;
6043 $tmpactivity->sectionnum = $cm->sectionnum;
6044 $tmpactivity->timestamp = $post->modified;
6046 $tmpactivity->content = new stdClass();
6047 $tmpactivity->content->id = $post->id;
6048 $tmpactivity->content->discussion = $post->discussion;
6049 $tmpactivity->content->subject = format_string($post->subject);
6050 $tmpactivity->content->parent = $post->parent;
6052 $tmpactivity->user = new stdClass();
6053 $tmpactivity->user->id = $post->userid;
6054 $tmpactivity->user->firstname = $post->firstname;
6055 $tmpactivity->user->lastname = $post->lastname;
6056 $tmpactivity->user->picture = $post->picture;
6057 $tmpactivity->user->imagealt = $post->imagealt;
6058 $tmpactivity->user->email = $post->email;
6060 $activities[$index++] = $tmpactivity;
6063 return;
6067 * @todo Document this function
6068 * @global object
6070 function forum_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
6071 global $CFG, $OUTPUT;
6073 if ($activity->content->parent) {
6074 $class = 'reply';
6075 } else {
6076 $class = 'discussion';
6079 echo '<table border="0" cellpadding="3" cellspacing="0" class="forum-recent">';
6081 echo "<tr><td class=\"userpicture\" valign=\"top\">";
6082 echo $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid));
6083 echo "</td><td class=\"$class\">";
6085 echo '<div class="title">';
6086 if ($detail) {
6087 $aname = s($activity->name);
6088 echo "<img src=\"" . $OUTPUT->pix_url('icon', $activity->type) . "\" ".
6089 "class=\"icon\" alt=\"{$aname}\" />";
6091 echo "<a href=\"$CFG->wwwroot/mod/forum/discuss.php?d={$activity->content->discussion}"
6092 ."#p{$activity->content->id}\">{$activity->content->subject}</a>";
6093 echo '</div>';
6095 echo '<div class="user">';
6096 $fullname = fullname($activity->user, $viewfullnames);
6097 echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&amp;course=$courseid\">"
6098 ."{$fullname}</a> - ".userdate($activity->timestamp);
6099 echo '</div>';
6100 echo "</td></tr></table>";
6102 return;
6106 * recursively sets the discussion field to $discussionid on $postid and all its children
6107 * used when pruning a post
6109 * @global object
6110 * @param int $postid
6111 * @param int $discussionid
6112 * @return bool
6114 function forum_change_discussionid($postid, $discussionid) {
6115 global $DB;
6116 $DB->set_field('forum_posts', 'discussion', $discussionid, array('id' => $postid));
6117 if ($posts = $DB->get_records('forum_posts', array('parent' => $postid))) {
6118 foreach ($posts as $post) {
6119 forum_change_discussionid($post->id, $discussionid);
6122 return true;
6126 * Prints the editing button on subscribers page
6128 * @global object
6129 * @global object
6130 * @param int $courseid
6131 * @param int $forumid
6132 * @return string
6134 function forum_update_subscriptions_button($courseid, $forumid) {
6135 global $CFG, $USER;
6137 if (!empty($USER->subscriptionsediting)) {
6138 $string = get_string('turneditingoff');
6139 $edit = "off";
6140 } else {
6141 $string = get_string('turneditingon');
6142 $edit = "on";
6145 return "<form method=\"get\" action=\"$CFG->wwwroot/mod/forum/subscribers.php\">".
6146 "<input type=\"hidden\" name=\"id\" value=\"$forumid\" />".
6147 "<input type=\"hidden\" name=\"edit\" value=\"$edit\" />".
6148 "<input type=\"submit\" value=\"$string\" /></form>";
6152 * This function gets run whenever user is enrolled into course
6154 * @deprecated deprecating this function as we will be using forum_user_role_assigned
6155 * @param stdClass $cp
6156 * @return void
6158 function forum_user_enrolled($cp) {
6159 global $DB;
6161 // NOTE: this has to be as fast as possible - we do not want to slow down enrolments!
6162 // Originally there used to be 'mod/forum:initialsubscriptions' which was
6163 // introduced because we did not have enrolment information in earlier versions...
6165 $sql = "SELECT f.id
6166 FROM {forum} f
6167 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = :userid)
6168 WHERE f.course = :courseid AND f.forcesubscribe = :initial AND fs.id IS NULL";
6169 $params = array('courseid'=>$cp->courseid, 'userid'=>$cp->userid, 'initial'=>FORUM_INITIALSUBSCRIBE);
6171 $forums = $DB->get_records_sql($sql, $params);
6172 foreach ($forums as $forum) {
6173 forum_subscribe($cp->userid, $forum->id);
6178 * This function gets run whenever user is assigned role in course
6180 * @param stdClass $cp
6181 * @return void
6183 function forum_user_role_assigned($cp) {
6184 global $DB;
6186 $context = context::instance_by_id($cp->contextid, MUST_EXIST);
6188 // If contextlevel is course then only subscribe user. Role assignment
6189 // at course level means user is enroled in course and can subscribe to forum.
6190 if ($context->contextlevel != CONTEXT_COURSE) {
6191 return;
6194 $sql = "SELECT f.id, cm.id AS cmid
6195 FROM {forum} f
6196 JOIN {course_modules} cm ON (cm.instance = f.id)
6197 JOIN {modules} m ON (m.id = cm.module)
6198 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = :userid)
6199 WHERE f.course = :courseid
6200 AND f.forcesubscribe = :initial
6201 AND m.name = 'forum'
6202 AND fs.id IS NULL";
6203 $params = array('courseid'=>$context->instanceid, 'userid'=>$cp->userid, 'initial'=>FORUM_INITIALSUBSCRIBE);
6205 $forums = $DB->get_records_sql($sql, $params);
6206 foreach ($forums as $forum) {
6207 // If user doesn't have allowforcesubscribe capability then don't subscribe.
6208 if (has_capability('mod/forum:allowforcesubscribe', context_module::instance($forum->cmid), $cp->userid)) {
6209 forum_subscribe($cp->userid, $forum->id);
6215 * This function gets run whenever user is unenrolled from course
6217 * @param stdClass $cp
6218 * @return void
6220 function forum_user_unenrolled($cp) {
6221 global $DB;
6223 // NOTE: this has to be as fast as possible!
6225 if ($cp->lastenrol) {
6226 $params = array('userid'=>$cp->userid, 'courseid'=>$cp->courseid);
6227 $forumselect = "IN (SELECT f.id FROM {forum} f WHERE f.course = :courseid)";
6229 $DB->delete_records_select('forum_subscriptions', "userid = :userid AND forum $forumselect", $params);
6230 $DB->delete_records_select('forum_track_prefs', "userid = :userid AND forumid $forumselect", $params);
6231 $DB->delete_records_select('forum_read', "userid = :userid AND forumid $forumselect", $params);
6235 // Functions to do with read tracking.
6238 * Mark posts as read.
6240 * @global object
6241 * @global object
6242 * @param object $user object
6243 * @param array $postids array of post ids
6244 * @return boolean success
6246 function forum_tp_mark_posts_read($user, $postids) {
6247 global $CFG, $DB;
6249 if (!forum_tp_can_track_forums(false, $user)) {
6250 return true;
6253 $status = true;
6255 $now = time();
6256 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
6258 if (empty($postids)) {
6259 return true;
6261 } else if (count($postids) > 200) {
6262 while ($part = array_splice($postids, 0, 200)) {
6263 $status = forum_tp_mark_posts_read($user, $part) && $status;
6265 return $status;
6268 list($usql, $params) = $DB->get_in_or_equal($postids);
6269 $params[] = $user->id;
6271 $sql = "SELECT id
6272 FROM {forum_read}
6273 WHERE postid $usql AND userid = ?";
6274 if ($existing = $DB->get_records_sql($sql, $params)) {
6275 $existing = array_keys($existing);
6276 } else {
6277 $existing = array();
6280 $new = array_diff($postids, $existing);
6282 if ($new) {
6283 list($usql, $new_params) = $DB->get_in_or_equal($new);
6284 $params = array($user->id, $now, $now, $user->id);
6285 $params = array_merge($params, $new_params);
6286 $params[] = $cutoffdate;
6288 $sql = "INSERT INTO {forum_read} (userid, postid, discussionid, forumid, firstread, lastread)
6290 SELECT ?, p.id, p.discussion, d.forum, ?, ?
6291 FROM {forum_posts} p
6292 JOIN {forum_discussions} d ON d.id = p.discussion
6293 JOIN {forum} f ON f.id = d.forum
6294 LEFT JOIN {forum_track_prefs} tf ON (tf.userid = ? AND tf.forumid = f.id)
6295 WHERE p.id $usql
6296 AND p.modified >= ?
6297 AND (f.trackingtype = ".FORUM_TRACKING_ON."
6298 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL))";
6299 $status = $DB->execute($sql, $params) && $status;
6302 if ($existing) {
6303 list($usql, $new_params) = $DB->get_in_or_equal($existing);
6304 $params = array($now, $user->id);
6305 $params = array_merge($params, $new_params);
6307 $sql = "UPDATE {forum_read}
6308 SET lastread = ?
6309 WHERE userid = ? AND postid $usql";
6310 $status = $DB->execute($sql, $params) && $status;
6313 return $status;
6317 * Mark post as read.
6318 * @global object
6319 * @global object
6320 * @param int $userid
6321 * @param int $postid
6323 function forum_tp_add_read_record($userid, $postid) {
6324 global $CFG, $DB;
6326 $now = time();
6327 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
6329 if (!$DB->record_exists('forum_read', array('userid' => $userid, 'postid' => $postid))) {
6330 $sql = "INSERT INTO {forum_read} (userid, postid, discussionid, forumid, firstread, lastread)
6332 SELECT ?, p.id, p.discussion, d.forum, ?, ?
6333 FROM {forum_posts} p
6334 JOIN {forum_discussions} d ON d.id = p.discussion
6335 WHERE p.id = ? AND p.modified >= ?";
6336 return $DB->execute($sql, array($userid, $now, $now, $postid, $cutoffdate));
6338 } else {
6339 $sql = "UPDATE {forum_read}
6340 SET lastread = ?
6341 WHERE userid = ? AND postid = ?";
6342 return $DB->execute($sql, array($now, $userid, $userid));
6347 * Returns all records in the 'forum_read' table matching the passed keys, indexed
6348 * by userid.
6350 * @global object
6351 * @param int $userid
6352 * @param int $postid
6353 * @param int $discussionid
6354 * @param int $forumid
6355 * @return array
6357 function forum_tp_get_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
6358 global $DB;
6359 $select = '';
6360 $params = array();
6362 if ($userid > -1) {
6363 if ($select != '') $select .= ' AND ';
6364 $select .= 'userid = ?';
6365 $params[] = $userid;
6367 if ($postid > -1) {
6368 if ($select != '') $select .= ' AND ';
6369 $select .= 'postid = ?';
6370 $params[] = $postid;
6372 if ($discussionid > -1) {
6373 if ($select != '') $select .= ' AND ';
6374 $select .= 'discussionid = ?';
6375 $params[] = $discussionid;
6377 if ($forumid > -1) {
6378 if ($select != '') $select .= ' AND ';
6379 $select .= 'forumid = ?';
6380 $params[] = $forumid;
6383 return $DB->get_records_select('forum_read', $select, $params);
6387 * Returns all read records for the provided user and discussion, indexed by postid.
6389 * @global object
6390 * @param inti $userid
6391 * @param int $discussionid
6393 function forum_tp_get_discussion_read_records($userid, $discussionid) {
6394 global $DB;
6395 $select = 'userid = ? AND discussionid = ?';
6396 $fields = 'postid, firstread, lastread';
6397 return $DB->get_records_select('forum_read', $select, array($userid, $discussionid), '', $fields);
6401 * If its an old post, do nothing. If the record exists, the maintenance will clear it up later.
6403 * @return bool
6405 function forum_tp_mark_post_read($userid, $post, $forumid) {
6406 if (!forum_tp_is_post_old($post)) {
6407 return forum_tp_add_read_record($userid, $post->id);
6408 } else {
6409 return true;
6414 * Marks a whole forum as read, for a given user
6416 * @global object
6417 * @global object
6418 * @param object $user
6419 * @param int $forumid
6420 * @param int|bool $groupid
6421 * @return bool
6423 function forum_tp_mark_forum_read($user, $forumid, $groupid=false) {
6424 global $CFG, $DB;
6426 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6428 $groupsel = "";
6429 $params = array($user->id, $forumid, $cutoffdate);
6431 if ($groupid !== false) {
6432 $groupsel = " AND (d.groupid = ? OR d.groupid = -1)";
6433 $params[] = $groupid;
6436 $sql = "SELECT p.id
6437 FROM {forum_posts} p
6438 LEFT JOIN {forum_discussions} d ON d.id = p.discussion
6439 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6440 WHERE d.forum = ?
6441 AND p.modified >= ? AND r.id is NULL
6442 $groupsel";
6444 if ($posts = $DB->get_records_sql($sql, $params)) {
6445 $postids = array_keys($posts);
6446 return forum_tp_mark_posts_read($user, $postids);
6449 return true;
6453 * Marks a whole discussion as read, for a given user
6455 * @global object
6456 * @global object
6457 * @param object $user
6458 * @param int $discussionid
6459 * @return bool
6461 function forum_tp_mark_discussion_read($user, $discussionid) {
6462 global $CFG, $DB;
6464 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6466 $sql = "SELECT p.id
6467 FROM {forum_posts} p
6468 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6469 WHERE p.discussion = ?
6470 AND p.modified >= ? AND r.id is NULL";
6472 if ($posts = $DB->get_records_sql($sql, array($user->id, $discussionid, $cutoffdate))) {
6473 $postids = array_keys($posts);
6474 return forum_tp_mark_posts_read($user, $postids);
6477 return true;
6481 * @global object
6482 * @param int $userid
6483 * @param object $post
6485 function forum_tp_is_post_read($userid, $post) {
6486 global $DB;
6487 return (forum_tp_is_post_old($post) ||
6488 $DB->record_exists('forum_read', array('userid' => $userid, 'postid' => $post->id)));
6492 * @global object
6493 * @param object $post
6494 * @param int $time Defautls to time()
6496 function forum_tp_is_post_old($post, $time=null) {
6497 global $CFG;
6499 if (is_null($time)) {
6500 $time = time();
6502 return ($post->modified < ($time - ($CFG->forum_oldpostdays * 24 * 3600)));
6506 * Returns the count of records for the provided user and discussion.
6508 * @global object
6509 * @global object
6510 * @param int $userid
6511 * @param int $discussionid
6512 * @return bool
6514 function forum_tp_count_discussion_read_records($userid, $discussionid) {
6515 global $CFG, $DB;
6517 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
6519 $sql = 'SELECT COUNT(DISTINCT p.id) '.
6520 'FROM {forum_discussions} d '.
6521 'LEFT JOIN {forum_read} r ON d.id = r.discussionid AND r.userid = ? '.
6522 'LEFT JOIN {forum_posts} p ON p.discussion = d.id '.
6523 'AND (p.modified < ? OR p.id = r.postid) '.
6524 'WHERE d.id = ? ';
6526 return ($DB->count_records_sql($sql, array($userid, $cutoffdate, $discussionid)));
6530 * Returns the count of records for the provided user and discussion.
6532 * @global object
6533 * @global object
6534 * @param int $userid
6535 * @param int $discussionid
6536 * @return int
6538 function forum_tp_count_discussion_unread_posts($userid, $discussionid) {
6539 global $CFG, $DB;
6541 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
6543 $sql = 'SELECT COUNT(p.id) '.
6544 'FROM {forum_posts} p '.
6545 'LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? '.
6546 'WHERE p.discussion = ? '.
6547 'AND p.modified >= ? AND r.id is NULL';
6549 return $DB->count_records_sql($sql, array($userid, $discussionid, $cutoffdate));
6553 * Returns the count of posts for the provided forum and [optionally] group.
6554 * @global object
6555 * @global object
6556 * @param int $forumid
6557 * @param int|bool $groupid
6558 * @return int
6560 function forum_tp_count_forum_posts($forumid, $groupid=false) {
6561 global $CFG, $DB;
6562 $params = array($forumid);
6563 $sql = 'SELECT COUNT(*) '.
6564 'FROM {forum_posts} fp,{forum_discussions} fd '.
6565 'WHERE fd.forum = ? AND fp.discussion = fd.id';
6566 if ($groupid !== false) {
6567 $sql .= ' AND (fd.groupid = ? OR fd.groupid = -1)';
6568 $params[] = $groupid;
6570 $count = $DB->count_records_sql($sql, $params);
6573 return $count;
6577 * Returns the count of records for the provided user and forum and [optionally] group.
6578 * @global object
6579 * @global object
6580 * @param int $userid
6581 * @param int $forumid
6582 * @param int|bool $groupid
6583 * @return int
6585 function forum_tp_count_forum_read_records($userid, $forumid, $groupid=false) {
6586 global $CFG, $DB;
6588 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6590 $groupsel = '';
6591 $params = array($userid, $forumid, $cutoffdate);
6592 if ($groupid !== false) {
6593 $groupsel = "AND (d.groupid = ? OR d.groupid = -1)";
6594 $params[] = $groupid;
6597 $sql = "SELECT COUNT(p.id)
6598 FROM {forum_posts} p
6599 JOIN {forum_discussions} d ON d.id = p.discussion
6600 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid= ?)
6601 WHERE d.forum = ?
6602 AND (p.modified < $cutoffdate OR (p.modified >= ? AND r.id IS NOT NULL))
6603 $groupsel";
6605 return $DB->get_field_sql($sql, $params);
6609 * Returns the count of records for the provided user and course.
6610 * Please note that group access is ignored!
6612 * @global object
6613 * @global object
6614 * @param int $userid
6615 * @param int $courseid
6616 * @return array
6618 function forum_tp_get_course_unread_posts($userid, $courseid) {
6619 global $CFG, $DB;
6621 $now = round(time(), -2); // db cache friendliness
6622 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
6623 $params = array($userid, $userid, $courseid, $cutoffdate);
6625 if (!empty($CFG->forum_enabletimedposts)) {
6626 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
6627 $params[] = $now;
6628 $params[] = $now;
6629 } else {
6630 $timedsql = "";
6633 $sql = "SELECT f.id, COUNT(p.id) AS unread
6634 FROM {forum_posts} p
6635 JOIN {forum_discussions} d ON d.id = p.discussion
6636 JOIN {forum} f ON f.id = d.forum
6637 JOIN {course} c ON c.id = f.course
6638 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6639 LEFT JOIN {forum_track_prefs} tf ON (tf.userid = ? AND tf.forumid = f.id)
6640 WHERE f.course = ?
6641 AND p.modified >= ? AND r.id is NULL
6642 AND (f.trackingtype = ".FORUM_TRACKING_ON."
6643 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL))
6644 $timedsql
6645 GROUP BY f.id";
6647 if ($return = $DB->get_records_sql($sql, $params)) {
6648 return $return;
6651 return array();
6655 * Returns the count of records for the provided user and forum and [optionally] group.
6657 * @global object
6658 * @global object
6659 * @global object
6660 * @param object $cm
6661 * @param object $course
6662 * @return int
6664 function forum_tp_count_forum_unread_posts($cm, $course) {
6665 global $CFG, $USER, $DB;
6667 static $readcache = array();
6669 $forumid = $cm->instance;
6671 if (!isset($readcache[$course->id])) {
6672 $readcache[$course->id] = array();
6673 if ($counts = forum_tp_get_course_unread_posts($USER->id, $course->id)) {
6674 foreach ($counts as $count) {
6675 $readcache[$course->id][$count->id] = $count->unread;
6680 if (empty($readcache[$course->id][$forumid])) {
6681 // no need to check group mode ;-)
6682 return 0;
6685 $groupmode = groups_get_activity_groupmode($cm, $course);
6687 if ($groupmode != SEPARATEGROUPS) {
6688 return $readcache[$course->id][$forumid];
6691 if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
6692 return $readcache[$course->id][$forumid];
6695 require_once($CFG->dirroot.'/course/lib.php');
6697 $modinfo = get_fast_modinfo($course);
6698 if (is_null($modinfo->groups)) {
6699 $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
6702 $mygroups = $modinfo->groups[$cm->groupingid];
6704 // add all groups posts
6705 if (empty($mygroups)) {
6706 $mygroups = array(-1=>-1);
6707 } else {
6708 $mygroups[-1] = -1;
6711 list ($groups_sql, $groups_params) = $DB->get_in_or_equal($mygroups);
6713 $now = round(time(), -2); // db cache friendliness
6714 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
6715 $params = array($USER->id, $forumid, $cutoffdate);
6717 if (!empty($CFG->forum_enabletimedposts)) {
6718 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
6719 $params[] = $now;
6720 $params[] = $now;
6721 } else {
6722 $timedsql = "";
6725 $params = array_merge($params, $groups_params);
6727 $sql = "SELECT COUNT(p.id)
6728 FROM {forum_posts} p
6729 JOIN {forum_discussions} d ON p.discussion = d.id
6730 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6731 WHERE d.forum = ?
6732 AND p.modified >= ? AND r.id is NULL
6733 $timedsql
6734 AND d.groupid $groups_sql";
6736 return $DB->get_field_sql($sql, $params);
6740 * Deletes read records for the specified index. At least one parameter must be specified.
6742 * @global object
6743 * @param int $userid
6744 * @param int $postid
6745 * @param int $discussionid
6746 * @param int $forumid
6747 * @return bool
6749 function forum_tp_delete_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
6750 global $DB;
6751 $params = array();
6753 $select = '';
6754 if ($userid > -1) {
6755 if ($select != '') $select .= ' AND ';
6756 $select .= 'userid = ?';
6757 $params[] = $userid;
6759 if ($postid > -1) {
6760 if ($select != '') $select .= ' AND ';
6761 $select .= 'postid = ?';
6762 $params[] = $postid;
6764 if ($discussionid > -1) {
6765 if ($select != '') $select .= ' AND ';
6766 $select .= 'discussionid = ?';
6767 $params[] = $discussionid;
6769 if ($forumid > -1) {
6770 if ($select != '') $select .= ' AND ';
6771 $select .= 'forumid = ?';
6772 $params[] = $forumid;
6774 if ($select == '') {
6775 return false;
6777 else {
6778 return $DB->delete_records_select('forum_read', $select, $params);
6782 * Get a list of forums not tracked by the user.
6784 * @global object
6785 * @global object
6786 * @param int $userid The id of the user to use.
6787 * @param int $courseid The id of the course being checked.
6788 * @return mixed An array indexed by forum id, or false.
6790 function forum_tp_get_untracked_forums($userid, $courseid) {
6791 global $CFG, $DB;
6793 $sql = "SELECT f.id
6794 FROM {forum} f
6795 LEFT JOIN {forum_track_prefs} ft ON (ft.forumid = f.id AND ft.userid = ?)
6796 WHERE f.course = ?
6797 AND (f.trackingtype = ".FORUM_TRACKING_OFF."
6798 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND ft.id IS NOT NULL))";
6800 if ($forums = $DB->get_records_sql($sql, array($userid, $courseid))) {
6801 foreach ($forums as $forum) {
6802 $forums[$forum->id] = $forum;
6804 return $forums;
6806 } else {
6807 return array();
6812 * Determine if a user can track forums and optionally a particular forum.
6813 * Checks the site settings, the user settings and the forum settings (if
6814 * requested).
6816 * @global object
6817 * @global object
6818 * @global object
6819 * @param mixed $forum The forum object to test, or the int id (optional).
6820 * @param mixed $userid The user object to check for (optional).
6821 * @return boolean
6823 function forum_tp_can_track_forums($forum=false, $user=false) {
6824 global $USER, $CFG, $DB;
6826 // if possible, avoid expensive
6827 // queries
6828 if (empty($CFG->forum_trackreadposts)) {
6829 return false;
6832 if ($user === false) {
6833 $user = $USER;
6836 if (isguestuser($user) or empty($user->id)) {
6837 return false;
6840 if ($forum === false) {
6841 // general abitily to track forums
6842 return (bool)$user->trackforums;
6846 // Work toward always passing an object...
6847 if (is_numeric($forum)) {
6848 debugging('Better use proper forum object.', DEBUG_DEVELOPER);
6849 $forum = $DB->get_record('forum', array('id' => $forum), '', 'id,trackingtype');
6852 $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
6853 $forumforced = ($forum->trackingtype == FORUM_TRACKING_ON);
6855 return ($forumforced || $forumallows) && !empty($user->trackforums);
6859 * Tells whether a specific forum is tracked by the user. A user can optionally
6860 * be specified. If not specified, the current user is assumed.
6862 * @global object
6863 * @global object
6864 * @global object
6865 * @param mixed $forum If int, the id of the forum being checked; if object, the forum object
6866 * @param int $userid The id of the user being checked (optional).
6867 * @return boolean
6869 function forum_tp_is_tracked($forum, $user=false) {
6870 global $USER, $CFG, $DB;
6872 if ($user === false) {
6873 $user = $USER;
6876 if (isguestuser($user) or empty($user->id)) {
6877 return false;
6880 // Work toward always passing an object...
6881 if (is_numeric($forum)) {
6882 debugging('Better use proper forum object.', DEBUG_DEVELOPER);
6883 $forum = $DB->get_record('forum', array('id' => $forum));
6886 if (!forum_tp_can_track_forums($forum, $user)) {
6887 return false;
6890 $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
6891 $forumforced = ($forum->trackingtype == FORUM_TRACKING_ON);
6893 return $forumforced ||
6894 ($forumallows && $DB->get_record('forum_track_prefs', array('userid' => $user->id, 'forumid' => $forum->id)) === false);
6898 * @global object
6899 * @global object
6900 * @param int $forumid
6901 * @param int $userid
6903 function forum_tp_start_tracking($forumid, $userid=false) {
6904 global $USER, $DB;
6906 if ($userid === false) {
6907 $userid = $USER->id;
6910 return $DB->delete_records('forum_track_prefs', array('userid' => $userid, 'forumid' => $forumid));
6914 * @global object
6915 * @global object
6916 * @param int $forumid
6917 * @param int $userid
6919 function forum_tp_stop_tracking($forumid, $userid=false) {
6920 global $USER, $DB;
6922 if ($userid === false) {
6923 $userid = $USER->id;
6926 if (!$DB->record_exists('forum_track_prefs', array('userid' => $userid, 'forumid' => $forumid))) {
6927 $track_prefs = new stdClass();
6928 $track_prefs->userid = $userid;
6929 $track_prefs->forumid = $forumid;
6930 $DB->insert_record('forum_track_prefs', $track_prefs);
6933 return forum_tp_delete_read_records($userid, -1, -1, $forumid);
6938 * Clean old records from the forum_read table.
6939 * @global object
6940 * @global object
6941 * @return void
6943 function forum_tp_clean_read_records() {
6944 global $CFG, $DB;
6946 if (!isset($CFG->forum_oldpostdays)) {
6947 return;
6949 // Look for records older than the cutoffdate that are still in the forum_read table.
6950 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6952 //first get the oldest tracking present - we need tis to speedup the next delete query
6953 $sql = "SELECT MIN(fp.modified) AS first
6954 FROM {forum_posts} fp
6955 JOIN {forum_read} fr ON fr.postid=fp.id";
6956 if (!$first = $DB->get_field_sql($sql)) {
6957 // nothing to delete;
6958 return;
6961 // now delete old tracking info
6962 $sql = "DELETE
6963 FROM {forum_read}
6964 WHERE postid IN (SELECT fp.id
6965 FROM {forum_posts} fp
6966 WHERE fp.modified >= ? AND fp.modified < ?)";
6967 $DB->execute($sql, array($first, $cutoffdate));
6971 * Sets the last post for a given discussion
6973 * @global object
6974 * @global object
6975 * @param into $discussionid
6976 * @return bool|int
6978 function forum_discussion_update_last_post($discussionid) {
6979 global $CFG, $DB;
6981 // Check the given discussion exists
6982 if (!$DB->record_exists('forum_discussions', array('id' => $discussionid))) {
6983 return false;
6986 // Use SQL to find the last post for this discussion
6987 $sql = "SELECT id, userid, modified
6988 FROM {forum_posts}
6989 WHERE discussion=?
6990 ORDER BY modified DESC";
6992 // Lets go find the last post
6993 if (($lastposts = $DB->get_records_sql($sql, array($discussionid), 0, 1))) {
6994 $lastpost = reset($lastposts);
6995 $discussionobject = new stdClass();
6996 $discussionobject->id = $discussionid;
6997 $discussionobject->usermodified = $lastpost->userid;
6998 $discussionobject->timemodified = $lastpost->modified;
6999 $DB->update_record('forum_discussions', $discussionobject);
7000 return $lastpost->id;
7003 // To get here either we couldn't find a post for the discussion (weird)
7004 // or we couldn't update the discussion record (weird x2)
7005 return false;
7010 * @return array
7012 function forum_get_view_actions() {
7013 return array('view discussion', 'search', 'forum', 'forums', 'subscribers', 'view forum');
7017 * @return array
7019 function forum_get_post_actions() {
7020 return array('add discussion','add post','delete discussion','delete post','move discussion','prune post','update post');
7024 * this function returns all the separate forum ids, given a courseid
7026 * @global object
7027 * @global object
7028 * @param int $courseid
7029 * @return array
7031 function forum_get_separate_modules($courseid) {
7033 global $CFG,$DB;
7034 $forummodule = $DB->get_record("modules", array("name" => "forum"));
7036 $sql = 'SELECT f.id, f.id FROM {forum} f, {course_modules} cm WHERE
7037 f.id = cm.instance AND cm.module =? AND cm.visible = 1 AND cm.course = ?
7038 AND cm.groupmode ='.SEPARATEGROUPS;
7040 return $DB->get_records_sql($sql, array($forummodule->id, $courseid));
7045 * @global object
7046 * @global object
7047 * @global object
7048 * @param object $forum
7049 * @param object $cm
7050 * @return bool
7052 function forum_check_throttling($forum, $cm=null) {
7053 global $USER, $CFG, $DB, $OUTPUT;
7055 if (is_numeric($forum)) {
7056 $forum = $DB->get_record('forum',array('id'=>$forum));
7058 if (!is_object($forum)) {
7059 return false; // this is broken.
7062 if (empty($forum->blockafter)) {
7063 return true;
7066 if (empty($forum->blockperiod)) {
7067 return true;
7070 if (!$cm) {
7071 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
7072 print_error('invalidcoursemodule');
7076 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
7077 if(has_capability('mod/forum:postwithoutthrottling', $modcontext)) {
7078 return true;
7081 // get the number of posts in the last period we care about
7082 $timenow = time();
7083 $timeafter = $timenow - $forum->blockperiod;
7085 $numposts = $DB->count_records_sql('SELECT COUNT(p.id) FROM {forum_posts} p'
7086 .' JOIN {forum_discussions} d'
7087 .' ON p.discussion = d.id WHERE d.forum = ?'
7088 .' AND p.userid = ? AND p.created > ?', array($forum->id, $USER->id, $timeafter));
7090 $a = new stdClass();
7091 $a->blockafter = $forum->blockafter;
7092 $a->numposts = $numposts;
7093 $a->blockperiod = get_string('secondstotime'.$forum->blockperiod);
7095 if ($forum->blockafter <= $numposts) {
7096 print_error('forumblockingtoomanyposts', 'error', $CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id, $a);
7098 if ($forum->warnafter <= $numposts) {
7099 echo $OUTPUT->notification(get_string('forumblockingalmosttoomanyposts','forum',$a));
7107 * Removes all grades from gradebook
7109 * @global object
7110 * @global object
7111 * @param int $courseid
7112 * @param string $type optional
7114 function forum_reset_gradebook($courseid, $type='') {
7115 global $CFG, $DB;
7117 $wheresql = '';
7118 $params = array($courseid);
7119 if ($type) {
7120 $wheresql = "AND f.type=?";
7121 $params[] = $type;
7124 $sql = "SELECT f.*, cm.idnumber as cmidnumber, f.course as courseid
7125 FROM {forum} f, {course_modules} cm, {modules} m
7126 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id AND f.course=? $wheresql";
7128 if ($forums = $DB->get_records_sql($sql, $params)) {
7129 foreach ($forums as $forum) {
7130 forum_grade_item_update($forum, 'reset');
7136 * This function is used by the reset_course_userdata function in moodlelib.
7137 * This function will remove all posts from the specified forum
7138 * and clean up any related data.
7140 * @global object
7141 * @global object
7142 * @param $data the data submitted from the reset course.
7143 * @return array status array
7145 function forum_reset_userdata($data) {
7146 global $CFG, $DB;
7147 require_once($CFG->dirroot.'/rating/lib.php');
7149 $componentstr = get_string('modulenameplural', 'forum');
7150 $status = array();
7152 $params = array($data->courseid);
7154 $removeposts = false;
7155 $typesql = "";
7156 if (!empty($data->reset_forum_all)) {
7157 $removeposts = true;
7158 $typesstr = get_string('resetforumsall', 'forum');
7159 $types = array();
7160 } else if (!empty($data->reset_forum_types)){
7161 $removeposts = true;
7162 $typesql = "";
7163 $types = array();
7164 $forum_types_all = forum_get_forum_types_all();
7165 foreach ($data->reset_forum_types as $type) {
7166 if (!array_key_exists($type, $forum_types_all)) {
7167 continue;
7169 $typesql .= " AND f.type=?";
7170 $types[] = $forum_types_all[$type];
7171 $params[] = $type;
7173 $typesstr = get_string('resetforums', 'forum').': '.implode(', ', $types);
7175 $alldiscussionssql = "SELECT fd.id
7176 FROM {forum_discussions} fd, {forum} f
7177 WHERE f.course=? AND f.id=fd.forum";
7179 $allforumssql = "SELECT f.id
7180 FROM {forum} f
7181 WHERE f.course=?";
7183 $allpostssql = "SELECT fp.id
7184 FROM {forum_posts} fp, {forum_discussions} fd, {forum} f
7185 WHERE f.course=? AND f.id=fd.forum AND fd.id=fp.discussion";
7187 $forumssql = $forums = $rm = null;
7189 if( $removeposts || !empty($data->reset_forum_ratings) ) {
7190 $forumssql = "$allforumssql $typesql";
7191 $forums = $forums = $DB->get_records_sql($forumssql, $params);
7192 $rm = new rating_manager();;
7193 $ratingdeloptions = new stdClass;
7194 $ratingdeloptions->component = 'mod_forum';
7195 $ratingdeloptions->ratingarea = 'post';
7198 if ($removeposts) {
7199 $discussionssql = "$alldiscussionssql $typesql";
7200 $postssql = "$allpostssql $typesql";
7202 // now get rid of all attachments
7203 $fs = get_file_storage();
7204 if ($forums) {
7205 foreach ($forums as $forumid=>$unused) {
7206 if (!$cm = get_coursemodule_from_instance('forum', $forumid)) {
7207 continue;
7209 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
7210 $fs->delete_area_files($context->id, 'mod_forum', 'attachment');
7211 $fs->delete_area_files($context->id, 'mod_forum', 'post');
7213 //remove ratings
7214 $ratingdeloptions->contextid = $context->id;
7215 $rm->delete_ratings($ratingdeloptions);
7219 // first delete all read flags
7220 $DB->delete_records_select('forum_read', "forumid IN ($forumssql)", $params);
7222 // remove tracking prefs
7223 $DB->delete_records_select('forum_track_prefs', "forumid IN ($forumssql)", $params);
7225 // remove posts from queue
7226 $DB->delete_records_select('forum_queue', "discussionid IN ($discussionssql)", $params);
7228 // all posts - initial posts must be kept in single simple discussion forums
7229 $DB->delete_records_select('forum_posts', "discussion IN ($discussionssql) AND parent <> 0", $params); // first all children
7230 $DB->delete_records_select('forum_posts', "discussion IN ($discussionssql AND f.type <> 'single') AND parent = 0", $params); // now the initial posts for non single simple
7232 // finally all discussions except single simple forums
7233 $DB->delete_records_select('forum_discussions', "forum IN ($forumssql AND f.type <> 'single')", $params);
7235 // remove all grades from gradebook
7236 if (empty($data->reset_gradebook_grades)) {
7237 if (empty($types)) {
7238 forum_reset_gradebook($data->courseid);
7239 } else {
7240 foreach ($types as $type) {
7241 forum_reset_gradebook($data->courseid, $type);
7246 $status[] = array('component'=>$componentstr, 'item'=>$typesstr, 'error'=>false);
7249 // remove all ratings in this course's forums
7250 if (!empty($data->reset_forum_ratings)) {
7251 if ($forums) {
7252 foreach ($forums as $forumid=>$unused) {
7253 if (!$cm = get_coursemodule_from_instance('forum', $forumid)) {
7254 continue;
7256 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
7258 //remove ratings
7259 $ratingdeloptions->contextid = $context->id;
7260 $rm->delete_ratings($ratingdeloptions);
7264 // remove all grades from gradebook
7265 if (empty($data->reset_gradebook_grades)) {
7266 forum_reset_gradebook($data->courseid);
7270 // remove all subscriptions unconditionally - even for users still enrolled in course
7271 if (!empty($data->reset_forum_subscriptions)) {
7272 $DB->delete_records_select('forum_subscriptions', "forum IN ($allforumssql)", $params);
7273 $status[] = array('component'=>$componentstr, 'item'=>get_string('resetsubscriptions','forum'), 'error'=>false);
7276 // remove all tracking prefs unconditionally - even for users still enrolled in course
7277 if (!empty($data->reset_forum_track_prefs)) {
7278 $DB->delete_records_select('forum_track_prefs', "forumid IN ($allforumssql)", $params);
7279 $status[] = array('component'=>$componentstr, 'item'=>get_string('resettrackprefs','forum'), 'error'=>false);
7282 /// updating dates - shift may be negative too
7283 if ($data->timeshift) {
7284 shift_course_mod_dates('forum', array('assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid);
7285 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
7288 return $status;
7292 * Called by course/reset.php
7294 * @param $mform form passed by reference
7296 function forum_reset_course_form_definition(&$mform) {
7297 $mform->addElement('header', 'forumheader', get_string('modulenameplural', 'forum'));
7299 $mform->addElement('checkbox', 'reset_forum_all', get_string('resetforumsall','forum'));
7301 $mform->addElement('select', 'reset_forum_types', get_string('resetforums', 'forum'), forum_get_forum_types_all(), array('multiple' => 'multiple'));
7302 $mform->setAdvanced('reset_forum_types');
7303 $mform->disabledIf('reset_forum_types', 'reset_forum_all', 'checked');
7305 $mform->addElement('checkbox', 'reset_forum_subscriptions', get_string('resetsubscriptions','forum'));
7306 $mform->setAdvanced('reset_forum_subscriptions');
7308 $mform->addElement('checkbox', 'reset_forum_track_prefs', get_string('resettrackprefs','forum'));
7309 $mform->setAdvanced('reset_forum_track_prefs');
7310 $mform->disabledIf('reset_forum_track_prefs', 'reset_forum_all', 'checked');
7312 $mform->addElement('checkbox', 'reset_forum_ratings', get_string('deleteallratings'));
7313 $mform->disabledIf('reset_forum_ratings', 'reset_forum_all', 'checked');
7317 * Course reset form defaults.
7318 * @return array
7320 function forum_reset_course_form_defaults($course) {
7321 return array('reset_forum_all'=>1, 'reset_forum_subscriptions'=>0, 'reset_forum_track_prefs'=>0, 'reset_forum_ratings'=>1);
7325 * Converts a forum to use the Roles System
7327 * @global object
7328 * @global object
7329 * @param object $forum a forum object with the same attributes as a record
7330 * from the forum database table
7331 * @param int $forummodid the id of the forum module, from the modules table
7332 * @param array $teacherroles array of roles that have archetype teacher
7333 * @param array $studentroles array of roles that have archetype student
7334 * @param array $guestroles array of roles that have archetype guest
7335 * @param int $cmid the course_module id for this forum instance
7336 * @return boolean forum was converted or not
7338 function forum_convert_to_roles($forum, $forummodid, $teacherroles=array(),
7339 $studentroles=array(), $guestroles=array(), $cmid=NULL) {
7341 global $CFG, $DB, $OUTPUT;
7343 if (!isset($forum->open) && !isset($forum->assesspublic)) {
7344 // We assume that this forum has already been converted to use the
7345 // Roles System. Columns forum.open and forum.assesspublic get dropped
7346 // once the forum module has been upgraded to use Roles.
7347 return false;
7350 if ($forum->type == 'teacher') {
7352 // Teacher forums should be converted to normal forums that
7353 // use the Roles System to implement the old behavior.
7354 // Note:
7355 // Seems that teacher forums were never backed up in 1.6 since they
7356 // didn't have an entry in the course_modules table.
7357 require_once($CFG->dirroot.'/course/lib.php');
7359 if ($DB->count_records('forum_discussions', array('forum' => $forum->id)) == 0) {
7360 // Delete empty teacher forums.
7361 $DB->delete_records('forum', array('id' => $forum->id));
7362 } else {
7363 // Create a course module for the forum and assign it to
7364 // section 0 in the course.
7365 $mod = new stdClass();
7366 $mod->course = $forum->course;
7367 $mod->module = $forummodid;
7368 $mod->instance = $forum->id;
7369 $mod->section = 0;
7370 $mod->visible = 0; // Hide the forum
7371 $mod->visibleold = 0; // Hide the forum
7372 $mod->groupmode = 0;
7374 if (!$cmid = add_course_module($mod)) {
7375 print_error('cannotcreateinstanceforteacher', 'forum');
7376 } else {
7377 $mod->coursemodule = $cmid;
7378 if (!$sectionid = add_mod_to_section($mod)) {
7379 print_error('cannotaddteacherforumto', 'forum');
7380 } else {
7381 $DB->set_field('course_modules', 'section', $sectionid, array('id' => $cmid));
7385 // Change the forum type to general.
7386 $forum->type = 'general';
7387 $DB->update_record('forum', $forum);
7389 $context = get_context_instance(CONTEXT_MODULE, $cmid);
7391 // Create overrides for default student and guest roles (prevent).
7392 foreach ($studentroles as $studentrole) {
7393 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7394 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $studentrole->id, $context->id);
7395 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7396 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
7397 assign_capability('mod/forum:viewrating', CAP_PREVENT, $studentrole->id, $context->id);
7398 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
7399 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
7400 assign_capability('mod/forum:createattachment', CAP_PREVENT, $studentrole->id, $context->id);
7401 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $studentrole->id, $context->id);
7402 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $studentrole->id, $context->id);
7403 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $studentrole->id, $context->id);
7404 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $studentrole->id, $context->id);
7405 assign_capability('mod/forum:editanypost', CAP_PREVENT, $studentrole->id, $context->id);
7406 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $studentrole->id, $context->id);
7407 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $studentrole->id, $context->id);
7408 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $studentrole->id, $context->id);
7409 assign_capability('mod/forum:postwithoutthrottling', CAP_PREVENT, $studentrole->id, $context->id);
7411 foreach ($guestroles as $guestrole) {
7412 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
7413 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $guestrole->id, $context->id);
7414 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
7415 assign_capability('mod/forum:replypost', CAP_PREVENT, $guestrole->id, $context->id);
7416 assign_capability('mod/forum:viewrating', CAP_PREVENT, $guestrole->id, $context->id);
7417 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $guestrole->id, $context->id);
7418 assign_capability('mod/forum:rate', CAP_PREVENT, $guestrole->id, $context->id);
7419 assign_capability('mod/forum:createattachment', CAP_PREVENT, $guestrole->id, $context->id);
7420 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $guestrole->id, $context->id);
7421 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $guestrole->id, $context->id);
7422 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $guestrole->id, $context->id);
7423 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $guestrole->id, $context->id);
7424 assign_capability('mod/forum:editanypost', CAP_PREVENT, $guestrole->id, $context->id);
7425 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $guestrole->id, $context->id);
7426 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $guestrole->id, $context->id);
7427 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $guestrole->id, $context->id);
7428 assign_capability('mod/forum:postwithoutthrottling', CAP_PREVENT, $guestrole->id, $context->id);
7431 } else {
7432 // Non-teacher forum.
7434 if (empty($cmid)) {
7435 // We were not given the course_module id. Try to find it.
7436 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
7437 echo $OUTPUT->notification('Could not get the course module for the forum');
7438 return false;
7439 } else {
7440 $cmid = $cm->id;
7443 $context = get_context_instance(CONTEXT_MODULE, $cmid);
7445 // $forum->open defines what students can do:
7446 // 0 = No discussions, no replies
7447 // 1 = No discussions, but replies are allowed
7448 // 2 = Discussions and replies are allowed
7449 switch ($forum->open) {
7450 case 0:
7451 foreach ($studentroles as $studentrole) {
7452 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7453 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
7455 break;
7456 case 1:
7457 foreach ($studentroles as $studentrole) {
7458 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7459 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
7461 break;
7462 case 2:
7463 foreach ($studentroles as $studentrole) {
7464 assign_capability('mod/forum:startdiscussion', CAP_ALLOW, $studentrole->id, $context->id);
7465 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
7467 break;
7470 // $forum->assessed defines whether forum rating is turned
7471 // on (1 or 2) and who can rate posts:
7472 // 1 = Everyone can rate posts
7473 // 2 = Only teachers can rate posts
7474 switch ($forum->assessed) {
7475 case 1:
7476 foreach ($studentroles as $studentrole) {
7477 assign_capability('mod/forum:rate', CAP_ALLOW, $studentrole->id, $context->id);
7479 foreach ($teacherroles as $teacherrole) {
7480 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
7482 break;
7483 case 2:
7484 foreach ($studentroles as $studentrole) {
7485 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
7487 foreach ($teacherroles as $teacherrole) {
7488 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
7490 break;
7493 // $forum->assesspublic defines whether students can see
7494 // everybody's ratings:
7495 // 0 = Students can only see their own ratings
7496 // 1 = Students can see everyone's ratings
7497 switch ($forum->assesspublic) {
7498 case 0:
7499 foreach ($studentroles as $studentrole) {
7500 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
7502 foreach ($teacherroles as $teacherrole) {
7503 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
7505 break;
7506 case 1:
7507 foreach ($studentroles as $studentrole) {
7508 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $studentrole->id, $context->id);
7510 foreach ($teacherroles as $teacherrole) {
7511 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
7513 break;
7516 if (empty($cm)) {
7517 $cm = $DB->get_record('course_modules', array('id' => $cmid));
7520 // $cm->groupmode:
7521 // 0 - No groups
7522 // 1 - Separate groups
7523 // 2 - Visible groups
7524 switch ($cm->groupmode) {
7525 case 0:
7526 break;
7527 case 1:
7528 foreach ($studentroles as $studentrole) {
7529 assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $studentrole->id, $context->id);
7531 foreach ($teacherroles as $teacherrole) {
7532 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
7534 break;
7535 case 2:
7536 foreach ($studentroles as $studentrole) {
7537 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $studentrole->id, $context->id);
7539 foreach ($teacherroles as $teacherrole) {
7540 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
7542 break;
7545 return true;
7549 * Returns array of forum layout modes
7551 * @return array
7553 function forum_get_layout_modes() {
7554 return array (FORUM_MODE_FLATOLDEST => get_string('modeflatoldestfirst', 'forum'),
7555 FORUM_MODE_FLATNEWEST => get_string('modeflatnewestfirst', 'forum'),
7556 FORUM_MODE_THREADED => get_string('modethreaded', 'forum'),
7557 FORUM_MODE_NESTED => get_string('modenested', 'forum'));
7561 * Returns array of forum types chooseable on the forum editing form
7563 * @return array
7565 function forum_get_forum_types() {
7566 return array ('general' => get_string('generalforum', 'forum'),
7567 'eachuser' => get_string('eachuserforum', 'forum'),
7568 'single' => get_string('singleforum', 'forum'),
7569 'qanda' => get_string('qandaforum', 'forum'),
7570 'blog' => get_string('blogforum', 'forum'));
7574 * Returns array of all forum layout modes
7576 * @return array
7578 function forum_get_forum_types_all() {
7579 return array ('news' => get_string('namenews','forum'),
7580 'social' => get_string('namesocial','forum'),
7581 'general' => get_string('generalforum', 'forum'),
7582 'eachuser' => get_string('eachuserforum', 'forum'),
7583 'single' => get_string('singleforum', 'forum'),
7584 'qanda' => get_string('qandaforum', 'forum'),
7585 'blog' => get_string('blogforum', 'forum'));
7589 * Returns array of forum open modes
7591 * @return array
7593 function forum_get_open_modes() {
7594 return array ('2' => get_string('openmode2', 'forum'),
7595 '1' => get_string('openmode1', 'forum'),
7596 '0' => get_string('openmode0', 'forum') );
7600 * Returns all other caps used in module
7602 * @return array
7604 function forum_get_extra_capabilities() {
7605 return array('moodle/site:accessallgroups', 'moodle/site:viewfullnames', 'moodle/site:trustcontent', 'moodle/rating:view', 'moodle/rating:viewany', 'moodle/rating:viewall', 'moodle/rating:rate');
7610 * This function is used to extend the global navigation by add forum nodes if there
7611 * is relevant content.
7613 * @param navigation_node $navref
7614 * @param stdClass $course
7615 * @param stdClass $module
7616 * @param stdClass $cm
7618 /*************************************************
7619 function forum_extend_navigation($navref, $course, $module, $cm) {
7620 global $CFG, $OUTPUT, $USER;
7622 $limit = 5;
7624 $discussions = forum_get_discussions($cm,"d.timemodified DESC", false, -1, $limit);
7625 $discussioncount = forum_get_discussions_count($cm);
7626 if (!is_array($discussions) || count($discussions)==0) {
7627 return;
7629 $discussionnode = $navref->add(get_string('discussions', 'forum').' ('.$discussioncount.')');
7630 $discussionnode->mainnavonly = true;
7631 $discussionnode->display = false; // Do not display on navigation (only on navbar)
7633 foreach ($discussions as $discussion) {
7634 $icon = new pix_icon('i/feedback', '');
7635 $url = new moodle_url('/mod/forum/discuss.php', array('d'=>$discussion->discussion));
7636 $discussionnode->add($discussion->subject, $url, navigation_node::TYPE_SETTING, null, null, $icon);
7639 if ($discussioncount > count($discussions)) {
7640 if (!empty($navref->action)) {
7641 $url = $navref->action;
7642 } else {
7643 $url = new moodle_url('/mod/forum/view.php', array('id'=>$cm->id));
7645 $discussionnode->add(get_string('viewalldiscussions', 'forum'), $url, navigation_node::TYPE_SETTING, null, null, $icon);
7648 $index = 0;
7649 $recentposts = array();
7650 $lastlogin = time() - COURSE_MAX_RECENT_PERIOD;
7651 if (!isguestuser() and !empty($USER->lastcourseaccess[$course->id])) {
7652 if ($USER->lastcourseaccess[$course->id] > $lastlogin) {
7653 $lastlogin = $USER->lastcourseaccess[$course->id];
7656 forum_get_recent_mod_activity($recentposts, $index, $lastlogin, $course->id, $cm->id);
7658 if (is_array($recentposts) && count($recentposts)>0) {
7659 $recentnode = $navref->add(get_string('recentactivity').' ('.count($recentposts).')');
7660 $recentnode->mainnavonly = true;
7661 $recentnode->display = false;
7662 foreach ($recentposts as $post) {
7663 $icon = new pix_icon('i/feedback', '');
7664 $url = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->content->discussion));
7665 $title = $post->content->subject."\n".userdate($post->timestamp, get_string('strftimerecent', 'langconfig'))."\n".$post->user->firstname.' '.$post->user->lastname;
7666 $recentnode->add($title, $url, navigation_node::TYPE_SETTING, null, null, $icon);
7670 *************************/
7673 * Adds module specific settings to the settings block
7675 * @param settings_navigation $settings The settings navigation object
7676 * @param navigation_node $forumnode The node to add module settings to
7678 function forum_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $forumnode) {
7679 global $USER, $PAGE, $CFG, $DB, $OUTPUT;
7681 $forumobject = $DB->get_record("forum", array("id" => $PAGE->cm->instance));
7682 if (empty($PAGE->cm->context)) {
7683 $PAGE->cm->context = get_context_instance(CONTEXT_MODULE, $PAGE->cm->instance);
7686 // for some actions you need to be enrolled, beiing admin is not enough sometimes here
7687 $enrolled = is_enrolled($PAGE->cm->context, $USER, '', false);
7688 $activeenrolled = is_enrolled($PAGE->cm->context, $USER, '', true);
7690 $canmanage = has_capability('mod/forum:managesubscriptions', $PAGE->cm->context);
7691 $subscriptionmode = forum_get_forcesubscribed($forumobject);
7692 $cansubscribe = ($activeenrolled && $subscriptionmode != FORUM_FORCESUBSCRIBE && ($subscriptionmode != FORUM_DISALLOWSUBSCRIBE || $canmanage));
7694 if ($canmanage) {
7695 $mode = $forumnode->add(get_string('subscriptionmode', 'forum'), null, navigation_node::TYPE_CONTAINER);
7697 $allowchoice = $mode->add(get_string('subscriptionoptional', 'forum'), new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'mode'=>FORUM_CHOOSESUBSCRIBE, 'sesskey'=>sesskey())), navigation_node::TYPE_SETTING);
7698 $forceforever = $mode->add(get_string("subscriptionforced", "forum"), new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'mode'=>FORUM_FORCESUBSCRIBE, 'sesskey'=>sesskey())), navigation_node::TYPE_SETTING);
7699 $forceinitially = $mode->add(get_string("subscriptionauto", "forum"), new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'mode'=>FORUM_INITIALSUBSCRIBE, 'sesskey'=>sesskey())), navigation_node::TYPE_SETTING);
7700 $disallowchoice = $mode->add(get_string('subscriptiondisabled', 'forum'), new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'mode'=>FORUM_DISALLOWSUBSCRIBE, 'sesskey'=>sesskey())), navigation_node::TYPE_SETTING);
7702 switch ($subscriptionmode) {
7703 case FORUM_CHOOSESUBSCRIBE : // 0
7704 $allowchoice->action = null;
7705 $allowchoice->add_class('activesetting');
7706 break;
7707 case FORUM_FORCESUBSCRIBE : // 1
7708 $forceforever->action = null;
7709 $forceforever->add_class('activesetting');
7710 break;
7711 case FORUM_INITIALSUBSCRIBE : // 2
7712 $forceinitially->action = null;
7713 $forceinitially->add_class('activesetting');
7714 break;
7715 case FORUM_DISALLOWSUBSCRIBE : // 3
7716 $disallowchoice->action = null;
7717 $disallowchoice->add_class('activesetting');
7718 break;
7721 } else if ($activeenrolled) {
7723 switch ($subscriptionmode) {
7724 case FORUM_CHOOSESUBSCRIBE : // 0
7725 $notenode = $forumnode->add(get_string('subscriptionoptional', 'forum'));
7726 break;
7727 case FORUM_FORCESUBSCRIBE : // 1
7728 $notenode = $forumnode->add(get_string('subscriptionforced', 'forum'));
7729 break;
7730 case FORUM_INITIALSUBSCRIBE : // 2
7731 $notenode = $forumnode->add(get_string('subscriptionauto', 'forum'));
7732 break;
7733 case FORUM_DISALLOWSUBSCRIBE : // 3
7734 $notenode = $forumnode->add(get_string('subscriptiondisabled', 'forum'));
7735 break;
7739 if ($cansubscribe) {
7740 if (forum_is_subscribed($USER->id, $forumobject)) {
7741 $linktext = get_string('unsubscribe', 'forum');
7742 } else {
7743 $linktext = get_string('subscribe', 'forum');
7745 $url = new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'sesskey'=>sesskey()));
7746 $forumnode->add($linktext, $url, navigation_node::TYPE_SETTING);
7749 if (has_capability('mod/forum:viewsubscribers', $PAGE->cm->context)){
7750 $url = new moodle_url('/mod/forum/subscribers.php', array('id'=>$forumobject->id));
7751 $forumnode->add(get_string('showsubscribers', 'forum'), $url, navigation_node::TYPE_SETTING);
7754 if ($enrolled && forum_tp_can_track_forums($forumobject)) { // keep tracking info for users with suspended enrolments
7755 if ($forumobject->trackingtype != FORUM_TRACKING_OPTIONAL) {
7756 //tracking forced on or off in forum settings so dont provide a link here to change it
7757 //could add unclickable text like for forced subscription but not sure this justifies adding another menu item
7758 } else {
7759 if (forum_tp_is_tracked($forumobject)) {
7760 $linktext = get_string('notrackforum', 'forum');
7761 } else {
7762 $linktext = get_string('trackforum', 'forum');
7764 $url = new moodle_url('/mod/forum/settracking.php', array('id'=>$forumobject->id));
7765 $forumnode->add($linktext, $url, navigation_node::TYPE_SETTING);
7769 if (!isloggedin() && $PAGE->course->id == SITEID) {
7770 $userid = guest_user()->id;
7771 } else {
7772 $userid = $USER->id;
7775 $hascourseaccess = ($PAGE->course->id == SITEID) || can_access_course($PAGE->course, $userid);
7776 $enablerssfeeds = !empty($CFG->enablerssfeeds) && !empty($CFG->forum_enablerssfeeds);
7778 if ($enablerssfeeds && $forumobject->rsstype && $forumobject->rssarticles && $hascourseaccess) {
7780 if (!function_exists('rss_get_url')) {
7781 require_once("$CFG->libdir/rsslib.php");
7784 if ($forumobject->rsstype == 1) {
7785 $string = get_string('rsssubscriberssdiscussions','forum');
7786 } else {
7787 $string = get_string('rsssubscriberssposts','forum');
7790 $url = new moodle_url(rss_get_url($PAGE->cm->context->id, $userid, "mod_forum", $forumobject->id));
7791 $forumnode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', ''));
7796 * Abstract class used by forum subscriber selection controls
7797 * @package mod-forum
7798 * @copyright 2009 Sam Hemelryk
7799 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7801 abstract class forum_subscriber_selector_base extends user_selector_base {
7804 * The id of the forum this selector is being used for
7805 * @var int
7807 protected $forumid = null;
7809 * The context of the forum this selector is being used for
7810 * @var object
7812 protected $context = null;
7814 * The id of the current group
7815 * @var int
7817 protected $currentgroup = null;
7820 * Constructor method
7821 * @param string $name
7822 * @param array $options
7824 public function __construct($name, $options) {
7825 $options['accesscontext'] = $options['context'];
7826 parent::__construct($name, $options);
7827 if (isset($options['context'])) {
7828 $this->context = $options['context'];
7830 if (isset($options['currentgroup'])) {
7831 $this->currentgroup = $options['currentgroup'];
7833 if (isset($options['forumid'])) {
7834 $this->forumid = $options['forumid'];
7839 * Returns an array of options to seralise and store for searches
7841 * @return array
7843 protected function get_options() {
7844 global $CFG;
7845 $options = parent::get_options();
7846 $options['file'] = substr(__FILE__, strlen($CFG->dirroot.'/'));
7847 $options['context'] = $this->context;
7848 $options['currentgroup'] = $this->currentgroup;
7849 $options['forumid'] = $this->forumid;
7850 return $options;
7856 * A user selector control for potential subscribers to the selected forum
7857 * @package mod-forum
7858 * @copyright 2009 Sam Hemelryk
7859 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7861 class forum_potential_subscriber_selector extends forum_subscriber_selector_base {
7864 * If set to true EVERYONE in this course is force subscribed to this forum
7865 * @var bool
7867 protected $forcesubscribed = false;
7869 * Can be used to store existing subscribers so that they can be removed from
7870 * the potential subscribers list
7872 protected $existingsubscribers = array();
7875 * Constructor method
7876 * @param string $name
7877 * @param array $options
7879 public function __construct($name, $options) {
7880 parent::__construct($name, $options);
7881 if (isset($options['forcesubscribed'])) {
7882 $this->forcesubscribed=true;
7887 * Returns an arary of options for this control
7888 * @return array
7890 protected function get_options() {
7891 $options = parent::get_options();
7892 if ($this->forcesubscribed===true) {
7893 $options['forcesubscribed']=1;
7895 return $options;
7899 * Finds all potential users
7901 * Potential users are determined by checking for users with a capability
7902 * determined in {@see forum_get_potential_subscribers()}
7904 * @param string $search
7905 * @return array
7907 public function find_users($search) {
7908 global $DB;
7910 $availableusers = forum_get_potential_subscribers($this->context, $this->currentgroup, $this->required_fields_sql('u'), 'u.firstname ASC, u.lastname ASC');
7912 if (empty($availableusers)) {
7913 $availableusers = array();
7914 } else if ($search) {
7915 $search = strtolower($search);
7916 foreach ($availableusers as $key=>$user) {
7917 if (stripos($user->firstname, $search) === false && stripos($user->lastname, $search) === false) {
7918 unset($availableusers[$key]);
7923 // Unset any existing subscribers
7924 if (count($this->existingsubscribers)>0 && !$this->forcesubscribed) {
7925 foreach ($this->existingsubscribers as $group) {
7926 foreach ($group as $user) {
7927 if (array_key_exists($user->id, $availableusers)) {
7928 unset($availableusers[$user->id]);
7934 if ($this->forcesubscribed) {
7935 return array(get_string("existingsubscribers", 'forum') => $availableusers);
7936 } else {
7937 return array(get_string("potentialsubscribers", 'forum') => $availableusers);
7942 * Sets the existing subscribers
7943 * @param array $users
7945 public function set_existing_subscribers(array $users) {
7946 $this->existingsubscribers = $users;
7950 * Sets this forum as force subscribed or not
7952 public function set_force_subscribed($setting=true) {
7953 $this->forcesubscribed = true;
7958 * User selector control for removing subscribed users
7959 * @package mod-forum
7960 * @copyright 2009 Sam Hemelryk
7961 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7963 class forum_existing_subscriber_selector extends forum_subscriber_selector_base {
7966 * Finds all subscribed users
7968 * @param string $search
7969 * @return array
7971 public function find_users($search) {
7972 global $DB;
7973 list($wherecondition, $params) = $this->search_sql($search, 'u');
7974 $params['forumid'] = $this->forumid;
7976 // only active enrolled or everybody on the frontpage
7977 list($esql, $eparams) = get_enrolled_sql($this->context, '', $this->currentgroup, true);
7978 $params = array_merge($params, $eparams);
7980 $fields = $this->required_fields_sql('u');
7982 $subscribers = $DB->get_records_sql("SELECT $fields
7983 FROM {user} u
7984 JOIN ($esql) je ON je.id = u.id
7985 JOIN {forum_subscriptions} s ON s.userid = u.id
7986 WHERE $wherecondition AND s.forum = :forumid
7987 ORDER BY u.lastname ASC, u.firstname ASC", $params);
7989 return array(get_string("existingsubscribers", 'forum') => $subscribers);
7995 * Adds information about unread messages, that is only required for the course view page (and
7996 * similar), to the course-module object.
7997 * @param cm_info $cm Course-module object
7999 function forum_cm_info_view(cm_info $cm) {
8000 global $CFG;
8002 // Get tracking status (once per request)
8003 static $initialised;
8004 static $usetracking, $strunreadpostsone;
8005 if (!isset($initialised)) {
8006 if ($usetracking = forum_tp_can_track_forums()) {
8007 $strunreadpostsone = get_string('unreadpostsone', 'forum');
8009 $initialised = true;
8012 if ($usetracking) {
8013 if ($unread = forum_tp_count_forum_unread_posts($cm, $cm->get_course())) {
8014 $out = '<span class="unread"> <a href="' . $cm->get_url() . '">';
8015 if ($unread == 1) {
8016 $out .= $strunreadpostsone;
8017 } else {
8018 $out .= get_string('unreadpostsnumber', 'forum', $unread);
8020 $out .= '</a></span>';
8021 $cm->set_after_link($out);
8027 * Return a list of page types
8028 * @param string $pagetype current page type
8029 * @param stdClass $parentcontext Block's parent context
8030 * @param stdClass $currentcontext Current context of block
8032 function forum_page_type_list($pagetype, $parentcontext, $currentcontext) {
8033 $forum_pagetype = array(
8034 'mod-forum-*'=>get_string('page-mod-forum-x', 'forum'),
8035 'mod-forum-view'=>get_string('page-mod-forum-view', 'forum'),
8036 'mod-forum-discuss'=>get_string('page-mod-forum-discuss', 'forum')
8038 return $forum_pagetype;
8042 * Gets all of the courses where the provided user has posted in a forum.
8044 * @global moodle_database $DB The database connection
8045 * @param stdClass $user The user who's posts we are looking for
8046 * @param bool $discussionsonly If true only look for discussions started by the user
8047 * @param bool $includecontexts If set to trye contexts for the courses will be preloaded
8048 * @param int $limitfrom The offset of records to return
8049 * @param int $limitnum The number of records to return
8050 * @return array An array of courses
8052 function forum_get_courses_user_posted_in($user, $discussionsonly = false, $includecontexts = true, $limitfrom = null, $limitnum = null) {
8053 global $DB;
8055 // If we are only after discussions we need only look at the forum_discussions
8056 // table and join to the userid there. If we are looking for posts then we need
8057 // to join to the forum_posts table.
8058 if (!$discussionsonly) {
8059 $joinsql = 'JOIN {forum_discussions} fd ON fd.course = c.id
8060 JOIN {forum_posts} fp ON fp.discussion = fd.id';
8061 $wheresql = 'fp.userid = :userid';
8062 $params = array('userid' => $user->id);
8063 } else {
8064 $joinsql = 'JOIN {forum_discussions} fd ON fd.course = c.id';
8065 $wheresql = 'fd.userid = :userid';
8066 $params = array('userid' => $user->id);
8069 // Join to the context table so that we can preload contexts if required.
8070 if ($includecontexts) {
8071 list($ctxselect, $ctxjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
8072 } else {
8073 $ctxselect = '';
8074 $ctxjoin = '';
8077 // Now we need to get all of the courses to search.
8078 // All courses where the user has posted within a forum will be returned.
8079 $sql = "SELECT DISTINCT c.* $ctxselect
8080 FROM {course} c
8081 $joinsql
8082 $ctxjoin
8083 WHERE $wheresql";
8084 $courses = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
8085 if ($includecontexts) {
8086 array_map('context_instance_preload', $courses);
8088 return $courses;
8092 * Gets all of the forums a user has posted in for one or more courses.
8094 * @global moodle_database $DB
8095 * @param stdClass $user
8096 * @param array $courseids An array of courseids to search or if not provided
8097 * all courses the user has posted within
8098 * @param bool $discussionsonly If true then only forums where the user has started
8099 * a discussion will be returned.
8100 * @param int $limitfrom The offset of records to return
8101 * @param int $limitnum The number of records to return
8102 * @return array An array of forums the user has posted within in the provided courses
8104 function forum_get_forums_user_posted_in($user, array $courseids = null, $discussionsonly = false, $limitfrom = null, $limitnum = null) {
8105 global $DB;
8107 if (!is_null($courseids)) {
8108 list($coursewhere, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'courseid');
8109 $coursewhere = ' AND f.course '.$coursewhere;
8110 } else {
8111 $coursewhere = '';
8112 $params = array();
8114 $params['userid'] = $user->id;
8115 $params['forum'] = 'forum';
8117 if ($discussionsonly) {
8118 $join = 'JOIN {forum_discussions} ff ON ff.forum = f.id';
8119 } else {
8120 $join = 'JOIN {forum_discussions} fd ON fd.forum = f.id
8121 JOIN {forum_posts} ff ON ff.discussion = fd.id';
8124 $sql = "SELECT f.*, cm.id AS cmid
8125 FROM {forum} f
8126 JOIN {course_modules} cm ON cm.instance = f.id
8127 JOIN {modules} m ON m.id = cm.module
8128 JOIN (
8129 SELECT f.id
8130 FROM {forum} f
8131 {$join}
8132 WHERE ff.userid = :userid
8133 GROUP BY f.id
8134 ) j ON j.id = f.id
8135 WHERE m.name = :forum
8136 {$coursewhere}";
8138 $courseforums = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
8139 return $courseforums;
8143 * Returns posts made by the selected user in the requested courses.
8145 * This method can be used to return all of the posts made by the requested user
8146 * within the given courses.
8147 * For each course the access of the current user and requested user is checked
8148 * and then for each post access to the post and forum is checked as well.
8150 * This function is safe to use with usercapabilities.
8152 * @global moodle_database $DB
8153 * @param stdClass $user The user whose posts we want to get
8154 * @param array $courses The courses to search
8155 * @param bool $musthaveaccess If set to true errors will be thrown if the user
8156 * cannot access one or more of the courses to search
8157 * @param bool $discussionsonly If set to true only discussion starting posts
8158 * will be returned.
8159 * @param int $limitfrom The offset of records to return
8160 * @param int $limitnum The number of records to return
8161 * @return stdClass An object the following properties
8162 * ->totalcount: the total number of posts made by the requested user
8163 * that the current user can see.
8164 * ->courses: An array of courses the current user can see that the
8165 * requested user has posted in.
8166 * ->forums: An array of forums relating to the posts returned in the
8167 * property below.
8168 * ->posts: An array containing the posts to show for this request.
8170 function forum_get_posts_by_user($user, array $courses, $musthaveaccess = false, $discussionsonly = false, $limitfrom = 0, $limitnum = 50) {
8171 global $DB, $USER, $CFG;
8173 $return = new stdClass;
8174 $return->totalcount = 0; // The total number of posts that the current user is able to view
8175 $return->courses = array(); // The courses the current user can access
8176 $return->forums = array(); // The forums that the current user can access that contain posts
8177 $return->posts = array(); // The posts to display
8179 // First up a small sanity check. If there are no courses to check we can
8180 // return immediately, there is obviously nothing to search.
8181 if (empty($courses)) {
8182 return $return;
8185 // A couple of quick setups
8186 $isloggedin = isloggedin();
8187 $isguestuser = $isloggedin && isguestuser();
8188 $iscurrentuser = $isloggedin && $USER->id == $user->id;
8190 // Checkout whether or not the current user has capabilities over the requested
8191 // user and if so they have the capabilities required to view the requested
8192 // users content.
8193 $usercontext = get_context_instance(CONTEXT_USER, $user->id, MUST_EXIST);
8194 $hascapsonuser = !$iscurrentuser && $DB->record_exists('role_assignments', array('userid' => $USER->id, 'contextid' => $usercontext->id));
8195 $hascapsonuser = $hascapsonuser && has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), $usercontext);
8197 // Before we actually search each course we need to check the user's access to the
8198 // course. If the user doesn't have the appropraite access then we either throw an
8199 // error if a particular course was requested or we just skip over the course.
8200 foreach ($courses as $course) {
8201 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST);
8202 if ($iscurrentuser || $hascapsonuser) {
8203 // If it is the current user, or the current user has capabilities to the
8204 // requested user then all we need to do is check the requested users
8205 // current access to the course.
8206 // Note: There is no need to check group access or anything of the like
8207 // as either the current user is the requested user, or has granted
8208 // capabilities on the requested user. Either way they can see what the
8209 // requested user posted, although its VERY unlikely in the `parent` situation
8210 // that the current user will be able to view the posts in context.
8211 if (!is_viewing($coursecontext, $user) && !is_enrolled($coursecontext, $user)) {
8212 // Need to have full access to a course to see the rest of own info
8213 if ($musthaveaccess) {
8214 print_error('errorenrolmentrequired', 'forum');
8216 continue;
8218 } else {
8219 // Check whether the current user is enrolled or has access to view the course
8220 // if they don't we immediately have a problem.
8221 if (!can_access_course($course)) {
8222 if ($musthaveaccess) {
8223 print_error('errorenrolmentrequired', 'forum');
8225 continue;
8228 // Check whether the requested user is enrolled or has access to view the course
8229 // if they don't we immediately have a problem.
8230 if (!can_access_course($course, $user)) {
8231 if ($musthaveaccess) {
8232 print_error('notenrolled', 'forum');
8234 continue;
8237 // If groups are in use and enforced throughout the course then make sure
8238 // we can meet in at least one course level group.
8239 // Note that we check if either the current user or the requested user have
8240 // the capability to access all groups. This is because with that capability
8241 // a user in group A could post in the group B forum. Grrrr.
8242 if (groups_get_course_groupmode($course) == SEPARATEGROUPS && $course->groupmodeforce
8243 && !has_capability('moodle/site:accessallgroups', $coursecontext) && !has_capability('moodle/site:accessallgroups', $coursecontext, $user->id)) {
8244 // If its the guest user to bad... the guest user cannot access groups
8245 if (!$isloggedin or $isguestuser) {
8246 // do not use require_login() here because we might have already used require_login($course)
8247 if ($musthaveaccess) {
8248 redirect(get_login_url());
8250 continue;
8252 // Get the groups of the current user
8253 $mygroups = array_keys(groups_get_all_groups($course->id, $USER->id, $course->defaultgroupingid, 'g.id, g.name'));
8254 // Get the groups the requested user is a member of
8255 $usergroups = array_keys(groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid, 'g.id, g.name'));
8256 // Check whether they are members of the same group. If they are great.
8257 $intersect = array_intersect($mygroups, $usergroups);
8258 if (empty($intersect)) {
8259 // But they're not... if it was a specific course throw an error otherwise
8260 // just skip this course so that it is not searched.
8261 if ($musthaveaccess) {
8262 print_error("groupnotamember", '', $CFG->wwwroot."/course/view.php?id=$course->id");
8264 continue;
8268 // Woo hoo we got this far which means the current user can search this
8269 // this course for the requested user. Although this is only the course accessibility
8270 // handling that is complete, the forum accessibility tests are yet to come.
8271 $return->courses[$course->id] = $course;
8273 // No longer beed $courses array - lose it not it may be big
8274 unset($courses);
8276 // Make sure that we have some courses to search
8277 if (empty($return->courses)) {
8278 // If we don't have any courses to search then the reality is that the current
8279 // user doesn't have access to any courses is which the requested user has posted.
8280 // Although we do know at this point that the requested user has posts.
8281 if ($musthaveaccess) {
8282 print_error('permissiondenied');
8283 } else {
8284 return $return;
8288 // Next step: Collect all of the forums that we will want to search.
8289 // It is important to note that this step isn't actually about searching, it is
8290 // about determining which forums we can search by testing accessibility.
8291 $forums = forum_get_forums_user_posted_in($user, array_keys($return->courses), $discussionsonly);
8293 // Will be used to build the where conditions for the search
8294 $forumsearchwhere = array();
8295 // Will be used to store the where condition params for the search
8296 $forumsearchparams = array();
8297 // Will record forums where the user can freely access everything
8298 $forumsearchfullaccess = array();
8299 // DB caching friendly
8300 $now = round(time(), -2);
8301 // For each course to search we want to find the forums the user has posted in
8302 // and providing the current user can access the forum create a search condition
8303 // for the forum to get the requested users posts.
8304 foreach ($return->courses as $course) {
8305 // Now we need to get the forums
8306 $modinfo = get_fast_modinfo($course);
8307 if (empty($modinfo->instances['forum'])) {
8308 // hmmm, no forums? well at least its easy... skip!
8309 continue;
8311 // Iterate
8312 foreach ($modinfo->get_instances_of('forum') as $forumid => $cm) {
8313 if (!$cm->uservisible or !isset($forums[$forumid])) {
8314 continue;
8316 // Get the forum in question
8317 $forum = $forums[$forumid];
8318 // This is needed for functionality later on in the forum code....
8319 $forum->cm = $cm;
8321 // Check that either the current user can view the forum, or that the
8322 // current user has capabilities over the requested user and the requested
8323 // user can view the discussion
8324 if (!has_capability('mod/forum:viewdiscussion', $cm->context) && !($hascapsonuser && has_capability('mod/forum:viewdiscussion', $cm->context, $user->id))) {
8325 continue;
8328 // This will contain forum specific where clauses
8329 $forumsearchselect = array();
8330 if (!$iscurrentuser && !$hascapsonuser) {
8331 // Make sure we check group access
8332 if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $cm->context)) {
8333 $groups = $modinfo->get_groups($cm->groupingid);
8334 $groups[] = -1;
8335 list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
8336 $forumsearchparams = array_merge($forumsearchparams, $groupid_params);
8337 $forumsearchselect[] = "d.groupid $groupid_sql";
8340 // hidden timed discussions
8341 if (!empty($CFG->forum_enabletimedposts) && !has_capability('mod/forum:viewhiddentimedposts', $cm->context)) {
8342 $forumsearchselect[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
8343 $forumsearchparams['userid'.$forumid] = $user->id;
8344 $forumsearchparams['timestart'.$forumid] = $now;
8345 $forumsearchparams['timeend'.$forumid] = $now;
8348 // qanda access
8349 if ($forum->type == 'qanda' && !has_capability('mod/forum:viewqandawithoutposting', $cm->context)) {
8350 // We need to check whether the user has posted in the qanda forum.
8351 $discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $user->id);
8352 if (!empty($discussionspostedin)) {
8353 $forumonlydiscussions = array(); // Holds discussion ids for the discussions the user is allowed to see in this forum.
8354 foreach ($discussionspostedin as $d) {
8355 $forumonlydiscussions[] = $d->id;
8357 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forumonlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
8358 $forumsearchparams = array_merge($forumsearchparams, $discussionid_params);
8359 $forumsearchselect[] = "(d.id $discussionid_sql OR p.parent = 0)";
8360 } else {
8361 $forumsearchselect[] = "p.parent = 0";
8366 if (count($forumsearchselect) > 0) {
8367 $forumsearchwhere[] = "(d.forum = :forum{$forumid} AND ".implode(" AND ", $forumsearchselect).")";
8368 $forumsearchparams['forum'.$forumid] = $forumid;
8369 } else {
8370 $forumsearchfullaccess[] = $forumid;
8372 } else {
8373 // The current user/parent can see all of their own posts
8374 $forumsearchfullaccess[] = $forumid;
8379 // If we dont have any search conditions, and we don't have any forums where
8380 // the user has full access then we just return the default.
8381 if (empty($forumsearchwhere) && empty($forumsearchfullaccess)) {
8382 return $return;
8385 // Prepare a where condition for the full access forums.
8386 if (count($forumsearchfullaccess) > 0) {
8387 list($fullidsql, $fullidparams) = $DB->get_in_or_equal($forumsearchfullaccess, SQL_PARAMS_NAMED, 'fula');
8388 $forumsearchparams = array_merge($forumsearchparams, $fullidparams);
8389 $forumsearchwhere[] = "(d.forum $fullidsql)";
8392 // Prepare SQL to both count and search.
8393 // We alias user.id to useridx because we forum_posts already has a userid field and not aliasing this would break
8394 // oracle and mssql.
8395 $userfields = user_picture::fields('u', null, 'useridx');
8396 $countsql = 'SELECT COUNT(*) ';
8397 $selectsql = 'SELECT p.*, d.forum, d.name AS discussionname, '.$userfields.' ';
8398 $wheresql = implode(" OR ", $forumsearchwhere);
8400 if ($discussionsonly) {
8401 if ($wheresql == '') {
8402 $wheresql = 'p.parent = 0';
8403 } else {
8404 $wheresql = 'p.parent = 0 AND ('.$wheresql.')';
8408 $sql = "FROM {forum_posts} p
8409 JOIN {forum_discussions} d ON d.id = p.discussion
8410 JOIN {user} u ON u.id = p.userid
8411 WHERE ($wheresql)
8412 AND p.userid = :userid ";
8413 $orderby = "ORDER BY p.modified DESC";
8414 $forumsearchparams['userid'] = $user->id;
8416 // Set the total number posts made by the requested user that the current user can see
8417 $return->totalcount = $DB->count_records_sql($countsql.$sql, $forumsearchparams);
8418 // Set the collection of posts that has been requested
8419 $return->posts = $DB->get_records_sql($selectsql.$sql.$orderby, $forumsearchparams, $limitfrom, $limitnum);
8421 // We need to build an array of forums for which posts will be displayed.
8422 // We do this here to save the caller needing to retrieve them themselves before
8423 // printing these forums posts. Given we have the forums already there is
8424 // practically no overhead here.
8425 foreach ($return->posts as $post) {
8426 if (!array_key_exists($post->forum, $return->forums)) {
8427 $return->forums[$post->forum] = $forums[$post->forum];
8431 return $return;