MDL-29731 Bugs in recent activity when using separate groups
[moodle.git] / mod / forum / lib.php
CommitLineData
df1ba0f4 1<?php
df1ba0f4 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.
13//
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/>.
16
17/**
bc196cbe
PS
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
df1ba0f4 22 */
23
bc196cbe
PS
24defined('MOODLE_INTERNAL') || die();
25
df1ba0f4 26/** Include required files */
f1e0649c 27require_once($CFG->libdir.'/filelib.php');
3b120e46 28require_once($CFG->libdir.'/eventslib.php');
c23c9009 29require_once($CFG->dirroot.'/user/selector/lib.php');
61a339e5 30require_once($CFG->dirroot.'/mod/forum/post_form.php');
7f6689e4 31
501cdbd8 32/// CONSTANTS ///////////////////////////////////////////////////////////
f93f848a 33
d3583b41 34define('FORUM_MODE_FLATOLDEST', 1);
35define('FORUM_MODE_FLATNEWEST', -1);
36define('FORUM_MODE_THREADED', 2);
37define('FORUM_MODE_NESTED', 3);
2e2e71a8 38
afef965e 39define('FORUM_CHOOSESUBSCRIBE', 0);
d3583b41 40define('FORUM_FORCESUBSCRIBE', 1);
41define('FORUM_INITIALSUBSCRIBE', 2);
098d27d4 42define('FORUM_DISALLOWSUBSCRIBE',3);
709f0ec8 43
eaf50aef 44define('FORUM_TRACKING_OFF', 0);
45define('FORUM_TRACKING_OPTIONAL', 1);
46define('FORUM_TRACKING_ON', 2);
47
8076010d 48define('FORUM_MAILED_PENDING', 0);
da484043
MO
49define('FORUM_MAILED_SUCCESS', 1);
50define('FORUM_MAILED_ERROR', 2);
51
103f34d8
PS
52if (!defined('FORUM_CRON_USER_CACHE')) {
53 /** Defines how many full user records are cached in forum cron. */
54 define('FORUM_CRON_USER_CACHE', 5000);
55}
56
caadf009 57/// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
58
0a4ac01b 59/**
60 * Given an object containing all the necessary data,
7cac0c4b 61 * (defined by the form in mod_form.php) this function
0a4ac01b 62 * will create a new instance and return the id number
63 * of the new instance.
df1ba0f4 64 *
6b04fdc0
PS
65 * @param stdClass $forum add forum instance
66 * @param mod_forum_mod_form $mform
6b7de0bb 67 * @return int intance id
3a5e1d06 68 */
6b04fdc0 69function forum_add_instance($forum, $mform = null) {
c18269c7 70 global $CFG, $DB;
caadf009 71
72 $forum->timemodified = time();
73
353228d8 74 if (empty($forum->assessed)) {
f2f56406 75 $forum->assessed = 0;
76 }
2b63df96 77
353228d8 78 if (empty($forum->ratingtime) or empty($forum->assessed)) {
98914efd 79 $forum->assesstimestart = 0;
80 $forum->assesstimefinish = 0;
81 }
caadf009 82
a8f3a651 83 $forum->id = $DB->insert_record('forum', $forum);
bf0f06b1 84 $modcontext = context_module::instance($forum->coursemodule);
cb9a975f 85
d3583b41 86 if ($forum->type == 'single') { // Create related discussion.
39790bd8 87 $discussion = new stdClass();
e2d7687f 88 $discussion->course = $forum->course;
89 $discussion->forum = $forum->id;
90 $discussion->name = $forum->name;
e2d7687f 91 $discussion->assessed = $forum->assessed;
6606c00f
MD
92 $discussion->message = $forum->intro;
93 $discussion->messageformat = $forum->introformat;
bf0f06b1 94 $discussion->messagetrust = trusttext_trusted(context_course::instance($forum->course));
e2d7687f 95 $discussion->mailnow = false;
96 $discussion->groupid = -1;
caadf009 97
dbf4d660 98 $message = '';
99
42776c94
PS
100 $discussion->id = forum_add_discussion($discussion, null, $message);
101
102 if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
a11aa38e 103 // Ugly hack - we need to copy the files somehow.
42776c94
PS
104 $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
105 $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
106
a11aa38e
PS
107 $options = array('subdirs'=>true); // Use the same options as intro field!
108 $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
42776c94 109 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
caadf009 110 }
111 }
8f0cd6ef 112
3c268f25 113 if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
9695ff81 114 $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email');
709f0ec8 115 foreach ($users as $user) {
116 forum_subscribe($user->id, $forum->id);
117 }
118 }
caadf009 119
612607bd 120 forum_grade_item_update($forum);
353228d8 121
caadf009 122 return $forum->id;
123}
124
125
13bbe067 126/**
0a4ac01b 127 * Given an object containing all the necessary data,
7cac0c4b 128 * (defined by the form in mod_form.php) this function
0a4ac01b 129 * will update an existing instance with new data.
df1ba0f4 130 *
131 * @global object
90f4745c 132 * @param object $forum forum instance (with magic quotes)
6b7de0bb 133 * @return bool success
90f4745c 134 */
42776c94 135function forum_update_instance($forum, $mform) {
862f5a49 136 global $DB, $OUTPUT, $USER;
c18269c7 137
caadf009 138 $forum->timemodified = time();
353228d8 139 $forum->id = $forum->instance;
caadf009 140
f0da6b85 141 if (empty($forum->assessed)) {
f2f56406 142 $forum->assessed = 0;
143 }
2b63df96 144
353228d8 145 if (empty($forum->ratingtime) or empty($forum->assessed)) {
98914efd 146 $forum->assesstimestart = 0;
147 $forum->assesstimefinish = 0;
148 }
149
c18269c7 150 $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
13bbe067 151
152 // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
153 // if scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
154 // 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
155 if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
156 forum_update_grades($forum); // recalculate grades for the forum
157 }
158
d3583b41 159 if ($forum->type == 'single') { // Update related discussion and post.
35b81f27
RT
160 $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
161 if (!empty($discussions)) {
162 if (count($discussions) > 1) {
163 echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
164 }
165 $discussion = array_pop($discussions);
166 } else {
167 // try to recover by creating initial discussion - MDL-16262
168 $discussion = new stdClass();
169 $discussion->course = $forum->course;
170 $discussion->forum = $forum->id;
171 $discussion->name = $forum->name;
172 $discussion->assessed = $forum->assessed;
173 $discussion->message = $forum->intro;
174 $discussion->messageformat = $forum->introformat;
175 $discussion->messagetrust = true;
176 $discussion->mailnow = false;
177 $discussion->groupid = -1;
178
179 $message = '';
180
181 forum_add_discussion($discussion, null, $message);
182
183 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
184 print_error('cannotadd', 'forum');
caadf009 185 }
186 }
c18269c7 187 if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
12e57b92 188 print_error('cannotfindfirstpost', 'forum');
caadf009 189 }
190
6606c00f 191 $cm = get_coursemodule_from_instance('forum', $forum->id);
bf0f06b1 192 $modcontext = context_module::instance($cm->id, MUST_EXIST);
42776c94 193
a11aa38e 194 $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
6606c00f
MD
195 $post->subject = $forum->name;
196 $post->message = $forum->intro;
197 $post->messageformat = $forum->introformat;
198 $post->messagetrust = trusttext_trusted($modcontext);
199 $post->modified = $forum->timemodified;
a11aa38e
PS
200 $post->userid = $USER->id; // MDL-18599, so that current teacher can take ownership of activities.
201
202 if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
203 // Ugly hack - we need to copy the files somehow.
204 $options = array('subdirs'=>true); // Use the same options as intro field!
205 $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
206 }
caadf009 207
a8d6ef8c 208 $DB->update_record('forum_posts', $post);
caadf009 209 $discussion->name = $forum->name;
a8d6ef8c 210 $DB->update_record('forum_discussions', $discussion);
caadf009 211 }
212
a8d6ef8c 213 $DB->update_record('forum', $forum);
353228d8 214
4a913724
GPL
215 $modcontext = context_module::instance($forum->coursemodule);
216 if (($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) && ($oldforum->forcesubscribe <> $forum->forcesubscribe)) {
217 $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
218 foreach ($users as $user) {
219 forum_subscribe($user->id, $forum->id);
220 }
221 }
222
353228d8 223 forum_grade_item_update($forum);
224
225 return true;
caadf009 226}
227
228
0a4ac01b 229/**
230 * Given an ID of an instance of this module,
231 * this function will permanently delete the instance
232 * and any data that depends on it.
df1ba0f4 233 *
234 * @global object
235 * @param int $id forum instance id
90f4745c 236 * @return bool success
0a4ac01b 237 */
caadf009 238function forum_delete_instance($id) {
c18269c7 239 global $DB;
caadf009 240
c18269c7 241 if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
caadf009 242 return false;
243 }
455a8fed 244 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
245 return false;
246 }
247 if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
248 return false;
249 }
250
bf0f06b1 251 $context = context_module::instance($cm->id);
caadf009 252
fa686bc4 253 // now get rid of all files
254 $fs = get_file_storage();
455a8fed 255 $fs->delete_area_files($context->id);
fa686bc4 256
caadf009 257 $result = true;
258
c18269c7 259 if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
caadf009 260 foreach ($discussions as $discussion) {
455a8fed 261 if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
caadf009 262 $result = false;
263 }
264 }
265 }
266
c18269c7 267 if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
caadf009 268 $result = false;
269 }
270
f37da850 271 forum_tp_delete_read_records(-1, -1, -1, $forum->id);
272
415f8c3e 273 if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
caadf009 274 $result = false;
275 }
276
353228d8 277 forum_grade_item_delete($forum);
278
caadf009 279 return $result;
280}
281
282
4e781c7b 283/**
284 * Indicates API features that the forum supports.
285 *
df1ba0f4 286 * @uses FEATURE_GROUPS
287 * @uses FEATURE_GROUPINGS
288 * @uses FEATURE_GROUPMEMBERSONLY
289 * @uses FEATURE_MOD_INTRO
290 * @uses FEATURE_COMPLETION_TRACKS_VIEWS
291 * @uses FEATURE_COMPLETION_HAS_RULES
292 * @uses FEATURE_GRADE_HAS_GRADE
293 * @uses FEATURE_GRADE_OUTCOMES
4e781c7b 294 * @param string $feature
295 * @return mixed True if yes (some features may use other values)
296 */
297function forum_supports($feature) {
298 switch($feature) {
42f103be 299 case FEATURE_GROUPS: return true;
300 case FEATURE_GROUPINGS: return true;
301 case FEATURE_GROUPMEMBERSONLY: return true;
dc5c2bd9 302 case FEATURE_MOD_INTRO: return true;
4e781c7b 303 case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
42f103be 304 case FEATURE_COMPLETION_HAS_RULES: return true;
305 case FEATURE_GRADE_HAS_GRADE: return true;
306 case FEATURE_GRADE_OUTCOMES: return true;
4bfdcfcf
EL
307 case FEATURE_RATE: return true;
308 case FEATURE_BACKUP_MOODLE2: return true;
3e4c2435 309 case FEATURE_SHOW_DESCRIPTION: return true;
50da4ddd 310 case FEATURE_PLAGIARISM: return true;
42f103be 311
49f6e5f4 312 default: return null;
4e781c7b 313 }
314}
315
316
317/**
318 * Obtains the automatic completion state for this forum based on any conditions
319 * in forum settings.
320 *
df1ba0f4 321 * @global object
322 * @global object
4e781c7b 323 * @param object $course Course
324 * @param object $cm Course-module
325 * @param int $userid User ID
326 * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
327 * @return bool True if completed, false if not. (If no conditions, then return
328 * value depends on comparison type)
329 */
330function forum_get_completion_state($course,$cm,$userid,$type) {
331 global $CFG,$DB;
332
333 // Get forum details
6093af9b 334 if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
4e781c7b 335 throw new Exception("Can't find forum {$cm->instance}");
336 }
337
338 $result=$type; // Default return value
339
340 $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
341 $postcountsql="
5e7f2b0b 342SELECT
343 COUNT(1)
344FROM
345 {forum_posts} fp
49f6e5f4 346 INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
4e781c7b 347WHERE
348 fp.userid=:userid AND fd.forum=:forumid";
349
6093af9b 350 if ($forum->completiondiscussions) {
4e781c7b 351 $value = $forum->completiondiscussions <=
6093af9b 352 $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
353 if ($type == COMPLETION_AND) {
354 $result = $result && $value;
4e781c7b 355 } else {
6093af9b 356 $result = $result || $value;
4e781c7b 357 }
358 }
6093af9b 359 if ($forum->completionreplies) {
5e7f2b0b 360 $value = $forum->completionreplies <=
6093af9b 361 $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
362 if ($type==COMPLETION_AND) {
363 $result = $result && $value;
4e781c7b 364 } else {
6093af9b 365 $result = $result || $value;
4e781c7b 366 }
367 }
6093af9b 368 if ($forum->completionposts) {
4e781c7b 369 $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
6093af9b 370 if ($type == COMPLETION_AND) {
371 $result = $result && $value;
4e781c7b 372 } else {
6093af9b 373 $result = $result || $value;
4e781c7b 374 }
375 }
376
5e7f2b0b 377 return $result;
4e781c7b 378}
379
8260050d
AD
380/**
381 * Create a message-id string to use in the custom headers of forum notification emails
382 *
383 * message-id is used by email clients to identify emails and to nest conversations
384 *
385 * @param int $postid The ID of the forum post we are notifying the user about
386 * @param int $usertoid The ID of the user being notified
387 * @param string $hostname The server's hostname
388 * @return string A unique message-id
389 */
1376b0dd 390function forum_get_email_message_id($postid, $usertoid, $hostname) {
2e616b54 391 return '<'.hash('sha256',$postid.'to'.$usertoid).'@'.$hostname.'>';
1376b0dd 392}
4e781c7b 393
103f34d8
PS
394/**
395 * Removes properties from user record that are not necessary
396 * for sending post notifications.
397 * @param stdClass $user
398 * @return void, $user parameter is modified
399 */
400function forum_cron_minimise_user_record(stdClass $user) {
401
402 // We store large amount of users in one huge array,
403 // make sure we do not store info there we do not actually need
404 // in mail generation code or messaging.
405
406 unset($user->institution);
407 unset($user->department);
408 unset($user->address);
409 unset($user->city);
410 unset($user->url);
411 unset($user->currentlogin);
412 unset($user->description);
413 unset($user->descriptionformat);
414}
415
0a4ac01b 416/**
417 * Function to be run periodically according to the moodle cron
418 * Finds all posts that have yet to be mailed out, and mails them
419 * out to all subscribers
df1ba0f4 420 *
421 * @global object
422 * @global object
423 * @global object
424 * @uses CONTEXT_MODULE
425 * @uses CONTEXT_COURSE
426 * @uses SITEID
427 * @uses FORMAT_PLAIN
90f4745c 428 * @return void
429 */
0fa18d5a 430function forum_cron() {
4e445355 431 global $CFG, $USER, $DB;
857b798b 432
a974c799 433 $site = get_site();
434
103f34d8
PS
435 // All users that are subscribed to any post that needs sending,
436 // please increase $CFG->extramemorylimit on large sites that
437 // send notifications to a large number of users.
a974c799 438 $users = array();
103f34d8 439 $userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!!
a974c799 440
441 // status arrays
442 $mailcount = array();
443 $errorcount = array();
444
445 // caches
446 $discussions = array();
447 $forums = array();
448 $courses = array();
449 $coursemodules = array();
a974c799 450 $subscribedusers = array();
aaf7a9dc 451
ec2137ba 452
0a4ac01b 453 // Posts older than 2 days will not be mailed. This is to avoid the problem where
454 // cron has not been running for a long time, and then suddenly people are flooded
455 // with mail from the past few weeks or months
3ecca1ee 456 $timenow = time();
457 $endtime = $timenow - $CFG->maxeditingtime;
0a4ac01b 458 $starttime = $endtime - 48 * 3600; // Two days earlier
3ecca1ee 459
90f4745c 460 if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
0a4ac01b 461 // Mark them all now as being mailed. It's unlikely but possible there
462 // might be an error later so that a post is NOT actually mailed out,
463 // but since mail isn't crucial, we can accept this risk. Doing it now
464 // prevents the risk of duplicated mails, which is a worse problem.
16b4e5b6 465
5fac3a5e 466 if (!forum_mark_old_posts_as_mailed($endtime)) {
467 mtrace('Errors occurred while trying to mark some posts as being mailed.');
468 return false; // Don't continue trying to mail them, in case we are in a cron loop
469 }
470
471 // checking post validity, and adding users to loop through later
472 foreach ($posts as $pid => $post) {
473
a974c799 474 $discussionid = $post->discussion;
475 if (!isset($discussions[$discussionid])) {
4e445355 476 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
a974c799 477 $discussions[$discussionid] = $discussion;
478 } else {
479 mtrace('Could not find discussion '.$discussionid);
480 unset($posts[$pid]);
481 continue;
482 }
5fac3a5e 483 }
a974c799 484 $forumid = $discussions[$discussionid]->forum;
485 if (!isset($forums[$forumid])) {
4e445355 486 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
a974c799 487 $forums[$forumid] = $forum;
488 } else {
489 mtrace('Could not find forum '.$forumid);
490 unset($posts[$pid]);
491 continue;
492 }
5fac3a5e 493 }
a974c799 494 $courseid = $forums[$forumid]->course;
495 if (!isset($courses[$courseid])) {
4e445355 496 if ($course = $DB->get_record('course', array('id' => $courseid))) {
a974c799 497 $courses[$courseid] = $course;
498 } else {
499 mtrace('Could not find course '.$courseid);
500 unset($posts[$pid]);
501 continue;
502 }
5fac3a5e 503 }
a974c799 504 if (!isset($coursemodules[$forumid])) {
505 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
506 $coursemodules[$forumid] = $cm;
507 } else {
9c670df6 508 mtrace('Could not find course module for forum '.$forumid);
a974c799 509 unset($posts[$pid]);
510 continue;
511 }
512 }
513
514
5fac3a5e 515 // caching subscribed users of each forum
a974c799 516 if (!isset($subscribedusers[$forumid])) {
bf0f06b1 517 $modcontext = context_module::instance($coursemodules[$forumid]->id);
c35ae136 518 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
704ca25c 519 foreach ($subusers as $postuser) {
520 // this user is subscribed to this forum
a5cef9c8 521 $subscribedusers[$forumid][$postuser->id] = $postuser->id;
103f34d8
PS
522 $userscount++;
523 if ($userscount > FORUM_CRON_USER_CACHE) {
524 // Store minimal user info.
525 $minuser = new stdClass();
526 $minuser->id = $postuser->id;
527 $users[$postuser->id] = $minuser;
528 } else {
529 // Cache full user record.
530 forum_cron_minimise_user_record($postuser);
531 $users[$postuser->id] = $postuser;
532 }
704ca25c 533 }
103f34d8
PS
534 // Release memory.
535 unset($subusers);
536 unset($postuser);
a974c799 537 }
5fac3a5e 538 }
a974c799 539
5fac3a5e 540 $mailcount[$pid] = 0;
541 $errorcount[$pid] = 0;
a974c799 542 }
5fac3a5e 543 }
caadf009 544
4dad2828 545 if ($users && $posts) {
edffca15 546
857b798b 547 $urlinfo = parse_url($CFG->wwwroot);
548 $hostname = $urlinfo['host'];
549
5fac3a5e 550 foreach ($users as $userto) {
a974c799 551
552 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
553
16b4e5b6 554 mtrace('Processing user '.$userto->id);
caadf009 555
103f34d8
PS
556 // Init user caches - we keep the cache for one cycle only,
557 // otherwise it could consume too much memory.
558 if (isset($userto->username)) {
559 $userto = clone($userto);
560 } else {
561 $userto = $DB->get_record('user', array('id' => $userto->id));
562 forum_cron_minimise_user_record($userto);
563 }
df1c2c71 564 $userto->viewfullnames = array();
565 $userto->canpost = array();
90f4745c 566 $userto->markposts = array();
669f2499 567
103f34d8
PS
568 // set this so that the capabilities are cached, and environment matches receiving user
569 cron_setup_user($userto);
570
9db5d080 571 // reset the caches
572 foreach ($coursemodules as $forumid=>$unused) {
39790bd8 573 $coursemodules[$forumid]->cache = new stdClass();
9db5d080 574 $coursemodules[$forumid]->cache->caps = array();
575 unset($coursemodules[$forumid]->uservisible);
576 }
577
4dad2828 578 foreach ($posts as $pid => $post) {
caadf009 579
4dad2828 580 // Set up the environment for the post, discussion, forum, course
a974c799 581 $discussion = $discussions[$post->discussion];
582 $forum = $forums[$discussion->forum];
583 $course = $courses[$forum->course];
90f4745c 584 $cm =& $coursemodules[$forum->id];
4dad2828 585
586 // Do some checks to see if we can bail out now
72650054 587 // Only active enrolled users are in the list of subscribers
a5cef9c8 588 if (!isset($subscribedusers[$forum->id][$userto->id])) {
a974c799 589 continue; // user does not subscribe to this forum
4dad2828 590 }
4dad2828 591
67fc4f00 592 // Don't send email if the forum is Q&A and the user has not posted
1e966b8a
CF
593 // Initial topics are still mailed
594 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
67fc4f00
DC
595 mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
596 continue;
597 }
90f4745c 598
a5cef9c8 599 // Get info about the sending user
600 if (array_key_exists($post->userid, $users)) { // we might know him/her already
601 $userfrom = $users[$post->userid];
103f34d8
PS
602 if (!isset($userfrom->idnumber)) {
603 // Minimalised user info, fetch full record.
604 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
605 forum_cron_minimise_user_record($userfrom);
606 }
607
4e445355 608 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
103f34d8
PS
609 forum_cron_minimise_user_record($userfrom);
610 // Fetch only once if possible, we can add it to user list, it will be skipped anyway.
611 if ($userscount <= FORUM_CRON_USER_CACHE) {
612 $userscount++;
613 $users[$userfrom->id] = $userfrom;
614 }
615
a5cef9c8 616 } else {
617 mtrace('Could not find user '.$post->userid);
618 continue;
619 }
620
505ab5aa
AD
621 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
622
a974c799 623 // setup global $COURSE properly - needed for roles and languages
e8b7114d 624 cron_setup_user($userto, $course);
4dad2828 625
df1c2c71 626 // Fill caches
627 if (!isset($userto->viewfullnames[$forum->id])) {
bf0f06b1 628 $modcontext = context_module::instance($cm->id);
df1c2c71 629 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
630 }
8b79a625 631 if (!isset($userto->canpost[$discussion->id])) {
bf0f06b1 632 $modcontext = context_module::instance($cm->id);
8b79a625 633 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
df1c2c71 634 }
635 if (!isset($userfrom->groups[$forum->id])) {
636 if (!isset($userfrom->groups)) {
637 $userfrom->groups = array();
103f34d8
PS
638 if (isset($users[$userfrom->id])) {
639 $users[$userfrom->id]->groups = array();
640 }
df1c2c71 641 }
642 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
103f34d8
PS
643 if (isset($users[$userfrom->id])) {
644 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
645 }
df1c2c71 646 }
9f2ded76 647
a974c799 648 // Make sure groups allow this user to see this email
df1c2c71 649 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
9f2ded76 650 if (!groups_group_exists($discussion->groupid)) { // Can't find group
38bd362a 651 continue; // Be safe and don't send it to anyone
5fac3a5e 652 }
918e9805 653
2c386f82 654 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
38bd362a 655 // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
656 continue;
9197e147 657 }
4dad2828 658 }
2b63df96 659
4dad2828 660 // Make sure we're allowed to see it...
9f2ded76 661 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4dad2828 662 mtrace('user '.$userto->id. ' can not see '.$post->id);
663 continue;
664 }
665
666 // OK so we need to send the email.
667
668 // Does the user want this post in a digest? If so postpone it for now.
669 if ($userto->maildigest > 0) {
670 // This user wants the mails to be in digest form
39790bd8 671 $queue = new stdClass();
a974c799 672 $queue->userid = $userto->id;
4dad2828 673 $queue->discussionid = $discussion->id;
a974c799 674 $queue->postid = $post->id;
90f4745c 675 $queue->timemodified = $post->created;
fc29e51b 676 $DB->insert_record('forum_queue', $queue);
4dad2828 677 continue;
678 }
65b0e537 679
4dad2828 680
681 // Prepare to actually send the post now, and build up the content
682
a974c799 683 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
4dad2828 684
685 $userfrom->customheaders = array ( // Headers to make emails easier to track
686 'Precedence: Bulk',
687 'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
688 'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
1376b0dd 689 'Message-ID: '.forum_get_email_message_id($post->id, $userto->id, $hostname),
4dad2828 690 'X-Course-Id: '.$course->id,
a974c799 691 'X-Course-Name: '.format_string($course->fullname, true)
4dad2828 692 );
a974c799 693
c1b47d94 694 if ($post->parent) { // This post is a reply, so add headers for threading (see MDL-22551)
1376b0dd
AD
695 $userfrom->customheaders[] = 'In-Reply-To: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
696 $userfrom->customheaders[] = 'References: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
c1b47d94 697 }
4dad2828 698
bf0f06b1 699 $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
8ebbb06a 700
93f11331 701 $postsubject = html_to_text("$shortname: ".format_string($post->subject, true));
0faf6791 702 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
703 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
4dad2828 704
705 // Send the post now!
706
707 mtrace('Sending ', '');
5e7f2b0b 708
39790bd8 709 $eventdata = new stdClass();
505ab5aa 710 $eventdata->component = 'mod_forum';
120b3758 711 $eventdata->name = 'posts';
3b120e46 712 $eventdata->userfrom = $userfrom;
713 $eventdata->userto = $userto;
714 $eventdata->subject = $postsubject;
715 $eventdata->fullmessage = $posttext;
716 $eventdata->fullmessageformat = FORMAT_PLAIN;
717 $eventdata->fullmessagehtml = $posthtml;
14a0e7dd 718 $eventdata->notification = 1;
6ee2611c 719
e9c55e39
RT
720 // If forum_replytouser is not set then send mail using the noreplyaddress.
721 if (empty($CFG->forum_replytouser)) {
722 // Clone userfrom as it is referenced by $users.
723 $cloneduserfrom = clone($userfrom);
724 $cloneduserfrom->email = $CFG->noreplyaddress;
725 $eventdata->userfrom = $cloneduserfrom;
726 }
727
6ee2611c
AD
728 $smallmessagestrings = new stdClass();
729 $smallmessagestrings->user = fullname($userfrom);
8ebbb06a 730 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
6ee2611c 731 $smallmessagestrings->message = $post->message;
4ffa1463
AD
732 //make sure strings are in message recipients language
733 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
14a0e7dd
AD
734
735 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
736 $eventdata->contexturlname = $discussion->name;
3b120e46 737
505ab5aa
AD
738 $mailresult = message_send($eventdata);
739 if (!$mailresult){
740 mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
4dad2828 741 " ($userto->email) .. not trying again.");
742 add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
743 substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
744 $errorcount[$post->id]++;
4dad2828 745 } else {
746 $mailcount[$post->id]++;
747
0a4ac01b 748 // Mark post as read if forum_usermarksread is set off
90f4745c 749 if (!$CFG->forum_usermarksread) {
750 $userto->markposts[$post->id] = $post->id;
caadf009 751 }
aaf7a9dc 752 }
4dad2828 753
754 mtrace('post '.$post->id. ': '.$post->subject);
aaf7a9dc 755 }
90f4745c 756
757 // mark processed posts as read
758 forum_tp_mark_posts_read($userto, $userto->markposts);
103f34d8 759 unset($userto);
5fac3a5e 760 }
761 }
762
763 if ($posts) {
764 foreach ($posts as $post) {
16b4e5b6 765 mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
5fac3a5e 766 if ($errorcount[$post->id]) {
8076010d 767 $DB->set_field('forum_posts', 'mailed', FORUM_MAILED_ERROR, array('id' => $post->id));
a974c799 768 }
aaf7a9dc 769 }
770 }
771
8cb121cc 772 // release some memory
773 unset($subscribedusers);
774 unset($mailcount);
775 unset($errorcount);
776
e8b7114d 777 cron_setup_user();
ad9ff3d3 778
9152fc99 779 $sitetimezone = $CFG->timezone;
944a2b28 780
0a4ac01b 781 // Now see if there are any digest mails waiting to be sent, and if we should send them
aaf7a9dc 782
f3c3a4d3 783 mtrace('Starting digest processing...');
784
910b6fa7 785 @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
786
8f0cd6ef 787 if (!isset($CFG->digestmailtimelast)) { // To catch the first time
ca8e8a10 788 set_config('digestmailtimelast', 0);
789 }
790
791 $timenow = time();
944a2b28 792 $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
ca8e8a10 793
f3c3a4d3 794 // Delete any really old ones (normally there shouldn't be any)
795 $weekago = $timenow - (7 * 24 * 3600);
2ac897cd 796 $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
910b6fa7 797 mtrace ('Cleaned old digest records');
9f2ded76 798
ca8e8a10 799 if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
b140ae85 800
b140ae85 801 mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
802
4e445355 803 $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
910b6fa7 804
4e445355 805 if ($digestposts_rs->valid()) {
8ad64455 806
aaf7a9dc 807 // We have work to do
808 $usermailcount = 0;
aaf7a9dc 809
a974c799 810 //caches - reuse the those filled before too
aaf7a9dc 811 $discussionposts = array();
812 $userdiscussions = array();
a974c799 813
4e445355 814 foreach ($digestposts_rs as $digestpost) {
a974c799 815 if (!isset($posts[$digestpost->postid])) {
4e445355 816 if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
a974c799 817 $posts[$digestpost->postid] = $post;
818 } else {
819 continue;
820 }
821 }
822 $discussionid = $digestpost->discussionid;
823 if (!isset($discussions[$discussionid])) {
4e445355 824 if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
a974c799 825 $discussions[$discussionid] = $discussion;
826 } else {
827 continue;
828 }
aaf7a9dc 829 }
a974c799 830 $forumid = $discussions[$discussionid]->forum;
831 if (!isset($forums[$forumid])) {
4e445355 832 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
a974c799 833 $forums[$forumid] = $forum;
834 } else {
835 continue;
836 }
837 }
838
839 $courseid = $forums[$forumid]->course;
840 if (!isset($courses[$courseid])) {
4e445355 841 if ($course = $DB->get_record('course', array('id' => $courseid))) {
a974c799 842 $courses[$courseid] = $course;
843 } else {
844 continue;
845 }
aaf7a9dc 846 }
a974c799 847
848 if (!isset($coursemodules[$forumid])) {
849 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
850 $coursemodules[$forumid] = $cm;
851 } else {
852 continue;
853 }
aaf7a9dc 854 }
855 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
856 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
857 }
4e445355 858 $digestposts_rs->close(); /// Finished iteration, let's close the resultset
aaf7a9dc 859
860 // Data collected, start sending out emails to each user
a974c799 861 foreach ($userdiscussions as $userid => $thesediscussions) {
aaf7a9dc 862
a974c799 863 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
aaf7a9dc 864
e8b7114d 865 cron_setup_user();
90f4745c 866
a974c799 867 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
aaf7a9dc 868
869 // First of all delete all the queue entries for this user
4e445355 870 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
aaf7a9dc 871
103f34d8
PS
872 // Init user caches - we keep the cache for one cycle only,
873 // otherwise it would unnecessarily consume memory.
874 if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
875 $userto = clone($users[$userid]);
876 } else {
877 $userto = $DB->get_record('user', array('id' => $userid));
878 forum_cron_minimise_user_record($userto);
879 }
df1c2c71 880 $userto->viewfullnames = array();
881 $userto->canpost = array();
90f4745c 882 $userto->markposts = array();
df1c2c71 883
103f34d8
PS
884 // Override the language and timezone of the "current" user, so that
885 // mail is customised for the receiver.
886 cron_setup_user($userto);
887
a974c799 888 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
aaf7a9dc 889
39790bd8 890 $headerdata = new stdClass();
a974c799 891 $headerdata->sitename = format_string($site->fullname, true);
839f2456 892 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
aaf7a9dc 893
894 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
895 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
9c674431 896
78c0d909 897 $posthtml = "<head>";
78946b9b
PS
898/* foreach ($CFG->stylesheets as $stylesheet) {
899 //TODO: MDL-21120
78c0d909 900 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
78946b9b 901 }*/
449b1c1a 902 $posthtml .= "</head>\n<body id=\"email\">\n";
a0330747 903 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
aaf7a9dc 904
a974c799 905 foreach ($thesediscussions as $discussionid) {
aaf7a9dc 906
0a4ac01b 907 @set_time_limit(120); // to be reset for each post
a974c799 908
909 $discussion = $discussions[$discussionid];
910 $forum = $forums[$discussion->forum];
911 $course = $courses[$forum->course];
912 $cm = $coursemodules[$forum->id];
65b0e537 913
9152fc99 914 //override language
e8b7114d 915 cron_setup_user($userto, $course);
9152fc99 916
df1c2c71 917 // Fill caches
918 if (!isset($userto->viewfullnames[$forum->id])) {
bf0f06b1 919 $modcontext = context_module::instance($cm->id);
df1c2c71 920 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
921 }
8b79a625 922 if (!isset($userto->canpost[$discussion->id])) {
bf0f06b1 923 $modcontext = context_module::instance($cm->id);
8b79a625 924 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
df1c2c71 925 }
caadf009 926
de85c320 927 $strforums = get_string('forums', 'forum');
928 $canunsubscribe = ! forum_is_forcesubscribed($forum);
8b79a625 929 $canreply = $userto->canpost[$discussion->id];
bf0f06b1 930 $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
de85c320 931
aaf7a9dc 932 $posttext .= "\n \n";
933 $posttext .= '=====================================================================';
934 $posttext .= "\n \n";
8ebbb06a 935 $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
aaf7a9dc 936 if ($discussion->name != $forum->name) {
c78ac798 937 $posttext .= " -> ".format_string($discussion->name,true);
caadf009 938 }
aaf7a9dc 939 $posttext .= "\n";
3f213cd3
DNA
940 $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
941 $posttext .= "\n";
65b0e537 942
aaf7a9dc 943 $posthtml .= "<p><font face=\"sans-serif\">".
8ebbb06a 944 "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
aaf7a9dc 945 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
3849dae8 946 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
aaf7a9dc 947 if ($discussion->name == $forum->name) {
948 $posthtml .= "</font></p>";
caadf009 949 } else {
c78ac798 950 $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
caadf009 951 }
aaf7a9dc 952 $posthtml .= '<p>';
953
e1c6dde1 954 $postsarray = $discussionposts[$discussionid];
955 sort($postsarray);
956
857b798b 957 foreach ($postsarray as $postid) {
a974c799 958 $post = $posts[$postid];
959
960 if (array_key_exists($post->userid, $users)) { // we might know him/her already
961 $userfrom = $users[$post->userid];
103f34d8
PS
962 if (!isset($userfrom->idnumber)) {
963 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
964 forum_cron_minimise_user_record($userfrom);
965 }
966
4e445355 967 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
103f34d8
PS
968 forum_cron_minimise_user_record($userfrom);
969 if ($userscount <= FORUM_CRON_USER_CACHE) {
970 $userscount++;
971 $users[$userfrom->id] = $userfrom;
972 }
973
df1c2c71 974 } else {
a974c799 975 mtrace('Could not find user '.$post->userid);
aaf7a9dc 976 continue;
977 }
978
df1c2c71 979 if (!isset($userfrom->groups[$forum->id])) {
980 if (!isset($userfrom->groups)) {
981 $userfrom->groups = array();
103f34d8
PS
982 if (isset($users[$userfrom->id])) {
983 $users[$userfrom->id]->groups = array();
984 }
df1c2c71 985 }
986 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
103f34d8
PS
987 if (isset($users[$userfrom->id])) {
988 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
989 }
df1c2c71 990 }
991
857b798b 992 $userfrom->customheaders = array ("Precedence: Bulk");
993
994 if ($userto->maildigest == 2) {
3f213cd3
DNA
995 // Subjects and link only
996 $posttext .= "\n";
997 $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
39790bd8 998 $by = new stdClass();
aaf7a9dc 999 $by->name = fullname($userfrom);
1000 $by->date = userdate($post->modified);
17dc3f3c 1001 $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
aaf7a9dc 1002 $posttext .= "\n---------------------------------------------------------------------";
1003
839f2456 1004 $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
0be4d8bf 1005 $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>';
857b798b 1006
1007 } else {
aaf7a9dc 1008 // The full treatment
0faf6791 1009 $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
1010 $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
df1c2c71 1011
0a4ac01b 1012 // Create an array of postid's for this user to mark as read.
90f4745c 1013 if (!$CFG->forum_usermarksread) {
1014 $userto->markposts[$post->id] = $post->id;
f37da850 1015 }
aaf7a9dc 1016 }
1017 }
1018 if ($canunsubscribe) {
ff9b4ea4 1019 $posthtml .= "\n<div class='mdl-right'><font size=\"1\"><a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">".get_string("unsubscribe", "forum")."</a></font></div>";
857b798b 1020 } else {
ff9b4ea4 1021 $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
aaf7a9dc 1022 }
1023 $posthtml .= '<hr size="1" noshade="noshade" /></p>';
caadf009 1024 }
a0330747 1025 $posthtml .= '</body>';
caadf009 1026
6d2e6936 1027 if (empty($userto->mailformat) || $userto->mailformat != 1) {
379a42cb 1028 // This user DOESN'T want to receive HTML
1029 $posthtml = '';
1030 }
5e7f2b0b 1031
b58bd1a2 1032 $attachment = $attachname='';
f2719728
DP
1033 // Directly email forum digests rather than sending them via messaging, use the
1034 // site shortname as 'from name', the noreply address will be used by email_to_user.
a7a1e05f 1035 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname);
b58bd1a2
AD
1036
1037 if (!$mailresult) {
b140ae85 1038 mtrace("ERROR!");
aaf7a9dc 1039 echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
1040 add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
1041 } else {
b140ae85 1042 mtrace("success.");
aaf7a9dc 1043 $usermailcount++;
e3ff14ca 1044
90f4745c 1045 // Mark post as read if forum_usermarksread is set off
1046 forum_tp_mark_posts_read($userto, $userto->markposts);
3d94772d 1047 }
caadf009 1048 }
caadf009 1049 }
226a1d9d 1050 /// We have finishied all digest emails, update $CFG->digestmailtimelast
1051 set_config('digestmailtimelast', $timenow);
caadf009 1052 }
1053
e8b7114d 1054 cron_setup_user();
de85c320 1055
a974c799 1056 if (!empty($usermailcount)) {
b140ae85 1057 mtrace(get_string('digestsentusers', 'forum', $usermailcount));
aaf7a9dc 1058 }
1059
8ad64455 1060 if (!empty($CFG->forum_lastreadclean)) {
f37da850 1061 $timenow = time();
8ad64455 1062 if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
1063 set_config('forum_lastreadclean', $timenow);
8cb121cc 1064 mtrace('Removing old forum read tracking info...');
f37da850 1065 forum_tp_clean_read_records();
1066 }
1067 } else {
8ad64455 1068 set_config('forum_lastreadclean', time());
f37da850 1069 }
1070
1071
caadf009 1072 return true;
1073}
1074
0a4ac01b 1075/**
1670305d 1076 * Builds and returns the body of the email notification in plain text.
1077 *
df1ba0f4 1078 * @global object
1079 * @global object
1080 * @uses CONTEXT_MODULE
1670305d 1081 * @param object $course
df1ba0f4 1082 * @param object $cm
1670305d 1083 * @param object $forum
1084 * @param object $discussion
1085 * @param object $post
1086 * @param object $userfrom
1087 * @param object $userto
1088 * @param boolean $bare
1089 * @return string The email body in plain text format.
0a4ac01b 1090 */
0faf6791 1091function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
15f81ee3 1092 global $CFG, $USER;
2b63df96 1093
bf0f06b1 1094 $modcontext = context_module::instance($cm->id);
18ff4d42 1095
df1c2c71 1096 if (!isset($userto->viewfullnames[$forum->id])) {
df1c2c71 1097 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1098 } else {
1099 $viewfullnames = $userto->viewfullnames[$forum->id];
1100 }
1101
8b79a625 1102 if (!isset($userto->canpost[$discussion->id])) {
8b79a625 1103 $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
df1c2c71 1104 } else {
8b79a625 1105 $canreply = $userto->canpost[$discussion->id];
0fa18d5a 1106 }
2b63df96 1107
aaf7a9dc 1108 $by = New stdClass;
df1c2c71 1109 $by->name = fullname($userfrom, $viewfullnames);
aaf7a9dc 1110 $by->date = userdate($post->modified, "", $userto->timezone);
1111
1112 $strbynameondate = get_string('bynameondate', 'forum', $by);
1113
64762ddc 1114 $strforums = get_string('forums', 'forum');
1115
a9900c73 1116 $canunsubscribe = ! forum_is_forcesubscribed($forum);
aaf7a9dc 1117
1118 $posttext = '';
1119
0fa18d5a 1120 if (!$bare) {
bf0f06b1 1121 $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
8ebbb06a 1122 $posttext = "$shortname -> $strforums -> ".format_string($forum->name,true);
aaf7a9dc 1123
1124 if ($discussion->name != $forum->name) {
c78ac798 1125 $posttext .= " -> ".format_string($discussion->name,true);
aaf7a9dc 1126 }
1127 }
1128
18ff4d42
PS
1129 // add absolute file links
1130 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1131
3f213cd3
DNA
1132 $posttext .= "\n";
1133 $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
aaf7a9dc 1134 $posttext .= "\n---------------------------------------------------------------------\n";
17dc3f3c 1135 $posttext .= format_string($post->subject,true);
0fa18d5a 1136 if ($bare) {
0be4d8bf 1137 $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
aaf7a9dc 1138 }
1139 $posttext .= "\n".$strbynameondate."\n";
1140 $posttext .= "---------------------------------------------------------------------\n";
e2d7687f 1141 $posttext .= format_text_email($post->message, $post->messageformat);
aaf7a9dc 1142 $posttext .= "\n\n";
0faf6791 1143 $posttext .= forum_print_attachments($post, $cm, "text");
1144
aaf7a9dc 1145 if (!$bare && $canreply) {
1146 $posttext .= "---------------------------------------------------------------------\n";
8ebbb06a 1147 $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
aaf7a9dc 1148 $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1149 }
1150 if (!$bare && $canunsubscribe) {
1151 $posttext .= "\n---------------------------------------------------------------------\n";
1152 $posttext .= get_string("unsubscribe", "forum");
1153 $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1154 }
1155
1156 return $posttext;
1157}
1158
0a4ac01b 1159/**
1670305d 1160 * Builds and returns the body of the email notification in html format.
1161 *
df1ba0f4 1162 * @global object
1670305d 1163 * @param object $course
df1ba0f4 1164 * @param object $cm
1670305d 1165 * @param object $forum
1166 * @param object $discussion
1167 * @param object $post
1168 * @param object $userfrom
1169 * @param object $userto
1170 * @return string The email text in HTML format
0a4ac01b 1171 */
0faf6791 1172function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
aaf7a9dc 1173 global $CFG;
1174
a0288610 1175 if ($userto->mailformat != 1) { // Needs to be HTML
1176 return '';
1177 }
aaf7a9dc 1178
8b79a625 1179 if (!isset($userto->canpost[$discussion->id])) {
11ec4ed5 1180 $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
df1c2c71 1181 } else {
8b79a625 1182 $canreply = $userto->canpost[$discussion->id];
9f2ded76 1183 }
1184
a0288610 1185 $strforums = get_string('forums', 'forum');
a9900c73 1186 $canunsubscribe = ! forum_is_forcesubscribed($forum);
bf0f06b1 1187 $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
aaf7a9dc 1188
a0288610 1189 $posthtml = '<head>';
78946b9b
PS
1190/* foreach ($CFG->stylesheets as $stylesheet) {
1191 //TODO: MDL-21120
a0288610 1192 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
78946b9b 1193 }*/
a0288610 1194 $posthtml .= '</head>';
f2379d2d 1195 $posthtml .= "\n<body id=\"email\">\n\n";
aaf7a9dc 1196
f2379d2d 1197 $posthtml .= '<div class="navbar">'.
8ebbb06a 1198 '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
a0288610 1199 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1200 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1201 if ($discussion->name == $forum->name) {
1202 $posthtml .= '</div>';
aaf7a9dc 1203 } else {
a0288610 1204 $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1205 format_string($discussion->name,true).'</a></div>';
aaf7a9dc 1206 }
0faf6791 1207 $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
a0288610 1208
1209 if ($canunsubscribe) {
85db96c5 1210 $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
739da48c 1211 <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1212 <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
a0288610 1213 }
1214
f2379d2d 1215 $posthtml .= '</body>';
1216
a0288610 1217 return $posthtml;
aaf7a9dc 1218}
1670305d 1219
1220
0a4ac01b 1221/**
13bbe067 1222 *
1670305d 1223 * @param object $course
1224 * @param object $user
1225 * @param object $mod TODO this is not used in this function, refactor
1226 * @param object $forum
1227 * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
0a4ac01b 1228 */
caadf009 1229function forum_user_outline($course, $user, $mod, $forum) {
1a96363a
NC
1230 global $CFG;
1231 require_once("$CFG->libdir/gradelib.php");
1232 $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1233 if (empty($grades->items[0]->grades)) {
1234 $grade = false;
1235 } else {
1236 $grade = reset($grades->items[0]->grades);
1237 }
1238
1239 $count = forum_count_user_posts($forum->id, $user->id);
1240
1241 if ($count && $count->postcount > 0) {
39790bd8 1242 $result = new stdClass();
1a96363a
NC
1243 $result->info = get_string("numposts", "forum", $count->postcount);
1244 $result->time = $count->lastpost;
1245 if ($grade) {
1246 $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
90f4745c 1247 }
1a96363a
NC
1248 return $result;
1249 } else if ($grade) {
39790bd8 1250 $result = new stdClass();
1a96363a 1251 $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
4433f871
AD
1252
1253 //datesubmitted == time created. dategraded == time modified or time overridden
1254 //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
94a74f54 1255 //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
4433f871
AD
1256 if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1257 $result->time = $grade->dategraded;
1258 } else {
1259 $result->time = $grade->datesubmitted;
1260 }
1261
1a96363a 1262 return $result;
caadf009 1263 }
1264 return NULL;
1265}
1266
1267
0a4ac01b 1268/**
df1ba0f4 1269 * @global object
1270 * @global object
1271 * @param object $coure
1272 * @param object $user
1273 * @param object $mod
1274 * @param object $forum
0a4ac01b 1275 */
caadf009 1276function forum_user_complete($course, $user, $mod, $forum) {
1a96363a
NC
1277 global $CFG,$USER, $OUTPUT;
1278 require_once("$CFG->libdir/gradelib.php");
1279
1280 $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1281 if (!empty($grades->items[0]->grades)) {
1282 $grade = reset($grades->items[0]->grades);
1283 echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1284 if ($grade->str_feedback) {
1285 echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1286 }
1287 }
caadf009 1288
1f48942e 1289 if ($posts = forum_get_user_posts($forum->id, $user->id)) {
e3ff14ca 1290
65bcf17b 1291 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
12e57b92 1292 print_error('invalidcoursemodule');
65bcf17b 1293 }
90f4745c 1294 $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
65bcf17b 1295
1296 foreach ($posts as $post) {
5e7f2b0b 1297 if (!isset($discussions[$post->discussion])) {
90f4745c 1298 continue;
1299 }
7a8d8f22 1300 $discussion = $discussions[$post->discussion];
5e7f2b0b 1301
63e87951 1302 forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
5e7f2b0b 1303 }
caadf009 1304 } else {
41905731 1305 echo "<p>".get_string("noposts", "forum")."</p>";
caadf009 1306 }
caadf009 1307}
1308
c38965fb 1309
1310
1311
5e7f2b0b 1312
1313
0a4ac01b 1314/**
df1ba0f4 1315 * @global object
1316 * @global object
1317 * @global object
1318 * @param array $courses
1319 * @param array $htmlarray
0a4ac01b 1320 */
185cfb09 1321function forum_print_overview($courses,&$htmlarray) {
261c6ef0 1322 global $USER, $CFG, $DB, $SESSION;
493f0820 1323
185cfb09 1324 if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1325 return array();
1326 }
f8716988 1327
185cfb09 1328 if (!$forums = get_all_instances_in_courses('forum',$courses)) {
f8716988 1329 return;
1330 }
185cfb09 1331
ba4ee840
DM
1332 // Courses to search for new posts
1333 $coursessqls = array();
4e445355 1334 $params = array();
185cfb09 1335 foreach ($courses as $course) {
ba4ee840
DM
1336
1337 // If the user has never entered into the course all posts are pending
1338 if ($course->lastaccess == 0) {
1339 $coursessqls[] = '(f.course = ?)';
1340 $params[] = $course->id;
1341
1342 // Only posts created after the course last access
1343 } else {
1344 $coursessqls[] = '(f.course = ? AND p.created > ?)';
1345 $params[] = $course->id;
1346 $params[] = $course->lastaccess;
1347 }
185cfb09 1348 }
4e445355 1349 $params[] = $USER->id;
ba4ee840
DM
1350 $coursessql = implode(' OR ', $coursessqls);
1351
1352 $sql = "SELECT f.id, COUNT(*) as count "
1353 .'FROM {forum} f '
1354 .'JOIN {forum_discussions} d ON d.forum = f.id '
1355 .'JOIN {forum_posts} p ON p.discussion = d.id '
1356 ."WHERE ($coursessql) "
1357 .'AND p.userid != ? '
1358 .'GROUP BY f.id';
2b63df96 1359
4e445355 1360 if (!$new = $DB->get_records_sql($sql, $params)) {
185cfb09 1361 $new = array(); // avoid warnings
1362 }
2b63df96 1363
185cfb09 1364 // also get all forum tracking stuff ONCE.
1365 $trackingforums = array();
1366 foreach ($forums as $forum) {
1367 if (forum_tp_can_track_forums($forum)) {
1368 $trackingforums[$forum->id] = $forum;
1369 }
1370 }
2b63df96 1371
185cfb09 1372 if (count($trackingforums) > 0) {
1373 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1374 $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
4e445355 1375 ' FROM {forum_posts} p '.
1376 ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1377 ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1378 $params = array($USER->id);
1379
d3553951 1380 foreach ($trackingforums as $track) {
4e445355 1381 $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1382 $params[] = $track->id;
261c6ef0 1383 if (isset($SESSION->currentgroup[$track->course])) {
1384 $groupid = $SESSION->currentgroup[$track->course];
1385 } else {
0209f964
PS
1386 // get first groupid
1387 $groupids = groups_get_all_groups($track->course, $USER->id);
1388 if ($groupids) {
1389 reset($groupids);
1390 $groupid = key($groupids);
261c6ef0 1391 $SESSION->currentgroup[$track->course] = $groupid;
1392 } else {
1393 $groupid = 0;
1394 }
0209f964 1395 unset($groupids);
261c6ef0 1396 }
1397 $params[] = $groupid;
185cfb09 1398 }
1399 $sql = substr($sql,0,-3); // take off the last OR
4e445355 1400 $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1401 $params[] = $cutoffdate;
185cfb09 1402
4e445355 1403 if (!$unread = $DB->get_records_sql($sql, $params)) {
185cfb09 1404 $unread = array();
1405 }
1406 } else {
1407 $unread = array();
95d71ad3 1408 }
185cfb09 1409
9cba7a8c 1410 if (empty($unread) and empty($new)) {
1411 return;
1412 }
1413
1414 $strforum = get_string('modulename','forum');
9cba7a8c 1415
f8716988 1416 foreach ($forums as $forum) {
185cfb09 1417 $str = '';
f8716988 1418 $count = 0;
185cfb09 1419 $thisunread = 0;
f8716988 1420 $showunread = false;
1421 // either we have something from logs, or trackposts, or nothing.
1422 if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1423 $count = $new[$forum->id]->count;
90558ec4 1424 }
185cfb09 1425 if (array_key_exists($forum->id,$unread)) {
1426 $thisunread = $unread[$forum->id]->count;
f8716988 1427 $showunread = true;
0d6b9d4f 1428 }
185cfb09 1429 if ($count > 0 || $thisunread > 0) {
e23800b7 1430 $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1431 $forum->name.'</a></div>';
6d641063 1432 $str .= '<div class="info"><span class="postsincelogin">';
54294601 1433 $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
f8716988 1434 if (!empty($showunread)) {
54294601 1435 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
f8716988 1436 }
e23800b7 1437 $str .= '</div></div>';
f8716988 1438 }
2b63df96 1439 if (!empty($str)) {
185cfb09 1440 if (!array_key_exists($forum->course,$htmlarray)) {
1441 $htmlarray[$forum->course] = array();
1442 }
1443 if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1444 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1445 }
1446 $htmlarray[$forum->course]['forum'] .= $str;
1447 }
2b63df96 1448 }
0d6b9d4f 1449}
1450
0a4ac01b 1451/**
1452 * Given a course and a date, prints a summary of all the new
1453 * messages posted in the course since that date
df1ba0f4 1454 *
1455 * @global object
1456 * @global object
1457 * @global object
1458 * @uses CONTEXT_MODULE
1459 * @uses VISIBLEGROUPS
90f4745c 1460 * @param object $course
1461 * @param bool $viewfullnames capability
1462 * @param int $timestart
1463 * @return bool success
0a4ac01b 1464 */
dd97c328 1465function forum_print_recent_activity($course, $viewfullnames, $timestart) {
cb860491 1466 global $CFG, $USER, $DB, $OUTPUT;
caadf009 1467
dd97c328 1468 // do not use log table if possible, it may be huge and is expensive to join with other tables
caadf009 1469
4e445355 1470 if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1471 d.timestart, d.timeend, d.userid AS duserid,
1472 u.firstname, u.lastname, u.email, u.picture
1473 FROM {forum_posts} p
1474 JOIN {forum_discussions} d ON d.id = p.discussion
1475 JOIN {forum} f ON f.id = d.forum
1476 JOIN {user} u ON u.id = p.userid
1477 WHERE p.created > ? AND f.course = ?
1478 ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
dd97c328 1479 return false;
1b5910c4 1480 }
1481
f20edd52 1482 $modinfo = get_fast_modinfo($course);
dcde9f02 1483
dd97c328 1484 $groupmodes = array();
1485 $cms = array();
d05956ac 1486
dd97c328 1487 $strftimerecent = get_string('strftimerecent');
d05956ac 1488
dd97c328 1489 $printposts = array();
1490 foreach ($posts as $post) {
1491 if (!isset($modinfo->instances['forum'][$post->forum])) {
1492 // not visible
1493 continue;
1494 }
1495 $cm = $modinfo->instances['forum'][$post->forum];
1496 if (!$cm->uservisible) {
1497 continue;
1498 }
bf0f06b1 1499 $context = context_module::instance($cm->id);
6b7de0bb 1500
1501 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1502 continue;
1503 }
583b57b4 1504
dd97c328 1505 if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1506 and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
6b7de0bb 1507 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
dd97c328 1508 continue;
ac1d9a22 1509 }
dd97c328 1510 }
583b57b4 1511
dd97c328 1512 $groupmode = groups_get_activity_groupmode($cm, $course);
b91d6dcd 1513
dd97c328 1514 if ($groupmode) {
6b7de0bb 1515 if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
dd97c328 1516 // oki (Open discussions have groupid -1)
1517 } else {
1518 // separate mode
1519 if (isguestuser()) {
1520 // shortcut
1521 continue;
1522 }
2b63df96 1523
dd97c328 1524 if (is_null($modinfo->groups)) {
1525 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1b5910c4 1526 }
ac1d9a22 1527
2511a97f 1528 if (!in_array($post->groupid, $modinfo->get_groups($cm->groupingid))) {
dd97c328 1529 continue;
1530 }
ac1d9a22 1531 }
dd97c328 1532 }
8f7dc7f1 1533
dd97c328 1534 $printposts[] = $post;
1535 }
1536 unset($posts);
8f7dc7f1 1537
dd97c328 1538 if (!$printposts) {
1539 return false;
1540 }
1541
cb860491 1542 echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
dd97c328 1543 echo "\n<ul class='unlist'>\n";
1544
1545 foreach ($printposts as $post) {
1546 $subjectclass = empty($post->parent) ? ' bold' : '';
1547
1548 echo '<li><div class="head">'.
1549 '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1550 '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1551 '</div>';
1552 echo '<div class="info'.$subjectclass.'">';
1553 if (empty($post->parent)) {
1554 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1555 } else {
1556 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
caadf009 1557 }
dd97c328 1558 $post->subject = break_up_long_words(format_string($post->subject, true));
1559 echo $post->subject;
1560 echo "</a>\"</div></li>\n";
caadf009 1561 }
dd97c328 1562
1306c5ea 1563 echo "</ul>\n";
dd97c328 1564
1565 return true;
caadf009 1566}
1567
353228d8 1568/**
1569 * Return grade for given user or all users.
1570 *
df1ba0f4 1571 * @global object
1572 * @global object
1573 * @param object $forum
353228d8 1574 * @param int $userid optional user id, 0 means all users
1575 * @return array array of grades, false if none
1576 */
2b04c41c 1577function forum_get_user_grades($forum, $userid = 0) {
63e87951 1578 global $CFG;
df997f84 1579
63e87951 1580 require_once($CFG->dirroot.'/rating/lib.php');
df997f84 1581
2b04c41c
SH
1582 $ratingoptions = new stdClass;
1583 $ratingoptions->component = 'mod_forum';
1584 $ratingoptions->ratingarea = 'post';
353228d8 1585
63e87951
AD
1586 //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1587 $ratingoptions->modulename = 'forum';
1588 $ratingoptions->moduleid = $forum->id;
63e87951
AD
1589 $ratingoptions->userid = $userid;
1590 $ratingoptions->aggregationmethod = $forum->assessed;
1591 $ratingoptions->scaleid = $forum->scale;
1592 $ratingoptions->itemtable = 'forum_posts';
1593 $ratingoptions->itemtableusercolumn = 'userid';
353228d8 1594
2b04c41c 1595 $rm = new rating_manager();
63e87951 1596 return $rm->get_user_grades($ratingoptions);
353228d8 1597}
caadf009 1598
0a4ac01b 1599/**
775f811a 1600 * Update activity grades
353228d8 1601 *
a153c9f2 1602 * @category grade
775f811a 1603 * @param object $forum
1604 * @param int $userid specific user only, 0 means all
6b7de0bb 1605 * @param boolean $nullifnone return null if grade does not exist
90f4745c 1606 * @return void
0a4ac01b 1607 */
775f811a 1608function forum_update_grades($forum, $userid=0, $nullifnone=true) {
4e445355 1609 global $CFG, $DB;
775f811a 1610 require_once($CFG->libdir.'/gradelib.php');
caadf009 1611
775f811a 1612 if (!$forum->assessed) {
1613 forum_grade_item_update($forum);
02ebf404 1614
775f811a 1615 } else if ($grades = forum_get_user_grades($forum, $userid)) {
1616 forum_grade_item_update($forum, $grades);
eafb9d9e 1617
775f811a 1618 } else if ($userid and $nullifnone) {
39790bd8 1619 $grade = new stdClass();
775f811a 1620 $grade->userid = $userid;
1621 $grade->rawgrade = NULL;
1622 forum_grade_item_update($forum, $grade);
02ebf404 1623
353228d8 1624 } else {
775f811a 1625 forum_grade_item_update($forum);
1626 }
1627}
1628
1629/**
1630 * Update all grades in gradebook.
df1ba0f4 1631 * @global object
775f811a 1632 */
1633function forum_upgrade_grades() {
1634 global $DB;
1635
1636 $sql = "SELECT COUNT('x')
1637 FROM {forum} f, {course_modules} cm, {modules} m
1638 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1639 $count = $DB->count_records_sql($sql);
1640
1641 $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1642 FROM {forum} f, {course_modules} cm, {modules} m
1643 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
56d1c48f
EL
1644 $rs = $DB->get_recordset_sql($sql);
1645 if ($rs->valid()) {
775f811a 1646 $pbar = new progress_bar('forumupgradegrades', 500, true);
1647 $i=0;
1648 foreach ($rs as $forum) {
1649 $i++;
1650 upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1651 forum_update_grades($forum, 0, false);
1652 $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
caadf009 1653 }
353228d8 1654 }
56d1c48f 1655 $rs->close();
353228d8 1656}
1657
1658/**
612607bd 1659 * Create/update grade item for given forum
353228d8 1660 *
a153c9f2 1661 * @category grade
df1ba0f4 1662 * @uses GRADE_TYPE_NONE
1663 * @uses GRADE_TYPE_VALUE
1664 * @uses GRADE_TYPE_SCALE
a153c9f2
AD
1665 * @param stdClass $forum Forum object with extra cmidnumber
1666 * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
612607bd 1667 * @return int 0 if ok
353228d8 1668 */
0b5a80a1 1669function forum_grade_item_update($forum, $grades=NULL) {
612607bd 1670 global $CFG;
1671 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1672 require_once($CFG->libdir.'/gradelib.php');
353228d8 1673 }
1674
612607bd 1675 $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
353228d8 1676
5980d52f 1677 if (!$forum->assessed or $forum->scale == 0) {
612607bd 1678 $params['gradetype'] = GRADE_TYPE_NONE;
353228d8 1679
1680 } else if ($forum->scale > 0) {
1681 $params['gradetype'] = GRADE_TYPE_VALUE;
1682 $params['grademax'] = $forum->scale;
1683 $params['grademin'] = 0;
1684
1685 } else if ($forum->scale < 0) {
1686 $params['gradetype'] = GRADE_TYPE_SCALE;
1687 $params['scaleid'] = -$forum->scale;
1688 }
1689
0b5a80a1 1690 if ($grades === 'reset') {
1691 $params['reset'] = true;
1692 $grades = NULL;
1693 }
1694
1695 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
353228d8 1696}
1697
1698/**
1699 * Delete grade item for given forum
1700 *
a153c9f2
AD
1701 * @category grade
1702 * @param stdClass $forum Forum object
1703 * @return grade_item
353228d8 1704 */
1705function forum_grade_item_delete($forum) {
612607bd 1706 global $CFG;
1707 require_once($CFG->libdir.'/gradelib.php');
1708
b67ec72f 1709 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
caadf009 1710}
1711
353228d8 1712
0a4ac01b 1713/**
90f4745c 1714 * This function returns if a scale is being used by one forum
df1ba0f4 1715 *
1716 * @global object
90f4745c 1717 * @param int $forumid
1718 * @param int $scaleid negative number
1719 * @return bool
0a4ac01b 1720 */
0f1a97c2 1721function forum_scale_used ($forumid,$scaleid) {
4e445355 1722 global $DB;
0f1a97c2 1723 $return = false;
65b0e537 1724
4e445355 1725 $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
65b0e537 1726
fa22fd5f 1727 if (!empty($rec) && !empty($scaleid)) {
0f1a97c2 1728 $return = true;
1729 }
65b0e537 1730
0f1a97c2 1731 return $return;
1732}
1733
85c9ebb9 1734/**
1735 * Checks if scale is being used by any instance of forum
1736 *
1737 * This is used to find out if scale used anywhere
df1ba0f4 1738 *
1739 * @global object
85c9ebb9 1740 * @param $scaleid int
1741 * @return boolean True if the scale is used by any forum
1742 */
1743function forum_scale_used_anywhere($scaleid) {
4e445355 1744 global $DB;
1745 if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
85c9ebb9 1746 return true;
1747 } else {
1748 return false;
1749 }
1750}
1751
0a4ac01b 1752// SQL FUNCTIONS ///////////////////////////////////////////////////////////
9fa49e22 1753
0a4ac01b 1754/**
1755 * Gets a post with all info ready for forum_print_post
1756 * Most of these joins are just to get the forum id
df1ba0f4 1757 *
1758 * @global object
1759 * @global object
90f4745c 1760 * @param int $postid
1761 * @return mixed array of posts or false
0a4ac01b 1762 */
1f48942e 1763function forum_get_post_full($postid) {
4e445355 1764 global $CFG, $DB;
1f48942e 1765
a327f25e
AG
1766 $allnames = get_all_user_name_fields(true, 'u');
1767 return $DB->get_record_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
4e445355 1768 FROM {forum_posts} p
1769 JOIN {forum_discussions} d ON p.discussion = d.id
1770 LEFT JOIN {user} u ON p.userid = u.id
1771 WHERE p.id = ?", array($postid));
1f48942e 1772}
1773
0a4ac01b 1774/**
1775 * Gets posts with all info ready for forum_print_post
1776 * We pass forumid in because we always know it so no need to make a
1777 * complicated join to find it out.
df1ba0f4 1778 *
1779 * @global object
1780 * @global object
90f4745c 1781 * @return mixed array of posts or false
0a4ac01b 1782 */
77efef3e 1783function forum_get_discussion_posts($discussion, $sort, $forumid) {
4e445355 1784 global $CFG, $DB;
1f48942e 1785
a327f25e
AG
1786 $allnames = get_all_user_name_fields(true, 'u');
1787 return $DB->get_records_sql("SELECT p.*, $forumid AS forum, $allnames, u.email, u.picture, u.imagealt
4e445355 1788 FROM {forum_posts} p
1789 LEFT JOIN {user} u ON p.userid = u.id
1790 WHERE p.discussion = ?
1791 AND p.parent > 0 $sort", array($discussion));
1f48942e 1792}
1793
65bcf17b 1794/**
1795 * Gets all posts in discussion including top parent.
df1ba0f4 1796 *
1797 * @global object
1798 * @global object
1799 * @global object
90f4745c 1800 * @param int $discussionid
1801 * @param string $sort
1802 * @param bool $tracking does user track the forum?
1803 * @return array of posts
65bcf17b 1804 */
90f4745c 1805function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
4e445355 1806 global $CFG, $DB, $USER;
65bcf17b 1807
3c2bf848 1808 $tr_sel = "";
1809 $tr_join = "";
4e445355 1810 $params = array();
3c2bf848 1811
90f4745c 1812 if ($tracking) {
1813 $now = time();
1814 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1815 $tr_sel = ", fr.id AS postread";
4e445355 1816 $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1817 $params[] = $USER->id;
90f4745c 1818 }
1819
a327f25e 1820 $allnames = get_all_user_name_fields(true, 'u');
4e445355 1821 $params[] = $discussionid;
a327f25e 1822 if (!$posts = $DB->get_records_sql("SELECT p.*, $allnames, u.email, u.picture, u.imagealt $tr_sel
4e445355 1823 FROM {forum_posts} p
1824 LEFT JOIN {user} u ON p.userid = u.id
90f4745c 1825 $tr_join
4e445355 1826 WHERE p.discussion = ?
1827 ORDER BY $sort", $params)) {
65bcf17b 1828 return array();
1829 }
1830
1831 foreach ($posts as $pid=>$p) {
90f4745c 1832 if ($tracking) {
1833 if (forum_tp_is_post_old($p)) {
6b7de0bb 1834 $posts[$pid]->postread = true;
90f4745c 1835 }
1836 }
65bcf17b 1837 if (!$p->parent) {
1838 continue;
1839 }
1840 if (!isset($posts[$p->parent])) {
1841 continue; // parent does not exist??
1842 }
1843 if (!isset($posts[$p->parent]->children)) {
1844 $posts[$p->parent]->children = array();
1845 }
1846 $posts[$p->parent]->children[$pid] =& $posts[$pid];
1847 }
1848
1849 return $posts;
1850}
1851
0a4ac01b 1852/**
1853 * Gets posts with all info ready for forum_print_post
1854 * We pass forumid in because we always know it so no need to make a
1855 * complicated join to find it out.
df1ba0f4 1856 *
1857 * @global object
1858 * @global object
1859 * @param int $parent
1860 * @param int $forumid
1861 * @return array
0a4ac01b 1862 */
77efef3e 1863function forum_get_child_posts($parent, $forumid) {
4e445355 1864 global $CFG, $DB;
1f48942e 1865
a327f25e
AG
1866 $allnames = get_all_user_name_fields(true, 'u');
1867 return $DB->get_records_sql("SELECT p.*, $forumid AS forum, $allnames, u.email, u.picture, u.imagealt
4e445355 1868 FROM {forum_posts} p
1869 LEFT JOIN {user} u ON p.userid = u.id
1870 WHERE p.parent = ?
1871 ORDER BY p.created ASC", array($parent));
1f48942e 1872}
1873
42fb3c85 1874/**
1875 * An array of forum objects that the user is allowed to read/search through.
df1ba0f4 1876 *
1877 * @global object
1878 * @global object
1879 * @global object
1880 * @param int $userid
1881 * @param int $courseid if 0, we look for forums throughout the whole site.
42fb3c85 1882 * @return array of forum objects, or false if no matches
1883 * Forum objects have the following attributes:
1884 * id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1885 * viewhiddentimedposts
1886 */
1887function forum_get_readable_forums($userid, $courseid=0) {
2b63df96 1888
4e445355 1889 global $CFG, $DB, $USER;
6b7de0bb 1890 require_once($CFG->dirroot.'/course/lib.php');
2b63df96 1891
4e445355 1892 if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
12e57b92 1893 print_error('notinstalled', 'forum');
42fb3c85 1894 }
2b63df96 1895
42fb3c85 1896 if ($courseid) {
4e445355 1897 $courses = $DB->get_records('course', array('id' => $courseid));
42fb3c85 1898 } else {
65bcf17b 1899 // If no course is specified, then the user can see SITE + his courses.
4e445355 1900 $courses1 = $DB->get_records('course', array('id' => SITEID));
b1d5d015 1901 $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
6155150c 1902 $courses = array_merge($courses1, $courses2);
42fb3c85 1903 }
1904 if (!$courses) {
6b7de0bb 1905 return array();
42fb3c85 1906 }
1907
1908 $readableforums = array();
2b63df96 1909
6527b5c2 1910 foreach ($courses as $course) {
1911
f20edd52 1912 $modinfo = get_fast_modinfo($course);
6b7de0bb 1913 if (is_null($modinfo->groups)) {
1914 $modinfo->groups = groups_get_user_groups($course->id, $userid);
42fb3c85 1915 }
2b63df96 1916
6b7de0bb 1917 if (empty($modinfo->instances['forum'])) {
1918 // hmm, no forums?
1919 continue;
1920 }
2b63df96 1921
4e445355 1922 $courseforums = $DB->get_records('forum', array('course' => $course->id));
2b63df96 1923
6b7de0bb 1924 foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1925 if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1926 continue;
1927 }
bf0f06b1 1928 $context = context_module::instance($cm->id);
6b7de0bb 1929 $forum = $courseforums[$forumid];
2e945910
AB
1930 $forum->context = $context;
1931 $forum->cm = $cm;
d50704bf 1932
6b7de0bb 1933 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1934 continue;
1935 }
6527b5c2 1936
6b7de0bb 1937 /// group access
1938 if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1939 if (is_null($modinfo->groups)) {
1940 $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1941 }
98da6021 1942 if (isset($modinfo->groups[$cm->groupingid])) {
6b7de0bb 1943 $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1944 $forum->onlygroups[] = -1;
1945 } else {
1946 $forum->onlygroups = array(-1);
1947 }
1948 }
2b63df96 1949
6b7de0bb 1950 /// hidden timed discussions
1951 $forum->viewhiddentimedposts = true;
1952 if (!empty($CFG->forum_enabletimedposts)) {
1953 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1954 $forum->viewhiddentimedposts = false;
1955 }
1956 }
d50704bf 1957
6b7de0bb 1958 /// qanda access
1959 if ($forum->type == 'qanda'
1960 && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2b63df96 1961
6b7de0bb 1962 // We need to check whether the user has posted in the qanda forum.
1963 $forum->onlydiscussions = array(); // Holds discussion ids for the discussions
1964 // the user is allowed to see in this forum.
1965 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1966 foreach ($discussionspostedin as $d) {
1967 $forum->onlydiscussions[] = $d->id;
d50704bf 1968 }
42fb3c85 1969 }
1970 }
6b7de0bb 1971
1972 $readableforums[$forum->id] = $forum;
42fb3c85 1973 }
6b7de0bb 1974
1975 unset($modinfo);
1976
42fb3c85 1977 } // End foreach $courses
2b63df96 1978
42fb3c85 1979 return $readableforums;
1980}
1981
bbbf2d40 1982/**
1983 * Returns a list of posts found using an array of search terms.
df1ba0f4 1984 *
1985 * @global object
1986 * @global object
1987 * @global object
1988 * @param array $searchterms array of search terms, e.g. word +word -word
1989 * @param int $courseid if 0, we search through the whole site
1990 * @param int $limitfrom
1991 * @param int $limitnum
1992 * @param int &$totalcount
1993 * @param string $extrasql
1994 * @return array|bool Array of posts found or false
42fb3c85 1995 */
2b63df96 1996function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
42fb3c85 1997 &$totalcount, $extrasql='') {
4e445355 1998 global $CFG, $DB, $USER;
42fb3c85 1999 require_once($CFG->libdir.'/searchlib.php');
2000
2001 $forums = forum_get_readable_forums($USER->id, $courseid);
2b63df96 2002
67875aa1 2003 if (count($forums) == 0) {
6b7de0bb 2004 $totalcount = 0;
67875aa1 2005 return false;
2006 }
42fb3c85 2007
6b7de0bb 2008 $now = round(time(), -2); // db friendly
2009
2010 $fullaccess = array();
2011 $where = array();
4e445355 2012 $params = array();
6b7de0bb 2013
2014 foreach ($forums as $forumid => $forum) {
2015 $select = array();
2016
2017 if (!$forum->viewhiddentimedposts) {
b1d5d015
PS
2018 $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
2019 $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
42fb3c85 2020 }
6b7de0bb 2021
2e945910
AB
2022 $cm = $forum->cm;
2023 $context = $forum->context;
ad9c22aa 2024
2025 if ($forum->type == 'qanda'
2026 && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
6b7de0bb 2027 if (!empty($forum->onlydiscussions)) {
cf717dc2 2028 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
4e445355 2029 $params = array_merge($params, $discussionid_params);
2030 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
d50704bf 2031 } else {
6b7de0bb 2032 $select[] = "p.parent = 0";
d50704bf 2033 }
2034 }
6b7de0bb 2035
2036 if (!empty($forum->onlygroups)) {
cf717dc2 2037 list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
4e445355 2038 $params = array_merge($params, $groupid_params);
2039 $select[] = "d.groupid $groupid_sql";
42fb3c85 2040 }
6b7de0bb 2041
2042 if ($select) {
2043 $selects = implode(" AND ", $select);
b1d5d015
PS
2044 $where[] = "(d.forum = :forum{$forumid} AND $selects)";
2045 $params['forum'.$forumid] = $forumid;
6b7de0bb 2046 } else {
2047 $fullaccess[] = $forumid;
2048 }
2049 }
2050
2051 if ($fullaccess) {
cf717dc2 2052 list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
4e445355 2053 $params = array_merge($params, $fullid_params);
2054 $where[] = "(d.forum $fullid_sql)";
42fb3c85 2055 }
42fb3c85 2056
6b7de0bb 2057 $selectdiscussion = "(".implode(" OR ", $where).")";
42fb3c85 2058
42fb3c85 2059 $messagesearch = '';
2060 $searchstring = '';
2b63df96 2061
42fb3c85 2062 // Need to concat these back together for parser to work.
2063 foreach($searchterms as $searchterm){
2064 if ($searchstring != '') {
2065 $searchstring .= ' ';
2066 }
2067 $searchstring .= $searchterm;
2068 }
2069
2070 // We need to allow quoted strings for the search. The quotes *should* be stripped
2071 // by the parser, but this should be examined carefully for security implications.
2072 $searchstring = str_replace("\\\"","\"",$searchstring);
2073 $parser = new search_parser();
2074 $lexer = new search_lexer($parser);
2075
2076 if ($lexer->parse($searchstring)) {
2077 $parsearray = $parser->get_parsed_array();
0a4ac01b 2078 // Experimental feature under 1.8! MDL-8830
2079 // Use alternative text searches if defined
2080 // This feature only works under mysql until properly implemented for other DBs
2081 // Requires manual creation of text index for forum_posts before enabling it:
2082 // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2083 // Experimental feature under 1.8! MDL-8830
532daab4 2084 if (!empty($CFG->forum_usetextsearches)) {
004efe66 2085 list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
532daab4 2086 'p.userid', 'u.id', 'u.firstname',
2087 'u.lastname', 'p.modified', 'd.forum');
2088 } else {
004efe66 2089 list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
532daab4 2090 'p.userid', 'u.id', 'u.firstname',
2091 'u.lastname', 'p.modified', 'd.forum');
2092 }
004efe66 2093 $params = array_merge($params, $msparams);
42fb3c85 2094 }
2095
4e445355 2096 $fromsql = "{forum_posts} p,
2097 {forum_discussions} d,
2098 {user} u";
42fb3c85 2099
2100 $selectsql = " $messagesearch
2101 AND p.discussion = d.id
2102 AND p.userid = u.id
2103 AND $selectdiscussion
2104 $extrasql";
2105
2106 $countsql = "SELECT COUNT(*)
2107 FROM $fromsql
2108 WHERE $selectsql";
2109
a327f25e 2110 $allnames = get_all_user_name_fields(true, 'u');
7f094149 2111 $searchsql = "SELECT p.*,
42fb3c85 2112 d.forum,
a327f25e 2113 $allnames,
42fb3c85 2114 u.email,
8ba59d07 2115 u.picture,
ce7382c9 2116 u.imagealt
42fb3c85 2117 FROM $fromsql
2118 WHERE $selectsql
2119 ORDER BY p.modified DESC";
2120
4e445355 2121 $totalcount = $DB->count_records_sql($countsql, $params);
d50704bf 2122
4e445355 2123 return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
42fb3c85 2124}
2125
0a4ac01b 2126/**
2127 * Returns a list of ratings for a particular post - sorted.
df1ba0f4 2128 *
2b04c41c
SH
2129 * TODO: Check if this function is actually used anywhere.
2130 * Up until the fix for MDL-27471 this function wasn't even returning.
2131 *
2132 * @param stdClass $context
90f4745c 2133 * @param int $postid
2134 * @param string $sort
df1ba0f4 2135 * @return array Array of ratings or false
0a4ac01b 2136 */
2b04c41c
SH
2137function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2138 $options = new stdClass;
2139 $options->context = $context;
2140 $options->component = 'mod_forum';
2141 $options->ratingarea = 'post';
63e87951
AD
2142 $options->itemid = $postid;
2143 $options->sort = "ORDER BY $sort";
2144
0bb69ab3 2145 $rm = new rating_manager();
2b04c41c 2146 return $rm->get_all_ratings_for_item($options);
9fa49e22 2147}
2148
0a4ac01b 2149/**
2150 * Returns a list of all new posts that have not been mailed yet
df1ba0f4 2151 *
df1ba0f4 2152 * @param int $starttime posts created after this time
2153 * @param int $endtime posts created before this
2154 * @param int $now used for timed discussions only
2155 * @return array
0a4ac01b 2156 */
90f4745c 2157function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
4e445355 2158 global $CFG, $DB;
90f4745c 2159
8076010d
MN
2160 $params = array();
2161 $params['mailed'] = FORUM_MAILED_PENDING;
2162 $params['ptimestart'] = $starttime;
2163 $params['ptimeend'] = $endtime;
2164 $params['mailnow'] = 1;
2165
90f4745c 2166 if (!empty($CFG->forum_enabletimedposts)) {
2167 if (empty($now)) {
2168 $now = time();
2169 }
8076010d
MN
2170 $timedsql = "AND (d.timestart < :dtimestart AND (d.timeend = 0 OR d.timeend > :dtimeend))";
2171 $params['dtimestart'] = $now;
2172 $params['dtimeend'] = $now;
90f4745c 2173 } else {
2174 $timedsql = "";
2175 }
2176
4e445355 2177 return $DB->get_records_sql("SELECT p.*, d.course, d.forum
8076010d
MN
2178 FROM {forum_posts} p
2179 JOIN {forum_discussions} d ON d.id = p.discussion
2180 WHERE p.mailed = :mailed
2181 AND p.created >= :ptimestart
2182 AND (p.created < :ptimeend OR p.mailnow = :mailnow)
2183 $timedsql
2184 ORDER BY p.modified ASC", $params);
1f48942e 2185}
2186
0a4ac01b 2187/**
2188 * Marks posts before a certain time as being mailed already
df1ba0f4 2189 *
2190 * @global object
2191 * @global object
2192 * @param int $endtime
2193 * @param int $now Defaults to time()
2194 * @return bool
0a4ac01b 2195 */
90f4745c 2196function forum_mark_old_posts_as_mailed($endtime, $now=null) {
4e445355 2197 global $CFG, $DB;
8076010d 2198
90f4745c 2199 if (empty($now)) {
2200 $now = time();
2201 }
2202
8076010d
MN
2203 $params = array();
2204 $params['mailedsuccess'] = FORUM_MAILED_SUCCESS;
2205 $params['now'] = $now;
2206 $params['endtime'] = $endtime;
2207 $params['mailnow'] = 1;
2208 $params['mailedpending'] = FORUM_MAILED_PENDING;
2209
90f4745c 2210 if (empty($CFG->forum_enabletimedposts)) {
4e445355 2211 return $DB->execute("UPDATE {forum_posts}
8076010d
MN
2212 SET mailed = :mailedsuccess
2213 WHERE (created < :endtime OR mailnow = :mailnow)
2214 AND mailed = :mailedpending", $params);
0f620d4b 2215 } else {
4e445355 2216 return $DB->execute("UPDATE {forum_posts}
8076010d 2217 SET mailed = :mailedsuccess
90f4745c 2218 WHERE discussion NOT IN (SELECT d.id
8076010d
MN
2219 FROM {forum_discussions} d
2220 WHERE d.timestart > :now)
2221 AND (created < :endtime OR mailnow = :mailnow)
2222 AND mailed = :mailedpending", $params);
0f620d4b 2223 }
3ecca1ee 2224}
2225
0a4ac01b 2226/**
2227 * Get all the posts for a user in a forum suitable for forum_print_post
df1ba0f4 2228 *
2229 * @global object
2230 * @global object
2231 * @uses CONTEXT_MODULE
2232 * @return array
0a4ac01b 2233 */
1f48942e 2234function forum_get_user_posts($forumid, $userid) {
4e445355 2235 global $CFG, $DB;
1f48942e 2236
90f4745c 2237 $timedsql = "";
4e445355 2238 $params = array($forumid, $userid);
2239
90f4745c 2240 if (!empty($CFG->forum_enabletimedposts)) {
2241 $cm = get_coursemodule_from_instance('forum', $forumid);
bf0f06b1 2242 if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
90f4745c 2243 $now = time();
4e445355 2244 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2245 $params[] = $now;
2246 $params[] = $now;
6b7de0bb 2247 }
90f4745c 2248 }
2249
a327f25e
AG
2250 $allnames = get_all_user_name_fields(true, 'u');
2251 return $DB->get_records_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
4e445355 2252 FROM {forum} f
2253 JOIN {forum_discussions} d ON d.forum = f.id
2254 JOIN {forum_posts} p ON p.discussion = d.id
2255 JOIN {user} u ON u.id = p.userid
2256 WHERE f.id = ?
2257 AND p.userid = ?
90f4745c 2258 $timedsql
4e445355 2259 ORDER BY p.modified ASC", $params);
1f48942e 2260}
2261
90f4745c 2262/**
2263 * Get all the discussions user participated in
df1ba0f4 2264 *
2265 * @global object
2266 * @global object
2267 * @uses CONTEXT_MODULE
90f4745c 2268 * @param int $forumid
2269 * @param int $userid
df1ba0f4 2270 * @return array Array or false
90f4745c 2271 */
2272function forum_get_user_involved_discussions($forumid, $userid) {
4e445355 2273 global $CFG, $DB;
90f4745c 2274
2275 $timedsql = "";
4e445355 2276 $params = array($forumid, $userid);
90f4745c 2277 if (!empty($CFG->forum_enabletimedposts)) {
2278 $cm = get_coursemodule_from_instance('forum', $forumid);
bf0f06b1 2279 if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
90f4745c 2280 $now = time();
4e445355 2281 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2282 $params[] = $now;
2283 $params[] = $now;
6b7de0bb 2284 }
90f4745c 2285 }
2286
4e445355 2287 return $DB->get_records_sql("SELECT DISTINCT d.*
2288 FROM {forum} f
2289 JOIN {forum_discussions} d ON d.forum = f.id
2290 JOIN {forum_posts} p ON p.discussion = d.id
2291 WHERE f.id = ?
2292 AND p.userid = ?
2293 $timedsql", $params);
90f4745c 2294}
2295
2296/**
2297 * Get all the posts for a user in a forum suitable for forum_print_post
df1ba0f4 2298 *
2299 * @global object
2300 * @global object
90f4745c 2301 * @param int $forumid
2302 * @param int $userid
2303 * @return array of counts or false
2304 */
2305function forum_count_user_posts($forumid, $userid) {
4e445355 2306 global $CFG, $DB;
90f4745c 2307
2308 $timedsql = "";
4e445355 2309 $params = array($forumid, $userid);
90f4745c 2310 if (!empty($CFG->forum_enabletimedposts)) {
2311 $cm = get_coursemodule_from_instance('forum', $forumid);
bf0f06b1 2312 if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
90f4745c 2313 $now = time();
4e445355 2314 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2315 $params[] = $now;
2316 $params[] = $now;
6b7de0bb 2317 }
90f4745c 2318 }
2319
4e445355 2320 return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2321 FROM {forum} f
2322 JOIN {forum_discussions} d ON d.forum = f.id
2323 JOIN {forum_posts} p ON p.discussion = d.id
2324 JOIN {user} u ON u.id = p.userid
2325 WHERE f.id = ?
2326 AND p.userid = ?
2327 $timedsql", $params);
90f4745c 2328}
2329
0a4ac01b 2330/**
2331 * Given a log entry, return the forum post details for it.
df1ba0f4 2332 *
2333 * @global object
2334 * @global object
2335 * @param object $log
2336 * @return array|null
0a4ac01b 2337 */
1f48942e 2338function forum_get_post_from_log($log) {
4e445355 2339 global $CFG, $DB;
1f48942e 2340
a327f25e 2341 $allnames = get_all_user_name_fields(true, 'u');
1f48942e 2342 if ($log->action == "add post") {
2343
a327f25e 2344 return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
4e445355 2345 FROM {forum_discussions} d,
2346 {forum_posts} p,
2347 {forum} f,
2348 {user} u
2349 WHERE p.id = ?
65b0e537 2350 AND d.id = p.discussion
2351 AND p.userid = u.id
8f7dc7f1 2352 AND u.deleted <> '1'
4e445355 2353 AND f.id = d.forum", array($log->info));
1f48942e 2354
2355
2356 } else if ($log->action == "add discussion") {
2357
a327f25e 2358 return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
4e445355 2359 FROM {forum_discussions} d,
2360 {forum_posts} p,
2361 {forum} f,
2362 {user} u
2363 WHERE d.id = ?
65b0e537 2364 AND d.firstpost = p.id
2365 AND p.userid = u.id
8f7dc7f1 2366 AND u.deleted <> '1'
4e445355 2367 AND f.id = d.forum", array($log->info));
1f48942e 2368 }
2369 return NULL;
2370}
2371
0a4ac01b 2372/**
2373 * Given a discussion id, return the first post from the discussion
df1ba0f4 2374 *
2375 * @global object
2376 * @global object
2377 * @param int $dicsussionid
2378 * @return array
0a4ac01b 2379 */
d05956ac 2380function forum_get_firstpost_from_discussion($discussionid) {
4e445355 2381 global $CFG, $DB;
d05956ac 2382
4e445355 2383 return $DB->get_record_sql("SELECT p.*
2384 FROM {forum_discussions} d,
2385 {forum_posts} p
2386 WHERE d.id = ?
2387 AND d.firstpost = p.id ", array($discussionid));
d05956ac 2388}
2389
0a4ac01b 2390/**
90f4745c 2391 * Returns an array of counts of replies to each discussion
df1ba0f4 2392 *
2393 * @global object
2394 * @global object
2395 * @param int $forumid
2396 * @param string $forumsort
2397 * @param int $limit
2398 * @param int $page
2399 * @param int $perpage
2400 * @return array
0a4ac01b 2401 */
90f4745c 2402function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
4e445355 2403 global $CFG, $DB;
1f48942e 2404
90f4745c 2405 if ($limit > 0) {
2406 $limitfrom = 0;
2407 $limitnum = $limit;
2408 } else if ($page != -1) {
2409 $limitfrom = $page*$perpage;
2410 $limitnum = $perpage;
2411 } else {
2412 $limitfrom = 0;
2413 $limitnum = 0;
2414 }
2415
2416 if ($forumsort == "") {
2417 $orderby = "";
2418 $groupby = "";
2419
2420 } else {
2421 $orderby = "ORDER BY $forumsort";
2422 $groupby = ", ".strtolower($forumsort);
2423 $groupby = str_replace('desc', '', $groupby);
2424 $groupby = str_replace('asc', '', $groupby);
2425 }
2426
bfeb10b7 2427 if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2428 $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
4e445355 2429 FROM {forum_posts} p
2430 JOIN {forum_discussions} d ON p.discussion = d.id
2431 WHERE p.parent > 0 AND d.forum = ?
bfeb10b7 2432 GROUP BY p.discussion";
4e445355 2433 return $DB->get_records_sql($sql, array($forumid));
bfeb10b7 2434
90f4745c 2435 } else {
bfeb10b7 2436 $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
4e445355 2437 FROM {forum_posts} p
2438 JOIN {forum_discussions} d ON p.discussion = d.id
2439 WHERE d.forum = ?
bfeb10b7 2440 GROUP BY p.discussion $groupby
2441 $orderby";
4e445355 2442 return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
90f4745c 2443 }
2444}
2445
df1ba0f4 2446/**
2447 * @global object
2448 * @global object
2449 * @global object
2450 * @staticvar array $cache
2451 * @param object $forum
2452 * @param object $cm
2453 * @param object $course
2454 * @return mixed
2455 */
90f4745c 2456function forum_count_discussions($forum, $cm, $course) {
4e445355 2457 global $CFG, $DB, $USER;
90f4745c 2458
2459 static $cache = array();
2460
2461 $now = round(time(), -2); // db cache friendliness
2462
4e445355 2463 $params = array($course->id);
2464
90f4745c 2465 if (!isset($cache[$course->id])) {
2466 if (!empty($CFG->forum_enabletimedposts)) {
4e445355 2467 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2468 $params[] = $now;
2469 $params[] = $now;
90f4745c 2470 } else {
2471 $timedsql = "";
2472 }
2473
2474 $sql = "SELECT f.id, COUNT(d.id) as dcount
4e445355 2475 FROM {forum} f
2476 JOIN {forum_discussions} d ON d.forum = f.id
2477 WHERE f.course = ?
90f4745c 2478 $timedsql
2479 GROUP BY f.id";
a48e8c4b 2480
4e445355 2481 if ($counts = $DB->get_records_sql($sql, $params)) {
90f4745c 2482 foreach ($counts as $count) {
2483 $counts[$count->id] = $count->dcount;
2484 }
2485 $cache[$course->id] = $counts;
2486 } else {
2487 $cache[$course->id] = array();
2488 }
a48e8c4b 2489 }
90f4745c 2490
2491 if (empty($cache[$course->id][$forum->id])) {
2492 return 0;
a48e8c4b 2493 }
90f4745c 2494
2495 $groupmode = groups_get_activity_groupmode($cm, $course);
2496
2497 if ($groupmode != SEPARATEGROUPS) {
2498 return $cache[$course->id][$forum->id];
1f48942e 2499 }
90f4745c 2500
bf0f06b1 2501 if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) {
90f4745c 2502 return $cache[$course->id][$forum->id];
2503 }
2504
2505 require_once($CFG->dirroot.'/course/lib.php');
2506
f20edd52 2507 $modinfo = get_fast_modinfo($course);
90f4745c 2508 if (is_null($modinfo->groups)) {
2509 $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2510 }
2511
98da6021
PS
2512 if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2513 $mygroups = $modinfo->groups[$cm->groupingid];
90f4745c 2514 } else {
98da6021 2515 $mygroups = false; // Will be set below
90f4745c 2516 }
2517
2518 // add all groups posts
2519 if (empty($mygroups)) {
2520 $mygroups = array(-1=>-1);
2521 } else {
2522 $mygroups[-1] = -1;
2523 }
4e445355 2524
2525 list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2526 $params[] = $forum->id;
90f4745c 2527
2528 if (!empty($CFG->forum_enabletimedposts)) {
2529 $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
4e445355 2530 $params[] = $now;
2531 $params[] = $now;
90f4745c 2532 } else {
2533 $timedsql = "";
2534 }
2535
2536 $sql = "SELECT COUNT(d.id)
4e445355 2537 FROM {forum_discussions} d
342650a7 2538 WHERE d.groupid $mygroups_sql AND d.forum = ?
90f4745c 2539 $timedsql";
2540
4e445355 2541 return $DB->get_field_sql($sql, $params);
1f48942e 2542}
2543
0a4ac01b 2544/**
63e87951 2545 * How many posts by other users are unrated by a given user in the given discussion?
df1ba0f4 2546 *
2b04c41c
SH
2547 * TODO: Is this function still used anywhere?
2548 *
df1ba0f4 2549 * @param int $discussionid
2550 * @param int $userid
2551 * @return mixed
0a4ac01b 2552 */
1f48942e 2553function forum_count_unrated_posts($discussionid, $userid) {
4e445355 2554 global $CFG, $DB;
2b04c41c
SH
2555
2556 $sql = "SELECT COUNT(*) as num
2557 FROM {forum_posts}
2558 WHERE parent > 0
2559 AND discussion = :discussionid
2560 AND userid <> :userid";
2561 $params = array('discussionid' => $discussionid, 'userid' => $userid);
2562 $posts = $DB->get_record_sql($sql, $params);
2563 if ($posts) {
2564 $sql = "SELECT count(*) as num
2565 FROM {forum_posts} p,
2566 {rating} r
2567 WHERE p.discussion = :discussionid AND
2568 p.id = r.itemid AND
2569 r.userid = userid AND
2570 r.component = 'mod_forum' AND
2571 r.ratingarea = 'post'";
2572 $rated = $DB->get_record_sql($sql, $params);
2573 if ($rated) {
2574 if ($posts->num > $rated->num) {
2575 return $posts->num - $rated->num;
1f48942e 2576 } else {
2577 return 0; // Just in case there was a counting error
2578 }
2579 } else {
2580 return $posts->num;
2581 }
2582 } else {
2583 return 0;
2584 }
2585}
2586
0a4ac01b 2587/**
2588 * Get all discussions in a forum
df1ba0f4 2589 *
2590 * @global object
2591 * @global object
2592 * @global object
2593 * @uses CONTEXT_MODULE
2594 * @uses VISIBLEGROUPS
2595 * @param object $cm
2596 * @param string $forumsort
2597 * @param bool $fullpost
2598 * @param int $unused
2599 * @param int $limit
2600 * @param bool $userlastmodified
2601 * @param int $page
2602 * @param int $perpage
2603 * @return array
0a4ac01b 2604 */
90f4745c 2605function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
4e445355 2606 global $CFG, $DB, $USER;
0fcac008 2607
3d284127 2608 $timelimit = '';
2609
90f4745c 2610 $now = round(time(), -2);
4e445355 2611 $params = array($cm->instance);
90f4745c 2612
bf0f06b1 2613 $modcontext = context_module::instance($cm->id);
2b63df96 2614
4436a63b 2615 if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2616 return array();
2617 }
2618
2619 if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2b63df96 2620
0468976c 2621 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
4e445355 2622 $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2623 $params[] = $now;
2624 $params[] = $now;
90f4745c 2625 if (isloggedin()) {
4e445355 2626 $timelimit .= " OR d.userid = ?";
2627 $params[] = $USER->id;
3d284127 2628 }
90f4745c 2629 $timelimit .= ")";
fbc21e82 2630 }
fbc21e82 2631 }
1f48942e 2632
90f4745c 2633 if ($limit > 0) {
2634 $limitfrom = 0;
2635 $limitnum = $limit;
2636 } else if ($page != -1) {
2637 $limitfrom = $page*$perpage;
2638 $limitnum = $perpage;
2639 } else {
2640 $limitfrom = 0;
2641 $limitnum = 0;
90ec387a 2642 }
8f0cd6ef 2643
90f4745c 2644 $groupmode = groups_get_activity_groupmode($cm);
2645 $currentgroup = groups_get_activity_group($cm);
353228d8 2646
fffa8b35 2647 if ($groupmode) {
2648 if (empty($modcontext)) {
bf0f06b1 2649 $modcontext = context_module::instance($cm->id);
fffa8b35 2650 }
2651
2652 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2653 if ($currentgroup) {
4e445355 2654 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2655 $params[] = $currentgroup;
fffa8b35 2656 } else {
2657 $groupselect = "";
2658 }
2659
2660 } else {
2661 //seprate groups without access all
2662 if ($currentgroup) {
4e445355 2663 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2664 $params[] = $currentgroup;
fffa8b35 2665 } else {
2666 $groupselect = "AND d.groupid = -1";
2667 }
2668 }
353228d8 2669 } else {
02509fe6 2670 $groupselect = "";
2671 }
2862b309 2672
fffa8b35 2673
29507631 2674 if (empty($forumsort)) {
2675 $forumsort = "d.timemodified DESC";
2676 }
2ab968e9 2677 if (empty($fullpost)) {
b879effb 2678 $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2ab968e9 2679 } else {
2680 $postdata = "p.*";
2681 }
9197e147 2682
13597d01 2683 if (empty($userlastmodified)) { // We don't need to know this
fffa8b35 2684 $umfields = "";
2685 $umtable = "";
13597d01 2686 } else {
ec71acb7
AG
2687 $umfields = '';
2688 $umnames = get_all_user_name_fields();
2689 foreach ($umnames as $umname) {
2690 $umfields .= ', um.' . $umname . ' AS um' . $umname;
2691 }
4e445355 2692 $umtable = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
90f4745c 2693 }
2694
a327f25e
AG
2695 $allnames = get_all_user_name_fields(true, 'u');
2696 $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend, $allnames,
2697 u.email, u.picture, u.imagealt $umfields
4e445355 2698 FROM {forum_discussions} d
2699 JOIN {forum_posts} p ON p.discussion = d.id
2700 JOIN {user} u ON p.userid = u.id
90f4745c 2701 $umtable
4e445355 2702 WHERE d.forum = ? AND p.parent = 0
90f4745c 2703 $timelimit $groupselect
2704 ORDER BY $forumsort";
4e445355 2705 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
90f4745c 2706}
2707
df1ba0f4 2708/**
2709 *
2710 * @global object
2711 * @global object
2712 * @global object
2713 * @uses CONTEXT_MODULE
2714 * @uses VISIBLEGROUPS
2715 * @param object $cm
2716 * @return array
2717 */
bfeb10b7 2718function forum_get_discussions_unread($cm) {
4e445355 2719 global $CFG, $DB, $USER;
90f4745c 2720
2721 $now = round(time(), -2);
ee151230 2722 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
35716b86 2723
ee151230 2724 $params = array();
90f4745c 2725 $groupmode = groups_get_activity_groupmode($cm);
2726 $currentgroup = groups_get_activity_group($cm);
2727
2728 if ($groupmode) {
bf0f06b1 2729 $modcontext = context_module::instance($cm->id);
90f4745c 2730
2731 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2732 if ($currentgroup) {
ee151230
AD
2733 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2734 $params['currentgroup'] = $currentgroup;
90f4745c 2735 } else {
2736 $groupselect = "";
2737 }
2738
2739 } else {
ee151230 2740 //separate groups without access all
90f4745c 2741 if ($currentgroup) {
ee151230
AD
2742 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2743 $params['currentgroup'] = $currentgroup;
90f4745c 2744 } else {
2745 $groupselect = "AND d.groupid = -1";
2746 }
2747 }
2748 } else {
2749 $groupselect = "";
13597d01 2750 }
2751
90f4745c 2752 if (!empty($CFG->forum_enabletimedposts)) {
ee151230
AD
2753 $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2754 $params['now1'] = $now;
2755 $params['now2'] = $now;
90f4745c 2756 } else {
2757 $timedsql = "";
2758 }
2759
2760 $sql = "SELECT d.id, COUNT(p.id) AS unread
4e445355 2761 FROM {forum_discussions} d
2762 JOIN {forum_posts} p ON p.discussion = d.id
2763 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
90f4745c 2764 WHERE d.forum = {$cm->instance}
ee151230 2765 AND p.modified >= :cutoffdate AND r.id is NULL
90f4745c 2766 $groupselect
4e445355 2767 $timedsql
bfeb10b7 2768 GROUP BY d.id";
ee151230
AD
2769 $params['cutoffdate'] = $cutoffdate;
2770
4e445355 2771 if ($unreads = $DB->get_records_sql($sql, $params)) {
90f4745c 2772 foreach ($unreads as $unread) {
2773 $unreads[$unread->id] = $unread->unread;
2774 }
2775 return $unreads;
2776 } else {
2777 return array();
2778 }
1f48942e 2779}
2780
df1ba0f4 2781/**
2782 * @global object
2783 * @global object
2784 * @global object
2785 * @uses CONEXT_MODULE
2786 * @uses VISIBLEGROUPS
2787 * @param object $cm
2788 * @return array
2789 */
90f4745c 2790function forum_get_discussions_count($cm) {
4e445355 2791 global $CFG, $DB, $USER;
90f4745c 2792
2793 $now = round(time(), -2);
4e445355 2794 $params = array($cm->instance);
90f4745c 2795 $groupmode = groups_get_activity_groupmode($cm);
2796 $currentgroup = groups_get_activity_group($cm);
2797
2798 if ($groupmode) {
bf0f06b1 2799 $modcontext = context_module::instance($cm->id);
90f4745c 2800
2801 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2802 if ($currentgroup) {
4e445355 2803 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2804 $params[] = $currentgroup;
90f4745c 2805 } else {
2806 $groupselect = "";
2807 }
2808
2809 } else {
2810 //seprate groups without access all
2811 if ($currentgroup) {
4e445355 2812 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2813 $params[] = $currentgroup;
90f4745c 2814 } else {
2815 $groupselect = "AND d.groupid = -1";
2816 }
2817 }
2818 } else {
2819 $groupselect = "";
2820 }
2821
2822 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2823
2824 $timelimit = "";
2825
2826 if (!empty($CFG->forum_enabletimedposts)) {
2827
bf0f06b1 2828 $modcontext = context_module::instance($cm->id);
90f4745c 2829
2830 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
4e445355 2831 $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2832 $params[] = $now;
2833 $params[] = $now;
90f4745c 2834 if (isloggedin()) {
4e445355 2835 $timelimit .= " OR d.userid = ?";
2836 $params[] = $USER->id;
90f4745c 2837 }
2838 $timelimit .= ")";
2839 }
2840 }
2841
2842 $sql = "SELECT COUNT(d.id)
4e445355 2843 FROM {forum_discussions} d
2844 JOIN {forum_posts} p ON p.discussion = d.id
2845 WHERE d.forum = ? AND p.parent = 0
2846 $groupselect $timelimit";
90f4745c 2847
4e445355 2848 return $DB->get_field_sql($sql, $params);
90f4745c 2849}
1f48942e 2850
2851
0a4ac01b 2852/**
2853 * Get all discussions started by a particular user in a course (or group)
2854 * This function no longer used ...
df1ba0f4 2855 *
2856 * @todo Remove this function if no longer used
2857 * @global object
2858 * @global object
2859 * @param int $courseid
2860 * @param int $userid
2861 * @param int $groupid
2862 * @return array
0a4ac01b 2863 */
b656e2a9 2864function forum_get_user_discussions($courseid, $userid, $groupid=0) {
4e445355 2865 global $CFG, $DB;
2866 $params = array($courseid, $userid);
b656e2a9 2867 if ($groupid) {
4e445355 2868 $groupselect = " AND d.groupid = ? ";
2869 $params[] = $groupid;
b656e2a9 2870 } else {
2871 $groupselect = "";
2872 }
2873
a327f25e
AG
2874 $allnames = get_all_user_name_fields(true, 'u');
2875 return $DB->get_records_sql("SELECT p.*, d.groupid, $allnames, u.email, u.picture, u.imagealt,
ebc3bd2b 2876 f.type as forumtype, f.name as forumname, f.id as forumid
4e445355 2877 FROM {forum_discussions} d,
2878 {forum_posts} p,
2879 {user} u,
2880 {forum} f
2881 WHERE d.course = ?
65b0e537 2882 AND p.discussion = d.id
2883 AND p.parent = 0
2884 AND p.userid = u.id
4e445355 2885 AND u.id = ?
b656e2a9 2886 AND d.forum = f.id $groupselect
4e445355 2887 ORDER BY p.created DESC", $params);
1f48942e 2888}
2889
0a4ac01b 2890/**
702dc57b 2891 * Get the list of potential subscribers to a forum.
8d8d0bfa 2892 *
2893 * @param object $forumcontext the forum context.
2894 * @param integer $groupid the id of a group, or 0 for all groups.
2895 * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2896 * @param string $sort sort order. As for get_users_by_capability.
2897 * @return array list of users.
0a4ac01b 2898 */
9695ff81 2899function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort = '') {
72650054
PS
2900 global $DB;
2901
bc196cbe 2902 // only active enrolled users or everybody on the frontpage
1659b1d1 2903 list($esql, $params) = get_enrolled_sql($forumcontext, 'mod/forum:allowforcesubscribe', $groupid, true);
9695ff81
TH
2904 if (!$sort) {
2905 list($sort, $sortparams) = users_order_by_sql('u');
2906 $params = array_merge($params, $sortparams);
2907 }
72650054
PS
2908
2909 $sql = "SELECT $fields
2910 FROM {user} u
9695ff81
TH
2911 JOIN ($esql) je ON je.id = u.id
2912 ORDER BY $sort";
72650054
PS
2913
2914 return $DB->get_records_sql($sql, $params);
8d8d0bfa 2915}
0a4ac01b 2916
8d8d0bfa 2917/**
2918 * Returns list of user objects that are subscribed to this forum
2919 *
df1ba0f4 2920 * @global object
2921 * @global object
8d8d0bfa 2922 * @param object $course the course
2923 * @param forum $forum the forum
2924 * @param integer $groupid group id, or 0 for all.
2925 * @param object $context the forum context, to save re-fetching it where possible.
c35ae136 2926 * @param string $fields requested user fields (with "u." table prefix)
8d8d0bfa 2927 * @return array list of users.
2928 */
c35ae136 2929function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
4e445355 2930 global $CFG, $DB;
6673d7bd 2931
a327f25e 2932 $allnames = get_all_user_name_fields(true, 'u');
c35ae136
PS
2933 if (empty($fields)) {
2934 $fields ="u.id,
2935 u.username,
a327f25e 2936 $allnames,
c35ae136
PS
2937 u.maildisplay,
2938 u.mailformat,
2939 u.maildigest,
2940 u.imagealt,
2941 u.email,
d8aa5ec7 2942 u.emailstop,
c35ae136
PS
2943 u.city,
2944 u.country,
2945 u.lastaccess,
2946 u.lastlogin,
2947 u.picture,
2948 u.timezone,
2949 u.theme,
2950 u.lang,
2951 u.trackforums,
2952 u.mnethostid";
2953 }
4e445355 2954
72650054
PS
2955 if (empty($context)) {
2956 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
bf0f06b1 2957 $context = context_module::instance($cm->id);
669f2499 2958 }
2959
72650054
PS
2960 if (forum_is_forcesubscribed($forum)) {
2961 $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
df1c2c71 2962
72650054
PS
2963 } else {
2964 // only active enrolled users or everybody on the frontpage
2965 list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2966 $params['forumid'] = $forum->id;
2967 $results = $DB->get_records_sql("SELECT $fields
2968 FROM {user} u
2969 JOIN ($esql) je ON je.id = u.id
2970 JOIN {forum_subscriptions} s ON s.userid = u.id
2971 WHERE s.forum = :forumid
2972 ORDER BY u.email ASC", $params);
669f2499 2973 }
2974
df1c2c71 2975 // Guest user should never be subscribed to a forum.
72650054 2976 unset($results[$CFG->siteguest]);
df1c2c71 2977
669f2499 2978 return $results;
1f48942e 2979}
9fa49e22 2980
067675c0 2981
2982
0a4ac01b 2983// OTHER FUNCTIONS ///////////////////////////////////////////////////////////
f93f848a 2984
2985
df1ba0f4 2986/**
2987 * @global object
2988 * @global object
2989 * @param int $courseid
2990 * @param string $type
2991 */
11b0c469 2992function forum_get_course_forum($courseid, $type) {
2993// How to set up special 1-per-course forums
c112bc60 2994 global $CFG, $DB, $OUTPUT, $USER;
a6fcdf98 2995
4e445355 2996 if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
65b0e537 2997 // There should always only be ONE, but with the right combination of
29cbd93a 2998 // errors there might be more. In this case, just return the oldest one (lowest ID).
2999 foreach ($forums as $forum) {
3000 return $forum; // ie the first one
11b0c469 3001 }
8daaf761 3002 }
e6874d9f 3003
8daaf761 3004 // Doesn't exist, so create one now.
b85b25eb 3005 $forum = new stdClass();
8daaf761 3006 $forum->course = $courseid;
3007 $forum->type = "$type";
c112bc60
ARN
3008 if (!empty($USER->htmleditor)) {
3009 $forum->introformat = $USER->htmleditor;
3010 }
8daaf761 3011 switch ($forum->type) {
3012 case "news":
294ce987 3013 $forum->name = get_string("namenews", "forum");
3014 $forum->intro = get_string("intronews", "forum");
906fef94 3015 $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
8daaf761 3016 $forum->assessed = 0;
709f0ec8 3017 if ($courseid == SITEID) {
3018 $forum->name = get_string("sitenews");
3019 $forum->forcesubscribe = 0;
8f0cd6ef 3020 }
8daaf761 3021 break;
3022 case "social":
294ce987 3023 $forum->name = get_string("namesocial", "forum");
3024 $forum->intro = get_string("introsocial", "forum");
8daaf761 3025 $forum->assessed = 0;
3026 $forum->forcesubscribe = 0;
3027 break;
1c7b8b93
NC
3028 case "blog":
3029 $forum->name = get_string('blogforum', 'forum');
3030 $forum->intro = get_string('introblog', 'forum');
3031 $forum->assessed = 0;
3032 $forum->forcesubscribe = 0;
3033 break;
8daaf761 3034 default:
9146b979 3035 echo $OUTPUT->notification("That forum type doesn't exist!");
8daaf761 3036 return false;
3037 break;
3038 }
3039
3040 $forum->timemodified = time();
4e445355 3041 $forum->id = $DB->insert_record("forum", $forum);
8daaf761 3042
4e445355 3043 if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
9146b979 3044 echo $OUTPUT->notification("Could not find forum module!!");
e1b5643f 3045 return false;
82aa0e8d 3046 }
39790bd8 3047 $mod = new stdClass();
e1b5643f 3048 $mod->course = $courseid;
3049 $mod->module = $module->id;
3050 $mod->instance = $forum->id;
3051 $mod->section = 0;
722e6ba9
MG
3052 include_once("$CFG->dirroot/course/lib.php");
3053 if (! $mod->coursemodule = add_course_module($mod) ) {
11cd754e 3054 echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
e1b5643f 3055 return false;
3056 }
722e6ba9 3057 $sectionid = course_add_cm_to_section($courseid, $mod->coursemodule, 0);
4e445355 3058 return $DB->get_record("forum", array("id" => "$forum->id"));
82aa0e8d 3059}
3060
f93f848a 3061
0a4ac01b 3062/**
df1ba0f4 3063 * Given the data about a posting, builds up the HTML to display it and
3064 * returns the HTML in a string. This is designed for sending via HTML email.
3065 *
3066 * @global object
3067 * @param object $course
3068 * @param object $cm
3069 * @param object $forum
3070 * @param object $discussion
3071 * @param object $post
3072 * @param object $userform
3073 * @param object $userto
3074 * @param bool $ownpost
3075 * @param bool $reply
3076 * @param bool $link
3077 * @param bool $rate
3078 * @param string $footer
3079 * @return string
3080 */
0faf6791 3081function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
11b0c469 3082 $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2b63df96 3083
59e28d8f 3084 global $CFG, $OUTPUT;
501cdbd8 3085
bf0f06b1 3086 $modcontext = context_module::instance($cm->id);
18ff4d42 3087
df1c2c71 3088 if (!isset($userto->viewfullnames[$forum->id])) {
df1c2c71 3089 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3090 } else {
3091 $viewfullnames = $userto->viewfullnames[$forum->id];
7613e6d7 3092 }
7613e6d7 3093
18ff4d42
PS
3094 // add absolute file links
3095 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3096
1306c5ea 3097 // format the post body
39790bd8 3098 $options = new stdClass();
1306c5ea 3099 $options->para = true;
e2d7687f 3100 $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
0d851f90 3101
add3201e 3102 $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
501cdbd8 3103
add3201e 3104 $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
812dbaf7 3105 $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
add3201e 3106 $output .= '</td>';
501cdbd8 3107
3108 if ($post->parent) {
add3201e 3109 $output .= '<td class="topic">';
501cdbd8 3110 } else {
add3201e