MDL-39942: invite only users who can post to discuss
[moodle.git] / mod / forum / lib.php
blob2bad212a5c8795c710afc67e7b466486d1d91710
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');
31 /// CONSTANTS ///////////////////////////////////////////////////////////
33 define('FORUM_MODE_FLATOLDEST', 1);
34 define('FORUM_MODE_FLATNEWEST', -1);
35 define('FORUM_MODE_THREADED', 2);
36 define('FORUM_MODE_NESTED', 3);
38 define('FORUM_CHOOSESUBSCRIBE', 0);
39 define('FORUM_FORCESUBSCRIBE', 1);
40 define('FORUM_INITIALSUBSCRIBE', 2);
41 define('FORUM_DISALLOWSUBSCRIBE',3);
43 /**
44 * FORUM_TRACKING_OFF - Tracking is not available for this forum.
46 define('FORUM_TRACKING_OFF', 0);
48 /**
49 * FORUM_TRACKING_OPTIONAL - Tracking is based on user preference.
51 define('FORUM_TRACKING_OPTIONAL', 1);
53 /**
54 * FORUM_TRACKING_FORCED - Tracking is on, regardless of user setting.
55 * Treated as FORUM_TRACKING_OPTIONAL if $CFG->forum_allowforcedreadtracking is off.
57 define('FORUM_TRACKING_FORCED', 2);
59 /**
60 * FORUM_TRACKING_ON - deprecated alias for FORUM_TRACKING_FORCED.
61 * @deprecated since 2.6
63 define('FORUM_TRACKING_ON', 2);
65 define('FORUM_MAILED_PENDING', 0);
66 define('FORUM_MAILED_SUCCESS', 1);
67 define('FORUM_MAILED_ERROR', 2);
69 if (!defined('FORUM_CRON_USER_CACHE')) {
70 /** Defines how many full user records are cached in forum cron. */
71 define('FORUM_CRON_USER_CACHE', 5000);
74 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
76 /**
77 * Given an object containing all the necessary data,
78 * (defined by the form in mod_form.php) this function
79 * will create a new instance and return the id number
80 * of the new instance.
82 * @param stdClass $forum add forum instance
83 * @param mod_forum_mod_form $mform
84 * @return int intance id
86 function forum_add_instance($forum, $mform = null) {
87 global $CFG, $DB;
89 $forum->timemodified = time();
91 if (empty($forum->assessed)) {
92 $forum->assessed = 0;
95 if (empty($forum->ratingtime) or empty($forum->assessed)) {
96 $forum->assesstimestart = 0;
97 $forum->assesstimefinish = 0;
100 $forum->id = $DB->insert_record('forum', $forum);
101 $modcontext = context_module::instance($forum->coursemodule);
103 if ($forum->type == 'single') { // Create related discussion.
104 $discussion = new stdClass();
105 $discussion->course = $forum->course;
106 $discussion->forum = $forum->id;
107 $discussion->name = $forum->name;
108 $discussion->assessed = $forum->assessed;
109 $discussion->message = $forum->intro;
110 $discussion->messageformat = $forum->introformat;
111 $discussion->messagetrust = trusttext_trusted(context_course::instance($forum->course));
112 $discussion->mailnow = false;
113 $discussion->groupid = -1;
115 $message = '';
117 $discussion->id = forum_add_discussion($discussion, null, $message);
119 if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
120 // Ugly hack - we need to copy the files somehow.
121 $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
122 $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
124 $options = array('subdirs'=>true); // Use the same options as intro field!
125 $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
126 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
130 if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
131 $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email');
132 foreach ($users as $user) {
133 forum_subscribe($user->id, $forum->id);
137 forum_grade_item_update($forum);
139 return $forum->id;
144 * Given an object containing all the necessary data,
145 * (defined by the form in mod_form.php) this function
146 * will update an existing instance with new data.
148 * @global object
149 * @param object $forum forum instance (with magic quotes)
150 * @return bool success
152 function forum_update_instance($forum, $mform) {
153 global $DB, $OUTPUT, $USER;
155 $forum->timemodified = time();
156 $forum->id = $forum->instance;
158 if (empty($forum->assessed)) {
159 $forum->assessed = 0;
162 if (empty($forum->ratingtime) or empty($forum->assessed)) {
163 $forum->assesstimestart = 0;
164 $forum->assesstimefinish = 0;
167 $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
169 // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
170 // if scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
171 // 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
172 if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
173 forum_update_grades($forum); // recalculate grades for the forum
176 if ($forum->type == 'single') { // Update related discussion and post.
177 $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
178 if (!empty($discussions)) {
179 if (count($discussions) > 1) {
180 echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
182 $discussion = array_pop($discussions);
183 } else {
184 // try to recover by creating initial discussion - MDL-16262
185 $discussion = new stdClass();
186 $discussion->course = $forum->course;
187 $discussion->forum = $forum->id;
188 $discussion->name = $forum->name;
189 $discussion->assessed = $forum->assessed;
190 $discussion->message = $forum->intro;
191 $discussion->messageformat = $forum->introformat;
192 $discussion->messagetrust = true;
193 $discussion->mailnow = false;
194 $discussion->groupid = -1;
196 $message = '';
198 forum_add_discussion($discussion, null, $message);
200 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
201 print_error('cannotadd', 'forum');
204 if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
205 print_error('cannotfindfirstpost', 'forum');
208 $cm = get_coursemodule_from_instance('forum', $forum->id);
209 $modcontext = context_module::instance($cm->id, MUST_EXIST);
211 $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
212 $post->subject = $forum->name;
213 $post->message = $forum->intro;
214 $post->messageformat = $forum->introformat;
215 $post->messagetrust = trusttext_trusted($modcontext);
216 $post->modified = $forum->timemodified;
217 $post->userid = $USER->id; // MDL-18599, so that current teacher can take ownership of activities.
219 if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
220 // Ugly hack - we need to copy the files somehow.
221 $options = array('subdirs'=>true); // Use the same options as intro field!
222 $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
225 $DB->update_record('forum_posts', $post);
226 $discussion->name = $forum->name;
227 $DB->update_record('forum_discussions', $discussion);
230 $DB->update_record('forum', $forum);
232 $modcontext = context_module::instance($forum->coursemodule);
233 if (($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) && ($oldforum->forcesubscribe <> $forum->forcesubscribe)) {
234 $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
235 foreach ($users as $user) {
236 forum_subscribe($user->id, $forum->id);
240 forum_grade_item_update($forum);
242 return true;
247 * Given an ID of an instance of this module,
248 * this function will permanently delete the instance
249 * and any data that depends on it.
251 * @global object
252 * @param int $id forum instance id
253 * @return bool success
255 function forum_delete_instance($id) {
256 global $DB;
258 if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
259 return false;
261 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
262 return false;
264 if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
265 return false;
268 $context = context_module::instance($cm->id);
270 // now get rid of all files
271 $fs = get_file_storage();
272 $fs->delete_area_files($context->id);
274 $result = true;
276 if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
277 foreach ($discussions as $discussion) {
278 if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
279 $result = false;
284 if (!$DB->delete_records('forum_digests', array('forum' => $forum->id))) {
285 $result = false;
288 if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
289 $result = false;
292 forum_tp_delete_read_records(-1, -1, -1, $forum->id);
294 if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
295 $result = false;
298 forum_grade_item_delete($forum);
300 return $result;
305 * Indicates API features that the forum supports.
307 * @uses FEATURE_GROUPS
308 * @uses FEATURE_GROUPINGS
309 * @uses FEATURE_GROUPMEMBERSONLY
310 * @uses FEATURE_MOD_INTRO
311 * @uses FEATURE_COMPLETION_TRACKS_VIEWS
312 * @uses FEATURE_COMPLETION_HAS_RULES
313 * @uses FEATURE_GRADE_HAS_GRADE
314 * @uses FEATURE_GRADE_OUTCOMES
315 * @param string $feature
316 * @return mixed True if yes (some features may use other values)
318 function forum_supports($feature) {
319 switch($feature) {
320 case FEATURE_GROUPS: return true;
321 case FEATURE_GROUPINGS: return true;
322 case FEATURE_GROUPMEMBERSONLY: return true;
323 case FEATURE_MOD_INTRO: return true;
324 case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
325 case FEATURE_COMPLETION_HAS_RULES: return true;
326 case FEATURE_GRADE_HAS_GRADE: return true;
327 case FEATURE_GRADE_OUTCOMES: return true;
328 case FEATURE_RATE: return true;
329 case FEATURE_BACKUP_MOODLE2: return true;
330 case FEATURE_SHOW_DESCRIPTION: return true;
331 case FEATURE_PLAGIARISM: return true;
333 default: return null;
339 * Obtains the automatic completion state for this forum based on any conditions
340 * in forum settings.
342 * @global object
343 * @global object
344 * @param object $course Course
345 * @param object $cm Course-module
346 * @param int $userid User ID
347 * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
348 * @return bool True if completed, false if not. (If no conditions, then return
349 * value depends on comparison type)
351 function forum_get_completion_state($course,$cm,$userid,$type) {
352 global $CFG,$DB;
354 // Get forum details
355 if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
356 throw new Exception("Can't find forum {$cm->instance}");
359 $result=$type; // Default return value
361 $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
362 $postcountsql="
363 SELECT
364 COUNT(1)
365 FROM
366 {forum_posts} fp
367 INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
368 WHERE
369 fp.userid=:userid AND fd.forum=:forumid";
371 if ($forum->completiondiscussions) {
372 $value = $forum->completiondiscussions <=
373 $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
374 if ($type == COMPLETION_AND) {
375 $result = $result && $value;
376 } else {
377 $result = $result || $value;
380 if ($forum->completionreplies) {
381 $value = $forum->completionreplies <=
382 $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
383 if ($type==COMPLETION_AND) {
384 $result = $result && $value;
385 } else {
386 $result = $result || $value;
389 if ($forum->completionposts) {
390 $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
391 if ($type == COMPLETION_AND) {
392 $result = $result && $value;
393 } else {
394 $result = $result || $value;
398 return $result;
402 * Create a message-id string to use in the custom headers of forum notification emails
404 * message-id is used by email clients to identify emails and to nest conversations
406 * @param int $postid The ID of the forum post we are notifying the user about
407 * @param int $usertoid The ID of the user being notified
408 * @param string $hostname The server's hostname
409 * @return string A unique message-id
411 function forum_get_email_message_id($postid, $usertoid, $hostname) {
412 return '<'.hash('sha256',$postid.'to'.$usertoid).'@'.$hostname.'>';
416 * Removes properties from user record that are not necessary
417 * for sending post notifications.
418 * @param stdClass $user
419 * @return void, $user parameter is modified
421 function forum_cron_minimise_user_record(stdClass $user) {
423 // We store large amount of users in one huge array,
424 // make sure we do not store info there we do not actually need
425 // in mail generation code or messaging.
427 unset($user->institution);
428 unset($user->department);
429 unset($user->address);
430 unset($user->city);
431 unset($user->url);
432 unset($user->currentlogin);
433 unset($user->description);
434 unset($user->descriptionformat);
438 * Function to be run periodically according to the moodle cron
439 * Finds all posts that have yet to be mailed out, and mails them
440 * out to all subscribers
442 * @global object
443 * @global object
444 * @global object
445 * @uses CONTEXT_MODULE
446 * @uses CONTEXT_COURSE
447 * @uses SITEID
448 * @uses FORMAT_PLAIN
449 * @return void
451 function forum_cron() {
452 global $CFG, $USER, $DB;
454 $site = get_site();
456 // All users that are subscribed to any post that needs sending,
457 // please increase $CFG->extramemorylimit on large sites that
458 // send notifications to a large number of users.
459 $users = array();
460 $userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!!
462 // status arrays
463 $mailcount = array();
464 $errorcount = array();
466 // caches
467 $discussions = array();
468 $forums = array();
469 $courses = array();
470 $coursemodules = array();
471 $subscribedusers = array();
474 // Posts older than 2 days will not be mailed. This is to avoid the problem where
475 // cron has not been running for a long time, and then suddenly people are flooded
476 // with mail from the past few weeks or months
477 $timenow = time();
478 $endtime = $timenow - $CFG->maxeditingtime;
479 $starttime = $endtime - 48 * 3600; // Two days earlier
481 // Get the list of forum subscriptions for per-user per-forum maildigest settings.
482 $digestsset = $DB->get_recordset('forum_digests', null, '', 'id, userid, forum, maildigest');
483 $digests = array();
484 foreach ($digestsset as $thisrow) {
485 if (!isset($digests[$thisrow->forum])) {
486 $digests[$thisrow->forum] = array();
488 $digests[$thisrow->forum][$thisrow->userid] = $thisrow->maildigest;
490 $digestsset->close();
492 if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
493 // Mark them all now as being mailed. It's unlikely but possible there
494 // might be an error later so that a post is NOT actually mailed out,
495 // but since mail isn't crucial, we can accept this risk. Doing it now
496 // prevents the risk of duplicated mails, which is a worse problem.
498 if (!forum_mark_old_posts_as_mailed($endtime)) {
499 mtrace('Errors occurred while trying to mark some posts as being mailed.');
500 return false; // Don't continue trying to mail them, in case we are in a cron loop
503 // checking post validity, and adding users to loop through later
504 foreach ($posts as $pid => $post) {
506 $discussionid = $post->discussion;
507 if (!isset($discussions[$discussionid])) {
508 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
509 $discussions[$discussionid] = $discussion;
510 } else {
511 mtrace('Could not find discussion '.$discussionid);
512 unset($posts[$pid]);
513 continue;
516 $forumid = $discussions[$discussionid]->forum;
517 if (!isset($forums[$forumid])) {
518 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
519 $forums[$forumid] = $forum;
520 } else {
521 mtrace('Could not find forum '.$forumid);
522 unset($posts[$pid]);
523 continue;
526 $courseid = $forums[$forumid]->course;
527 if (!isset($courses[$courseid])) {
528 if ($course = $DB->get_record('course', array('id' => $courseid))) {
529 $courses[$courseid] = $course;
530 } else {
531 mtrace('Could not find course '.$courseid);
532 unset($posts[$pid]);
533 continue;
536 if (!isset($coursemodules[$forumid])) {
537 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
538 $coursemodules[$forumid] = $cm;
539 } else {
540 mtrace('Could not find course module for forum '.$forumid);
541 unset($posts[$pid]);
542 continue;
547 // caching subscribed users of each forum
548 if (!isset($subscribedusers[$forumid])) {
549 $modcontext = context_module::instance($coursemodules[$forumid]->id);
550 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
551 foreach ($subusers as $postuser) {
552 // this user is subscribed to this forum
553 $subscribedusers[$forumid][$postuser->id] = $postuser->id;
554 $userscount++;
555 if ($userscount > FORUM_CRON_USER_CACHE) {
556 // Store minimal user info.
557 $minuser = new stdClass();
558 $minuser->id = $postuser->id;
559 $users[$postuser->id] = $minuser;
560 } else {
561 // Cache full user record.
562 forum_cron_minimise_user_record($postuser);
563 $users[$postuser->id] = $postuser;
566 // Release memory.
567 unset($subusers);
568 unset($postuser);
572 $mailcount[$pid] = 0;
573 $errorcount[$pid] = 0;
577 if ($users && $posts) {
579 $urlinfo = parse_url($CFG->wwwroot);
580 $hostname = $urlinfo['host'];
582 foreach ($users as $userto) {
584 core_php_time_limit::raise(120); // terminate if processing of any account takes longer than 2 minutes
586 mtrace('Processing user '.$userto->id);
588 // Init user caches - we keep the cache for one cycle only,
589 // otherwise it could consume too much memory.
590 if (isset($userto->username)) {
591 $userto = clone($userto);
592 } else {
593 $userto = $DB->get_record('user', array('id' => $userto->id));
594 forum_cron_minimise_user_record($userto);
596 $userto->viewfullnames = array();
597 $userto->canpost = array();
598 $userto->markposts = array();
600 // set this so that the capabilities are cached, and environment matches receiving user
601 cron_setup_user($userto);
603 // reset the caches
604 foreach ($coursemodules as $forumid=>$unused) {
605 $coursemodules[$forumid]->cache = new stdClass();
606 $coursemodules[$forumid]->cache->caps = array();
607 unset($coursemodules[$forumid]->uservisible);
610 foreach ($posts as $pid => $post) {
612 // Set up the environment for the post, discussion, forum, course
613 $discussion = $discussions[$post->discussion];
614 $forum = $forums[$discussion->forum];
615 $course = $courses[$forum->course];
616 $cm =& $coursemodules[$forum->id];
618 // Do some checks to see if we can bail out now
619 // Only active enrolled users are in the list of subscribers
620 if (!isset($subscribedusers[$forum->id][$userto->id])) {
621 continue; // user does not subscribe to this forum
624 // Don't send email if the forum is Q&A and the user has not posted
625 // Initial topics are still mailed
626 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
627 mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
628 continue;
631 // Get info about the sending user
632 if (array_key_exists($post->userid, $users)) { // we might know him/her already
633 $userfrom = $users[$post->userid];
634 if (!isset($userfrom->idnumber)) {
635 // Minimalised user info, fetch full record.
636 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
637 forum_cron_minimise_user_record($userfrom);
640 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
641 forum_cron_minimise_user_record($userfrom);
642 // Fetch only once if possible, we can add it to user list, it will be skipped anyway.
643 if ($userscount <= FORUM_CRON_USER_CACHE) {
644 $userscount++;
645 $users[$userfrom->id] = $userfrom;
648 } else {
649 mtrace('Could not find user '.$post->userid);
650 continue;
653 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
655 // setup global $COURSE properly - needed for roles and languages
656 cron_setup_user($userto, $course);
658 // Fill caches
659 if (!isset($userto->viewfullnames[$forum->id])) {
660 $modcontext = context_module::instance($cm->id);
661 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
663 if (!isset($userto->canpost[$discussion->id])) {
664 $modcontext = context_module::instance($cm->id);
665 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
667 if (!isset($userfrom->groups[$forum->id])) {
668 if (!isset($userfrom->groups)) {
669 $userfrom->groups = array();
670 if (isset($users[$userfrom->id])) {
671 $users[$userfrom->id]->groups = array();
674 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
675 if (isset($users[$userfrom->id])) {
676 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
680 // Make sure groups allow this user to see this email
681 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
682 if (!groups_group_exists($discussion->groupid)) { // Can't find group
683 continue; // Be safe and don't send it to anyone
686 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
687 // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
688 continue;
692 // Make sure we're allowed to see it...
693 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
694 mtrace('user '.$userto->id. ' can not see '.$post->id);
695 continue;
698 // OK so we need to send the email.
700 // Does the user want this post in a digest? If so postpone it for now.
701 $maildigest = forum_get_user_maildigest_bulk($digests, $userto, $forum->id);
703 if ($maildigest > 0) {
704 // This user wants the mails to be in digest form
705 $queue = new stdClass();
706 $queue->userid = $userto->id;
707 $queue->discussionid = $discussion->id;
708 $queue->postid = $post->id;
709 $queue->timemodified = $post->created;
710 $DB->insert_record('forum_queue', $queue);
711 continue;
715 // Prepare to actually send the post now, and build up the content
717 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
719 $userfrom->customheaders = array ( // Headers to make emails easier to track
720 'Precedence: Bulk',
721 'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
722 'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
723 'Message-ID: '.forum_get_email_message_id($post->id, $userto->id, $hostname),
724 'X-Course-Id: '.$course->id,
725 'X-Course-Name: '.format_string($course->fullname, true)
728 if ($post->parent) { // This post is a reply, so add headers for threading (see MDL-22551)
729 $userfrom->customheaders[] = 'In-Reply-To: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
730 $userfrom->customheaders[] = 'References: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
733 $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
735 $postsubject = html_to_text("$shortname: ".format_string($post->subject, true));
736 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
737 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
739 // Send the post now!
741 mtrace('Sending ', '');
743 $eventdata = new stdClass();
744 $eventdata->component = 'mod_forum';
745 $eventdata->name = 'posts';
746 $eventdata->userfrom = $userfrom;
747 $eventdata->userto = $userto;
748 $eventdata->subject = $postsubject;
749 $eventdata->fullmessage = $posttext;
750 $eventdata->fullmessageformat = FORMAT_PLAIN;
751 $eventdata->fullmessagehtml = $posthtml;
752 $eventdata->notification = 1;
754 // If forum_replytouser is not set then send mail using the noreplyaddress.
755 if (empty($CFG->forum_replytouser)) {
756 // Clone userfrom as it is referenced by $users.
757 $cloneduserfrom = clone($userfrom);
758 $cloneduserfrom->email = $CFG->noreplyaddress;
759 $eventdata->userfrom = $cloneduserfrom;
762 $smallmessagestrings = new stdClass();
763 $smallmessagestrings->user = fullname($userfrom);
764 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
765 $smallmessagestrings->message = $post->message;
766 //make sure strings are in message recipients language
767 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
769 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
770 $eventdata->contexturlname = $discussion->name;
772 $mailresult = message_send($eventdata);
773 if (!$mailresult){
774 mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
775 " ($userto->email) .. not trying again.");
776 add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
777 substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
778 $errorcount[$post->id]++;
779 } else {
780 $mailcount[$post->id]++;
782 // Mark post as read if forum_usermarksread is set off
783 if (!$CFG->forum_usermarksread) {
784 $userto->markposts[$post->id] = $post->id;
788 mtrace('post '.$post->id. ': '.$post->subject);
791 // mark processed posts as read
792 forum_tp_mark_posts_read($userto, $userto->markposts);
793 unset($userto);
797 if ($posts) {
798 foreach ($posts as $post) {
799 mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
800 if ($errorcount[$post->id]) {
801 $DB->set_field('forum_posts', 'mailed', FORUM_MAILED_ERROR, array('id' => $post->id));
806 // release some memory
807 unset($subscribedusers);
808 unset($mailcount);
809 unset($errorcount);
811 cron_setup_user();
813 $sitetimezone = $CFG->timezone;
815 // Now see if there are any digest mails waiting to be sent, and if we should send them
817 mtrace('Starting digest processing...');
819 core_php_time_limit::raise(300); // terminate if not able to fetch all digests in 5 minutes
821 if (!isset($CFG->digestmailtimelast)) { // To catch the first time
822 set_config('digestmailtimelast', 0);
825 $timenow = time();
826 $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
828 // Delete any really old ones (normally there shouldn't be any)
829 $weekago = $timenow - (7 * 24 * 3600);
830 $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
831 mtrace ('Cleaned old digest records');
833 if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
835 mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
837 $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
839 if ($digestposts_rs->valid()) {
841 // We have work to do
842 $usermailcount = 0;
844 //caches - reuse the those filled before too
845 $discussionposts = array();
846 $userdiscussions = array();
848 foreach ($digestposts_rs as $digestpost) {
849 if (!isset($posts[$digestpost->postid])) {
850 if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
851 $posts[$digestpost->postid] = $post;
852 } else {
853 continue;
856 $discussionid = $digestpost->discussionid;
857 if (!isset($discussions[$discussionid])) {
858 if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
859 $discussions[$discussionid] = $discussion;
860 } else {
861 continue;
864 $forumid = $discussions[$discussionid]->forum;
865 if (!isset($forums[$forumid])) {
866 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
867 $forums[$forumid] = $forum;
868 } else {
869 continue;
873 $courseid = $forums[$forumid]->course;
874 if (!isset($courses[$courseid])) {
875 if ($course = $DB->get_record('course', array('id' => $courseid))) {
876 $courses[$courseid] = $course;
877 } else {
878 continue;
882 if (!isset($coursemodules[$forumid])) {
883 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
884 $coursemodules[$forumid] = $cm;
885 } else {
886 continue;
889 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
890 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
892 $digestposts_rs->close(); /// Finished iteration, let's close the resultset
894 // Data collected, start sending out emails to each user
895 foreach ($userdiscussions as $userid => $thesediscussions) {
897 core_php_time_limit::raise(120); // terminate if processing of any account takes longer than 2 minutes
899 cron_setup_user();
901 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
903 // First of all delete all the queue entries for this user
904 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
906 // Init user caches - we keep the cache for one cycle only,
907 // otherwise it would unnecessarily consume memory.
908 if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
909 $userto = clone($users[$userid]);
910 } else {
911 $userto = $DB->get_record('user', array('id' => $userid));
912 forum_cron_minimise_user_record($userto);
914 $userto->viewfullnames = array();
915 $userto->canpost = array();
916 $userto->markposts = array();
918 // Override the language and timezone of the "current" user, so that
919 // mail is customised for the receiver.
920 cron_setup_user($userto);
922 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
924 $headerdata = new stdClass();
925 $headerdata->sitename = format_string($site->fullname, true);
926 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
928 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
929 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
931 $posthtml = "<head>";
932 /* foreach ($CFG->stylesheets as $stylesheet) {
933 //TODO: MDL-21120
934 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
936 $posthtml .= "</head>\n<body id=\"email\">\n";
937 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
939 foreach ($thesediscussions as $discussionid) {
941 core_php_time_limit::raise(120); // to be reset for each post
943 $discussion = $discussions[$discussionid];
944 $forum = $forums[$discussion->forum];
945 $course = $courses[$forum->course];
946 $cm = $coursemodules[$forum->id];
948 //override language
949 cron_setup_user($userto, $course);
951 // Fill caches
952 if (!isset($userto->viewfullnames[$forum->id])) {
953 $modcontext = context_module::instance($cm->id);
954 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
956 if (!isset($userto->canpost[$discussion->id])) {
957 $modcontext = context_module::instance($cm->id);
958 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
961 $strforums = get_string('forums', 'forum');
962 $canunsubscribe = ! forum_is_forcesubscribed($forum);
963 $canreply = $userto->canpost[$discussion->id];
964 $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
966 $posttext .= "\n \n";
967 $posttext .= '=====================================================================';
968 $posttext .= "\n \n";
969 $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
970 if ($discussion->name != $forum->name) {
971 $posttext .= " -> ".format_string($discussion->name,true);
973 $posttext .= "\n";
974 $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
975 $posttext .= "\n";
977 $posthtml .= "<p><font face=\"sans-serif\">".
978 "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
979 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
980 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
981 if ($discussion->name == $forum->name) {
982 $posthtml .= "</font></p>";
983 } else {
984 $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
986 $posthtml .= '<p>';
988 $postsarray = $discussionposts[$discussionid];
989 sort($postsarray);
991 foreach ($postsarray as $postid) {
992 $post = $posts[$postid];
994 if (array_key_exists($post->userid, $users)) { // we might know him/her already
995 $userfrom = $users[$post->userid];
996 if (!isset($userfrom->idnumber)) {
997 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
998 forum_cron_minimise_user_record($userfrom);
1001 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
1002 forum_cron_minimise_user_record($userfrom);
1003 if ($userscount <= FORUM_CRON_USER_CACHE) {
1004 $userscount++;
1005 $users[$userfrom->id] = $userfrom;
1008 } else {
1009 mtrace('Could not find user '.$post->userid);
1010 continue;
1013 if (!isset($userfrom->groups[$forum->id])) {
1014 if (!isset($userfrom->groups)) {
1015 $userfrom->groups = array();
1016 if (isset($users[$userfrom->id])) {
1017 $users[$userfrom->id]->groups = array();
1020 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
1021 if (isset($users[$userfrom->id])) {
1022 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
1026 $userfrom->customheaders = array ("Precedence: Bulk");
1028 $maildigest = forum_get_user_maildigest_bulk($digests, $userto, $forum->id);
1029 if ($maildigest == 2) {
1030 // Subjects and link only
1031 $posttext .= "\n";
1032 $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
1033 $by = new stdClass();
1034 $by->name = fullname($userfrom);
1035 $by->date = userdate($post->modified);
1036 $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
1037 $posttext .= "\n---------------------------------------------------------------------";
1039 $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
1040 $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>';
1042 } else {
1043 // The full treatment
1044 $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
1045 $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1047 // Create an array of postid's for this user to mark as read.
1048 if (!$CFG->forum_usermarksread) {
1049 $userto->markposts[$post->id] = $post->id;
1053 $footerlinks = array();
1054 if ($canunsubscribe) {
1055 $footerlinks[] = "<a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">" . get_string("unsubscribe", "forum") . "</a>";
1056 } else {
1057 $footerlinks[] = get_string("everyoneissubscribed", "forum");
1059 $footerlinks[] = "<a href='{$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}'>" . get_string("digestmailpost", "forum") . '</a>';
1060 $posthtml .= "\n<div class='mdl-right'><font size=\"1\">" . implode('&nbsp;', $footerlinks) . '</font></div>';
1061 $posthtml .= '<hr size="1" noshade="noshade" /></p>';
1063 $posthtml .= '</body>';
1065 if (empty($userto->mailformat) || $userto->mailformat != 1) {
1066 // This user DOESN'T want to receive HTML
1067 $posthtml = '';
1070 $attachment = $attachname='';
1071 // Directly email forum digests rather than sending them via messaging, use the
1072 // site shortname as 'from name', the noreply address will be used by email_to_user.
1073 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname);
1075 if (!$mailresult) {
1076 mtrace("ERROR!");
1077 echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
1078 add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
1079 } else {
1080 mtrace("success.");
1081 $usermailcount++;
1083 // Mark post as read if forum_usermarksread is set off
1084 forum_tp_mark_posts_read($userto, $userto->markposts);
1088 /// We have finishied all digest emails, update $CFG->digestmailtimelast
1089 set_config('digestmailtimelast', $timenow);
1092 cron_setup_user();
1094 if (!empty($usermailcount)) {
1095 mtrace(get_string('digestsentusers', 'forum', $usermailcount));
1098 if (!empty($CFG->forum_lastreadclean)) {
1099 $timenow = time();
1100 if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
1101 set_config('forum_lastreadclean', $timenow);
1102 mtrace('Removing old forum read tracking info...');
1103 forum_tp_clean_read_records();
1105 } else {
1106 set_config('forum_lastreadclean', time());
1110 return true;
1114 * Builds and returns the body of the email notification in plain text.
1116 * @global object
1117 * @global object
1118 * @uses CONTEXT_MODULE
1119 * @param object $course
1120 * @param object $cm
1121 * @param object $forum
1122 * @param object $discussion
1123 * @param object $post
1124 * @param object $userfrom
1125 * @param object $userto
1126 * @param boolean $bare
1127 * @return string The email body in plain text format.
1129 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
1130 global $CFG, $USER;
1132 $modcontext = context_module::instance($cm->id);
1134 if (!isset($userto->viewfullnames[$forum->id])) {
1135 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1136 } else {
1137 $viewfullnames = $userto->viewfullnames[$forum->id];
1140 if (!isset($userto->canpost[$discussion->id])) {
1141 $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1142 } else {
1143 $canreply = $userto->canpost[$discussion->id];
1146 $by = New stdClass;
1147 $by->name = fullname($userfrom, $viewfullnames);
1148 $by->date = userdate($post->modified, "", $userto->timezone);
1150 $strbynameondate = get_string('bynameondate', 'forum', $by);
1152 $strforums = get_string('forums', 'forum');
1154 $canunsubscribe = ! forum_is_forcesubscribed($forum);
1156 $posttext = '';
1158 if (!$bare) {
1159 $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1160 $posttext = "$shortname -> $strforums -> ".format_string($forum->name,true);
1162 if ($discussion->name != $forum->name) {
1163 $posttext .= " -> ".format_string($discussion->name,true);
1167 // add absolute file links
1168 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1170 $posttext .= "\n";
1171 $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
1172 $posttext .= "\n---------------------------------------------------------------------\n";
1173 $posttext .= format_string($post->subject,true);
1174 if ($bare) {
1175 $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1177 $posttext .= "\n".$strbynameondate."\n";
1178 $posttext .= "---------------------------------------------------------------------\n";
1179 $posttext .= format_text_email($post->message, $post->messageformat);
1180 $posttext .= "\n\n";
1181 $posttext .= forum_print_attachments($post, $cm, "text");
1183 if (!$bare && $canreply) {
1184 $posttext .= "---------------------------------------------------------------------\n";
1185 $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1186 $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1188 if (!$bare && $canunsubscribe) {
1189 $posttext .= "\n---------------------------------------------------------------------\n";
1190 $posttext .= get_string("unsubscribe", "forum");
1191 $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1194 $posttext .= "\n---------------------------------------------------------------------\n";
1195 $posttext .= get_string("digestmailpost", "forum");
1196 $posttext .= ": {$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}\n";
1198 return $posttext;
1202 * Builds and returns the body of the email notification in html format.
1204 * @global object
1205 * @param object $course
1206 * @param object $cm
1207 * @param object $forum
1208 * @param object $discussion
1209 * @param object $post
1210 * @param object $userfrom
1211 * @param object $userto
1212 * @return string The email text in HTML format
1214 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1215 global $CFG;
1217 if ($userto->mailformat != 1) { // Needs to be HTML
1218 return '';
1221 if (!isset($userto->canpost[$discussion->id])) {
1222 $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1223 } else {
1224 $canreply = $userto->canpost[$discussion->id];
1227 $strforums = get_string('forums', 'forum');
1228 $canunsubscribe = ! forum_is_forcesubscribed($forum);
1229 $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1231 $posthtml = '<head>';
1232 /* foreach ($CFG->stylesheets as $stylesheet) {
1233 //TODO: MDL-21120
1234 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1236 $posthtml .= '</head>';
1237 $posthtml .= "\n<body id=\"email\">\n\n";
1239 $posthtml .= '<div class="navbar">'.
1240 '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1241 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1242 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1243 if ($discussion->name == $forum->name) {
1244 $posthtml .= '</div>';
1245 } else {
1246 $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1247 format_string($discussion->name,true).'</a></div>';
1249 $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1251 $footerlinks = array();
1252 if ($canunsubscribe) {
1253 $footerlinks[] = '<a href="' . $CFG->wwwroot . '/mod/forum/subscribe.php?id=' . $forum->id . '">' . get_string('unsubscribe', 'forum') . '</a>';
1254 $footerlinks[] = '<a href="' . $CFG->wwwroot . '/mod/forum/unsubscribeall.php">' . get_string('unsubscribeall', 'forum') . '</a>';
1256 $footerlinks[] = "<a href='{$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}'>" . get_string('digestmailpost', 'forum') . '</a>';
1257 $posthtml .= '<hr /><div class="mdl-align unsubscribelink">' . implode('&nbsp;', $footerlinks) . '</div>';
1259 $posthtml .= '</body>';
1261 return $posthtml;
1267 * @param object $course
1268 * @param object $user
1269 * @param object $mod TODO this is not used in this function, refactor
1270 * @param object $forum
1271 * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1273 function forum_user_outline($course, $user, $mod, $forum) {
1274 global $CFG;
1275 require_once("$CFG->libdir/gradelib.php");
1276 $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1277 if (empty($grades->items[0]->grades)) {
1278 $grade = false;
1279 } else {
1280 $grade = reset($grades->items[0]->grades);
1283 $count = forum_count_user_posts($forum->id, $user->id);
1285 if ($count && $count->postcount > 0) {
1286 $result = new stdClass();
1287 $result->info = get_string("numposts", "forum", $count->postcount);
1288 $result->time = $count->lastpost;
1289 if ($grade) {
1290 $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1292 return $result;
1293 } else if ($grade) {
1294 $result = new stdClass();
1295 $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1297 //datesubmitted == time created. dategraded == time modified or time overridden
1298 //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1299 //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1300 if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1301 $result->time = $grade->dategraded;
1302 } else {
1303 $result->time = $grade->datesubmitted;
1306 return $result;
1308 return NULL;
1313 * @global object
1314 * @global object
1315 * @param object $coure
1316 * @param object $user
1317 * @param object $mod
1318 * @param object $forum
1320 function forum_user_complete($course, $user, $mod, $forum) {
1321 global $CFG,$USER, $OUTPUT;
1322 require_once("$CFG->libdir/gradelib.php");
1324 $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1325 if (!empty($grades->items[0]->grades)) {
1326 $grade = reset($grades->items[0]->grades);
1327 echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1328 if ($grade->str_feedback) {
1329 echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1333 if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1335 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1336 print_error('invalidcoursemodule');
1338 $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1340 foreach ($posts as $post) {
1341 if (!isset($discussions[$post->discussion])) {
1342 continue;
1344 $discussion = $discussions[$post->discussion];
1346 forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1348 } else {
1349 echo "<p>".get_string("noposts", "forum")."</p>";
1359 * @global object
1360 * @global object
1361 * @global object
1362 * @param array $courses
1363 * @param array $htmlarray
1365 function forum_print_overview($courses,&$htmlarray) {
1366 global $USER, $CFG, $DB, $SESSION;
1368 if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1369 return array();
1372 if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1373 return;
1376 // Courses to search for new posts
1377 $coursessqls = array();
1378 $params = array();
1379 foreach ($courses as $course) {
1381 // If the user has never entered into the course all posts are pending
1382 if ($course->lastaccess == 0) {
1383 $coursessqls[] = '(f.course = ?)';
1384 $params[] = $course->id;
1386 // Only posts created after the course last access
1387 } else {
1388 $coursessqls[] = '(f.course = ? AND p.created > ?)';
1389 $params[] = $course->id;
1390 $params[] = $course->lastaccess;
1393 $params[] = $USER->id;
1394 $coursessql = implode(' OR ', $coursessqls);
1396 $sql = "SELECT f.id, COUNT(*) as count "
1397 .'FROM {forum} f '
1398 .'JOIN {forum_discussions} d ON d.forum = f.id '
1399 .'JOIN {forum_posts} p ON p.discussion = d.id '
1400 ."WHERE ($coursessql) "
1401 .'AND p.userid != ? '
1402 .'GROUP BY f.id';
1404 if (!$new = $DB->get_records_sql($sql, $params)) {
1405 $new = array(); // avoid warnings
1408 // also get all forum tracking stuff ONCE.
1409 $trackingforums = array();
1410 foreach ($forums as $forum) {
1411 if (forum_tp_can_track_forums($forum)) {
1412 $trackingforums[$forum->id] = $forum;
1416 if (count($trackingforums) > 0) {
1417 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1418 $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1419 ' FROM {forum_posts} p '.
1420 ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1421 ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1422 $params = array($USER->id);
1424 foreach ($trackingforums as $track) {
1425 $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1426 $params[] = $track->id;
1427 if (isset($SESSION->currentgroup[$track->course])) {
1428 $groupid = $SESSION->currentgroup[$track->course];
1429 } else {
1430 // get first groupid
1431 $groupids = groups_get_all_groups($track->course, $USER->id);
1432 if ($groupids) {
1433 reset($groupids);
1434 $groupid = key($groupids);
1435 $SESSION->currentgroup[$track->course] = $groupid;
1436 } else {
1437 $groupid = 0;
1439 unset($groupids);
1441 $params[] = $groupid;
1443 $sql = substr($sql,0,-3); // take off the last OR
1444 $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1445 $params[] = $cutoffdate;
1447 if (!$unread = $DB->get_records_sql($sql, $params)) {
1448 $unread = array();
1450 } else {
1451 $unread = array();
1454 if (empty($unread) and empty($new)) {
1455 return;
1458 $strforum = get_string('modulename','forum');
1460 foreach ($forums as $forum) {
1461 $str = '';
1462 $count = 0;
1463 $thisunread = 0;
1464 $showunread = false;
1465 // either we have something from logs, or trackposts, or nothing.
1466 if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1467 $count = $new[$forum->id]->count;
1469 if (array_key_exists($forum->id,$unread)) {
1470 $thisunread = $unread[$forum->id]->count;
1471 $showunread = true;
1473 if ($count > 0 || $thisunread > 0) {
1474 $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1475 $forum->name.'</a></div>';
1476 $str .= '<div class="info"><span class="postsincelogin">';
1477 $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
1478 if (!empty($showunread)) {
1479 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
1481 $str .= '</div></div>';
1483 if (!empty($str)) {
1484 if (!array_key_exists($forum->course,$htmlarray)) {
1485 $htmlarray[$forum->course] = array();
1487 if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1488 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1490 $htmlarray[$forum->course]['forum'] .= $str;
1496 * Given a course and a date, prints a summary of all the new
1497 * messages posted in the course since that date
1499 * @global object
1500 * @global object
1501 * @global object
1502 * @uses CONTEXT_MODULE
1503 * @uses VISIBLEGROUPS
1504 * @param object $course
1505 * @param bool $viewfullnames capability
1506 * @param int $timestart
1507 * @return bool success
1509 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1510 global $CFG, $USER, $DB, $OUTPUT;
1512 // do not use log table if possible, it may be huge and is expensive to join with other tables
1514 $allnamefields = user_picture::fields('u', null, 'duserid');
1515 if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1516 d.timestart, d.timeend, $allnamefields
1517 FROM {forum_posts} p
1518 JOIN {forum_discussions} d ON d.id = p.discussion
1519 JOIN {forum} f ON f.id = d.forum
1520 JOIN {user} u ON u.id = p.userid
1521 WHERE p.created > ? AND f.course = ?
1522 ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1523 return false;
1526 $modinfo = get_fast_modinfo($course);
1528 $groupmodes = array();
1529 $cms = array();
1531 $strftimerecent = get_string('strftimerecent');
1533 $printposts = array();
1534 foreach ($posts as $post) {
1535 if (!isset($modinfo->instances['forum'][$post->forum])) {
1536 // not visible
1537 continue;
1539 $cm = $modinfo->instances['forum'][$post->forum];
1540 if (!$cm->uservisible) {
1541 continue;
1543 $context = context_module::instance($cm->id);
1545 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1546 continue;
1549 if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1550 and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1551 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1552 continue;
1556 $groupmode = groups_get_activity_groupmode($cm, $course);
1558 if ($groupmode) {
1559 if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1560 // oki (Open discussions have groupid -1)
1561 } else {
1562 // separate mode
1563 if (isguestuser()) {
1564 // shortcut
1565 continue;
1568 if (!in_array($post->groupid, $modinfo->get_groups($cm->groupingid))) {
1569 continue;
1574 $printposts[] = $post;
1576 unset($posts);
1578 if (!$printposts) {
1579 return false;
1582 echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1583 echo "\n<ul class='unlist'>\n";
1585 foreach ($printposts as $post) {
1586 $subjectclass = empty($post->parent) ? ' bold' : '';
1588 echo '<li><div class="head">'.
1589 '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1590 '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1591 '</div>';
1592 echo '<div class="info'.$subjectclass.'">';
1593 if (empty($post->parent)) {
1594 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1595 } else {
1596 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1598 $post->subject = break_up_long_words(format_string($post->subject, true));
1599 echo $post->subject;
1600 echo "</a>\"</div></li>\n";
1603 echo "</ul>\n";
1605 return true;
1609 * Return grade for given user or all users.
1611 * @global object
1612 * @global object
1613 * @param object $forum
1614 * @param int $userid optional user id, 0 means all users
1615 * @return array array of grades, false if none
1617 function forum_get_user_grades($forum, $userid = 0) {
1618 global $CFG;
1620 require_once($CFG->dirroot.'/rating/lib.php');
1622 $ratingoptions = new stdClass;
1623 $ratingoptions->component = 'mod_forum';
1624 $ratingoptions->ratingarea = 'post';
1626 //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1627 $ratingoptions->modulename = 'forum';
1628 $ratingoptions->moduleid = $forum->id;
1629 $ratingoptions->userid = $userid;
1630 $ratingoptions->aggregationmethod = $forum->assessed;
1631 $ratingoptions->scaleid = $forum->scale;
1632 $ratingoptions->itemtable = 'forum_posts';
1633 $ratingoptions->itemtableusercolumn = 'userid';
1635 $rm = new rating_manager();
1636 return $rm->get_user_grades($ratingoptions);
1640 * Update activity grades
1642 * @category grade
1643 * @param object $forum
1644 * @param int $userid specific user only, 0 means all
1645 * @param boolean $nullifnone return null if grade does not exist
1646 * @return void
1648 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1649 global $CFG, $DB;
1650 require_once($CFG->libdir.'/gradelib.php');
1652 if (!$forum->assessed) {
1653 forum_grade_item_update($forum);
1655 } else if ($grades = forum_get_user_grades($forum, $userid)) {
1656 forum_grade_item_update($forum, $grades);
1658 } else if ($userid and $nullifnone) {
1659 $grade = new stdClass();
1660 $grade->userid = $userid;
1661 $grade->rawgrade = NULL;
1662 forum_grade_item_update($forum, $grade);
1664 } else {
1665 forum_grade_item_update($forum);
1670 * Update all grades in gradebook.
1671 * @global object
1673 function forum_upgrade_grades() {
1674 global $DB;
1676 $sql = "SELECT COUNT('x')
1677 FROM {forum} f, {course_modules} cm, {modules} m
1678 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1679 $count = $DB->count_records_sql($sql);
1681 $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1682 FROM {forum} f, {course_modules} cm, {modules} m
1683 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1684 $rs = $DB->get_recordset_sql($sql);
1685 if ($rs->valid()) {
1686 $pbar = new progress_bar('forumupgradegrades', 500, true);
1687 $i=0;
1688 foreach ($rs as $forum) {
1689 $i++;
1690 upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1691 forum_update_grades($forum, 0, false);
1692 $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1695 $rs->close();
1699 * Create/update grade item for given forum
1701 * @category grade
1702 * @uses GRADE_TYPE_NONE
1703 * @uses GRADE_TYPE_VALUE
1704 * @uses GRADE_TYPE_SCALE
1705 * @param stdClass $forum Forum object with extra cmidnumber
1706 * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1707 * @return int 0 if ok
1709 function forum_grade_item_update($forum, $grades=NULL) {
1710 global $CFG;
1711 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1712 require_once($CFG->libdir.'/gradelib.php');
1715 $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1717 if (!$forum->assessed or $forum->scale == 0) {
1718 $params['gradetype'] = GRADE_TYPE_NONE;
1720 } else if ($forum->scale > 0) {
1721 $params['gradetype'] = GRADE_TYPE_VALUE;
1722 $params['grademax'] = $forum->scale;
1723 $params['grademin'] = 0;
1725 } else if ($forum->scale < 0) {
1726 $params['gradetype'] = GRADE_TYPE_SCALE;
1727 $params['scaleid'] = -$forum->scale;
1730 if ($grades === 'reset') {
1731 $params['reset'] = true;
1732 $grades = NULL;
1735 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1739 * Delete grade item for given forum
1741 * @category grade
1742 * @param stdClass $forum Forum object
1743 * @return grade_item
1745 function forum_grade_item_delete($forum) {
1746 global $CFG;
1747 require_once($CFG->libdir.'/gradelib.php');
1749 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1754 * This function returns if a scale is being used by one forum
1756 * @global object
1757 * @param int $forumid
1758 * @param int $scaleid negative number
1759 * @return bool
1761 function forum_scale_used ($forumid,$scaleid) {
1762 global $DB;
1763 $return = false;
1765 $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1767 if (!empty($rec) && !empty($scaleid)) {
1768 $return = true;
1771 return $return;
1775 * Checks if scale is being used by any instance of forum
1777 * This is used to find out if scale used anywhere
1779 * @global object
1780 * @param $scaleid int
1781 * @return boolean True if the scale is used by any forum
1783 function forum_scale_used_anywhere($scaleid) {
1784 global $DB;
1785 if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1786 return true;
1787 } else {
1788 return false;
1792 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1795 * Gets a post with all info ready for forum_print_post
1796 * Most of these joins are just to get the forum id
1798 * @global object
1799 * @global object
1800 * @param int $postid
1801 * @return mixed array of posts or false
1803 function forum_get_post_full($postid) {
1804 global $CFG, $DB;
1806 $allnames = get_all_user_name_fields(true, 'u');
1807 return $DB->get_record_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
1808 FROM {forum_posts} p
1809 JOIN {forum_discussions} d ON p.discussion = d.id
1810 LEFT JOIN {user} u ON p.userid = u.id
1811 WHERE p.id = ?", array($postid));
1815 * Gets posts with all info ready for forum_print_post
1816 * We pass forumid in because we always know it so no need to make a
1817 * complicated join to find it out.
1819 * @global object
1820 * @global object
1821 * @return mixed array of posts or false
1823 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1824 global $CFG, $DB;
1826 $allnames = get_all_user_name_fields(true, 'u');
1827 return $DB->get_records_sql("SELECT p.*, $forumid AS forum, $allnames, u.email, u.picture, u.imagealt
1828 FROM {forum_posts} p
1829 LEFT JOIN {user} u ON p.userid = u.id
1830 WHERE p.discussion = ?
1831 AND p.parent > 0 $sort", array($discussion));
1835 * Gets all posts in discussion including top parent.
1837 * @global object
1838 * @global object
1839 * @global object
1840 * @param int $discussionid
1841 * @param string $sort
1842 * @param bool $tracking does user track the forum?
1843 * @return array of posts
1845 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1846 global $CFG, $DB, $USER;
1848 $tr_sel = "";
1849 $tr_join = "";
1850 $params = array();
1852 if ($tracking) {
1853 $now = time();
1854 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1855 $tr_sel = ", fr.id AS postread";
1856 $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1857 $params[] = $USER->id;
1860 $allnames = get_all_user_name_fields(true, 'u');
1861 $params[] = $discussionid;
1862 if (!$posts = $DB->get_records_sql("SELECT p.*, $allnames, u.email, u.picture, u.imagealt $tr_sel
1863 FROM {forum_posts} p
1864 LEFT JOIN {user} u ON p.userid = u.id
1865 $tr_join
1866 WHERE p.discussion = ?
1867 ORDER BY $sort", $params)) {
1868 return array();
1871 foreach ($posts as $pid=>$p) {
1872 if ($tracking) {
1873 if (forum_tp_is_post_old($p)) {
1874 $posts[$pid]->postread = true;
1877 if (!$p->parent) {
1878 continue;
1880 if (!isset($posts[$p->parent])) {
1881 continue; // parent does not exist??
1883 if (!isset($posts[$p->parent]->children)) {
1884 $posts[$p->parent]->children = array();
1886 $posts[$p->parent]->children[$pid] =& $posts[$pid];
1889 return $posts;
1893 * Gets posts with all info ready for forum_print_post
1894 * We pass forumid in because we always know it so no need to make a
1895 * complicated join to find it out.
1897 * @global object
1898 * @global object
1899 * @param int $parent
1900 * @param int $forumid
1901 * @return array
1903 function forum_get_child_posts($parent, $forumid) {
1904 global $CFG, $DB;
1906 $allnames = get_all_user_name_fields(true, 'u');
1907 return $DB->get_records_sql("SELECT p.*, $forumid AS forum, $allnames, u.email, u.picture, u.imagealt
1908 FROM {forum_posts} p
1909 LEFT JOIN {user} u ON p.userid = u.id
1910 WHERE p.parent = ?
1911 ORDER BY p.created ASC", array($parent));
1915 * An array of forum objects that the user is allowed to read/search through.
1917 * @global object
1918 * @global object
1919 * @global object
1920 * @param int $userid
1921 * @param int $courseid if 0, we look for forums throughout the whole site.
1922 * @return array of forum objects, or false if no matches
1923 * Forum objects have the following attributes:
1924 * id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1925 * viewhiddentimedposts
1927 function forum_get_readable_forums($userid, $courseid=0) {
1929 global $CFG, $DB, $USER;
1930 require_once($CFG->dirroot.'/course/lib.php');
1932 if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1933 print_error('notinstalled', 'forum');
1936 if ($courseid) {
1937 $courses = $DB->get_records('course', array('id' => $courseid));
1938 } else {
1939 // If no course is specified, then the user can see SITE + his courses.
1940 $courses1 = $DB->get_records('course', array('id' => SITEID));
1941 $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1942 $courses = array_merge($courses1, $courses2);
1944 if (!$courses) {
1945 return array();
1948 $readableforums = array();
1950 foreach ($courses as $course) {
1952 $modinfo = get_fast_modinfo($course);
1954 if (empty($modinfo->instances['forum'])) {
1955 // hmm, no forums?
1956 continue;
1959 $courseforums = $DB->get_records('forum', array('course' => $course->id));
1961 foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1962 if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1963 continue;
1965 $context = context_module::instance($cm->id);
1966 $forum = $courseforums[$forumid];
1967 $forum->context = $context;
1968 $forum->cm = $cm;
1970 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1971 continue;
1974 /// group access
1975 if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1977 $forum->onlygroups = $modinfo->get_groups($cm->groupingid);
1978 $forum->onlygroups[] = -1;
1981 /// hidden timed discussions
1982 $forum->viewhiddentimedposts = true;
1983 if (!empty($CFG->forum_enabletimedposts)) {
1984 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1985 $forum->viewhiddentimedposts = false;
1989 /// qanda access
1990 if ($forum->type == 'qanda'
1991 && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1993 // We need to check whether the user has posted in the qanda forum.
1994 $forum->onlydiscussions = array(); // Holds discussion ids for the discussions
1995 // the user is allowed to see in this forum.
1996 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1997 foreach ($discussionspostedin as $d) {
1998 $forum->onlydiscussions[] = $d->id;
2003 $readableforums[$forum->id] = $forum;
2006 unset($modinfo);
2008 } // End foreach $courses
2010 return $readableforums;
2014 * Returns a list of posts found using an array of search terms.
2016 * @global object
2017 * @global object
2018 * @global object
2019 * @param array $searchterms array of search terms, e.g. word +word -word
2020 * @param int $courseid if 0, we search through the whole site
2021 * @param int $limitfrom
2022 * @param int $limitnum
2023 * @param int &$totalcount
2024 * @param string $extrasql
2025 * @return array|bool Array of posts found or false
2027 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
2028 &$totalcount, $extrasql='') {
2029 global $CFG, $DB, $USER;
2030 require_once($CFG->libdir.'/searchlib.php');
2032 $forums = forum_get_readable_forums($USER->id, $courseid);
2034 if (count($forums) == 0) {
2035 $totalcount = 0;
2036 return false;
2039 $now = round(time(), -2); // db friendly
2041 $fullaccess = array();
2042 $where = array();
2043 $params = array();
2045 foreach ($forums as $forumid => $forum) {
2046 $select = array();
2048 if (!$forum->viewhiddentimedposts) {
2049 $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
2050 $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
2053 $cm = $forum->cm;
2054 $context = $forum->context;
2056 if ($forum->type == 'qanda'
2057 && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2058 if (!empty($forum->onlydiscussions)) {
2059 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
2060 $params = array_merge($params, $discussionid_params);
2061 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
2062 } else {
2063 $select[] = "p.parent = 0";
2067 if (!empty($forum->onlygroups)) {
2068 list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
2069 $params = array_merge($params, $groupid_params);
2070 $select[] = "d.groupid $groupid_sql";
2073 if ($select) {
2074 $selects = implode(" AND ", $select);
2075 $where[] = "(d.forum = :forum{$forumid} AND $selects)";
2076 $params['forum'.$forumid] = $forumid;
2077 } else {
2078 $fullaccess[] = $forumid;
2082 if ($fullaccess) {
2083 list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
2084 $params = array_merge($params, $fullid_params);
2085 $where[] = "(d.forum $fullid_sql)";
2088 $selectdiscussion = "(".implode(" OR ", $where).")";
2090 $messagesearch = '';
2091 $searchstring = '';
2093 // Need to concat these back together for parser to work.
2094 foreach($searchterms as $searchterm){
2095 if ($searchstring != '') {
2096 $searchstring .= ' ';
2098 $searchstring .= $searchterm;
2101 // We need to allow quoted strings for the search. The quotes *should* be stripped
2102 // by the parser, but this should be examined carefully for security implications.
2103 $searchstring = str_replace("\\\"","\"",$searchstring);
2104 $parser = new search_parser();
2105 $lexer = new search_lexer($parser);
2107 if ($lexer->parse($searchstring)) {
2108 $parsearray = $parser->get_parsed_array();
2109 // Experimental feature under 1.8! MDL-8830
2110 // Use alternative text searches if defined
2111 // This feature only works under mysql until properly implemented for other DBs
2112 // Requires manual creation of text index for forum_posts before enabling it:
2113 // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2114 // Experimental feature under 1.8! MDL-8830
2115 if (!empty($CFG->forum_usetextsearches)) {
2116 list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2117 'p.userid', 'u.id', 'u.firstname',
2118 'u.lastname', 'p.modified', 'd.forum');
2119 } else {
2120 list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2121 'p.userid', 'u.id', 'u.firstname',
2122 'u.lastname', 'p.modified', 'd.forum');
2124 $params = array_merge($params, $msparams);
2127 $fromsql = "{forum_posts} p,
2128 {forum_discussions} d,
2129 {user} u";
2131 $selectsql = " $messagesearch
2132 AND p.discussion = d.id
2133 AND p.userid = u.id
2134 AND $selectdiscussion
2135 $extrasql";
2137 $countsql = "SELECT COUNT(*)
2138 FROM $fromsql
2139 WHERE $selectsql";
2141 $allnames = get_all_user_name_fields(true, 'u');
2142 $searchsql = "SELECT p.*,
2143 d.forum,
2144 $allnames,
2145 u.email,
2146 u.picture,
2147 u.imagealt
2148 FROM $fromsql
2149 WHERE $selectsql
2150 ORDER BY p.modified DESC";
2152 $totalcount = $DB->count_records_sql($countsql, $params);
2154 return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2158 * Returns a list of ratings for a particular post - sorted.
2160 * TODO: Check if this function is actually used anywhere.
2161 * Up until the fix for MDL-27471 this function wasn't even returning.
2163 * @param stdClass $context
2164 * @param int $postid
2165 * @param string $sort
2166 * @return array Array of ratings or false
2168 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2169 $options = new stdClass;
2170 $options->context = $context;
2171 $options->component = 'mod_forum';
2172 $options->ratingarea = 'post';
2173 $options->itemid = $postid;
2174 $options->sort = "ORDER BY $sort";
2176 $rm = new rating_manager();
2177 return $rm->get_all_ratings_for_item($options);
2181 * Returns a list of all new posts that have not been mailed yet
2183 * @param int $starttime posts created after this time
2184 * @param int $endtime posts created before this
2185 * @param int $now used for timed discussions only
2186 * @return array
2188 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2189 global $CFG, $DB;
2191 $params = array();
2192 $params['mailed'] = FORUM_MAILED_PENDING;
2193 $params['ptimestart'] = $starttime;
2194 $params['ptimeend'] = $endtime;
2195 $params['mailnow'] = 1;
2197 if (!empty($CFG->forum_enabletimedposts)) {
2198 if (empty($now)) {
2199 $now = time();
2201 $timedsql = "AND (d.timestart < :dtimestart AND (d.timeend = 0 OR d.timeend > :dtimeend))";
2202 $params['dtimestart'] = $now;
2203 $params['dtimeend'] = $now;
2204 } else {
2205 $timedsql = "";
2208 return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2209 FROM {forum_posts} p
2210 JOIN {forum_discussions} d ON d.id = p.discussion
2211 WHERE p.mailed = :mailed
2212 AND p.created >= :ptimestart
2213 AND (p.created < :ptimeend OR p.mailnow = :mailnow)
2214 $timedsql
2215 ORDER BY p.modified ASC", $params);
2219 * Marks posts before a certain time as being mailed already
2221 * @global object
2222 * @global object
2223 * @param int $endtime
2224 * @param int $now Defaults to time()
2225 * @return bool
2227 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2228 global $CFG, $DB;
2230 if (empty($now)) {
2231 $now = time();
2234 $params = array();
2235 $params['mailedsuccess'] = FORUM_MAILED_SUCCESS;
2236 $params['now'] = $now;
2237 $params['endtime'] = $endtime;
2238 $params['mailnow'] = 1;
2239 $params['mailedpending'] = FORUM_MAILED_PENDING;
2241 if (empty($CFG->forum_enabletimedposts)) {
2242 return $DB->execute("UPDATE {forum_posts}
2243 SET mailed = :mailedsuccess
2244 WHERE (created < :endtime OR mailnow = :mailnow)
2245 AND mailed = :mailedpending", $params);
2246 } else {
2247 return $DB->execute("UPDATE {forum_posts}
2248 SET mailed = :mailedsuccess
2249 WHERE discussion NOT IN (SELECT d.id
2250 FROM {forum_discussions} d
2251 WHERE d.timestart > :now)
2252 AND (created < :endtime OR mailnow = :mailnow)
2253 AND mailed = :mailedpending", $params);
2258 * Get all the posts for a user in a forum suitable for forum_print_post
2260 * @global object
2261 * @global object
2262 * @uses CONTEXT_MODULE
2263 * @return array
2265 function forum_get_user_posts($forumid, $userid) {
2266 global $CFG, $DB;
2268 $timedsql = "";
2269 $params = array($forumid, $userid);
2271 if (!empty($CFG->forum_enabletimedposts)) {
2272 $cm = get_coursemodule_from_instance('forum', $forumid);
2273 if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2274 $now = time();
2275 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2276 $params[] = $now;
2277 $params[] = $now;
2281 $allnames = get_all_user_name_fields(true, 'u');
2282 return $DB->get_records_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
2283 FROM {forum} f
2284 JOIN {forum_discussions} d ON d.forum = f.id
2285 JOIN {forum_posts} p ON p.discussion = d.id
2286 JOIN {user} u ON u.id = p.userid
2287 WHERE f.id = ?
2288 AND p.userid = ?
2289 $timedsql
2290 ORDER BY p.modified ASC", $params);
2294 * Get all the discussions user participated in
2296 * @global object
2297 * @global object
2298 * @uses CONTEXT_MODULE
2299 * @param int $forumid
2300 * @param int $userid
2301 * @return array Array or false
2303 function forum_get_user_involved_discussions($forumid, $userid) {
2304 global $CFG, $DB;
2306 $timedsql = "";
2307 $params = array($forumid, $userid);
2308 if (!empty($CFG->forum_enabletimedposts)) {
2309 $cm = get_coursemodule_from_instance('forum', $forumid);
2310 if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2311 $now = time();
2312 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2313 $params[] = $now;
2314 $params[] = $now;
2318 return $DB->get_records_sql("SELECT DISTINCT d.*
2319 FROM {forum} f
2320 JOIN {forum_discussions} d ON d.forum = f.id
2321 JOIN {forum_posts} p ON p.discussion = d.id
2322 WHERE f.id = ?
2323 AND p.userid = ?
2324 $timedsql", $params);
2328 * Get all the posts for a user in a forum suitable for forum_print_post
2330 * @global object
2331 * @global object
2332 * @param int $forumid
2333 * @param int $userid
2334 * @return array of counts or false
2336 function forum_count_user_posts($forumid, $userid) {
2337 global $CFG, $DB;
2339 $timedsql = "";
2340 $params = array($forumid, $userid);
2341 if (!empty($CFG->forum_enabletimedposts)) {
2342 $cm = get_coursemodule_from_instance('forum', $forumid);
2343 if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2344 $now = time();
2345 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2346 $params[] = $now;
2347 $params[] = $now;
2351 return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2352 FROM {forum} f
2353 JOIN {forum_discussions} d ON d.forum = f.id
2354 JOIN {forum_posts} p ON p.discussion = d.id
2355 JOIN {user} u ON u.id = p.userid
2356 WHERE f.id = ?
2357 AND p.userid = ?
2358 $timedsql", $params);
2362 * Given a log entry, return the forum post details for it.
2364 * @global object
2365 * @global object
2366 * @param object $log
2367 * @return array|null
2369 function forum_get_post_from_log($log) {
2370 global $CFG, $DB;
2372 $allnames = get_all_user_name_fields(true, 'u');
2373 if ($log->action == "add post") {
2375 return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
2376 FROM {forum_discussions} d,
2377 {forum_posts} p,
2378 {forum} f,
2379 {user} u
2380 WHERE p.id = ?
2381 AND d.id = p.discussion
2382 AND p.userid = u.id
2383 AND u.deleted <> '1'
2384 AND f.id = d.forum", array($log->info));
2387 } else if ($log->action == "add discussion") {
2389 return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
2390 FROM {forum_discussions} d,
2391 {forum_posts} p,
2392 {forum} f,
2393 {user} u
2394 WHERE d.id = ?
2395 AND d.firstpost = p.id
2396 AND p.userid = u.id
2397 AND u.deleted <> '1'
2398 AND f.id = d.forum", array($log->info));
2400 return NULL;
2404 * Given a discussion id, return the first post from the discussion
2406 * @global object
2407 * @global object
2408 * @param int $dicsussionid
2409 * @return array
2411 function forum_get_firstpost_from_discussion($discussionid) {
2412 global $CFG, $DB;
2414 return $DB->get_record_sql("SELECT p.*
2415 FROM {forum_discussions} d,
2416 {forum_posts} p
2417 WHERE d.id = ?
2418 AND d.firstpost = p.id ", array($discussionid));
2422 * Returns an array of counts of replies to each discussion
2424 * @global object
2425 * @global object
2426 * @param int $forumid
2427 * @param string $forumsort
2428 * @param int $limit
2429 * @param int $page
2430 * @param int $perpage
2431 * @return array
2433 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2434 global $CFG, $DB;
2436 if ($limit > 0) {
2437 $limitfrom = 0;
2438 $limitnum = $limit;
2439 } else if ($page != -1) {
2440 $limitfrom = $page*$perpage;
2441 $limitnum = $perpage;
2442 } else {
2443 $limitfrom = 0;
2444 $limitnum = 0;
2447 if ($forumsort == "") {
2448 $orderby = "";
2449 $groupby = "";
2451 } else {
2452 $orderby = "ORDER BY $forumsort";
2453 $groupby = ", ".strtolower($forumsort);
2454 $groupby = str_replace('desc', '', $groupby);
2455 $groupby = str_replace('asc', '', $groupby);
2458 if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2459 $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2460 FROM {forum_posts} p
2461 JOIN {forum_discussions} d ON p.discussion = d.id
2462 WHERE p.parent > 0 AND d.forum = ?
2463 GROUP BY p.discussion";
2464 return $DB->get_records_sql($sql, array($forumid));
2466 } else {
2467 $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2468 FROM {forum_posts} p
2469 JOIN {forum_discussions} d ON p.discussion = d.id
2470 WHERE d.forum = ?
2471 GROUP BY p.discussion $groupby
2472 $orderby";
2473 return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2478 * @global object
2479 * @global object
2480 * @global object
2481 * @staticvar array $cache
2482 * @param object $forum
2483 * @param object $cm
2484 * @param object $course
2485 * @return mixed
2487 function forum_count_discussions($forum, $cm, $course) {
2488 global $CFG, $DB, $USER;
2490 static $cache = array();
2492 $now = round(time(), -2); // db cache friendliness
2494 $params = array($course->id);
2496 if (!isset($cache[$course->id])) {
2497 if (!empty($CFG->forum_enabletimedposts)) {
2498 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2499 $params[] = $now;
2500 $params[] = $now;
2501 } else {
2502 $timedsql = "";
2505 $sql = "SELECT f.id, COUNT(d.id) as dcount
2506 FROM {forum} f
2507 JOIN {forum_discussions} d ON d.forum = f.id
2508 WHERE f.course = ?
2509 $timedsql
2510 GROUP BY f.id";
2512 if ($counts = $DB->get_records_sql($sql, $params)) {
2513 foreach ($counts as $count) {
2514 $counts[$count->id] = $count->dcount;
2516 $cache[$course->id] = $counts;
2517 } else {
2518 $cache[$course->id] = array();
2522 if (empty($cache[$course->id][$forum->id])) {
2523 return 0;
2526 $groupmode = groups_get_activity_groupmode($cm, $course);
2528 if ($groupmode != SEPARATEGROUPS) {
2529 return $cache[$course->id][$forum->id];
2532 if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) {
2533 return $cache[$course->id][$forum->id];
2536 require_once($CFG->dirroot.'/course/lib.php');
2538 $modinfo = get_fast_modinfo($course);
2540 $mygroups = $modinfo->get_groups($cm->groupingid);
2542 // add all groups posts
2543 $mygroups[-1] = -1;
2545 list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2546 $params[] = $forum->id;
2548 if (!empty($CFG->forum_enabletimedposts)) {
2549 $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2550 $params[] = $now;
2551 $params[] = $now;
2552 } else {
2553 $timedsql = "";
2556 $sql = "SELECT COUNT(d.id)
2557 FROM {forum_discussions} d
2558 WHERE d.groupid $mygroups_sql AND d.forum = ?
2559 $timedsql";
2561 return $DB->get_field_sql($sql, $params);
2565 * How many posts by other users are unrated by a given user in the given discussion?
2567 * TODO: Is this function still used anywhere?
2569 * @param int $discussionid
2570 * @param int $userid
2571 * @return mixed
2573 function forum_count_unrated_posts($discussionid, $userid) {
2574 global $CFG, $DB;
2576 $sql = "SELECT COUNT(*) as num
2577 FROM {forum_posts}
2578 WHERE parent > 0
2579 AND discussion = :discussionid
2580 AND userid <> :userid";
2581 $params = array('discussionid' => $discussionid, 'userid' => $userid);
2582 $posts = $DB->get_record_sql($sql, $params);
2583 if ($posts) {
2584 $sql = "SELECT count(*) as num
2585 FROM {forum_posts} p,
2586 {rating} r
2587 WHERE p.discussion = :discussionid AND
2588 p.id = r.itemid AND
2589 r.userid = userid AND
2590 r.component = 'mod_forum' AND
2591 r.ratingarea = 'post'";
2592 $rated = $DB->get_record_sql($sql, $params);
2593 if ($rated) {
2594 if ($posts->num > $rated->num) {
2595 return $posts->num - $rated->num;
2596 } else {
2597 return 0; // Just in case there was a counting error
2599 } else {
2600 return $posts->num;
2602 } else {
2603 return 0;
2608 * Get all discussions in a forum
2610 * @global object
2611 * @global object
2612 * @global object
2613 * @uses CONTEXT_MODULE
2614 * @uses VISIBLEGROUPS
2615 * @param object $cm
2616 * @param string $forumsort
2617 * @param bool $fullpost
2618 * @param int $unused
2619 * @param int $limit
2620 * @param bool $userlastmodified
2621 * @param int $page
2622 * @param int $perpage
2623 * @return array
2625 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2626 global $CFG, $DB, $USER;
2628 $timelimit = '';
2630 $now = round(time(), -2);
2631 $params = array($cm->instance);
2633 $modcontext = context_module::instance($cm->id);
2635 if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2636 return array();
2639 if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2641 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2642 $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2643 $params[] = $now;
2644 $params[] = $now;
2645 if (isloggedin()) {
2646 $timelimit .= " OR d.userid = ?";
2647 $params[] = $USER->id;
2649 $timelimit .= ")";
2653 if ($limit > 0) {
2654 $limitfrom = 0;
2655 $limitnum = $limit;
2656 } else if ($page != -1) {
2657 $limitfrom = $page*$perpage;
2658 $limitnum = $perpage;
2659 } else {
2660 $limitfrom = 0;
2661 $limitnum = 0;
2664 $groupmode = groups_get_activity_groupmode($cm);
2665 $currentgroup = groups_get_activity_group($cm);
2667 if ($groupmode) {
2668 if (empty($modcontext)) {
2669 $modcontext = context_module::instance($cm->id);
2672 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2673 if ($currentgroup) {
2674 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2675 $params[] = $currentgroup;
2676 } else {
2677 $groupselect = "";
2680 } else {
2681 //seprate groups without access all
2682 if ($currentgroup) {
2683 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2684 $params[] = $currentgroup;
2685 } else {
2686 $groupselect = "AND d.groupid = -1";
2689 } else {
2690 $groupselect = "";
2694 if (empty($forumsort)) {
2695 $forumsort = "d.timemodified DESC";
2697 if (empty($fullpost)) {
2698 $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2699 } else {
2700 $postdata = "p.*";
2703 if (empty($userlastmodified)) { // We don't need to know this
2704 $umfields = "";
2705 $umtable = "";
2706 } else {
2707 $umfields = ', ' . get_all_user_name_fields(true, 'um', null, 'um');
2708 $umtable = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2711 $allnames = get_all_user_name_fields(true, 'u');
2712 $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend, $allnames,
2713 u.email, u.picture, u.imagealt $umfields
2714 FROM {forum_discussions} d
2715 JOIN {forum_posts} p ON p.discussion = d.id
2716 JOIN {user} u ON p.userid = u.id
2717 $umtable
2718 WHERE d.forum = ? AND p.parent = 0
2719 $timelimit $groupselect
2720 ORDER BY $forumsort";
2721 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2726 * @global object
2727 * @global object
2728 * @global object
2729 * @uses CONTEXT_MODULE
2730 * @uses VISIBLEGROUPS
2731 * @param object $cm
2732 * @return array
2734 function forum_get_discussions_unread($cm) {
2735 global $CFG, $DB, $USER;
2737 $now = round(time(), -2);
2738 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2740 $params = array();
2741 $groupmode = groups_get_activity_groupmode($cm);
2742 $currentgroup = groups_get_activity_group($cm);
2744 if ($groupmode) {
2745 $modcontext = context_module::instance($cm->id);
2747 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2748 if ($currentgroup) {
2749 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2750 $params['currentgroup'] = $currentgroup;
2751 } else {
2752 $groupselect = "";
2755 } else {
2756 //separate groups without access all
2757 if ($currentgroup) {
2758 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2759 $params['currentgroup'] = $currentgroup;
2760 } else {
2761 $groupselect = "AND d.groupid = -1";
2764 } else {
2765 $groupselect = "";
2768 if (!empty($CFG->forum_enabletimedposts)) {
2769 $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2770 $params['now1'] = $now;
2771 $params['now2'] = $now;
2772 } else {
2773 $timedsql = "";
2776 $sql = "SELECT d.id, COUNT(p.id) AS unread
2777 FROM {forum_discussions} d
2778 JOIN {forum_posts} p ON p.discussion = d.id
2779 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2780 WHERE d.forum = {$cm->instance}
2781 AND p.modified >= :cutoffdate AND r.id is NULL
2782 $groupselect
2783 $timedsql
2784 GROUP BY d.id";
2785 $params['cutoffdate'] = $cutoffdate;
2787 if ($unreads = $DB->get_records_sql($sql, $params)) {
2788 foreach ($unreads as $unread) {
2789 $unreads[$unread->id] = $unread->unread;
2791 return $unreads;
2792 } else {
2793 return array();
2798 * @global object
2799 * @global object
2800 * @global object
2801 * @uses CONEXT_MODULE
2802 * @uses VISIBLEGROUPS
2803 * @param object $cm
2804 * @return array
2806 function forum_get_discussions_count($cm) {
2807 global $CFG, $DB, $USER;
2809 $now = round(time(), -2);
2810 $params = array($cm->instance);
2811 $groupmode = groups_get_activity_groupmode($cm);
2812 $currentgroup = groups_get_activity_group($cm);
2814 if ($groupmode) {
2815 $modcontext = context_module::instance($cm->id);
2817 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2818 if ($currentgroup) {
2819 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2820 $params[] = $currentgroup;
2821 } else {
2822 $groupselect = "";
2825 } else {
2826 //seprate groups without access all
2827 if ($currentgroup) {
2828 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2829 $params[] = $currentgroup;
2830 } else {
2831 $groupselect = "AND d.groupid = -1";
2834 } else {
2835 $groupselect = "";
2838 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2840 $timelimit = "";
2842 if (!empty($CFG->forum_enabletimedposts)) {
2844 $modcontext = context_module::instance($cm->id);
2846 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2847 $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2848 $params[] = $now;
2849 $params[] = $now;
2850 if (isloggedin()) {
2851 $timelimit .= " OR d.userid = ?";
2852 $params[] = $USER->id;
2854 $timelimit .= ")";
2858 $sql = "SELECT COUNT(d.id)
2859 FROM {forum_discussions} d
2860 JOIN {forum_posts} p ON p.discussion = d.id
2861 WHERE d.forum = ? AND p.parent = 0
2862 $groupselect $timelimit";
2864 return $DB->get_field_sql($sql, $params);
2869 * Get all discussions started by a particular user in a course (or group)
2870 * This function no longer used ...
2872 * @todo Remove this function if no longer used
2873 * @global object
2874 * @global object
2875 * @param int $courseid
2876 * @param int $userid
2877 * @param int $groupid
2878 * @return array
2880 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2881 global $CFG, $DB;
2882 $params = array($courseid, $userid);
2883 if ($groupid) {
2884 $groupselect = " AND d.groupid = ? ";
2885 $params[] = $groupid;
2886 } else {
2887 $groupselect = "";
2890 $allnames = get_all_user_name_fields(true, 'u');
2891 return $DB->get_records_sql("SELECT p.*, d.groupid, $allnames, u.email, u.picture, u.imagealt,
2892 f.type as forumtype, f.name as forumname, f.id as forumid
2893 FROM {forum_discussions} d,
2894 {forum_posts} p,
2895 {user} u,
2896 {forum} f
2897 WHERE d.course = ?
2898 AND p.discussion = d.id
2899 AND p.parent = 0
2900 AND p.userid = u.id
2901 AND u.id = ?
2902 AND d.forum = f.id $groupselect
2903 ORDER BY p.created DESC", $params);
2907 * Get the list of potential subscribers to a forum.
2909 * @param object $forumcontext the forum context.
2910 * @param integer $groupid the id of a group, or 0 for all groups.
2911 * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2912 * @param string $sort sort order. As for get_users_by_capability.
2913 * @return array list of users.
2915 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort = '') {
2916 global $DB;
2918 // only active enrolled users or everybody on the frontpage
2919 list($esql, $params) = get_enrolled_sql($forumcontext, 'mod/forum:allowforcesubscribe', $groupid, true);
2920 if (!$sort) {
2921 list($sort, $sortparams) = users_order_by_sql('u');
2922 $params = array_merge($params, $sortparams);
2925 $sql = "SELECT $fields
2926 FROM {user} u
2927 JOIN ($esql) je ON je.id = u.id
2928 ORDER BY $sort";
2930 return $DB->get_records_sql($sql, $params);
2934 * Returns list of user objects that are subscribed to this forum
2936 * @global object
2937 * @global object
2938 * @param object $course the course
2939 * @param forum $forum the forum
2940 * @param integer $groupid group id, or 0 for all.
2941 * @param object $context the forum context, to save re-fetching it where possible.
2942 * @param string $fields requested user fields (with "u." table prefix)
2943 * @return array list of users.
2945 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2946 global $CFG, $DB;
2948 $allnames = get_all_user_name_fields(true, 'u');
2949 if (empty($fields)) {
2950 $fields ="u.id,
2951 u.username,
2952 $allnames,
2953 u.maildisplay,
2954 u.mailformat,
2955 u.maildigest,
2956 u.imagealt,
2957 u.email,
2958 u.emailstop,
2959 u.city,
2960 u.country,
2961 u.lastaccess,
2962 u.lastlogin,
2963 u.picture,
2964 u.timezone,
2965 u.theme,
2966 u.lang,
2967 u.trackforums,
2968 u.mnethostid";
2971 if (empty($context)) {
2972 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2973 $context = context_module::instance($cm->id);
2976 if (forum_is_forcesubscribed($forum)) {
2977 $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2979 } else {
2980 // only active enrolled users or everybody on the frontpage
2981 list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2982 $params['forumid'] = $forum->id;
2983 $results = $DB->get_records_sql("SELECT $fields
2984 FROM {user} u
2985 JOIN ($esql) je ON je.id = u.id
2986 JOIN {forum_subscriptions} s ON s.userid = u.id
2987 WHERE s.forum = :forumid
2988 ORDER BY u.email ASC", $params);
2991 // Guest user should never be subscribed to a forum.
2992 unset($results[$CFG->siteguest]);
2994 return $results;
2999 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
3003 * @global object
3004 * @global object
3005 * @param int $courseid
3006 * @param string $type
3008 function forum_get_course_forum($courseid, $type) {
3009 // How to set up special 1-per-course forums
3010 global $CFG, $DB, $OUTPUT, $USER;
3012 if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
3013 // There should always only be ONE, but with the right combination of
3014 // errors there might be more. In this case, just return the oldest one (lowest ID).
3015 foreach ($forums as $forum) {
3016 return $forum; // ie the first one
3020 // Doesn't exist, so create one now.
3021 $forum = new stdClass();
3022 $forum->course = $courseid;
3023 $forum->type = "$type";
3024 if (!empty($USER->htmleditor)) {
3025 $forum->introformat = $USER->htmleditor;
3027 switch ($forum->type) {
3028 case "news":
3029 $forum->name = get_string("namenews", "forum");
3030 $forum->intro = get_string("intronews", "forum");
3031 $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
3032 $forum->assessed = 0;
3033 if ($courseid == SITEID) {
3034 $forum->name = get_string("sitenews");
3035 $forum->forcesubscribe = 0;
3037 break;
3038 case "social":
3039 $forum->name = get_string("namesocial", "forum");
3040 $forum->intro = get_string("introsocial", "forum");
3041 $forum->assessed = 0;
3042 $forum->forcesubscribe = 0;
3043 break;
3044 case "blog":
3045 $forum->name = get_string('blogforum', 'forum');
3046 $forum->intro = get_string('introblog', 'forum');
3047 $forum->assessed = 0;
3048 $forum->forcesubscribe = 0;
3049 break;
3050 default:
3051 echo $OUTPUT->notification("That forum type doesn't exist!");
3052 return false;
3053 break;
3056 $forum->timemodified = time();
3057 $forum->id = $DB->insert_record("forum", $forum);
3059 if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
3060 echo $OUTPUT->notification("Could not find forum module!!");
3061 return false;
3063 $mod = new stdClass();
3064 $mod->course = $courseid;
3065 $mod->module = $module->id;
3066 $mod->instance = $forum->id;
3067 $mod->section = 0;
3068 include_once("$CFG->dirroot/course/lib.php");
3069 if (! $mod->coursemodule = add_course_module($mod) ) {
3070 echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
3071 return false;
3073 $sectionid = course_add_cm_to_section($courseid, $mod->coursemodule, 0);
3074 return $DB->get_record("forum", array("id" => "$forum->id"));
3079 * Given the data about a posting, builds up the HTML to display it and
3080 * returns the HTML in a string. This is designed for sending via HTML email.
3082 * @global object
3083 * @param object $course
3084 * @param object $cm
3085 * @param object $forum
3086 * @param object $discussion
3087 * @param object $post
3088 * @param object $userform
3089 * @param object $userto
3090 * @param bool $ownpost
3091 * @param bool $reply
3092 * @param bool $link
3093 * @param bool $rate
3094 * @param string $footer
3095 * @return string
3097 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3098 $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3100 global $CFG, $OUTPUT;
3102 $modcontext = context_module::instance($cm->id);
3104 if (!isset($userto->viewfullnames[$forum->id])) {
3105 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3106 } else {
3107 $viewfullnames = $userto->viewfullnames[$forum->id];
3110 // add absolute file links
3111 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3113 // format the post body
3114 $options = new stdClass();
3115 $options->para = true;
3116 $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3118 $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3120 $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3121 $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3122 $output .= '</td>';
3124 if ($post->parent) {
3125 $output .= '<td class="topic">';
3126 } else {
3127 $output .= '<td class="topic starter">';
3129 $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3131 $fullname = fullname($userfrom, $viewfullnames);
3132 $by = new stdClass();
3133 $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3134 $by->date = userdate($post->modified, '', $userto->timezone);
3135 $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3137 $output .= '</td></tr>';
3139 $output .= '<tr><td class="left side" valign="top">';
3141 if (isset($userfrom->groups)) {
3142 $groups = $userfrom->groups[$forum->id];
3143 } else {
3144 $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3147 if ($groups) {
3148 $output .= print_group_picture($groups, $course->id, false, true, true);
3149 } else {
3150 $output .= '&nbsp;';
3153 $output .= '</td><td class="content">';
3155 $attachments = forum_print_attachments($post, $cm, 'html');
3156 if ($attachments !== '') {
3157 $output .= '<div class="attachments">';
3158 $output .= $attachments;
3159 $output .= '</div>';
3162 $output .= $formattedtext;
3164 // Commands
3165 $commands = array();
3167 if ($post->parent) {
3168 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3169 $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3172 if ($reply) {
3173 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3174 get_string('reply', 'forum').'</a>';
3177 $output .= '<div class="commands">';
3178 $output .= implode(' | ', $commands);
3179 $output .= '</div>';
3181 // Context link to post if required
3182 if ($link) {
3183 $output .= '<div class="link">';
3184 $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3185 get_string('postincontext', 'forum').'</a>';
3186 $output .= '</div>';
3189 if ($footer) {
3190 $output .= '<div class="footer">'.$footer.'</div>';
3192 $output .= '</td></tr></table>'."\n\n";
3194 return $output;
3198 * Print a forum post
3200 * @global object
3201 * @global object
3202 * @uses FORUM_MODE_THREADED
3203 * @uses PORTFOLIO_FORMAT_PLAINHTML
3204 * @uses PORTFOLIO_FORMAT_FILE
3205 * @uses PORTFOLIO_FORMAT_RICHHTML
3206 * @uses PORTFOLIO_ADD_TEXT_LINK
3207 * @uses CONTEXT_MODULE
3208 * @param object $post The post to print.
3209 * @param object $discussion
3210 * @param object $forum
3211 * @param object $cm
3212 * @param object $course
3213 * @param boolean $ownpost Whether this post belongs to the current user.
3214 * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3215 * @param boolean $link Just print a shortened version of the post as a link to the full post.
3216 * @param string $footer Extra stuff to print after the message.
3217 * @param string $highlight Space-separated list of terms to highlight.
3218 * @param int $post_read true, false or -99. If we already know whether this user
3219 * has read this post, pass that in, otherwise, pass in -99, and this
3220 * function will work it out.
3221 * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3222 * the current user can't see this post, if this argument is true
3223 * (the default) then print a dummy 'you can't see this post' post.
3224 * If false, don't output anything at all.
3225 * @param bool|null $istracked
3226 * @return void
3228 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3229 $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3230 global $USER, $CFG, $OUTPUT;
3232 require_once($CFG->libdir . '/filelib.php');
3234 // String cache
3235 static $str;
3237 $modcontext = context_module::instance($cm->id);
3239 $post->course = $course->id;
3240 $post->forum = $forum->id;
3241 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3242 if (!empty($CFG->enableplagiarism)) {
3243 require_once($CFG->libdir.'/plagiarismlib.php');
3244 $post->message .= plagiarism_get_links(array('userid' => $post->userid,
3245 'content' => $post->message,
3246 'cmid' => $cm->id,
3247 'course' => $post->course,
3248 'forum' => $post->forum));
3251 // caching
3252 if (!isset($cm->cache)) {
3253 $cm->cache = new stdClass;
3256 if (!isset($cm->cache->caps)) {
3257 $cm->cache->caps = array();
3258 $cm->cache->caps['mod/forum:viewdiscussion'] = has_capability('mod/forum:viewdiscussion', $modcontext);
3259 $cm->cache->caps['moodle/site:viewfullnames'] = has_capability('moodle/site:viewfullnames', $modcontext);
3260 $cm->cache->caps['mod/forum:editanypost'] = has_capability('mod/forum:editanypost', $modcontext);
3261 $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3262 $cm->cache->caps['mod/forum:deleteownpost'] = has_capability('mod/forum:deleteownpost', $modcontext);
3263 $cm->cache->caps['mod/forum:deleteanypost'] = has_capability('mod/forum:deleteanypost', $modcontext);
3264 $cm->cache->caps['mod/forum:viewanyrating'] = has_capability('mod/forum:viewanyrating', $modcontext);
3265 $cm->cache->caps['mod/forum:exportpost'] = has_capability('mod/forum:exportpost', $modcontext);
3266 $cm->cache->caps['mod/forum:exportownpost'] = has_capability('mod/forum:exportownpost', $modcontext);
3269 if (!isset($cm->uservisible)) {
3270 $cm->uservisible = coursemodule_visible_for_user($cm);
3273 if ($istracked && is_null($postisread)) {
3274 $postisread = forum_tp_is_post_read($USER->id, $post);
3277 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3278 $output = '';
3279 if (!$dummyifcantsee) {
3280 if ($return) {
3281 return $output;
3283 echo $output;
3284 return;
3286 $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3287 $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix',
3288 'role' => 'region',
3289 'aria-label' => get_string('hiddenforumpost', 'forum')));
3290 $output .= html_writer::start_tag('div', array('class'=>'row header'));
3291 $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3292 if ($post->parent) {
3293 $output .= html_writer::start_tag('div', array('class'=>'topic'));
3294 } else {
3295 $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3297 $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class' => 'subject',
3298 'role' => 'header')); // Subject.
3299 $output .= html_writer::tag('div', get_string('forumauthorhidden', 'forum'), array('class' => 'author',
3300 'role' => 'header')); // Author.
3301 $output .= html_writer::end_tag('div');
3302 $output .= html_writer::end_tag('div'); // row
3303 $output .= html_writer::start_tag('div', array('class'=>'row'));
3304 $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3305 $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3306 $output .= html_writer::end_tag('div'); // row
3307 $output .= html_writer::end_tag('div'); // forumpost
3309 if ($return) {
3310 return $output;
3312 echo $output;
3313 return;
3316 if (empty($str)) {
3317 $str = new stdClass;
3318 $str->edit = get_string('edit', 'forum');
3319 $str->delete = get_string('delete', 'forum');
3320 $str->reply = get_string('reply', 'forum');
3321 $str->parent = get_string('parent', 'forum');
3322 $str->pruneheading = get_string('pruneheading', 'forum');
3323 $str->prune = get_string('prune', 'forum');
3324 $str->displaymode = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3325 $str->markread = get_string('markread', 'forum');
3326 $str->markunread = get_string('markunread', 'forum');
3329 $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3331 // Build an object that represents the posting user
3332 $postuser = new stdClass;
3333 $postuserfields = explode(',', user_picture::fields());
3334 $postuser = username_load_fields_from_object($postuser, $post, null, $postuserfields);
3335 $postuser->id = $post->userid;
3336 $postuser->fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3337 $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3339 // Prepare the groups the posting user belongs to
3340 if (isset($cm->cache->usersgroups)) {
3341 $groups = array();
3342 if (isset($cm->cache->usersgroups[$post->userid])) {
3343 foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3344 $groups[$gid] = $cm->cache->groups[$gid];
3347 } else {
3348 $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3351 // Prepare the attachements for the post, files then images
3352 list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3354 // Determine if we need to shorten this post
3355 $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3358 // Prepare an array of commands
3359 $commands = array();
3361 // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3362 // Don't display the mark read / unread controls in this case.
3363 if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3364 $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3365 $text = $str->markunread;
3366 if (!$postisread) {
3367 $url->param('mark', 'read');
3368 $text = $str->markread;
3370 if ($str->displaymode == FORUM_MODE_THREADED) {
3371 $url->param('parent', $post->parent);
3372 } else {
3373 $url->set_anchor('p'.$post->id);
3375 $commands[] = array('url'=>$url, 'text'=>$text);
3378 // Zoom in to the parent specifically
3379 if ($post->parent) {
3380 $url = new moodle_url($discussionlink);
3381 if ($str->displaymode == FORUM_MODE_THREADED) {
3382 $url->param('parent', $post->parent);
3383 } else {
3384 $url->set_anchor('p'.$post->parent);
3386 $commands[] = array('url'=>$url, 'text'=>$str->parent);
3389 // Hack for allow to edit news posts those are not displayed yet until they are displayed
3390 $age = time() - $post->created;
3391 if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3392 $age = 0;
3395 if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3396 if (has_capability('moodle/course:manageactivities', $modcontext)) {
3397 // The first post in single simple is the forum description.
3398 $commands[] = array('url'=>new moodle_url('/course/modedit.php', array('update'=>$cm->id, 'sesskey'=>sesskey(), 'return'=>1)), 'text'=>$str->edit);
3400 } else if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3401 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3404 if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3405 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3408 if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3409 // Do not allow deleting of first post in single simple type.
3410 } else if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3411 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3414 if ($reply) {
3415 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mformforum', array('reply'=>$post->id)), 'text'=>$str->reply);
3418 if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3419 $p = array('postid' => $post->id);
3420 require_once($CFG->libdir.'/portfoliolib.php');
3421 $button = new portfolio_add_button();
3422 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), 'mod_forum');
3423 if (empty($attachments)) {
3424 $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3425 } else {
3426 $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3429 $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3430 if (!empty($porfoliohtml)) {
3431 $commands[] = $porfoliohtml;
3434 // Finished building commands
3437 // Begin output
3439 $output = '';
3441 if ($istracked) {
3442 if ($postisread) {
3443 $forumpostclass = ' read';
3444 } else {
3445 $forumpostclass = ' unread';
3446 $output .= html_writer::tag('a', '', array('name'=>'unread'));
3448 } else {
3449 // ignore trackign status if not tracked or tracked param missing
3450 $forumpostclass = '';
3453 $topicclass = '';
3454 if (empty($post->parent)) {
3455 $topicclass = ' firstpost starter';
3458 $postbyuser = new stdClass;
3459 $postbyuser->post = $post->subject;
3460 $postbyuser->user = $postuser->fullname;
3461 $discussionbyuser = get_string('postbyuser', 'forum', $postbyuser);
3462 $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3463 $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass,
3464 'role' => 'region',
3465 'aria-label' => $discussionbyuser));
3466 $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3467 $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3468 $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3469 $output .= html_writer::end_tag('div');
3472 $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3474 $postsubject = $post->subject;
3475 if (empty($post->subjectnoformat)) {
3476 $postsubject = format_string($postsubject);
3478 $output .= html_writer::tag('div', $postsubject, array('class'=>'subject',
3479 'role' => 'heading',
3480 'aria-level' => '2'));
3482 $by = new stdClass();
3483 $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3484 $by->date = userdate($post->modified);
3485 $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author',
3486 'role' => 'heading',
3487 'aria-level' => '2'));
3489 $output .= html_writer::end_tag('div'); //topic
3490 $output .= html_writer::end_tag('div'); //row
3492 $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3493 $output .= html_writer::start_tag('div', array('class'=>'left'));
3495 $groupoutput = '';
3496 if ($groups) {
3497 $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3499 if (empty($groupoutput)) {
3500 $groupoutput = '&nbsp;';
3502 $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3504 $output .= html_writer::end_tag('div'); //left side
3505 $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3506 $output .= html_writer::start_tag('div', array('class'=>'content'));
3507 if (!empty($attachments)) {
3508 $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3511 $options = new stdClass;
3512 $options->para = false;
3513 $options->trusted = $post->messagetrust;
3514 $options->context = $modcontext;
3515 if ($shortenpost) {
3516 // Prepare shortened version by filtering the text then shortening it.
3517 $postclass = 'shortenedpost';
3518 $postcontent = format_text($post->message, $post->messageformat, $options);
3519 $postcontent = shorten_text($postcontent, $CFG->forum_shortpost);
3520 $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3521 $postcontent .= html_writer::tag('div', '('.get_string('numwords', 'moodle', count_words($post->message)).')',
3522 array('class'=>'post-word-count'));
3523 } else {
3524 // Prepare whole post
3525 $postclass = 'fullpost';
3526 $postcontent = format_text($post->message, $post->messageformat, $options, $course->id);
3527 if (!empty($highlight)) {
3528 $postcontent = highlight($highlight, $postcontent);
3530 if (!empty($forum->displaywordcount)) {
3531 $postcontent .= html_writer::tag('div', get_string('numwords', 'moodle', count_words($post->message)),
3532 array('class'=>'post-word-count'));
3534 $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3537 // Output the post content
3538 $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3539 $output .= html_writer::end_tag('div'); // Content
3540 $output .= html_writer::end_tag('div'); // Content mask
3541 $output .= html_writer::end_tag('div'); // Row
3543 $output .= html_writer::start_tag('div', array('class'=>'row side'));
3544 $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3545 $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3547 // Output ratings
3548 if (!empty($post->rating)) {
3549 $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3552 // Output the commands
3553 $commandhtml = array();
3554 foreach ($commands as $command) {
3555 if (is_array($command)) {
3556 $commandhtml[] = html_writer::link($command['url'], $command['text']);
3557 } else {
3558 $commandhtml[] = $command;
3561 $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3563 // Output link to post if required
3564 if ($link && forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext)) {
3565 if ($post->replies == 1) {
3566 $replystring = get_string('repliesone', 'forum', $post->replies);
3567 } else {
3568 $replystring = get_string('repliesmany', 'forum', $post->replies);
3571 $output .= html_writer::start_tag('div', array('class'=>'link'));
3572 $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3573 $output .= '&nbsp;('.$replystring.')';
3574 $output .= html_writer::end_tag('div'); // link
3577 // Output footer if required
3578 if ($footer) {
3579 $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3582 // Close remaining open divs
3583 $output .= html_writer::end_tag('div'); // content
3584 $output .= html_writer::end_tag('div'); // row
3585 $output .= html_writer::end_tag('div'); // forumpost
3587 // Mark the forum post as read if required
3588 if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3589 forum_tp_mark_post_read($USER->id, $post, $forum->id);
3592 if ($return) {
3593 return $output;
3595 echo $output;
3596 return;
3600 * Return rating related permissions
3602 * @param string $options the context id
3603 * @return array an associative array of the user's rating permissions
3605 function forum_rating_permissions($contextid, $component, $ratingarea) {
3606 $context = context::instance_by_id($contextid, MUST_EXIST);
3607 if ($component != 'mod_forum' || $ratingarea != 'post') {
3608 // We don't know about this component/ratingarea so just return null to get the
3609 // default restrictive permissions.
3610 return null;
3612 return array(
3613 'view' => has_capability('mod/forum:viewrating', $context),
3614 'viewany' => has_capability('mod/forum:viewanyrating', $context),
3615 'viewall' => has_capability('mod/forum:viewallratings', $context),
3616 'rate' => has_capability('mod/forum:rate', $context)
3621 * Validates a submitted rating
3622 * @param array $params submitted data
3623 * context => object the context in which the rated items exists [required]
3624 * component => The component for this module - should always be mod_forum [required]
3625 * ratingarea => object the context in which the rated items exists [required]
3626 * itemid => int the ID of the object being rated [required]
3627 * scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
3628 * rating => int the submitted rating [required]
3629 * rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
3630 * aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
3631 * @return boolean true if the rating is valid. Will throw rating_exception if not
3633 function forum_rating_validate($params) {
3634 global $DB, $USER;
3636 // Check the component is mod_forum
3637 if ($params['component'] != 'mod_forum') {
3638 throw new rating_exception('invalidcomponent');
3641 // Check the ratingarea is post (the only rating area in forum)
3642 if ($params['ratingarea'] != 'post') {
3643 throw new rating_exception('invalidratingarea');
3646 // Check the rateduserid is not the current user .. you can't rate your own posts
3647 if ($params['rateduserid'] == $USER->id) {
3648 throw new rating_exception('nopermissiontorate');
3651 // Fetch all the related records ... we need to do this anyway to call forum_user_can_see_post
3652 $post = $DB->get_record('forum_posts', array('id' => $params['itemid'], 'userid' => $params['rateduserid']), '*', MUST_EXIST);
3653 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion), '*', MUST_EXIST);
3654 $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
3655 $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
3656 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id , false, MUST_EXIST);
3657 $context = context_module::instance($cm->id);
3659 // Make sure the context provided is the context of the forum
3660 if ($context->id != $params['context']->id) {
3661 throw new rating_exception('invalidcontext');
3664 if ($forum->scale != $params['scaleid']) {
3665 //the scale being submitted doesnt match the one in the database
3666 throw new rating_exception('invalidscaleid');
3669 // check the item we're rating was created in the assessable time window
3670 if (!empty($forum->assesstimestart) && !empty($forum->assesstimefinish)) {
3671 if ($post->created < $forum->assesstimestart || $post->created > $forum->assesstimefinish) {
3672 throw new rating_exception('notavailable');
3676 //check that the submitted rating is valid for the scale
3678 // lower limit
3679 if ($params['rating'] < 0 && $params['rating'] != RATING_UNSET_RATING) {
3680 throw new rating_exception('invalidnum');
3683 // upper limit
3684 if ($forum->scale < 0) {
3685 //its a custom scale
3686 $scalerecord = $DB->get_record('scale', array('id' => -$forum->scale));
3687 if ($scalerecord) {
3688 $scalearray = explode(',', $scalerecord->scale);
3689 if ($params['rating'] > count($scalearray)) {
3690 throw new rating_exception('invalidnum');
3692 } else {
3693 throw new rating_exception('invalidscaleid');
3695 } else if ($params['rating'] > $forum->scale) {
3696 //if its numeric and submitted rating is above maximum
3697 throw new rating_exception('invalidnum');
3700 // Make sure groups allow this user to see the item they're rating
3701 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
3702 if (!groups_group_exists($discussion->groupid)) { // Can't find group
3703 throw new rating_exception('cannotfindgroup');//something is wrong
3706 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
3707 // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
3708 throw new rating_exception('notmemberofgroup');
3712 // perform some final capability checks
3713 if (!forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) {
3714 throw new rating_exception('nopermissiontorate');
3717 return true;
3722 * This function prints the overview of a discussion in the forum listing.
3723 * It needs some discussion information and some post information, these
3724 * happen to be combined for efficiency in the $post parameter by the function
3725 * that calls this one: forum_print_latest_discussions()
3727 * @global object
3728 * @global object
3729 * @param object $post The post object (passed by reference for speed).
3730 * @param object $forum The forum object.
3731 * @param int $group Current group.
3732 * @param string $datestring Format to use for the dates.
3733 * @param boolean $cantrack Is tracking enabled for this forum.
3734 * @param boolean $forumtracked Is the user tracking this forum.
3735 * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3737 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3738 $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3740 global $USER, $CFG, $OUTPUT;
3742 static $rowcount;
3743 static $strmarkalldread;
3745 if (empty($modcontext)) {
3746 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3747 print_error('invalidcoursemodule');
3749 $modcontext = context_module::instance($cm->id);
3752 if (!isset($rowcount)) {
3753 $rowcount = 0;
3754 $strmarkalldread = get_string('markalldread', 'forum');
3755 } else {
3756 $rowcount = ($rowcount + 1) % 2;
3759 $post->subject = format_string($post->subject,true);
3761 echo "\n\n";
3762 echo '<tr class="discussion r'.$rowcount.'">';
3764 // Topic
3765 echo '<td class="topic starter">';
3766 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3767 echo "</td>\n";
3769 // Picture
3770 $postuser = new stdClass();
3771 $postuserfields = explode(',', user_picture::fields());
3772 $postuser = username_load_fields_from_object($postuser, $post, null, $postuserfields);
3773 $postuser->id = $post->userid;
3774 echo '<td class="picture">';
3775 echo $OUTPUT->user_picture($postuser, array('courseid'=>$forum->course));
3776 echo "</td>\n";
3778 // User name
3779 $fullname = fullname($postuser, has_capability('moodle/site:viewfullnames', $modcontext));
3780 echo '<td class="author">';
3781 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
3782 echo "</td>\n";
3784 // Group picture
3785 if ($group !== -1) { // Groups are active - group is a group data object or NULL
3786 echo '<td class="picture group">';
3787 if (!empty($group->picture) and empty($group->hidepicture)) {
3788 print_group_picture($group, $forum->course, false, false, true);
3789 } else if (isset($group->id)) {
3790 if($canviewparticipants) {
3791 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
3792 } else {
3793 echo $group->name;
3796 echo "</td>\n";
3799 if (has_capability('mod/forum:viewdiscussion', $modcontext)) { // Show the column with replies
3800 echo '<td class="replies">';
3801 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3802 echo $post->replies.'</a>';
3803 echo "</td>\n";
3805 if ($cantrack) {
3806 echo '<td class="replies">';
3807 if ($forumtracked) {
3808 if ($post->unread > 0) {
3809 echo '<span class="unread">';
3810 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
3811 echo $post->unread;
3812 echo '</a>';
3813 echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3814 $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
3815 '<img src="'.$OUTPUT->pix_url('t/markasread') . '" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
3816 echo '</span>';
3817 } else {
3818 echo '<span class="read">';
3819 echo $post->unread;
3820 echo '</span>';
3822 } else {
3823 echo '<span class="read">';
3824 echo '-';
3825 echo '</span>';
3827 echo "</td>\n";
3831 echo '<td class="lastpost">';
3832 $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified; // Just in case
3833 $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
3834 $usermodified = new stdClass();
3835 $usermodified->id = $post->usermodified;
3836 $usermodified = username_load_fields_from_object($usermodified, $post, 'um');
3837 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
3838 fullname($usermodified).'</a><br />';
3839 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
3840 userdate($usedate, $datestring).'</a>';
3841 echo "</td>\n";
3843 echo "</tr>\n\n";
3848 * This function is now deprecated. Use shorten_text($message, $CFG->forum_shortpost) instead.
3850 * Given a post object that we already know has a long message
3851 * this function truncates the message nicely to the first
3852 * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
3854 * @deprecated since Moodle 2.6
3855 * @see shorten_text()
3856 * @todo finalise deprecation in 2.8 in MDL-40851
3857 * @global object
3858 * @param string $message
3859 * @return string
3861 function forum_shorten_post($message) {
3862 global $CFG;
3863 debugging('forum_shorten_post() is deprecated since Moodle 2.6. Please use shorten_text($message, $CFG->forum_shortpost) instead.', DEBUG_DEVELOPER);
3864 return shorten_text($message, $CFG->forum_shortpost);
3868 * Print the drop down that allows the user to select how they want to have
3869 * the discussion displayed.
3871 * @param int $id forum id if $forumtype is 'single',
3872 * discussion id for any other forum type
3873 * @param mixed $mode forum layout mode
3874 * @param string $forumtype optional
3876 function forum_print_mode_form($id, $mode, $forumtype='') {
3877 global $OUTPUT;
3878 if ($forumtype == 'single') {
3879 $select = new single_select(new moodle_url("/mod/forum/view.php", array('f'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3880 $select->set_label(get_string('displaymode', 'forum'), array('class' => 'accesshide'));
3881 $select->class = "forummode";
3882 } else {
3883 $select = new single_select(new moodle_url("/mod/forum/discuss.php", array('d'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3884 $select->set_label(get_string('displaymode', 'forum'), array('class' => 'accesshide'));
3886 echo $OUTPUT->render($select);
3890 * @global object
3891 * @param object $course
3892 * @param string $search
3893 * @return string
3895 function forum_search_form($course, $search='') {
3896 global $CFG, $OUTPUT;
3898 $output = '<div class="forumsearch">';
3899 $output .= '<form action="'.$CFG->wwwroot.'/mod/forum/search.php" style="display:inline">';
3900 $output .= '<fieldset class="invisiblefieldset">';
3901 $output .= $OUTPUT->help_icon('search');
3902 $output .= '<label class="accesshide" for="search" >'.get_string('search', 'forum').'</label>';
3903 $output .= '<input id="search" name="search" type="text" size="18" value="'.s($search, true).'" alt="search" />';
3904 $output .= '<label class="accesshide" for="searchforums" >'.get_string('searchforums', 'forum').'</label>';
3905 $output .= '<input id="searchforums" value="'.get_string('searchforums', 'forum').'" type="submit" />';
3906 $output .= '<input name="id" type="hidden" value="'.$course->id.'" />';
3907 $output .= '</fieldset>';
3908 $output .= '</form>';
3909 $output .= '</div>';
3911 return $output;
3916 * @global object
3917 * @global object
3919 function forum_set_return() {
3920 global $CFG, $SESSION;
3922 if (! isset($SESSION->fromdiscussion)) {
3923 if (!empty($_SERVER['HTTP_REFERER'])) {
3924 $referer = $_SERVER['HTTP_REFERER'];
3925 } else {
3926 $referer = "";
3928 // If the referer is NOT a login screen then save it.
3929 if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) {
3930 $SESSION->fromdiscussion = $_SERVER["HTTP_REFERER"];
3937 * @global object
3938 * @param string $default
3939 * @return string
3941 function forum_go_back_to($default) {
3942 global $SESSION;
3944 if (!empty($SESSION->fromdiscussion)) {
3945 $returnto = $SESSION->fromdiscussion;
3946 unset($SESSION->fromdiscussion);
3947 return $returnto;
3948 } else {
3949 return $default;
3954 * Given a discussion object that is being moved to $forumto,
3955 * this function checks all posts in that discussion
3956 * for attachments, and if any are found, these are
3957 * moved to the new forum directory.
3959 * @global object
3960 * @param object $discussion
3961 * @param int $forumfrom source forum id
3962 * @param int $forumto target forum id
3963 * @return bool success
3965 function forum_move_attachments($discussion, $forumfrom, $forumto) {
3966 global $DB;
3968 $fs = get_file_storage();
3970 $newcm = get_coursemodule_from_instance('forum', $forumto);
3971 $oldcm = get_coursemodule_from_instance('forum', $forumfrom);
3973 $newcontext = context_module::instance($newcm->id);
3974 $oldcontext = context_module::instance($oldcm->id);
3976 // loop through all posts, better not use attachment flag ;-)
3977 if ($posts = $DB->get_records('forum_posts', array('discussion'=>$discussion->id), '', 'id, attachment')) {
3978 foreach ($posts as $post) {
3979 $fs->move_area_files_to_new_context($oldcontext->id,
3980 $newcontext->id, 'mod_forum', 'post', $post->id);
3981 $attachmentsmoved = $fs->move_area_files_to_new_context($oldcontext->id,
3982 $newcontext->id, 'mod_forum', 'attachment', $post->id);
3983 if ($attachmentsmoved > 0 && $post->attachment != '1') {
3984 // Weird - let's fix it
3985 $post->attachment = '1';
3986 $DB->update_record('forum_posts', $post);
3987 } else if ($attachmentsmoved == 0 && $post->attachment != '') {
3988 // Weird - let's fix it
3989 $post->attachment = '';
3990 $DB->update_record('forum_posts', $post);
3995 return true;
3999 * Returns attachments as formated text/html optionally with separate images
4001 * @global object
4002 * @global object
4003 * @global object
4004 * @param object $post
4005 * @param object $cm
4006 * @param string $type html/text/separateimages
4007 * @return mixed string or array of (html text withouth images and image HTML)
4009 function forum_print_attachments($post, $cm, $type) {
4010 global $CFG, $DB, $USER, $OUTPUT;
4012 if (empty($post->attachment)) {
4013 return $type !== 'separateimages' ? '' : array('', '');
4016 if (!in_array($type, array('separateimages', 'html', 'text'))) {
4017 return $type !== 'separateimages' ? '' : array('', '');
4020 if (!$context = context_module::instance($cm->id)) {
4021 return $type !== 'separateimages' ? '' : array('', '');
4023 $strattachment = get_string('attachment', 'forum');
4025 $fs = get_file_storage();
4027 $imagereturn = '';
4028 $output = '';
4030 $canexport = !empty($CFG->enableportfolios) && (has_capability('mod/forum:exportpost', $context) || ($post->userid == $USER->id && has_capability('mod/forum:exportownpost', $context)));
4032 if ($canexport) {
4033 require_once($CFG->libdir.'/portfoliolib.php');
4036 $files = $fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id, "timemodified", false);
4037 if ($files) {
4038 if ($canexport) {
4039 $button = new portfolio_add_button();
4041 foreach ($files as $file) {
4042 $filename = $file->get_filename();
4043 $mimetype = $file->get_mimetype();
4044 $iconimage = $OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file), 'moodle', array('class' => 'icon'));
4045 $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$context->id.'/mod_forum/attachment/'.$post->id.'/'.$filename);
4047 if ($type == 'html') {
4048 $output .= "<a href=\"$path\">$iconimage</a> ";
4049 $output .= "<a href=\"$path\">".s($filename)."</a>";
4050 if ($canexport) {
4051 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
4052 $button->set_format_by_file($file);
4053 $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
4055 $output .= "<br />";
4057 } else if ($type == 'text') {
4058 $output .= "$strattachment ".s($filename).":\n$path\n";
4060 } else { //'returnimages'
4061 if (in_array($mimetype, array('image/gif', 'image/jpeg', 'image/png'))) {
4062 // Image attachments don't get printed as links
4063 $imagereturn .= "<br /><img src=\"$path\" alt=\"\" />";
4064 if ($canexport) {
4065 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
4066 $button->set_format_by_file($file);
4067 $imagereturn .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
4069 } else {
4070 $output .= "<a href=\"$path\">$iconimage</a> ";
4071 $output .= format_text("<a href=\"$path\">".s($filename)."</a>", FORMAT_HTML, array('context'=>$context));
4072 if ($canexport) {
4073 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
4074 $button->set_format_by_file($file);
4075 $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
4077 $output .= '<br />';
4081 if (!empty($CFG->enableplagiarism)) {
4082 require_once($CFG->libdir.'/plagiarismlib.php');
4083 $output .= plagiarism_get_links(array('userid' => $post->userid,
4084 'file' => $file,
4085 'cmid' => $cm->id,
4086 'course' => $post->course,
4087 'forum' => $post->forum));
4088 $output .= '<br />';
4093 if ($type !== 'separateimages') {
4094 return $output;
4096 } else {
4097 return array($output, $imagereturn);
4101 ////////////////////////////////////////////////////////////////////////////////
4102 // File API //
4103 ////////////////////////////////////////////////////////////////////////////////
4106 * Lists all browsable file areas
4108 * @package mod_forum
4109 * @category files
4110 * @param stdClass $course course object
4111 * @param stdClass $cm course module object
4112 * @param stdClass $context context object
4113 * @return array
4115 function forum_get_file_areas($course, $cm, $context) {
4116 return array(
4117 'attachment' => get_string('areaattachment', 'mod_forum'),
4118 'post' => get_string('areapost', 'mod_forum'),
4123 * File browsing support for forum module.
4125 * @package mod_forum
4126 * @category files
4127 * @param stdClass $browser file browser object
4128 * @param stdClass $areas file areas
4129 * @param stdClass $course course object
4130 * @param stdClass $cm course module
4131 * @param stdClass $context context module
4132 * @param string $filearea file area
4133 * @param int $itemid item ID
4134 * @param string $filepath file path
4135 * @param string $filename file name
4136 * @return file_info instance or null if not found
4138 function forum_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
4139 global $CFG, $DB, $USER;
4141 if ($context->contextlevel != CONTEXT_MODULE) {
4142 return null;
4145 // filearea must contain a real area
4146 if (!isset($areas[$filearea])) {
4147 return null;
4150 // Note that forum_user_can_see_post() additionally allows access for parent roles
4151 // and it explicitly checks qanda forum type, too. One day, when we stop requiring
4152 // course:managefiles, we will need to extend this.
4153 if (!has_capability('mod/forum:viewdiscussion', $context)) {
4154 return null;
4157 if (is_null($itemid)) {
4158 require_once($CFG->dirroot.'/mod/forum/locallib.php');
4159 return new forum_file_info_container($browser, $course, $cm, $context, $areas, $filearea);
4162 static $cached = array();
4163 // $cached will store last retrieved post, discussion and forum. To make sure that the cache
4164 // is cleared between unit tests we check if this is the same session
4165 if (!isset($cached['sesskey']) || $cached['sesskey'] != sesskey()) {
4166 $cached = array('sesskey' => sesskey());
4169 if (isset($cached['post']) && $cached['post']->id == $itemid) {
4170 $post = $cached['post'];
4171 } else if ($post = $DB->get_record('forum_posts', array('id' => $itemid))) {
4172 $cached['post'] = $post;
4173 } else {
4174 return null;
4177 if (isset($cached['discussion']) && $cached['discussion']->id == $post->discussion) {
4178 $discussion = $cached['discussion'];
4179 } else if ($discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion))) {
4180 $cached['discussion'] = $discussion;
4181 } else {
4182 return null;
4185 if (isset($cached['forum']) && $cached['forum']->id == $cm->instance) {
4186 $forum = $cached['forum'];
4187 } else if ($forum = $DB->get_record('forum', array('id' => $cm->instance))) {
4188 $cached['forum'] = $forum;
4189 } else {
4190 return null;
4193 $fs = get_file_storage();
4194 $filepath = is_null($filepath) ? '/' : $filepath;
4195 $filename = is_null($filename) ? '.' : $filename;
4196 if (!($storedfile = $fs->get_file($context->id, 'mod_forum', $filearea, $itemid, $filepath, $filename))) {
4197 return null;
4200 // Checks to see if the user can manage files or is the owner.
4201 // TODO MDL-33805 - Do not use userid here and move the capability check above.
4202 if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
4203 return null;
4205 // Make sure groups allow this user to see this file
4206 if ($discussion->groupid > 0 && !has_capability('moodle/site:accessallgroups', $context)) {
4207 $groupmode = groups_get_activity_groupmode($cm, $course);
4208 if ($groupmode == SEPARATEGROUPS && !groups_is_member($discussion->groupid)) {
4209 return null;
4213 // Make sure we're allowed to see it...
4214 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4215 return null;
4218 $urlbase = $CFG->wwwroot.'/pluginfile.php';
4219 return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
4223 * Serves the forum attachments. Implements needed access control ;-)
4225 * @package mod_forum
4226 * @category files
4227 * @param stdClass $course course object
4228 * @param stdClass $cm course module object
4229 * @param stdClass $context context object
4230 * @param string $filearea file area
4231 * @param array $args extra arguments
4232 * @param bool $forcedownload whether or not force download
4233 * @param array $options additional options affecting the file serving
4234 * @return bool false if file not found, does not return if found - justsend the file
4236 function forum_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
4237 global $CFG, $DB;
4239 if ($context->contextlevel != CONTEXT_MODULE) {
4240 return false;
4243 require_course_login($course, true, $cm);
4245 $areas = forum_get_file_areas($course, $cm, $context);
4247 // filearea must contain a real area
4248 if (!isset($areas[$filearea])) {
4249 return false;
4252 $postid = (int)array_shift($args);
4254 if (!$post = $DB->get_record('forum_posts', array('id'=>$postid))) {
4255 return false;
4258 if (!$discussion = $DB->get_record('forum_discussions', array('id'=>$post->discussion))) {
4259 return false;
4262 if (!$forum = $DB->get_record('forum', array('id'=>$cm->instance))) {
4263 return false;
4266 $fs = get_file_storage();
4267 $relativepath = implode('/', $args);
4268 $fullpath = "/$context->id/mod_forum/$filearea/$postid/$relativepath";
4269 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
4270 return false;
4273 // Make sure groups allow this user to see this file
4274 if ($discussion->groupid > 0) {
4275 $groupmode = groups_get_activity_groupmode($cm, $course);
4276 if ($groupmode == SEPARATEGROUPS) {
4277 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
4278 return false;
4283 // Make sure we're allowed to see it...
4284 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4285 return false;
4288 // finally send the file
4289 send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security!
4293 * If successful, this function returns the name of the file
4295 * @global object
4296 * @param object $post is a full post record, including course and forum
4297 * @param object $forum
4298 * @param object $cm
4299 * @param mixed $mform
4300 * @param string $unused
4301 * @return bool
4303 function forum_add_attachment($post, $forum, $cm, $mform=null, $unused=null) {
4304 global $DB;
4306 if (empty($mform)) {
4307 return false;
4310 if (empty($post->attachments)) {
4311 return true; // Nothing to do
4314 $context = context_module::instance($cm->id);
4316 $info = file_get_draft_area_info($post->attachments);
4317 $present = ($info['filecount']>0) ? '1' : '';
4318 file_save_draft_area_files($post->attachments, $context->id, 'mod_forum', 'attachment', $post->id,
4319 mod_forum_post_form::attachment_options($forum));
4321 $DB->set_field('forum_posts', 'attachment', $present, array('id'=>$post->id));
4323 return true;
4327 * Add a new post in an existing discussion.
4329 * @global object
4330 * @global object
4331 * @global object
4332 * @param object $post
4333 * @param mixed $mform
4334 * @param string $message
4335 * @return int
4337 function forum_add_new_post($post, $mform, &$message) {
4338 global $USER, $CFG, $DB;
4340 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
4341 $forum = $DB->get_record('forum', array('id' => $discussion->forum));
4342 $cm = get_coursemodule_from_instance('forum', $forum->id);
4343 $context = context_module::instance($cm->id);
4345 $post->created = $post->modified = time();
4346 $post->mailed = FORUM_MAILED_PENDING;
4347 $post->userid = $USER->id;
4348 $post->attachment = "";
4350 $post->id = $DB->insert_record("forum_posts", $post);
4351 $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id,
4352 mod_forum_post_form::editor_options($context, null), $post->message);
4353 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
4354 forum_add_attachment($post, $forum, $cm, $mform, $message);
4356 // Update discussion modified date
4357 $DB->set_field("forum_discussions", "timemodified", $post->modified, array("id" => $post->discussion));
4358 $DB->set_field("forum_discussions", "usermodified", $post->userid, array("id" => $post->discussion));
4360 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4361 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4364 // Let Moodle know that assessable content is uploaded (eg for plagiarism detection)
4365 forum_trigger_content_uploaded_event($post, $cm, 'forum_add_new_post');
4367 return $post->id;
4371 * Update a post
4373 * @global object
4374 * @global object
4375 * @global object
4376 * @param object $post
4377 * @param mixed $mform
4378 * @param string $message
4379 * @return bool
4381 function forum_update_post($post, $mform, &$message) {
4382 global $USER, $CFG, $DB;
4384 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
4385 $forum = $DB->get_record('forum', array('id' => $discussion->forum));
4386 $cm = get_coursemodule_from_instance('forum', $forum->id);
4387 $context = context_module::instance($cm->id);
4389 $post->modified = time();
4391 $DB->update_record('forum_posts', $post);
4393 $discussion->timemodified = $post->modified; // last modified tracking
4394 $discussion->usermodified = $post->userid; // last modified tracking
4396 if (!$post->parent) { // Post is a discussion starter - update discussion title and times too
4397 $discussion->name = $post->subject;
4398 $discussion->timestart = $post->timestart;
4399 $discussion->timeend = $post->timeend;
4401 $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id,
4402 mod_forum_post_form::editor_options($context, $post->id), $post->message);
4403 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
4405 $DB->update_record('forum_discussions', $discussion);
4407 forum_add_attachment($post, $forum, $cm, $mform, $message);
4409 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4410 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4413 // Let Moodle know that assessable content is uploaded (eg for plagiarism detection)
4414 forum_trigger_content_uploaded_event($post, $cm, 'forum_update_post');
4416 return true;
4420 * Given an object containing all the necessary data,
4421 * create a new discussion and return the id
4423 * @param object $post
4424 * @param mixed $mform
4425 * @param string $unused
4426 * @param int $userid
4427 * @return object
4429 function forum_add_discussion($discussion, $mform=null, $unused=null, $userid=null) {
4430 global $USER, $CFG, $DB;
4432 $timenow = time();
4434 if (is_null($userid)) {
4435 $userid = $USER->id;
4438 // The first post is stored as a real post, and linked
4439 // to from the discuss entry.
4441 $forum = $DB->get_record('forum', array('id'=>$discussion->forum));
4442 $cm = get_coursemodule_from_instance('forum', $forum->id);
4444 $post = new stdClass();
4445 $post->discussion = 0;
4446 $post->parent = 0;
4447 $post->userid = $userid;
4448 $post->created = $timenow;
4449 $post->modified = $timenow;
4450 $post->mailed = FORUM_MAILED_PENDING;
4451 $post->subject = $discussion->name;
4452 $post->message = $discussion->message;
4453 $post->messageformat = $discussion->messageformat;
4454 $post->messagetrust = $discussion->messagetrust;
4455 $post->attachments = isset($discussion->attachments) ? $discussion->attachments : null;
4456 $post->forum = $forum->id; // speedup
4457 $post->course = $forum->course; // speedup
4458 $post->mailnow = $discussion->mailnow;
4460 $post->id = $DB->insert_record("forum_posts", $post);
4462 // TODO: Fix the calling code so that there always is a $cm when this function is called
4463 if (!empty($cm->id) && !empty($discussion->itemid)) { // In "single simple discussions" this may not exist yet
4464 $context = context_module::instance($cm->id);
4465 $text = file_save_draft_area_files($discussion->itemid, $context->id, 'mod_forum', 'post', $post->id,
4466 mod_forum_post_form::editor_options($context, null), $post->message);
4467 $DB->set_field('forum_posts', 'message', $text, array('id'=>$post->id));
4470 // Now do the main entry for the discussion, linking to this first post
4472 $discussion->firstpost = $post->id;
4473 $discussion->timemodified = $timenow;
4474 $discussion->usermodified = $post->userid;
4475 $discussion->userid = $userid;
4477 $post->discussion = $DB->insert_record("forum_discussions", $discussion);
4479 // Finally, set the pointer on the post.
4480 $DB->set_field("forum_posts", "discussion", $post->discussion, array("id"=>$post->id));
4482 if (!empty($cm->id)) {
4483 forum_add_attachment($post, $forum, $cm, $mform, $unused);
4486 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4487 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4490 // Let Moodle know that assessable content is uploaded (eg for plagiarism detection)
4491 if (!empty($cm->id)) {
4492 forum_trigger_content_uploaded_event($post, $cm, 'forum_add_discussion');
4495 return $post->discussion;
4500 * Deletes a discussion and handles all associated cleanup.
4502 * @global object
4503 * @param object $discussion Discussion to delete
4504 * @param bool $fulldelete True when deleting entire forum
4505 * @param object $course Course
4506 * @param object $cm Course-module
4507 * @param object $forum Forum
4508 * @return bool
4510 function forum_delete_discussion($discussion, $fulldelete, $course, $cm, $forum) {
4511 global $DB, $CFG;
4512 require_once($CFG->libdir.'/completionlib.php');
4514 $result = true;
4516 if ($posts = $DB->get_records("forum_posts", array("discussion" => $discussion->id))) {
4517 foreach ($posts as $post) {
4518 $post->course = $discussion->course;
4519 $post->forum = $discussion->forum;
4520 if (!forum_delete_post($post, 'ignore', $course, $cm, $forum, $fulldelete)) {
4521 $result = false;
4526 forum_tp_delete_read_records(-1, -1, $discussion->id);
4528 if (!$DB->delete_records("forum_discussions", array("id"=>$discussion->id))) {
4529 $result = false;
4532 // Update completion state if we are tracking completion based on number of posts
4533 // But don't bother when deleting whole thing
4534 if (!$fulldelete) {
4535 $completion = new completion_info($course);
4536 if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC &&
4537 ($forum->completiondiscussions || $forum->completionreplies || $forum->completionposts)) {
4538 $completion->update_state($cm, COMPLETION_INCOMPLETE, $discussion->userid);
4542 return $result;
4547 * Deletes a single forum post.
4549 * @global object
4550 * @param object $post Forum post object
4551 * @param mixed $children Whether to delete children. If false, returns false
4552 * if there are any children (without deleting the post). If true,
4553 * recursively deletes all children. If set to special value 'ignore', deletes
4554 * post regardless of children (this is for use only when deleting all posts
4555 * in a disussion).
4556 * @param object $course Course
4557 * @param object $cm Course-module
4558 * @param object $forum Forum
4559 * @param bool $skipcompletion True to skip updating completion state if it
4560 * would otherwise be updated, i.e. when deleting entire forum anyway.
4561 * @return bool
4563 function forum_delete_post($post, $children, $course, $cm, $forum, $skipcompletion=false) {
4564 global $DB, $CFG;
4565 require_once($CFG->libdir.'/completionlib.php');
4567 $context = context_module::instance($cm->id);
4569 if ($children !== 'ignore' && ($childposts = $DB->get_records('forum_posts', array('parent'=>$post->id)))) {
4570 if ($children) {
4571 foreach ($childposts as $childpost) {
4572 forum_delete_post($childpost, true, $course, $cm, $forum, $skipcompletion);
4574 } else {
4575 return false;
4579 // Delete ratings.
4580 require_once($CFG->dirroot.'/rating/lib.php');
4581 $delopt = new stdClass;
4582 $delopt->contextid = $context->id;
4583 $delopt->component = 'mod_forum';
4584 $delopt->ratingarea = 'post';
4585 $delopt->itemid = $post->id;
4586 $rm = new rating_manager();
4587 $rm->delete_ratings($delopt);
4589 // Delete attachments.
4590 $fs = get_file_storage();
4591 $fs->delete_area_files($context->id, 'mod_forum', 'attachment', $post->id);
4592 $fs->delete_area_files($context->id, 'mod_forum', 'post', $post->id);
4594 // Delete cached RSS feeds.
4595 if (!empty($CFG->enablerssfeeds)) {
4596 require_once($CFG->dirroot.'/mod/forum/rsslib.php');
4597 forum_rss_delete_file($forum);
4600 if ($DB->delete_records("forum_posts", array("id" => $post->id))) {
4602 forum_tp_delete_read_records(-1, $post->id);
4604 // Just in case we are deleting the last post
4605 forum_discussion_update_last_post($post->discussion);
4607 // Update completion state if we are tracking completion based on number of posts
4608 // But don't bother when deleting whole thing
4610 if (!$skipcompletion) {
4611 $completion = new completion_info($course);
4612 if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC &&
4613 ($forum->completiondiscussions || $forum->completionreplies || $forum->completionposts)) {
4614 $completion->update_state($cm, COMPLETION_INCOMPLETE, $post->userid);
4618 return true;
4620 return false;
4624 * Sends post content to plagiarism plugin
4625 * @param object $post Forum post object
4626 * @param object $cm Course-module
4627 * @param string $name
4628 * @return bool
4630 function forum_trigger_content_uploaded_event($post, $cm, $name) {
4631 $context = context_module::instance($cm->id);
4632 $fs = get_file_storage();
4633 $files = $fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id, "timemodified", false);
4634 $params = array(
4635 'context' => $context,
4636 'objectid' => $post->id,
4637 'other' => array(
4638 'content' => $post->message,
4639 'discussionid' => $post->discussion,
4640 'pathnamehashes' => array_keys($files),
4641 'triggeredfrom' => $name,
4644 $event = \mod_forum\event\assessable_uploaded::create($params);
4645 $event->trigger();
4646 return true;
4650 * @global object
4651 * @param object $post
4652 * @param bool $children
4653 * @return int
4655 function forum_count_replies($post, $children=true) {
4656 global $DB;
4657 $count = 0;
4659 if ($children) {
4660 if ($childposts = $DB->get_records('forum_posts', array('parent' => $post->id))) {
4661 foreach ($childposts as $childpost) {
4662 $count ++; // For this child
4663 $count += forum_count_replies($childpost, true);
4666 } else {
4667 $count += $DB->count_records('forum_posts', array('parent' => $post->id));
4670 return $count;
4675 * @global object
4676 * @param int $forumid
4677 * @param mixed $value
4678 * @return bool
4680 function forum_forcesubscribe($forumid, $value=1) {
4681 global $DB;
4682 return $DB->set_field("forum", "forcesubscribe", $value, array("id" => $forumid));
4686 * @global object
4687 * @param object $forum
4688 * @return bool
4690 function forum_is_forcesubscribed($forum) {
4691 global $DB;
4692 if (isset($forum->forcesubscribe)) { // then we use that
4693 return ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE);
4694 } else { // Check the database
4695 return ($DB->get_field('forum', 'forcesubscribe', array('id' => $forum)) == FORUM_FORCESUBSCRIBE);
4699 function forum_get_forcesubscribed($forum) {
4700 global $DB;
4701 if (isset($forum->forcesubscribe)) { // then we use that
4702 return $forum->forcesubscribe;
4703 } else { // Check the database
4704 return $DB->get_field('forum', 'forcesubscribe', array('id' => $forum));
4709 * @global object
4710 * @param int $userid
4711 * @param object $forum
4712 * @return bool
4714 function forum_is_subscribed($userid, $forum) {
4715 global $DB;
4716 if (is_numeric($forum)) {
4717 $forum = $DB->get_record('forum', array('id' => $forum));
4719 // If forum is force subscribed and has allowforcesubscribe, then user is subscribed.
4720 $cm = get_coursemodule_from_instance('forum', $forum->id);
4721 if (forum_is_forcesubscribed($forum) && $cm &&
4722 has_capability('mod/forum:allowforcesubscribe', context_module::instance($cm->id), $userid)) {
4723 return true;
4725 return $DB->record_exists("forum_subscriptions", array("userid" => $userid, "forum" => $forum->id));
4728 function forum_get_subscribed_forums($course) {
4729 global $USER, $CFG, $DB;
4730 $sql = "SELECT f.id
4731 FROM {forum} f
4732 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = ?)
4733 WHERE f.course = ?
4734 AND f.forcesubscribe <> ".FORUM_DISALLOWSUBSCRIBE."
4735 AND (f.forcesubscribe = ".FORUM_FORCESUBSCRIBE." OR fs.id IS NOT NULL)";
4736 if ($subscribed = $DB->get_records_sql($sql, array($USER->id, $course->id))) {
4737 foreach ($subscribed as $s) {
4738 $subscribed[$s->id] = $s->id;
4740 return $subscribed;
4741 } else {
4742 return array();
4747 * Returns an array of forums that the current user is subscribed to and is allowed to unsubscribe from
4749 * @return array An array of unsubscribable forums
4751 function forum_get_optional_subscribed_forums() {
4752 global $USER, $DB;
4754 // Get courses that $USER is enrolled in and can see
4755 $courses = enrol_get_my_courses();
4756 if (empty($courses)) {
4757 return array();
4760 $courseids = array();
4761 foreach($courses as $course) {
4762 $courseids[] = $course->id;
4764 list($coursesql, $courseparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'c');
4766 // get all forums from the user's courses that they are subscribed to and which are not set to forced
4767 $sql = "SELECT f.id, cm.id as cm, cm.visible
4768 FROM {forum} f
4769 JOIN {course_modules} cm ON cm.instance = f.id
4770 JOIN {modules} m ON m.name = :modulename AND m.id = cm.module
4771 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = :userid)
4772 WHERE f.forcesubscribe <> :forcesubscribe AND fs.id IS NOT NULL
4773 AND cm.course $coursesql";
4774 $params = array_merge($courseparams, array('modulename'=>'forum', 'userid'=>$USER->id, 'forcesubscribe'=>FORUM_FORCESUBSCRIBE));
4775 if (!$forums = $DB->get_records_sql($sql, $params)) {
4776 return array();
4779 $unsubscribableforums = array(); // Array to return
4781 foreach($forums as $forum) {
4783 if (empty($forum->visible)) {
4784 // the forum is hidden
4785 $context = context_module::instance($forum->cm);
4786 if (!has_capability('moodle/course:viewhiddenactivities', $context)) {
4787 // the user can't see the hidden forum
4788 continue;
4792 // subscribe.php only requires 'mod/forum:managesubscriptions' when
4793 // unsubscribing a user other than yourself so we don't require it here either
4795 // A check for whether the forum has subscription set to forced is built into the SQL above
4797 $unsubscribableforums[] = $forum;
4800 return $unsubscribableforums;
4804 * Adds user to the subscriber list
4806 * @global object
4807 * @param int $userid
4808 * @param int $forumid
4810 function forum_subscribe($userid, $forumid) {
4811 global $DB;
4813 if ($DB->record_exists("forum_subscriptions", array("userid"=>$userid, "forum"=>$forumid))) {
4814 return true;
4817 $sub = new stdClass();
4818 $sub->userid = $userid;
4819 $sub->forum = $forumid;
4821 return $DB->insert_record("forum_subscriptions", $sub);
4825 * Removes user from the subscriber list
4827 * @global object
4828 * @param int $userid
4829 * @param int $forumid
4831 function forum_unsubscribe($userid, $forumid) {
4832 global $DB;
4833 return ($DB->delete_records('forum_digests', array('userid' => $userid, 'forum' => $forumid))
4834 && $DB->delete_records('forum_subscriptions', array('userid' => $userid, 'forum' => $forumid)));
4838 * Given a new post, subscribes or unsubscribes as appropriate.
4839 * Returns some text which describes what happened.
4841 * @global objec
4842 * @param object $post
4843 * @param object $forum
4845 function forum_post_subscription($post, $forum) {
4847 global $USER;
4849 $action = '';
4850 $subscribed = forum_is_subscribed($USER->id, $forum);
4852 if ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE) { // database ignored
4853 return "";
4855 } elseif (($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE)
4856 && !has_capability('moodle/course:manageactivities', context_course::instance($forum->course), $USER->id)) {
4857 if ($subscribed) {
4858 $action = 'unsubscribe'; // sanity check, following MDL-14558
4859 } else {
4860 return "";
4863 } else { // go with the user's choice
4864 if (isset($post->subscribe)) {
4865 // no change
4866 if ((!empty($post->subscribe) && $subscribed)
4867 || (empty($post->subscribe) && !$subscribed)) {
4868 return "";
4870 } elseif (!empty($post->subscribe) && !$subscribed) {
4871 $action = 'subscribe';
4873 } elseif (empty($post->subscribe) && $subscribed) {
4874 $action = 'unsubscribe';
4879 $info = new stdClass();
4880 $info->name = fullname($USER);
4881 $info->forum = format_string($forum->name);
4883 switch ($action) {
4884 case 'subscribe':
4885 forum_subscribe($USER->id, $post->forum);
4886 return "<p>".get_string("nowsubscribed", "forum", $info)."</p>";
4887 case 'unsubscribe':
4888 forum_unsubscribe($USER->id, $post->forum);
4889 return "<p>".get_string("nownotsubscribed", "forum", $info)."</p>";
4894 * Generate and return the subscribe or unsubscribe link for a forum.
4896 * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
4897 * @param object $context the context object for this forum.
4898 * @param array $messages text used for the link in its various states
4899 * (subscribed, unsubscribed, forcesubscribed or cantsubscribe).
4900 * Any strings not passed in are taken from the $defaultmessages array
4901 * at the top of the function.
4902 * @param bool $cantaccessagroup
4903 * @param bool $fakelink
4904 * @param bool $backtoindex
4905 * @param array $subscribed_forums
4906 * @return string
4908 function forum_get_subscribe_link($forum, $context, $messages = array(), $cantaccessagroup = false, $fakelink=true, $backtoindex=false, $subscribed_forums=null) {
4909 global $CFG, $USER, $PAGE, $OUTPUT;
4910 $defaultmessages = array(
4911 'subscribed' => get_string('unsubscribe', 'forum'),
4912 'unsubscribed' => get_string('subscribe', 'forum'),
4913 'cantaccessgroup' => get_string('no'),
4914 'forcesubscribed' => get_string('everyoneissubscribed', 'forum'),
4915 'cantsubscribe' => get_string('disallowsubscribe','forum')
4917 $messages = $messages + $defaultmessages;
4919 if (forum_is_forcesubscribed($forum)) {
4920 return $messages['forcesubscribed'];
4921 } else if ($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE && !has_capability('mod/forum:managesubscriptions', $context)) {
4922 return $messages['cantsubscribe'];
4923 } else if ($cantaccessagroup) {
4924 return $messages['cantaccessgroup'];
4925 } else {
4926 if (!is_enrolled($context, $USER, '', true)) {
4927 return '';
4929 if (is_null($subscribed_forums)) {
4930 $subscribed = forum_is_subscribed($USER->id, $forum);
4931 } else {
4932 $subscribed = !empty($subscribed_forums[$forum->id]);
4934 if ($subscribed) {
4935 $linktext = $messages['subscribed'];
4936 $linktitle = get_string('subscribestop', 'forum');
4937 } else {
4938 $linktext = $messages['unsubscribed'];
4939 $linktitle = get_string('subscribestart', 'forum');
4942 $options = array();
4943 if ($backtoindex) {
4944 $backtoindexlink = '&amp;backtoindex=1';
4945 $options['backtoindex'] = 1;
4946 } else {
4947 $backtoindexlink = '';
4949 $link = '';
4951 if ($fakelink) {
4952 $PAGE->requires->js('/mod/forum/forum.js');
4953 $PAGE->requires->js_function_call('forum_produce_subscribe_link', array($forum->id, $backtoindexlink, $linktext, $linktitle));
4954 $link = "<noscript>";
4956 $options['id'] = $forum->id;
4957 $options['sesskey'] = sesskey();
4958 $url = new moodle_url('/mod/forum/subscribe.php', $options);
4959 $link .= $OUTPUT->single_button($url, $linktext, 'get', array('title'=>$linktitle));
4960 if ($fakelink) {
4961 $link .= '</noscript>';
4964 return $link;
4970 * Generate and return the track or no track link for a forum.
4972 * @global object
4973 * @global object
4974 * @global object
4975 * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
4976 * @param array $messages
4977 * @param bool $fakelink
4978 * @return string
4980 function forum_get_tracking_link($forum, $messages=array(), $fakelink=true) {
4981 global $CFG, $USER, $PAGE, $OUTPUT;
4983 static $strnotrackforum, $strtrackforum;
4985 if (isset($messages['trackforum'])) {
4986 $strtrackforum = $messages['trackforum'];
4988 if (isset($messages['notrackforum'])) {
4989 $strnotrackforum = $messages['notrackforum'];
4991 if (empty($strtrackforum)) {
4992 $strtrackforum = get_string('trackforum', 'forum');
4994 if (empty($strnotrackforum)) {
4995 $strnotrackforum = get_string('notrackforum', 'forum');
4998 if (forum_tp_is_tracked($forum)) {
4999 $linktitle = $strnotrackforum;
5000 $linktext = $strnotrackforum;
5001 } else {
5002 $linktitle = $strtrackforum;
5003 $linktext = $strtrackforum;
5006 $link = '';
5007 if ($fakelink) {
5008 $PAGE->requires->js('/mod/forum/forum.js');
5009 $PAGE->requires->js_function_call('forum_produce_tracking_link', Array($forum->id, $linktext, $linktitle));
5010 // use <noscript> to print button in case javascript is not enabled
5011 $link .= '<noscript>';
5013 $url = new moodle_url('/mod/forum/settracking.php', array('id'=>$forum->id));
5014 $link .= $OUTPUT->single_button($url, $linktext, 'get', array('title'=>$linktitle));
5016 if ($fakelink) {
5017 $link .= '</noscript>';
5020 return $link;
5026 * Returns true if user created new discussion already
5028 * @global object
5029 * @global object
5030 * @param int $forumid
5031 * @param int $userid
5032 * @return bool
5034 function forum_user_has_posted_discussion($forumid, $userid) {
5035 global $CFG, $DB;
5037 $sql = "SELECT 'x'
5038 FROM {forum_discussions} d, {forum_posts} p
5039 WHERE d.forum = ? AND p.discussion = d.id AND p.parent = 0 and p.userid = ?";
5041 return $DB->record_exists_sql($sql, array($forumid, $userid));
5045 * @global object
5046 * @global object
5047 * @param int $forumid
5048 * @param int $userid
5049 * @return array
5051 function forum_discussions_user_has_posted_in($forumid, $userid) {
5052 global $CFG, $DB;
5054 $haspostedsql = "SELECT d.id AS id,
5056 FROM {forum_posts} p,
5057 {forum_discussions} d
5058 WHERE p.discussion = d.id
5059 AND d.forum = ?
5060 AND p.userid = ?";
5062 return $DB->get_records_sql($haspostedsql, array($forumid, $userid));
5066 * @global object
5067 * @global object
5068 * @param int $forumid
5069 * @param int $did
5070 * @param int $userid
5071 * @return bool
5073 function forum_user_has_posted($forumid, $did, $userid) {
5074 global $DB;
5076 if (empty($did)) {
5077 // posted in any forum discussion?
5078 $sql = "SELECT 'x'
5079 FROM {forum_posts} p
5080 JOIN {forum_discussions} d ON d.id = p.discussion
5081 WHERE p.userid = :userid AND d.forum = :forumid";
5082 return $DB->record_exists_sql($sql, array('forumid'=>$forumid,'userid'=>$userid));
5083 } else {
5084 return $DB->record_exists('forum_posts', array('discussion'=>$did,'userid'=>$userid));
5089 * Returns creation time of the first user's post in given discussion
5090 * @global object $DB
5091 * @param int $did Discussion id
5092 * @param int $userid User id
5093 * @return int|bool post creation time stamp or return false
5095 function forum_get_user_posted_time($did, $userid) {
5096 global $DB;
5098 $posttime = $DB->get_field('forum_posts', 'MIN(created)', array('userid'=>$userid, 'discussion'=>$did));
5099 if (empty($posttime)) {
5100 return false;
5102 return $posttime;
5106 * @global object
5107 * @param object $forum
5108 * @param object $currentgroup
5109 * @param int $unused
5110 * @param object $cm
5111 * @param object $context
5112 * @return bool
5114 function forum_user_can_post_discussion($forum, $currentgroup=null, $unused=-1, $cm=NULL, $context=NULL) {
5115 // $forum is an object
5116 global $USER;
5118 // shortcut - guest and not-logged-in users can not post
5119 if (isguestuser() or !isloggedin()) {
5120 return false;
5123 if (!$cm) {
5124 debugging('missing cm', DEBUG_DEVELOPER);
5125 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5126 print_error('invalidcoursemodule');
5130 if (!$context) {
5131 $context = context_module::instance($cm->id);
5134 if ($currentgroup === null) {
5135 $currentgroup = groups_get_activity_group($cm);
5138 $groupmode = groups_get_activity_groupmode($cm);
5140 if ($forum->type == 'news') {
5141 $capname = 'mod/forum:addnews';
5142 } else if ($forum->type == 'qanda') {
5143 $capname = 'mod/forum:addquestion';
5144 } else {
5145 $capname = 'mod/forum:startdiscussion';
5148 if (!has_capability($capname, $context)) {
5149 return false;
5152 if ($forum->type == 'single') {
5153 return false;
5156 if ($forum->type == 'eachuser') {
5157 if (forum_user_has_posted_discussion($forum->id, $USER->id)) {
5158 return false;
5162 if (!$groupmode or has_capability('moodle/site:accessallgroups', $context)) {
5163 return true;
5166 if ($currentgroup) {
5167 return groups_is_member($currentgroup);
5168 } else {
5169 // no group membership and no accessallgroups means no new discussions
5170 // reverted to 1.7 behaviour in 1.9+, buggy in 1.8.0-1.9.0
5171 return false;
5176 * This function checks whether the user can reply to posts in a forum
5177 * discussion. Use forum_user_can_post_discussion() to check whether the user
5178 * can start discussions.
5180 * @global object
5181 * @global object
5182 * @uses DEBUG_DEVELOPER
5183 * @uses CONTEXT_MODULE
5184 * @uses VISIBLEGROUPS
5185 * @param object $forum forum object
5186 * @param object $discussion
5187 * @param object $user
5188 * @param object $cm
5189 * @param object $course
5190 * @param object $context
5191 * @return bool
5193 function forum_user_can_post($forum, $discussion, $user=NULL, $cm=NULL, $course=NULL, $context=NULL) {
5194 global $USER, $DB;
5195 if (empty($user)) {
5196 $user = $USER;
5199 // shortcut - guest and not-logged-in users can not post
5200 if (isguestuser($user) or empty($user->id)) {
5201 return false;
5204 if (!isset($discussion->groupid)) {
5205 debugging('incorrect discussion parameter', DEBUG_DEVELOPER);
5206 return false;
5209 if (!$cm) {
5210 debugging('missing cm', DEBUG_DEVELOPER);
5211 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5212 print_error('invalidcoursemodule');
5216 if (!$course) {
5217 debugging('missing course', DEBUG_DEVELOPER);
5218 if (!$course = $DB->get_record('course', array('id' => $forum->course))) {
5219 print_error('invalidcourseid');
5223 if (!$context) {
5224 $context = context_module::instance($cm->id);
5227 // normal users with temporary guest access can not post, suspended users can not post either
5228 if (!is_viewing($context, $user->id) and !is_enrolled($context, $user->id, '', true)) {
5229 return false;
5232 if ($forum->type == 'news') {
5233 $capname = 'mod/forum:replynews';
5234 } else {
5235 $capname = 'mod/forum:replypost';
5238 if (!has_capability($capname, $context, $user->id)) {
5239 return false;
5242 if (!$groupmode = groups_get_activity_groupmode($cm, $course)) {
5243 return true;
5246 if (has_capability('moodle/site:accessallgroups', $context)) {
5247 return true;
5250 if ($groupmode == VISIBLEGROUPS) {
5251 if ($discussion->groupid == -1) {
5252 // allow students to reply to all participants discussions - this was not possible in Moodle <1.8
5253 return true;
5255 return groups_is_member($discussion->groupid);
5257 } else {
5258 //separate groups
5259 if ($discussion->groupid == -1) {
5260 return false;
5262 return groups_is_member($discussion->groupid);
5267 * Checks to see if a user can view a particular post.
5269 * @deprecated since Moodle 2.4 use forum_user_can_see_post() instead
5271 * @param object $post
5272 * @param object $course
5273 * @param object $cm
5274 * @param object $forum
5275 * @param object $discussion
5276 * @param object $user
5277 * @return boolean
5279 function forum_user_can_view_post($post, $course, $cm, $forum, $discussion, $user=null){
5280 debugging('forum_user_can_view_post() is deprecated. Please use forum_user_can_see_post() instead.', DEBUG_DEVELOPER);
5281 return forum_user_can_see_post($forum, $discussion, $post, $user, $cm);
5285 * Check to ensure a user can view a timed discussion.
5287 * @param object $discussion
5288 * @param object $user
5289 * @param object $context
5290 * @return boolean returns true if they can view post, false otherwise
5292 function forum_user_can_see_timed_discussion($discussion, $user, $context) {
5293 global $CFG;
5295 // Check that the user can view a discussion that is normally hidden due to access times.
5296 if (!empty($CFG->forum_enabletimedposts)) {
5297 $time = time();
5298 if (($discussion->timestart != 0 && $discussion->timestart > $time)
5299 || ($discussion->timeend != 0 && $discussion->timeend < $time)) {
5300 if (!has_capability('mod/forum:viewhiddentimedposts', $context, $user->id)) {
5301 return false;
5306 return true;
5310 * Check to ensure a user can view a group discussion.
5312 * @param object $discussion
5313 * @param object $cm
5314 * @param object $context
5315 * @return boolean returns true if they can view post, false otherwise
5317 function forum_user_can_see_group_discussion($discussion, $cm, $context) {
5319 // If it's a grouped discussion, make sure the user is a member.
5320 if ($discussion->groupid > 0) {
5321 $groupmode = groups_get_activity_groupmode($cm);
5322 if ($groupmode == SEPARATEGROUPS) {
5323 return groups_is_member($discussion->groupid) || has_capability('moodle/site:accessallgroups', $context);
5327 return true;
5331 * @global object
5332 * @global object
5333 * @uses DEBUG_DEVELOPER
5334 * @param object $forum
5335 * @param object $discussion
5336 * @param object $context
5337 * @param object $user
5338 * @return bool
5340 function forum_user_can_see_discussion($forum, $discussion, $context, $user=NULL) {
5341 global $USER, $DB;
5343 if (empty($user) || empty($user->id)) {
5344 $user = $USER;
5347 // retrieve objects (yuk)
5348 if (is_numeric($forum)) {
5349 debugging('missing full forum', DEBUG_DEVELOPER);
5350 if (!$forum = $DB->get_record('forum',array('id'=>$forum))) {
5351 return false;
5354 if (is_numeric($discussion)) {
5355 debugging('missing full discussion', DEBUG_DEVELOPER);
5356 if (!$discussion = $DB->get_record('forum_discussions',array('id'=>$discussion))) {
5357 return false;
5360 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5361 print_error('invalidcoursemodule');
5364 if (!has_capability('mod/forum:viewdiscussion', $context)) {
5365 return false;
5368 if (!forum_user_can_see_timed_discussion($discussion, $user, $context)) {
5369 return false;
5372 if (!forum_user_can_see_group_discussion($discussion, $cm, $context)) {
5373 return false;
5376 if ($forum->type == 'qanda' &&
5377 !forum_user_has_posted($forum->id, $discussion->id, $user->id) &&
5378 !has_capability('mod/forum:viewqandawithoutposting', $context)) {
5379 return false;
5381 return true;
5385 * @global object
5386 * @global object
5387 * @param object $forum
5388 * @param object $discussion
5389 * @param object $post
5390 * @param object $user
5391 * @param object $cm
5392 * @return bool
5394 function forum_user_can_see_post($forum, $discussion, $post, $user=NULL, $cm=NULL) {
5395 global $CFG, $USER, $DB;
5397 // Context used throughout function.
5398 $modcontext = context_module::instance($cm->id);
5400 // retrieve objects (yuk)
5401 if (is_numeric($forum)) {
5402 debugging('missing full forum', DEBUG_DEVELOPER);
5403 if (!$forum = $DB->get_record('forum',array('id'=>$forum))) {
5404 return false;
5408 if (is_numeric($discussion)) {
5409 debugging('missing full discussion', DEBUG_DEVELOPER);
5410 if (!$discussion = $DB->get_record('forum_discussions',array('id'=>$discussion))) {
5411 return false;
5414 if (is_numeric($post)) {
5415 debugging('missing full post', DEBUG_DEVELOPER);
5416 if (!$post = $DB->get_record('forum_posts',array('id'=>$post))) {
5417 return false;
5421 if (!isset($post->id) && isset($post->parent)) {
5422 $post->id = $post->parent;
5425 if (!$cm) {
5426 debugging('missing cm', DEBUG_DEVELOPER);
5427 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5428 print_error('invalidcoursemodule');
5432 if (empty($user) || empty($user->id)) {
5433 $user = $USER;
5436 $canviewdiscussion = !empty($cm->cache->caps['mod/forum:viewdiscussion']) || has_capability('mod/forum:viewdiscussion', $modcontext, $user->id);
5437 if (!$canviewdiscussion && !has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), context_user::instance($post->userid))) {
5438 return false;
5441 if (isset($cm->uservisible)) {
5442 if (!$cm->uservisible) {
5443 return false;
5445 } else {
5446 if (!coursemodule_visible_for_user($cm, $user->id)) {
5447 return false;
5451 if (!forum_user_can_see_timed_discussion($discussion, $user, $modcontext)) {
5452 return false;
5455 if (!forum_user_can_see_group_discussion($discussion, $cm, $modcontext)) {
5456 return false;
5459 if ($forum->type == 'qanda') {
5460 $firstpost = forum_get_firstpost_from_discussion($discussion->id);
5461 $userfirstpost = forum_get_user_posted_time($discussion->id, $user->id);
5463 return (($userfirstpost !== false && (time() - $userfirstpost >= $CFG->maxeditingtime)) ||
5464 $firstpost->id == $post->id || $post->userid == $user->id || $firstpost->userid == $user->id ||
5465 has_capability('mod/forum:viewqandawithoutposting', $modcontext, $user->id));
5467 return true;
5472 * Prints the discussion view screen for a forum.
5474 * @global object
5475 * @global object
5476 * @param object $course The current course object.
5477 * @param object $forum Forum to be printed.
5478 * @param int $maxdiscussions .
5479 * @param string $displayformat The display format to use (optional).
5480 * @param string $sort Sort arguments for database query (optional).
5481 * @param int $groupmode Group mode of the forum (optional).
5482 * @param void $unused (originally current group)
5483 * @param int $page Page mode, page to display (optional).
5484 * @param int $perpage The maximum number of discussions per page(optional)
5487 function forum_print_latest_discussions($course, $forum, $maxdiscussions=-1, $displayformat='plain', $sort='',
5488 $currentgroup=-1, $groupmode=-1, $page=-1, $perpage=100, $cm=NULL) {
5489 global $CFG, $USER, $OUTPUT;
5491 if (!$cm) {
5492 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5493 print_error('invalidcoursemodule');
5496 $context = context_module::instance($cm->id);
5498 if (empty($sort)) {
5499 $sort = "d.timemodified DESC";
5502 $olddiscussionlink = false;
5504 // Sort out some defaults
5505 if ($perpage <= 0) {
5506 $perpage = 0;
5507 $page = -1;
5510 if ($maxdiscussions == 0) {
5511 // all discussions - backwards compatibility
5512 $page = -1;
5513 $perpage = 0;
5514 if ($displayformat == 'plain') {
5515 $displayformat = 'header'; // Abbreviate display by default
5518 } else if ($maxdiscussions > 0) {
5519 $page = -1;
5520 $perpage = $maxdiscussions;
5523 $fullpost = false;
5524 if ($displayformat == 'plain') {
5525 $fullpost = true;
5529 // Decide if current user is allowed to see ALL the current discussions or not
5531 // First check the group stuff
5532 if ($currentgroup == -1 or $groupmode == -1) {
5533 $groupmode = groups_get_activity_groupmode($cm, $course);
5534 $currentgroup = groups_get_activity_group($cm);
5537 $groups = array(); //cache
5539 // If the user can post discussions, then this is a good place to put the
5540 // button for it. We do not show the button if we are showing site news
5541 // and the current user is a guest.
5543 $canstart = forum_user_can_post_discussion($forum, $currentgroup, $groupmode, $cm, $context);
5544 if (!$canstart and $forum->type !== 'news') {
5545 if (isguestuser() or !isloggedin()) {
5546 $canstart = true;
5548 if (!is_enrolled($context) and !is_viewing($context)) {
5549 // allow guests and not-logged-in to see the button - they are prompted to log in after clicking the link
5550 // normal users with temporary guest access see this button too, they are asked to enrol instead
5551 // do not show the button to users with suspended enrolments here
5552 $canstart = enrol_selfenrol_available($course->id);
5556 if ($canstart) {
5557 echo '<div class="singlebutton forumaddnew">';
5558 echo "<form id=\"newdiscussionform\" method=\"get\" action=\"$CFG->wwwroot/mod/forum/post.php\">";
5559 echo '<div>';
5560 echo "<input type=\"hidden\" name=\"forum\" value=\"$forum->id\" />";
5561 switch ($forum->type) {
5562 case 'news':
5563 case 'blog':
5564 $buttonadd = get_string('addanewtopic', 'forum');
5565 break;
5566 case 'qanda':
5567 $buttonadd = get_string('addanewquestion', 'forum');
5568 break;
5569 default:
5570 $buttonadd = get_string('addanewdiscussion', 'forum');
5571 break;
5573 echo '<input type="submit" value="'.$buttonadd.'" />';
5574 echo '</div>';
5575 echo '</form>';
5576 echo "</div>\n";
5578 } else if (isguestuser() or !isloggedin() or $forum->type == 'news') {
5579 // no button and no info
5581 } else if ($groupmode and has_capability('mod/forum:startdiscussion', $context)) {
5582 // inform users why they can not post new discussion
5583 if ($currentgroup) {
5584 echo $OUTPUT->notification(get_string('cannotadddiscussion', 'forum'));
5585 } else {
5586 echo $OUTPUT->notification(get_string('cannotadddiscussionall', 'forum'));
5590 // Get all the recent discussions we're allowed to see
5592 $getuserlastmodified = ($displayformat == 'header');
5594 if (! $discussions = forum_get_discussions($cm, $sort, $fullpost, null, $maxdiscussions, $getuserlastmodified, $page, $perpage) ) {
5595 echo '<div class="forumnodiscuss">';
5596 if ($forum->type == 'news') {
5597 echo '('.get_string('nonews', 'forum').')';
5598 } else if ($forum->type == 'qanda') {
5599 echo '('.get_string('noquestions','forum').')';
5600 } else {
5601 echo '('.get_string('nodiscussions', 'forum').')';
5603 echo "</div>\n";
5604 return;
5607 // If we want paging
5608 if ($page != -1) {
5609 ///Get the number of discussions found
5610 $numdiscussions = forum_get_discussions_count($cm);
5612 ///Show the paging bar
5613 echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
5614 if ($numdiscussions > 1000) {
5615 // saves some memory on sites with very large forums
5616 $replies = forum_count_discussion_replies($forum->id, $sort, $maxdiscussions, $page, $perpage);
5617 } else {
5618 $replies = forum_count_discussion_replies($forum->id);
5621 } else {
5622 $replies = forum_count_discussion_replies($forum->id);
5624 if ($maxdiscussions > 0 and $maxdiscussions <= count($discussions)) {
5625 $olddiscussionlink = true;
5629 $canviewparticipants = has_capability('moodle/course:viewparticipants',$context);
5631 $strdatestring = get_string('strftimerecentfull');
5633 // Check if the forum is tracked.
5634 if ($cantrack = forum_tp_can_track_forums($forum)) {
5635 $forumtracked = forum_tp_is_tracked($forum);
5636 } else {
5637 $forumtracked = false;
5640 if ($forumtracked) {
5641 $unreads = forum_get_discussions_unread($cm);
5642 } else {
5643 $unreads = array();
5646 if ($displayformat == 'header') {
5647 echo '<table cellspacing="0" class="forumheaderlist">';
5648 echo '<thead>';
5649 echo '<tr>';
5650 echo '<th class="header topic" scope="col">'.get_string('discussion', 'forum').'</th>';
5651 echo '<th class="header author" colspan="2" scope="col">'.get_string('startedby', 'forum').'</th>';
5652 if ($groupmode > 0) {
5653 echo '<th class="header group" scope="col">'.get_string('group').'</th>';
5655 if (has_capability('mod/forum:viewdiscussion', $context)) {
5656 echo '<th class="header replies" scope="col">'.get_string('replies', 'forum').'</th>';
5657 // If the forum can be tracked, display the unread column.
5658 if ($cantrack) {
5659 echo '<th class="header replies" scope="col">'.get_string('unread', 'forum');
5660 if ($forumtracked) {
5661 echo '<a title="'.get_string('markallread', 'forum').
5662 '" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
5663 $forum->id.'&amp;mark=read&amp;returnpage=view.php">'.
5664 '<img src="'.$OUTPUT->pix_url('t/markasread') . '" class="iconsmall" alt="'.get_string('markallread', 'forum').'" /></a>';
5666 echo '</th>';
5669 echo '<th class="header lastpost" scope="col">'.get_string('lastpost', 'forum').'</th>';
5670 echo '</tr>';
5671 echo '</thead>';
5672 echo '<tbody>';
5675 foreach ($discussions as $discussion) {
5676 if (!empty($replies[$discussion->discussion])) {
5677 $discussion->replies = $replies[$discussion->discussion]->replies;
5678 $discussion->lastpostid = $replies[$discussion->discussion]->lastpostid;
5679 } else {
5680 $discussion->replies = 0;
5683 // SPECIAL CASE: The front page can display a news item post to non-logged in users.
5684 // All posts are read in this case.
5685 if (!$forumtracked) {
5686 $discussion->unread = '-';
5687 } else if (empty($USER)) {
5688 $discussion->unread = 0;
5689 } else {
5690 if (empty($unreads[$discussion->discussion])) {
5691 $discussion->unread = 0;
5692 } else {
5693 $discussion->unread = $unreads[$discussion->discussion];
5697 if (isloggedin()) {
5698 $ownpost = ($discussion->userid == $USER->id);
5699 } else {
5700 $ownpost=false;
5702 // Use discussion name instead of subject of first post
5703 $discussion->subject = $discussion->name;
5705 switch ($displayformat) {
5706 case 'header':
5707 if ($groupmode > 0) {
5708 if (isset($groups[$discussion->groupid])) {
5709 $group = $groups[$discussion->groupid];
5710 } else {
5711 $group = $groups[$discussion->groupid] = groups_get_group($discussion->groupid);
5713 } else {
5714 $group = -1;
5716 forum_print_discussion_header($discussion, $forum, $group, $strdatestring, $cantrack, $forumtracked,
5717 $canviewparticipants, $context);
5718 break;
5719 default:
5720 $link = false;
5722 if ($discussion->replies) {
5723 $link = true;
5724 } else {
5725 $modcontext = context_module::instance($cm->id);
5726 $link = forum_user_can_see_discussion($forum, $discussion, $modcontext, $USER);
5729 $discussion->forum = $forum->id;
5731 forum_print_post($discussion, $discussion, $forum, $cm, $course, $ownpost, 0, $link, false,
5732 '', null, true, $forumtracked);
5733 break;
5737 if ($displayformat == "header") {
5738 echo '</tbody>';
5739 echo '</table>';
5742 if ($olddiscussionlink) {
5743 if ($forum->type == 'news') {
5744 $strolder = get_string('oldertopics', 'forum');
5745 } else {
5746 $strolder = get_string('olderdiscussions', 'forum');
5748 echo '<div class="forumolddiscuss">';
5749 echo '<a href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'&amp;showall=1">';
5750 echo $strolder.'</a> ...</div>';
5753 if ($page != -1) { ///Show the paging bar
5754 echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
5760 * Prints a forum discussion
5762 * @uses CONTEXT_MODULE
5763 * @uses FORUM_MODE_FLATNEWEST
5764 * @uses FORUM_MODE_FLATOLDEST
5765 * @uses FORUM_MODE_THREADED
5766 * @uses FORUM_MODE_NESTED
5767 * @param stdClass $course
5768 * @param stdClass $cm
5769 * @param stdClass $forum
5770 * @param stdClass $discussion
5771 * @param stdClass $post
5772 * @param int $mode
5773 * @param mixed $canreply
5774 * @param bool $canrate
5776 function forum_print_discussion($course, $cm, $forum, $discussion, $post, $mode, $canreply=NULL, $canrate=false) {
5777 global $USER, $CFG;
5779 require_once($CFG->dirroot.'/rating/lib.php');
5781 $ownpost = (isloggedin() && $USER->id == $post->userid);
5783 $modcontext = context_module::instance($cm->id);
5784 if ($canreply === NULL) {
5785 $reply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
5786 } else {
5787 $reply = $canreply;
5790 // $cm holds general cache for forum functions
5791 $cm->cache = new stdClass;
5792 $cm->cache->groups = groups_get_all_groups($course->id, 0, $cm->groupingid);
5793 $cm->cache->usersgroups = array();
5795 $posters = array();
5797 // preload all posts - TODO: improve...
5798 if ($mode == FORUM_MODE_FLATNEWEST) {
5799 $sort = "p.created DESC";
5800 } else {
5801 $sort = "p.created ASC";
5804 $forumtracked = forum_tp_is_tracked($forum);
5805 $posts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
5806 $post = $posts[$post->id];
5808 foreach ($posts as $pid=>$p) {
5809 $posters[$p->userid] = $p->userid;
5812 // preload all groups of ppl that posted in this discussion
5813 if ($postersgroups = groups_get_all_groups($course->id, $posters, $cm->groupingid, 'gm.id, gm.groupid, gm.userid')) {
5814 foreach($postersgroups as $pg) {
5815 if (!isset($cm->cache->usersgroups[$pg->userid])) {
5816 $cm->cache->usersgroups[$pg->userid] = array();
5818 $cm->cache->usersgroups[$pg->userid][$pg->groupid] = $pg->groupid;
5820 unset($postersgroups);
5823 //load ratings
5824 if ($forum->assessed != RATING_AGGREGATE_NONE) {
5825 $ratingoptions = new stdClass;
5826 $ratingoptions->context = $modcontext;
5827 $ratingoptions->component = 'mod_forum';
5828 $ratingoptions->ratingarea = 'post';
5829 $ratingoptions->items = $posts;
5830 $ratingoptions->aggregate = $forum->assessed;//the aggregation method
5831 $ratingoptions->scaleid = $forum->scale;
5832 $ratingoptions->userid = $USER->id;
5833 if ($forum->type == 'single' or !$discussion->id) {
5834 $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/view.php?id=$cm->id";
5835 } else {
5836 $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id";
5838 $ratingoptions->assesstimestart = $forum->assesstimestart;
5839 $ratingoptions->assesstimefinish = $forum->assesstimefinish;
5841 $rm = new rating_manager();
5842 $posts = $rm->get_ratings($ratingoptions);
5846 $post->forum = $forum->id; // Add the forum id to the post object, later used by forum_print_post
5847 $post->forumtype = $forum->type;
5849 $post->subject = format_string($post->subject);
5851 $postread = !empty($post->postread);
5853 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, false,
5854 '', '', $postread, true, $forumtracked);
5856 switch ($mode) {
5857 case FORUM_MODE_FLATOLDEST :
5858 case FORUM_MODE_FLATNEWEST :
5859 default:
5860 forum_print_posts_flat($course, $cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts);
5861 break;
5863 case FORUM_MODE_THREADED :
5864 forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, 0, $reply, $forumtracked, $posts);
5865 break;
5867 case FORUM_MODE_NESTED :
5868 forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
5869 break;
5875 * @global object
5876 * @global object
5877 * @uses FORUM_MODE_FLATNEWEST
5878 * @param object $course
5879 * @param object $cm
5880 * @param object $forum
5881 * @param object $discussion
5882 * @param object $post
5883 * @param object $mode
5884 * @param bool $reply
5885 * @param bool $forumtracked
5886 * @param array $posts
5887 * @return void
5889 function forum_print_posts_flat($course, &$cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts) {
5890 global $USER, $CFG;
5892 $link = false;
5894 if ($mode == FORUM_MODE_FLATNEWEST) {
5895 $sort = "ORDER BY created DESC";
5896 } else {
5897 $sort = "ORDER BY created ASC";
5900 foreach ($posts as $post) {
5901 if (!$post->parent) {
5902 continue;
5904 $post->subject = format_string($post->subject);
5905 $ownpost = ($USER->id == $post->userid);
5907 $postread = !empty($post->postread);
5909 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5910 '', '', $postread, true, $forumtracked);
5915 * @todo Document this function
5917 * @global object
5918 * @global object
5919 * @uses CONTEXT_MODULE
5920 * @return void
5922 function forum_print_posts_threaded($course, &$cm, $forum, $discussion, $parent, $depth, $reply, $forumtracked, $posts) {
5923 global $USER, $CFG;
5925 $link = false;
5927 if (!empty($posts[$parent->id]->children)) {
5928 $posts = $posts[$parent->id]->children;
5930 $modcontext = context_module::instance($cm->id);
5931 $canviewfullnames = has_capability('moodle/site:viewfullnames', $modcontext);
5933 foreach ($posts as $post) {
5935 echo '<div class="indent">';
5936 if ($depth > 0) {
5937 $ownpost = ($USER->id == $post->userid);
5938 $post->subject = format_string($post->subject);
5940 $postread = !empty($post->postread);
5942 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5943 '', '', $postread, true, $forumtracked);
5944 } else {
5945 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
5946 echo "</div>\n";
5947 continue;
5949 $by = new stdClass();
5950 $by->name = fullname($post, $canviewfullnames);
5951 $by->date = userdate($post->modified);
5953 if ($forumtracked) {
5954 if (!empty($post->postread)) {
5955 $style = '<span class="forumthread read">';
5956 } else {
5957 $style = '<span class="forumthread unread">';
5959 } else {
5960 $style = '<span class="forumthread">';
5962 echo $style."<a name=\"$post->id\"></a>".
5963 "<a href=\"discuss.php?d=$post->discussion&amp;parent=$post->id\">".format_string($post->subject,true)."</a> ";
5964 print_string("bynameondate", "forum", $by);
5965 echo "</span>";
5968 forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, $depth-1, $reply, $forumtracked, $posts);
5969 echo "</div>\n";
5975 * @todo Document this function
5976 * @global object
5977 * @global object
5978 * @return void
5980 function forum_print_posts_nested($course, &$cm, $forum, $discussion, $parent, $reply, $forumtracked, $posts) {
5981 global $USER, $CFG;
5983 $link = false;
5985 if (!empty($posts[$parent->id]->children)) {
5986 $posts = $posts[$parent->id]->children;
5988 foreach ($posts as $post) {
5990 echo '<div class="indent">';
5991 if (!isloggedin()) {
5992 $ownpost = false;
5993 } else {
5994 $ownpost = ($USER->id == $post->userid);
5997 $post->subject = format_string($post->subject);
5998 $postread = !empty($post->postread);
6000 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
6001 '', '', $postread, true, $forumtracked);
6002 forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
6003 echo "</div>\n";
6009 * Returns all forum posts since a given time in specified forum.
6011 * @todo Document this functions args
6012 * @global object
6013 * @global object
6014 * @global object
6015 * @global object
6017 function forum_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
6018 global $CFG, $COURSE, $USER, $DB;
6020 if ($COURSE->id == $courseid) {
6021 $course = $COURSE;
6022 } else {
6023 $course = $DB->get_record('course', array('id' => $courseid));
6026 $modinfo = get_fast_modinfo($course);
6028 $cm = $modinfo->cms[$cmid];
6029 $params = array($timestart, $cm->instance);
6031 if ($userid) {
6032 $userselect = "AND u.id = ?";
6033 $params[] = $userid;
6034 } else {
6035 $userselect = "";
6038 if ($groupid) {
6039 $groupselect = "AND d.groupid = ?";
6040 $params[] = $groupid;
6041 } else {
6042 $groupselect = "";
6045 $allnames = get_all_user_name_fields(true, 'u');
6046 if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
6047 d.timestart, d.timeend, d.userid AS duserid,
6048 $allnames, u.email, u.picture, u.imagealt, u.email
6049 FROM {forum_posts} p
6050 JOIN {forum_discussions} d ON d.id = p.discussion
6051 JOIN {forum} f ON f.id = d.forum
6052 JOIN {user} u ON u.id = p.userid
6053 WHERE p.created > ? AND f.id = ?
6054 $userselect $groupselect
6055 ORDER BY p.id ASC", $params)) { // order by initial posting date
6056 return;
6059 $groupmode = groups_get_activity_groupmode($cm, $course);
6060 $cm_context = context_module::instance($cm->id);
6061 $viewhiddentimed = has_capability('mod/forum:viewhiddentimedposts', $cm_context);
6062 $accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context);
6064 $printposts = array();
6065 foreach ($posts as $post) {
6067 if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
6068 and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
6069 if (!$viewhiddentimed) {
6070 continue;
6074 if ($groupmode) {
6075 if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or $accessallgroups) {
6076 // oki (Open discussions have groupid -1)
6077 } else {
6078 // separate mode
6079 if (isguestuser()) {
6080 // shortcut
6081 continue;
6084 if (!in_array($post->groupid, $modinfo->get_groups($cm->groupingid))) {
6085 continue;
6090 $printposts[] = $post;
6093 if (!$printposts) {
6094 return;
6097 $aname = format_string($cm->name,true);
6099 foreach ($printposts as $post) {
6100 $tmpactivity = new stdClass();
6102 $tmpactivity->type = 'forum';
6103 $tmpactivity->cmid = $cm->id;
6104 $tmpactivity->name = $aname;
6105 $tmpactivity->sectionnum = $cm->sectionnum;
6106 $tmpactivity->timestamp = $post->modified;
6108 $tmpactivity->content = new stdClass();
6109 $tmpactivity->content->id = $post->id;
6110 $tmpactivity->content->discussion = $post->discussion;
6111 $tmpactivity->content->subject = format_string($post->subject);
6112 $tmpactivity->content->parent = $post->parent;
6114 $tmpactivity->user = new stdClass();
6115 $additionalfields = array('id' => 'userid', 'picture', 'imagealt', 'email');
6116 $additionalfields = explode(',', user_picture::fields());
6117 $tmpactivity->user = username_load_fields_from_object($tmpactivity->user, $post, null, $additionalfields);
6118 $tmpactivity->user->id = $post->userid;
6120 $activities[$index++] = $tmpactivity;
6123 return;
6127 * @todo Document this function
6128 * @global object
6130 function forum_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
6131 global $CFG, $OUTPUT;
6133 if ($activity->content->parent) {
6134 $class = 'reply';
6135 } else {
6136 $class = 'discussion';
6139 echo '<table border="0" cellpadding="3" cellspacing="0" class="forum-recent">';
6141 echo "<tr><td class=\"userpicture\" valign=\"top\">";
6142 echo $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid));
6143 echo "</td><td class=\"$class\">";
6145 echo '<div class="title">';
6146 if ($detail) {
6147 $aname = s($activity->name);
6148 echo "<img src=\"" . $OUTPUT->pix_url('icon', $activity->type) . "\" ".
6149 "class=\"icon\" alt=\"{$aname}\" />";
6151 echo "<a href=\"$CFG->wwwroot/mod/forum/discuss.php?d={$activity->content->discussion}"
6152 ."#p{$activity->content->id}\">{$activity->content->subject}</a>";
6153 echo '</div>';
6155 echo '<div class="user">';
6156 $fullname = fullname($activity->user, $viewfullnames);
6157 echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&amp;course=$courseid\">"
6158 ."{$fullname}</a> - ".userdate($activity->timestamp);
6159 echo '</div>';
6160 echo "</td></tr></table>";
6162 return;
6166 * recursively sets the discussion field to $discussionid on $postid and all its children
6167 * used when pruning a post
6169 * @global object
6170 * @param int $postid
6171 * @param int $discussionid
6172 * @return bool
6174 function forum_change_discussionid($postid, $discussionid) {
6175 global $DB;
6176 $DB->set_field('forum_posts', 'discussion', $discussionid, array('id' => $postid));
6177 if ($posts = $DB->get_records('forum_posts', array('parent' => $postid))) {
6178 foreach ($posts as $post) {
6179 forum_change_discussionid($post->id, $discussionid);
6182 return true;
6186 * Prints the editing button on subscribers page
6188 * @global object
6189 * @global object
6190 * @param int $courseid
6191 * @param int $forumid
6192 * @return string
6194 function forum_update_subscriptions_button($courseid, $forumid) {
6195 global $CFG, $USER;
6197 if (!empty($USER->subscriptionsediting)) {
6198 $string = get_string('turneditingoff');
6199 $edit = "off";
6200 } else {
6201 $string = get_string('turneditingon');
6202 $edit = "on";
6205 return "<form method=\"get\" action=\"$CFG->wwwroot/mod/forum/subscribers.php\">".
6206 "<input type=\"hidden\" name=\"id\" value=\"$forumid\" />".
6207 "<input type=\"hidden\" name=\"edit\" value=\"$edit\" />".
6208 "<input type=\"submit\" value=\"$string\" /></form>";
6212 * This function gets run whenever user is enrolled into course
6214 * @deprecated deprecating this function as we will be using \mod_forum\observer::role_assigned()
6215 * @param stdClass $cp
6216 * @return void
6218 function forum_user_enrolled($cp) {
6219 global $DB;
6221 // NOTE: this has to be as fast as possible - we do not want to slow down enrolments!
6222 // Originally there used to be 'mod/forum:initialsubscriptions' which was
6223 // introduced because we did not have enrolment information in earlier versions...
6225 $sql = "SELECT f.id
6226 FROM {forum} f
6227 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = :userid)
6228 WHERE f.course = :courseid AND f.forcesubscribe = :initial AND fs.id IS NULL";
6229 $params = array('courseid'=>$cp->courseid, 'userid'=>$cp->userid, 'initial'=>FORUM_INITIALSUBSCRIBE);
6231 $forums = $DB->get_records_sql($sql, $params);
6232 foreach ($forums as $forum) {
6233 forum_subscribe($cp->userid, $forum->id);
6237 // Functions to do with read tracking.
6240 * Mark posts as read.
6242 * @global object
6243 * @global object
6244 * @param object $user object
6245 * @param array $postids array of post ids
6246 * @return boolean success
6248 function forum_tp_mark_posts_read($user, $postids) {
6249 global $CFG, $DB;
6251 if (!forum_tp_can_track_forums(false, $user)) {
6252 return true;
6255 $status = true;
6257 $now = time();
6258 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
6260 if (empty($postids)) {
6261 return true;
6263 } else if (count($postids) > 200) {
6264 while ($part = array_splice($postids, 0, 200)) {
6265 $status = forum_tp_mark_posts_read($user, $part) && $status;
6267 return $status;
6270 list($usql, $params) = $DB->get_in_or_equal($postids);
6271 $params[] = $user->id;
6273 $sql = "SELECT id
6274 FROM {forum_read}
6275 WHERE postid $usql AND userid = ?";
6276 if ($existing = $DB->get_records_sql($sql, $params)) {
6277 $existing = array_keys($existing);
6278 } else {
6279 $existing = array();
6282 $new = array_diff($postids, $existing);
6284 if ($new) {
6285 list($usql, $new_params) = $DB->get_in_or_equal($new);
6286 $params = array($user->id, $now, $now, $user->id);
6287 $params = array_merge($params, $new_params);
6288 $params[] = $cutoffdate;
6290 if ($CFG->forum_allowforcedreadtracking) {
6291 $trackingsql = "AND (f.trackingtype = ".FORUM_TRACKING_FORCED."
6292 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL))";
6293 } else {
6294 $trackingsql = "AND ((f.trackingtype = ".FORUM_TRACKING_OPTIONAL." OR f.trackingtype = ".FORUM_TRACKING_FORCED.")
6295 AND tf.id IS NULL)";
6298 $sql = "INSERT INTO {forum_read} (userid, postid, discussionid, forumid, firstread, lastread)
6300 SELECT ?, p.id, p.discussion, d.forum, ?, ?
6301 FROM {forum_posts} p
6302 JOIN {forum_discussions} d ON d.id = p.discussion
6303 JOIN {forum} f ON f.id = d.forum
6304 LEFT JOIN {forum_track_prefs} tf ON (tf.userid = ? AND tf.forumid = f.id)
6305 WHERE p.id $usql
6306 AND p.modified >= ?
6307 $trackingsql";
6308 $status = $DB->execute($sql, $params) && $status;
6311 if ($existing) {
6312 list($usql, $new_params) = $DB->get_in_or_equal($existing);
6313 $params = array($now, $user->id);
6314 $params = array_merge($params, $new_params);
6316 $sql = "UPDATE {forum_read}
6317 SET lastread = ?
6318 WHERE userid = ? AND postid $usql";
6319 $status = $DB->execute($sql, $params) && $status;
6322 return $status;
6326 * Mark post as read.
6327 * @global object
6328 * @global object
6329 * @param int $userid
6330 * @param int $postid
6332 function forum_tp_add_read_record($userid, $postid) {
6333 global $CFG, $DB;
6335 $now = time();
6336 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
6338 if (!$DB->record_exists('forum_read', array('userid' => $userid, 'postid' => $postid))) {
6339 $sql = "INSERT INTO {forum_read} (userid, postid, discussionid, forumid, firstread, lastread)
6341 SELECT ?, p.id, p.discussion, d.forum, ?, ?
6342 FROM {forum_posts} p
6343 JOIN {forum_discussions} d ON d.id = p.discussion
6344 WHERE p.id = ? AND p.modified >= ?";
6345 return $DB->execute($sql, array($userid, $now, $now, $postid, $cutoffdate));
6347 } else {
6348 $sql = "UPDATE {forum_read}
6349 SET lastread = ?
6350 WHERE userid = ? AND postid = ?";
6351 return $DB->execute($sql, array($now, $userid, $userid));
6356 * Returns all records in the 'forum_read' table matching the passed keys, indexed
6357 * by userid.
6359 * @global object
6360 * @param int $userid
6361 * @param int $postid
6362 * @param int $discussionid
6363 * @param int $forumid
6364 * @return array
6366 function forum_tp_get_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
6367 global $DB;
6368 $select = '';
6369 $params = array();
6371 if ($userid > -1) {
6372 if ($select != '') $select .= ' AND ';
6373 $select .= 'userid = ?';
6374 $params[] = $userid;
6376 if ($postid > -1) {
6377 if ($select != '') $select .= ' AND ';
6378 $select .= 'postid = ?';
6379 $params[] = $postid;
6381 if ($discussionid > -1) {
6382 if ($select != '') $select .= ' AND ';
6383 $select .= 'discussionid = ?';
6384 $params[] = $discussionid;
6386 if ($forumid > -1) {
6387 if ($select != '') $select .= ' AND ';
6388 $select .= 'forumid = ?';
6389 $params[] = $forumid;
6392 return $DB->get_records_select('forum_read', $select, $params);
6396 * Returns all read records for the provided user and discussion, indexed by postid.
6398 * @global object
6399 * @param inti $userid
6400 * @param int $discussionid
6402 function forum_tp_get_discussion_read_records($userid, $discussionid) {
6403 global $DB;
6404 $select = 'userid = ? AND discussionid = ?';
6405 $fields = 'postid, firstread, lastread';
6406 return $DB->get_records_select('forum_read', $select, array($userid, $discussionid), '', $fields);
6410 * If its an old post, do nothing. If the record exists, the maintenance will clear it up later.
6412 * @return bool
6414 function forum_tp_mark_post_read($userid, $post, $forumid) {
6415 if (!forum_tp_is_post_old($post)) {
6416 return forum_tp_add_read_record($userid, $post->id);
6417 } else {
6418 return true;
6423 * Marks a whole forum as read, for a given user
6425 * @global object
6426 * @global object
6427 * @param object $user
6428 * @param int $forumid
6429 * @param int|bool $groupid
6430 * @return bool
6432 function forum_tp_mark_forum_read($user, $forumid, $groupid=false) {
6433 global $CFG, $DB;
6435 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6437 $groupsel = "";
6438 $params = array($user->id, $forumid, $cutoffdate);
6440 if ($groupid !== false) {
6441 $groupsel = " AND (d.groupid = ? OR d.groupid = -1)";
6442 $params[] = $groupid;
6445 $sql = "SELECT p.id
6446 FROM {forum_posts} p
6447 LEFT JOIN {forum_discussions} d ON d.id = p.discussion
6448 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6449 WHERE d.forum = ?
6450 AND p.modified >= ? AND r.id is NULL
6451 $groupsel";
6453 if ($posts = $DB->get_records_sql($sql, $params)) {
6454 $postids = array_keys($posts);
6455 return forum_tp_mark_posts_read($user, $postids);
6458 return true;
6462 * Marks a whole discussion as read, for a given user
6464 * @global object
6465 * @global object
6466 * @param object $user
6467 * @param int $discussionid
6468 * @return bool
6470 function forum_tp_mark_discussion_read($user, $discussionid) {
6471 global $CFG, $DB;
6473 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6475 $sql = "SELECT p.id
6476 FROM {forum_posts} p
6477 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6478 WHERE p.discussion = ?
6479 AND p.modified >= ? AND r.id is NULL";
6481 if ($posts = $DB->get_records_sql($sql, array($user->id, $discussionid, $cutoffdate))) {
6482 $postids = array_keys($posts);
6483 return forum_tp_mark_posts_read($user, $postids);
6486 return true;
6490 * @global object
6491 * @param int $userid
6492 * @param object $post
6494 function forum_tp_is_post_read($userid, $post) {
6495 global $DB;
6496 return (forum_tp_is_post_old($post) ||
6497 $DB->record_exists('forum_read', array('userid' => $userid, 'postid' => $post->id)));
6501 * @global object
6502 * @param object $post
6503 * @param int $time Defautls to time()
6505 function forum_tp_is_post_old($post, $time=null) {
6506 global $CFG;
6508 if (is_null($time)) {
6509 $time = time();
6511 return ($post->modified < ($time - ($CFG->forum_oldpostdays * 24 * 3600)));
6515 * Returns the count of records for the provided user and discussion.
6517 * @global object
6518 * @global object
6519 * @param int $userid
6520 * @param int $discussionid
6521 * @return bool
6523 function forum_tp_count_discussion_read_records($userid, $discussionid) {
6524 global $CFG, $DB;
6526 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
6528 $sql = 'SELECT COUNT(DISTINCT p.id) '.
6529 'FROM {forum_discussions} d '.
6530 'LEFT JOIN {forum_read} r ON d.id = r.discussionid AND r.userid = ? '.
6531 'LEFT JOIN {forum_posts} p ON p.discussion = d.id '.
6532 'AND (p.modified < ? OR p.id = r.postid) '.
6533 'WHERE d.id = ? ';
6535 return ($DB->count_records_sql($sql, array($userid, $cutoffdate, $discussionid)));
6539 * Returns the count of records for the provided user and discussion.
6541 * @global object
6542 * @global object
6543 * @param int $userid
6544 * @param int $discussionid
6545 * @return int
6547 function forum_tp_count_discussion_unread_posts($userid, $discussionid) {
6548 global $CFG, $DB;
6550 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
6552 $sql = 'SELECT COUNT(p.id) '.
6553 'FROM {forum_posts} p '.
6554 'LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? '.
6555 'WHERE p.discussion = ? '.
6556 'AND p.modified >= ? AND r.id is NULL';
6558 return $DB->count_records_sql($sql, array($userid, $discussionid, $cutoffdate));
6562 * Returns the count of posts for the provided forum and [optionally] group.
6563 * @global object
6564 * @global object
6565 * @param int $forumid
6566 * @param int|bool $groupid
6567 * @return int
6569 function forum_tp_count_forum_posts($forumid, $groupid=false) {
6570 global $CFG, $DB;
6571 $params = array($forumid);
6572 $sql = 'SELECT COUNT(*) '.
6573 'FROM {forum_posts} fp,{forum_discussions} fd '.
6574 'WHERE fd.forum = ? AND fp.discussion = fd.id';
6575 if ($groupid !== false) {
6576 $sql .= ' AND (fd.groupid = ? OR fd.groupid = -1)';
6577 $params[] = $groupid;
6579 $count = $DB->count_records_sql($sql, $params);
6582 return $count;
6586 * Returns the count of records for the provided user and forum and [optionally] group.
6587 * @global object
6588 * @global object
6589 * @param int $userid
6590 * @param int $forumid
6591 * @param int|bool $groupid
6592 * @return int
6594 function forum_tp_count_forum_read_records($userid, $forumid, $groupid=false) {
6595 global $CFG, $DB;
6597 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6599 $groupsel = '';
6600 $params = array($userid, $forumid, $cutoffdate);
6601 if ($groupid !== false) {
6602 $groupsel = "AND (d.groupid = ? OR d.groupid = -1)";
6603 $params[] = $groupid;
6606 $sql = "SELECT COUNT(p.id)
6607 FROM {forum_posts} p
6608 JOIN {forum_discussions} d ON d.id = p.discussion
6609 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid= ?)
6610 WHERE d.forum = ?
6611 AND (p.modified < $cutoffdate OR (p.modified >= ? AND r.id IS NOT NULL))
6612 $groupsel";
6614 return $DB->get_field_sql($sql, $params);
6618 * Returns the count of records for the provided user and course.
6619 * Please note that group access is ignored!
6621 * @global object
6622 * @global object
6623 * @param int $userid
6624 * @param int $courseid
6625 * @return array
6627 function forum_tp_get_course_unread_posts($userid, $courseid) {
6628 global $CFG, $DB;
6630 $now = round(time(), -2); // DB cache friendliness.
6631 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 60 * 60);
6632 $params = array($userid, $userid, $courseid, $cutoffdate, $userid);
6634 if (!empty($CFG->forum_enabletimedposts)) {
6635 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
6636 $params[] = $now;
6637 $params[] = $now;
6638 } else {
6639 $timedsql = "";
6642 if ($CFG->forum_allowforcedreadtracking) {
6643 $trackingsql = "AND (f.trackingtype = ".FORUM_TRACKING_FORCED."
6644 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL
6645 AND (SELECT trackforums FROM {user} WHERE id = ?) = 1))";
6646 } else {
6647 $trackingsql = "AND ((f.trackingtype = ".FORUM_TRACKING_OPTIONAL." OR f.trackingtype = ".FORUM_TRACKING_FORCED.")
6648 AND tf.id IS NULL
6649 AND (SELECT trackforums FROM {user} WHERE id = ?) = 1)";
6652 $sql = "SELECT f.id, COUNT(p.id) AS unread
6653 FROM {forum_posts} p
6654 JOIN {forum_discussions} d ON d.id = p.discussion
6655 JOIN {forum} f ON f.id = d.forum
6656 JOIN {course} c ON c.id = f.course
6657 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6658 LEFT JOIN {forum_track_prefs} tf ON (tf.userid = ? AND tf.forumid = f.id)
6659 WHERE f.course = ?
6660 AND p.modified >= ? AND r.id is NULL
6661 $trackingsql
6662 $timedsql
6663 GROUP BY f.id";
6665 if ($return = $DB->get_records_sql($sql, $params)) {
6666 return $return;
6669 return array();
6673 * Returns the count of records for the provided user and forum and [optionally] group.
6675 * @global object
6676 * @global object
6677 * @global object
6678 * @param object $cm
6679 * @param object $course
6680 * @return int
6682 function forum_tp_count_forum_unread_posts($cm, $course) {
6683 global $CFG, $USER, $DB;
6685 static $readcache = array();
6687 $forumid = $cm->instance;
6689 if (!isset($readcache[$course->id])) {
6690 $readcache[$course->id] = array();
6691 if ($counts = forum_tp_get_course_unread_posts($USER->id, $course->id)) {
6692 foreach ($counts as $count) {
6693 $readcache[$course->id][$count->id] = $count->unread;
6698 if (empty($readcache[$course->id][$forumid])) {
6699 // no need to check group mode ;-)
6700 return 0;
6703 $groupmode = groups_get_activity_groupmode($cm, $course);
6705 if ($groupmode != SEPARATEGROUPS) {
6706 return $readcache[$course->id][$forumid];
6709 if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) {
6710 return $readcache[$course->id][$forumid];
6713 require_once($CFG->dirroot.'/course/lib.php');
6715 $modinfo = get_fast_modinfo($course);
6717 $mygroups = $modinfo->get_groups($cm->groupingid);
6719 // add all groups posts
6720 $mygroups[-1] = -1;
6722 list ($groups_sql, $groups_params) = $DB->get_in_or_equal($mygroups);
6724 $now = round(time(), -2); // db cache friendliness
6725 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
6726 $params = array($USER->id, $forumid, $cutoffdate);
6728 if (!empty($CFG->forum_enabletimedposts)) {
6729 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
6730 $params[] = $now;
6731 $params[] = $now;
6732 } else {
6733 $timedsql = "";
6736 $params = array_merge($params, $groups_params);
6738 $sql = "SELECT COUNT(p.id)
6739 FROM {forum_posts} p
6740 JOIN {forum_discussions} d ON p.discussion = d.id
6741 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6742 WHERE d.forum = ?
6743 AND p.modified >= ? AND r.id is NULL
6744 $timedsql
6745 AND d.groupid $groups_sql";
6747 return $DB->get_field_sql($sql, $params);
6751 * Deletes read records for the specified index. At least one parameter must be specified.
6753 * @global object
6754 * @param int $userid
6755 * @param int $postid
6756 * @param int $discussionid
6757 * @param int $forumid
6758 * @return bool
6760 function forum_tp_delete_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
6761 global $DB;
6762 $params = array();
6764 $select = '';
6765 if ($userid > -1) {
6766 if ($select != '') $select .= ' AND ';
6767 $select .= 'userid = ?';
6768 $params[] = $userid;
6770 if ($postid > -1) {
6771 if ($select != '') $select .= ' AND ';
6772 $select .= 'postid = ?';
6773 $params[] = $postid;
6775 if ($discussionid > -1) {
6776 if ($select != '') $select .= ' AND ';
6777 $select .= 'discussionid = ?';
6778 $params[] = $discussionid;
6780 if ($forumid > -1) {
6781 if ($select != '') $select .= ' AND ';
6782 $select .= 'forumid = ?';
6783 $params[] = $forumid;
6785 if ($select == '') {
6786 return false;
6788 else {
6789 return $DB->delete_records_select('forum_read', $select, $params);
6793 * Get a list of forums not tracked by the user.
6795 * @global object
6796 * @global object
6797 * @param int $userid The id of the user to use.
6798 * @param int $courseid The id of the course being checked.
6799 * @return mixed An array indexed by forum id, or false.
6801 function forum_tp_get_untracked_forums($userid, $courseid) {
6802 global $CFG, $DB;
6804 if ($CFG->forum_allowforcedreadtracking) {
6805 $trackingsql = "AND (f.trackingtype = ".FORUM_TRACKING_OFF."
6806 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND (ft.id IS NOT NULL
6807 OR (SELECT trackforums FROM {user} WHERE id = ?) = 0)))";
6808 } else {
6809 $trackingsql = "AND (f.trackingtype = ".FORUM_TRACKING_OFF."
6810 OR ((f.trackingtype = ".FORUM_TRACKING_OPTIONAL." OR f.trackingtype = ".FORUM_TRACKING_FORCED.")
6811 AND (ft.id IS NOT NULL
6812 OR (SELECT trackforums FROM {user} WHERE id = ?) = 0)))";
6815 $sql = "SELECT f.id
6816 FROM {forum} f
6817 LEFT JOIN {forum_track_prefs} ft ON (ft.forumid = f.id AND ft.userid = ?)
6818 WHERE f.course = ?
6819 $trackingsql";
6821 if ($forums = $DB->get_records_sql($sql, array($userid, $courseid, $userid))) {
6822 foreach ($forums as $forum) {
6823 $forums[$forum->id] = $forum;
6825 return $forums;
6827 } else {
6828 return array();
6833 * Determine if a user can track forums and optionally a particular forum.
6834 * Checks the site settings, the user settings and the forum settings (if
6835 * requested).
6837 * @global object
6838 * @global object
6839 * @global object
6840 * @param mixed $forum The forum object to test, or the int id (optional).
6841 * @param mixed $userid The user object to check for (optional).
6842 * @return boolean
6844 function forum_tp_can_track_forums($forum=false, $user=false) {
6845 global $USER, $CFG, $DB;
6847 // if possible, avoid expensive
6848 // queries
6849 if (empty($CFG->forum_trackreadposts)) {
6850 return false;
6853 if ($user === false) {
6854 $user = $USER;
6857 if (isguestuser($user) or empty($user->id)) {
6858 return false;
6861 if ($forum === false) {
6862 if ($CFG->forum_allowforcedreadtracking) {
6863 // Since we can force tracking, assume yes without a specific forum.
6864 return true;
6865 } else {
6866 return (bool)$user->trackforums;
6870 // Work toward always passing an object...
6871 if (is_numeric($forum)) {
6872 debugging('Better use proper forum object.', DEBUG_DEVELOPER);
6873 $forum = $DB->get_record('forum', array('id' => $forum), '', 'id,trackingtype');
6876 $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
6877 $forumforced = ($forum->trackingtype == FORUM_TRACKING_FORCED);
6879 if ($CFG->forum_allowforcedreadtracking) {
6880 // If we allow forcing, then forced forums takes procidence over user setting.
6881 return ($forumforced || ($forumallows && (!empty($user->trackforums) && (bool)$user->trackforums)));
6882 } else {
6883 // If we don't allow forcing, user setting trumps.
6884 return ($forumforced || $forumallows) && !empty($user->trackforums);
6889 * Tells whether a specific forum is tracked by the user. A user can optionally
6890 * be specified. If not specified, the current user is assumed.
6892 * @global object
6893 * @global object
6894 * @global object
6895 * @param mixed $forum If int, the id of the forum being checked; if object, the forum object
6896 * @param int $userid The id of the user being checked (optional).
6897 * @return boolean
6899 function forum_tp_is_tracked($forum, $user=false) {
6900 global $USER, $CFG, $DB;
6902 if ($user === false) {
6903 $user = $USER;
6906 if (isguestuser($user) or empty($user->id)) {
6907 return false;
6910 // Work toward always passing an object...
6911 if (is_numeric($forum)) {
6912 debugging('Better use proper forum object.', DEBUG_DEVELOPER);
6913 $forum = $DB->get_record('forum', array('id' => $forum));
6916 if (!forum_tp_can_track_forums($forum, $user)) {
6917 return false;
6920 $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
6921 $forumforced = ($forum->trackingtype == FORUM_TRACKING_FORCED);
6922 $userpref = $DB->get_record('forum_track_prefs', array('userid' => $user->id, 'forumid' => $forum->id));
6924 if ($CFG->forum_allowforcedreadtracking) {
6925 return $forumforced || ($forumallows && $userpref === false);
6926 } else {
6927 return ($forumallows || $forumforced) && $userpref === false;
6932 * @global object
6933 * @global object
6934 * @param int $forumid
6935 * @param int $userid
6937 function forum_tp_start_tracking($forumid, $userid=false) {
6938 global $USER, $DB;
6940 if ($userid === false) {
6941 $userid = $USER->id;
6944 return $DB->delete_records('forum_track_prefs', array('userid' => $userid, 'forumid' => $forumid));
6948 * @global object
6949 * @global object
6950 * @param int $forumid
6951 * @param int $userid
6953 function forum_tp_stop_tracking($forumid, $userid=false) {
6954 global $USER, $DB;
6956 if ($userid === false) {
6957 $userid = $USER->id;
6960 if (!$DB->record_exists('forum_track_prefs', array('userid' => $userid, 'forumid' => $forumid))) {
6961 $track_prefs = new stdClass();
6962 $track_prefs->userid = $userid;
6963 $track_prefs->forumid = $forumid;
6964 $DB->insert_record('forum_track_prefs', $track_prefs);
6967 return forum_tp_delete_read_records($userid, -1, -1, $forumid);
6972 * Clean old records from the forum_read table.
6973 * @global object
6974 * @global object
6975 * @return void
6977 function forum_tp_clean_read_records() {
6978 global $CFG, $DB;
6980 if (!isset($CFG->forum_oldpostdays)) {
6981 return;
6983 // Look for records older than the cutoffdate that are still in the forum_read table.
6984 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6986 //first get the oldest tracking present - we need tis to speedup the next delete query
6987 $sql = "SELECT MIN(fp.modified) AS first
6988 FROM {forum_posts} fp
6989 JOIN {forum_read} fr ON fr.postid=fp.id";
6990 if (!$first = $DB->get_field_sql($sql)) {
6991 // nothing to delete;
6992 return;
6995 // now delete old tracking info
6996 $sql = "DELETE
6997 FROM {forum_read}
6998 WHERE postid IN (SELECT fp.id
6999 FROM {forum_posts} fp
7000 WHERE fp.modified >= ? AND fp.modified < ?)";
7001 $DB->execute($sql, array($first, $cutoffdate));
7005 * Sets the last post for a given discussion
7007 * @global object
7008 * @global object
7009 * @param into $discussionid
7010 * @return bool|int
7012 function forum_discussion_update_last_post($discussionid) {
7013 global $CFG, $DB;
7015 // Check the given discussion exists
7016 if (!$DB->record_exists('forum_discussions', array('id' => $discussionid))) {
7017 return false;
7020 // Use SQL to find the last post for this discussion
7021 $sql = "SELECT id, userid, modified
7022 FROM {forum_posts}
7023 WHERE discussion=?
7024 ORDER BY modified DESC";
7026 // Lets go find the last post
7027 if (($lastposts = $DB->get_records_sql($sql, array($discussionid), 0, 1))) {
7028 $lastpost = reset($lastposts);
7029 $discussionobject = new stdClass();
7030 $discussionobject->id = $discussionid;
7031 $discussionobject->usermodified = $lastpost->userid;
7032 $discussionobject->timemodified = $lastpost->modified;
7033 $DB->update_record('forum_discussions', $discussionobject);
7034 return $lastpost->id;
7037 // To get here either we couldn't find a post for the discussion (weird)
7038 // or we couldn't update the discussion record (weird x2)
7039 return false;
7044 * @return array
7046 function forum_get_view_actions() {
7047 return array('view discussion', 'search', 'forum', 'forums', 'subscribers', 'view forum');
7051 * @return array
7053 function forum_get_post_actions() {
7054 return array('add discussion','add post','delete discussion','delete post','move discussion','prune post','update post');
7058 * Returns a warning object if a user has reached the number of posts equal to
7059 * the warning/blocking setting, or false if there is no warning to show.
7061 * @param int|stdClass $forum the forum id or the forum object
7062 * @param stdClass $cm the course module
7063 * @return stdClass|bool returns an object with the warning information, else
7064 * returns false if no warning is required.
7066 function forum_check_throttling($forum, $cm = null) {
7067 global $CFG, $DB, $USER;
7069 if (is_numeric($forum)) {
7070 $forum = $DB->get_record('forum', array('id' => $forum), '*', MUST_EXIST);
7073 if (!is_object($forum)) {
7074 return false; // This is broken.
7077 if (!$cm) {
7078 $cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course, false, MUST_EXIST);
7081 if (empty($forum->blockafter)) {
7082 return false;
7085 if (empty($forum->blockperiod)) {
7086 return false;
7089 $modcontext = context_module::instance($cm->id);
7090 if (has_capability('mod/forum:postwithoutthrottling', $modcontext)) {
7091 return false;
7094 // Get the number of posts in the last period we care about.
7095 $timenow = time();
7096 $timeafter = $timenow - $forum->blockperiod;
7097 $numposts = $DB->count_records_sql('SELECT COUNT(p.id) FROM {forum_posts} p
7098 JOIN {forum_discussions} d
7099 ON p.discussion = d.id WHERE d.forum = ?
7100 AND p.userid = ? AND p.created > ?', array($forum->id, $USER->id, $timeafter));
7102 $a = new stdClass();
7103 $a->blockafter = $forum->blockafter;
7104 $a->numposts = $numposts;
7105 $a->blockperiod = get_string('secondstotime'.$forum->blockperiod);
7107 if ($forum->blockafter <= $numposts) {
7108 $warning = new stdClass();
7109 $warning->canpost = false;
7110 $warning->errorcode = 'forumblockingtoomanyposts';
7111 $warning->module = 'error';
7112 $warning->additional = $a;
7113 $warning->link = $CFG->wwwroot . '/mod/forum/view.php?f=' . $forum->id;
7115 return $warning;
7118 if ($forum->warnafter <= $numposts) {
7119 $warning = new stdClass();
7120 $warning->canpost = true;
7121 $warning->errorcode = 'forumblockingalmosttoomanyposts';
7122 $warning->module = 'forum';
7123 $warning->additional = $a;
7124 $warning->link = null;
7126 return $warning;
7131 * Throws an error if the user is no longer allowed to post due to having reached
7132 * or exceeded the number of posts specified in 'Post threshold for blocking'
7133 * setting.
7135 * @since Moodle 2.5
7136 * @param stdClass $thresholdwarning the warning information returned
7137 * from the function forum_check_throttling.
7139 function forum_check_blocking_threshold($thresholdwarning) {
7140 if (!empty($thresholdwarning) && !$thresholdwarning->canpost) {
7141 print_error($thresholdwarning->errorcode,
7142 $thresholdwarning->module,
7143 $thresholdwarning->link,
7144 $thresholdwarning->additional);
7150 * Removes all grades from gradebook
7152 * @global object
7153 * @global object
7154 * @param int $courseid
7155 * @param string $type optional
7157 function forum_reset_gradebook($courseid, $type='') {
7158 global $CFG, $DB;
7160 $wheresql = '';
7161 $params = array($courseid);
7162 if ($type) {
7163 $wheresql = "AND f.type=?";
7164 $params[] = $type;
7167 $sql = "SELECT f.*, cm.idnumber as cmidnumber, f.course as courseid
7168 FROM {forum} f, {course_modules} cm, {modules} m
7169 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id AND f.course=? $wheresql";
7171 if ($forums = $DB->get_records_sql($sql, $params)) {
7172 foreach ($forums as $forum) {
7173 forum_grade_item_update($forum, 'reset');
7179 * This function is used by the reset_course_userdata function in moodlelib.
7180 * This function will remove all posts from the specified forum
7181 * and clean up any related data.
7183 * @global object
7184 * @global object
7185 * @param $data the data submitted from the reset course.
7186 * @return array status array
7188 function forum_reset_userdata($data) {
7189 global $CFG, $DB;
7190 require_once($CFG->dirroot.'/rating/lib.php');
7192 $componentstr = get_string('modulenameplural', 'forum');
7193 $status = array();
7195 $params = array($data->courseid);
7197 $removeposts = false;
7198 $typesql = "";
7199 if (!empty($data->reset_forum_all)) {
7200 $removeposts = true;
7201 $typesstr = get_string('resetforumsall', 'forum');
7202 $types = array();
7203 } else if (!empty($data->reset_forum_types)){
7204 $removeposts = true;
7205 $typesql = "";
7206 $types = array();
7207 $forum_types_all = forum_get_forum_types_all();
7208 foreach ($data->reset_forum_types as $type) {
7209 if (!array_key_exists($type, $forum_types_all)) {
7210 continue;
7212 $typesql .= " AND f.type=?";
7213 $types[] = $forum_types_all[$type];
7214 $params[] = $type;
7216 $typesstr = get_string('resetforums', 'forum').': '.implode(', ', $types);
7218 $alldiscussionssql = "SELECT fd.id
7219 FROM {forum_discussions} fd, {forum} f
7220 WHERE f.course=? AND f.id=fd.forum";
7222 $allforumssql = "SELECT f.id
7223 FROM {forum} f
7224 WHERE f.course=?";
7226 $allpostssql = "SELECT fp.id
7227 FROM {forum_posts} fp, {forum_discussions} fd, {forum} f
7228 WHERE f.course=? AND f.id=fd.forum AND fd.id=fp.discussion";
7230 $forumssql = $forums = $rm = null;
7232 if( $removeposts || !empty($data->reset_forum_ratings) ) {
7233 $forumssql = "$allforumssql $typesql";
7234 $forums = $forums = $DB->get_records_sql($forumssql, $params);
7235 $rm = new rating_manager();
7236 $ratingdeloptions = new stdClass;
7237 $ratingdeloptions->component = 'mod_forum';
7238 $ratingdeloptions->ratingarea = 'post';
7241 if ($removeposts) {
7242 $discussionssql = "$alldiscussionssql $typesql";
7243 $postssql = "$allpostssql $typesql";
7245 // now get rid of all attachments
7246 $fs = get_file_storage();
7247 if ($forums) {
7248 foreach ($forums as $forumid=>$unused) {
7249 if (!$cm = get_coursemodule_from_instance('forum', $forumid)) {
7250 continue;
7252 $context = context_module::instance($cm->id);
7253 $fs->delete_area_files($context->id, 'mod_forum', 'attachment');
7254 $fs->delete_area_files($context->id, 'mod_forum', 'post');
7256 //remove ratings
7257 $ratingdeloptions->contextid = $context->id;
7258 $rm->delete_ratings($ratingdeloptions);
7262 // first delete all read flags
7263 $DB->delete_records_select('forum_read', "forumid IN ($forumssql)", $params);
7265 // remove tracking prefs
7266 $DB->delete_records_select('forum_track_prefs', "forumid IN ($forumssql)", $params);
7268 // remove posts from queue
7269 $DB->delete_records_select('forum_queue', "discussionid IN ($discussionssql)", $params);
7271 // all posts - initial posts must be kept in single simple discussion forums
7272 $DB->delete_records_select('forum_posts', "discussion IN ($discussionssql) AND parent <> 0", $params); // first all children
7273 $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
7275 // finally all discussions except single simple forums
7276 $DB->delete_records_select('forum_discussions', "forum IN ($forumssql AND f.type <> 'single')", $params);
7278 // remove all grades from gradebook
7279 if (empty($data->reset_gradebook_grades)) {
7280 if (empty($types)) {
7281 forum_reset_gradebook($data->courseid);
7282 } else {
7283 foreach ($types as $type) {
7284 forum_reset_gradebook($data->courseid, $type);
7289 $status[] = array('component'=>$componentstr, 'item'=>$typesstr, 'error'=>false);
7292 // remove all ratings in this course's forums
7293 if (!empty($data->reset_forum_ratings)) {
7294 if ($forums) {
7295 foreach ($forums as $forumid=>$unused) {
7296 if (!$cm = get_coursemodule_from_instance('forum', $forumid)) {
7297 continue;
7299 $context = context_module::instance($cm->id);
7301 //remove ratings
7302 $ratingdeloptions->contextid = $context->id;
7303 $rm->delete_ratings($ratingdeloptions);
7307 // remove all grades from gradebook
7308 if (empty($data->reset_gradebook_grades)) {
7309 forum_reset_gradebook($data->courseid);
7313 // remove all digest settings unconditionally - even for users still enrolled in course.
7314 if (!empty($data->reset_forum_digests)) {
7315 $DB->delete_records_select('forum_digests', "forum IN ($allforumssql)", $params);
7316 $status[] = array('component' => $componentstr, 'item' => get_string('resetdigests', 'forum'), 'error' => false);
7319 // remove all subscriptions unconditionally - even for users still enrolled in course
7320 if (!empty($data->reset_forum_subscriptions)) {
7321 $DB->delete_records_select('forum_subscriptions', "forum IN ($allforumssql)", $params);
7322 $status[] = array('component'=>$componentstr, 'item'=>get_string('resetsubscriptions','forum'), 'error'=>false);
7325 // remove all tracking prefs unconditionally - even for users still enrolled in course
7326 if (!empty($data->reset_forum_track_prefs)) {
7327 $DB->delete_records_select('forum_track_prefs', "forumid IN ($allforumssql)", $params);
7328 $status[] = array('component'=>$componentstr, 'item'=>get_string('resettrackprefs','forum'), 'error'=>false);
7331 /// updating dates - shift may be negative too
7332 if ($data->timeshift) {
7333 shift_course_mod_dates('forum', array('assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid);
7334 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
7337 return $status;
7341 * Called by course/reset.php
7343 * @param $mform form passed by reference
7345 function forum_reset_course_form_definition(&$mform) {
7346 $mform->addElement('header', 'forumheader', get_string('modulenameplural', 'forum'));
7348 $mform->addElement('checkbox', 'reset_forum_all', get_string('resetforumsall','forum'));
7350 $mform->addElement('select', 'reset_forum_types', get_string('resetforums', 'forum'), forum_get_forum_types_all(), array('multiple' => 'multiple'));
7351 $mform->setAdvanced('reset_forum_types');
7352 $mform->disabledIf('reset_forum_types', 'reset_forum_all', 'checked');
7354 $mform->addElement('checkbox', 'reset_forum_digests', get_string('resetdigests','forum'));
7355 $mform->setAdvanced('reset_forum_digests');
7357 $mform->addElement('checkbox', 'reset_forum_subscriptions', get_string('resetsubscriptions','forum'));
7358 $mform->setAdvanced('reset_forum_subscriptions');
7360 $mform->addElement('checkbox', 'reset_forum_track_prefs', get_string('resettrackprefs','forum'));
7361 $mform->setAdvanced('reset_forum_track_prefs');
7362 $mform->disabledIf('reset_forum_track_prefs', 'reset_forum_all', 'checked');
7364 $mform->addElement('checkbox', 'reset_forum_ratings', get_string('deleteallratings'));
7365 $mform->disabledIf('reset_forum_ratings', 'reset_forum_all', 'checked');
7369 * Course reset form defaults.
7370 * @return array
7372 function forum_reset_course_form_defaults($course) {
7373 return array('reset_forum_all'=>1, 'reset_forum_digests' => 0, 'reset_forum_subscriptions'=>0, 'reset_forum_track_prefs'=>0, 'reset_forum_ratings'=>1);
7377 * Converts a forum to use the Roles System
7379 * @global object
7380 * @global object
7381 * @param object $forum a forum object with the same attributes as a record
7382 * from the forum database table
7383 * @param int $forummodid the id of the forum module, from the modules table
7384 * @param array $teacherroles array of roles that have archetype teacher
7385 * @param array $studentroles array of roles that have archetype student
7386 * @param array $guestroles array of roles that have archetype guest
7387 * @param int $cmid the course_module id for this forum instance
7388 * @return boolean forum was converted or not
7390 function forum_convert_to_roles($forum, $forummodid, $teacherroles=array(),
7391 $studentroles=array(), $guestroles=array(), $cmid=NULL) {
7393 global $CFG, $DB, $OUTPUT;
7395 if (!isset($forum->open) && !isset($forum->assesspublic)) {
7396 // We assume that this forum has already been converted to use the
7397 // Roles System. Columns forum.open and forum.assesspublic get dropped
7398 // once the forum module has been upgraded to use Roles.
7399 return false;
7402 if ($forum->type == 'teacher') {
7404 // Teacher forums should be converted to normal forums that
7405 // use the Roles System to implement the old behavior.
7406 // Note:
7407 // Seems that teacher forums were never backed up in 1.6 since they
7408 // didn't have an entry in the course_modules table.
7409 require_once($CFG->dirroot.'/course/lib.php');
7411 if ($DB->count_records('forum_discussions', array('forum' => $forum->id)) == 0) {
7412 // Delete empty teacher forums.
7413 $DB->delete_records('forum', array('id' => $forum->id));
7414 } else {
7415 // Create a course module for the forum and assign it to
7416 // section 0 in the course.
7417 $mod = new stdClass();
7418 $mod->course = $forum->course;
7419 $mod->module = $forummodid;
7420 $mod->instance = $forum->id;
7421 $mod->section = 0;
7422 $mod->visible = 0; // Hide the forum
7423 $mod->visibleold = 0; // Hide the forum
7424 $mod->groupmode = 0;
7426 if (!$cmid = add_course_module($mod)) {
7427 print_error('cannotcreateinstanceforteacher', 'forum');
7428 } else {
7429 $sectionid = course_add_cm_to_section($forum->course, $mod->coursemodule, 0);
7432 // Change the forum type to general.
7433 $forum->type = 'general';
7434 $DB->update_record('forum', $forum);
7436 $context = context_module::instance($cmid);
7438 // Create overrides for default student and guest roles (prevent).
7439 foreach ($studentroles as $studentrole) {
7440 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7441 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $studentrole->id, $context->id);
7442 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7443 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
7444 assign_capability('mod/forum:viewrating', CAP_PREVENT, $studentrole->id, $context->id);
7445 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
7446 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
7447 assign_capability('mod/forum:createattachment', CAP_PREVENT, $studentrole->id, $context->id);
7448 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $studentrole->id, $context->id);
7449 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $studentrole->id, $context->id);
7450 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $studentrole->id, $context->id);
7451 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $studentrole->id, $context->id);
7452 assign_capability('mod/forum:editanypost', CAP_PREVENT, $studentrole->id, $context->id);
7453 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $studentrole->id, $context->id);
7454 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $studentrole->id, $context->id);
7455 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $studentrole->id, $context->id);
7456 assign_capability('mod/forum:postwithoutthrottling', CAP_PREVENT, $studentrole->id, $context->id);
7458 foreach ($guestroles as $guestrole) {
7459 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
7460 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $guestrole->id, $context->id);
7461 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
7462 assign_capability('mod/forum:replypost', CAP_PREVENT, $guestrole->id, $context->id);
7463 assign_capability('mod/forum:viewrating', CAP_PREVENT, $guestrole->id, $context->id);
7464 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $guestrole->id, $context->id);
7465 assign_capability('mod/forum:rate', CAP_PREVENT, $guestrole->id, $context->id);
7466 assign_capability('mod/forum:createattachment', CAP_PREVENT, $guestrole->id, $context->id);
7467 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $guestrole->id, $context->id);
7468 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $guestrole->id, $context->id);
7469 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $guestrole->id, $context->id);
7470 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $guestrole->id, $context->id);
7471 assign_capability('mod/forum:editanypost', CAP_PREVENT, $guestrole->id, $context->id);
7472 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $guestrole->id, $context->id);
7473 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $guestrole->id, $context->id);
7474 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $guestrole->id, $context->id);
7475 assign_capability('mod/forum:postwithoutthrottling', CAP_PREVENT, $guestrole->id, $context->id);
7478 } else {
7479 // Non-teacher forum.
7481 if (empty($cmid)) {
7482 // We were not given the course_module id. Try to find it.
7483 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
7484 echo $OUTPUT->notification('Could not get the course module for the forum');
7485 return false;
7486 } else {
7487 $cmid = $cm->id;
7490 $context = context_module::instance($cmid);
7492 // $forum->open defines what students can do:
7493 // 0 = No discussions, no replies
7494 // 1 = No discussions, but replies are allowed
7495 // 2 = Discussions and replies are allowed
7496 switch ($forum->open) {
7497 case 0:
7498 foreach ($studentroles as $studentrole) {
7499 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7500 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
7502 break;
7503 case 1:
7504 foreach ($studentroles as $studentrole) {
7505 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7506 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
7508 break;
7509 case 2:
7510 foreach ($studentroles as $studentrole) {
7511 assign_capability('mod/forum:startdiscussion', CAP_ALLOW, $studentrole->id, $context->id);
7512 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
7514 break;
7517 // $forum->assessed defines whether forum rating is turned
7518 // on (1 or 2) and who can rate posts:
7519 // 1 = Everyone can rate posts
7520 // 2 = Only teachers can rate posts
7521 switch ($forum->assessed) {
7522 case 1:
7523 foreach ($studentroles as $studentrole) {
7524 assign_capability('mod/forum:rate', CAP_ALLOW, $studentrole->id, $context->id);
7526 foreach ($teacherroles as $teacherrole) {
7527 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
7529 break;
7530 case 2:
7531 foreach ($studentroles as $studentrole) {
7532 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
7534 foreach ($teacherroles as $teacherrole) {
7535 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
7537 break;
7540 // $forum->assesspublic defines whether students can see
7541 // everybody's ratings:
7542 // 0 = Students can only see their own ratings
7543 // 1 = Students can see everyone's ratings
7544 switch ($forum->assesspublic) {
7545 case 0:
7546 foreach ($studentroles as $studentrole) {
7547 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
7549 foreach ($teacherroles as $teacherrole) {
7550 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
7552 break;
7553 case 1:
7554 foreach ($studentroles as $studentrole) {
7555 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $studentrole->id, $context->id);
7557 foreach ($teacherroles as $teacherrole) {
7558 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
7560 break;
7563 if (empty($cm)) {
7564 $cm = $DB->get_record('course_modules', array('id' => $cmid));
7567 // $cm->groupmode:
7568 // 0 - No groups
7569 // 1 - Separate groups
7570 // 2 - Visible groups
7571 switch ($cm->groupmode) {
7572 case 0:
7573 break;
7574 case 1:
7575 foreach ($studentroles as $studentrole) {
7576 assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $studentrole->id, $context->id);
7578 foreach ($teacherroles as $teacherrole) {
7579 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
7581 break;
7582 case 2:
7583 foreach ($studentroles as $studentrole) {
7584 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $studentrole->id, $context->id);
7586 foreach ($teacherroles as $teacherrole) {
7587 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
7589 break;
7592 return true;
7596 * Returns array of forum layout modes
7598 * @return array
7600 function forum_get_layout_modes() {
7601 return array (FORUM_MODE_FLATOLDEST => get_string('modeflatoldestfirst', 'forum'),
7602 FORUM_MODE_FLATNEWEST => get_string('modeflatnewestfirst', 'forum'),
7603 FORUM_MODE_THREADED => get_string('modethreaded', 'forum'),
7604 FORUM_MODE_NESTED => get_string('modenested', 'forum'));
7608 * Returns array of forum types chooseable on the forum editing form
7610 * @return array
7612 function forum_get_forum_types() {
7613 return array ('general' => get_string('generalforum', 'forum'),
7614 'eachuser' => get_string('eachuserforum', 'forum'),
7615 'single' => get_string('singleforum', 'forum'),
7616 'qanda' => get_string('qandaforum', 'forum'),
7617 'blog' => get_string('blogforum', 'forum'));
7621 * Returns array of all forum layout modes
7623 * @return array
7625 function forum_get_forum_types_all() {
7626 return array ('news' => get_string('namenews','forum'),
7627 'social' => get_string('namesocial','forum'),
7628 'general' => get_string('generalforum', 'forum'),
7629 'eachuser' => get_string('eachuserforum', 'forum'),
7630 'single' => get_string('singleforum', 'forum'),
7631 'qanda' => get_string('qandaforum', 'forum'),
7632 'blog' => get_string('blogforum', 'forum'));
7636 * Returns array of forum open modes
7638 * @return array
7640 function forum_get_open_modes() {
7641 return array ('2' => get_string('openmode2', 'forum'),
7642 '1' => get_string('openmode1', 'forum'),
7643 '0' => get_string('openmode0', 'forum') );
7647 * Returns all other caps used in module
7649 * @return array
7651 function forum_get_extra_capabilities() {
7652 return array('moodle/site:accessallgroups', 'moodle/site:viewfullnames', 'moodle/site:trustcontent', 'moodle/rating:view', 'moodle/rating:viewany', 'moodle/rating:viewall', 'moodle/rating:rate');
7656 * Adds module specific settings to the settings block
7658 * @param settings_navigation $settings The settings navigation object
7659 * @param navigation_node $forumnode The node to add module settings to
7661 function forum_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $forumnode) {
7662 global $USER, $PAGE, $CFG, $DB, $OUTPUT;
7664 $forumobject = $DB->get_record("forum", array("id" => $PAGE->cm->instance));
7665 if (empty($PAGE->cm->context)) {
7666 $PAGE->cm->context = context_module::instance($PAGE->cm->instance);
7669 // for some actions you need to be enrolled, beiing admin is not enough sometimes here
7670 $enrolled = is_enrolled($PAGE->cm->context, $USER, '', false);
7671 $activeenrolled = is_enrolled($PAGE->cm->context, $USER, '', true);
7673 $canmanage = has_capability('mod/forum:managesubscriptions', $PAGE->cm->context);
7674 $subscriptionmode = forum_get_forcesubscribed($forumobject);
7675 $cansubscribe = ($activeenrolled && $subscriptionmode != FORUM_FORCESUBSCRIBE && ($subscriptionmode != FORUM_DISALLOWSUBSCRIBE || $canmanage));
7677 if ($canmanage) {
7678 $mode = $forumnode->add(get_string('subscriptionmode', 'forum'), null, navigation_node::TYPE_CONTAINER);
7680 $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);
7681 $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);
7682 $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);
7683 $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);
7685 switch ($subscriptionmode) {
7686 case FORUM_CHOOSESUBSCRIBE : // 0
7687 $allowchoice->action = null;
7688 $allowchoice->add_class('activesetting');
7689 break;
7690 case FORUM_FORCESUBSCRIBE : // 1
7691 $forceforever->action = null;
7692 $forceforever->add_class('activesetting');
7693 break;
7694 case FORUM_INITIALSUBSCRIBE : // 2
7695 $forceinitially->action = null;
7696 $forceinitially->add_class('activesetting');
7697 break;
7698 case FORUM_DISALLOWSUBSCRIBE : // 3
7699 $disallowchoice->action = null;
7700 $disallowchoice->add_class('activesetting');
7701 break;
7704 } else if ($activeenrolled) {
7706 switch ($subscriptionmode) {
7707 case FORUM_CHOOSESUBSCRIBE : // 0
7708 $notenode = $forumnode->add(get_string('subscriptionoptional', 'forum'));
7709 break;
7710 case FORUM_FORCESUBSCRIBE : // 1
7711 $notenode = $forumnode->add(get_string('subscriptionforced', 'forum'));
7712 break;
7713 case FORUM_INITIALSUBSCRIBE : // 2
7714 $notenode = $forumnode->add(get_string('subscriptionauto', 'forum'));
7715 break;
7716 case FORUM_DISALLOWSUBSCRIBE : // 3
7717 $notenode = $forumnode->add(get_string('subscriptiondisabled', 'forum'));
7718 break;
7722 if ($cansubscribe) {
7723 if (forum_is_subscribed($USER->id, $forumobject)) {
7724 $linktext = get_string('unsubscribe', 'forum');
7725 } else {
7726 $linktext = get_string('subscribe', 'forum');
7728 $url = new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'sesskey'=>sesskey()));
7729 $forumnode->add($linktext, $url, navigation_node::TYPE_SETTING);
7732 if (has_capability('mod/forum:viewsubscribers', $PAGE->cm->context)){
7733 $url = new moodle_url('/mod/forum/subscribers.php', array('id'=>$forumobject->id));
7734 $forumnode->add(get_string('showsubscribers', 'forum'), $url, navigation_node::TYPE_SETTING);
7737 if ($enrolled && forum_tp_can_track_forums($forumobject)) { // keep tracking info for users with suspended enrolments
7738 if ($forumobject->trackingtype == FORUM_TRACKING_OPTIONAL
7739 || ((!$CFG->forum_allowforcedreadtracking) && $forumobject->trackingtype == FORUM_TRACKING_FORCED)) {
7740 if (forum_tp_is_tracked($forumobject)) {
7741 $linktext = get_string('notrackforum', 'forum');
7742 } else {
7743 $linktext = get_string('trackforum', 'forum');
7745 $url = new moodle_url('/mod/forum/settracking.php', array('id'=>$forumobject->id));
7746 $forumnode->add($linktext, $url, navigation_node::TYPE_SETTING);
7750 if (!isloggedin() && $PAGE->course->id == SITEID) {
7751 $userid = guest_user()->id;
7752 } else {
7753 $userid = $USER->id;
7756 $hascourseaccess = ($PAGE->course->id == SITEID) || can_access_course($PAGE->course, $userid);
7757 $enablerssfeeds = !empty($CFG->enablerssfeeds) && !empty($CFG->forum_enablerssfeeds);
7759 if ($enablerssfeeds && $forumobject->rsstype && $forumobject->rssarticles && $hascourseaccess) {
7761 if (!function_exists('rss_get_url')) {
7762 require_once("$CFG->libdir/rsslib.php");
7765 if ($forumobject->rsstype == 1) {
7766 $string = get_string('rsssubscriberssdiscussions','forum');
7767 } else {
7768 $string = get_string('rsssubscriberssposts','forum');
7771 $url = new moodle_url(rss_get_url($PAGE->cm->context->id, $userid, "mod_forum", $forumobject->id));
7772 $forumnode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', ''));
7777 * Abstract class used by forum subscriber selection controls
7778 * @package mod-forum
7779 * @copyright 2009 Sam Hemelryk
7780 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7782 abstract class forum_subscriber_selector_base extends user_selector_base {
7785 * The id of the forum this selector is being used for
7786 * @var int
7788 protected $forumid = null;
7790 * The context of the forum this selector is being used for
7791 * @var object
7793 protected $context = null;
7795 * The id of the current group
7796 * @var int
7798 protected $currentgroup = null;
7801 * Constructor method
7802 * @param string $name
7803 * @param array $options
7805 public function __construct($name, $options) {
7806 $options['accesscontext'] = $options['context'];
7807 parent::__construct($name, $options);
7808 if (isset($options['context'])) {
7809 $this->context = $options['context'];
7811 if (isset($options['currentgroup'])) {
7812 $this->currentgroup = $options['currentgroup'];
7814 if (isset($options['forumid'])) {
7815 $this->forumid = $options['forumid'];
7820 * Returns an array of options to seralise and store for searches
7822 * @return array
7824 protected function get_options() {
7825 global $CFG;
7826 $options = parent::get_options();
7827 $options['file'] = substr(__FILE__, strlen($CFG->dirroot.'/'));
7828 $options['context'] = $this->context;
7829 $options['currentgroup'] = $this->currentgroup;
7830 $options['forumid'] = $this->forumid;
7831 return $options;
7837 * A user selector control for potential subscribers to the selected forum
7838 * @package mod-forum
7839 * @copyright 2009 Sam Hemelryk
7840 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7842 class forum_potential_subscriber_selector extends forum_subscriber_selector_base {
7844 * If set to true EVERYONE in this course is force subscribed to this forum
7845 * @var bool
7847 protected $forcesubscribed = false;
7849 * Can be used to store existing subscribers so that they can be removed from
7850 * the potential subscribers list
7852 protected $existingsubscribers = array();
7855 * Constructor method
7856 * @param string $name
7857 * @param array $options
7859 public function __construct($name, $options) {
7860 parent::__construct($name, $options);
7861 if (isset($options['forcesubscribed'])) {
7862 $this->forcesubscribed=true;
7867 * Returns an arary of options for this control
7868 * @return array
7870 protected function get_options() {
7871 $options = parent::get_options();
7872 if ($this->forcesubscribed===true) {
7873 $options['forcesubscribed']=1;
7875 return $options;
7879 * Finds all potential users
7881 * Potential subscribers are all enroled users who are not already subscribed.
7883 * @param string $search
7884 * @return array
7886 public function find_users($search) {
7887 global $DB;
7889 $whereconditions = array();
7890 list($wherecondition, $params) = $this->search_sql($search, 'u');
7891 if ($wherecondition) {
7892 $whereconditions[] = $wherecondition;
7895 if (!$this->forcesubscribed) {
7896 $existingids = array();
7897 foreach ($this->existingsubscribers as $group) {
7898 foreach ($group as $user) {
7899 $existingids[$user->id] = 1;
7902 if ($existingids) {
7903 list($usertest, $userparams) = $DB->get_in_or_equal(
7904 array_keys($existingids), SQL_PARAMS_NAMED, 'existing', false);
7905 $whereconditions[] = 'u.id ' . $usertest;
7906 $params = array_merge($params, $userparams);
7910 if ($whereconditions) {
7911 $wherecondition = 'WHERE ' . implode(' AND ', $whereconditions);
7914 list($esql, $eparams) = get_enrolled_sql($this->context, '', $this->currentgroup, true);
7915 $params = array_merge($params, $eparams);
7917 $fields = 'SELECT ' . $this->required_fields_sql('u');
7918 $countfields = 'SELECT COUNT(u.id)';
7920 $sql = " FROM {user} u
7921 JOIN ($esql) je ON je.id = u.id
7922 $wherecondition";
7924 list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
7925 $order = ' ORDER BY ' . $sort;
7927 // Check to see if there are too many to show sensibly.
7928 if (!$this->is_validating()) {
7929 $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
7930 if ($potentialmemberscount > $this->maxusersperpage) {
7931 return $this->too_many_results($search, $potentialmemberscount);
7935 // If not, show them.
7936 $availableusers = $DB->get_records_sql($fields . $sql . $order, array_merge($params, $sortparams));
7938 if (empty($availableusers)) {
7939 return array();
7942 if ($this->forcesubscribed) {
7943 return array(get_string("existingsubscribers", 'forum') => $availableusers);
7944 } else {
7945 return array(get_string("potentialsubscribers", 'forum') => $availableusers);
7950 * Sets the existing subscribers
7951 * @param array $users
7953 public function set_existing_subscribers(array $users) {
7954 $this->existingsubscribers = $users;
7958 * Sets this forum as force subscribed or not
7960 public function set_force_subscribed($setting=true) {
7961 $this->forcesubscribed = true;
7966 * User selector control for removing subscribed users
7967 * @package mod-forum
7968 * @copyright 2009 Sam Hemelryk
7969 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7971 class forum_existing_subscriber_selector extends forum_subscriber_selector_base {
7974 * Finds all subscribed users
7976 * @param string $search
7977 * @return array
7979 public function find_users($search) {
7980 global $DB;
7981 list($wherecondition, $params) = $this->search_sql($search, 'u');
7982 $params['forumid'] = $this->forumid;
7984 // only active enrolled or everybody on the frontpage
7985 list($esql, $eparams) = get_enrolled_sql($this->context, '', $this->currentgroup, true);
7986 $fields = $this->required_fields_sql('u');
7987 list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
7988 $params = array_merge($params, $eparams, $sortparams);
7990 $subscribers = $DB->get_records_sql("SELECT $fields
7991 FROM {user} u
7992 JOIN ($esql) je ON je.id = u.id
7993 JOIN {forum_subscriptions} s ON s.userid = u.id
7994 WHERE $wherecondition AND s.forum = :forumid
7995 ORDER BY $sort", $params);
7997 return array(get_string("existingsubscribers", 'forum') => $subscribers);
8003 * Adds information about unread messages, that is only required for the course view page (and
8004 * similar), to the course-module object.
8005 * @param cm_info $cm Course-module object
8007 function forum_cm_info_view(cm_info $cm) {
8008 global $CFG;
8010 if (forum_tp_can_track_forums()) {
8011 if ($unread = forum_tp_count_forum_unread_posts($cm, $cm->get_course())) {
8012 $out = '<span class="unread"> <a href="' . $cm->url . '">';
8013 if ($unread == 1) {
8014 $out .= get_string('unreadpostsone', 'forum');
8015 } else {
8016 $out .= get_string('unreadpostsnumber', 'forum', $unread);
8018 $out .= '</a></span>';
8019 $cm->set_after_link($out);
8025 * Return a list of page types
8026 * @param string $pagetype current page type
8027 * @param stdClass $parentcontext Block's parent context
8028 * @param stdClass $currentcontext Current context of block
8030 function forum_page_type_list($pagetype, $parentcontext, $currentcontext) {
8031 $forum_pagetype = array(
8032 'mod-forum-*'=>get_string('page-mod-forum-x', 'forum'),
8033 'mod-forum-view'=>get_string('page-mod-forum-view', 'forum'),
8034 'mod-forum-discuss'=>get_string('page-mod-forum-discuss', 'forum')
8036 return $forum_pagetype;
8040 * Gets all of the courses where the provided user has posted in a forum.
8042 * @global moodle_database $DB The database connection
8043 * @param stdClass $user The user who's posts we are looking for
8044 * @param bool $discussionsonly If true only look for discussions started by the user
8045 * @param bool $includecontexts If set to trye contexts for the courses will be preloaded
8046 * @param int $limitfrom The offset of records to return
8047 * @param int $limitnum The number of records to return
8048 * @return array An array of courses
8050 function forum_get_courses_user_posted_in($user, $discussionsonly = false, $includecontexts = true, $limitfrom = null, $limitnum = null) {
8051 global $DB;
8053 // If we are only after discussions we need only look at the forum_discussions
8054 // table and join to the userid there. If we are looking for posts then we need
8055 // to join to the forum_posts table.
8056 if (!$discussionsonly) {
8057 $subquery = "(SELECT DISTINCT fd.course
8058 FROM {forum_discussions} fd
8059 JOIN {forum_posts} fp ON fp.discussion = fd.id
8060 WHERE fp.userid = :userid )";
8061 } else {
8062 $subquery= "(SELECT DISTINCT fd.course
8063 FROM {forum_discussions} fd
8064 WHERE fd.userid = :userid )";
8067 $params = array('userid' => $user->id);
8069 // Join to the context table so that we can preload contexts if required.
8070 if ($includecontexts) {
8071 $ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
8072 $ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
8073 $params['contextlevel'] = CONTEXT_COURSE;
8074 } else {
8075 $ctxselect = '';
8076 $ctxjoin = '';
8079 // Now we need to get all of the courses to search.
8080 // All courses where the user has posted within a forum will be returned.
8081 $sql = "SELECT c.* $ctxselect
8082 FROM {course} c
8083 $ctxjoin
8084 WHERE c.id IN ($subquery)";
8085 $courses = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
8086 if ($includecontexts) {
8087 array_map('context_helper::preload_from_record', $courses);
8089 return $courses;
8093 * Gets all of the forums a user has posted in for one or more courses.
8095 * @global moodle_database $DB
8096 * @param stdClass $user
8097 * @param array $courseids An array of courseids to search or if not provided
8098 * all courses the user has posted within
8099 * @param bool $discussionsonly If true then only forums where the user has started
8100 * a discussion will be returned.
8101 * @param int $limitfrom The offset of records to return
8102 * @param int $limitnum The number of records to return
8103 * @return array An array of forums the user has posted within in the provided courses
8105 function forum_get_forums_user_posted_in($user, array $courseids = null, $discussionsonly = false, $limitfrom = null, $limitnum = null) {
8106 global $DB;
8108 if (!is_null($courseids)) {
8109 list($coursewhere, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'courseid');
8110 $coursewhere = ' AND f.course '.$coursewhere;
8111 } else {
8112 $coursewhere = '';
8113 $params = array();
8115 $params['userid'] = $user->id;
8116 $params['forum'] = 'forum';
8118 if ($discussionsonly) {
8119 $join = 'JOIN {forum_discussions} ff ON ff.forum = f.id';
8120 } else {
8121 $join = 'JOIN {forum_discussions} fd ON fd.forum = f.id
8122 JOIN {forum_posts} ff ON ff.discussion = fd.id';
8125 $sql = "SELECT f.*, cm.id AS cmid
8126 FROM {forum} f
8127 JOIN {course_modules} cm ON cm.instance = f.id
8128 JOIN {modules} m ON m.id = cm.module
8129 JOIN (
8130 SELECT f.id
8131 FROM {forum} f
8132 {$join}
8133 WHERE ff.userid = :userid
8134 GROUP BY f.id
8135 ) j ON j.id = f.id
8136 WHERE m.name = :forum
8137 {$coursewhere}";
8139 $courseforums = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
8140 return $courseforums;
8144 * Returns posts made by the selected user in the requested courses.
8146 * This method can be used to return all of the posts made by the requested user
8147 * within the given courses.
8148 * For each course the access of the current user and requested user is checked
8149 * and then for each post access to the post and forum is checked as well.
8151 * This function is safe to use with usercapabilities.
8153 * @global moodle_database $DB
8154 * @param stdClass $user The user whose posts we want to get
8155 * @param array $courses The courses to search
8156 * @param bool $musthaveaccess If set to true errors will be thrown if the user
8157 * cannot access one or more of the courses to search
8158 * @param bool $discussionsonly If set to true only discussion starting posts
8159 * will be returned.
8160 * @param int $limitfrom The offset of records to return
8161 * @param int $limitnum The number of records to return
8162 * @return stdClass An object the following properties
8163 * ->totalcount: the total number of posts made by the requested user
8164 * that the current user can see.
8165 * ->courses: An array of courses the current user can see that the
8166 * requested user has posted in.
8167 * ->forums: An array of forums relating to the posts returned in the
8168 * property below.
8169 * ->posts: An array containing the posts to show for this request.
8171 function forum_get_posts_by_user($user, array $courses, $musthaveaccess = false, $discussionsonly = false, $limitfrom = 0, $limitnum = 50) {
8172 global $DB, $USER, $CFG;
8174 $return = new stdClass;
8175 $return->totalcount = 0; // The total number of posts that the current user is able to view
8176 $return->courses = array(); // The courses the current user can access
8177 $return->forums = array(); // The forums that the current user can access that contain posts
8178 $return->posts = array(); // The posts to display
8180 // First up a small sanity check. If there are no courses to check we can
8181 // return immediately, there is obviously nothing to search.
8182 if (empty($courses)) {
8183 return $return;
8186 // A couple of quick setups
8187 $isloggedin = isloggedin();
8188 $isguestuser = $isloggedin && isguestuser();
8189 $iscurrentuser = $isloggedin && $USER->id == $user->id;
8191 // Checkout whether or not the current user has capabilities over the requested
8192 // user and if so they have the capabilities required to view the requested
8193 // users content.
8194 $usercontext = context_user::instance($user->id, MUST_EXIST);
8195 $hascapsonuser = !$iscurrentuser && $DB->record_exists('role_assignments', array('userid' => $USER->id, 'contextid' => $usercontext->id));
8196 $hascapsonuser = $hascapsonuser && has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), $usercontext);
8198 // Before we actually search each course we need to check the user's access to the
8199 // course. If the user doesn't have the appropraite access then we either throw an
8200 // error if a particular course was requested or we just skip over the course.
8201 foreach ($courses as $course) {
8202 $coursecontext = context_course::instance($course->id, MUST_EXIST);
8203 if ($iscurrentuser || $hascapsonuser) {
8204 // If it is the current user, or the current user has capabilities to the
8205 // requested user then all we need to do is check the requested users
8206 // current access to the course.
8207 // Note: There is no need to check group access or anything of the like
8208 // as either the current user is the requested user, or has granted
8209 // capabilities on the requested user. Either way they can see what the
8210 // requested user posted, although its VERY unlikely in the `parent` situation
8211 // that the current user will be able to view the posts in context.
8212 if (!is_viewing($coursecontext, $user) && !is_enrolled($coursecontext, $user)) {
8213 // Need to have full access to a course to see the rest of own info
8214 if ($musthaveaccess) {
8215 print_error('errorenrolmentrequired', 'forum');
8217 continue;
8219 } else {
8220 // Check whether the current user is enrolled or has access to view the course
8221 // if they don't we immediately have a problem.
8222 if (!can_access_course($course)) {
8223 if ($musthaveaccess) {
8224 print_error('errorenrolmentrequired', 'forum');
8226 continue;
8229 // Check whether the requested user is enrolled or has access to view the course
8230 // if they don't we immediately have a problem.
8231 if (!can_access_course($course, $user)) {
8232 if ($musthaveaccess) {
8233 print_error('notenrolled', 'forum');
8235 continue;
8238 // If groups are in use and enforced throughout the course then make sure
8239 // we can meet in at least one course level group.
8240 // Note that we check if either the current user or the requested user have
8241 // the capability to access all groups. This is because with that capability
8242 // a user in group A could post in the group B forum. Grrrr.
8243 if (groups_get_course_groupmode($course) == SEPARATEGROUPS && $course->groupmodeforce
8244 && !has_capability('moodle/site:accessallgroups', $coursecontext) && !has_capability('moodle/site:accessallgroups', $coursecontext, $user->id)) {
8245 // If its the guest user to bad... the guest user cannot access groups
8246 if (!$isloggedin or $isguestuser) {
8247 // do not use require_login() here because we might have already used require_login($course)
8248 if ($musthaveaccess) {
8249 redirect(get_login_url());
8251 continue;
8253 // Get the groups of the current user
8254 $mygroups = array_keys(groups_get_all_groups($course->id, $USER->id, $course->defaultgroupingid, 'g.id, g.name'));
8255 // Get the groups the requested user is a member of
8256 $usergroups = array_keys(groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid, 'g.id, g.name'));
8257 // Check whether they are members of the same group. If they are great.
8258 $intersect = array_intersect($mygroups, $usergroups);
8259 if (empty($intersect)) {
8260 // But they're not... if it was a specific course throw an error otherwise
8261 // just skip this course so that it is not searched.
8262 if ($musthaveaccess) {
8263 print_error("groupnotamember", '', $CFG->wwwroot."/course/view.php?id=$course->id");
8265 continue;
8269 // Woo hoo we got this far which means the current user can search this
8270 // this course for the requested user. Although this is only the course accessibility
8271 // handling that is complete, the forum accessibility tests are yet to come.
8272 $return->courses[$course->id] = $course;
8274 // No longer beed $courses array - lose it not it may be big
8275 unset($courses);
8277 // Make sure that we have some courses to search
8278 if (empty($return->courses)) {
8279 // If we don't have any courses to search then the reality is that the current
8280 // user doesn't have access to any courses is which the requested user has posted.
8281 // Although we do know at this point that the requested user has posts.
8282 if ($musthaveaccess) {
8283 print_error('permissiondenied');
8284 } else {
8285 return $return;
8289 // Next step: Collect all of the forums that we will want to search.
8290 // It is important to note that this step isn't actually about searching, it is
8291 // about determining which forums we can search by testing accessibility.
8292 $forums = forum_get_forums_user_posted_in($user, array_keys($return->courses), $discussionsonly);
8294 // Will be used to build the where conditions for the search
8295 $forumsearchwhere = array();
8296 // Will be used to store the where condition params for the search
8297 $forumsearchparams = array();
8298 // Will record forums where the user can freely access everything
8299 $forumsearchfullaccess = array();
8300 // DB caching friendly
8301 $now = round(time(), -2);
8302 // For each course to search we want to find the forums the user has posted in
8303 // and providing the current user can access the forum create a search condition
8304 // for the forum to get the requested users posts.
8305 foreach ($return->courses as $course) {
8306 // Now we need to get the forums
8307 $modinfo = get_fast_modinfo($course);
8308 if (empty($modinfo->instances['forum'])) {
8309 // hmmm, no forums? well at least its easy... skip!
8310 continue;
8312 // Iterate
8313 foreach ($modinfo->get_instances_of('forum') as $forumid => $cm) {
8314 if (!$cm->uservisible or !isset($forums[$forumid])) {
8315 continue;
8317 // Get the forum in question
8318 $forum = $forums[$forumid];
8320 // This is needed for functionality later on in the forum code. It is converted to an object
8321 // because the cm_info is readonly from 2.6. This is a dirty hack because some other parts of the
8322 // code were expecting an writeable object. See {@link forum_print_post()}.
8323 $forum->cm = new stdClass();
8324 foreach ($cm as $key => $value) {
8325 $forum->cm->$key = $value;
8328 // Check that either the current user can view the forum, or that the
8329 // current user has capabilities over the requested user and the requested
8330 // user can view the discussion
8331 if (!has_capability('mod/forum:viewdiscussion', $cm->context) && !($hascapsonuser && has_capability('mod/forum:viewdiscussion', $cm->context, $user->id))) {
8332 continue;
8335 // This will contain forum specific where clauses
8336 $forumsearchselect = array();
8337 if (!$iscurrentuser && !$hascapsonuser) {
8338 // Make sure we check group access
8339 if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $cm->context)) {
8340 $groups = $modinfo->get_groups($cm->groupingid);
8341 $groups[] = -1;
8342 list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
8343 $forumsearchparams = array_merge($forumsearchparams, $groupid_params);
8344 $forumsearchselect[] = "d.groupid $groupid_sql";
8347 // hidden timed discussions
8348 if (!empty($CFG->forum_enabletimedposts) && !has_capability('mod/forum:viewhiddentimedposts', $cm->context)) {
8349 $forumsearchselect[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
8350 $forumsearchparams['userid'.$forumid] = $user->id;
8351 $forumsearchparams['timestart'.$forumid] = $now;
8352 $forumsearchparams['timeend'.$forumid] = $now;
8355 // qanda access
8356 if ($forum->type == 'qanda' && !has_capability('mod/forum:viewqandawithoutposting', $cm->context)) {
8357 // We need to check whether the user has posted in the qanda forum.
8358 $discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $user->id);
8359 if (!empty($discussionspostedin)) {
8360 $forumonlydiscussions = array(); // Holds discussion ids for the discussions the user is allowed to see in this forum.
8361 foreach ($discussionspostedin as $d) {
8362 $forumonlydiscussions[] = $d->id;
8364 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forumonlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
8365 $forumsearchparams = array_merge($forumsearchparams, $discussionid_params);
8366 $forumsearchselect[] = "(d.id $discussionid_sql OR p.parent = 0)";
8367 } else {
8368 $forumsearchselect[] = "p.parent = 0";
8373 if (count($forumsearchselect) > 0) {
8374 $forumsearchwhere[] = "(d.forum = :forum{$forumid} AND ".implode(" AND ", $forumsearchselect).")";
8375 $forumsearchparams['forum'.$forumid] = $forumid;
8376 } else {
8377 $forumsearchfullaccess[] = $forumid;
8379 } else {
8380 // The current user/parent can see all of their own posts
8381 $forumsearchfullaccess[] = $forumid;
8386 // If we dont have any search conditions, and we don't have any forums where
8387 // the user has full access then we just return the default.
8388 if (empty($forumsearchwhere) && empty($forumsearchfullaccess)) {
8389 return $return;
8392 // Prepare a where condition for the full access forums.
8393 if (count($forumsearchfullaccess) > 0) {
8394 list($fullidsql, $fullidparams) = $DB->get_in_or_equal($forumsearchfullaccess, SQL_PARAMS_NAMED, 'fula');
8395 $forumsearchparams = array_merge($forumsearchparams, $fullidparams);
8396 $forumsearchwhere[] = "(d.forum $fullidsql)";
8399 // Prepare SQL to both count and search.
8400 // We alias user.id to useridx because we forum_posts already has a userid field and not aliasing this would break
8401 // oracle and mssql.
8402 $userfields = user_picture::fields('u', null, 'useridx');
8403 $countsql = 'SELECT COUNT(*) ';
8404 $selectsql = 'SELECT p.*, d.forum, d.name AS discussionname, '.$userfields.' ';
8405 $wheresql = implode(" OR ", $forumsearchwhere);
8407 if ($discussionsonly) {
8408 if ($wheresql == '') {
8409 $wheresql = 'p.parent = 0';
8410 } else {
8411 $wheresql = 'p.parent = 0 AND ('.$wheresql.')';
8415 $sql = "FROM {forum_posts} p
8416 JOIN {forum_discussions} d ON d.id = p.discussion
8417 JOIN {user} u ON u.id = p.userid
8418 WHERE ($wheresql)
8419 AND p.userid = :userid ";
8420 $orderby = "ORDER BY p.modified DESC";
8421 $forumsearchparams['userid'] = $user->id;
8423 // Set the total number posts made by the requested user that the current user can see
8424 $return->totalcount = $DB->count_records_sql($countsql.$sql, $forumsearchparams);
8425 // Set the collection of posts that has been requested
8426 $return->posts = $DB->get_records_sql($selectsql.$sql.$orderby, $forumsearchparams, $limitfrom, $limitnum);
8428 // We need to build an array of forums for which posts will be displayed.
8429 // We do this here to save the caller needing to retrieve them themselves before
8430 // printing these forums posts. Given we have the forums already there is
8431 // practically no overhead here.
8432 foreach ($return->posts as $post) {
8433 if (!array_key_exists($post->forum, $return->forums)) {
8434 $return->forums[$post->forum] = $forums[$post->forum];
8438 return $return;
8442 * Set the per-forum maildigest option for the specified user.
8444 * @param stdClass $forum The forum to set the option for.
8445 * @param int $maildigest The maildigest option.
8446 * @param stdClass $user The user object. This defaults to the global $USER object.
8447 * @throws invalid_digest_setting thrown if an invalid maildigest option is provided.
8449 function forum_set_user_maildigest($forum, $maildigest, $user = null) {
8450 global $DB, $USER;
8452 if (is_number($forum)) {
8453 $forum = $DB->get_record('forum', array('id' => $forum));
8456 if ($user === null) {
8457 $user = $USER;
8460 $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
8461 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
8462 $context = context_module::instance($cm->id);
8464 // User must be allowed to see this forum.
8465 require_capability('mod/forum:viewdiscussion', $context, $user->id);
8467 // Validate the maildigest setting.
8468 $digestoptions = forum_get_user_digest_options($user);
8470 if (!isset($digestoptions[$maildigest])) {
8471 throw new moodle_exception('invaliddigestsetting', 'mod_forum');
8474 // Attempt to retrieve any existing forum digest record.
8475 $subscription = $DB->get_record('forum_digests', array(
8476 'userid' => $user->id,
8477 'forum' => $forum->id,
8480 // Create or Update the existing maildigest setting.
8481 if ($subscription) {
8482 if ($maildigest == -1) {
8483 $DB->delete_records('forum_digests', array('forum' => $forum->id, 'userid' => $user->id));
8484 } else if ($maildigest !== $subscription->maildigest) {
8485 // Only update the maildigest setting if it's changed.
8487 $subscription->maildigest = $maildigest;
8488 $DB->update_record('forum_digests', $subscription);
8490 } else {
8491 if ($maildigest != -1) {
8492 // Only insert the maildigest setting if it's non-default.
8494 $subscription = new stdClass();
8495 $subscription->forum = $forum->id;
8496 $subscription->userid = $user->id;
8497 $subscription->maildigest = $maildigest;
8498 $subscription->id = $DB->insert_record('forum_digests', $subscription);
8504 * Determine the maildigest setting for the specified user against the
8505 * specified forum.
8507 * @param Array $digests An array of forums and user digest settings.
8508 * @param stdClass $user The user object containing the id and maildigest default.
8509 * @param int $forumid The ID of the forum to check.
8510 * @return int The calculated maildigest setting for this user and forum.
8512 function forum_get_user_maildigest_bulk($digests, $user, $forumid) {
8513 if (isset($digests[$forumid]) && isset($digests[$forumid][$user->id])) {
8514 $maildigest = $digests[$forumid][$user->id];
8515 if ($maildigest === -1) {
8516 $maildigest = $user->maildigest;
8518 } else {
8519 $maildigest = $user->maildigest;
8521 return $maildigest;
8525 * Retrieve the list of available user digest options.
8527 * @param stdClass $user The user object. This defaults to the global $USER object.
8528 * @return array The mapping of values to digest options.
8530 function forum_get_user_digest_options($user = null) {
8531 global $USER;
8533 // Revert to the global user object.
8534 if ($user === null) {
8535 $user = $USER;
8538 $digestoptions = array();
8539 $digestoptions['0'] = get_string('emaildigestoffshort', 'mod_forum');
8540 $digestoptions['1'] = get_string('emaildigestcompleteshort', 'mod_forum');
8541 $digestoptions['2'] = get_string('emaildigestsubjectsshort', 'mod_forum');
8543 // We need to add the default digest option at the end - it relies on
8544 // the contents of the existing values.
8545 $digestoptions['-1'] = get_string('emaildigestdefault', 'mod_forum',
8546 $digestoptions[$user->maildigest]);
8548 // Resort the options to be in a sensible order.
8549 ksort($digestoptions);
8551 return $digestoptions;