[ Index ]

PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008]

title

Body

[close]

/mod/forum/ -> lib.php (source)

   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.'&amp;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&amp;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> &raquo; '.
 892      '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
 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 .= ' &raquo; <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>&nbsp;
 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.'&amp;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);