MDL-22077 forum: Deprecate unused helper trait
[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');
7f6689e4 28
501cdbd8 29/// CONSTANTS ///////////////////////////////////////////////////////////
f93f848a 30
d3583b41 31define('FORUM_MODE_FLATOLDEST', 1);
32define('FORUM_MODE_FLATNEWEST', -1);
33define('FORUM_MODE_THREADED', 2);
34define('FORUM_MODE_NESTED', 3);
2e2e71a8 35
afef965e 36define('FORUM_CHOOSESUBSCRIBE', 0);
d3583b41 37define('FORUM_FORCESUBSCRIBE', 1);
38define('FORUM_INITIALSUBSCRIBE', 2);
098d27d4 39define('FORUM_DISALLOWSUBSCRIBE',3);
709f0ec8 40
bd8f5d45
EM
41/**
42 * FORUM_TRACKING_OFF - Tracking is not available for this forum.
43 */
eaf50aef 44define('FORUM_TRACKING_OFF', 0);
bd8f5d45
EM
45
46/**
47 * FORUM_TRACKING_OPTIONAL - Tracking is based on user preference.
48 */
eaf50aef 49define('FORUM_TRACKING_OPTIONAL', 1);
bd8f5d45
EM
50
51/**
52 * FORUM_TRACKING_FORCED - Tracking is on, regardless of user setting.
53 * Treated as FORUM_TRACKING_OPTIONAL if $CFG->forum_allowforcedreadtracking is off.
54 */
55define('FORUM_TRACKING_FORCED', 2);
56
8076010d 57define('FORUM_MAILED_PENDING', 0);
da484043
MO
58define('FORUM_MAILED_SUCCESS', 1);
59define('FORUM_MAILED_ERROR', 2);
60
103f34d8
PS
61if (!defined('FORUM_CRON_USER_CACHE')) {
62 /** Defines how many full user records are cached in forum cron. */
63 define('FORUM_CRON_USER_CACHE', 5000);
64}
65
4f3a2d21
JL
66/**
67 * FORUM_POSTS_ALL_USER_GROUPS - All the posts in groups where the user is enrolled.
68 */
69define('FORUM_POSTS_ALL_USER_GROUPS', -2);
70
87b007b4
CF
71define('FORUM_DISCUSSION_PINNED', 1);
72define('FORUM_DISCUSSION_UNPINNED', 0);
73
caadf009 74/// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
75
0a4ac01b 76/**
77 * Given an object containing all the necessary data,
7cac0c4b 78 * (defined by the form in mod_form.php) this function
0a4ac01b 79 * will create a new instance and return the id number
80 * of the new instance.
df1ba0f4 81 *
6b04fdc0
PS
82 * @param stdClass $forum add forum instance
83 * @param mod_forum_mod_form $mform
6b7de0bb 84 * @return int intance id
3a5e1d06 85 */
6b04fdc0 86function forum_add_instance($forum, $mform = null) {
c18269c7 87 global $CFG, $DB;
caadf009 88
89 $forum->timemodified = time();
90
353228d8 91 if (empty($forum->assessed)) {
f2f56406 92 $forum->assessed = 0;
93 }
2b63df96 94
353228d8 95 if (empty($forum->ratingtime) or empty($forum->assessed)) {
98914efd 96 $forum->assesstimestart = 0;
97 $forum->assesstimefinish = 0;
98 }
caadf009 99
a8f3a651 100 $forum->id = $DB->insert_record('forum', $forum);
bf0f06b1 101 $modcontext = context_module::instance($forum->coursemodule);
cb9a975f 102
d3583b41 103 if ($forum->type == 'single') { // Create related discussion.
39790bd8 104 $discussion = new stdClass();
e2d7687f 105 $discussion->course = $forum->course;
106 $discussion->forum = $forum->id;
107 $discussion->name = $forum->name;
e2d7687f 108 $discussion->assessed = $forum->assessed;
6606c00f
MD
109 $discussion->message = $forum->intro;
110 $discussion->messageformat = $forum->introformat;
bf0f06b1 111 $discussion->messagetrust = trusttext_trusted(context_course::instance($forum->course));
e2d7687f 112 $discussion->mailnow = false;
113 $discussion->groupid = -1;
caadf009 114
dbf4d660 115 $message = '';
116
42776c94
PS
117 $discussion->id = forum_add_discussion($discussion, null, $message);
118
119 if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
a11aa38e 120 // Ugly hack - we need to copy the files somehow.
42776c94
PS
121 $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
122 $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
123
a11aa38e
PS
124 $options = array('subdirs'=>true); // Use the same options as intro field!
125 $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
42776c94 126 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
caadf009 127 }
128 }
8f0cd6ef 129
8e6775d8
AN
130 forum_grade_item_update($forum);
131
b3bd7a66
MN
132 $completiontimeexpected = !empty($forum->completionexpected) ? $forum->completionexpected : null;
133 \core_completion\api::update_completion_date_event($forum->coursemodule, 'forum', $forum->id, $completiontimeexpected);
134
8e6775d8
AN
135 return $forum->id;
136}
137
138/**
139 * Handle changes following the creation of a forum instance.
140 * This function is typically called by the course_module_created observer.
141 *
142 * @param object $context the forum context
143 * @param stdClass $forum The forum object
144 * @return void
145 */
146function forum_instance_created($context, $forum) {
3c268f25 147 if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
59075a43 148 $users = \mod_forum\subscriptions::get_potential_subscribers($context, 0, 'u.id, u.email');
709f0ec8 149 foreach ($users as $user) {
59075a43 150 \mod_forum\subscriptions::subscribe_user($user->id, $forum, $context);
709f0ec8 151 }
152 }
caadf009 153}
154
13bbe067 155/**
0a4ac01b 156 * Given an object containing all the necessary data,
7cac0c4b 157 * (defined by the form in mod_form.php) this function
0a4ac01b 158 * will update an existing instance with new data.
df1ba0f4 159 *
160 * @global object
90f4745c 161 * @param object $forum forum instance (with magic quotes)
6b7de0bb 162 * @return bool success
90f4745c 163 */
42776c94 164function forum_update_instance($forum, $mform) {
862f5a49 165 global $DB, $OUTPUT, $USER;
c18269c7 166
caadf009 167 $forum->timemodified = time();
353228d8 168 $forum->id = $forum->instance;
caadf009 169
f0da6b85 170 if (empty($forum->assessed)) {
f2f56406 171 $forum->assessed = 0;
172 }
2b63df96 173
353228d8 174 if (empty($forum->ratingtime) or empty($forum->assessed)) {
98914efd 175 $forum->assesstimestart = 0;
176 $forum->assesstimefinish = 0;
177 }
178
c18269c7 179 $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
13bbe067 180
181 // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
182 // if scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
183 // 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
184 if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
185 forum_update_grades($forum); // recalculate grades for the forum
186 }
187
d3583b41 188 if ($forum->type == 'single') { // Update related discussion and post.
35b81f27
RT
189 $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
190 if (!empty($discussions)) {
191 if (count($discussions) > 1) {
192 echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
193 }
194 $discussion = array_pop($discussions);
195 } else {
196 // try to recover by creating initial discussion - MDL-16262
197 $discussion = new stdClass();
198 $discussion->course = $forum->course;
199 $discussion->forum = $forum->id;
200 $discussion->name = $forum->name;
201 $discussion->assessed = $forum->assessed;
202 $discussion->message = $forum->intro;
203 $discussion->messageformat = $forum->introformat;
204 $discussion->messagetrust = true;
205 $discussion->mailnow = false;
206 $discussion->groupid = -1;
207
208 $message = '';
209
210 forum_add_discussion($discussion, null, $message);
211
212 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
213 print_error('cannotadd', 'forum');
caadf009 214 }
215 }
c18269c7 216 if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
12e57b92 217 print_error('cannotfindfirstpost', 'forum');
caadf009 218 }
219
6606c00f 220 $cm = get_coursemodule_from_instance('forum', $forum->id);
bf0f06b1 221 $modcontext = context_module::instance($cm->id, MUST_EXIST);
42776c94 222
a11aa38e 223 $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
6606c00f
MD
224 $post->subject = $forum->name;
225 $post->message = $forum->intro;
226 $post->messageformat = $forum->introformat;
227 $post->messagetrust = trusttext_trusted($modcontext);
228 $post->modified = $forum->timemodified;
a11aa38e
PS
229 $post->userid = $USER->id; // MDL-18599, so that current teacher can take ownership of activities.
230
231 if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
232 // Ugly hack - we need to copy the files somehow.
233 $options = array('subdirs'=>true); // Use the same options as intro field!
234 $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
235 }
caadf009 236
a8d6ef8c 237 $DB->update_record('forum_posts', $post);
caadf009 238 $discussion->name = $forum->name;
a8d6ef8c 239 $DB->update_record('forum_discussions', $discussion);
caadf009 240 }
241
a8d6ef8c 242 $DB->update_record('forum', $forum);
353228d8 243
4a913724
GPL
244 $modcontext = context_module::instance($forum->coursemodule);
245 if (($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) && ($oldforum->forcesubscribe <> $forum->forcesubscribe)) {
59075a43 246 $users = \mod_forum\subscriptions::get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
4a913724 247 foreach ($users as $user) {
59075a43 248 \mod_forum\subscriptions::subscribe_user($user->id, $forum, $modcontext);
4a913724
GPL
249 }
250 }
251
353228d8 252 forum_grade_item_update($forum);
253
b3bd7a66
MN
254 $completiontimeexpected = !empty($forum->completionexpected) ? $forum->completionexpected : null;
255 \core_completion\api::update_completion_date_event($forum->coursemodule, 'forum', $forum->id, $completiontimeexpected);
256
353228d8 257 return true;
caadf009 258}
259
260
0a4ac01b 261/**
262 * Given an ID of an instance of this module,
263 * this function will permanently delete the instance
264 * and any data that depends on it.
df1ba0f4 265 *
266 * @global object
267 * @param int $id forum instance id
90f4745c 268 * @return bool success
0a4ac01b 269 */
caadf009 270function forum_delete_instance($id) {
c18269c7 271 global $DB;
caadf009 272
c18269c7 273 if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
caadf009 274 return false;
275 }
455a8fed 276 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
277 return false;
278 }
279 if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
280 return false;
281 }
282
bf0f06b1 283 $context = context_module::instance($cm->id);
caadf009 284
fa686bc4 285 // now get rid of all files
286 $fs = get_file_storage();
455a8fed 287 $fs->delete_area_files($context->id);
fa686bc4 288
caadf009 289 $result = true;
290
b3bd7a66
MN
291 \core_completion\api::update_completion_date_event($cm->id, 'forum', $forum->id, null);
292
361a41d3
AN
293 // Delete digest and subscription preferences.
294 $DB->delete_records('forum_digests', array('forum' => $forum->id));
295 $DB->delete_records('forum_subscriptions', array('forum'=>$forum->id));
296 $DB->delete_records('forum_discussion_subs', array('forum' => $forum->id));
297
c18269c7 298 if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
caadf009 299 foreach ($discussions as $discussion) {
455a8fed 300 if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
caadf009 301 $result = false;
302 }
303 }
304 }
305
f37da850 306 forum_tp_delete_read_records(-1, -1, -1, $forum->id);
307
40fe1538
DW
308 forum_grade_item_delete($forum);
309
310 // We must delete the module record after we delete the grade item.
415f8c3e 311 if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
caadf009 312 $result = false;
313 }
314
315 return $result;
316}
317
318
4e781c7b 319/**
320 * Indicates API features that the forum supports.
321 *
df1ba0f4 322 * @uses FEATURE_GROUPS
323 * @uses FEATURE_GROUPINGS
df1ba0f4 324 * @uses FEATURE_MOD_INTRO
325 * @uses FEATURE_COMPLETION_TRACKS_VIEWS
326 * @uses FEATURE_COMPLETION_HAS_RULES
327 * @uses FEATURE_GRADE_HAS_GRADE
328 * @uses FEATURE_GRADE_OUTCOMES
4e781c7b 329 * @param string $feature
330 * @return mixed True if yes (some features may use other values)
331 */
332function forum_supports($feature) {
333 switch($feature) {
42f103be 334 case FEATURE_GROUPS: return true;
335 case FEATURE_GROUPINGS: return true;
dc5c2bd9 336 case FEATURE_MOD_INTRO: return true;
4e781c7b 337 case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
42f103be 338 case FEATURE_COMPLETION_HAS_RULES: return true;
339 case FEATURE_GRADE_HAS_GRADE: return true;
340 case FEATURE_GRADE_OUTCOMES: return true;
4bfdcfcf
EL
341 case FEATURE_RATE: return true;
342 case FEATURE_BACKUP_MOODLE2: return true;
3e4c2435 343 case FEATURE_SHOW_DESCRIPTION: return true;
50da4ddd 344 case FEATURE_PLAGIARISM: return true;
42f103be 345
49f6e5f4 346 default: return null;
4e781c7b 347 }
348}
349
350
351/**
352 * Obtains the automatic completion state for this forum based on any conditions
353 * in forum settings.
354 *
df1ba0f4 355 * @global object
356 * @global object
4e781c7b 357 * @param object $course Course
358 * @param object $cm Course-module
359 * @param int $userid User ID
360 * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
361 * @return bool True if completed, false if not. (If no conditions, then return
362 * value depends on comparison type)
363 */
364function forum_get_completion_state($course,$cm,$userid,$type) {
365 global $CFG,$DB;
366
367 // Get forum details
6093af9b 368 if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
4e781c7b 369 throw new Exception("Can't find forum {$cm->instance}");
370 }
371
372 $result=$type; // Default return value
373
374 $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
375 $postcountsql="
5e7f2b0b 376SELECT
377 COUNT(1)
378FROM
379 {forum_posts} fp
49f6e5f4 380 INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
4e781c7b 381WHERE
382 fp.userid=:userid AND fd.forum=:forumid";
383
6093af9b 384 if ($forum->completiondiscussions) {
4e781c7b 385 $value = $forum->completiondiscussions <=
6093af9b 386 $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
387 if ($type == COMPLETION_AND) {
388 $result = $result && $value;
4e781c7b 389 } else {
6093af9b 390 $result = $result || $value;
4e781c7b 391 }
392 }
6093af9b 393 if ($forum->completionreplies) {
5e7f2b0b 394 $value = $forum->completionreplies <=
6093af9b 395 $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
396 if ($type==COMPLETION_AND) {
397 $result = $result && $value;
4e781c7b 398 } else {
6093af9b 399 $result = $result || $value;
4e781c7b 400 }
401 }
6093af9b 402 if ($forum->completionposts) {
4e781c7b 403 $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
6093af9b 404 if ($type == COMPLETION_AND) {
405 $result = $result && $value;
4e781c7b 406 } else {
6093af9b 407 $result = $result || $value;
4e781c7b 408 }
409 }
410
5e7f2b0b 411 return $result;
4e781c7b 412}
413
8260050d
AD
414/**
415 * Create a message-id string to use in the custom headers of forum notification emails
416 *
417 * message-id is used by email clients to identify emails and to nest conversations
418 *
419 * @param int $postid The ID of the forum post we are notifying the user about
420 * @param int $usertoid The ID of the user being notified
8260050d
AD
421 * @return string A unique message-id
422 */
54dceeed
BH
423function forum_get_email_message_id($postid, $usertoid) {
424 return generate_email_messageid(hash('sha256', $postid . 'to' . $usertoid));
1376b0dd 425}
4e781c7b 426
0a4ac01b 427/**
13bbe067 428 *
1670305d 429 * @param object $course
430 * @param object $user
431 * @param object $mod TODO this is not used in this function, refactor
432 * @param object $forum
433 * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
0a4ac01b 434 */
caadf009 435function forum_user_outline($course, $user, $mod, $forum) {
1a96363a
NC
436 global $CFG;
437 require_once("$CFG->libdir/gradelib.php");
438 $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
439 if (empty($grades->items[0]->grades)) {
440 $grade = false;
441 } else {
442 $grade = reset($grades->items[0]->grades);
443 }
444
445 $count = forum_count_user_posts($forum->id, $user->id);
446
447 if ($count && $count->postcount > 0) {
39790bd8 448 $result = new stdClass();
1a96363a
NC
449 $result->info = get_string("numposts", "forum", $count->postcount);
450 $result->time = $count->lastpost;
451 if ($grade) {
f5d1dbb3
MF
452 if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
453 $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
454 } else {
455 $result->info = get_string('grade') . ': ' . get_string('hidden', 'grades');
456 }
90f4745c 457 }
1a96363a
NC
458 return $result;
459 } else if ($grade) {
39790bd8 460 $result = new stdClass();
f5d1dbb3
MF
461 if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
462 $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
463 } else {
464 $result->info = get_string('grade') . ': ' . get_string('hidden', 'grades');
465 }
4433f871
AD
466
467 //datesubmitted == time created. dategraded == time modified or time overridden
468 //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
94a74f54 469 //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
4433f871
AD
470 if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
471 $result->time = $grade->dategraded;
472 } else {
473 $result->time = $grade->datesubmitted;
474 }
475
1a96363a 476 return $result;
caadf009 477 }
478 return NULL;
479}
480
481
0a4ac01b 482/**
df1ba0f4 483 * @global object
484 * @global object
485 * @param object $coure
486 * @param object $user
487 * @param object $mod
488 * @param object $forum
0a4ac01b 489 */
caadf009 490function forum_user_complete($course, $user, $mod, $forum) {
1a96363a
NC
491 global $CFG,$USER, $OUTPUT;
492 require_once("$CFG->libdir/gradelib.php");
493
494 $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
495 if (!empty($grades->items[0]->grades)) {
496 $grade = reset($grades->items[0]->grades);
f5d1dbb3
MF
497 if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
498 echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
499 if ($grade->str_feedback) {
500 echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
501 }
502 } else {
503 echo $OUTPUT->container(get_string('grade') . ': ' . get_string('hidden', 'grades'));
1a96363a
NC
504 }
505 }
caadf009 506
1f48942e 507 if ($posts = forum_get_user_posts($forum->id, $user->id)) {
e3ff14ca 508
65bcf17b 509 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
12e57b92 510 print_error('invalidcoursemodule');
65bcf17b 511 }
2e19ca18 512 $context = context_module::instance($cm->id);
90f4745c 513 $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
2e19ca18
RW
514 $posts = array_filter($posts, function($post) use ($discussions) {
515 return isset($discussions[$post->discussion]);
516 });
517 $entityfactory = mod_forum\local\container::get_entity_factory();
518 $rendererfactory = mod_forum\local\container::get_renderer_factory();
519 $postrenderer = $rendererfactory->get_posts_renderer();
520
521 echo $postrenderer->render(
522 $USER,
523 [$forum->id => $entityfactory->get_forum_from_stdclass($forum, $context, $cm, $course)],
524 array_map(function($discussion) use ($entityfactory) {
525 return $entityfactory->get_discussion_from_stdclass($discussion);
526 }, $discussions),
527 array_map(function($post) use ($entityfactory) {
528 return $entityfactory->get_post_from_stdclass($post);
529 }, $posts)
530 );
caadf009 531 } else {
41905731 532 echo "<p>".get_string("noposts", "forum")."</p>";
caadf009 533 }
caadf009 534}
535
8538c562
DM
536/**
537 * Filters the forum discussions according to groups membership and config.
538 *
e9dfeec9 539 * @deprecated since 3.3
063b7ee6 540 * @todo The final deprecation of this function will take place in Moodle 3.7 - see MDL-57487.
8538c562
DM
541 * @since Moodle 2.8, 2.7.1, 2.6.4
542 * @param array $discussions Discussions with new posts array
543 * @return array Forums with the number of new posts
544 */
545function forum_filter_user_groups_discussions($discussions) {
c38965fb 546
e9dfeec9
MN
547 debugging('The function forum_filter_user_groups_discussions() is now deprecated.', DEBUG_DEVELOPER);
548
8538c562
DM
549 // Group the remaining discussions posts by their forumid.
550 $filteredforums = array();
c38965fb 551
8538c562
DM
552 // Discard not visible groups.
553 foreach ($discussions as $discussion) {
c38965fb 554
8538c562
DM
555 // Course data is already cached.
556 $instances = get_fast_modinfo($discussion->course)->get_instances();
557 $forum = $instances['forum'][$discussion->forum];
5e7f2b0b 558
8538c562
DM
559 // Continue if the user should not see this discussion.
560 if (!forum_is_user_group_discussion($forum, $discussion->groupid)) {
561 continue;
562 }
563
564 // Grouping results by forum.
565 if (empty($filteredforums[$forum->instance])) {
566 $filteredforums[$forum->instance] = new stdClass();
567 $filteredforums[$forum->instance]->id = $forum->id;
568 $filteredforums[$forum->instance]->count = 0;
569 }
570 $filteredforums[$forum->instance]->count += $discussion->count;
571
572 }
573
574 return $filteredforums;
575}
576
577/**
578 * Returns whether the discussion group is visible by the current user or not.
579 *
580 * @since Moodle 2.8, 2.7.1, 2.6.4
581 * @param cm_info $cm The discussion course module
582 * @param int $discussiongroupid The discussion groupid
583 * @return bool
584 */
585function forum_is_user_group_discussion(cm_info $cm, $discussiongroupid) {
586
587 if ($discussiongroupid == -1 || $cm->effectivegroupmode != SEPARATEGROUPS) {
588 return true;
589 }
590
591 if (isguestuser()) {
592 return false;
593 }
594
595 if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id)) ||
596 in_array($discussiongroupid, $cm->get_modinfo()->get_groups($cm->groupingid))) {
597 return true;
598 }
599
600 return false;
601}
5e7f2b0b 602
0a4ac01b 603/**
e9dfeec9 604 * @deprecated since 3.3
063b7ee6 605 * @todo The final deprecation of this function will take place in Moodle 3.7 - see MDL-57487.
df1ba0f4 606 * @global object
607 * @global object
608 * @global object
609 * @param array $courses
610 * @param array $htmlarray
0a4ac01b 611 */
185cfb09 612function forum_print_overview($courses,&$htmlarray) {
261c6ef0 613 global $USER, $CFG, $DB, $SESSION;
493f0820 614
e9dfeec9
MN
615 debugging('The function forum_print_overview() is now deprecated.', DEBUG_DEVELOPER);
616
185cfb09 617 if (empty($courses) || !is_array($courses) || count($courses) == 0) {
618 return array();
619 }
f8716988 620
185cfb09 621 if (!$forums = get_all_instances_in_courses('forum',$courses)) {
f8716988 622 return;
623 }
185cfb09 624
ba4ee840
DM
625 // Courses to search for new posts
626 $coursessqls = array();
4e445355 627 $params = array();
185cfb09 628 foreach ($courses as $course) {
ba4ee840
DM
629
630 // If the user has never entered into the course all posts are pending
631 if ($course->lastaccess == 0) {
8538c562 632 $coursessqls[] = '(d.course = ?)';
ba4ee840
DM
633 $params[] = $course->id;
634
635 // Only posts created after the course last access
636 } else {
8538c562 637 $coursessqls[] = '(d.course = ? AND p.created > ?)';
ba4ee840
DM
638 $params[] = $course->id;
639 $params[] = $course->lastaccess;
640 }
185cfb09 641 }
4e445355 642 $params[] = $USER->id;
ba4ee840
DM
643 $coursessql = implode(' OR ', $coursessqls);
644
8538c562
DM
645 $sql = "SELECT d.id, d.forum, d.course, d.groupid, COUNT(*) as count "
646 .'FROM {forum_discussions} d '
ba4ee840
DM
647 .'JOIN {forum_posts} p ON p.discussion = d.id '
648 ."WHERE ($coursessql) "
3e95e09b 649 .'AND p.deleted <> 1 '
ba4ee840 650 .'AND p.userid != ? '
6667ebb9 651 .'AND (d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?)) '
81ba2632
JH
652 .'GROUP BY d.id, d.forum, d.course, d.groupid '
653 .'ORDER BY d.course, d.forum';
6667ebb9
AN
654 $params[] = time();
655 $params[] = time();
2b63df96 656
8538c562
DM
657 // Avoid warnings.
658 if (!$discussions = $DB->get_records_sql($sql, $params)) {
659 $discussions = array();
185cfb09 660 }
2b63df96 661
8538c562
DM
662 $forumsnewposts = forum_filter_user_groups_discussions($discussions);
663
185cfb09 664 // also get all forum tracking stuff ONCE.
665 $trackingforums = array();
666 foreach ($forums as $forum) {
667 if (forum_tp_can_track_forums($forum)) {
668 $trackingforums[$forum->id] = $forum;
669 }
670 }
2b63df96 671
185cfb09 672 if (count($trackingforums) > 0) {
673 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
674 $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
4e445355 675 ' FROM {forum_posts} p '.
676 ' JOIN {forum_discussions} d ON p.discussion = d.id '.
3e95e09b 677 ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE p.deleted <> 1 AND (';
4e445355 678 $params = array($USER->id);
679
d3553951 680 foreach ($trackingforums as $track) {
4e445355 681 $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
682 $params[] = $track->id;
261c6ef0 683 if (isset($SESSION->currentgroup[$track->course])) {
684 $groupid = $SESSION->currentgroup[$track->course];
685 } else {
0209f964
PS
686 // get first groupid
687 $groupids = groups_get_all_groups($track->course, $USER->id);
688 if ($groupids) {
689 reset($groupids);
690 $groupid = key($groupids);
261c6ef0 691 $SESSION->currentgroup[$track->course] = $groupid;
692 } else {
693 $groupid = 0;
694 }
0209f964 695 unset($groupids);
261c6ef0 696 }
697 $params[] = $groupid;
185cfb09 698 }
699 $sql = substr($sql,0,-3); // take off the last OR
6667ebb9
AN
700 $sql .= ') AND p.modified >= ? AND r.id is NULL ';
701 $sql .= 'AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)) ';
702 $sql .= 'GROUP BY d.forum,d.course';
4e445355 703 $params[] = $cutoffdate;
6667ebb9
AN
704 $params[] = time();
705 $params[] = time();
185cfb09 706
4e445355 707 if (!$unread = $DB->get_records_sql($sql, $params)) {
185cfb09 708 $unread = array();
709 }
710 } else {
711 $unread = array();
95d71ad3 712 }
185cfb09 713
8538c562 714 if (empty($unread) and empty($forumsnewposts)) {
9cba7a8c 715 return;
716 }
717
718 $strforum = get_string('modulename','forum');
9cba7a8c 719
f8716988 720 foreach ($forums as $forum) {
185cfb09 721 $str = '';
f8716988 722 $count = 0;
185cfb09 723 $thisunread = 0;
f8716988 724 $showunread = false;
725 // either we have something from logs, or trackposts, or nothing.
8538c562
DM
726 if (array_key_exists($forum->id, $forumsnewposts) && !empty($forumsnewposts[$forum->id])) {
727 $count = $forumsnewposts[$forum->id]->count;
90558ec4 728 }
185cfb09 729 if (array_key_exists($forum->id,$unread)) {
730 $thisunread = $unread[$forum->id]->count;
f8716988 731 $showunread = true;
0d6b9d4f 732 }
185cfb09 733 if ($count > 0 || $thisunread > 0) {
e23800b7 734 $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
735 $forum->name.'</a></div>';
6d641063 736 $str .= '<div class="info"><span class="postsincelogin">';
54294601 737 $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
f8716988 738 if (!empty($showunread)) {
54294601 739 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
f8716988 740 }
e23800b7 741 $str .= '</div></div>';
f8716988 742 }
2b63df96 743 if (!empty($str)) {
185cfb09 744 if (!array_key_exists($forum->course,$htmlarray)) {
745 $htmlarray[$forum->course] = array();
746 }
747 if (!array_key_exists('forum',$htmlarray[$forum->course])) {
748 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
749 }
750 $htmlarray[$forum->course]['forum'] .= $str;
751 }
2b63df96 752 }
0d6b9d4f 753}
754
0a4ac01b 755/**
756 * Given a course and a date, prints a summary of all the new
757 * messages posted in the course since that date
df1ba0f4 758 *
759 * @global object
760 * @global object
761 * @global object
762 * @uses CONTEXT_MODULE
763 * @uses VISIBLEGROUPS
90f4745c 764 * @param object $course
765 * @param bool $viewfullnames capability
766 * @param int $timestart
767 * @return bool success
0a4ac01b 768 */
dd97c328 769function forum_print_recent_activity($course, $viewfullnames, $timestart) {
cb860491 770 global $CFG, $USER, $DB, $OUTPUT;
caadf009 771
dd97c328 772 // do not use log table if possible, it may be huge and is expensive to join with other tables
caadf009 773
dda60f88 774 $allnamefields = user_picture::fields('u', null, 'duserid');
4e445355 775 if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
dda60f88 776 d.timestart, d.timeend, $allnamefields
4e445355 777 FROM {forum_posts} p
778 JOIN {forum_discussions} d ON d.id = p.discussion
779 JOIN {forum} f ON f.id = d.forum
780 JOIN {user} u ON u.id = p.userid
3e95e09b 781 WHERE p.created > ? AND f.course = ? AND p.deleted <> 1
4e445355 782 ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
dd97c328 783 return false;
1b5910c4 784 }
785
f20edd52 786 $modinfo = get_fast_modinfo($course);
dcde9f02 787
dd97c328 788 $groupmodes = array();
789 $cms = array();
d05956ac 790
dd97c328 791 $strftimerecent = get_string('strftimerecent');
d05956ac 792
dd97c328 793 $printposts = array();
794 foreach ($posts as $post) {
795 if (!isset($modinfo->instances['forum'][$post->forum])) {
796 // not visible
797 continue;
798 }
799 $cm = $modinfo->instances['forum'][$post->forum];
800 if (!$cm->uservisible) {
801 continue;
802 }
bf0f06b1 803 $context = context_module::instance($cm->id);
6b7de0bb 804
805 if (!has_capability('mod/forum:viewdiscussion', $context)) {
806 continue;
807 }
583b57b4 808
dd97c328 809 if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
810 and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
6b7de0bb 811 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
dd97c328 812 continue;
ac1d9a22 813 }
dd97c328 814 }
583b57b4 815
8538c562
DM
816 // Check that the user can see the discussion.
817 if (forum_is_user_group_discussion($cm, $post->groupid)) {
818 $printposts[] = $post;
dd97c328 819 }
8f7dc7f1 820
dd97c328 821 }
822 unset($posts);
8f7dc7f1 823
dd97c328 824 if (!$printposts) {
825 return false;
826 }
827
cb860491 828 echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
af0fc6a2 829 $list = html_writer::start_tag('ul', ['class' => 'unlist']);
dd97c328 830
831 foreach ($printposts as $post) {
832 $subjectclass = empty($post->parent) ? ' bold' : '';
af0fc6a2 833 $authorhidden = forum_is_author_hidden($post, (object) ['type' => $post->forumtype]);
dd97c328 834
af0fc6a2
JP
835 $list .= html_writer::start_tag('li');
836 $list .= html_writer::start_div('head');
1bf3a76a 837 $list .= html_writer::div(userdate_htmltime($post->modified, $strftimerecent), 'date');
af0fc6a2
JP
838 if (!$authorhidden) {
839 $list .= html_writer::div(fullname($post, $viewfullnames), 'name');
840 }
841 $list .= html_writer::end_div(); // Head.
842
843 $list .= html_writer::start_div('info' . $subjectclass);
844 $discussionurl = new moodle_url('/mod/forum/discuss.php', ['d' => $post->discussion]);
845 if (!empty($post->parent)) {
846 $discussionurl->param('parent', $post->parent);
847 $discussionurl->set_anchor('p'. $post->id);
caadf009 848 }
dd97c328 849 $post->subject = break_up_long_words(format_string($post->subject, true));
934ee47b 850 $list .= html_writer::link($discussionurl, $post->subject, ['rel' => 'bookmark']);
af0fc6a2
JP
851 $list .= html_writer::end_div(); // Info.
852 $list .= html_writer::end_tag('li');
caadf009 853 }
dd97c328 854
af0fc6a2
JP
855 $list .= html_writer::end_tag('ul');
856 echo $list;
dd97c328 857
858 return true;
caadf009 859}
860
353228d8 861/**
862 * Return grade for given user or all users.
863 *
df1ba0f4 864 * @global object
865 * @global object
866 * @param object $forum
353228d8 867 * @param int $userid optional user id, 0 means all users
868 * @return array array of grades, false if none
869 */
2b04c41c 870function forum_get_user_grades($forum, $userid = 0) {
63e87951 871 global $CFG;
df997f84 872
63e87951 873 require_once($CFG->dirroot.'/rating/lib.php');
df997f84 874
2b04c41c
SH
875 $ratingoptions = new stdClass;
876 $ratingoptions->component = 'mod_forum';
877 $ratingoptions->ratingarea = 'post';
353228d8 878
63e87951
AD
879 //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
880 $ratingoptions->modulename = 'forum';
881 $ratingoptions->moduleid = $forum->id;
63e87951
AD
882 $ratingoptions->userid = $userid;
883 $ratingoptions->aggregationmethod = $forum->assessed;
884 $ratingoptions->scaleid = $forum->scale;
885 $ratingoptions->itemtable = 'forum_posts';
886 $ratingoptions->itemtableusercolumn = 'userid';
353228d8 887
2b04c41c 888 $rm = new rating_manager();
63e87951 889 return $rm->get_user_grades($ratingoptions);
353228d8 890}
caadf009 891
0a4ac01b 892/**
775f811a 893 * Update activity grades
353228d8 894 *
a153c9f2 895 * @category grade
775f811a 896 * @param object $forum
897 * @param int $userid specific user only, 0 means all
6b7de0bb 898 * @param boolean $nullifnone return null if grade does not exist
90f4745c 899 * @return void
0a4ac01b 900 */
775f811a 901function forum_update_grades($forum, $userid=0, $nullifnone=true) {
4e445355 902 global $CFG, $DB;
775f811a 903 require_once($CFG->libdir.'/gradelib.php');
caadf009 904
775f811a 905 if (!$forum->assessed) {
906 forum_grade_item_update($forum);
02ebf404 907
775f811a 908 } else if ($grades = forum_get_user_grades($forum, $userid)) {
909 forum_grade_item_update($forum, $grades);
eafb9d9e 910
775f811a 911 } else if ($userid and $nullifnone) {
39790bd8 912 $grade = new stdClass();
775f811a 913 $grade->userid = $userid;
914 $grade->rawgrade = NULL;
915 forum_grade_item_update($forum, $grade);
02ebf404 916
353228d8 917 } else {
775f811a 918 forum_grade_item_update($forum);
919 }
920}
921
353228d8 922/**
612607bd 923 * Create/update grade item for given forum
353228d8 924 *
a153c9f2 925 * @category grade
df1ba0f4 926 * @uses GRADE_TYPE_NONE
927 * @uses GRADE_TYPE_VALUE
928 * @uses GRADE_TYPE_SCALE
a153c9f2
AD
929 * @param stdClass $forum Forum object with extra cmidnumber
930 * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
612607bd 931 * @return int 0 if ok
353228d8 932 */
0b5a80a1 933function forum_grade_item_update($forum, $grades=NULL) {
612607bd 934 global $CFG;
935 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
936 require_once($CFG->libdir.'/gradelib.php');
353228d8 937 }
938
612607bd 939 $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
353228d8 940
5980d52f 941 if (!$forum->assessed or $forum->scale == 0) {
612607bd 942 $params['gradetype'] = GRADE_TYPE_NONE;
353228d8 943
944 } else if ($forum->scale > 0) {
945 $params['gradetype'] = GRADE_TYPE_VALUE;
946 $params['grademax'] = $forum->scale;
947 $params['grademin'] = 0;
948
949 } else if ($forum->scale < 0) {
950 $params['gradetype'] = GRADE_TYPE_SCALE;
951 $params['scaleid'] = -$forum->scale;
952 }
953
0b5a80a1 954 if ($grades === 'reset') {
955 $params['reset'] = true;
956 $grades = NULL;
957 }
958
959 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
353228d8 960}
961
962/**
963 * Delete grade item for given forum
964 *
a153c9f2
AD
965 * @category grade
966 * @param stdClass $forum Forum object
967 * @return grade_item
353228d8 968 */
969function forum_grade_item_delete($forum) {
612607bd 970 global $CFG;
971 require_once($CFG->libdir.'/gradelib.php');
972
b67ec72f 973 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
caadf009 974}
975
353228d8 976
0a4ac01b 977/**
90f4745c 978 * This function returns if a scale is being used by one forum
df1ba0f4 979 *
980 * @global object
90f4745c 981 * @param int $forumid
982 * @param int $scaleid negative number
983 * @return bool
0a4ac01b 984 */
0f1a97c2 985function forum_scale_used ($forumid,$scaleid) {
4e445355 986 global $DB;
0f1a97c2 987 $return = false;
65b0e537 988
4e445355 989 $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
65b0e537 990
fa22fd5f 991 if (!empty($rec) && !empty($scaleid)) {
0f1a97c2 992 $return = true;
993 }
65b0e537 994
0f1a97c2 995 return $return;
996}
997
85c9ebb9 998/**
999 * Checks if scale is being used by any instance of forum
1000 *
1001 * This is used to find out if scale used anywhere
df1ba0f4 1002 *
1003 * @global object
85c9ebb9 1004 * @param $scaleid int
1005 * @return boolean True if the scale is used by any forum
1006 */
1007function forum_scale_used_anywhere($scaleid) {
4e445355 1008 global $DB;
1009 if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
85c9ebb9 1010 return true;
1011 } else {
1012 return false;
1013 }
1014}
1015
0a4ac01b 1016// SQL FUNCTIONS ///////////////////////////////////////////////////////////
9fa49e22 1017
0a4ac01b 1018/**
1019 * Gets a post with all info ready for forum_print_post
1020 * Most of these joins are just to get the forum id
df1ba0f4 1021 *
1022 * @global object
1023 * @global object
90f4745c 1024 * @param int $postid
1025 * @return mixed array of posts or false
0a4ac01b 1026 */
1f48942e 1027function forum_get_post_full($postid) {
4e445355 1028 global $CFG, $DB;
1f48942e 1029
a327f25e
AG
1030 $allnames = get_all_user_name_fields(true, 'u');
1031 return $DB->get_record_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
4e445355 1032 FROM {forum_posts} p
1033 JOIN {forum_discussions} d ON p.discussion = d.id
1034 LEFT JOIN {user} u ON p.userid = u.id
1035 WHERE p.id = ?", array($postid));
1f48942e 1036}
1037
65bcf17b 1038/**
1039 * Gets all posts in discussion including top parent.
df1ba0f4 1040 *
1041 * @global object
1042 * @global object
1043 * @global object
90f4745c 1044 * @param int $discussionid
1045 * @param string $sort
1046 * @param bool $tracking does user track the forum?
1047 * @return array of posts
65bcf17b 1048 */
90f4745c 1049function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
4e445355 1050 global $CFG, $DB, $USER;
65bcf17b 1051
3c2bf848 1052 $tr_sel = "";
1053 $tr_join = "";
4e445355 1054 $params = array();
3c2bf848 1055
90f4745c 1056 if ($tracking) {
90f4745c 1057 $tr_sel = ", fr.id AS postread";
4e445355 1058 $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1059 $params[] = $USER->id;
90f4745c 1060 }
1061
a327f25e 1062 $allnames = get_all_user_name_fields(true, 'u');
4e445355 1063 $params[] = $discussionid;
a327f25e 1064 if (!$posts = $DB->get_records_sql("SELECT p.*, $allnames, u.email, u.picture, u.imagealt $tr_sel
4e445355 1065 FROM {forum_posts} p
1066 LEFT JOIN {user} u ON p.userid = u.id
90f4745c 1067 $tr_join
4e445355 1068 WHERE p.discussion = ?
1069 ORDER BY $sort", $params)) {
65bcf17b 1070 return array();
1071 }
1072
1073 foreach ($posts as $pid=>$p) {
90f4745c 1074 if ($tracking) {
1075 if (forum_tp_is_post_old($p)) {
6b7de0bb 1076 $posts[$pid]->postread = true;
90f4745c 1077 }
1078 }
65bcf17b 1079 if (!$p->parent) {
1080 continue;
1081 }
1082 if (!isset($posts[$p->parent])) {
1083 continue; // parent does not exist??
1084 }
1085 if (!isset($posts[$p->parent]->children)) {
1086 $posts[$p->parent]->children = array();
1087 }
1088 $posts[$p->parent]->children[$pid] =& $posts[$pid];
1089 }
1090
bfa7bece
AN
1091 // Start with the last child of the first post.
1092 $post = &$posts[reset($posts)->id];
1093
1094 $lastpost = false;
1095 while (!$lastpost) {
1096 if (!isset($post->children)) {
1097 $post->lastpost = true;
1098 $lastpost = true;
1099 } else {
1100 // Go to the last child of this post.
1101 $post = &$posts[end($post->children)->id];
1102 }
1103 }
1104
65bcf17b 1105 return $posts;
1106}
1107
42fb3c85 1108/**
1109 * An array of forum objects that the user is allowed to read/search through.
df1ba0f4 1110 *
1111 * @global object
1112 * @global object
1113 * @global object
1114 * @param int $userid
1115 * @param int $courseid if 0, we look for forums throughout the whole site.
42fb3c85 1116 * @return array of forum objects, or false if no matches
1117 * Forum objects have the following attributes:
1118 * id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1119 * viewhiddentimedposts
1120 */
1121function forum_get_readable_forums($userid, $courseid=0) {
2b63df96 1122
4e445355 1123 global $CFG, $DB, $USER;
6b7de0bb 1124 require_once($CFG->dirroot.'/course/lib.php');
2b63df96 1125
4e445355 1126 if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
12e57b92 1127 print_error('notinstalled', 'forum');
42fb3c85 1128 }
2b63df96 1129
42fb3c85 1130 if ($courseid) {
4e445355 1131 $courses = $DB->get_records('course', array('id' => $courseid));
42fb3c85 1132 } else {
65bcf17b 1133 // If no course is specified, then the user can see SITE + his courses.
4e445355 1134 $courses1 = $DB->get_records('course', array('id' => SITEID));
b1d5d015 1135 $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
6155150c 1136 $courses = array_merge($courses1, $courses2);
42fb3c85 1137 }
1138 if (!$courses) {
6b7de0bb 1139 return array();
42fb3c85 1140 }
1141
1142 $readableforums = array();
2b63df96 1143
6527b5c2 1144 foreach ($courses as $course) {
1145
f20edd52 1146 $modinfo = get_fast_modinfo($course);
2b63df96 1147
6b7de0bb 1148 if (empty($modinfo->instances['forum'])) {
1149 // hmm, no forums?
1150 continue;
1151 }
2b63df96 1152
4e445355 1153 $courseforums = $DB->get_records('forum', array('course' => $course->id));
2b63df96 1154
6b7de0bb 1155 foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1156 if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1157 continue;
1158 }
bf0f06b1 1159 $context = context_module::instance($cm->id);
6b7de0bb 1160 $forum = $courseforums[$forumid];
2e945910
AB
1161 $forum->context = $context;
1162 $forum->cm = $cm;
d50704bf 1163
6b7de0bb 1164 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1165 continue;
1166 }
6527b5c2 1167
6b7de0bb 1168 /// group access
1169 if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
2c1bbbc5
MG
1170
1171 $forum->onlygroups = $modinfo->get_groups($cm->groupingid);
1172 $forum->onlygroups[] = -1;
6b7de0bb 1173 }
2b63df96 1174
6b7de0bb 1175 /// hidden timed discussions
1176 $forum->viewhiddentimedposts = true;
1177 if (!empty($CFG->forum_enabletimedposts)) {
1178 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1179 $forum->viewhiddentimedposts = false;
1180 }
1181 }
d50704bf 1182
6b7de0bb 1183 /// qanda access
1184 if ($forum->type == 'qanda'
1185 && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2b63df96 1186
6b7de0bb 1187 // We need to check whether the user has posted in the qanda forum.
1188 $forum->onlydiscussions = array(); // Holds discussion ids for the discussions
1189 // the user is allowed to see in this forum.
1190 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1191 foreach ($discussionspostedin as $d) {
1192 $forum->onlydiscussions[] = $d->id;
d50704bf 1193 }
42fb3c85 1194 }
1195 }
6b7de0bb 1196
1197 $readableforums[$forum->id] = $forum;
42fb3c85 1198 }
6b7de0bb 1199
1200 unset($modinfo);
1201
42fb3c85 1202 } // End foreach $courses
2b63df96 1203
42fb3c85 1204 return $readableforums;
1205}
1206
bbbf2d40 1207/**
1208 * Returns a list of posts found using an array of search terms.
df1ba0f4 1209 *
1210 * @global object
1211 * @global object
1212 * @global object
1213 * @param array $searchterms array of search terms, e.g. word +word -word
1214 * @param int $courseid if 0, we search through the whole site
1215 * @param int $limitfrom
1216 * @param int $limitnum
1217 * @param int &$totalcount
1218 * @param string $extrasql
1219 * @return array|bool Array of posts found or false
42fb3c85 1220 */
2b63df96 1221function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
42fb3c85 1222 &$totalcount, $extrasql='') {
4e445355 1223 global $CFG, $DB, $USER;
42fb3c85 1224 require_once($CFG->libdir.'/searchlib.php');
1225
1226 $forums = forum_get_readable_forums($USER->id, $courseid);
2b63df96 1227
67875aa1 1228 if (count($forums) == 0) {
6b7de0bb 1229 $totalcount = 0;
67875aa1 1230 return false;
1231 }
42fb3c85 1232
098f4337 1233 $now = floor(time() / 60) * 60; // DB Cache Friendly.
6b7de0bb 1234
1235 $fullaccess = array();
1236 $where = array();
4e445355 1237 $params = array();
6b7de0bb 1238
1239 foreach ($forums as $forumid => $forum) {
1240 $select = array();
1241
1242 if (!$forum->viewhiddentimedposts) {
b1d5d015
PS
1243 $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
1244 $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
42fb3c85 1245 }
6b7de0bb 1246
2e945910
AB
1247 $cm = $forum->cm;
1248 $context = $forum->context;
ad9c22aa 1249
1250 if ($forum->type == 'qanda'
1251 && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
6b7de0bb 1252 if (!empty($forum->onlydiscussions)) {
cf717dc2 1253 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
4e445355 1254 $params = array_merge($params, $discussionid_params);
1255 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
d50704bf 1256 } else {
6b7de0bb 1257 $select[] = "p.parent = 0";
d50704bf 1258 }
1259 }
6b7de0bb 1260
1261 if (!empty($forum->onlygroups)) {
cf717dc2 1262 list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
4e445355 1263 $params = array_merge($params, $groupid_params);
1264 $select[] = "d.groupid $groupid_sql";
42fb3c85 1265 }
6b7de0bb 1266
1267 if ($select) {
1268 $selects = implode(" AND ", $select);
b1d5d015
PS
1269 $where[] = "(d.forum = :forum{$forumid} AND $selects)";
1270 $params['forum'.$forumid] = $forumid;
6b7de0bb 1271 } else {
1272 $fullaccess[] = $forumid;
1273 }
1274 }
1275
1276 if ($fullaccess) {
cf717dc2 1277 list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
4e445355 1278 $params = array_merge($params, $fullid_params);
1279 $where[] = "(d.forum $fullid_sql)";
42fb3c85 1280 }
42fb3c85 1281
6b7de0bb 1282 $selectdiscussion = "(".implode(" OR ", $where).")";
42fb3c85 1283
42fb3c85 1284 $messagesearch = '';
1285 $searchstring = '';
2b63df96 1286
42fb3c85 1287 // Need to concat these back together for parser to work.
1288 foreach($searchterms as $searchterm){
1289 if ($searchstring != '') {
1290 $searchstring .= ' ';
1291 }
1292 $searchstring .= $searchterm;
1293 }
1294
1295 // We need to allow quoted strings for the search. The quotes *should* be stripped
1296 // by the parser, but this should be examined carefully for security implications.
1297 $searchstring = str_replace("\\\"","\"",$searchstring);
1298 $parser = new search_parser();
1299 $lexer = new search_lexer($parser);
1300
1301 if ($lexer->parse($searchstring)) {
1302 $parsearray = $parser->get_parsed_array();
d2ba493c
AH
1303
1304 $tagjoins = '';
1305 $tagfields = [];
e9c46768 1306 $tagfieldcount = 0;
d2ba493c
AH
1307 foreach ($parsearray as $token) {
1308 if ($token->getType() == TOKEN_TAGS) {
e9c46768
AH
1309 for ($i = 0; $i <= substr_count($token->getValue(), ','); $i++) {
1310 // Queries can only have a limited number of joins so set a limit sensible users won't exceed.
1311 if ($tagfieldcount > 10) {
1312 continue;
1313 }
1314 $tagjoins .= " LEFT JOIN {tag_instance} ti_$tagfieldcount
1315 ON p.id = ti_$tagfieldcount.itemid
1316 AND ti_$tagfieldcount.component = 'mod_forum'
1317 AND ti_$tagfieldcount.itemtype = 'forum_posts'";
1318 $tagjoins .= " LEFT JOIN {tag} t_$tagfieldcount ON t_$tagfieldcount.id = ti_$tagfieldcount.tagid";
1319 $tagfields[] = "t_$tagfieldcount.rawname";
1320 $tagfieldcount++;
d2ba493c
AH
1321 }
1322 }
1323 }
2f194173
AN
1324 list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
1325 'p.userid', 'u.id', 'u.firstname',
d2ba493c
AH
1326 'u.lastname', 'p.modified', 'd.forum',
1327 $tagfields);
004efe66 1328 $params = array_merge($params, $msparams);
42fb3c85 1329 }
1330
d2ba493c
AH
1331 $fromsql = "{forum_posts} p
1332 INNER JOIN {forum_discussions} d ON d.id = p.discussion
1333 INNER JOIN {user} u ON u.id = p.userid $tagjoins";
42fb3c85 1334
1335 $selectsql = " $messagesearch
1336 AND p.discussion = d.id
1337 AND p.userid = u.id
1338 AND $selectdiscussion
1339 $extrasql";
1340
1341 $countsql = "SELECT COUNT(*)
1342 FROM $fromsql
1343 WHERE $selectsql";
1344
a327f25e 1345 $allnames = get_all_user_name_fields(true, 'u');
7f094149 1346 $searchsql = "SELECT p.*,
42fb3c85 1347 d.forum,
a327f25e 1348 $allnames,
42fb3c85 1349 u.email,
8ba59d07 1350 u.picture,
ce7382c9 1351 u.imagealt
42fb3c85 1352 FROM $fromsql
1353 WHERE $selectsql
1354 ORDER BY p.modified DESC";
1355
4e445355 1356 $totalcount = $DB->count_records_sql($countsql, $params);
d50704bf 1357
4e445355 1358 return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
42fb3c85 1359}
1360
0a4ac01b 1361/**
1362 * Get all the posts for a user in a forum suitable for forum_print_post
df1ba0f4 1363 *
1364 * @global object
1365 * @global object
1366 * @uses CONTEXT_MODULE
1367 * @return array
0a4ac01b 1368 */
1f48942e 1369function forum_get_user_posts($forumid, $userid) {
4e445355 1370 global $CFG, $DB;
1f48942e 1371
90f4745c 1372 $timedsql = "";
4e445355 1373 $params = array($forumid, $userid);
1374
90f4745c 1375 if (!empty($CFG->forum_enabletimedposts)) {
1376 $cm = get_coursemodule_from_instance('forum', $forumid);
bf0f06b1 1377 if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
90f4745c 1378 $now = time();
4e445355 1379 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
1380 $params[] = $now;
1381 $params[] = $now;
6b7de0bb 1382 }
90f4745c 1383 }
1384
a327f25e
AG
1385 $allnames = get_all_user_name_fields(true, 'u');
1386 return $DB->get_records_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
4e445355 1387 FROM {forum} f
1388 JOIN {forum_discussions} d ON d.forum = f.id
1389 JOIN {forum_posts} p ON p.discussion = d.id
1390 JOIN {user} u ON u.id = p.userid
1391 WHERE f.id = ?
1392 AND p.userid = ?
90f4745c 1393 $timedsql
4e445355 1394 ORDER BY p.modified ASC", $params);
1f48942e 1395}
1396
90f4745c 1397/**
1398 * Get all the discussions user participated in
df1ba0f4 1399 *
1400 * @global object
1401 * @global object
1402 * @uses CONTEXT_MODULE
90f4745c 1403 * @param int $forumid
1404 * @param int $userid
df1ba0f4 1405 * @return array Array or false
90f4745c 1406 */
1407function forum_get_user_involved_discussions($forumid, $userid) {
4e445355 1408 global $CFG, $DB;
90f4745c 1409
1410 $timedsql = "";
4e445355 1411 $params = array($forumid, $userid);
90f4745c 1412 if (!empty($CFG->forum_enabletimedposts)) {
1413 $cm = get_coursemodule_from_instance('forum', $forumid);
bf0f06b1 1414 if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
90f4745c 1415 $now = time();
4e445355 1416 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
1417 $params[] = $now;
1418 $params[] = $now;
6b7de0bb 1419 }
90f4745c 1420 }
1421
4e445355 1422 return $DB->get_records_sql("SELECT DISTINCT d.*
1423 FROM {forum} f
1424 JOIN {forum_discussions} d ON d.forum = f.id
1425 JOIN {forum_posts} p ON p.discussion = d.id
1426 WHERE f.id = ?
1427 AND p.userid = ?
1428 $timedsql", $params);
90f4745c 1429}
1430
1431/**
1432 * Get all the posts for a user in a forum suitable for forum_print_post
df1ba0f4 1433 *
1434 * @global object
1435 * @global object
90f4745c 1436 * @param int $forumid
1437 * @param int $userid
1438 * @return array of counts or false
1439 */
1440function forum_count_user_posts($forumid, $userid) {
4e445355 1441 global $CFG, $DB;
90f4745c 1442
1443 $timedsql = "";
4e445355 1444 $params = array($forumid, $userid);
90f4745c 1445 if (!empty($CFG->forum_enabletimedposts)) {
1446 $cm = get_coursemodule_from_instance('forum', $forumid);
bf0f06b1 1447 if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
90f4745c 1448 $now = time();
4e445355 1449 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
1450 $params[] = $now;
1451 $params[] = $now;
6b7de0bb 1452 }
90f4745c 1453 }
1454
4e445355 1455 return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
1456 FROM {forum} f
1457 JOIN {forum_discussions} d ON d.forum = f.id
1458 JOIN {forum_posts} p ON p.discussion = d.id
1459 JOIN {user} u ON u.id = p.userid
1460 WHERE f.id = ?
1461 AND p.userid = ?
1462 $timedsql", $params);
90f4745c 1463}
1464
0a4ac01b 1465/**
1466 * Given a log entry, return the forum post details for it.
df1ba0f4 1467 *
1468 * @global object
1469 * @global object
1470 * @param object $log
1471 * @return array|null
0a4ac01b 1472 */
1f48942e 1473function forum_get_post_from_log($log) {
4e445355 1474 global $CFG, $DB;
1f48942e 1475
a327f25e 1476 $allnames = get_all_user_name_fields(true, 'u');
1f48942e 1477 if ($log->action == "add post") {
1478
a327f25e 1479 return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
4e445355 1480 FROM {forum_discussions} d,
1481 {forum_posts} p,
1482 {forum} f,
1483 {user} u
1484 WHERE p.id = ?
65b0e537 1485 AND d.id = p.discussion
1486 AND p.userid = u.id
8f7dc7f1 1487 AND u.deleted <> '1'
4e445355 1488 AND f.id = d.forum", array($log->info));
1f48942e 1489
1490
1491 } else if ($log->action == "add discussion") {
1492
a327f25e 1493 return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
4e445355 1494 FROM {forum_discussions} d,
1495 {forum_posts} p,
1496 {forum} f,
1497 {user} u
1498 WHERE d.id = ?
65b0e537 1499 AND d.firstpost = p.id
1500 AND p.userid = u.id
8f7dc7f1 1501 AND u.deleted <> '1'
4e445355 1502 AND f.id = d.forum", array($log->info));
1f48942e 1503 }
1504 return NULL;
1505}
1506
0a4ac01b 1507/**
1508 * Given a discussion id, return the first post from the discussion
df1ba0f4 1509 *
1510 * @global object
1511 * @global object
1512 * @param int $dicsussionid
1513 * @return array
0a4ac01b 1514 */
d05956ac 1515function forum_get_firstpost_from_discussion($discussionid) {
4e445355 1516 global $CFG, $DB;
d05956ac 1517
4e445355 1518 return $DB->get_record_sql("SELECT p.*
1519 FROM {forum_discussions} d,
1520 {forum_posts} p
1521 WHERE d.id = ?
1522 AND d.firstpost = p.id ", array($discussionid));
d05956ac 1523}
1524
0a4ac01b 1525/**
90f4745c 1526 * Returns an array of counts of replies to each discussion
df1ba0f4 1527 *
1528 * @global object
1529 * @global object
1530 * @param int $forumid
1531 * @param string $forumsort
1532 * @param int $limit
1533 * @param int $page
1534 * @param int $perpage
1535 * @return array
0a4ac01b 1536 */
90f4745c 1537function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
4e445355 1538 global $CFG, $DB;
1f48942e 1539
90f4745c 1540 if ($limit > 0) {
1541 $limitfrom = 0;
1542 $limitnum = $limit;
1543 } else if ($page != -1) {
1544 $limitfrom = $page*$perpage;
1545 $limitnum = $perpage;
1546 } else {
1547 $limitfrom = 0;
1548 $limitnum = 0;
1549 }
1550
1551 if ($forumsort == "") {
1552 $orderby = "";
1553 $groupby = "";
1554
1555 } else {
1556 $orderby = "ORDER BY $forumsort";
1557 $groupby = ", ".strtolower($forumsort);
1558 $groupby = str_replace('desc', '', $groupby);
1559 $groupby = str_replace('asc', '', $groupby);
1560 }
1561
bfeb10b7 1562 if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
1563 $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
4e445355 1564 FROM {forum_posts} p
1565 JOIN {forum_discussions} d ON p.discussion = d.id
1566 WHERE p.parent > 0 AND d.forum = ?
bfeb10b7 1567 GROUP BY p.discussion";
4e445355 1568 return $DB->get_records_sql($sql, array($forumid));
bfeb10b7 1569
90f4745c 1570 } else {
bfeb10b7 1571 $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
4e445355 1572 FROM {forum_posts} p
1573 JOIN {forum_discussions} d ON p.discussion = d.id
1574 WHERE d.forum = ?
38ec3fb0
AN
1575 GROUP BY p.discussion $groupby $orderby";
1576 return $DB->get_records_sql($sql, array($forumid), $limitfrom, $limitnum);
90f4745c 1577 }
1578}
1579
df1ba0f4 1580/**
1581 * @global object
1582 * @global object
1583 * @global object
1584 * @staticvar array $cache
1585 * @param object $forum
1586 * @param object $cm
1587 * @param object $course
1588 * @return mixed
1589 */
90f4745c 1590function forum_count_discussions($forum, $cm, $course) {
4e445355 1591 global $CFG, $DB, $USER;
90f4745c 1592
1593 static $cache = array();
1594
098f4337 1595 $now = floor(time() / 60) * 60; // DB Cache Friendly.
90f4745c 1596
4e445355 1597 $params = array($course->id);
1598
90f4745c 1599 if (!isset($cache[$course->id])) {
1600 if (!empty($CFG->forum_enabletimedposts)) {
4e445355 1601 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
1602 $params[] = $now;
1603 $params[] = $now;
90f4745c 1604 } else {
1605 $timedsql = "";
1606 }
1607
1608 $sql = "SELECT f.id, COUNT(d.id) as dcount
4e445355 1609 FROM {forum} f
1610 JOIN {forum_discussions} d ON d.forum = f.id
1611 WHERE f.course = ?
90f4745c 1612 $timedsql
1613 GROUP BY f.id";
a48e8c4b 1614
4e445355 1615 if ($counts = $DB->get_records_sql($sql, $params)) {
90f4745c 1616 foreach ($counts as $count) {
1617 $counts[$count->id] = $count->dcount;
1618 }
1619 $cache[$course->id] = $counts;
1620 } else {
1621 $cache[$course->id] = array();
1622 }
a48e8c4b 1623 }
90f4745c 1624
1625 if (empty($cache[$course->id][$forum->id])) {
1626 return 0;
a48e8c4b 1627 }
90f4745c 1628
1629 $groupmode = groups_get_activity_groupmode($cm, $course);
1630
1631 if ($groupmode != SEPARATEGROUPS) {
1632 return $cache[$course->id][$forum->id];
1f48942e 1633 }
90f4745c 1634
bf0f06b1 1635 if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) {
90f4745c 1636 return $cache[$course->id][$forum->id];
1637 }
1638
1639 require_once($CFG->dirroot.'/course/lib.php');
1640
f20edd52 1641 $modinfo = get_fast_modinfo($course);
90f4745c 1642
2c1bbbc5 1643 $mygroups = $modinfo->get_groups($cm->groupingid);
90f4745c 1644
1645 // add all groups posts
2c1bbbc5 1646 $mygroups[-1] = -1;
4e445355 1647
1648 list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
1649 $params[] = $forum->id;
90f4745c 1650
1651 if (!empty($CFG->forum_enabletimedposts)) {
1652 $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
4e445355 1653 $params[] = $now;
1654 $params[] = $now;
90f4745c 1655 } else {
1656 $timedsql = "";
1657 }
1658
1659 $sql = "SELECT COUNT(d.id)
4e445355 1660 FROM {forum_discussions} d
342650a7 1661 WHERE d.groupid $mygroups_sql AND d.forum = ?
90f4745c 1662 $timedsql";
1663
4e445355 1664 return $DB->get_field_sql($sql, $params);
1f48942e 1665}
1666
0a4ac01b 1667/**
1668 * Get all discussions in a forum
df1ba0f4 1669 *
1670 * @global object
1671 * @global object
1672 * @global object
1673 * @uses CONTEXT_MODULE
1674 * @uses VISIBLEGROUPS
1675 * @param object $cm
1676 * @param string $forumsort
1677 * @param bool $fullpost
1678 * @param int $unused
1679 * @param int $limit
1680 * @param bool $userlastmodified
1681 * @param int $page
1682 * @param int $perpage
4f3a2d21
JL
1683 * @param int $groupid if groups enabled, get discussions for this group overriding the current group.
1684 * Use FORUM_POSTS_ALL_USER_GROUPS for all the user groups
65b2669d 1685 * @param int $updatedsince retrieve only discussions updated since the given time
df1ba0f4 1686 * @return array
0a4ac01b 1687 */
1e366657 1688function forum_get_discussions($cm, $forumsort="", $fullpost=true, $unused=-1, $limit=-1,
02a73d76 1689 $userlastmodified=false, $page=-1, $perpage=0, $groupid = -1,
65b2669d 1690 $updatedsince = 0) {
4e445355 1691 global $CFG, $DB, $USER;
0fcac008 1692
3d284127 1693 $timelimit = '';
1694
098f4337 1695 $now = floor(time() / 60) * 60;
4e445355 1696 $params = array($cm->instance);
90f4745c 1697
bf0f06b1 1698 $modcontext = context_module::instance($cm->id);
2b63df96 1699
4436a63b 1700 if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
1701 return array();
1702 }
1703
1704 if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2b63df96 1705
0468976c 1706 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
4e445355 1707 $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
1708 $params[] = $now;
1709 $params[] = $now;
90f4745c 1710 if (isloggedin()) {
4e445355 1711 $timelimit .= " OR d.userid = ?";
1712 $params[] = $USER->id;
3d284127 1713 }
90f4745c 1714 $timelimit .= ")";
fbc21e82 1715 }
fbc21e82 1716 }
1f48942e 1717
90f4745c 1718 if ($limit > 0) {
1719 $limitfrom = 0;
1720 $limitnum = $limit;
1721 } else if ($page != -1) {
1722 $limitfrom = $page*$perpage;
1723 $limitnum = $perpage;
1724 } else {
1725 $limitfrom = 0;
1726 $limitnum = 0;
90ec387a 1727 }
8f0cd6ef 1728
90f4745c 1729 $groupmode = groups_get_activity_groupmode($cm);
353228d8 1730
fffa8b35 1731 if ($groupmode) {
4f3a2d21 1732
fffa8b35 1733 if (empty($modcontext)) {
bf0f06b1 1734 $modcontext = context_module::instance($cm->id);
fffa8b35 1735 }
1736
4f3a2d21
JL
1737 // Special case, we received a groupid to override currentgroup.
1738 if ($groupid > 0) {
1739 $course = get_course($cm->course);
1740 if (!groups_group_visible($groupid, $course, $cm)) {
1741 // User doesn't belong to this group, return nothing.
1742 return array();
1743 }
1744 $currentgroup = $groupid;
1745 } else if ($groupid === -1) {
1746 $currentgroup = groups_get_activity_group($cm);
1747 } else {
1748 // Get discussions for all groups current user can see.
1749 $currentgroup = null;
1750 }
1751
fffa8b35 1752 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
1753 if ($currentgroup) {
4e445355 1754 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
1755 $params[] = $currentgroup;
fffa8b35 1756 } else {
1757 $groupselect = "";
1758 }
1759
1760 } else {
4f3a2d21
JL
1761 // Separate groups.
1762
1763 // Get discussions for all groups current user can see.
1764 if ($currentgroup === null) {
1765 $mygroups = array_keys(groups_get_all_groups($cm->course, $USER->id, $cm->groupingid, 'g.id'));
1766 if (empty($mygroups)) {
1767 $groupselect = "AND d.groupid = -1";
1768 } else {
1769 list($insqlgroups, $inparamsgroups) = $DB->get_in_or_equal($mygroups);
1770 $groupselect = "AND (d.groupid = -1 OR d.groupid $insqlgroups)";
1771 $params = array_merge($params, $inparamsgroups);
1772 }
1773 } else if ($currentgroup) {
4e445355 1774 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
1775 $params[] = $currentgroup;
fffa8b35 1776 } else {
1777 $groupselect = "AND d.groupid = -1";
1778 }
1779 }
353228d8 1780 } else {
02509fe6 1781 $groupselect = "";
1782 }
29507631 1783 if (empty($forumsort)) {
1e366657 1784 $forumsort = forum_get_default_sort_order();
29507631 1785 }
2ab968e9 1786 if (empty($fullpost)) {
94df729b 1787 $postdata = "p.id, p.subject, p.modified, p.discussion, p.userid, p.created";
2ab968e9 1788 } else {
1789 $postdata = "p.*";
1790 }
9197e147 1791
13597d01 1792 if (empty($userlastmodified)) { // We don't need to know this
fffa8b35 1793 $umfields = "";
1794 $umtable = "";
13597d01 1795 } else {
d85bedf7
JL
1796 $umfields = ', ' . get_all_user_name_fields(true, 'um', null, 'um') . ', um.email AS umemail, um.picture AS umpicture,
1797 um.imagealt AS umimagealt';
4e445355 1798 $umtable = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
90f4745c 1799 }
1800
65b2669d
JL
1801 $updatedsincesql = '';
1802 if (!empty($updatedsince)) {
1803 $updatedsincesql = 'AND d.timemodified > ?';
1804 $params[] = $updatedsince;
1805 }
4c8f0f0d 1806 $discussionfields = "d.id as discussionid, d.course, d.forum, d.name, d.firstpost, d.groupid, d.assessed," .
2f6f3397 1807 " d.timemodified, d.usermodified, d.timestart, d.timeend, d.pinned";
65b2669d 1808
a327f25e 1809 $allnames = get_all_user_name_fields(true, 'u');
2f6f3397 1810 $sql = "SELECT $postdata, $discussionfields,
424920f8 1811 $allnames, u.email, u.picture, u.imagealt $umfields
4e445355 1812 FROM {forum_discussions} d
1813 JOIN {forum_posts} p ON p.discussion = d.id
1814 JOIN {user} u ON p.userid = u.id
90f4745c 1815 $umtable
4e445355 1816 WHERE d.forum = ? AND p.parent = 0
65b2669d 1817 $timelimit $groupselect $updatedsincesql
5f219cf1 1818 ORDER BY $forumsort, d.id DESC";
65b2669d 1819
4e445355 1820 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
90f4745c 1821}
1822
8d3a5ba1
FM
1823/**
1824 * Gets the neighbours (previous and next) of a discussion.
1825 *
5f219cf1
BK
1826 * The calculation is based on the timemodified when time modified or time created is identical
1827 * It will revert to using the ID to sort consistently. This is better tha skipping a discussion.
8d3a5ba1 1828 *
9e381efd
DM
1829 * For blog-style forums, the calculation is based on the original creation time of the
1830 * blog post.
1831 *
8d3a5ba1
FM
1832 * Please note that this does not check whether or not the discussion passed is accessible
1833 * by the user, it simply uses it as a reference to find the neighbours. On the other hand,
1834 * the returned neighbours are checked and are accessible to the current user.
1835 *
1836 * @param object $cm The CM record.
1837 * @param object $discussion The discussion record.
9e381efd 1838 * @param object $forum The forum instance record.
8d3a5ba1
FM
1839 * @return array That always contains the keys 'prev' and 'next'. When there is a result
1840 * they contain the record with minimal information such as 'id' and 'name'.
1841 * When the neighbour is not found the value is false.
1842 */
9e381efd 1843function forum_get_discussion_neighbours($cm, $discussion, $forum) {
8d3a5ba1
FM
1844 global $CFG, $DB, $USER;
1845
9e381efd 1846 if ($cm->instance != $discussion->forum or $discussion->forum != $forum->id or $forum->id != $cm->instance) {
8d3a5ba1
FM
1847 throw new coding_exception('Discussion is not part of the same forum.');
1848 }
1849
1850 $neighbours = array('prev' => false, 'next' => false);
098f4337 1851 $now = floor(time() / 60) * 60;
8d3a5ba1
FM
1852 $params = array();
1853
1854 $modcontext = context_module::instance($cm->id);
1855 $groupmode = groups_get_activity_groupmode($cm);
1856 $currentgroup = groups_get_activity_group($cm);
1857
1858 // Users must fulfill timed posts.
1859 $timelimit = '';
1860 if (!empty($CFG->forum_enabletimedposts)) {
1861 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
1862 $timelimit = ' AND ((d.timestart <= :tltimestart AND (d.timeend = 0 OR d.timeend > :tltimeend))';
1863 $params['tltimestart'] = $now;
1864 $params['tltimeend'] = $now;
1865 if (isloggedin()) {
1866 $timelimit .= ' OR d.userid = :tluserid';
1867 $params['tluserid'] = $USER->id;
1868 }
1869 $timelimit .= ')';
1870 }
1871 }
1872
1873 // Limiting to posts accessible according to groups.
1874 $groupselect = '';
1875 if ($groupmode) {
1876 if ($groupmode == VISIBLEGROUPS || has_capability('moodle/site:accessallgroups', $modcontext)) {
1877 if ($currentgroup) {
1878 $groupselect = 'AND (d.groupid = :groupid OR d.groupid = -1)';
1879 $params['groupid'] = $currentgroup;
1880 }
1881 } else {
1882 if ($currentgroup) {
1883 $groupselect = 'AND (d.groupid = :groupid OR d.groupid = -1)';
1884 $params['groupid'] = $currentgroup;
1885 } else {
1886 $groupselect = 'AND d.groupid = -1';
1887 }
1888 }
1889 }
1890
5f219cf1
BK
1891 $params['forumid'] = $cm->instance;
1892 $params['discid1'] = $discussion->id;
1893 $params['discid2'] = $discussion->id;
1894 $params['discid3'] = $discussion->id;
1895 $params['discid4'] = $discussion->id;
1896 $params['disctimecompare1'] = $discussion->timemodified;
1897 $params['disctimecompare2'] = $discussion->timemodified;
1898 $params['pinnedstate1'] = (int) $discussion->pinned;
1899 $params['pinnedstate2'] = (int) $discussion->pinned;
1900 $params['pinnedstate3'] = (int) $discussion->pinned;
1901 $params['pinnedstate4'] = (int) $discussion->pinned;
9e381efd 1902
5f219cf1
BK
1903 $sql = "SELECT d.id, d.name, d.timemodified, d.groupid, d.timestart, d.timeend
1904 FROM {forum_discussions} d
1905 JOIN {forum_posts} p ON d.firstpost = p.id
1906 WHERE d.forum = :forumid
1907 AND d.id <> :discid1
1908 $timelimit
1909 $groupselect";
1910 $comparefield = "d.timemodified";
1911 $comparevalue = ":disctimecompare1";
1912 $comparevalue2 = ":disctimecompare2";
1913 if (!empty($CFG->forum_enabletimedposts)) {
1914 // Here we need to take into account the release time (timestart)
1915 // if one is set, of the neighbouring posts and compare it to the
1916 // timestart or timemodified of *this* post depending on if the
1917 // release date of this post is in the future or not.
1918 // This stops discussions that appear later because of the
1919 // timestart value from being buried under discussions that were
1920 // made afterwards.
1921 $comparefield = "CASE WHEN d.timemodified < d.timestart
1922 THEN d.timestart ELSE d.timemodified END";
1923 if ($discussion->timemodified < $discussion->timestart) {
1924 // Normally we would just use the timemodified for sorting
1925 // discussion posts. However, when timed discussions are enabled,
1926 // then posts need to be sorted base on the later of timemodified
1927 // or the release date of the post (timestart).
1928 $params['disctimecompare1'] = $discussion->timestart;
1929 $params['disctimecompare2'] = $discussion->timestart;
1930 }
1931 }
1932 $orderbydesc = forum_get_default_sort_order(true, $comparefield, 'd', false);
1933 $orderbyasc = forum_get_default_sort_order(false, $comparefield, 'd', false);
9e381efd 1934
5f219cf1
BK
1935 if ($forum->type === 'blog') {
1936 $subselect = "SELECT pp.created
1937 FROM {forum_discussions} dd
1938 JOIN {forum_posts} pp ON dd.firstpost = pp.id ";
8d3a5ba1 1939
5f219cf1
BK
1940 $subselectwhere1 = " WHERE dd.id = :discid3";
1941 $subselectwhere2 = " WHERE dd.id = :discid4";
8d3a5ba1 1942
5f219cf1 1943 $comparefield = "p.created";
9e381efd 1944
5f219cf1
BK
1945 $sub1 = $subselect.$subselectwhere1;
1946 $comparevalue = "($sub1)";
9e381efd 1947
5f219cf1
BK
1948 $sub2 = $subselect.$subselectwhere2;
1949 $comparevalue2 = "($sub2)";
8d3a5ba1 1950
5f219cf1
BK
1951 $orderbydesc = "d.pinned, p.created DESC";
1952 $orderbyasc = "d.pinned, p.created ASC";
1953 }
1e366657 1954
5f219cf1
BK
1955 $prevsql = $sql . " AND ( (($comparefield < $comparevalue) AND :pinnedstate1 = d.pinned)
1956 OR ($comparefield = $comparevalue2 AND (d.pinned = 0 OR d.pinned = :pinnedstate4) AND d.id < :discid2)
1957 OR (d.pinned = 0 AND d.pinned <> :pinnedstate2))
1958 ORDER BY CASE WHEN d.pinned = :pinnedstate3 THEN 1 ELSE 0 END DESC, $orderbydesc, d.id DESC";
9e381efd 1959
5f219cf1
BK
1960 $nextsql = $sql . " AND ( (($comparefield > $comparevalue) AND :pinnedstate1 = d.pinned)
1961 OR ($comparefield = $comparevalue2 AND (d.pinned = 1 OR d.pinned = :pinnedstate4) AND d.id > :discid2)
1962 OR (d.pinned = 1 AND d.pinned <> :pinnedstate2))
1963 ORDER BY CASE WHEN d.pinned = :pinnedstate3 THEN 1 ELSE 0 END DESC, $orderbyasc, d.id ASC";
8d3a5ba1 1964
5f219cf1
BK
1965 $neighbours['prev'] = $DB->get_record_sql($prevsql, $params, IGNORE_MULTIPLE);
1966 $neighbours['next'] = $DB->get_record_sql($nextsql, $params, IGNORE_MULTIPLE);
8d3a5ba1
FM
1967 return $neighbours;
1968}
1969
1e366657
AO
1970/**
1971 * Get the sql to use in the ORDER BY clause for forum discussions.
1972 *
1973 * This has the ordering take timed discussion windows into account.
1974 *
1975 * @param bool $desc True for DESC, False for ASC.
1976 * @param string $compare The field in the SQL to compare to normally sort by.
1977 * @param string $prefix The prefix being used for the discussion table.
5f219cf1 1978 * @param bool $pinned sort pinned posts to the top
1e366657
AO
1979 * @return string
1980 */
5f219cf1 1981function forum_get_default_sort_order($desc = true, $compare = 'd.timemodified', $prefix = 'd', $pinned = true) {
1e366657
AO
1982 global $CFG;
1983
1984 if (!empty($prefix)) {
1985 $prefix .= '.';
1986 }
1987
1988 $dir = $desc ? 'DESC' : 'ASC';
1989
5f219cf1
BK
1990 if ($pinned == true) {
1991 $pinned = "{$prefix}pinned DESC,";
1992 } else {
1993 $pinned = '';
1994 }
1995
1e366657
AO
1996 $sort = "{$prefix}timemodified";
1997 if (!empty($CFG->forum_enabletimedposts)) {
1998 $sort = "CASE WHEN {$compare} < {$prefix}timestart
1999 THEN {$prefix}timestart
2000 ELSE {$compare}
2001 END";
2002 }
5f219cf1 2003 return "$pinned $sort $dir";
1e366657
AO
2004}
2005
df1ba0f4 2006/**
2007 *
2008 * @global object
2009 * @global object
2010 * @global object
2011 * @uses CONTEXT_MODULE
2012 * @uses VISIBLEGROUPS
2013 * @param object $cm
2014 * @return array
2015 */
bfeb10b7 2016function forum_get_discussions_unread($cm) {
4e445355 2017 global $CFG, $DB, $USER;
90f4745c 2018
098f4337 2019 $now = floor(time() / 60) * 60;
ee151230 2020 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
35716b86 2021
ee151230 2022 $params = array();
90f4745c 2023 $groupmode = groups_get_activity_groupmode($cm);
2024 $currentgroup = groups_get_activity_group($cm);
2025
2026 if ($groupmode) {
bf0f06b1 2027 $modcontext = context_module::instance($cm->id);
90f4745c 2028
2029 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2030 if ($currentgroup) {
ee151230
AD
2031 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2032 $params['currentgroup'] = $currentgroup;
90f4745c 2033 } else {
2034 $groupselect = "";
2035 }
2036
2037 } else {
ee151230 2038 //separate groups without access all
90f4745c 2039 if ($currentgroup) {
ee151230
AD
2040 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2041 $params['currentgroup'] = $currentgroup;
90f4745c 2042 } else {
2043 $groupselect = "AND d.groupid = -1";
2044 }
2045 }
2046 } else {
2047 $groupselect = "";
13597d01 2048 }
2049
90f4745c 2050 if (!empty($CFG->forum_enabletimedposts)) {
ee151230
AD
2051 $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2052 $params['now1'] = $now;
2053 $params['now2'] = $now;
90f4745c 2054 } else {
2055 $timedsql = "";
2056 }
2057
2058 $sql = "SELECT d.id, COUNT(p.id) AS unread
4e445355 2059 FROM {forum_discussions} d
2060 JOIN {forum_posts} p ON p.discussion = d.id
2061 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
90f4745c 2062 WHERE d.forum = {$cm->instance}
ee151230 2063 AND p.modified >= :cutoffdate AND r.id is NULL
90f4745c 2064 $groupselect
4e445355 2065 $timedsql
bfeb10b7 2066 GROUP BY d.id";
ee151230
AD
2067 $params['cutoffdate'] = $cutoffdate;
2068
4e445355 2069 if ($unreads = $DB->get_records_sql($sql, $params)) {
90f4745c 2070 foreach ($unreads as $unread) {
2071 $unreads[$unread->id] = $unread->unread;
2072 }
2073 return $unreads;
2074 } else {
2075 return array();
2076 }
1f48942e 2077}
2078
df1ba0f4 2079/**
2080 * @global object
2081 * @global object
2082 * @global object
2083 * @uses CONEXT_MODULE
2084 * @uses VISIBLEGROUPS
2085 * @param object $cm
2086 * @return array
2087 */
90f4745c 2088function forum_get_discussions_count($cm) {
4e445355 2089 global $CFG, $DB, $USER;
90f4745c 2090
098f4337 2091 $now = floor(time() / 60) * 60;
4e445355 2092 $params = array($cm->instance);
90f4745c 2093 $groupmode = groups_get_activity_groupmode($cm);
2094 $currentgroup = groups_get_activity_group($cm);
2095
2096 if ($groupmode) {
bf0f06b1 2097 $modcontext = context_module::instance($cm->id);
90f4745c 2098
2099 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2100 if ($currentgroup) {
4e445355 2101 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2102 $params[] = $currentgroup;
90f4745c 2103 } else {
2104 $groupselect = "";
2105 }
2106
2107 } else {
2108 //seprate groups without access all
2109 if ($currentgroup) {
4e445355 2110 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2111 $params[] = $currentgroup;
90f4745c 2112 } else {
2113 $groupselect = "AND d.groupid = -1";
2114 }
2115 }
2116 } else {
2117 $groupselect = "";
2118 }
2119
90f4745c 2120 $timelimit = "";
2121
2122 if (!empty($CFG->forum_enabletimedposts)) {
2123
bf0f06b1 2124 $modcontext = context_module::instance($cm->id);
90f4745c 2125
2126 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
4e445355 2127 $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2128 $params[] = $now;
2129 $params[] = $now;
90f4745c 2130 if (isloggedin()) {
4e445355 2131 $timelimit .= " OR d.userid = ?";
2132 $params[] = $USER->id;
90f4745c 2133 }
2134 $timelimit .= ")";
2135 }
2136 }
2137
2138 $sql = "SELECT COUNT(d.id)
4e445355 2139 FROM {forum_discussions} d
2140 JOIN {forum_posts} p ON p.discussion = d.id
2141 WHERE d.forum = ? AND p.parent = 0
2142 $groupselect $timelimit";
90f4745c 2143
4e445355 2144 return $DB->get_field_sql($sql, $params);
90f4745c 2145}
1f48942e 2146
2147
0a4ac01b 2148// OTHER FUNCTIONS ///////////////////////////////////////////////////////////
f93f848a 2149
2150
df1ba0f4 2151/**
2152 * @global object
2153 * @global object
2154 * @param int $courseid
2155 * @param string $type
2156 */
11b0c469 2157function forum_get_course_forum($courseid, $type) {
2158// How to set up special 1-per-course forums
c112bc60 2159 global $CFG, $DB, $OUTPUT, $USER;
a6fcdf98 2160
4e445355 2161 if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
65b0e537 2162 // There should always only be ONE, but with the right combination of
29cbd93a 2163 // errors there might be more. In this case, just return the oldest one (lowest ID).
2164 foreach ($forums as $forum) {
2165 return $forum; // ie the first one
11b0c469 2166 }
8daaf761 2167 }
e6874d9f 2168
8daaf761 2169 // Doesn't exist, so create one now.
b85b25eb 2170 $forum = new stdClass();
8daaf761 2171 $forum->course = $courseid;
2172 $forum->type = "$type";
c112bc60
ARN
2173 if (!empty($USER->htmleditor)) {
2174 $forum->introformat = $USER->htmleditor;
2175 }
8daaf761 2176 switch ($forum->type) {
2177 case "news":
294ce987 2178 $forum->name = get_string("namenews", "forum");
2179 $forum->intro = get_string("intronews", "forum");
490476a1 2180 $forum->introformat = FORMAT_HTML;
906fef94 2181 $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
8daaf761 2182 $forum->assessed = 0;
709f0ec8 2183 if ($courseid == SITEID) {
2184 $forum->name = get_string("sitenews");
2185 $forum->forcesubscribe = 0;
8f0cd6ef 2186 }
8daaf761 2187 break;
2188 case "social":
294ce987 2189 $forum->name = get_string("namesocial", "forum");
2190 $forum->intro = get_string("introsocial", "forum");
490476a1 2191 $forum->introformat = FORMAT_HTML;
8daaf761 2192 $forum->assessed = 0;
2193 $forum->forcesubscribe = 0;
2194 break;
1c7b8b93
NC
2195 case "blog":
2196 $forum->name = get_string('blogforum', 'forum');
2197 $forum->intro = get_string('introblog', 'forum');
490476a1 2198 $forum->introformat = FORMAT_HTML;
1c7b8b93
NC
2199 $forum->assessed = 0;
2200 $forum->forcesubscribe = 0;
2201 break;
8daaf761 2202 default:
9146b979 2203 echo $OUTPUT->notification("That forum type doesn't exist!");
8daaf761 2204 return false;
2205 break;
2206 }
2207
2208 $forum->timemodified = time();
4e445355 2209 $forum->id = $DB->insert_record("forum", $forum);
8daaf761 2210
4e445355 2211 if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
9146b979 2212 echo $OUTPUT->notification("Could not find forum module!!");
e1b5643f 2213 return false;
82aa0e8d 2214 }
39790bd8 2215 $mod = new stdClass();
e1b5643f 2216 $mod->course = $courseid;
2217 $mod->module = $module->id;
2218 $mod->instance = $forum->id;
2219 $mod->section = 0;
722e6ba9
MG
2220 include_once("$CFG->dirroot/course/lib.php");
2221 if (! $mod->coursemodule = add_course_module($mod) ) {
11cd754e 2222 echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
e1b5643f 2223 return false;
2224 }
722e6ba9 2225 $sectionid = course_add_cm_to_section($courseid, $mod->coursemodule, 0);
4e445355 2226 return $DB->get_record("forum", array("id" => "$forum->id"));
82aa0e8d 2227}
2228
d251b259
AD
2229/**
2230 * Return rating related permissions
2b04c41c 2231 *
d251b259
AD
2232 * @param string $options the context id
2233 * @return array an associative array of the user's rating permissions
2234 */
2b04c41c 2235function forum_rating_permissions($contextid, $component, $ratingarea) {
d197ea43 2236 $context = context::instance_by_id($contextid, MUST_EXIST);
2b04c41c
SH
2237 if ($component != 'mod_forum' || $ratingarea != 'post') {
2238 // We don't know about this component/ratingarea so just return null to get the
2239 // default restrictive permissions.
d251b259 2240 return null;
d251b259 2241 }
2b04c41c
SH
2242 return array(
2243 'view' => has_capability('mod/forum:viewrating', $context),
2244 'viewany' => has_capability('mod/forum:viewanyrating', $context),
2245 'viewall' => has_capability('mod/forum:viewallratings', $context),
2246 'rate' => has_capability('mod/forum:rate', $context)
2247 );
d251b259
AD
2248}
2249
aeafd436 2250/**
2c2ff8d5
AD
2251 * Validates a submitted rating
2252 * @param array $params submitted data
2253 * context => object the context in which the rated items exists [required]
2b04c41c
SH
2254 * component => The component for this module - should always be mod_forum [required]
2255 * ratingarea => object the context in which the rated items exists [required]
db4754e9 2256 *
2c2ff8d5
AD
2257 * itemid => int the ID of the object being rated [required]
2258 * scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
2259 * rating => int the submitted rating [required]
2260 * rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
2261 * aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
778361c3 2262 * @return boolean true if the rating is valid. Will throw rating_exception if not
aeafd436 2263 */
778361c3 2264function forum_rating_validate($params) {
2c2ff8d5
AD
2265 global $DB, $USER;
2266
2b04c41c
SH
2267 // Check the component is mod_forum
2268 if ($params['component'] != 'mod_forum') {
2269 throw new rating_exception('invalidcomponent');
2c2ff8d5
AD
2270 }
2271
2b04c41c
SH
2272 // Check the ratingarea is post (the only rating area in forum)
2273 if ($params['ratingarea'] != 'post') {
2274 throw new rating_exception('invalidratingarea');
2c2ff8d5
AD
2275 }
2276
2b04c41c
SH
2277 // Check the rateduserid is not the current user .. you can't rate your own posts
2278 if ($params['rateduserid'] == $USER->id) {
778361c3
AD
2279 throw new rating_exception('nopermissiontorate');
2280 }
2281
2b04c41c
SH
2282 // Fetch all the related records ... we need to do this anyway to call forum_user_can_see_post
2283 $post = $DB->get_record('forum_posts', array('id' => $params['itemid'], 'userid' => $params['rateduserid']), '*', MUST_EXIST);
2284 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion), '*', MUST_EXIST);
2285 $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
74df2951 2286 $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
2b04c41c 2287 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id , false, MUST_EXIST);
bf0f06b1 2288 $context = context_module::instance($cm->id);
2b04c41c
SH
2289
2290 // Make sure the context provided is the context of the forum
2291 if ($context->id != $params['context']->id) {
2292 throw new rating_exception('invalidcontext');
2293 }
f2e72593 2294
2b04c41c
SH
2295 if ($forum->scale != $params['scaleid']) {
2296 //the scale being submitted doesnt match the one in the database
2297 throw new rating_exception('invalidscaleid');
2c2ff8d5
AD
2298 }
2299
2b04c41c
SH
2300 // check the item we're rating was created in the assessable time window
2301 if (!empty($forum->assesstimestart) && !empty($forum->assesstimefinish)) {
2302 if ($post->created < $forum->assesstimestart || $post->created > $forum->assesstimefinish) {
2303 throw new rating_exception('notavailable');
2304 }
2305 }
f2e72593 2306
6ac149dc 2307 //check that the submitted rating is valid for the scale
5693d56c
EL
2308
2309 // lower limit
2310 if ($params['rating'] < 0 && $params['rating'] != RATING_UNSET_RATING) {
6ac149dc 2311 throw new rating_exception('invalidnum');
5693d56c
EL
2312 }
2313
2314 // upper limit
c4cee99c 2315 if ($forum->scale < 0) {
6ac149dc 2316 //its a custom scale
2b04c41c 2317 $scalerecord = $DB->get_record('scale', array('id' => -$forum->scale));
6ac149dc
AD
2318 if ($scalerecord) {
2319 $scalearray = explode(',', $scalerecord->scale);
2320 if ($params['rating'] > count($scalearray)) {
2321 throw new rating_exception('invalidnum');
2322 }
2323 } else {
2324 throw new rating_exception('invalidscaleid');
2325 }
2b04c41c 2326 } else if ($params['rating'] > $forum->scale) {
6ac149dc
AD
2327 //if its numeric and submitted rating is above maximum
2328 throw new rating_exception('invalidnum');
2329 }
2330
2c2ff8d5 2331 // Make sure groups allow this user to see the item they're rating
2b04c41c
SH
2332 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
2333 if (!groups_group_exists($discussion->groupid)) { // Can't find group
778361c3 2334 throw new rating_exception('cannotfindgroup');//something is wrong
2c2ff8d5
AD
2335 }
2336
2b04c41c 2337 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
2c2ff8d5 2338 // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
778361c3 2339 throw new rating_exception('notmemberofgroup');
2c2ff8d5
AD
2340 }
2341 }
2342
2b04c41c
SH
2343 // perform some final capability checks
2344 if (!forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) {
778361c3 2345 throw new rating_exception('nopermissiontorate');
2c2ff8d5
AD
2346 }
2347
2348 return true;
aeafd436
AD
2349}
2350
731c2712
AA
2351/**
2352 * Can the current user see ratings for a given itemid?
2353 *
2354 * @param array $params submitted data
2355 * contextid => int contextid [required]
2356 * component => The component for this module - should always be mod_forum [required]
2357 * ratingarea => object the context in which the rated items exists [required]
2358 * itemid => int the ID of the object being rated [required]
2359 * scaleid => int scale id [optional]
2360 * @return bool
2361 * @throws coding_exception
2362 * @throws rating_exception
2363 */
2364function mod_forum_rating_can_see_item_ratings($params) {
2365 global $DB, $USER;
2366
2367 // Check the component is mod_forum.
2368 if (!isset($params['component']) || $params['component'] != 'mod_forum') {
2369 throw new rating_exception('invalidcomponent');
2370 }
2371
2372 // Check the ratingarea is post (the only rating area in forum).
2373 if (!isset($params['ratingarea']) || $params['ratingarea'] != 'post') {
2374 throw new rating_exception('invalidratingarea');
2375 }
2376
2377 if (!isset($params['itemid'])) {
2378 throw new rating_exception('invaliditemid');
2379 }
2380
2381 $post = $DB->get_record('forum_posts', array('id' => $params['itemid']), '*', MUST_EXIST);
2382 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion), '*', MUST_EXIST);
2383 $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
2384 $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
2385 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id , false, MUST_EXIST);
2386
2387 // Perform some final capability checks.
2388 if (!forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) {
2389 return false;
2390 }
3e95e09b 2391
731c2712
AA
2392 return true;
2393}
42fb3c85 2394
eaf50aef 2395/**
0a4ac01b 2396 * This function prints the overview of a discussion in the forum listing.
2397 * It needs some discussion information and some post information, these
2398 * happen to be combined for efficiency in the $post parameter by the function
2399 * that calls this one: forum_print_latest_discussions()
2400 *
df1ba0f4 2401 * @global object
2402 * @global object
0a4ac01b 2403 * @param object $post The post object (passed by reference for speed).
2404 * @param object $forum The forum object.
2405 * @param int $group Current group.
2406 * @param string $datestring Format to use for the dates.
2407 * @param boolean $cantrack Is tracking enabled for this forum.
2408 * @param boolean $forumtracked Is the user tracking this forum.
2409 * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
05f1455b 2410 * @param boolean $canviewhiddentimedposts True if user has the viewhiddentimedposts permission for this forum
0a4ac01b 2411 */
05f1455b
AO
2412function forum_print_discussion_header(&$post, $forum, $group = -1, $datestring = "",
2413 $cantrack = true, $forumtracked = true, $canviewparticipants = true, $modcontext = null,
2414 $canviewhiddentimedposts = false) {
43921b8a 2415
05f1455b 2416 global $COURSE, $USER, $CFG, $OUTPUT, $PAGE;
3335f6fb 2417
f51e8d7e 2418 static $rowcount;
5733262d 2419 static $strmarkalldread;
f51e8d7e 2420
2c894bb9 2421 if (empty($modcontext)) {
2422 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
12e57b92 2423 print_error('invalidcoursemodule');
2c894bb9 2424 }
bf0f06b1 2425 $modcontext = context_module::instance($cm->id);
951e1073 2426 }
951e1073 2427
f51e8d7e 2428 if (!isset($rowcount)) {
2429 $rowcount = 0;
5733262d 2430 $strmarkalldread = get_string('markalldread', 'forum');
f51e8d7e 2431 } else {
2432 $rowcount = ($rowcount + 1) % 2;
2433 }
2434
17dc3f3c 2435 $post->subject = format_string($post->subject,true);
436a7cae 2436
26cee437 2437 $canviewfullnames = has_capability('moodle/site:viewfullnames', $modcontext);
05f1455b
AO
2438 $timeddiscussion = !empty($CFG->forum_enabletimedposts) && ($post->timestart || $post->timeend);
2439 $timedoutsidewindow = '';
2440 if ($timeddiscussion && ($post->timestart > time() || ($post->timeend != 0 && $post->timeend < time()))) {
2441 $timedoutsidewindow = ' dimmed_text';
2442 }
2443
f51e8d7e 2444 echo "\n\n";
05f1455b 2445 echo '<tr class="discussion r'.$rowcount.$timedoutsidewindow.'">';
3335f6fb 2446
87b007b4
CF
2447 $topicclass = 'topic starter';
2448 if (FORUM_DISCUSSION_PINNED == $post->pinned) {
2449 $topicclass .= ' pinned';
2450 }
2451 echo '<td class="'.$topicclass.'">';
5f219cf1
BK
2452 if (FORUM_DISCUSSION_PINNED == $post->pinned) {
2453 echo $OUTPUT->pix_icon('i/pinned', get_string('discussionpinned', 'forum'), 'mod_forum');
2454 }
05f1455b
AO
2455 $canalwaysseetimedpost = $USER->id == $post->userid || $canviewhiddentimedposts;
2456 if ($timeddiscussion && $canalwaysseetimedpost) {
2457 echo $PAGE->get_renderer('mod_forum')->timed_discussion_tooltip($post, empty($timedoutsidewindow));
2458 }
2459
f51e8d7e 2460 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
29507631 2461 echo "</td>\n";
2462
2463 // Picture
39790bd8 2464 $postuser = new stdClass();
e351bed5 2465 $postuserfields = explode(',', user_picture::fields());
5b1944bb 2466 $postuser = username_load_fields_from_object($postuser, $post, null, $postuserfields);
e351bed5 2467 $postuser->id = $post->userid;
0433d36d 2468 echo '<td class="author">';
b1a74e40 2469 echo '<div class="media">';
5db1ce53 2470 echo '<span class="float-left">';
812dbaf7 2471 echo $OUTPUT->user_picture($postuser, array('courseid'=>$forum->course));
0433d36d 2472 echo '</span>';
29507631 2473 // User name
b1a74e40 2474 echo '<div class="media-body">';
26cee437 2475 $fullname = fullname($postuser, $canviewfullnames);
f51e8d7e 2476 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
b1a74e40
BB
2477 echo '</div>';
2478 echo '</div>';
29507631 2479 echo "</td>\n";
2480
3a68fbbb 2481 // Group picture
425b4f1a 2482 if ($group !== -1) { // Groups are active - group is a group data object or NULL
f51e8d7e 2483 echo '<td class="picture group">';
c2b552fe 2484 if (!empty($group->picture) and empty($group->hidepicture)) {
800fa051
DM
2485 if ($canviewparticipants && $COURSE->groupmode) {
2486 $picturelink = true;
2487 } else {
2488 $picturelink = false;
2489 }
2490 print_group_picture($group, $forum->course, false, false, $picturelink);
3a68fbbb 2491 } else if (isset($group->id)) {
c980dd12 2492 if ($canviewparticipants && $COURSE->groupmode) {
18d6ef01 2493 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
2494 } else {
2495 echo $group->name;
2496 }
3a68fbbb 2497 }
2498 echo "</td>\n";
2499 }
2500
f0da6b85 2501 if (has_capability('mod/forum:viewdiscussion', $modcontext)) { // Show the column with replies
f51e8d7e 2502 echo '<td class="replies">';
2503 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
2504 echo $post->replies.'</a>';
a796d0b8 2505 echo "</td>\n";
e3ff14ca 2506
eaf50aef 2507 if ($cantrack) {
f51e8d7e 2508 echo '<td class="replies">';
eaf50aef 2509 if ($forumtracked) {
2510 if ($post->unread > 0) {
2511 echo '<span class="unread">';
2512 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
2513 echo $post->unread;
2514 echo '</a>';
c39748f4 2515 echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
2e19ca18
RW
2516 $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;return=/mod/forum/view.php&amp;sesskey=' .
2517 sesskey() . '">' . $OUTPUT->pix_icon('t/markasread', $strmarkalldread) . '</a>';
eaf50aef 2518 echo '</span>';
2519 } else {
2520 echo '<span class="read">';
eaf50aef 2521 echo $post->unread;
eaf50aef 2522 echo '</span>';
2523 }
f51e8d7e 2524 } else {
343af274 2525 echo '<span class="read">';
eaf50aef 2526 echo '-';
343af274 2527 echo '</span>';
3a68fbbb 2528 }
f37da850 2529 echo "</td>\n";
2530 }
a796d0b8 2531 }
3335f6fb 2532
f51e8d7e 2533 echo '<td class="lastpost">';
014fe0ad 2534 $usedate = (empty($post->timemodified)) ? $post->created : $post->timemodified;
8f2e8060 2535 $parenturl = '';
39790bd8 2536 $usermodified = new stdClass();
e351bed5
AG
2537 $usermodified->id = $post->usermodified;
2538 $usermodified = username_load_fields_from_object($usermodified, $post, 'um');
8f2e8060 2539
cbebd40d
DM
2540 // In QA forums we check that the user can view participants.
2541 if ($forum->type !== 'qanda' || $canviewparticipants) {
b53b69c1 2542 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
26cee437 2543 fullname($usermodified, $canviewfullnames).'</a><br />';
8f2e8060 2544 $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
b53b69c1 2545 }
8f2e8060 2546
ac00b904 2547 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
1bf3a76a 2548 userdate_htmltime($usedate, $datestring).'</a>';
29507631 2549 echo "</td>\n";
2550
ddb4a31d
AN
2551 // is_guest should be used here as this also checks whether the user is a guest in the current course.
2552 // Guests and visitors cannot subscribe - only enrolled users.
2553 if ((!is_guest($modcontext, $USER) && isloggedin()) && has_capability('mod/forum:viewdiscussion', $modcontext)) {
ebfb73db
AN
2554 // Discussion subscription.
2555 if (\mod_forum\subscriptions::is_subscribable($forum)) {
2556 echo '<td class="discussionsubscription">';
2557 echo forum_get_discussion_subscription_icon($forum, $post->discussion);
2558 echo '</td>';
2559 }
2560 }
2561
f51e8d7e 2562 echo "</tr>\n\n";
3335f6fb 2563
3335f6fb 2564}
2565
ebfb73db
AN
2566/**
2567 * Return the markup for the discussion subscription toggling icon.
2568 *
2569 * @param stdClass $forum The forum object.
2570 * @param int $discussionid The discussion to create an icon for.
2571 * @return string The generated markup.
2572 */
517e1782 2573function forum_get_discussion_subscription_icon($forum, $discussionid, $returnurl = null, $includetext = false) {
ebfb73db
AN
2574 global $USER, $OUTPUT, $PAGE;
2575
2576 if ($returnurl === null && $PAGE->url) {
2577 $returnurl = $PAGE->url->out();
2578 }
2579
2580 $o = '';
2581 $subscriptionstatus = \mod_forum\subscriptions::is_subscribed($USER->id, $forum, $discussionid);
2582 $subscriptionlink = new moodle_url('/mod/forum/subscribe.php', array(
2583 'sesskey' => sesskey(),
2584 'id' => $forum->id,
2585 'd' => $discussionid,
2586 'returnurl' => $returnurl,
2587 ));
a35ce611
AN
2588
2589 if ($includetext) {
2590 $o .= $subscriptionstatus ? get_string('subscribed', 'mod_forum') : get_string('notsubscribed', 'mod_forum');
2591 }
2592
ebfb73db 2593 if ($subscriptionstatus) {
a35ce611
AN
2594 $output = $OUTPUT->pix_icon('t/subscribed', get_string('clicktounsubscribe', 'forum'), 'mod_forum');
2595 if ($includetext) {
2596 $output .= get_string('subscribed', 'mod_forum');
2597 }
2598
2599 return html_writer::link($subscriptionlink, $output, array(
ebfb73db
AN
2600 'title' => get_string('clicktounsubscribe', 'forum'),
2601 'class' => 'discussiontoggle iconsmall',
2602 'data-forumid' => $forum->id,
2603 'data-discussionid' => $discussionid,
79b4fb12 2604 'data-includetext' => $includetext,
a35ce611
AN
2605 ));
2606
ebfb73db 2607 } else {
a35ce611
AN
2608 $output = $OUTPUT->pix_icon('t/unsubscribed', get_string('clicktosubscribe', 'forum'), 'mod_forum');
2609 if ($includetext) {
2610 $output .= get_string('notsubscribed', 'mod_forum');
2611 }
2612
2613 return html_writer::link($subscriptionlink, $output, array(
ebfb73db
AN
2614 'title' => get_string('clicktosubscribe', 'forum'),
2615 'class' => 'discussiontoggle iconsmall',
2616 'data-forumid' => $forum->id,
2617 'data-discussionid' => $discussionid,
79b4fb12 2618 'data-includetext' => $includetext,
a35ce611 2619 ));
ebfb73db 2620 }
ebfb73db
AN
2621}
2622
548453ec
AN
2623/**
2624 * Return a pair of spans containing classes to allow the subscribe and
2625 * unsubscribe icons to be pre-loaded by a browser.
2626 *
2627 * @return string The generated markup
2628 */
2629function forum_get_discussion_subscription_icon_preloaders() {
2630 $o = '';
2631 $o .= html_writer::span('&nbsp;', 'preload-subscribe');
2632 $o .= html_writer::span('&nbsp;', 'preload-unsubscribe');
2633 return $o;
2634}
2635
83da3d28 2636/**
2637 * Print the drop down that allows the user to select how they want to have
2638 * the discussion displayed.
df1ba0f4 2639 *
2640 * @param int $id forum id if $forumtype is 'single',
83da3d28 2641 * discussion id for any other forum type
df1ba0f4 2642 * @param mixed $mode forum layout mode
2643 * @param string $forumtype optional
83da3d28 2644 */
2645function forum_print_mode_form($id, $mode, $forumtype='') {
9fc666e5 2646 global $OUTPUT;
83da3d28 2647 if ($forumtype == 'single') {
8295918f 2648 $select = new single_select(new moodle_url("/mod/forum/view.php", array('f'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
d1881bec 2649 $select->set_label(get_string('displaymode', 'forum'), array('class' => 'accesshide'));
f8dab966 2650 $select->class = "forummode";
8295918f
MD
2651 } else {
2652 $select = new single_select(new moodle_url("/mod/forum/discuss.php", array('d'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
d1881bec 2653 $select->set_label(get_string('displaymode', 'forum'), array('class' => 'accesshide'));
83da3d28 2654 }
f8dab966 2655 echo $OUTPUT->render($select);
501cdbd8 2656}
2657
0a4ac01b 2658/**
df1ba0f4 2659 * @global object
2660 * @param object $course
2661 * @param string $search
2662 * @return string
0a4ac01b 2663 */
6f1cc8d6 2664function forum_search_form($course, $search='') {
66bb9ead
FM
2665 global $CFG, $PAGE;
2666 $forumsearch = new \mod_forum\output\quick_search_form($course->id, $search);
2667 $output = $PAGE->get_renderer('mod_forum');
2668 return $output->render($forumsearch);
501cdbd8 2669}
2670
2671
0a4ac01b 2672/**
df1ba0f4 2673 * @global object
2674 * @global object
0a4ac01b 2675 */
11b0c469 2676function forum_set_return() {
607809b3 2677 global $CFG, $SESSION;
501cdbd8 2678
28e1e8b9 2679 if (! isset($SESSION->fromdiscussion)) {
dcee0b94 2680 $referer = get_local_referer(false);
28e1e8b9 2681 // If the referer is NOT a login screen then save it.
48d38fad 2682 if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) {
b2687a05 2683 $SESSION->fromdiscussion = $referer;
28e1e8b9 2684 }
501cdbd8 2685 }
2686}
2687
2688
0a4ac01b 2689/**
df1ba0f4 2690 * @global object
3d8d8a8c 2691 * @param string|\moodle_url $default
df1ba0f4 2692 * @return string
0a4ac01b 2693 */
11b0c469 2694function forum_go_back_to($default) {
501cdbd8 2695 global $SESSION;
2696
9c9f7d77 2697 if (!empty($SESSION->fromdiscussion)) {
501cdbd8 2698 $returnto = $SESSION->fromdiscussion;
2699 unset($SESSION->fromdiscussion);
2700 return $returnto;
2701 } else {
2702 return $default;
2703 }
2704}
2705
0a4ac01b 2706/**
0faf6791 2707 * Given a discussion object that is being moved to $forumto,
2708 * this function checks all posts in that discussion
2709 * for attachments, and if any are found, these are
2710 * moved to the new forum directory.
2711 *
df1ba0f4 2712 * @global object
2713 * @param object $discussion
0faf6791 2714 * @param int $forumfrom source forum id
2715 * @param int $forumto target forum id
2716 * @return bool success
0a4ac01b 2717 */
0faf6791 2718function forum_move_attachments($discussion, $forumfrom, $forumto) {
2719 global $DB;
65bcf17b 2720
0faf6791 2721 $fs = get_file_storage();
7f6689e4 2722
0faf6791 2723 $newcm = get_coursemodule_from_instance('forum', $forumto);
2724 $oldcm = get_coursemodule_from_instance('forum', $forumfrom);
7f6689e4 2725
bf0f06b1
AA
2726 $newcontext = context_module::instance($newcm->id);
2727 $oldcontext = context_module::instance($oldcm->id);
7f6689e4 2728
0faf6791 2729 // loop through all posts, better not use attachment flag ;-)
1f0e7702 2730 if ($posts = $DB->get_records('forum_posts', array('discussion'=>$discussion->id), '', 'id, attachment')) {
0faf6791 2731 foreach ($posts as $post) {
d2af1014
TH
2732 $fs->move_area_files_to_new_context($oldcontext->id,
2733 $newcontext->id, 'mod_forum', 'post', $post->id);
2734 $attachmentsmoved = $fs->move_area_files_to_new_context($oldcontext->id,
2735 $newcontext->id, 'mod_forum', 'attachment', $post->id);
2736 if ($attachmentsmoved > 0 && $post->attachment != '1') {
2737 // Weird - let's fix it
2738 $post->attachment = '1';
2739 $DB->update_record('forum_posts', $post);
2740 } else if ($attachmentsmoved == 0 && $post->attachment != '') {
2741 // Weird - let's fix it
2742 $post->attachment = '';
2743 $DB->update_record('forum_posts', $post);
7f6689e4 2744 }
2745 }
7f6689e4 2746 }
0faf6791 2747
2748 return true;
7f6689e4 2749}
2750
0a4ac01b 2751/**
0faf6791 2752 * Returns attachments as formated text/html optionally with separate images
df1ba0f4 2753 *
2754 * @global object
2755 * @global object
2756 * @global object
0faf6791 2757 * @param object $post
2758 * @param object $cm
df1ba0f4 2759 * @param string $type html/text/separateimages
0faf6791 2760 * @return mixed string or array of (html text withouth images and image HTML)
0a4ac01b 2761 */
0faf6791 2762function forum_print_attachments($post, $cm, $type) {
d436d197 2763 global $CFG, $DB, $USER, $OUTPUT;
cc2b7ea5 2764
0faf6791 2765 if (empty($post->attachment)) {
2766 return $type !== 'separateimages' ? '' : array('', '');
cc2b7ea5 2767 }
cc2b7ea5 2768
0faf6791 2769 if (!in_array($type, array('separateimages', 'html', 'text'))) {
2770 return $type !== 'separateimages' ? '' : array('', '');
2771 }
7f6689e4 2772
bf0f06b1 2773 if (!$context = context_module::instance($cm->id)) {
0faf6791 2774 return $type !== 'separateimages' ? '' : array('', '');
2775 }
2776 $strattachment = get_string('attachment', 'forum');
7f6689e4 2777
0faf6791 2778 $fs = get_file_storage();
7f6689e4 2779
0faf6791 2780 $imagereturn = '';
2781 $output = '';
72d497d4 2782
b8d3c3f7 2783 $canexport = !empty($CFG->enableportfolios) && (has_capability('mod/forum:exportpost', $context) || ($post->userid == $USER->id && has_capability('mod/forum:exportownpost', $context)));
7f6689e4 2784
b8d3c3f7 2785 if ($canexport) {
6708a1f5 2786 require_once($CFG->libdir.'/portfoliolib.php');
b8d3c3f7
SH
2787 }
2788
cc8bd704
AN
2789 // We retrieve all files according to the time that they were created. In the case that several files were uploaded
2790 // at the sametime (e.g. in the case of drag/drop upload) we revert to using the filename.
2791 $files = $fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id, "filename", false);
b8d3c3f7
SH
2792 if ($files) {
2793 if ($canexport) {
6708a1f5 2794 $button = new portfolio_add_button();
b8d3c3f7
SH
2795 }
2796 foreach ($files as $file) {
2797 $filename = $file->get_filename();
2798 $mimetype = $file->get_mimetype();
559276b1 2799 $iconimage = $OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file), 'moodle', array('class' => 'icon'));
b8d3c3f7
SH
2800 $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$context->id.'/mod_forum/attachment/'.$post->id.'/'.$filename);
2801
2802 if ($type == 'html') {
2803 $output .= "<a href=\"$path\">$iconimage</a> ";
2804 $output .= "<a href=\"$path\">".s($filename)."</a>";
2805 if ($canexport) {
37743241 2806 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
b8d3c3f7
SH
2807 $button->set_format_by_file($file);
2808 $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
2809 }
2810 $output .= "<br />";
6708a1f5 2811
b8d3c3f7
SH
2812 } else if ($type == 'text') {
2813 $output .= "$strattachment ".s($filename).":\n$path\n";
2814
2815 } else { //'returnimages'
2816 if (in_array($mimetype, array('image/gif', 'image/jpeg', 'image/png'))) {
2817 // Image attachments don't get printed as links
2818 $imagereturn .= "<br /><img src=\"$path\" alt=\"\" />";
2819 if ($canexport) {
37743241 2820 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
b8d3c3f7
SH
2821 $button->set_format_by_file($file);
2822 $imagereturn .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
2823 }
2824 } else {
0faf6791 2825 $output .= "<a href=\"$path\">$iconimage</a> ";
b8d3c3f7 2826 $output .= format_text("<a href=\"$path\">".s($filename)."</a>", FORMAT_HTML, array('context'=>$context));
f98b13a6 2827 if ($canexport) {
37743241 2828 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
887160c7 2829 $button->set_format_by_file($file);
0d06b6fd 2830 $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
10ae55f9 2831 }
b8d3c3f7 2832 $output .= '<br />';
7f6689e4 2833 }
2834 }
103e7cba
KG
2835
2836 if (!empty($CFG->enableplagiarism)) {
2837 require_once($CFG->libdir.'/plagiarismlib.php');
2838 $output .= plagiarism_get_links(array('userid' => $post->userid,
2839 'file' => $file,
2840 'cmid' => $cm->id,
334bee5f
CC
2841 'course' => $cm->course,
2842 'forum' => $cm->instance));
103e7cba
KG
2843 $output .= '<br />';
2844 }
7f6689e4 2845 }
2846 }
72d497d4 2847
0faf6791 2848 if ($type !== 'separateimages') {
7f6689e4 2849 return $output;
0faf6791 2850
2851 } else {
2852 return array($output, $imagereturn);
2853 }
2854}
2855
9a1c0751
DM
2856////////////////////////////////////////////////////////////////////////////////
2857// File API //
2858////////////////////////////////////////////////////////////////////////////////
2859
d6f7cbfd 2860/**
2861 * Lists all browsable file areas
df1ba0f4 2862 *
d2b7803e
DC
2863 * @package mod_forum
2864 * @category files
2865 * @param stdClass $course course object
2866 * @param stdClass $cm course module object
2867 * @param stdClass $context context object
df1ba0f4 2868 * @return array
d6f7cbfd 2869 */
2870function forum_get_file_areas($course, $cm, $context) {
9a1c0751
DM
2871 return array(
2872 'attachment' => get_string('areaattachment', 'mod_forum'),
2873 'post' => get_string('areapost', 'mod_forum'),
2874 );
d6f7cbfd 2875}
2876
beb18068
MG
2877/**
2878 * File browsing support for forum module.
2879 *
d2b7803e
DC
2880 * @package mod_forum
2881 * @category files
2882 * @param stdClass $browser file browser object
2883 * @param stdClass $areas file areas
2884 * @param stdClass $course course object
2885 * @param stdClass $cm course module
2886 * @param stdClass $context context module
2887 * @param string $filearea file area
2888 * @param int $itemid item ID
2889 * @param string $filepath file path
2890 * @param string $filename file name
2891 * @return file_info instance or null if not found
beb18068
MG
2892 */
2893function forum_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
35ca63c1 2894 global $CFG, $DB, $USER;
beb18068
MG
2895
2896 if ($context->contextlevel != CONTEXT_MODULE) {
2897 return null;
2898 }
2899
9a1c0751
DM
2900 // filearea must contain a real area
2901 if (!isset($areas[$filearea])) {
2902 return null;
2903 }
2904
9a1c0751
DM
2905 // Note that forum_user_can_see_post() additionally allows access for parent roles
2906 // and it explicitly checks qanda forum type, too. One day, when we stop requiring
2907 // course:managefiles, we will need to extend this.
2908 if (!has_capability('mod/forum:viewdiscussion', $context)) {
beb18068
MG
2909 return null;
2910 }
2911
9a1c0751
DM
2912 if (is_null($itemid)) {
2913 require_once($CFG->dirroot.'/mod/forum/locallib.php');
2914 return new forum_file_info_container($browser, $course, $cm, $context, $areas, $filearea);
2915 }
2916
406f8dd8
MG
2917 static $cached = array();
2918 // $cached will store last retrieved post, discussion and forum. To make sure that the cache
2919 // is cleared between unit tests we check if this is the same session
2920 if (!isset($cached['sesskey']) || $cached['sesskey'] != sesskey()) {
2921 $cached = array('sesskey' => sesskey());
2922 }
2923
2924 if (isset($cached['post']) && $cached['post']->id == $itemid) {
2925 $post = $cached['post'];
2926 } else if ($post = $DB->get_record('forum_posts', array('id' => $itemid))) {
2927 $cached['post'] = $post;
2928 } else {
beb18068
MG
2929 return null;
2930 }
2931
406f8dd8
MG
2932 if (isset($cached['discussion']) && $cached['discussion']->id == $post->discussion) {
2933 $discussion = $cached['discussion'];
2934 } else if ($discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion))) {
2935 $cached['discussion'] = $discussion;
2936 } else {
beb18068
MG
2937 return null;
2938 }
2939
406f8dd8
MG
2940 if (isset($cached['forum']) && $cached['forum']->id == $cm->instance) {
2941 $forum = $cached['forum'];
2942 } else if ($forum = $DB->get_record('forum', array('id' => $cm->instance))) {
2943 $cached['forum'] = $forum;
2944 } else {
beb18068
MG
2945 return null;
2946 }
2947
2948 $fs = get_file_storage();
2949 $filepath = is_null($filepath) ? '/' : $filepath;
2950 $filename = is_null($filename) ? '.' : $filename;
2951 if (!($storedfile = $fs->get_file($context->id, 'mod_forum', $filearea, $itemid, $filepath, $filename))) {
2952 return null;
2953 }
2954
35ca63c1
AG
2955 // Checks to see if the user can manage files or is the owner.
2956 // TODO MDL-33805 - Do not use userid here and move the capability check above.
2957 if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
2958 return null;
2959 }
beb18068 2960 // Make sure groups allow this user to see this file
406f8dd8 2961 if ($discussion->groupid > 0 && !has_capability('moodle/site:accessallgroups', $context)) {
b75ec104 2962 $groupmode = groups_get_activity_groupmode($cm, $course);
406f8dd8
MG
2963 if ($groupmode == SEPARATEGROUPS && !groups_is_member($discussion->groupid)) {
2964 return null;
beb18068
MG
2965 }
2966 }
2967
2968 // Make sure we're allowed to see it...
2969 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
2970 return null;
2971 }
2972
2973 $urlbase = $CFG->wwwroot.'/pluginfile.php';
9a1c0751 2974 return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
beb18068
MG
2975}
2976
0faf6791 2977/**
2978 * Serves the forum attachments. Implements needed access control ;-)
df1ba0f4 2979 *
d2b7803e
DC
2980 * @package mod_forum
2981 * @category files
2982 * @param stdClass $course course object
2983 * @param stdClass $cm course module object
2984 * @param stdClass $context context object
2985 * @param string $filearea file area
2986 * @param array $args extra arguments
2987 * @param bool $forcedownload whether or not force download
261cbbac 2988 * @param array $options additional options affecting the file serving
86900a93 2989 * @return bool false if file not found, does not return if found - justsend the file
0faf6791 2990 */
261cbbac 2991function forum_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
0faf6791 2992 global $CFG, $DB;
2993
64f93798 2994 if ($context->contextlevel != CONTEXT_MODULE) {
7e55d513 2995 return false;
2996 }
2997
64f93798
PS
2998 require_course_login($course, true, $cm);
2999
9a1c0751
DM
3000 $areas = forum_get_file_areas($course, $cm, $context);
3001
3002 // filearea must contain a real area
3003 if (!isset($areas[$filearea])) {
0faf6791 3004 return false;
3005 }
3006
3007 $postid = (int)array_shift($args);
3008
3009 if (!$post = $DB->get_record('forum_posts', array('id'=>$postid))) {
3010 return false;
7f6689e4 3011 }
72d497d4 3012
0faf6791 3013 if (!$discussion = $DB->get_record('forum_discussions', array('id'=>$post->discussion))) {
3014 return false;
3015 }
3016
64f93798 3017 if (!$forum = $DB->get_record('forum', array('id'=>$cm->instance))) {
0faf6791 3018 return false;
3019 }
3020
3021 $fs = get_file_storage();
64f93798
PS
3022 $relativepath = implode('/', $args);
3023 $fullpath = "/$context->id/mod_forum/$filearea/$postid/$relativepath";
0faf6791 3024 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
3025 return false;
3026 }
3027
3028 // Make sure groups allow this user to see this file
b75ec104
DM
3029 if ($discussion->groupid > 0) {
3030 $groupmode = groups_get_activity_groupmode($cm, $course);
3031 if ($groupmode == SEPARATEGROUPS) {
3032 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
3033 return false;
3034 }
0faf6791 3035 }
3036 }
3037
3038 // Make sure we're allowed to see it...
1d797cac 3039 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
0faf6791 3040 return false;
3041 }
3042
0faf6791 3043 // finally send the file
261cbbac 3044 send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security!
7f6689e4 3045}
0faf6791 3046
db290a6e 3047/**
e3ff14ca 3048 * If successful, this function returns the name of the file
df1ba0f4 3049 *
3050 * @global object
3051 * @param object $post is a full post record, including course and forum
3052 * @param object $forum
3053 * @param object $cm
3054 * @param mixed $mform
a9a0cb69 3055 * @param string $unused
df1ba0f4 3056 * @return bool
e3ff14ca 3057 */
a9a0cb69 3058function forum_add_attachment($post, $forum, $cm, $mform=null, $unused=null) {
12fab708 3059 global $DB;
7f6689e4 3060
dbf4d660 3061 if (empty($mform)) {
3062 return false;
3063 }
7f6689e4 3064
6606c00f
MD
3065 if (empty($post->attachments)) {
3066 return true; // Nothing to do
3067 }
3068
bf0f06b1 3069 $context = context_module::instance($cm->id);
0faf6791 3070
edc0c493 3071 $info = file_get_draft_area_info($post->attachments);
12fab708 3072 $present = ($info['filecount']>0) ? '1' : '';
61a339e5
MG
3073 file_save_draft_area_files($post->attachments, $context->id, 'mod_forum', 'attachment', $post->id,
3074 mod_forum_post_form::attachment_options($forum));
0faf6791 3075
12fab708 3076 $DB->set_field('forum_posts', 'attachment', $present, array('id'=>$post->id));
274e2947 3077
12fab708 3078 return true;
927ce887 3079}
3080
3081/**
3082 * Add a new post in an existing discussion.
df1ba0f4 3083 *
2e48384d
AN
3084 * @param stdClass $post The post data
3085 * @param mixed $mform The submitted form
3086 * @param string $unused
df1ba0f4 3087 * @return int
0a4ac01b 3088 */
91223df6 3089function forum_add_new_post($post, $mform, $unused = null) {
2e48384d 3090 global $USER, $DB;
e3ff14ca 3091
4e445355 3092 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
3093 $forum = $DB->get_record('forum', array('id' => $discussion->forum));
0faf6791 3094 $cm = get_coursemodule_from_instance('forum', $forum->id);
bf0f06b1 3095 $context = context_module::instance($cm->id);
b9973acc 3096
3097 $post->created = $post->modified = time();
8076010d 3098 $post->mailed = FORUM_MAILED_PENDING;
b9973acc 3099 $post->userid = $USER->id;
7f6689e4 3100 $post->attachment = "";
cd5be9a5
MG
3101 if (!isset($post->totalscore)) {
3102 $post->totalscore = 0;
3103 }
3104 if (!isset($post->mailnow)) {
3105 $post->mailnow = 0;
3106 }
7f6689e4 3107
a8f3a651 3108 $post->id = $DB->insert_record("forum_posts", $post);
61a339e5 3109 $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id,
5e95223e 3110 mod_forum_post_form::editor_options($context, null), $post->message);
e58357bf 3111 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
91223df6 3112 forum_add_attachment($post, $forum, $cm, $mform);
29507631 3113
3114 // Update discussion modified date
4e445355 3115 $DB->set_field("forum_discussions", "timemodified", $post->modified, array("id" => $post->discussion));
3116 $DB->set_field("forum_discussions", "usermodified", $post->userid, array("id" => $post->discussion));
65b0e537 3117
90f4745c 3118 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
6c635746 3119 forum_tp_mark_post_read($post->userid, $post);
f37da850 3120 }
3121
34c8585d
MG
3122 if (isset($post->tags)) {
3123 core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id, $context, $post->tags);
3124 }
3125
103e7cba
KG
3126 // Let Moodle know that assessable content is uploaded (eg for plagiarism detection)
3127 forum_trigger_content_uploaded_event($post, $cm, 'forum_add_new_post');
3128
7f6689e4 3129 return $post->id;
501cdbd8 3130}
3131
0a4ac01b 3132/**
2e48384d 3133 * Update a post.
df1ba0f4 3134 *
6528ec35 3135 * @param stdClass $newpost The post to update
2e48384d
AN
3136 * @param mixed $mform The submitted form
3137 * @param string $unused
3138 * @return bool
0a4ac01b 3139 */
6528ec35
AN
3140function forum_update_post($newpost, $mform, $unused = null) {
3141 global $DB, $USER;
a56f0d60 3142
6528ec35 3143 $post = $DB->get_record('forum_posts', array('id' => $newpost->id));
0faf6791 3144 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
3145 $forum = $DB->get_record('forum', array('id' => $discussion->forum));
3146 $cm = get_coursemodule_from_instance('forum', $forum->id);
bf0f06b1 3147 $context = context_module::instance($cm->id);
90f4745c 3148
6528ec35
AN
3149 // Allowed modifiable fields.
3150 $modifiablefields = [
3151 'subject',
3152 'message',
3153 'messageformat',