| [ Index ] |
PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008] |
[Summary view] [Print] [Text view]
1 <?php // $Id$ 2 3 require_once($CFG->libdir.'/filelib.php'); 4 5 /// CONSTANTS /////////////////////////////////////////////////////////// 6 7 define('FORUM_MODE_FLATOLDEST', 1); 8 define('FORUM_MODE_FLATNEWEST', -1); 9 define('FORUM_MODE_THREADED', 2); 10 define('FORUM_MODE_NESTED', 3); 11 12 define('FORUM_FORCESUBSCRIBE', 1); 13 define('FORUM_INITIALSUBSCRIBE', 2); 14 define('FORUM_DISALLOWSUBSCRIBE',3); 15 16 define('FORUM_TRACKING_OFF', 0); 17 define('FORUM_TRACKING_OPTIONAL', 1); 18 define('FORUM_TRACKING_ON', 2); 19 20 define('FORUM_UNSET_POST_RATING', -999); 21 22 define ('FORUM_AGGREGATE_NONE', 0); //no ratings 23 define ('FORUM_AGGREGATE_AVG', 1); 24 define ('FORUM_AGGREGATE_COUNT', 2); 25 define ('FORUM_AGGREGATE_MAX', 3); 26 define ('FORUM_AGGREGATE_MIN', 4); 27 define ('FORUM_AGGREGATE_SUM', 5); 28 29 /// STANDARD FUNCTIONS /////////////////////////////////////////////////////////// 30 31 /** 32 * Given an object containing all the necessary data, 33 * (defined by the form in mod.html) this function 34 * will create a new instance and return the id number 35 * of the new instance. 36 * @param object $forum add forum instance (with magic quotes) 37 * @return int intance id 38 */ 39 function forum_add_instance($forum) { 40 global $CFG; 41 42 $forum->timemodified = time(); 43 44 if (empty($forum->assessed)) { 45 $forum->assessed = 0; 46 } 47 48 if (empty($forum->ratingtime) or empty($forum->assessed)) { 49 $forum->assesstimestart = 0; 50 $forum->assesstimefinish = 0; 51 } 52 53 if (!$forum->id = insert_record('forum', $forum)) { 54 return false; 55 } 56 57 if ($forum->type == 'single') { // Create related discussion. 58 $discussion = new object(); 59 $discussion->course = $forum->course; 60 $discussion->forum = $forum->id; 61 $discussion->name = $forum->name; 62 $discussion->intro = $forum->intro; 63 $discussion->assessed = $forum->assessed; 64 $discussion->format = $forum->type; 65 $discussion->mailnow = false; 66 $discussion->groupid = -1; 67 68 if (! forum_add_discussion($discussion, $discussion->intro)) { 69 error('Could not add the discussion for this forum'); 70 } 71 } 72 73 if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) { // all users should be subscribed initially 74 $users = get_course_users($forum->course); 75 foreach ($users as $user) { 76 forum_subscribe($user->id, $forum->id); 77 } 78 } 79 80 $forum = stripslashes_recursive($forum); 81 forum_grade_item_update($forum); 82 83 return $forum->id; 84 } 85 86 87 /** 88 * Given an object containing all the necessary data, 89 * (defined by the form in mod.html) this function 90 * will update an existing instance with new data. 91 * @param object $forum forum instance (with magic quotes) 92 * @return bool success 93 */ 94 function forum_update_instance($forum) { 95 $forum->timemodified = time(); 96 $forum->id = $forum->instance; 97 98 if (empty($forum->assessed)) { 99 $forum->assessed = 0; 100 } 101 102 if (empty($forum->ratingtime) or empty($forum->assessed)) { 103 $forum->assesstimestart = 0; 104 $forum->assesstimefinish = 0; 105 } 106 107 $oldforum = get_record('forum', 'id', $forum->id); 108 109 // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum 110 // if scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond? 111 // 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 112 if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) { 113 forum_update_grades($forum); // recalculate grades for the forum 114 } 115 116 if ($forum->type == 'single') { // Update related discussion and post. 117 if (! $discussion = get_record('forum_discussions', 'forum', $forum->id)) { 118 if ($discussions = get_records('forum_discussions', 'forum', $forum->id, 'timemodified ASC')) { 119 notify('Warning! There is more than one discussion in this forum - using the most recent'); 120 $discussion = array_pop($discussions); 121 } else { 122 error('Could not find the discussion in this forum'); 123 } 124 } 125 if (! $post = get_record('forum_posts', 'id', $discussion->firstpost)) { 126 error('Could not find the first post in this forum discussion'); 127 } 128 129 $post->subject = $forum->name; 130 $post->message = $forum->intro; 131 $post->modified = $forum->timemodified; 132 133 if (! update_record('forum_posts', ($post))) { 134 error('Could not update the first post'); 135 } 136 137 $discussion->name = $forum->name; 138 139 if (! update_record('forum_discussions', ($discussion))) { 140 error('Could not update the discussion'); 141 } 142 } 143 144 if (!update_record('forum', $forum)) { 145 error('Can not update forum'); 146 } 147 148 $forum = stripslashes_recursive($forum); 149 forum_grade_item_update($forum); 150 151 return true; 152 } 153 154 155 /** 156 * Given an ID of an instance of this module, 157 * this function will permanently delete the instance 158 * and any data that depends on it. 159 * @param int forum instance id 160 * @return bool success 161 */ 162 function forum_delete_instance($id) { 163 164 if (!$forum = get_record('forum', 'id', $id)) { 165 return false; 166 } 167 168 $result = true; 169 170 if ($discussions = get_records('forum_discussions', 'forum', $forum->id)) { 171 foreach ($discussions as $discussion) { 172 if (!forum_delete_discussion($discussion, true)) { 173 $result = false; 174 } 175 } 176 } 177 178 if (!delete_records('forum_subscriptions', 'forum', $forum->id)) { 179 $result = false; 180 } 181 182 forum_tp_delete_read_records(-1, -1, -1, $forum->id); 183 184 if (!delete_records('forum', 'id', $forum->id)) { 185 $result = false; 186 } 187 188 forum_grade_item_delete($forum); 189 190 return $result; 191 } 192 193 194 /** 195 * Function to be run periodically according to the moodle cron 196 * Finds all posts that have yet to be mailed out, and mails them 197 * out to all subscribers 198 * @return void 199 */ 200 function forum_cron() { 201 global $CFG, $USER; 202 203 $cronuser = clone($USER); 204 $site = get_site(); 205 206 // all users that are subscribed to any post that needs sending 207 $users = array(); 208 209 // status arrays 210 $mailcount = array(); 211 $errorcount = array(); 212 213 // caches 214 $discussions = array(); 215 $forums = array(); 216 $courses = array(); 217 $coursemodules = array(); 218 $subscribedusers = array(); 219 220 221 // Posts older than 2 days will not be mailed. This is to avoid the problem where 222 // cron has not been running for a long time, and then suddenly people are flooded 223 // with mail from the past few weeks or months 224 $timenow = time(); 225 $endtime = $timenow - $CFG->maxeditingtime; 226 $starttime = $endtime - 48 * 3600; // Two days earlier 227 228 if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) { 229 // Mark them all now as being mailed. It's unlikely but possible there 230 // might be an error later so that a post is NOT actually mailed out, 231 // but since mail isn't crucial, we can accept this risk. Doing it now 232 // prevents the risk of duplicated mails, which is a worse problem. 233 234 if (!forum_mark_old_posts_as_mailed($endtime)) { 235 mtrace('Errors occurred while trying to mark some posts as being mailed.'); 236 return false; // Don't continue trying to mail them, in case we are in a cron loop 237 } 238 239 // checking post validity, and adding users to loop through later 240 foreach ($posts as $pid => $post) { 241 242 $discussionid = $post->discussion; 243 if (!isset($discussions[$discussionid])) { 244 if ($discussion = get_record('forum_discussions', 'id', $post->discussion)) { 245 $discussions[$discussionid] = $discussion; 246 } else { 247 mtrace('Could not find discussion '.$discussionid); 248 unset($posts[$pid]); 249 continue; 250 } 251 } 252 $forumid = $discussions[$discussionid]->forum; 253 if (!isset($forums[$forumid])) { 254 if ($forum = get_record('forum', 'id', $forumid)) { 255 $forums[$forumid] = $forum; 256 } else { 257 mtrace('Could not find forum '.$forumid); 258 unset($posts[$pid]); 259 continue; 260 } 261 } 262 $courseid = $forums[$forumid]->course; 263 if (!isset($courses[$courseid])) { 264 if ($course = get_record('course', 'id', $courseid)) { 265 $courses[$courseid] = $course; 266 } else { 267 mtrace('Could not find course '.$courseid); 268 unset($posts[$pid]); 269 continue; 270 } 271 } 272 if (!isset($coursemodules[$forumid])) { 273 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) { 274 $coursemodules[$forumid] = $cm; 275 } else { 276 mtrace('Could not course module for forum '.$forumid); 277 unset($posts[$pid]); 278 continue; 279 } 280 } 281 282 283 // caching subscribed users of each forum 284 if (!isset($subscribedusers[$forumid])) { 285 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, false)) { 286 foreach ($subusers as $postuser) { 287 // do not try to mail users with stopped email 288 if ($postuser->emailstop) { 289 if (!empty($CFG->forum_logblocked)) { 290 add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id); 291 } 292 continue; 293 } 294 // this user is subscribed to this forum 295 $subscribedusers[$forumid][$postuser->id] = $postuser->id; 296 // this user is a user we have to process later 297 $users[$postuser->id] = $postuser; 298 } 299 unset($subusers); // release memory 300 } 301 } 302 303 $mailcount[$pid] = 0; 304 $errorcount[$pid] = 0; 305 } 306 } 307 308 if ($users && $posts) { 309 310 $urlinfo = parse_url($CFG->wwwroot); 311 $hostname = $urlinfo['host']; 312 313 foreach ($users as $userto) { 314 315 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes 316 317 // set this so that the capabilities are cached, and environment matches receiving user 318 $USER = $userto; 319 320 mtrace('Processing user '.$userto->id); 321 322 // init caches 323 $userto->viewfullnames = array(); 324 $userto->canpost = array(); 325 $userto->markposts = array(); 326 $userto->enrolledin = array(); 327 328 // reset the caches 329 foreach ($coursemodules as $forumid=>$unused) { 330 $coursemodules[$forumid]->cache = new object(); 331 $coursemodules[$forumid]->cache->caps = array(); 332 unset($coursemodules[$forumid]->uservisible); 333 } 334 335 foreach ($posts as $pid => $post) { 336 337 // Set up the environment for the post, discussion, forum, course 338 $discussion = $discussions[$post->discussion]; 339 $forum = $forums[$discussion->forum]; 340 $course = $courses[$forum->course]; 341 $cm =& $coursemodules[$forum->id]; 342 343 // Do some checks to see if we can bail out now 344 if (!isset($subscribedusers[$forum->id][$userto->id])) { 345 continue; // user does not subscribe to this forum 346 } 347 348 // Verify user is enrollend in course - if not do not send any email 349 if (!isset($userto->enrolledin[$course->id])) { 350 $userto->enrolledin[$course->id] = has_capability('moodle/course:view', get_context_instance(CONTEXT_COURSE, $course->id)); 351 } 352 if (!$userto->enrolledin[$course->id]) { 353 // oops - this user should not receive anything from this course 354 continue; 355 } 356 357 // Get info about the sending user 358 if (array_key_exists($post->userid, $users)) { // we might know him/her already 359 $userfrom = $users[$post->userid]; 360 } else if ($userfrom = get_record('user', 'id', $post->userid)) { 361 $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway 362 } else { 363 mtrace('Could not find user '.$post->userid); 364 continue; 365 } 366 367 // setup global $COURSE properly - needed for roles and languages 368 course_setup($course); // More environment 369 370 // Fill caches 371 if (!isset($userto->viewfullnames[$forum->id])) { 372 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id); 373 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext); 374 } 375 if (!isset($userto->canpost[$discussion->id])) { 376 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id); 377 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext); 378 } 379 if (!isset($userfrom->groups[$forum->id])) { 380 if (!isset($userfrom->groups)) { 381 $userfrom->groups = array(); 382 $users[$userfrom->id]->groups = array(); 383 } 384 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid); 385 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id]; 386 } 387 388 // Make sure groups allow this user to see this email 389 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used 390 if (!groups_group_exists($discussion->groupid)) { // Can't find group 391 continue; // Be safe and don't send it to anyone 392 } 393 394 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) { 395 // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS 396 continue; 397 } 398 } 399 400 // Make sure we're allowed to see it... 401 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) { 402 mtrace('user '.$userto->id. ' can not see '.$post->id); 403 continue; 404 } 405 406 // OK so we need to send the email. 407 408 // Does the user want this post in a digest? If so postpone it for now. 409 if ($userto->maildigest > 0) { 410 // This user wants the mails to be in digest form 411 $queue = new object(); 412 $queue->userid = $userto->id; 413 $queue->discussionid = $discussion->id; 414 $queue->postid = $post->id; 415 $queue->timemodified = $post->created; 416 if (!insert_record('forum_queue', $queue)) { 417 mtrace("Error: mod/forum/cron.php: Could not queue for digest mail for id $post->id to user $userto->id ($userto->email) .. not trying again."); 418 } 419 continue; 420 } 421 422 423 // Prepare to actually send the post now, and build up the content 424 425 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name))); 426 427 $userfrom->customheaders = array ( // Headers to make emails easier to track 428 'Precedence: Bulk', 429 'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>', 430 'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id, 431 'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>', 432 'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>', 433 'References: <moodlepost'.$post->parent.'@'.$hostname.'>', 434 'X-Course-Id: '.$course->id, 435 'X-Course-Name: '.format_string($course->fullname, true) 436 ); 437 438 439 $postsubject = "$course->shortname: ".format_string($post->subject,true); 440 $posttext = forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto); 441 $posthtml = forum_make_mail_html($course, $forum, $discussion, $post, $userfrom, $userto); 442 443 // Send the post now! 444 445 mtrace('Sending ', ''); 446 447 if (!$mailresult = email_to_user($userto, $userfrom, $postsubject, $posttext, 448 $posthtml, '', '', $CFG->forum_replytouser)) { 449 mtrace("Error: mod/forum/cron.php: Could not send out mail for id $post->id to user $userto->id". 450 " ($userto->email) .. not trying again."); 451 add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id", 452 substr(format_string($post->subject,true),0,30), $cm->id, $userto->id); 453 $errorcount[$post->id]++; 454 } else if ($mailresult === 'emailstop') { 455 // should not be reached anymore - see check above 456 } else { 457 $mailcount[$post->id]++; 458 459 // Mark post as read if forum_usermarksread is set off 460 if (!$CFG->forum_usermarksread) { 461 $userto->markposts[$post->id] = $post->id; 462 } 463 } 464 465 mtrace('post '.$post->id. ': '.$post->subject); 466 } 467 468 // mark processed posts as read 469 forum_tp_mark_posts_read($userto, $userto->markposts); 470 } 471 } 472 473 if ($posts) { 474 foreach ($posts as $post) { 475 mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'"); 476 if ($errorcount[$post->id]) { 477 set_field("forum_posts", "mailed", "2", "id", "$post->id"); 478 } 479 } 480 } 481 482 // release some memory 483 unset($subscribedusers); 484 unset($mailcount); 485 unset($errorcount); 486 487 $USER = clone($cronuser); 488 course_setup(SITEID); 489 490 $sitetimezone = $CFG->timezone; 491 492 // Now see if there are any digest mails waiting to be sent, and if we should send them 493 494 mtrace('Starting digest processing...'); 495 496 @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes 497 498 if (!isset($CFG->digestmailtimelast)) { // To catch the first time 499 set_config('digestmailtimelast', 0); 500 } 501 502 $timenow = time(); 503 $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600); 504 505 // Delete any really old ones (normally there shouldn't be any) 506 $weekago = $timenow - (7 * 24 * 3600); 507 delete_records_select('forum_queue', "timemodified < $weekago"); 508 mtrace ('Cleaned old digest records'); 509 510 if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) { 511 512 mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone)); 513 514 $digestposts_rs = get_recordset_select('forum_queue', "timemodified < $digesttime"); 515 516 if (!rs_EOF($digestposts_rs)) { 517 518 // We have work to do 519 $usermailcount = 0; 520 521 //caches - reuse the those filled before too 522 $discussionposts = array(); 523 $userdiscussions = array(); 524 525 while ($digestpost = rs_fetch_next_record($digestposts_rs)) { 526 if (!isset($users[$digestpost->userid])) { 527 if ($user = get_record('user', 'id', $digestpost->userid)) { 528 $users[$digestpost->userid] = $user; 529 } else { 530 continue; 531 } 532 } 533 $postuser = $users[$digestpost->userid]; 534 if ($postuser->emailstop) { 535 if (!empty($CFG->forum_logblocked)) { 536 add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id); 537 } 538 continue; 539 } 540 541 if (!isset($posts[$digestpost->postid])) { 542 if ($post = get_record('forum_posts', 'id', $digestpost->postid)) { 543 $posts[$digestpost->postid] = $post; 544 } else { 545 continue; 546 } 547 } 548 $discussionid = $digestpost->discussionid; 549 if (!isset($discussions[$discussionid])) { 550 if ($discussion = get_record('forum_discussions', 'id', $discussionid)) { 551 $discussions[$discussionid] = $discussion; 552 } else { 553 continue; 554 } 555 } 556 $forumid = $discussions[$discussionid]->forum; 557 if (!isset($forums[$forumid])) { 558 if ($forum = get_record('forum', 'id', $forumid)) { 559 $forums[$forumid] = $forum; 560 } else { 561 continue; 562 } 563 } 564 565 $courseid = $forums[$forumid]->course; 566 if (!isset($courses[$courseid])) { 567 if ($course = get_record('course', 'id', $courseid)) { 568 $courses[$courseid] = $course; 569 } else { 570 continue; 571 } 572 } 573 574 if (!isset($coursemodules[$forumid])) { 575 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) { 576 $coursemodules[$forumid] = $cm; 577 } else { 578 continue; 579 } 580 } 581 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid; 582 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid; 583 } 584 rs_close($digestposts_rs); /// Finished iteration, let's close the resultset 585 586 // Data collected, start sending out emails to each user 587 foreach ($userdiscussions as $userid => $thesediscussions) { 588 589 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes 590 591 $USER = $cronuser; 592 course_setup(SITEID); // reset cron user language, theme and timezone settings 593 594 mtrace(get_string('processingdigest', 'forum', $userid), '... '); 595 596 // First of all delete all the queue entries for this user 597 delete_records_select('forum_queue', "userid = $userid AND timemodified < $digesttime"); 598 $userto = $users[$userid]; 599 600 // Override the language and timezone of the "current" user, so that 601 // mail is customised for the receiver. 602 $USER = $userto; 603 course_setup(SITEID); 604 605 // init caches 606 $userto->viewfullnames = array(); 607 $userto->canpost = array(); 608 $userto->markposts = array(); 609 610 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true)); 611 612 $headerdata = new object(); 613 $headerdata->sitename = format_string($site->fullname, true); 614 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&course='.$site->id; 615 616 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n"; 617 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>'; 618 619 $posthtml = "<head>"; 620 foreach ($CFG->stylesheets as $stylesheet) { 621 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n"; 622 } 623 $posthtml .= "</head>\n<body id=\"email\">\n"; 624 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />'; 625 626 foreach ($thesediscussions as $discussionid) { 627 628 @set_time_limit(120); // to be reset for each post 629 630 $discussion = $discussions[$discussionid]; 631 $forum = $forums[$discussion->forum]; 632 $course = $courses[$forum->course]; 633 $cm = $coursemodules[$forum->id]; 634 635 //override language 636 course_setup($course); 637 638 // Fill caches 639 if (!isset($userto->viewfullnames[$forum->id])) { 640 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id); 641 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext); 642 } 643 if (!isset($userto->canpost[$discussion->id])) { 644 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id); 645 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext); 646 } 647 648 $strforums = get_string('forums', 'forum'); 649 $canunsubscribe = ! forum_is_forcesubscribed($forum); 650 $canreply = $userto->canpost[$discussion->id]; 651 652 $posttext .= "\n \n"; 653 $posttext .= '====================================================================='; 654 $posttext .= "\n \n"; 655 $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true); 656 if ($discussion->name != $forum->name) { 657 $posttext .= " -> ".format_string($discussion->name,true); 658 } 659 $posttext .= "\n"; 660 661 $posthtml .= "<p><font face=\"sans-serif\">". 662 "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ". 663 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ". 664 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>"; 665 if ($discussion->name == $forum->name) { 666 $posthtml .= "</font></p>"; 667 } else { 668 $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>"; 669 } 670 $posthtml .= '<p>'; 671 672 $postsarray = $discussionposts[$discussionid]; 673 sort($postsarray); 674 675 foreach ($postsarray as $postid) { 676 $post = $posts[$postid]; 677 678 if (array_key_exists($post->userid, $users)) { // we might know him/her already 679 $userfrom = $users[$post->userid]; 680 } else if ($userfrom = get_record('user', 'id', $post->userid)) { 681 $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway 682 } else { 683 mtrace('Could not find user '.$post->userid); 684 continue; 685 } 686 687 if (!isset($userfrom->groups[$forum->id])) { 688 if (!isset($userfrom->groups)) { 689 $userfrom->groups = array(); 690 $users[$userfrom->id]->groups = array(); 691 } 692 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid); 693 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id]; 694 } 695 696 $userfrom->customheaders = array ("Precedence: Bulk"); 697 698 if ($userto->maildigest == 2) { 699 // Subjects only 700 $by = new object(); 701 $by->name = fullname($userfrom); 702 $by->date = userdate($post->modified); 703 $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by); 704 $posttext .= "\n---------------------------------------------------------------------"; 705 706 $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&course=$course->id\">$by->name</a>"; 707 $posthtml .= '<div><a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'#p'.$post->id.'">'.format_string($post->subject,true).'</a> '.get_string("bynameondate", "forum", $by).'</div>'; 708 709 } else { 710 // The full treatment 711 $posttext .= forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto, true); 712 $posthtml .= forum_make_mail_post($course, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false); 713 714 // Create an array of postid's for this user to mark as read. 715 if (!$CFG->forum_usermarksread) { 716 $userto->markposts[$post->id] = $post->id; 717 } 718 } 719 } 720 if ($canunsubscribe) { 721 $posthtml .= "\n<div align=\"right\"><font size=\"1\"><a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">".get_string("unsubscribe", "forum")."</a></font></div>"; 722 } else { 723 $posthtml .= "\n<div align=\"right\"><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>"; 724 } 725 $posthtml .= '<hr size="1" noshade="noshade" /></p>'; 726 } 727 $posthtml .= '</body>'; 728 729 if ($userto->mailformat != 1) { 730 // This user DOESN'T want to receive HTML 731 $posthtml = ''; 732 } 733 734 if (!$mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, 735 '', '', $CFG->forum_replytouser)) { 736 mtrace("ERROR!"); 737 echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n"; 738 add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id); 739 } else if ($mailresult === 'emailstop') { 740 // should not happen anymore - see check above 741 } else { 742 mtrace("success."); 743 $usermailcount++; 744 745 // Mark post as read if forum_usermarksread is set off 746 forum_tp_mark_posts_read($userto, $userto->markposts); 747 } 748 } 749 } 750 /// We have finishied all digest emails, update $CFG->digestmailtimelast 751 set_config('digestmailtimelast', $timenow); 752 } 753 754 $USER = $cronuser; 755 course_setup(SITEID); // reset cron user language, theme and timezone settings 756 757 if (!empty($usermailcount)) { 758 mtrace(get_string('digestsentusers', 'forum', $usermailcount)); 759 } 760 761 if (!empty($CFG->forum_lastreadclean)) { 762 $timenow = time(); 763 if ($CFG->forum_lastreadclean + (24*3600) < $timenow) { 764 set_config('forum_lastreadclean', $timenow); 765 mtrace('Removing old forum read tracking info...'); 766 forum_tp_clean_read_records(); 767 } 768 } else { 769 set_config('forum_lastreadclean', time()); 770 } 771 772 773 return true; 774 } 775 776 /** 777 * Builds and returns the body of the email notification in plain text. 778 * 779 * @param object $course 780 * @param object $forum 781 * @param object $discussion 782 * @param object $post 783 * @param object $userfrom 784 * @param object $userto 785 * @param boolean $bare 786 * @return string The email body in plain text format. 787 */ 788 function forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto, $bare = false) { 789 global $CFG, $USER; 790 791 if (!isset($userto->viewfullnames[$forum->id])) { 792 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) { 793 error('Course Module ID was incorrect'); 794 } 795 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id); 796 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id); 797 } else { 798 $viewfullnames = $userto->viewfullnames[$forum->id]; 799 } 800 801 if (!isset($userto->canpost[$discussion->id])) { 802 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id); 803 $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext); 804 } else { 805 $canreply = $userto->canpost[$discussion->id]; 806 } 807 808 $by = New stdClass; 809 $by->name = fullname($userfrom, $viewfullnames); 810 $by->date = userdate($post->modified, "", $userto->timezone); 811 812 $strbynameondate = get_string('bynameondate', 'forum', $by); 813 814 $strforums = get_string('forums', 'forum'); 815 816 $canunsubscribe = ! forum_is_forcesubscribed($forum); 817 818 $posttext = ''; 819 820 if (!$bare) { 821 $posttext = "$course->shortname -> $strforums -> ".format_string($forum->name,true); 822 823 if ($discussion->name != $forum->name) { 824 $posttext .= " -> ".format_string($discussion->name,true); 825 } 826 } 827 828 $posttext .= "\n---------------------------------------------------------------------\n"; 829 $posttext .= format_string($post->subject,true); 830 if ($bare) { 831 $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)"; 832 } 833 $posttext .= "\n".$strbynameondate."\n"; 834 $posttext .= "---------------------------------------------------------------------\n"; 835 $posttext .= format_text_email(trusttext_strip($post->message), $post->format); 836 $posttext .= "\n\n"; 837 if ($post->attachment) { 838 $post->course = $course->id; 839 $post->forum = $forum->id; 840 $posttext .= forum_print_attachments($post, "text"); 841 } 842 if (!$bare && $canreply) { 843 $posttext .= "---------------------------------------------------------------------\n"; 844 $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n"; 845 $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n"; 846 } 847 if (!$bare && $canunsubscribe) { 848 $posttext .= "\n---------------------------------------------------------------------\n"; 849 $posttext .= get_string("unsubscribe", "forum"); 850 $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n"; 851 } 852 853 return $posttext; 854 } 855 856 /** 857 * Builds and returns the body of the email notification in html format. 858 * 859 * @param object $course 860 * @param object $forum 861 * @param object $discussion 862 * @param object $post 863 * @param object $userfrom 864 * @param object $userto 865 * @return string The email text in HTML format 866 */ 867 function forum_make_mail_html($course, $forum, $discussion, $post, $userfrom, $userto) { 868 global $CFG; 869 870 if ($userto->mailformat != 1) { // Needs to be HTML 871 return ''; 872 } 873 874 if (!isset($userto->canpost[$discussion->id])) { 875 $canreply = forum_user_can_post($forum, $discussion, $userto); 876 } else { 877 $canreply = $userto->canpost[$discussion->id]; 878 } 879 880 $strforums = get_string('forums', 'forum'); 881 $canunsubscribe = ! forum_is_forcesubscribed($forum); 882 883 $posthtml = '<head>'; 884 foreach ($CFG->stylesheets as $stylesheet) { 885 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n"; 886 } 887 $posthtml .= '</head>'; 888 $posthtml .= "\n<body id=\"email\">\n\n"; 889 890 $posthtml .= '<div class="navbar">'. 891 '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> » '. 892 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> » '. 893 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>'; 894 if ($discussion->name == $forum->name) { 895 $posthtml .= '</div>'; 896 } else { 897 $posthtml .= ' » <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'. 898 format_string($discussion->name,true).'</a></div>'; 899 } 900 $posthtml .= forum_make_mail_post($course, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false); 901 902 if ($canunsubscribe) { 903 $posthtml .= '<hr /><div align="center" class="unsubscribelink"> 904 <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a> 905 <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>'; 906 } 907 908 $posthtml .= '</body>'; 909 910 return $posthtml; 911 } 912 913 914 /** 915 * 916 * @param object $course 917 * @param object $user 918 * @param object $mod TODO this is not used in this function, refactor 919 * @param object $forum 920 * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified) 921 */ 922 function forum_user_outline($course, $user, $mod, $forum) { 923 if ($count = forum_count_user_posts($forum->id, $user->id)) { 924 if ($count->postcount > 0) { 925 $result = new object(); 926 $result->info = get_string("numposts", "forum", $count->postcount); 927 $result->time = $count->lastpost; 928 return $result; 929 } 930 } 931 return NULL; 932 } 933 934 935 /** 936 * 937 */ 938 function forum_user_complete($course, $user, $mod, $forum) { 939 global $CFG,$USER; 940 941 if ($posts = forum_get_user_posts($forum->id, $user->id)) { 942 943 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) { 944 error('Course Module ID was incorrect'); 945 } 946 $discussions = forum_get_user_involved_discussions($forum->id, $user->id); 947 948 // preload all user ratings for these discussions - one query only and minimal memory 949 $cm->cache->ratings = array(); 950 $cm->cache->myratings = array(); 951 if ($postratings = forum_get_all_user_ratings($user->id, $discussions)) { 952 foreach ($postratings as $pr) { 953 if (!isset($cm->cache->ratings[$pr->postid])) { 954 $cm->cache->ratings[$pr->postid] = array(); 955 } 956 $cm->cache->ratings[$pr->postid][$pr->id] = $pr->rating; 957 958 if ($pr->userid == $USER->id) { 959 $cm->cache->myratings[$pr->postid] = $pr->rating; 960 } 961 } 962 unset($postratings); 963 } 964 965 foreach ($posts as $post) { 966 if (!isset($discussions[$post->discussion])) { 967 continue; 968 } 969 $discussion = $discussions[$post->discussion]; 970 971 $ratings = null; 972 973 if ($forum->assessed) { 974 if ($scale = make_grades_menu($forum->scale)) { 975 $ratings =new object(); 976 $ratings->scale = $scale; 977 $ratings->assesstimestart = $forum->assesstimestart; 978 $ratings->assesstimefinish = $forum->assesstimefinish; 979 $ratings->allow = false; 980 } 981 } 982 983 forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false, $ratings); 984 985 } 986 } else { 987 echo "<p>".get_string("noposts", "forum")."</p>"; 988 } 989 } 990 991 992 /** 993 * 994 */ 995 function forum_print_overview($courses,&$htmlarray) { 996 global $USER, $CFG; 997 $LIKE = sql_ilike(); 998 999 if (empty($courses) || !is_array($courses) || count($courses) == 0) { 1000 return array(); 1001 } 1002 1003 if (!$forums = get_all_instances_in_courses('forum',$courses)) { 1004 return; 1005 } 1006 1007 1008 // get all forum logs in ONE query (much better!) 1009 $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {$CFG->prefix}log l " 1010 ." JOIN {$CFG->prefix}course_modules cm ON cm.id = cmid " 1011 ." WHERE ("; 1012 foreach ($courses as $course) { 1013 $sql .= '(l.course = '.$course->id.' AND l.time > '.$course->lastaccess.') OR '; 1014 } 1015 $sql = substr($sql,0,-3); // take off the last OR 1016 1017 $sql .= ") AND l.module = 'forum' AND action $LIKE 'add post%' " 1018 ." AND userid != ".$USER->id." GROUP BY cmid,l.course,instance"; 1019 1020 if (!$new = get_records_sql($sql)) { 1021 $new = array(); // avoid warnings 1022 } 1023 1024 // also get all forum tracking stuff ONCE. 1025 $trackingforums = array(); 1026 foreach ($forums as $forum) { 1027 if (forum_tp_can_track_forums($forum)) { 1028 $trackingforums[$forum->id] = $forum; 1029 } 1030 } 1031 1032 if (count($trackingforums) > 0) { 1033 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0; 1034 $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '. 1035 ' FROM '.$CFG->prefix.'forum_posts p '. 1036 ' JOIN '.$CFG->prefix.'forum_discussions d ON p.discussion = d.id '. 1037 ' LEFT JOIN '.$CFG->prefix.'forum_read r ON r.postid = p.id AND r.userid = '.$USER->id.' WHERE ('; 1038 foreach ($trackingforums as $track) { 1039 $sql .= '(d.forum = '.$track->id.' AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = '.get_current_group($track->course).')) OR '; 1040 } 1041 $sql = substr($sql,0,-3); // take off the last OR 1042 $sql .= ') AND p.modified >= '.$cutoffdate.' AND r.id is NULL GROUP BY d.forum,d.course'; 1043 1044 if (!$unread = get_records_sql($sql)) { 1045 $unread = array(); 1046 } 1047 } else { 1048 $unread = array(); 1049 } 1050 1051 if (empty($unread) and empty($new)) { 1052 return; 1053 } 1054 1055 $strforum = get_string('modulename','forum'); 1056 $strnumunread = get_string('overviewnumunread','forum'); 1057 $strnumpostssince = get_string('overviewnumpostssince','forum'); 1058 1059 foreach ($forums as $forum) { 1060 $str = ''; 1061 $count = 0; 1062 $thisunread = 0; 1063 $showunread = false; 1064 // either we have something from logs, or trackposts, or nothing. 1065 if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) { 1066 $count = $new[$forum->id]->count; 1067 } 1068 if (array_key_exists($forum->id,$unread)) { 1069 $thisunread = $unread[$forum->id]->count; 1070 $showunread = true; 1071 } 1072 if ($count > 0 || $thisunread > 0) { 1073 $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'. 1074 $forum->name.'</a></div>'; 1075 $str .= '<div class="info">'; 1076 $str .= $count.' '.$strnumpostssince; 1077 if (!empty($showunread)) { 1078 $str .= '<br />'.$thisunread .' '.$strnumunread; 1079 } 1080 $str .= '</div></div>'; 1081 } 1082 if (!empty($str)) { 1083 if (!array_key_exists($forum->course,$htmlarray)) { 1084 $htmlarray[$forum->course] = array(); 1085 } 1086 if (!array_key_exists('forum',$htmlarray[$forum->course])) { 1087 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings 1088 } 1089 $htmlarray[$forum->course]['forum'] .= $str; 1090 } 1091 } 1092 } 1093 1094 /** 1095 * Given a course and a date, prints a summary of all the new 1096 * messages posted in the course since that date 1097 * @param object $course 1098 * @param bool $viewfullnames capability 1099 * @param int $timestart 1100 * @return bool success 1101 */ 1102 function forum_print_recent_activity($course, $viewfullnames, $timestart) { 1103 global $CFG, $USER; 1104 1105 // do not use log table if possible, it may be huge and is expensive to join with other tables 1106 1107 if (!$posts = get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, 1108 d.timestart, d.timeend, d.userid AS duserid, 1109 u.firstname, u.lastname, u.email, u.picture 1110 FROM {$CFG->prefix}forum_posts p 1111 JOIN {$CFG->prefix}forum_discussions d ON d.id = p.discussion 1112 JOIN {$CFG->prefix}forum f ON f.id = d.forum 1113 JOIN {$CFG->prefix}user u ON u.id = p.userid 1114 WHERE p.created > $timestart AND f.course = {$course->id} 1115 ORDER BY p.id ASC")) { // order by initial posting date 1116 return false; 1117 } 1118 1119 $modinfo =& get_fast_modinfo($course); 1120 1121 $groupmodes = array(); 1122 $cms = array(); 1123 1124 $strftimerecent = get_string('strftimerecent'); 1125 1126 $printposts = array(); 1127 foreach ($posts as $post) { 1128 if (!isset($modinfo->instances['forum'][$post->forum])) { 1129 // not visible 1130 continue; 1131 } 1132 $cm = $modinfo->instances['forum'][$post->forum]; 1133 if (!$cm->uservisible) { 1134 continue; 1135 } 1136 $context = get_context_instance(CONTEXT_MODULE, $cm->id); 1137 1138 if (!has_capability('mod/forum:viewdiscussion', $context)) { 1139 continue; 1140 } 1141 1142 if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid 1143 and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) { 1144 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) { 1145 continue; 1146 } 1147 } 1148 1149 $groupmode = groups_get_activity_groupmode($cm, $course); 1150 1151 if ($groupmode) { 1152 if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) { 1153 // oki (Open discussions have groupid -1) 1154 } else { 1155 // separate mode 1156 if (isguestuser()) { 1157 // shortcut 1158 continue; 1159 } 1160 1161 if (is_null($modinfo->groups)) { 1162 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo 1163 } 1164 1165 if (!array_key_exists($post->groupid, $modinfo->groups[0])) { 1166 continue; 1167 } 1168 } 1169 } 1170 1171 $printposts[] = $post; 1172 } 1173 unset($posts); 1174 1175 if (!$printposts) { 1176 return false; 1177 } 1178 1179 print_headline(get_string('newforumposts', 'forum').':', 3); 1180 echo "\n<ul class='unlist'>\n"; 1181 1182 foreach ($printposts as $post) { 1183 $subjectclass = empty($post->parent) ? ' bold' : ''; 1184 1185 echo '<li><div class="head">'. 1186 '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'. 1187 '<div class="name">'.fullname($post, $viewfullnames).'</div>'. 1188 '</div>'; 1189 echo '<div class="info'.$subjectclass.'">'; 1190 if (empty($post->parent)) { 1191 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'; 1192 } else { 1193 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&parent='.$post->parent.'#p'.$post->id.'">'; 1194 } 1195 $post->subject = break_up_long_words(format_string($post->subject, true)); 1196 echo $post->subject; 1197 echo "</a>\"</div></li>\n"; 1198 } 1199 1200 echo "</ul>\n"; 1201 1202 return true; 1203 } 1204 1205 /** 1206 * Return grade for given user or all users. 1207 * 1208 * @param int $forumid id of forum 1209 * @param int $userid optional user id, 0 means all users 1210 * @return array array of grades, false if none 1211 */ 1212 function forum_get_user_grades($forum, $userid=0) { 1213 global $CFG; 1214 1215 $user = $userid ? "AND u.id = $userid" : ""; 1216 1217 $aggtype = $forum->assessed; 1218 switch ($aggtype) { 1219 case FORUM_AGGREGATE_COUNT : 1220 $sql = "SELECT u.id, u.id AS userid, COUNT(fr.rating) AS rawgrade 1221 FROM {$CFG->prefix}user u, {$CFG->prefix}forum_posts fp, 1222 {$CFG->prefix}forum_ratings fr, {$CFG->prefix}forum_discussions fd 1223 WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id 1224 AND fr.userid != u.id AND fd.forum = $forum->id 1225 $user 1226 GROUP BY u.id"; 1227 break; 1228 case FORUM_AGGREGATE_MAX : 1229 $sql = "SELECT u.id, u.id AS userid, MAX(fr.rating) AS rawgrade 1230 FROM {$CFG->prefix}user u, {$CFG->prefix}forum_posts fp, 1231 {$CFG->prefix}forum_ratings fr, {$CFG->prefix}forum_discussions fd 1232 WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id 1233 AND fr.userid != u.id AND fd.forum = $forum->id 1234 $user 1235 GROUP BY u.id"; 1236 break; 1237 case FORUM_AGGREGATE_MIN : 1238 $sql = "SELECT u.id, u.id AS userid, MIN(fr.rating) AS rawgrade 1239 FROM {$CFG->prefix}user u, {$CFG->prefix}forum_posts fp, 1240 {$CFG->prefix}forum_ratings fr, {$CFG->prefix}forum_discussions fd 1241 WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id 1242 AND fr.userid != u.id AND fd.forum = $forum->id 1243 $user 1244 GROUP BY u.id"; 1245 break; 1246 case FORUM_AGGREGATE_SUM : 1247 $sql = "SELECT u.id, u.id AS userid, SUM(fr.rating) AS rawgrade 1248 FROM {$CFG->prefix}user u, {$CFG->prefix}forum_posts fp, 1249 {$CFG->prefix}forum_ratings fr, {$CFG->prefix}forum_discussions fd 1250 WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id 1251 AND fr.userid != u.id AND fd.forum = $forum->id 1252 $user 1253 GROUP BY u.id"; 1254 break; 1255 default : //avg 1256 $sql = "SELECT u.id, u.id AS userid, AVG(fr.rating) AS rawgrade 1257 FROM {$CFG->prefix}user u, {$CFG->prefix}forum_posts fp, 1258 {$CFG->prefix}forum_ratings fr, {$CFG->prefix}forum_discussions fd 1259 WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id 1260 AND fr.userid != u.id AND fd.forum = $forum->id 1261 $user 1262 GROUP BY u.id"; 1263 break; 1264 } 1265 1266 if ($results = get_records_sql($sql)) { 1267 // it could throw off the grading if count and sum returned a rawgrade higher than scale 1268 // so to prevent it we review the results and ensure that rawgrade does not exceed the scale, if it does we set rawgrade = scale (i.e. full credit) 1269 foreach ($results as $rid=>$result) { 1270 if ($forum->scale >= 0) { 1271 //numeric 1272 if ($result->rawgrade > $forum->scale) { 1273 $results[$rid]->rawgrade = $forum->scale; 1274 } 1275 } else { 1276 //scales 1277 if ($scale = get_record('scale', 'id', -$forum->scale)) { 1278 $scale = explode(',', $scale->scale); 1279 $max = count($scale); 1280 if ($result->rawgrade > $max) { 1281 $results[$rid]->rawgrade = $max; 1282 } 1283 } 1284 } 1285 } 1286 } 1287 1288 return $results; 1289 } 1290 1291 /** 1292 * Update grades by firing grade_updated event 1293 * 1294 * @param object $forum null means all forums 1295 * @param int $userid specific user only, 0 mean all 1296 * @param boolean $nullifnone return null if grade does not exist 1297 * @return void 1298 */ 1299 function forum_update_grades($forum=null, $userid=0, $nullifnone=true) { 1300 global $CFG; 1301 1302 if ($forum != null) { 1303 require_once($CFG->libdir.'/gradelib.php'); 1304 if ($grades = forum_get_user_grades($forum, $userid)) { 1305 forum_grade_item_update($forum, $grades); 1306 1307 } else if ($userid and $nullifnone) { 1308 $grade = new object(); 1309 $grade->userid = $userid; 1310 $grade->rawgrade = NULL; 1311 forum_grade_item_update($forum, $grade); 1312 1313 } else { 1314 forum_grade_item_update($forum); 1315 } 1316 1317 } else { 1318 $sql = "SELECT f.*, cm.idnumber as cmidnumber 1319 FROM {$CFG->prefix}forum f, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m 1320 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id"; 1321 if ($rs = get_recordset_sql($sql)) { 1322 while ($forum = rs_fetch_next_record($rs)) { 1323 if ($forum->assessed) { 1324 forum_update_grades($forum, 0, false); 1325 } else { 1326 forum_grade_item_update($forum); 1327 } 1328 } 1329 rs_close($rs); 1330 } 1331 } 1332 } 1333 1334 /** 1335 * Create/update grade item for given forum 1336 * 1337 * @param object $forum object with extra cmidnumber 1338 * @param mixed optional array/object of grade(s); 'reset' means reset grades in gradebook 1339 * @return int 0 if ok 1340 */ 1341 function forum_grade_item_update($forum, $grades=NULL) { 1342 global $CFG; 1343 if (!function_exists('grade_update')) { //workaround for buggy PHP versions 1344 require_once($CFG->libdir.'/gradelib.php'); 1345 } 1346 1347 $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber); 1348 1349 if (!$forum->assessed or $forum->scale == 0) { 1350 $params['gradetype'] = GRADE_TYPE_NONE; 1351 1352 } else if ($forum->scale > 0) { 1353 $params['gradetype'] = GRADE_TYPE_VALUE; 1354 $params['grademax'] = $forum->scale; 1355 $params['grademin'] = 0; 1356 1357 } else if ($forum->scale < 0) { 1358 $params['gradetype'] = GRADE_TYPE_SCALE; 1359 $params['scaleid'] = -$forum->scale; 1360 } 1361 1362 if ($grades === 'reset') { 1363 $params['reset'] = true; 1364 $grades = NULL; 1365 } 1366 1367 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params); 1368 } 1369 1370 /** 1371 * Delete grade item for given forum 1372 * 1373 * @param object $forum object 1374 * @return object grade_item 1375 */ 1376 function forum_grade_item_delete($forum) { 1377 global $CFG; 1378 require_once($CFG->libdir.'/gradelib.php'); 1379 1380 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1)); 1381 } 1382 1383 1384 /** 1385 * Returns the users with data in one forum 1386 * (users with records in forum_subscriptions, forum_posts and forum_ratings, students) 1387 * @param int $forumid 1388 * @return mixed array or false if none 1389 */ 1390 function forum_get_participants($forumid) { 1391 1392 global $CFG; 1393 1394 //Get students from forum_subscriptions 1395 $st_subscriptions = get_records_sql("SELECT DISTINCT u.id, u.id 1396 FROM {$CFG->prefix}user u, 1397 {$CFG->prefix}forum_subscriptions s 1398 WHERE s.forum = '$forumid' and 1399 u.id = s.userid"); 1400 //Get students from forum_posts 1401 $st_posts = get_records_sql("SELECT DISTINCT u.id, u.id 1402 FROM {$CFG->prefix}user u, 1403 {$CFG->prefix}forum_discussions d, 1404 {$CFG->prefix}forum_posts p 1405 WHERE d.forum = '$forumid' and 1406 p.discussion = d.id and 1407 u.id = p.userid"); 1408 1409 //Get students from forum_ratings 1410 $st_ratings = get_records_sql("SELECT DISTINCT u.id, u.id 1411 FROM {$CFG->prefix}user u, 1412 {$CFG->prefix}forum_discussions d, 1413 {$CFG->prefix}forum_posts p, 1414 {$CFG->prefix}forum_ratings r 1415 WHERE d.forum = '$forumid' and 1416 p.discussion = d.id and 1417 r.post = p.id and 1418 u.id = r.userid"); 1419 1420 //Add st_posts to st_subscriptions 1421 if ($st_posts) { 1422 foreach ($st_posts as $st_post) { 1423 $st_subscriptions[$st_post->id] = $st_post; 1424 } 1425 } 1426 //Add st_ratings to st_subscriptions 1427 if ($st_ratings) { 1428 foreach ($st_ratings as $st_rating) { 1429 $st_subscriptions[$st_rating->id] = $st_rating; 1430 } 1431 } 1432 //Return st_subscriptions array (it contains an array of unique users) 1433 return ($st_subscriptions); 1434 } 1435 1436 /** 1437 * This function returns if a scale is being used by one forum 1438 * @param int $forumid 1439 * @param int $scaleid negative number 1440 * @return bool 1441 */ 1442 function forum_scale_used ($forumid,$scaleid) { 1443 1444 $return = false; 1445 1446 $rec = get_record("forum","id","$forumid","scale","-$scaleid"); 1447 1448 if (!empty($rec) && !empty($scaleid)) { 1449 $return = true; 1450 } 1451 1452 return $return; 1453 } 1454 1455 /** 1456 * Checks if scale is being used by any instance of forum 1457 * 1458 * This is used to find out if scale used anywhere 1459 * @param $scaleid int 1460 * @return boolean True if the scale is used by any forum 1461 */ 1462 function forum_scale_used_anywhere($scaleid) { 1463 if ($scaleid and record_exists('forum', 'scale', -$scaleid)) { 1464 return true; 1465 } else { 1466 return false; 1467 } 1468 } 1469 1470 // SQL FUNCTIONS /////////////////////////////////////////////////////////// 1471 1472 /** 1473 * Gets a post with all info ready for forum_print_post 1474 * Most of these joins are just to get the forum id 1475 * @param int $postid 1476 * @return mixed array of posts or false 1477 */ 1478 function forum_get_post_full($postid) { 1479 global $CFG; 1480 1481 return get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt 1482 FROM {$CFG->prefix}forum_posts p 1483 JOIN {$CFG->prefix}forum_discussions d ON p.discussion = d.id 1484 LEFT JOIN {$CFG->prefix}user u ON p.userid = u.id 1485 WHERE p.id = '$postid'"); 1486 } 1487 1488 /** 1489 * Gets posts with all info ready for forum_print_post 1490 * We pass forumid in because we always know it so no need to make a 1491 * complicated join to find it out. 1492 * @return mixed array of posts or false 1493 */ 1494 function forum_get_discussion_posts($discussion, $sort, $forumid) { 1495 global $CFG; 1496 1497 return get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt 1498 FROM {$CFG->prefix}forum_posts p 1499 LEFT JOIN {$CFG->prefix}user u ON p.userid = u.id 1500 WHERE p.discussion = $discussion 1501 AND p.parent > 0 $sort"); 1502 } 1503 1504 /** 1505 * Gets all posts in discussion including top parent. 1506 * @param int $discussionid 1507 * @param string $sort 1508 * @param bool $tracking does user track the forum? 1509 * @return array of posts 1510 */ 1511 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) { 1512 global $CFG, $USER; 1513 1514 $tr_sel = ""; 1515 $tr_join = ""; 1516 1517 if ($tracking) { 1518 $now = time(); 1519 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600); 1520 $tr_sel = ", fr.id AS postread"; 1521 $tr_join = "LEFT JOIN {$CFG->prefix}forum_read fr ON (fr.postid = p.id AND fr.userid = $USER->id)"; 1522 } 1523 1524 if (!$posts = get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel 1525 FROM {$CFG->prefix}forum_posts p 1526 LEFT JOIN {$CFG->prefix}user u ON p.userid = u.id 1527 $tr_join 1528 WHERE p.discussion = $discussionid 1529 ORDER BY $sort")) { 1530 return array(); 1531 } 1532 1533 foreach ($posts as $pid=>$p) { 1534 if ($tracking) { 1535 if (forum_tp_is_post_old($p)) { 1536 $posts[$pid]->postread = true; 1537 } 1538 } 1539 if (!$p->parent) { 1540 continue; 1541 } 1542 if (!isset($posts[$p->parent])) { 1543 continue; // parent does not exist?? 1544 } 1545 if (!isset($posts[$p->parent]->children)) { 1546 $posts[$p->parent]->children = array(); 1547 } 1548 $posts[$p->parent]->children[$pid] =& $posts[$pid]; 1549 } 1550 1551 return $posts; 1552 } 1553 1554 /** 1555 * Gets posts with all info ready for forum_print_post 1556 * We pass forumid in because we always know it so no need to make a 1557 * complicated join to find it out. 1558 */ 1559 function forum_get_child_posts($parent, $forumid) { 1560 global $CFG; 1561 1562 return get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt 1563 FROM {$CFG->prefix}forum_posts p 1564 LEFT JOIN {$CFG->prefix}user u ON p.userid = u.id 1565 WHERE p.parent = '$parent' 1566 ORDER BY p.created ASC"); 1567 } 1568 1569 /** 1570 * An array of forum objects that the user is allowed to read/search through. 1571 * @param $userid 1572 * @param $courseid - if 0, we look for forums throughout the whole site. 1573 * @return array of forum objects, or false if no matches 1574 * Forum objects have the following attributes: 1575 * id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups, 1576 * viewhiddentimedposts 1577 */ 1578 function forum_get_readable_forums($userid, $courseid=0) { 1579 1580 global $CFG, $USER; 1581 require_once($CFG->dirroot.'/course/lib.php'); 1582 1583 if (!$forummod = get_record('modules', 'name', 'forum')) { 1584 error('The forum module is not installed'); 1585 } 1586 1587 if ($courseid) { 1588 $courses = get_records('course', 'id', $courseid); 1589 } else { 1590 // If no course is specified, then the user can see SITE + his courses. 1591 // And admins can see all courses, so pass the $doanything flag enabled 1592 $courses1 = get_records('course', 'id', SITEID); 1593 $courses2 = get_my_courses($userid, null, null, true); 1594 $courses = array_merge($courses1, $courses2); 1595 } 1596 if (!$courses) { 1597 return array(); 1598 } 1599 1600 $readableforums = array(); 1601 1602 foreach ($courses as $course) { 1603 1604 $modinfo =& get_fast_modinfo($course); 1605 if (is_null($modinfo->groups)) { 1606 $modinfo->groups = groups_get_user_groups($course->id, $userid); 1607 } 1608 1609 if (empty($modinfo->instances['forum'])) { 1610 // hmm, no forums? 1611 continue; 1612 } 1613 1614 $courseforums = get_records('forum', 'course', $course->id); 1615 1616 foreach ($modinfo->instances['forum'] as $forumid => $cm) { 1617 if (!$cm->uservisible or !isset($courseforums[$forumid])) { 1618 continue; 1619 } 1620 $context = get_context_instance(CONTEXT_MODULE, $cm->id); 1621 $forum = $courseforums[$forumid]; 1622 1623 if (!has_capability('mod/forum:viewdiscussion', $context)) { 1624 continue; 1625 } 1626 1627 /// group access 1628 if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) { 1629 if (is_null($modinfo->groups)) { 1630 $modinfo->groups = groups_get_user_groups($course->id, $USER->id); 1631 } 1632 if (empty($CFG->enablegroupings)) { 1633 $forum->onlygroups = $modinfo->groups[0]; 1634 $forum->onlygroups[] = -1; 1635 } else if (isset($modinfo->groups[$cm->groupingid])) { 1636 $forum->onlygroups = $modinfo->groups[$cm->groupingid]; 1637 $forum->onlygroups[] = -1; 1638 } else { 1639 $forum->onlygroups = array(-1); 1640 } 1641 } 1642 1643 /// hidden timed discussions 1644 $forum->viewhiddentimedposts = true; 1645 if (!empty($CFG->forum_enabletimedposts)) { 1646 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) { 1647 $forum->viewhiddentimedposts = false; 1648 } 1649 } 1650 1651 /// qanda access 1652 if ($forum->type == 'qanda' 1653 && !has_capability('mod/forum:viewqandawithoutposting', $context)) { 1654 1655 // We need to check whether the user has posted in the qanda forum. 1656 $forum->onlydiscussions = array(); // Holds discussion ids for the discussions 1657 // the user is allowed to see in this forum. 1658 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) { 1659 foreach ($discussionspostedin as $d) { 1660 $forum->onlydiscussions[] = $d->id; 1661 } 1662 } 1663 } 1664 1665 $readableforums[$forum->id] = $forum; 1666 } 1667 1668 unset($modinfo); 1669 1670 } // End foreach $courses 1671 1672 //print_object($courses); 1673 //print_object($readableforums); 1674 1675 return $readableforums; 1676 } 1677 1678 /** 1679 * Returns a list of posts found using an array of search terms. 1680 * @param $searchterms - array of search terms, e.g. word +word -word 1681 * @param $courseid - if 0, we search through the whole site 1682 * @param $page 1683 * @param $recordsperpage=50 1684 * @param &$totalcount 1685 * @param $extrasql 1686 * @return array of posts found 1687 */ 1688 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50, 1689 &$totalcount, $extrasql='') { 1690 global $CFG, $USER; 1691 require_once($CFG->libdir.'/searchlib.php'); 1692 1693 $forums = forum_get_readable_forums($USER->id, $courseid);