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