[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * @package   mod_forum
  19   * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
  20   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21   */
  22  
  23  defined('MOODLE_INTERNAL') || die();
  24  
  25  /** Include required files */
  26  require_once (__DIR__ . '/deprecatedlib.php');
  27  require_once($CFG->libdir.'/filelib.php');
  28  require_once($CFG->libdir.'/eventslib.php');
  29  
  30  /// CONSTANTS ///////////////////////////////////////////////////////////
  31  
  32  define('FORUM_MODE_FLATOLDEST', 1);
  33  define('FORUM_MODE_FLATNEWEST', -1);
  34  define('FORUM_MODE_THREADED', 2);
  35  define('FORUM_MODE_NESTED', 3);
  36  
  37  define('FORUM_CHOOSESUBSCRIBE', 0);
  38  define('FORUM_FORCESUBSCRIBE', 1);
  39  define('FORUM_INITIALSUBSCRIBE', 2);
  40  define('FORUM_DISALLOWSUBSCRIBE',3);
  41  
  42  /**
  43   * FORUM_TRACKING_OFF - Tracking is not available for this forum.
  44   */
  45  define('FORUM_TRACKING_OFF', 0);
  46  
  47  /**
  48   * FORUM_TRACKING_OPTIONAL - Tracking is based on user preference.
  49   */
  50  define('FORUM_TRACKING_OPTIONAL', 1);
  51  
  52  /**
  53   * FORUM_TRACKING_FORCED - Tracking is on, regardless of user setting.
  54   * Treated as FORUM_TRACKING_OPTIONAL if $CFG->forum_allowforcedreadtracking is off.
  55   */
  56  define('FORUM_TRACKING_FORCED', 2);
  57  
  58  define('FORUM_MAILED_PENDING', 0);
  59  define('FORUM_MAILED_SUCCESS', 1);
  60  define('FORUM_MAILED_ERROR', 2);
  61  
  62  if (!defined('FORUM_CRON_USER_CACHE')) {
  63      /** Defines how many full user records are cached in forum cron. */
  64      define('FORUM_CRON_USER_CACHE', 5000);
  65  }
  66  
  67  /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
  68  
  69  /**
  70   * Given an object containing all the necessary data,
  71   * (defined by the form in mod_form.php) this function
  72   * will create a new instance and return the id number
  73   * of the new instance.
  74   *
  75   * @param stdClass $forum add forum instance
  76   * @param mod_forum_mod_form $mform
  77   * @return int intance id
  78   */
  79  function forum_add_instance($forum, $mform = null) {
  80      global $CFG, $DB;
  81  
  82      $forum->timemodified = time();
  83  
  84      if (empty($forum->assessed)) {
  85          $forum->assessed = 0;
  86      }
  87  
  88      if (empty($forum->ratingtime) or empty($forum->assessed)) {
  89          $forum->assesstimestart  = 0;
  90          $forum->assesstimefinish = 0;
  91      }
  92  
  93      $forum->id = $DB->insert_record('forum', $forum);
  94      $modcontext = context_module::instance($forum->coursemodule);
  95  
  96      if ($forum->type == 'single') {  // Create related discussion.
  97          $discussion = new stdClass();
  98          $discussion->course        = $forum->course;
  99          $discussion->forum         = $forum->id;
 100          $discussion->name          = $forum->name;
 101          $discussion->assessed      = $forum->assessed;
 102          $discussion->message       = $forum->intro;
 103          $discussion->messageformat = $forum->introformat;
 104          $discussion->messagetrust  = trusttext_trusted(context_course::instance($forum->course));
 105          $discussion->mailnow       = false;
 106          $discussion->groupid       = -1;
 107  
 108          $message = '';
 109  
 110          $discussion->id = forum_add_discussion($discussion, null, $message);
 111  
 112          if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
 113              // Ugly hack - we need to copy the files somehow.
 114              $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
 115              $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
 116  
 117              $options = array('subdirs'=>true); // Use the same options as intro field!
 118              $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
 119              $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
 120          }
 121      }
 122  
 123      forum_grade_item_update($forum);
 124  
 125      return $forum->id;
 126  }
 127  
 128  /**
 129   * Handle changes following the creation of a forum instance.
 130   * This function is typically called by the course_module_created observer.
 131   *
 132   * @param object $context the forum context
 133   * @param stdClass $forum The forum object
 134   * @return void
 135   */
 136  function forum_instance_created($context, $forum) {
 137      if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
 138          $users = \mod_forum\subscriptions::get_potential_subscribers($context, 0, 'u.id, u.email');
 139          foreach ($users as $user) {
 140              \mod_forum\subscriptions::subscribe_user($user->id, $forum, $context);
 141          }
 142      }
 143  }
 144  
 145  /**
 146   * Given an object containing all the necessary data,
 147   * (defined by the form in mod_form.php) this function
 148   * will update an existing instance with new data.
 149   *
 150   * @global object
 151   * @param object $forum forum instance (with magic quotes)
 152   * @return bool success
 153   */
 154  function forum_update_instance($forum, $mform) {
 155      global $DB, $OUTPUT, $USER;
 156  
 157      $forum->timemodified = time();
 158      $forum->id           = $forum->instance;
 159  
 160      if (empty($forum->assessed)) {
 161          $forum->assessed = 0;
 162      }
 163  
 164      if (empty($forum->ratingtime) or empty($forum->assessed)) {
 165          $forum->assesstimestart  = 0;
 166          $forum->assesstimefinish = 0;
 167      }
 168  
 169      $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
 170  
 171      // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
 172      // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
 173      // for count and sum aggregation types the grade we check to make sure they do not exceed the scale (i.e. max score) when calculating the grade
 174      if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
 175          forum_update_grades($forum); // recalculate grades for the forum
 176      }
 177  
 178      if ($forum->type == 'single') {  // Update related discussion and post.
 179          $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
 180          if (!empty($discussions)) {
 181              if (count($discussions) > 1) {
 182                  echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
 183              }
 184              $discussion = array_pop($discussions);
 185          } else {
 186              // try to recover by creating initial discussion - MDL-16262
 187              $discussion = new stdClass();
 188              $discussion->course          = $forum->course;
 189              $discussion->forum           = $forum->id;
 190              $discussion->name            = $forum->name;
 191              $discussion->assessed        = $forum->assessed;
 192              $discussion->message         = $forum->intro;
 193              $discussion->messageformat   = $forum->introformat;
 194              $discussion->messagetrust    = true;
 195              $discussion->mailnow         = false;
 196              $discussion->groupid         = -1;
 197  
 198              $message = '';
 199  
 200              forum_add_discussion($discussion, null, $message);
 201  
 202              if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
 203                  print_error('cannotadd', 'forum');
 204              }
 205          }
 206          if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
 207              print_error('cannotfindfirstpost', 'forum');
 208          }
 209  
 210          $cm         = get_coursemodule_from_instance('forum', $forum->id);
 211          $modcontext = context_module::instance($cm->id, MUST_EXIST);
 212  
 213          $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
 214          $post->subject       = $forum->name;
 215          $post->message       = $forum->intro;
 216          $post->messageformat = $forum->introformat;
 217          $post->messagetrust  = trusttext_trusted($modcontext);
 218          $post->modified      = $forum->timemodified;
 219          $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities.
 220  
 221          if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
 222              // Ugly hack - we need to copy the files somehow.
 223              $options = array('subdirs'=>true); // Use the same options as intro field!
 224              $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
 225          }
 226  
 227          $DB->update_record('forum_posts', $post);
 228          $discussion->name = $forum->name;
 229          $DB->update_record('forum_discussions', $discussion);
 230      }
 231  
 232      $DB->update_record('forum', $forum);
 233  
 234      $modcontext = context_module::instance($forum->coursemodule);
 235      if (($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) && ($oldforum->forcesubscribe <> $forum->forcesubscribe)) {
 236          $users = \mod_forum\subscriptions::get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
 237          foreach ($users as $user) {
 238              \mod_forum\subscriptions::subscribe_user($user->id, $forum, $modcontext);
 239          }
 240      }
 241  
 242      forum_grade_item_update($forum);
 243  
 244      return true;
 245  }
 246  
 247  
 248  /**
 249   * Given an ID of an instance of this module,
 250   * this function will permanently delete the instance
 251   * and any data that depends on it.
 252   *
 253   * @global object
 254   * @param int $id forum instance id
 255   * @return bool success
 256   */
 257  function forum_delete_instance($id) {
 258      global $DB;
 259  
 260      if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
 261          return false;
 262      }
 263      if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
 264          return false;
 265      }
 266      if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
 267          return false;
 268      }
 269  
 270      $context = context_module::instance($cm->id);
 271  
 272      // now get rid of all files
 273      $fs = get_file_storage();
 274      $fs->delete_area_files($context->id);
 275  
 276      $result = true;
 277  
 278      // Delete digest and subscription preferences.
 279      $DB->delete_records('forum_digests', array('forum' => $forum->id));
 280      $DB->delete_records('forum_subscriptions', array('forum'=>$forum->id));
 281      $DB->delete_records('forum_discussion_subs', array('forum' => $forum->id));
 282  
 283      if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
 284          foreach ($discussions as $discussion) {
 285              if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
 286                  $result = false;
 287              }
 288          }
 289      }
 290  
 291      forum_tp_delete_read_records(-1, -1, -1, $forum->id);
 292  
 293      if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
 294          $result = false;
 295      }
 296  
 297      forum_grade_item_delete($forum);
 298  
 299      return $result;
 300  }
 301  
 302  
 303  /**
 304   * Indicates API features that the forum supports.
 305   *
 306   * @uses FEATURE_GROUPS
 307   * @uses FEATURE_GROUPINGS
 308   * @uses FEATURE_MOD_INTRO
 309   * @uses FEATURE_COMPLETION_TRACKS_VIEWS
 310   * @uses FEATURE_COMPLETION_HAS_RULES
 311   * @uses FEATURE_GRADE_HAS_GRADE
 312   * @uses FEATURE_GRADE_OUTCOMES
 313   * @param string $feature
 314   * @return mixed True if yes (some features may use other values)
 315   */
 316  function forum_supports($feature) {
 317      switch($feature) {
 318          case FEATURE_GROUPS:                  return true;
 319          case FEATURE_GROUPINGS:               return true;
 320          case FEATURE_MOD_INTRO:               return true;
 321          case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
 322          case FEATURE_COMPLETION_HAS_RULES:    return true;
 323          case FEATURE_GRADE_HAS_GRADE:         return true;
 324          case FEATURE_GRADE_OUTCOMES:          return true;
 325          case FEATURE_RATE:                    return true;
 326          case FEATURE_BACKUP_MOODLE2:          return true;
 327          case FEATURE_SHOW_DESCRIPTION:        return true;
 328          case FEATURE_PLAGIARISM:              return true;
 329  
 330          default: return null;
 331      }
 332  }
 333  
 334  
 335  /**
 336   * Obtains the automatic completion state for this forum based on any conditions
 337   * in forum settings.
 338   *
 339   * @global object
 340   * @global object
 341   * @param object $course Course
 342   * @param object $cm Course-module
 343   * @param int $userid User ID
 344   * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
 345   * @return bool True if completed, false if not. (If no conditions, then return
 346   *   value depends on comparison type)
 347   */
 348  function forum_get_completion_state($course,$cm,$userid,$type) {
 349      global $CFG,$DB;
 350  
 351      // Get forum details
 352      if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
 353          throw new Exception("Can't find forum {$cm->instance}");
 354      }
 355  
 356      $result=$type; // Default return value
 357  
 358      $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
 359      $postcountsql="
 360  SELECT
 361      COUNT(1)
 362  FROM
 363      {forum_posts} fp
 364      INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
 365  WHERE
 366      fp.userid=:userid AND fd.forum=:forumid";
 367  
 368      if ($forum->completiondiscussions) {
 369          $value = $forum->completiondiscussions <=
 370                   $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
 371          if ($type == COMPLETION_AND) {
 372              $result = $result && $value;
 373          } else {
 374              $result = $result || $value;
 375          }
 376      }
 377      if ($forum->completionreplies) {
 378          $value = $forum->completionreplies <=
 379                   $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
 380          if ($type==COMPLETION_AND) {
 381              $result = $result && $value;
 382          } else {
 383              $result = $result || $value;
 384          }
 385      }
 386      if ($forum->completionposts) {
 387          $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
 388          if ($type == COMPLETION_AND) {
 389              $result = $result && $value;
 390          } else {
 391              $result = $result || $value;
 392          }
 393      }
 394  
 395      return $result;
 396  }
 397  
 398  /**
 399   * Create a message-id string to use in the custom headers of forum notification emails
 400   *
 401   * message-id is used by email clients to identify emails and to nest conversations
 402   *
 403   * @param int $postid The ID of the forum post we are notifying the user about
 404   * @param int $usertoid The ID of the user being notified
 405   * @param string $hostname The server's hostname
 406   * @return string A unique message-id
 407   */
 408  function forum_get_email_message_id($postid, $usertoid, $hostname) {
 409      return '<'.hash('sha256',$postid.'to'.$usertoid).'@'.$hostname.'>';
 410  }
 411  
 412  /**
 413   * Removes properties from user record that are not necessary
 414   * for sending post notifications.
 415   * @param stdClass $user
 416   * @return void, $user parameter is modified
 417   */
 418  function forum_cron_minimise_user_record(stdClass $user) {
 419  
 420      // We store large amount of users in one huge array,
 421      // make sure we do not store info there we do not actually need
 422      // in mail generation code or messaging.
 423  
 424      unset($user->institution);
 425      unset($user->department);
 426      unset($user->address);
 427      unset($user->city);
 428      unset($user->url);
 429      unset($user->currentlogin);
 430      unset($user->description);
 431      unset($user->descriptionformat);
 432  }
 433  
 434  /**
 435   * Function to be run periodically according to the scheduled task.
 436   *
 437   * Finds all posts that have yet to be mailed out, and mails them
 438   * out to all subscribers as well as other maintance tasks.
 439   *
 440   * NOTE: Since 2.7.2 this function is run by scheduled task rather
 441   * than standard cron.
 442   *
 443   * @todo MDL-44734 The function will be split up into seperate tasks.
 444   */
 445  function forum_cron() {
 446      global $CFG, $USER, $DB;
 447  
 448      $site = get_site();
 449  
 450      // All users that are subscribed to any post that needs sending,
 451      // please increase $CFG->extramemorylimit on large sites that
 452      // send notifications to a large number of users.
 453      $users = array();
 454      $userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!!
 455  
 456      // Status arrays.
 457      $mailcount  = array();
 458      $errorcount = array();
 459  
 460      // caches
 461      $discussions        = array();
 462      $forums             = array();
 463      $courses            = array();
 464      $coursemodules      = array();
 465      $subscribedusers    = array();
 466      $messageinboundhandlers = array();
 467  
 468      // Posts older than 2 days will not be mailed.  This is to avoid the problem where
 469      // cron has not been running for a long time, and then suddenly people are flooded
 470      // with mail from the past few weeks or months
 471      $timenow   = time();
 472      $endtime   = $timenow - $CFG->maxeditingtime;
 473      $starttime = $endtime - 48 * 3600;   // Two days earlier
 474  
 475      // Get the list of forum subscriptions for per-user per-forum maildigest settings.
 476      $digestsset = $DB->get_recordset('forum_digests', null, '', 'id, userid, forum, maildigest');
 477      $digests = array();
 478      foreach ($digestsset as $thisrow) {
 479          if (!isset($digests[$thisrow->forum])) {
 480              $digests[$thisrow->forum] = array();
 481          }
 482          $digests[$thisrow->forum][$thisrow->userid] = $thisrow->maildigest;
 483      }
 484      $digestsset->close();
 485  
 486      // Create the generic messageinboundgenerator.
 487      $messageinboundgenerator = new \core\message\inbound\address_manager();
 488      $messageinboundgenerator->set_handler('\mod_forum\message\inbound\reply_handler');
 489  
 490      if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
 491          // Mark them all now as being mailed.  It's unlikely but possible there
 492          // might be an error later so that a post is NOT actually mailed out,
 493          // but since mail isn't crucial, we can accept this risk.  Doing it now
 494          // prevents the risk of duplicated mails, which is a worse problem.
 495  
 496          if (!forum_mark_old_posts_as_mailed($endtime)) {
 497              mtrace('Errors occurred while trying to mark some posts as being mailed.');
 498              return false;  // Don't continue trying to mail them, in case we are in a cron loop
 499          }
 500  
 501          // checking post validity, and adding users to loop through later
 502          foreach ($posts as $pid => $post) {
 503  
 504              $discussionid = $post->discussion;
 505              if (!isset($discussions[$discussionid])) {
 506                  if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
 507                      $discussions[$discussionid] = $discussion;
 508                      \mod_forum\subscriptions::fill_subscription_cache($discussion->forum);
 509                      \mod_forum\subscriptions::fill_discussion_subscription_cache($discussion->forum);
 510  
 511                  } else {
 512                      mtrace('Could not find discussion ' . $discussionid);
 513                      unset($posts[$pid]);
 514                      continue;
 515                  }
 516              }
 517              $forumid = $discussions[$discussionid]->forum;
 518              if (!isset($forums[$forumid])) {
 519                  if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
 520                      $forums[$forumid] = $forum;
 521                  } else {
 522                      mtrace('Could not find forum '.$forumid);
 523                      unset($posts[$pid]);
 524                      continue;
 525                  }
 526              }
 527              $courseid = $forums[$forumid]->course;
 528              if (!isset($courses[$courseid])) {
 529                  if ($course = $DB->get_record('course', array('id' => $courseid))) {
 530                      $courses[$courseid] = $course;
 531                  } else {
 532                      mtrace('Could not find course '.$courseid);
 533                      unset($posts[$pid]);
 534                      continue;
 535                  }
 536              }
 537              if (!isset($coursemodules[$forumid])) {
 538                  if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
 539                      $coursemodules[$forumid] = $cm;
 540                  } else {
 541                      mtrace('Could not find course module for forum '.$forumid);
 542                      unset($posts[$pid]);
 543                      continue;
 544                  }
 545              }
 546  
 547              // Save the Inbound Message datakey here to reduce DB queries later.
 548              $messageinboundgenerator->set_data($pid);
 549              $messageinboundhandlers[$pid] = $messageinboundgenerator->fetch_data_key();
 550  
 551              // Caching subscribed users of each forum.
 552              if (!isset($subscribedusers[$forumid])) {
 553                  $modcontext = context_module::instance($coursemodules[$forumid]->id);
 554                  if ($subusers = \mod_forum\subscriptions::fetch_subscribed_users($forums[$forumid], 0, $modcontext, 'u.*', true)) {
 555  
 556                      foreach ($subusers as $postuser) {
 557                          // this user is subscribed to this forum
 558                          $subscribedusers[$forumid][$postuser->id] = $postuser->id;
 559                          $userscount++;
 560                          if ($userscount > FORUM_CRON_USER_CACHE) {
 561                              // Store minimal user info.
 562                              $minuser = new stdClass();
 563                              $minuser->id = $postuser->id;
 564                              $users[$postuser->id] = $minuser;
 565                          } else {
 566                              // Cache full user record.
 567                              forum_cron_minimise_user_record($postuser);
 568                              $users[$postuser->id] = $postuser;
 569                          }
 570                      }
 571                      // Release memory.
 572                      unset($subusers);
 573                      unset($postuser);
 574                  }
 575              }
 576              $mailcount[$pid] = 0;
 577              $errorcount[$pid] = 0;
 578          }
 579      }
 580  
 581      if ($users && $posts) {
 582  
 583          $urlinfo = parse_url($CFG->wwwroot);
 584          $hostname = $urlinfo['host'];
 585  
 586          foreach ($users as $userto) {
 587              // Terminate if processing of any account takes longer than 2 minutes.
 588              core_php_time_limit::raise(120);
 589  
 590              mtrace('Processing user ' . $userto->id);
 591  
 592              // Init user caches - we keep the cache for one cycle only, otherwise it could consume too much memory.
 593              if (isset($userto->username)) {
 594                  $userto = clone($userto);
 595              } else {
 596                  $userto = $DB->get_record('user', array('id' => $userto->id));
 597                  forum_cron_minimise_user_record($userto);
 598              }
 599              $userto->viewfullnames = array();
 600              $userto->canpost       = array();
 601              $userto->markposts     = array();
 602  
 603              // Setup this user so that the capabilities are cached, and environment matches receiving user.
 604              cron_setup_user($userto);
 605  
 606              // Reset the caches.
 607              foreach ($coursemodules as $forumid => $unused) {
 608                  $coursemodules[$forumid]->cache       = new stdClass();
 609                  $coursemodules[$forumid]->cache->caps = array();
 610                  unset($coursemodules[$forumid]->uservisible);
 611              }
 612  
 613              foreach ($posts as $pid => $post) {
 614                  $discussion = $discussions[$post->discussion];
 615                  $forum      = $forums[$discussion->forum];
 616                  $course     = $courses[$forum->course];
 617                  $cm         =& $coursemodules[$forum->id];
 618  
 619                  // Do some checks to see if we can bail out now.
 620  
 621                  // Only active enrolled users are in the list of subscribers.
 622                  // This does not necessarily mean that the user is subscribed to the forum or to the discussion though.
 623                  if (!isset($subscribedusers[$forum->id][$userto->id])) {
 624                      // The user does not subscribe to this forum.
 625                      continue;
 626                  }
 627  
 628                  if (!\mod_forum\subscriptions::is_subscribed($userto->id, $forum, $post->discussion, $coursemodules[$forum->id])) {
 629                      // The user does not subscribe to this forum, or to this specific discussion.
 630                      continue;
 631                  }
 632  
 633                  if ($subscriptiontime = \mod_forum\subscriptions::fetch_discussion_subscription($forum->id, $userto->id)) {
 634                      // Skip posts if the user subscribed to the discussion after it was created.
 635                      if (isset($subscriptiontime[$post->discussion]) && ($subscriptiontime[$post->discussion] > $post->created)) {
 636                          continue;
 637                      }
 638                  }
 639  
 640                  // Don't send email if the forum is Q&A and the user has not posted.
 641                  // Initial topics are still mailed.
 642                  if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
 643                      mtrace('Did not email ' . $userto->id.' because user has not posted in discussion');
 644                      continue;
 645                  }
 646  
 647                  // Get info about the sending user.
 648                  if (array_key_exists($post->userid, $users)) {
 649                      // We might know the user already.
 650                      $userfrom = $users[$post->userid];
 651                      if (!isset($userfrom->idnumber)) {
 652                          // Minimalised user info, fetch full record.
 653                          $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
 654                          forum_cron_minimise_user_record($userfrom);
 655                      }
 656  
 657                  } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
 658                      forum_cron_minimise_user_record($userfrom);
 659                      // Fetch only once if possible, we can add it to user list, it will be skipped anyway.
 660                      if ($userscount <= FORUM_CRON_USER_CACHE) {
 661                          $userscount++;
 662                          $users[$userfrom->id] = $userfrom;
 663                      }
 664                  } else {
 665                      mtrace('Could not find user ' . $post->userid . ', author of post ' . $post->id . '. Unable to send message.');
 666                      continue;
 667                  }
 668  
 669                  // Note: If we want to check that userto and userfrom are not the same person this is probably the spot to do it.
 670  
 671                  // Setup global $COURSE properly - needed for roles and languages.
 672                  cron_setup_user($userto, $course);
 673  
 674                  // Fill caches.
 675                  if (!isset($userto->viewfullnames[$forum->id])) {
 676                      $modcontext = context_module::instance($cm->id);
 677                      $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
 678                  }
 679                  if (!isset($userto->canpost[$discussion->id])) {
 680                      $modcontext = context_module::instance($cm->id);
 681                      $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
 682                  }
 683                  if (!isset($userfrom->groups[$forum->id])) {
 684                      if (!isset($userfrom->groups)) {
 685                          $userfrom->groups = array();
 686                          if (isset($users[$userfrom->id])) {
 687                              $users[$userfrom->id]->groups = array();
 688                          }
 689                      }
 690                      $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
 691                      if (isset($users[$userfrom->id])) {
 692                          $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
 693                      }
 694                  }
 695  
 696                  // Make sure groups allow this user to see this email.
 697                  if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {
 698                      // Groups are being used.
 699                      if (!groups_group_exists($discussion->groupid)) {
 700                          // Can't find group - be safe and don't this message.
 701                          continue;
 702                      }
 703  
 704                      if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
 705                          // Do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS.
 706                          continue;
 707                      }
 708                  }
 709  
 710                  // Make sure we're allowed to see the post.
 711                  if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm)) {
 712                      mtrace('User ' . $userto->id .' can not see ' . $post->id . '. Not sending message.');
 713                      continue;
 714                  }
 715  
 716                  // OK so we need to send the email.
 717  
 718                  // Does the user want this post in a digest?  If so postpone it for now.
 719                  $maildigest = forum_get_user_maildigest_bulk($digests, $userto, $forum->id);
 720  
 721                  if ($maildigest > 0) {
 722                      // This user wants the mails to be in digest form.
 723                      $queue = new stdClass();
 724                      $queue->userid       = $userto->id;
 725                      $queue->discussionid = $discussion->id;
 726                      $queue->postid       = $post->id;
 727                      $queue->timemodified = $post->created;
 728                      $DB->insert_record('forum_queue', $queue);
 729                      continue;
 730                  }
 731  
 732                  // Prepare to actually send the post now, and build up the content.
 733  
 734                  $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
 735  
 736                  $userfrom->customheaders = array (
 737                      // Headers to make emails easier to track.
 738                      'Precedence: Bulk',
 739                      'List-Id: "'        . $cleanforumname . '" <moodleforum' . $forum->id . '@' . $hostname.'>',
 740                      'List-Help: '       . $CFG->wwwroot . '/mod/forum/view.php?f=' . $forum->id,
 741                      'Message-ID: '      . forum_get_email_message_id($post->id, $userto->id, $hostname),
 742                      'X-Course-Id: '     . $course->id,
 743                      'X-Course-Name: '   . format_string($course->fullname, true)
 744                  );
 745  
 746                  if ($post->parent) {
 747                      // This post is a reply, so add headers for threading (see MDL-22551).
 748                      $userfrom->customheaders[] = 'In-Reply-To: ' . forum_get_email_message_id($post->parent, $userto->id, $hostname);
 749                      $userfrom->customheaders[] = 'References: '  . forum_get_email_message_id($post->parent, $userto->id, $hostname);
 750                  }
 751  
 752                  $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
 753  
 754                  // Generate a reply-to address from using the Inbound Message handler.
 755                  $replyaddress = null;
 756                  if ($userto->canpost[$discussion->id] && array_key_exists($post->id, $messageinboundhandlers)) {
 757                      $messageinboundgenerator->set_data($post->id, $messageinboundhandlers[$post->id]);
 758                      $replyaddress = $messageinboundgenerator->generate($userto->id);
 759                  }
 760  
 761                  $a = new stdClass();
 762                  $a->courseshortname = $shortname;
 763                  $a->forumname = $cleanforumname;
 764                  $a->subject = format_string($post->subject, true);
 765                  $postsubject = html_to_text(get_string('postmailsubject', 'forum', $a));
 766                  $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false,
 767                          $replyaddress);
 768                  $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
 769                          $replyaddress);
 770  
 771                  // Send the post now!
 772                  mtrace('Sending ', '');
 773  
 774                  $eventdata = new stdClass();
 775                  $eventdata->component           = 'mod_forum';
 776                  $eventdata->name                = 'posts';
 777                  $eventdata->userfrom            = $userfrom;
 778                  $eventdata->userto              = $userto;
 779                  $eventdata->subject             = $postsubject;
 780                  $eventdata->fullmessage         = $posttext;
 781                  $eventdata->fullmessageformat   = FORMAT_PLAIN;
 782                  $eventdata->fullmessagehtml     = $posthtml;
 783                  $eventdata->notification        = 1;
 784                  $eventdata->replyto             = $replyaddress;
 785  
 786                  // If forum_replytouser is not set then send mail using the noreplyaddress.
 787                  if (empty($CFG->forum_replytouser)) {
 788                      // Clone userfrom as it is referenced by $users.
 789                      $cloneduserfrom = clone($userfrom);
 790                      $cloneduserfrom->email = $CFG->noreplyaddress;
 791                      $eventdata->userfrom = $cloneduserfrom;
 792                  }
 793  
 794                  $smallmessagestrings = new stdClass();
 795                  $smallmessagestrings->user          = fullname($userfrom);
 796                  $smallmessagestrings->forumname     = "$shortname: " . format_string($forum->name, true) . ": " . $discussion->name;
 797                  $smallmessagestrings->message       = $post->message;
 798  
 799                  // Make sure strings are in message recipients language.
 800                  $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
 801  
 802                  $contexturl = new moodle_url('/mod/forum/discuss.php', array('d' => $discussion->id), 'p' . $post->id);
 803                  $eventdata->contexturl = $contexturl->out();
 804                  $eventdata->contexturlname = $discussion->name;
 805  
 806                  $mailresult = message_send($eventdata);
 807                  if (!$mailresult) {
 808                      mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
 809                              " ($userto->email) .. not trying again.");
 810                      $errorcount[$post->id]++;
 811                  } else {
 812                      $mailcount[$post->id]++;
 813  
 814                      // Mark post as read if forum_usermarksread is set off.
 815                      if (!$CFG->forum_usermarksread) {
 816                          $userto->markposts[$post->id] = $post->id;
 817                      }
 818                  }
 819  
 820                  mtrace('post ' . $post->id . ': ' . $post->subject);
 821              }
 822  
 823              // Mark processed posts as read.
 824              forum_tp_mark_posts_read($userto, $userto->markposts);
 825              unset($userto);
 826          }
 827      }
 828  
 829      if ($posts) {
 830          foreach ($posts as $post) {
 831              mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
 832              if ($errorcount[$post->id]) {
 833                  $DB->set_field('forum_posts', 'mailed', FORUM_MAILED_ERROR, array('id' => $post->id));
 834              }
 835          }
 836      }
 837  
 838      // release some memory
 839      unset($subscribedusers);
 840      unset($mailcount);
 841      unset($errorcount);
 842  
 843      cron_setup_user();
 844  
 845      $sitetimezone = $CFG->timezone;
 846  
 847      // Now see if there are any digest mails waiting to be sent, and if we should send them
 848  
 849      mtrace('Starting digest processing...');
 850  
 851      core_php_time_limit::raise(300); // terminate if not able to fetch all digests in 5 minutes
 852  
 853      if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
 854          set_config('digestmailtimelast', 0);
 855      }
 856  
 857      $timenow = time();
 858      $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
 859  
 860      // Delete any really old ones (normally there shouldn't be any)
 861      $weekago = $timenow - (7 * 24 * 3600);
 862      $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
 863      mtrace ('Cleaned old digest records');
 864  
 865      if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
 866  
 867          mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
 868  
 869          $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
 870  
 871          if ($digestposts_rs->valid()) {
 872  
 873              // We have work to do
 874              $usermailcount = 0;
 875  
 876              //caches - reuse the those filled before too
 877              $discussionposts = array();
 878              $userdiscussions = array();
 879  
 880              foreach ($digestposts_rs as $digestpost) {
 881                  if (!isset($posts[$digestpost->postid])) {
 882                      if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
 883                          $posts[$digestpost->postid] = $post;
 884                      } else {
 885                          continue;
 886                      }
 887                  }
 888                  $discussionid = $digestpost->discussionid;
 889                  if (!isset($discussions[$discussionid])) {
 890                      if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
 891                          $discussions[$discussionid] = $discussion;
 892                      } else {
 893                          continue;
 894                      }
 895                  }
 896                  $forumid = $discussions[$discussionid]->forum;
 897                  if (!isset($forums[$forumid])) {
 898                      if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
 899                          $forums[$forumid] = $forum;
 900                      } else {
 901                          continue;
 902                      }
 903                  }
 904  
 905                  $courseid = $forums[$forumid]->course;
 906                  if (!isset($courses[$courseid])) {
 907                      if ($course = $DB->get_record('course', array('id' => $courseid))) {
 908                          $courses[$courseid] = $course;
 909                      } else {
 910                          continue;
 911                      }
 912                  }
 913  
 914                  if (!isset($coursemodules[$forumid])) {
 915                      if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
 916                          $coursemodules[$forumid] = $cm;
 917                      } else {
 918                          continue;
 919                      }
 920                  }
 921                  $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
 922                  $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
 923              }
 924              $digestposts_rs->close(); /// Finished iteration, let's close the resultset
 925  
 926              // Data collected, start sending out emails to each user
 927              foreach ($userdiscussions as $userid => $thesediscussions) {
 928  
 929                  core_php_time_limit::raise(120); // terminate if processing of any account takes longer than 2 minutes
 930  
 931                  cron_setup_user();
 932  
 933                  mtrace(get_string('processingdigest', 'forum', $userid), '... ');
 934  
 935                  // First of all delete all the queue entries for this user
 936                  $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
 937  
 938                  // Init user caches - we keep the cache for one cycle only,
 939                  // otherwise it would unnecessarily consume memory.
 940                  if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
 941                      $userto = clone($users[$userid]);
 942                  } else {
 943                      $userto = $DB->get_record('user', array('id' => $userid));
 944                      forum_cron_minimise_user_record($userto);
 945                  }
 946                  $userto->viewfullnames = array();
 947                  $userto->canpost       = array();
 948                  $userto->markposts     = array();
 949  
 950                  // Override the language and timezone of the "current" user, so that
 951                  // mail is customised for the receiver.
 952                  cron_setup_user($userto);
 953  
 954                  $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
 955  
 956                  $headerdata = new stdClass();
 957                  $headerdata->sitename = format_string($site->fullname, true);
 958                  $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
 959  
 960                  $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
 961                  $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
 962  
 963                  $posthtml = "<head>";
 964  /*                foreach ($CFG->stylesheets as $stylesheet) {
 965                      //TODO: MDL-21120
 966                      $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
 967                  }*/
 968                  $posthtml .= "</head>\n<body id=\"email\">\n";
 969                  $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
 970  
 971                  foreach ($thesediscussions as $discussionid) {
 972  
 973                      core_php_time_limit::raise(120);   // to be reset for each post
 974  
 975                      $discussion = $discussions[$discussionid];
 976                      $forum      = $forums[$discussion->forum];
 977                      $course     = $courses[$forum->course];
 978                      $cm         = $coursemodules[$forum->id];
 979  
 980                      //override language
 981                      cron_setup_user($userto, $course);
 982  
 983                      // Fill caches
 984                      if (!isset($userto->viewfullnames[$forum->id])) {
 985                          $modcontext = context_module::instance($cm->id);
 986                          $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
 987                      }
 988                      if (!isset($userto->canpost[$discussion->id])) {
 989                          $modcontext = context_module::instance($cm->id);
 990                          $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
 991                      }
 992  
 993                      $strforums      = get_string('forums', 'forum');
 994                      $canunsubscribe = ! \mod_forum\subscriptions::is_forcesubscribed($forum);
 995                      $canreply       = $userto->canpost[$discussion->id];
 996                      $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
 997  
 998                      $posttext .= "\n \n";
 999                      $posttext .= '=====================================================================';
1000                      $posttext .= "\n \n";
1001                      $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
1002                      if ($discussion->name != $forum->name) {
1003                          $posttext  .= " -> ".format_string($discussion->name,true);
1004                      }
1005                      $posttext .= "\n";
1006                      $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
1007                      $posttext .= "\n";
1008  
1009                      $posthtml .= "<p><font face=\"sans-serif\">".
1010                      "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
1011                      "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
1012                      "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
1013                      if ($discussion->name == $forum->name) {
1014                          $posthtml .= "</font></p>";
1015                      } else {
1016                          $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
1017                      }
1018                      $posthtml .= '<p>';
1019  
1020                      $postsarray = $discussionposts[$discussionid];
1021                      sort($postsarray);
1022  
1023                      foreach ($postsarray as $postid) {
1024                          $post = $posts[$postid];
1025  
1026                          if (array_key_exists($post->userid, $users)) { // we might know him/her already
1027                              $userfrom = $users[$post->userid];
1028                              if (!isset($userfrom->idnumber)) {
1029                                  $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
1030                                  forum_cron_minimise_user_record($userfrom);
1031                              }
1032  
1033                          } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
1034                              forum_cron_minimise_user_record($userfrom);
1035                              if ($userscount <= FORUM_CRON_USER_CACHE) {
1036                                  $userscount++;
1037                                  $users[$userfrom->id] = $userfrom;
1038                              }
1039  
1040                          } else {
1041                              mtrace('Could not find user '.$post->userid);
1042                              continue;
1043                          }
1044  
1045                          if (!isset($userfrom->groups[$forum->id])) {
1046                              if (!isset($userfrom->groups)) {
1047                                  $userfrom->groups = array();
1048                                  if (isset($users[$userfrom->id])) {
1049                                      $users[$userfrom->id]->groups = array();
1050                                  }
1051                              }
1052                              $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
1053                              if (isset($users[$userfrom->id])) {
1054                                  $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
1055                              }
1056                          }
1057  
1058                          $userfrom->customheaders = array ("Precedence: Bulk");
1059  
1060                          $maildigest = forum_get_user_maildigest_bulk($digests, $userto, $forum->id);
1061                          if ($maildigest == 2) {
1062                              // Subjects and link only
1063                              $posttext .= "\n";
1064                              $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
1065                              $by = new stdClass();
1066                              $by->name = fullname($userfrom);
1067                              $by->date = userdate($post->modified);
1068                              $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
1069                              $posttext .= "\n---------------------------------------------------------------------";
1070  
1071                              $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
1072                              $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>';
1073  
1074                          } else {
1075                              // The full treatment
1076                              $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
1077                              $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1078  
1079                          // Create an array of postid's for this user to mark as read.
1080                              if (!$CFG->forum_usermarksread) {
1081                                  $userto->markposts[$post->id] = $post->id;
1082                              }
1083                          }
1084                      }
1085                      $footerlinks = array();
1086                      if ($canunsubscribe) {
1087                          $footerlinks[] = "<a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">" . get_string("unsubscribe", "forum") . "</a>";
1088                      } else {
1089                          $footerlinks[] = get_string("everyoneissubscribed", "forum");
1090                      }
1091                      $footerlinks[] = "<a href='{$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}'>" . get_string("digestmailpost", "forum") . '</a>';
1092                      $posthtml .= "\n<div class='mdl-right'><font size=\"1\">" . implode('&nbsp;', $footerlinks) . '</font></div>';
1093                      $posthtml .= '<hr size="1" noshade="noshade" /></p>';
1094                  }
1095                  $posthtml .= '</body>';
1096  
1097                  if (empty($userto->mailformat) || $userto->mailformat != 1) {
1098                      // This user DOESN'T want to receive HTML
1099                      $posthtml = '';
1100                  }
1101  
1102                  $attachment = $attachname='';
1103                  // Directly email forum digests rather than sending them via messaging, use the
1104                  // site shortname as 'from name', the noreply address will be used by email_to_user.
1105                  $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname);
1106  
1107                  if (!$mailresult) {
1108                      mtrace("ERROR: mod/forum/cron.php: Could not send out digest mail to user $userto->id ".
1109                          "($userto->email)... not trying again.");
1110                  } else {
1111                      mtrace("success.");
1112                      $usermailcount++;
1113  
1114                      // Mark post as read if forum_usermarksread is set off
1115                      forum_tp_mark_posts_read($userto, $userto->markposts);
1116                  }
1117              }
1118          }
1119      /// We have finishied all digest emails, update $CFG->digestmailtimelast
1120          set_config('digestmailtimelast', $timenow);
1121      }
1122  
1123      cron_setup_user();
1124  
1125      if (!empty($usermailcount)) {
1126          mtrace(get_string('digestsentusers', 'forum', $usermailcount));
1127      }
1128  
1129      if (!empty($CFG->forum_lastreadclean)) {
1130          $timenow = time();
1131          if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
1132              set_config('forum_lastreadclean', $timenow);
1133              mtrace('Removing old forum read tracking info...');
1134              forum_tp_clean_read_records();
1135          }
1136      } else {
1137          set_config('forum_lastreadclean', time());
1138      }
1139  
1140      return true;
1141  }
1142  
1143  /**
1144   * Builds and returns the body of the email notification in plain text.
1145   *
1146   * @global object
1147   * @global object
1148   * @uses CONTEXT_MODULE
1149   * @param object $course
1150   * @param object $cm
1151   * @param object $forum
1152   * @param object $discussion
1153   * @param object $post
1154   * @param object $userfrom
1155   * @param object $userto
1156   * @param boolean $bare
1157   * @param string $replyaddress The inbound address that a user can reply to the generated e-mail with. [Since 2.8].
1158   * @return string The email body in plain text format.
1159   */
1160  function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false, $replyaddress = null) {
1161      global $CFG, $USER;
1162  
1163      $modcontext = context_module::instance($cm->id);
1164  
1165      if (!isset($userto->viewfullnames[$forum->id])) {
1166          $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1167      } else {
1168          $viewfullnames = $userto->viewfullnames[$forum->id];
1169      }
1170  
1171      if (!isset($userto->canpost[$discussion->id])) {
1172          $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1173      } else {
1174          $canreply = $userto->canpost[$discussion->id];
1175      }
1176  
1177      $by = New stdClass;
1178      $by->name = fullname($userfrom, $viewfullnames);
1179      $by->date = userdate($post->modified, "", $userto->timezone);
1180  
1181      $strbynameondate = get_string('bynameondate', 'forum', $by);
1182  
1183      $strforums = get_string('forums', 'forum');
1184  
1185      $canunsubscribe = !\mod_forum\subscriptions::is_forcesubscribed($forum);
1186  
1187      $posttext = '';
1188  
1189      if ($replyaddress) {
1190          $posttext .= "--" . get_string('deleteoriginalonreply', 'mod_forum') . "--\n";
1191      }
1192  
1193      if (!$bare) {
1194          $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1195          $posttext  .= "$shortname -> $strforums -> ".format_string($forum->name,true);
1196  
1197          if ($discussion->name != $forum->name) {
1198              $posttext  .= " -> ".format_string($discussion->name,true);
1199          }
1200      }
1201  
1202      // add absolute file links
1203      $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1204  
1205      $posttext .= "\n";
1206      $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
1207      $posttext .= "\n---------------------------------------------------------------------\n";
1208      $posttext .= format_string($post->subject,true);
1209      if ($bare) {
1210          $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1211      }
1212      $posttext .= "\n".$strbynameondate."\n";
1213      $posttext .= "---------------------------------------------------------------------\n";
1214      $posttext .= format_text_email($post->message, $post->messageformat);
1215      $posttext .= "\n\n";
1216      $posttext .= forum_print_attachments($post, $cm, "text");
1217  
1218      if (!$bare) {
1219          if ($canreply) {
1220              $posttext .= "---------------------------------------------------------------------\n";
1221              $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1222              $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1223          }
1224  
1225          if ($canunsubscribe) {
1226              if (\mod_forum\subscriptions::is_subscribed($userto->id, $forum, null, $cm)) {
1227                  // If subscribed to this forum, offer the unsubscribe link.
1228                  $posttext .= "\n---------------------------------------------------------------------\n";
1229                  $posttext .= get_string("unsubscribe", "forum");
1230                  $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1231              }
1232              // Always offer the unsubscribe from discussion link.
1233              $posttext .= "\n---------------------------------------------------------------------\n";
1234              $posttext .= get_string("unsubscribediscussion", "forum");
1235              $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id&amp;d=$discussion->id\n";
1236          }
1237      }
1238  
1239      $posttext .= "\n---------------------------------------------------------------------\n";
1240      $posttext .= get_string("digestmailpost", "forum");
1241      $posttext .= ": {$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}\n";
1242  
1243      if ($replyaddress) {
1244          $posttext .= "\n\n" . get_string('replytoforumpost', 'mod_forum');
1245      }
1246  
1247      return $posttext;
1248  }
1249  
1250  /**
1251   * Builds and returns the body of the email notification in html format.
1252   *
1253   * @global object
1254   * @param object $course
1255   * @param object $cm
1256   * @param object $forum
1257   * @param object $discussion
1258   * @param object $post
1259   * @param object $userfrom
1260   * @param object $userto
1261   * @param string $replyaddress The inbound address that a user can reply to the generated e-mail with. [Since 2.8].
1262   * @return string The email text in HTML format
1263   */
1264  function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $replyaddress = null) {
1265      global $CFG;
1266  
1267      if ($userto->mailformat != 1) {  // Needs to be HTML
1268          return '';
1269      }
1270  
1271      if (!isset($userto->canpost[$discussion->id])) {
1272          $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1273      } else {
1274          $canreply = $userto->canpost[$discussion->id];
1275      }
1276  
1277      $strforums = get_string('forums', 'forum');
1278      $canunsubscribe = ! \mod_forum\subscriptions::is_forcesubscribed($forum);
1279      $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1280  
1281      $posthtml = '<head>';
1282  /*    foreach ($CFG->stylesheets as $stylesheet) {
1283          //TODO: MDL-21120
1284          $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1285      }*/
1286      $posthtml .= '</head>';
1287      $posthtml .= "\n<body id=\"email\">\n\n";
1288  
1289      if ($replyaddress) {
1290          $posthtml .= "<p><em>--" . get_string('deleteoriginalonreply', 'mod_forum') . "--</em></p>";
1291      }
1292  
1293      $posthtml .= '<div class="navbar">'.
1294      '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1295      '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1296      '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1297      if ($discussion->name == $forum->name) {
1298          $posthtml .= '</div>';
1299      } else {
1300          $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1301                       format_string($discussion->name,true).'</a></div>';
1302      }
1303      $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1304  
1305      if ($replyaddress) {
1306          $posthtml .= html_writer::tag('p', get_string('replytoforumpost', 'mod_forum'));
1307      }
1308  
1309      $footerlinks = array();
1310      if ($canunsubscribe) {
1311          if (\mod_forum\subscriptions::is_subscribed($userto->id, $forum, null, $cm)) {
1312              // If subscribed to this forum, offer the unsubscribe link.
1313              $unsublink = new moodle_url('/mod/forum/subscribe.php', array('id' => $forum->id));
1314              $footerlinks[] = html_writer::link($unsublink, get_string('unsubscribe', 'mod_forum'));
1315          }
1316          // Always offer the unsubscribe from discussion link.
1317          $unsublink = new moodle_url('/mod/forum/subscribe.php', array(
1318                  'id' => $forum->id,
1319                  'd' => $discussion->id,
1320              ));
1321          $footerlinks[] = html_writer::link($unsublink, get_string('unsubscribediscussion', 'mod_forum'));
1322  
1323          $footerlinks[] = '<a href="' . $CFG->wwwroot . '/mod/forum/unsubscribeall.php">' . get_string('unsubscribeall', 'forum') . '</a>';
1324      }
1325      $footerlinks[] = "<a href='{$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}'>" . get_string('digestmailpost', 'forum') . '</a>';
1326      $posthtml .= '<hr /><div class="mdl-align unsubscribelink">' . implode('&nbsp;', $footerlinks) . '</div>';
1327  
1328      $posthtml .= '</body>';
1329  
1330      return $posthtml;
1331  }
1332  
1333  
1334  /**
1335   *
1336   * @param object $course
1337   * @param object $user
1338   * @param object $mod TODO this is not used in this function, refactor
1339   * @param object $forum
1340   * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1341   */
1342  function forum_user_outline($course, $user, $mod, $forum) {
1343      global $CFG;
1344      require_once("$CFG->libdir/gradelib.php");
1345      $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1346      if (empty($grades->items[0]->grades)) {
1347          $grade = false;
1348      } else {
1349          $grade = reset($grades->items[0]->grades);
1350      }
1351  
1352      $count = forum_count_user_posts($forum->id, $user->id);
1353  
1354      if ($count && $count->postcount > 0) {
1355          $result = new stdClass();
1356          $result->info = get_string("numposts", "forum", $count->postcount);
1357          $result->time = $count->lastpost;
1358          if ($grade) {
1359              $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1360          }
1361          return $result;
1362      } else if ($grade) {
1363          $result = new stdClass();
1364          $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1365  
1366          //datesubmitted == time created. dategraded == time modified or time overridden
1367          //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1368          //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1369          if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1370              $result->time = $grade->dategraded;
1371          } else {
1372              $result->time = $grade->datesubmitted;
1373          }
1374  
1375          return $result;
1376      }
1377      return NULL;
1378  }
1379  
1380  
1381  /**
1382   * @global object
1383   * @global object
1384   * @param object $coure
1385   * @param object $user
1386   * @param object $mod
1387   * @param object $forum
1388   */
1389  function forum_user_complete($course, $user, $mod, $forum) {
1390      global $CFG,$USER, $OUTPUT;
1391      require_once("$CFG->libdir/gradelib.php");
1392  
1393      $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1394      if (!empty($grades->items[0]->grades)) {
1395          $grade = reset($grades->items[0]->grades);
1396          echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1397          if ($grade->str_feedback) {
1398              echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1399          }
1400      }
1401  
1402      if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1403  
1404          if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1405              print_error('invalidcoursemodule');
1406          }
1407          $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1408  
1409          foreach ($posts as $post) {
1410              if (!isset($discussions[$post->discussion])) {
1411                  continue;
1412              }
1413              $discussion = $discussions[$post->discussion];
1414  
1415              forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1416          }
1417      } else {
1418          echo "<p>".get_string("noposts", "forum")."</p>";
1419      }
1420  }
1421  
1422  /**
1423   * Filters the forum discussions according to groups membership and config.
1424   *
1425   * @since  Moodle 2.8, 2.7.1, 2.6.4
1426   * @param  array $discussions Discussions with new posts array
1427   * @return array Forums with the number of new posts
1428   */
1429  function forum_filter_user_groups_discussions($discussions) {
1430  
1431      // Group the remaining discussions posts by their forumid.
1432      $filteredforums = array();
1433  
1434      // Discard not visible groups.
1435      foreach ($discussions as $discussion) {
1436  
1437          // Course data is already cached.
1438          $instances = get_fast_modinfo($discussion->course)->get_instances();
1439          $forum = $instances['forum'][$discussion->forum];
1440  
1441          // Continue if the user should not see this discussion.
1442          if (!forum_is_user_group_discussion($forum, $discussion->groupid)) {
1443              continue;
1444          }
1445  
1446          // Grouping results by forum.
1447          if (empty($filteredforums[$forum->instance])) {
1448              $filteredforums[$forum->instance] = new stdClass();
1449              $filteredforums[$forum->instance]->id = $forum->id;
1450              $filteredforums[$forum->instance]->count = 0;
1451          }
1452          $filteredforums[$forum->instance]->count += $discussion->count;
1453  
1454      }
1455  
1456      return $filteredforums;
1457  }
1458  
1459  /**
1460   * Returns whether the discussion group is visible by the current user or not.
1461   *
1462   * @since Moodle 2.8, 2.7.1, 2.6.4
1463   * @param cm_info $cm The discussion course module
1464   * @param int $discussiongroupid The discussion groupid
1465   * @return bool
1466   */
1467  function forum_is_user_group_discussion(cm_info $cm, $discussiongroupid) {
1468  
1469      if ($discussiongroupid == -1 || $cm->effectivegroupmode != SEPARATEGROUPS) {
1470          return true;
1471      }
1472  
1473      if (isguestuser()) {
1474          return false;
1475      }
1476  
1477      if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id)) ||
1478              in_array($discussiongroupid, $cm->get_modinfo()->get_groups($cm->groupingid))) {
1479          return true;
1480      }
1481  
1482      return false;
1483  }
1484  
1485  /**
1486   * @global object
1487   * @global object
1488   * @global object
1489   * @param array $courses
1490   * @param array $htmlarray
1491   */
1492  function forum_print_overview($courses,&$htmlarray) {
1493      global $USER, $CFG, $DB, $SESSION;
1494  
1495      if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1496          return array();
1497      }
1498  
1499      if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1500          return;
1501      }
1502  
1503      // Courses to search for new posts
1504      $coursessqls = array();
1505      $params = array();
1506      foreach ($courses as $course) {
1507  
1508          // If the user has never entered into the course all posts are pending
1509          if ($course->lastaccess == 0) {
1510              $coursessqls[] = '(d.course = ?)';
1511              $params[] = $course->id;
1512  
1513          // Only posts created after the course last access
1514          } else {
1515              $coursessqls[] = '(d.course = ? AND p.created > ?)';
1516              $params[] = $course->id;
1517              $params[] = $course->lastaccess;
1518          }
1519      }
1520      $params[] = $USER->id;
1521      $coursessql = implode(' OR ', $coursessqls);
1522  
1523      $sql = "SELECT d.id, d.forum, d.course, d.groupid, COUNT(*) as count "
1524                  .'FROM {forum_discussions} d '
1525                  .'JOIN {forum_posts} p ON p.discussion = d.id '
1526                  ."WHERE ($coursessql) "
1527                  .'AND p.userid != ? '
1528                  .'GROUP BY d.id, d.forum, d.course, d.groupid '
1529                  .'ORDER BY d.course, d.forum';
1530  
1531      // Avoid warnings.
1532      if (!$discussions = $DB->get_records_sql($sql, $params)) {
1533          $discussions = array();
1534      }
1535  
1536      $forumsnewposts = forum_filter_user_groups_discussions($discussions);
1537  
1538      // also get all forum tracking stuff ONCE.
1539      $trackingforums = array();
1540      foreach ($forums as $forum) {
1541          if (forum_tp_can_track_forums($forum)) {
1542              $trackingforums[$forum->id] = $forum;
1543          }
1544      }
1545  
1546      if (count($trackingforums) > 0) {
1547          $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1548          $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1549              ' FROM {forum_posts} p '.
1550              ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1551              ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1552          $params = array($USER->id);
1553  
1554          foreach ($trackingforums as $track) {
1555              $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1556              $params[] = $track->id;
1557              if (isset($SESSION->currentgroup[$track->course])) {
1558                  $groupid =  $SESSION->currentgroup[$track->course];
1559              } else {
1560                  // get first groupid
1561                  $groupids = groups_get_all_groups($track->course, $USER->id);
1562                  if ($groupids) {
1563                      reset($groupids);
1564                      $groupid = key($groupids);
1565                      $SESSION->currentgroup[$track->course] = $groupid;
1566                  } else {
1567                      $groupid = 0;
1568                  }
1569                  unset($groupids);
1570              }
1571              $params[] = $groupid;
1572          }
1573          $sql = substr($sql,0,-3); // take off the last OR
1574          $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1575          $params[] = $cutoffdate;
1576  
1577          if (!$unread = $DB->get_records_sql($sql, $params)) {
1578              $unread = array();
1579          }
1580      } else {
1581          $unread = array();
1582      }
1583  
1584      if (empty($unread) and empty($forumsnewposts)) {
1585          return;
1586      }
1587  
1588      $strforum = get_string('modulename','forum');
1589  
1590      foreach ($forums as $forum) {
1591          $str = '';
1592          $count = 0;
1593          $thisunread = 0;
1594          $showunread = false;
1595          // either we have something from logs, or trackposts, or nothing.
1596          if (array_key_exists($forum->id, $forumsnewposts) && !empty($forumsnewposts[$forum->id])) {
1597              $count = $forumsnewposts[$forum->id]->count;
1598          }
1599          if (array_key_exists($forum->id,$unread)) {
1600              $thisunread = $unread[$forum->id]->count;
1601              $showunread = true;
1602          }
1603          if ($count > 0 || $thisunread > 0) {
1604              $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1605                  $forum->name.'</a></div>';
1606              $str .= '<div class="info"><span class="postsincelogin">';
1607              $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
1608              if (!empty($showunread)) {
1609                  $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
1610              }
1611              $str .= '</div></div>';
1612          }
1613          if (!empty($str)) {
1614              if (!array_key_exists($forum->course,$htmlarray)) {
1615                  $htmlarray[$forum->course] = array();
1616              }
1617              if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1618                  $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1619              }
1620              $htmlarray[$forum->course]['forum'] .= $str;
1621          }
1622      }
1623  }
1624  
1625  /**
1626   * Given a course and a date, prints a summary of all the new
1627   * messages posted in the course since that date
1628   *
1629   * @global object
1630   * @global object
1631   * @global object
1632   * @uses CONTEXT_MODULE
1633   * @uses VISIBLEGROUPS
1634   * @param object $course
1635   * @param bool $viewfullnames capability
1636   * @param int $timestart
1637   * @return bool success
1638   */
1639  function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1640      global $CFG, $USER, $DB, $OUTPUT;
1641  
1642      // do not use log table if possible, it may be huge and is expensive to join with other tables
1643  
1644      $allnamefields = user_picture::fields('u', null, 'duserid');
1645      if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1646                                                d.timestart, d.timeend, $allnamefields
1647                                           FROM {forum_posts} p
1648                                                JOIN {forum_discussions} d ON d.id = p.discussion
1649                                                JOIN {forum} f             ON f.id = d.forum
1650                                                JOIN {user} u              ON u.id = p.userid
1651                                          WHERE p.created > ? AND f.course = ?
1652                                       ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1653           return false;
1654      }
1655  
1656      $modinfo = get_fast_modinfo($course);
1657  
1658      $groupmodes = array();
1659      $cms    = array();
1660  
1661      $strftimerecent = get_string('strftimerecent');
1662  
1663      $printposts = array();
1664      foreach ($posts as $post) {
1665          if (!isset($modinfo->instances['forum'][$post->forum])) {
1666              // not visible
1667              continue;
1668          }
1669          $cm = $modinfo->instances['forum'][$post->forum];
1670          if (!$cm->uservisible) {
1671              continue;
1672          }
1673          $context = context_module::instance($cm->id);
1674  
1675          if (!has_capability('mod/forum:viewdiscussion', $context)) {
1676              continue;
1677          }
1678  
1679          if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1680            and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1681              if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1682                  continue;
1683              }
1684          }
1685  
1686          // Check that the user can see the discussion.
1687          if (forum_is_user_group_discussion($cm, $post->groupid)) {
1688              $printposts[] = $post;
1689          }
1690  
1691      }
1692      unset($posts);
1693  
1694      if (!$printposts) {
1695          return false;
1696      }
1697  
1698      echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1699      echo "\n<ul class='unlist'>\n";
1700  
1701      foreach ($printposts as $post) {
1702          $subjectclass = empty($post->parent) ? ' bold' : '';
1703  
1704          echo '<li><div class="head">'.
1705                 '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1706                 '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1707               '</div>';
1708          echo '<div class="info'.$subjectclass.'">';
1709          if (empty($post->parent)) {
1710              echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1711          } else {
1712              echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1713          }
1714          $post->subject = break_up_long_words(format_string($post->subject, true));
1715          echo $post->subject;
1716          echo "</a>\"</div></li>\n";
1717      }
1718  
1719      echo "</ul>\n";
1720  
1721      return true;
1722  }
1723  
1724  /**
1725   * Return grade for given user or all users.
1726   *
1727   * @global object
1728   * @global object
1729   * @param object $forum
1730   * @param int $userid optional user id, 0 means all users
1731   * @return array array of grades, false if none
1732   */
1733  function forum_get_user_grades($forum, $userid = 0) {
1734      global $CFG;
1735  
1736      require_once($CFG->dirroot.'/rating/lib.php');
1737  
1738      $ratingoptions = new stdClass;
1739      $ratingoptions->component = 'mod_forum';
1740      $ratingoptions->ratingarea = 'post';
1741  
1742      //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1743      $ratingoptions->modulename = 'forum';
1744      $ratingoptions->moduleid   = $forum->id;
1745      $ratingoptions->userid = $userid;
1746      $ratingoptions->aggregationmethod = $forum->assessed;
1747      $ratingoptions->scaleid = $forum->scale;
1748      $ratingoptions->itemtable = 'forum_posts';
1749      $ratingoptions->itemtableusercolumn = 'userid';
1750  
1751      $rm = new rating_manager();
1752      return $rm->get_user_grades($ratingoptions);
1753  }
1754  
1755  /**
1756   * Update activity grades
1757   *
1758   * @category grade
1759   * @param object $forum
1760   * @param int $userid specific user only, 0 means all
1761   * @param boolean $nullifnone return null if grade does not exist
1762   * @return void
1763   */
1764  function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1765      global $CFG, $DB;
1766      require_once($CFG->libdir.'/gradelib.php');
1767  
1768      if (!$forum->assessed) {
1769          forum_grade_item_update($forum);
1770  
1771      } else if ($grades = forum_get_user_grades($forum, $userid)) {
1772          forum_grade_item_update($forum, $grades);
1773  
1774      } else if ($userid and $nullifnone) {
1775          $grade = new stdClass();
1776          $grade->userid   = $userid;
1777          $grade->rawgrade = NULL;
1778          forum_grade_item_update($forum, $grade);
1779  
1780      } else {
1781          forum_grade_item_update($forum);
1782      }
1783  }
1784  
1785  /**
1786   * Create/update grade item for given forum
1787   *
1788   * @category grade
1789   * @uses GRADE_TYPE_NONE
1790   * @uses GRADE_TYPE_VALUE
1791   * @uses GRADE_TYPE_SCALE
1792   * @param stdClass $forum Forum object with extra cmidnumber
1793   * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1794   * @return int 0 if ok
1795   */
1796  function forum_grade_item_update($forum, $grades=NULL) {
1797      global $CFG;
1798      if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1799          require_once($CFG->libdir.'/gradelib.php');
1800      }
1801  
1802      $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1803  
1804      if (!$forum->assessed or $forum->scale == 0) {
1805          $params['gradetype'] = GRADE_TYPE_NONE;
1806  
1807      } else if ($forum->scale > 0) {
1808          $params['gradetype'] = GRADE_TYPE_VALUE;
1809          $params['grademax']  = $forum->scale;
1810          $params['grademin']  = 0;
1811  
1812      } else if ($forum->scale < 0) {
1813          $params['gradetype'] = GRADE_TYPE_SCALE;
1814          $params['scaleid']   = -$forum->scale;
1815      }
1816  
1817      if ($grades  === 'reset') {
1818          $params['reset'] = true;
1819          $grades = NULL;
1820      }
1821  
1822      return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1823  }
1824  
1825  /**
1826   * Delete grade item for given forum
1827   *
1828   * @category grade
1829   * @param stdClass $forum Forum object
1830   * @return grade_item
1831   */
1832  function forum_grade_item_delete($forum) {
1833      global $CFG;
1834      require_once($CFG->libdir.'/gradelib.php');
1835  
1836      return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1837  }
1838  
1839  
1840  /**
1841   * This function returns if a scale is being used by one forum
1842   *
1843   * @global object
1844   * @param int $forumid
1845   * @param int $scaleid negative number
1846   * @return bool
1847   */
1848  function forum_scale_used ($forumid,$scaleid) {
1849      global $DB;
1850      $return = false;
1851  
1852      $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1853  
1854      if (!empty($rec) && !empty($scaleid)) {
1855          $return = true;
1856      }
1857  
1858      return $return;
1859  }
1860  
1861  /**
1862   * Checks if scale is being used by any instance of forum
1863   *
1864   * This is used to find out if scale used anywhere
1865   *
1866   * @global object
1867   * @param $scaleid int
1868   * @return boolean True if the scale is used by any forum
1869   */
1870  function forum_scale_used_anywhere($scaleid) {
1871      global $DB;
1872      if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1873          return true;
1874      } else {
1875          return false;
1876      }
1877  }
1878  
1879  // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1880  
1881  /**
1882   * Gets a post with all info ready for forum_print_post
1883   * Most of these joins are just to get the forum id
1884   *
1885   * @global object
1886   * @global object
1887   * @param int $postid
1888   * @return mixed array of posts or false
1889   */
1890  function forum_get_post_full($postid) {
1891      global $CFG, $DB;
1892  
1893      $allnames = get_all_user_name_fields(true, 'u');
1894      return $DB->get_record_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
1895                               FROM {forum_posts} p
1896                                    JOIN {forum_discussions} d ON p.discussion = d.id
1897                                    LEFT JOIN {user} u ON p.userid = u.id
1898                              WHERE p.id = ?", array($postid));
1899  }
1900  
1901  /**
1902   * Gets all posts in discussion including top parent.
1903   *
1904   * @global object
1905   * @global object
1906   * @global object
1907   * @param int $discussionid
1908   * @param string $sort
1909   * @param bool $tracking does user track the forum?
1910   * @return array of posts
1911   */
1912  function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1913      global $CFG, $DB, $USER;
1914  
1915      $tr_sel  = "";
1916      $tr_join = "";
1917      $params = array();
1918  
1919      if ($tracking) {
1920          $now = time();
1921          $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1922          $tr_sel  = ", fr.id AS postread";
1923          $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1924          $params[] = $USER->id;
1925      }
1926  
1927      $allnames = get_all_user_name_fields(true, 'u');
1928      $params[] = $discussionid;
1929      if (!$posts = $DB->get_records_sql("SELECT p.*, $allnames, u.email, u.picture, u.imagealt $tr_sel
1930                                       FROM {forum_posts} p
1931                                            LEFT JOIN {user} u ON p.userid = u.id
1932                                            $tr_join
1933                                      WHERE p.discussion = ?
1934                                   ORDER BY $sort", $params)) {
1935          return array();
1936      }
1937  
1938      foreach ($posts as $pid=>$p) {
1939          if ($tracking) {
1940              if (forum_tp_is_post_old($p)) {
1941                   $posts[$pid]->postread = true;
1942              }
1943          }
1944          if (!$p->parent) {
1945              continue;
1946          }
1947          if (!isset($posts[$p->parent])) {
1948              continue; // parent does not exist??
1949          }
1950          if (!isset($posts[$p->parent]->children)) {
1951              $posts[$p->parent]->children = array();
1952          }
1953          $posts[$p->parent]->children[$pid] =& $posts[$pid];
1954      }
1955  
1956      return $posts;
1957  }
1958  
1959  /**
1960   * An array of forum objects that the user is allowed to read/search through.
1961   *
1962   * @global object
1963   * @global object
1964   * @global object
1965   * @param int $userid
1966   * @param int $courseid if 0, we look for forums throughout the whole site.
1967   * @return array of forum objects, or false if no matches
1968   *         Forum objects have the following attributes:
1969   *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1970   *         viewhiddentimedposts
1971   */
1972  function forum_get_readable_forums($userid, $courseid=0) {
1973  
1974      global $CFG, $DB, $USER;
1975      require_once($CFG->dirroot.'/course/lib.php');
1976  
1977      if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1978          print_error('notinstalled', 'forum');
1979      }
1980  
1981      if ($courseid) {
1982          $courses = $DB->get_records('course', array('id' => $courseid));
1983      } else {
1984          // If no course is specified, then the user can see SITE + his courses.
1985          $courses1 = $DB->get_records('course', array('id' => SITEID));
1986          $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1987          $courses = array_merge($courses1, $courses2);
1988      }
1989      if (!$courses) {
1990          return array();
1991      }
1992  
1993      $readableforums = array();
1994  
1995      foreach ($courses as $course) {
1996  
1997          $modinfo = get_fast_modinfo($course);
1998  
1999          if (empty($modinfo->instances['forum'])) {
2000              // hmm, no forums?
2001              continue;
2002          }
2003  
2004          $courseforums = $DB->get_records('forum', array('course' => $course->id));
2005  
2006          foreach ($modinfo->instances['forum'] as $forumid => $cm) {
2007              if (!$cm->uservisible or !isset($courseforums[$forumid])) {
2008                  continue;
2009              }
2010              $context = context_module::instance($cm->id);
2011              $forum = $courseforums[$forumid];
2012              $forum->context = $context;
2013              $forum->cm = $cm;
2014  
2015              if (!has_capability('mod/forum:viewdiscussion', $context)) {
2016                  continue;
2017              }
2018  
2019           /// group access
2020              if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
2021  
2022                  $forum->onlygroups = $modinfo->get_groups($cm->groupingid);
2023                  $forum->onlygroups[] = -1;
2024              }
2025  
2026          /// hidden timed discussions
2027              $forum->viewhiddentimedposts = true;
2028              if (!empty($CFG->forum_enabletimedposts)) {
2029                  if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
2030                      $forum->viewhiddentimedposts = false;
2031                  }
2032              }
2033  
2034          /// qanda access
2035              if ($forum->type == 'qanda'
2036                      && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2037  
2038                  // We need to check whether the user has posted in the qanda forum.
2039                  $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
2040                                                      // the user is allowed to see in this forum.
2041                  if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
2042                      foreach ($discussionspostedin as $d) {
2043                          $forum->onlydiscussions[] = $d->id;
2044                      }
2045                  }
2046              }
2047  
2048              $readableforums[$forum->id] = $forum;
2049          }
2050  
2051          unset($modinfo);
2052  
2053      } // End foreach $courses
2054  
2055      return $readableforums;
2056  }
2057  
2058  /**
2059   * Returns a list of posts found using an array of search terms.
2060   *
2061   * @global object
2062   * @global object
2063   * @global object
2064   * @param array $searchterms array of search terms, e.g. word +word -word
2065   * @param int $courseid if 0, we search through the whole site
2066   * @param int $limitfrom
2067   * @param int $limitnum
2068   * @param int &$totalcount
2069   * @param string $extrasql
2070   * @return array|bool Array of posts found or false
2071   */
2072  function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
2073                              &$totalcount, $extrasql='') {
2074      global $CFG, $DB, $USER;
2075      require_once($CFG->libdir.'/searchlib.php');
2076  
2077      $forums = forum_get_readable_forums($USER->id, $courseid);
2078  
2079      if (count($forums) == 0) {
2080          $totalcount = 0;
2081          return false;
2082      }
2083  
2084      $now = round(time(), -2); // db friendly
2085  
2086      $fullaccess = array();
2087      $where = array();
2088      $params = array();
2089  
2090      foreach ($forums as $forumid => $forum) {
2091          $select = array();
2092  
2093          if (!$forum->viewhiddentimedposts) {
2094              $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
2095              $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
2096          }
2097  
2098          $cm = $forum->cm;
2099          $context = $forum->context;
2100  
2101          if ($forum->type == 'qanda'
2102              && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2103              if (!empty($forum->onlydiscussions)) {
2104                  list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
2105                  $params = array_merge($params, $discussionid_params);
2106                  $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
2107              } else {
2108                  $select[] = "p.parent = 0";
2109              }
2110          }
2111  
2112          if (!empty($forum->onlygroups)) {
2113              list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
2114              $params = array_merge($params, $groupid_params);
2115              $select[] = "d.groupid $groupid_sql";
2116          }
2117  
2118          if ($select) {
2119              $selects = implode(" AND ", $select);
2120              $where[] = "(d.forum = :forum{$forumid} AND $selects)";
2121              $params['forum'.$forumid] = $forumid;
2122          } else {
2123              $fullaccess[] = $forumid;
2124          }
2125      }
2126  
2127      if ($fullaccess) {
2128          list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
2129          $params = array_merge($params, $fullid_params);
2130          $where[] = "(d.forum $fullid_sql)";
2131      }
2132  
2133      $selectdiscussion = "(".implode(" OR ", $where).")";
2134  
2135      $messagesearch = '';
2136      $searchstring = '';
2137  
2138      // Need to concat these back together for parser to work.
2139      foreach($searchterms as $searchterm){
2140          if ($searchstring != '') {
2141              $searchstring .= ' ';
2142          }
2143          $searchstring .= $searchterm;
2144      }
2145  
2146      // We need to allow quoted strings for the search. The quotes *should* be stripped
2147      // by the parser, but this should be examined carefully for security implications.
2148      $searchstring = str_replace("\\\"","\"",$searchstring);
2149      $parser = new search_parser();
2150      $lexer = new search_lexer($parser);
2151  
2152      if ($lexer->parse($searchstring)) {
2153          $parsearray = $parser->get_parsed_array();
2154      // Experimental feature under 1.8! MDL-8830
2155      // Use alternative text searches if defined
2156      // This feature only works under mysql until properly implemented for other DBs
2157      // Requires manual creation of text index for forum_posts before enabling it:
2158      // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2159      // Experimental feature under 1.8! MDL-8830
2160          if (!empty($CFG->forum_usetextsearches)) {
2161              list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2162                                                   'p.userid', 'u.id', 'u.firstname',
2163                                                   'u.lastname', 'p.modified', 'd.forum');
2164          } else {
2165              list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2166                                                   'p.userid', 'u.id', 'u.firstname',
2167                                                   'u.lastname', 'p.modified', 'd.forum');
2168          }
2169          $params = array_merge($params, $msparams);
2170      }
2171  
2172      $fromsql = "{forum_posts} p,
2173                    {forum_discussions} d,
2174                    {user} u";
2175  
2176      $selectsql = " $messagesearch
2177                 AND p.discussion = d.id
2178                 AND p.userid = u.id
2179                 AND $selectdiscussion
2180                     $extrasql";
2181  
2182      $countsql = "SELECT COUNT(*)
2183                     FROM $fromsql
2184                    WHERE $selectsql";
2185  
2186      $allnames = get_all_user_name_fields(true, 'u');
2187      $searchsql = "SELECT p.*,
2188                           d.forum,
2189                           $allnames,
2190                           u.email,
2191                           u.picture,
2192                           u.imagealt
2193                      FROM $fromsql
2194                     WHERE $selectsql
2195                  ORDER BY p.modified DESC";
2196  
2197      $totalcount = $DB->count_records_sql($countsql, $params);
2198  
2199      return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2200  }
2201  
2202  /**
2203   * Returns a list of all new posts that have not been mailed yet
2204   *
2205   * @param int $starttime posts created after this time
2206   * @param int $endtime posts created before this
2207   * @param int $now used for timed discussions only
2208   * @return array
2209   */
2210  function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2211      global $CFG, $DB;
2212  
2213      $params = array();
2214      $params['mailed'] = FORUM_MAILED_PENDING;
2215      $params['ptimestart'] = $starttime;
2216      $params['ptimeend'] = $endtime;
2217      $params['mailnow'] = 1;
2218  
2219      if (!empty($CFG->forum_enabletimedposts)) {
2220          if (empty($now)) {
2221              $now = time();
2222          }
2223          $timedsql = "AND (d.timestart < :dtimestart AND (d.timeend = 0 OR d.timeend > :dtimeend))";
2224          $params['dtimestart'] = $now;
2225          $params['dtimeend'] = $now;
2226      } else {
2227          $timedsql = "";
2228      }
2229  
2230      return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2231                                   FROM {forum_posts} p
2232                                   JOIN {forum_discussions} d ON d.id = p.discussion
2233                                   WHERE p.mailed = :mailed
2234                                   AND p.created >= :ptimestart
2235                                   AND (p.created < :ptimeend OR p.mailnow = :mailnow)
2236                                   $timedsql
2237                                   ORDER BY p.modified ASC", $params);
2238  }
2239  
2240  /**
2241   * Marks posts before a certain time as being mailed already
2242   *
2243   * @global object
2244   * @global object
2245   * @param int $endtime
2246   * @param int $now Defaults to time()
2247   * @return bool
2248   */
2249  function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2250      global $CFG, $DB;
2251  
2252      if (empty($now)) {
2253          $now = time();
2254      }
2255  
2256      $params = array();
2257      $params['mailedsuccess'] = FORUM_MAILED_SUCCESS;
2258      $params['now'] = $now;
2259      $params['endtime'] = $endtime;
2260      $params['mailnow'] = 1;
2261      $params['mailedpending'] = FORUM_MAILED_PENDING;
2262  
2263      if (empty($CFG->forum_enabletimedposts)) {
2264          return $DB->execute("UPDATE {forum_posts}
2265                               SET mailed = :mailedsuccess
2266                               WHERE (created < :endtime OR mailnow = :mailnow)
2267                               AND mailed = :mailedpending", $params);
2268      } else {
2269          return $DB->execute("UPDATE {forum_posts}
2270                               SET mailed = :mailedsuccess
2271                               WHERE discussion NOT IN (SELECT d.id
2272                                                        FROM {forum_discussions} d
2273                                                        WHERE d.timestart > :now)
2274                               AND (created < :endtime OR mailnow = :mailnow)
2275                               AND mailed = :mailedpending", $params);
2276      }
2277  }
2278  
2279  /**
2280   * Get all the posts for a user in a forum suitable for forum_print_post
2281   *
2282   * @global object
2283   * @global object
2284   * @uses CONTEXT_MODULE
2285   * @return array
2286   */
2287  function forum_get_user_posts($forumid, $userid) {
2288      global $CFG, $DB;
2289  
2290      $timedsql = "";
2291      $params = array($forumid, $userid);
2292  
2293      if (!empty($CFG->forum_enabletimedposts)) {
2294          $cm = get_coursemodule_from_instance('forum', $forumid);
2295          if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2296              $now = time();
2297              $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2298              $params[] = $now;
2299              $params[] = $now;
2300          }
2301      }
2302  
2303      $allnames = get_all_user_name_fields(true, 'u');
2304      return $DB->get_records_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
2305                                FROM {forum} f
2306                                     JOIN {forum_discussions} d ON d.forum = f.id
2307                                     JOIN {forum_posts} p       ON p.discussion = d.id
2308                                     JOIN {user} u              ON u.id = p.userid
2309                               WHERE f.id = ?
2310                                     AND p.userid = ?
2311                                     $timedsql
2312                            ORDER BY p.modified ASC", $params);
2313  }
2314  
2315  /**
2316   * Get all the discussions user participated in
2317   *
2318   * @global object
2319   * @global object
2320   * @uses CONTEXT_MODULE
2321   * @param int $forumid
2322   * @param int $userid
2323   * @return array Array or false
2324   */
2325  function forum_get_user_involved_discussions($forumid, $userid) {
2326      global $CFG, $DB;
2327  
2328      $timedsql = "";
2329      $params = array($forumid, $userid);
2330      if (!empty($CFG->forum_enabletimedposts)) {
2331          $cm = get_coursemodule_from_instance('forum', $forumid);
2332          if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2333              $now = time();
2334              $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2335              $params[] = $now;
2336              $params[] = $now;
2337          }
2338      }
2339  
2340      return $DB->get_records_sql("SELECT DISTINCT d.*
2341                                FROM {forum} f
2342                                     JOIN {forum_discussions} d ON d.forum = f.id
2343                                     JOIN {forum_posts} p       ON p.discussion = d.id
2344                               WHERE f.id = ?
2345                                     AND p.userid = ?
2346                                     $timedsql", $params);
2347  }
2348  
2349  /**
2350   * Get all the posts for a user in a forum suitable for forum_print_post
2351   *
2352   * @global object
2353   * @global object
2354   * @param int $forumid
2355   * @param int $userid
2356   * @return array of counts or false
2357   */
2358  function forum_count_user_posts($forumid, $userid) {
2359      global $CFG, $DB;
2360  
2361      $timedsql = "";
2362      $params = array($forumid, $userid);
2363      if (!empty($CFG->forum_enabletimedposts)) {
2364          $cm = get_coursemodule_from_instance('forum', $forumid);
2365          if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2366              $now = time();
2367              $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2368              $params[] = $now;
2369              $params[] = $now;
2370          }
2371      }
2372  
2373      return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2374                               FROM {forum} f
2375                                    JOIN {forum_discussions} d ON d.forum = f.id
2376                                    JOIN {forum_posts} p       ON p.discussion = d.id
2377                                    JOIN {user} u              ON u.id = p.userid
2378                              WHERE f.id = ?
2379                                    AND p.userid = ?
2380                                    $timedsql", $params);
2381  }
2382  
2383  /**
2384   * Given a log entry, return the forum post details for it.
2385   *
2386   * @global object
2387   * @global object
2388   * @param object $log
2389   * @return array|null
2390   */
2391  function forum_get_post_from_log($log) {
2392      global $CFG, $DB;
2393  
2394      $allnames = get_all_user_name_fields(true, 'u');
2395      if ($log->action == "add post") {
2396  
2397          return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
2398                                   FROM {forum_discussions} d,
2399                                        {forum_posts} p,
2400                                        {forum} f,
2401                                        {user} u
2402                                  WHERE p.id = ?
2403                                    AND d.id = p.discussion
2404                                    AND p.userid = u.id
2405                                    AND u.deleted <> '1'
2406                                    AND f.id = d.forum", array($log->info));
2407  
2408  
2409      } else if ($log->action == "add discussion") {
2410  
2411          return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
2412                                   FROM {forum_discussions} d,
2413                                        {forum_posts} p,
2414                                        {forum} f,
2415                                        {user} u
2416                                  WHERE d.id = ?
2417                                    AND d.firstpost = p.id
2418                                    AND p.userid = u.id
2419                                    AND u.deleted <> '1'
2420                                    AND f.id = d.forum", array($log->info));
2421      }
2422      return NULL;
2423  }
2424  
2425  /**
2426   * Given a discussion id, return the first post from the discussion
2427   *
2428   * @global object
2429   * @global object
2430   * @param int $dicsussionid
2431   * @return array
2432   */
2433  function forum_get_firstpost_from_discussion($discussionid) {
2434      global $CFG, $DB;
2435  
2436      return $DB->get_record_sql("SELECT p.*
2437                               FROM {forum_discussions} d,
2438                                    {forum_posts} p
2439                              WHERE d.id = ?
2440                                AND d.firstpost = p.id ", array($discussionid));
2441  }
2442  
2443  /**
2444   * Returns an array of counts of replies to each discussion
2445   *
2446   * @global object
2447   * @global object
2448   * @param int $forumid
2449   * @param string $forumsort
2450   * @param int $limit
2451   * @param int $page
2452   * @param int $perpage
2453   * @return array
2454   */
2455  function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2456      global $CFG, $DB;
2457  
2458      if ($limit > 0) {
2459          $limitfrom = 0;
2460          $limitnum  = $limit;
2461      } else if ($page != -1) {
2462          $limitfrom = $page*$perpage;
2463          $limitnum  = $perpage;
2464      } else {
2465          $limitfrom = 0;
2466          $limitnum  = 0;
2467      }
2468  
2469      if ($forumsort == "") {
2470          $orderby = "";
2471          $groupby = "";
2472  
2473      } else {
2474          $orderby = "ORDER BY $forumsort";
2475          $groupby = ", ".strtolower($forumsort);
2476          $groupby = str_replace('desc', '', $groupby);
2477          $groupby = str_replace('asc', '', $groupby);
2478      }
2479  
2480      if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2481          $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2482                    FROM {forum_posts} p
2483                         JOIN {forum_discussions} d ON p.discussion = d.id
2484                   WHERE p.parent > 0 AND d.forum = ?
2485                GROUP BY p.discussion";
2486          return $DB->get_records_sql($sql, array($forumid));
2487  
2488      } else {
2489          $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2490                    FROM {forum_posts} p
2491                         JOIN {forum_discussions} d ON p.discussion = d.id
2492                   WHERE d.forum = ?
2493                GROUP BY p.discussion $groupby
2494                $orderby";
2495          return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2496      }
2497  }
2498  
2499  /**
2500   * @global object
2501   * @global object
2502   * @global object
2503   * @staticvar array $cache
2504   * @param object $forum
2505   * @param object $cm
2506   * @param object $course
2507   * @return mixed
2508   */
2509  function forum_count_discussions($forum, $cm, $course) {
2510      global $CFG, $DB, $USER;
2511  
2512      static $cache = array();
2513  
2514      $now = round(time(), -2); // db cache friendliness
2515  
2516      $params = array($course->id);
2517  
2518      if (!isset($cache[$course->id])) {
2519          if (!empty($CFG->forum_enabletimedposts)) {
2520              $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2521              $params[] = $now;
2522              $params[] = $now;
2523          } else {
2524              $timedsql = "";
2525          }
2526  
2527          $sql = "SELECT f.id, COUNT(d.id) as dcount
2528                    FROM {forum} f
2529                         JOIN {forum_discussions} d ON d.forum = f.id
2530                   WHERE f.course = ?
2531                         $timedsql
2532                GROUP BY f.id";
2533  
2534          if ($counts = $DB->get_records_sql($sql, $params)) {
2535              foreach ($counts as $count) {
2536                  $counts[$count->id] = $count->dcount;
2537              }
2538              $cache[$course->id] = $counts;
2539          } else {
2540              $cache[$course->id] = array();
2541          }
2542      }
2543  
2544      if (empty($cache[$course->id][$forum->id])) {
2545          return 0;
2546      }
2547  
2548      $groupmode = groups_get_activity_groupmode($cm, $course);
2549  
2550      if ($groupmode != SEPARATEGROUPS) {
2551          return $cache[$course->id][$forum->id];
2552      }
2553  
2554      if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) {
2555          return $cache[$course->id][$forum->id];
2556      }
2557  
2558      require_once($CFG->dirroot.'/course/lib.php');
2559  
2560      $modinfo = get_fast_modinfo($course);
2561  
2562      $mygroups = $modinfo->get_groups($cm->groupingid);
2563  
2564      // add all groups posts
2565      $mygroups[-1] = -1;
2566  
2567      list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2568      $params[] = $forum->id;
2569  
2570      if (!empty($CFG->forum_enabletimedposts)) {
2571          $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2572          $params[] = $now;
2573          $params[] = $now;
2574      } else {
2575          $timedsql = "";
2576      }
2577  
2578      $sql = "SELECT COUNT(d.id)
2579                FROM {forum_discussions} d
2580               WHERE d.groupid $mygroups_sql AND d.forum = ?
2581                     $timedsql";
2582  
2583      return $DB->get_field_sql($sql, $params);
2584  }
2585  
2586  /**
2587   * Get all discussions in a forum
2588   *
2589   * @global object
2590   * @global object
2591   * @global object
2592   * @uses CONTEXT_MODULE
2593   * @uses VISIBLEGROUPS
2594   * @param object $cm
2595   * @param string $forumsort
2596   * @param bool $fullpost
2597   * @param int $unused
2598   * @param int $limit
2599   * @param bool $userlastmodified
2600   * @param int $page
2601   * @param int $perpage
2602   * @return array
2603   */
2604  function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2605      global $CFG, $DB, $USER;
2606  
2607      $timelimit = '';
2608  
2609      $now = round(time(), -2);
2610      $params = array($cm->instance);
2611  
2612      $modcontext = context_module::instance($cm->id);
2613  
2614      if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2615          return array();
2616      }
2617  
2618      if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2619  
2620          if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2621              $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2622              $params[] = $now;
2623              $params[] = $now;
2624              if (isloggedin()) {
2625                  $timelimit .= " OR d.userid = ?";
2626                  $params[] = $USER->id;
2627              }
2628              $timelimit .= ")";
2629          }
2630      }
2631  
2632      if ($limit > 0) {
2633          $limitfrom = 0;
2634          $limitnum  = $limit;
2635      } else if ($page != -1) {
2636          $limitfrom = $page*$perpage;
2637          $limitnum  = $perpage;
2638      } else {
2639          $limitfrom = 0;
2640          $limitnum  = 0;
2641      }
2642  
2643      $groupmode    = groups_get_activity_groupmode($cm);
2644      $currentgroup = groups_get_activity_group($cm);
2645  
2646      if ($groupmode) {
2647          if (empty($modcontext)) {
2648              $modcontext = context_module::instance($cm->id);
2649          }
2650  
2651          if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2652              if ($currentgroup) {
2653                  $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2654                  $params[] = $currentgroup;
2655              } else {
2656                  $groupselect = "";
2657              }
2658  
2659          } else {
2660              //seprate groups without access all
2661              if ($currentgroup) {
2662                  $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2663                  $params[] = $currentgroup;
2664              } else {
2665                  $groupselect = "AND d.groupid = -1";
2666              }
2667          }
2668      } else {
2669          $groupselect = "";
2670      }
2671  
2672  
2673      if (empty($forumsort)) {
2674          $forumsort = "d.timemodified DESC";
2675      }
2676      if (empty($fullpost)) {
2677          $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2678      } else {
2679          $postdata = "p.*";
2680      }
2681  
2682      if (empty($userlastmodified)) {  // We don't need to know this
2683          $umfields = "";
2684          $umtable  = "";
2685      } else {
2686          $umfields = ', ' . get_all_user_name_fields(true, 'um', null, 'um');
2687          $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2688      }
2689  
2690      $allnames = get_all_user_name_fields(true, 'u');
2691      $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend, $allnames,
2692                     u.email, u.picture, u.imagealt $umfields
2693                FROM {forum_discussions} d
2694                     JOIN {forum_posts} p ON p.discussion = d.id
2695                     JOIN {user} u ON p.userid = u.id
2696                     $umtable
2697               WHERE d.forum = ? AND p.parent = 0
2698                     $timelimit $groupselect
2699            ORDER BY $forumsort";
2700      return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2701  }
2702  
2703  /**
2704   * Gets the neighbours (previous and next) of a discussion.
2705   *
2706   * The calculation is based on the timemodified of the discussion and does not handle
2707   * the neighbours having an identical timemodified. The reason is that we do not have any
2708   * other mean to sort the records, e.g. we cannot use IDs as a greater ID can have a lower
2709   * timemodified.
2710   *
2711   * Please note that this does not check whether or not the discussion passed is accessible
2712   * by the user, it simply uses it as a reference to find the neighbours. On the other hand,
2713   * the returned neighbours are checked and are accessible to the current user.
2714   *
2715   * @param object $cm The CM record.
2716   * @param object $discussion The discussion record.
2717   * @return array That always contains the keys 'prev' and 'next'. When there is a result
2718   *               they contain the record with minimal information such as 'id' and 'name'.
2719   *               When the neighbour is not found the value is false.
2720   */
2721  function forum_get_discussion_neighbours($cm, $discussion) {
2722      global $CFG, $DB, $USER;
2723  
2724      if ($cm->instance != $discussion->forum) {
2725          throw new coding_exception('Discussion is not part of the same forum.');
2726      }
2727  
2728      $neighbours = array('prev' => false, 'next' => false);
2729      $now = round(time(), -2);
2730      $params = array();
2731  
2732      $modcontext = context_module::instance($cm->id);
2733      $groupmode    = groups_get_activity_groupmode($cm);
2734      $currentgroup = groups_get_activity_group($cm);
2735  
2736      // Users must fulfill timed posts.
2737      $timelimit = '';
2738      if (!empty($CFG->forum_enabletimedposts)) {
2739          if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2740              $timelimit = ' AND ((d.timestart <= :tltimestart AND (d.timeend = 0 OR d.timeend > :tltimeend))';
2741              $params['tltimestart'] = $now;
2742              $params['tltimeend'] = $now;
2743              if (isloggedin()) {
2744                  $timelimit .= ' OR d.userid = :tluserid';
2745                  $params['tluserid'] = $USER->id;
2746              }
2747              $timelimit .= ')';
2748          }
2749      }
2750  
2751      // Limiting to posts accessible according to groups.
2752      $groupselect = '';
2753      if ($groupmode) {
2754          if ($groupmode == VISIBLEGROUPS || has_capability('moodle/site:accessallgroups', $modcontext)) {
2755              if ($currentgroup) {
2756                  $groupselect = 'AND (d.groupid = :groupid OR d.groupid = -1)';
2757                  $params['groupid'] = $currentgroup;
2758              }
2759          } else {
2760              if ($currentgroup) {
2761                  $groupselect = 'AND (d.groupid = :groupid OR d.groupid = -1)';
2762                  $params['groupid'] = $currentgroup;
2763              } else {
2764                  $groupselect = 'AND d.groupid = -1';
2765              }
2766          }
2767      }
2768  
2769      $params['forumid'] = $cm->instance;
2770      $params['discid'] = $discussion->id;
2771      $params['disctimemodified'] = $discussion->timemodified;
2772  
2773      $sql = "SELECT d.id, d.name, d.timemodified, d.groupid, d.timestart, d.timeend
2774                FROM {forum_discussions} d
2775               WHERE d.forum = :forumid
2776                 AND d.id <> :discid
2777                     $timelimit
2778                     $groupselect";
2779  
2780      $prevsql = $sql . " AND d.timemodified < :disctimemodified
2781                     ORDER BY d.timemodified DESC";
2782  
2783      $nextsql = $sql . " AND d.timemodified > :disctimemodified
2784                     ORDER BY d.timemodified ASC";
2785  
2786      $neighbours['prev'] = $DB->get_record_sql($prevsql, $params, IGNORE_MULTIPLE);
2787      $neighbours['next'] = $DB->get_record_sql($nextsql, $params, IGNORE_MULTIPLE);
2788  
2789      return $neighbours;
2790  }
2791  
2792  /**
2793   *
2794   * @global object
2795   * @global object
2796   * @global object
2797   * @uses CONTEXT_MODULE
2798   * @uses VISIBLEGROUPS
2799   * @param object $cm
2800   * @return array
2801   */
2802  function forum_get_discussions_unread($cm) {
2803      global $CFG, $DB, $USER;
2804  
2805      $now = round(time(), -2);
2806      $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2807  
2808      $params = array();
2809      $groupmode    = groups_get_activity_groupmode($cm);
2810      $currentgroup = groups_get_activity_group($cm);
2811  
2812      if ($groupmode) {
2813          $modcontext = context_module::instance($cm->id);
2814  
2815          if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2816              if ($currentgroup) {
2817                  $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2818                  $params['currentgroup'] = $currentgroup;
2819              } else {
2820                  $groupselect = "";
2821              }
2822  
2823          } else {
2824              //separate groups without access all
2825              if ($currentgroup) {
2826                  $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2827                  $params['currentgroup'] = $currentgroup;
2828              } else {
2829                  $groupselect = "AND d.groupid = -1";
2830              }
2831          }
2832      } else {
2833          $groupselect = "";
2834      }
2835  
2836      if (!empty($CFG->forum_enabletimedposts)) {
2837          $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2838          $params['now1'] = $now;
2839          $params['now2'] = $now;
2840      } else {
2841          $timedsql = "";
2842      }
2843  
2844      $sql = "SELECT d.id, COUNT(p.id) AS unread
2845                FROM {forum_discussions} d
2846                     JOIN {forum_posts} p     ON p.discussion = d.id
2847                     LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2848               WHERE d.forum = {$cm->instance}
2849                     AND p.modified >= :cutoffdate AND r.id is NULL
2850                     $groupselect
2851                     $timedsql
2852            GROUP BY d.id";
2853      $params['cutoffdate'] = $cutoffdate;
2854  
2855      if ($unreads = $DB->get_records_sql($sql, $params)) {
2856          foreach ($unreads as $unread) {
2857              $unreads[$unread->id] = $unread->unread;
2858          }
2859          return $unreads;
2860      } else {
2861          return array();
2862      }
2863  }
2864  
2865  /**
2866   * @global object
2867   * @global object
2868   * @global object
2869   * @uses CONEXT_MODULE
2870   * @uses VISIBLEGROUPS
2871   * @param object $cm
2872   * @return array
2873   */
2874  function forum_get_discussions_count($cm) {
2875      global $CFG, $DB, $USER;
2876  
2877      $now = round(time(), -2);
2878      $params = array($cm->instance);
2879      $groupmode    = groups_get_activity_groupmode($cm);
2880      $currentgroup = groups_get_activity_group($cm);
2881  
2882      if ($groupmode) {
2883          $modcontext = context_module::instance($cm->id);
2884  
2885          if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2886              if ($currentgroup) {
2887                  $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2888                  $params[] = $currentgroup;
2889              } else {
2890                  $groupselect = "";
2891              }
2892  
2893          } else {
2894              //seprate groups without access all
2895              if ($currentgroup) {
2896                  $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2897                  $params[] = $currentgroup;
2898              } else {
2899                  $groupselect = "AND d.groupid = -1";
2900              }
2901          }
2902      } else {
2903          $groupselect = "";
2904      }
2905  
2906      $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2907  
2908      $timelimit = "";
2909  
2910      if (!empty($CFG->forum_enabletimedposts)) {
2911  
2912          $modcontext = context_module::instance($cm->id);
2913  
2914          if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2915              $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2916              $params[] = $now;
2917              $params[] = $now;
2918              if (isloggedin()) {
2919                  $timelimit .= " OR d.userid = ?";
2920                  $params[] = $USER->id;
2921              }
2922              $timelimit .= ")";
2923          }
2924      }
2925  
2926      $sql = "SELECT COUNT(d.id)
2927                FROM {forum_discussions} d
2928                     JOIN {forum_posts} p ON p.discussion = d.id
2929               WHERE d.forum = ? AND p.parent = 0
2930                     $groupselect $timelimit";
2931  
2932      return $DB->get_field_sql($sql, $params);
2933  }
2934  
2935  
2936  // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2937  
2938  
2939  /**
2940   * @global object
2941   * @global object
2942   * @param int $courseid
2943   * @param string $type
2944   */
2945  function forum_get_course_forum($courseid, $type) {
2946  // How to set up special 1-per-course forums
2947      global $CFG, $DB, $OUTPUT, $USER;
2948  
2949      if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2950          // There should always only be ONE, but with the right combination of
2951          // errors there might be more.  In this case, just return the oldest one (lowest ID).
2952          foreach ($forums as $forum) {
2953              return $forum;   // ie the first one
2954          }
2955      }
2956  
2957      // Doesn't exist, so create one now.
2958      $forum = new stdClass();
2959      $forum->course = $courseid;
2960      $forum->type = "$type";
2961      if (!empty($USER->htmleditor)) {
2962          $forum->introformat = $USER->htmleditor;
2963      }
2964      switch ($forum->type) {
2965          case "news":
2966              $forum->name  = get_string("namenews", "forum");
2967              $forum->intro = get_string("intronews", "forum");
2968              $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2969              $forum->assessed = 0;
2970              if ($courseid == SITEID) {
2971                  $forum->name  = get_string("sitenews");
2972                  $forum->forcesubscribe = 0;
2973              }
2974              break;
2975          case "social":
2976              $forum->name  = get_string("namesocial", "forum");
2977              $forum->intro = get_string("introsocial", "forum");
2978              $forum->assessed = 0;
2979              $forum->forcesubscribe = 0;
2980              break;
2981          case "blog":
2982              $forum->name = get_string('blogforum', 'forum');
2983              $forum->intro = get_string('introblog', 'forum');
2984              $forum->assessed = 0;
2985              $forum->forcesubscribe = 0;
2986              break;
2987          default:
2988              echo $OUTPUT->notification("That forum type doesn't exist!");
2989              return false;
2990              break;
2991      }
2992  
2993      $forum->timemodified = time();
2994      $forum->id = $DB->insert_record("forum", $forum);
2995  
2996      if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2997          echo $OUTPUT->notification("Could not find forum module!!");
2998          return false;
2999      }
3000      $mod = new stdClass();
3001      $mod->course = $courseid;
3002      $mod->module = $module->id;
3003      $mod->instance = $forum->id;
3004      $mod->section = 0;
3005      include_once("$CFG->dirroot/course/lib.php");
3006      if (! $mod->coursemodule = add_course_module($mod) ) {
3007          echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
3008          return false;
3009      }
3010      $sectionid = course_add_cm_to_section($courseid, $mod->coursemodule, 0);
3011      return $DB->get_record("forum", array("id" => "$forum->id"));
3012  }
3013  
3014  
3015  /**
3016   * Given the data about a posting, builds up the HTML to display it and
3017   * returns the HTML in a string.  This is designed for sending via HTML email.
3018   *
3019   * @global object
3020   * @param object $course
3021   * @param object $cm
3022   * @param object $forum
3023   * @param object $discussion
3024   * @param object $post
3025   * @param object $userform
3026   * @param object $userto
3027   * @param bool $ownpost
3028   * @param bool $reply
3029   * @param bool $link
3030   * @param bool $rate
3031   * @param string $footer
3032   * @return string
3033   */
3034  function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3035                                $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3036  
3037      global $CFG, $OUTPUT;
3038  
3039      $modcontext = context_module::instance($cm->id);
3040  
3041      if (!isset($userto->viewfullnames[$forum->id])) {
3042          $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3043      } else {
3044          $viewfullnames = $userto->viewfullnames[$forum->id];
3045      }
3046  
3047      // add absolute file links
3048      $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3049  
3050      // format the post body
3051      $options = new stdClass();
3052      $options->para = true;
3053      $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3054  
3055      $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3056  
3057      $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3058      $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3059      $output .= '</td>';
3060  
3061      if ($post->parent) {
3062          $output .= '<td class="topic">';
3063      } else {
3064          $output .= '<td class="topic starter">';
3065      }
3066      $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3067  
3068      $fullname = fullname($userfrom, $viewfullnames);
3069      $by = new stdClass();
3070      $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3071      $by->date = userdate($post->modified, '', $userto->timezone);
3072      $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3073  
3074      $output .= '</td></tr>';
3075  
3076      $output .= '<tr><td class="left side" valign="top">';
3077  
3078      if (isset($userfrom->groups)) {
3079          $groups = $userfrom->groups[$forum->id];
3080      } else {
3081          $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3082      }
3083  
3084      if ($groups) {
3085          $output .= print_group_picture($groups, $course->id, false, true, true);
3086      } else {
3087          $output .= '&nbsp;';
3088      }
3089  
3090      $output .= '</td><td class="content">';
3091  
3092      $attachments = forum_print_attachments($post, $cm, 'html');
3093      if ($attachments !== '') {
3094          $output .= '<div class="attachments">';
3095          $output .= $attachments;
3096          $output .= '</div>';
3097      }
3098  
3099      $output .= $formattedtext;
3100  
3101  // Commands
3102      $commands = array();
3103  
3104      if ($post->parent) {
3105          $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3106                        $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3107      }
3108  
3109      if ($reply) {
3110          $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3111                        get_string('reply', 'forum').'</a>';
3112      }
3113  
3114      $output .= '<div class="commands">';
3115      $output .= implode(' | ', $commands);
3116      $output .= '</div>';
3117  
3118  // Context link to post if required
3119      if ($link) {
3120          $output .= '<div class="link">';
3121          $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3122                       get_string('postincontext', 'forum').'</a>';
3123          $output .= '</div>';
3124      }
3125  
3126      if ($footer) {
3127          $output .= '<div class="footer">'.$footer.'</div>';
3128      }
3129      $output .= '</td></tr></table>'."\n\n";
3130  
3131      return $output;
3132  }
3133  
3134  /**
3135   * Print a forum post
3136   *
3137   * @global object
3138   * @global object
3139   * @uses FORUM_MODE_THREADED
3140   * @uses PORTFOLIO_FORMAT_PLAINHTML
3141   * @uses PORTFOLIO_FORMAT_FILE
3142   * @uses PORTFOLIO_FORMAT_RICHHTML
3143   * @uses PORTFOLIO_ADD_TEXT_LINK
3144   * @uses CONTEXT_MODULE
3145   * @param object $post The post to print.
3146   * @param object $discussion
3147   * @param object $forum
3148   * @param object $cm
3149   * @param object $course
3150   * @param boolean $ownpost Whether this post belongs to the current user.
3151   * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3152   * @param boolean $link Just print a shortened version of the post as a link to the full post.
3153   * @param string $footer Extra stuff to print after the message.
3154   * @param string $highlight Space-separated list of terms to highlight.
3155   * @param int $post_read true, false or -99. If we already know whether this user
3156   *          has read this post, pass that in, otherwise, pass in -99, and this
3157   *          function will work it out.
3158   * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3159   *          the current user can't see this post, if this argument is true
3160   *          (the default) then print a dummy 'you can't see this post' post.
3161   *          If false, don't output anything at all.
3162   * @param bool|null $istracked
3163   * @return void
3164   */
3165  function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3166                            $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3167      global $USER, $CFG, $OUTPUT;
3168  
3169      require_once($CFG->libdir . '/filelib.php');
3170  
3171      // String cache
3172      static $str;
3173  
3174      $modcontext = context_module::instance($cm->id);
3175  
3176      $post->course = $course->id;
3177      $post->forum  = $forum->id;
3178      $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3179      if (!empty($CFG->enableplagiarism)) {
3180          require_once($CFG->libdir.'/plagiarismlib.php');
3181          $post->message .= plagiarism_get_links(array('userid' => $post->userid,
3182              'content' => $post->message,
3183              'cmid' => $cm->id,
3184              'course' => $post->course,
3185              'forum' => $post->forum));
3186      }
3187  
3188      // caching
3189      if (!isset($cm->cache)) {
3190          $cm->cache = new stdClass;
3191      }
3192  
3193      if (!isset($cm->cache->caps)) {
3194          $cm->cache->caps = array();
3195          $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3196          $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3197          $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3198          $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3199          $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3200          $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3201          $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3202          $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3203          $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3204      }
3205  
3206      if (!isset($cm->uservisible)) {
3207          $cm->uservisible = \core_availability\info_module::is_user_visible($cm, 0, false);
3208      }
3209  
3210      if ($istracked && is_null($postisread)) {
3211          $postisread = forum_tp_is_post_read($USER->id, $post);
3212      }
3213  
3214      if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3215          $output = '';
3216          if (!$dummyifcantsee) {
3217              if ($return) {
3218                  return $output;
3219              }
3220              echo $output;
3221              return;
3222          }
3223          $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3224          $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix',
3225                                                         'role' => 'region',
3226                                                         'aria-label' => get_string('hiddenforumpost', 'forum')));
3227          $output .= html_writer::start_tag('div', array('class'=>'row header'));
3228          $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3229          if ($post->parent) {
3230              $output .= html_writer::start_tag('div', array('class'=>'topic'));
3231          } else {
3232              $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3233          }
3234          $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class' => 'subject',
3235                                                                                             'role' => 'header')); // Subject.
3236          $output .= html_writer::tag('div', get_string('forumauthorhidden', 'forum'), array('class' => 'author',
3237                                                                                             'role' => 'header')); // Author.
3238          $output .= html_writer::end_tag('div');
3239          $output .= html_writer::end_tag('div'); // row
3240          $output .= html_writer::start_tag('div', array('class'=>'row'));
3241          $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3242          $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3243          $output .= html_writer::end_tag('div'); // row
3244          $output .= html_writer::end_tag('div'); // forumpost
3245  
3246          if ($return) {
3247              return $output;
3248          }
3249          echo $output;
3250          return;
3251      }
3252  
3253      if (empty($str)) {
3254          $str = new stdClass;
3255          $str->edit         = get_string('edit', 'forum');
3256          $str->delete       = get_string('delete', 'forum');
3257          $str->reply        = get_string('reply', 'forum');
3258          $str->parent       = get_string('parent', 'forum');
3259          $str->pruneheading = get_string('pruneheading', 'forum');
3260          $str->prune        = get_string('prune', 'forum');
3261          $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3262          $str->markread     = get_string('markread', 'forum');
3263          $str->markunread   = get_string('markunread', 'forum');
3264      }
3265  
3266      $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3267  
3268      // Build an object that represents the posting user
3269      $postuser = new stdClass;
3270      $postuserfields = explode(',', user_picture::fields());
3271      $postuser = username_load_fields_from_object($postuser, $post, null, $postuserfields);
3272      $postuser->id = $post->userid;
3273      $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3274      $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3275  
3276      // Prepare the groups the posting user belongs to
3277      if (isset($cm->cache->usersgroups)) {
3278          $groups = array();
3279          if (isset($cm->cache->usersgroups[$post->userid])) {
3280              foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3281                  $groups[$gid] = $cm->cache->groups[$gid];
3282              }
3283          }
3284      } else {
3285          $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3286      }
3287  
3288      // Prepare the attachements for the post, files then images
3289      list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3290  
3291      // Determine if we need to shorten this post
3292      $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3293  
3294  
3295      // Prepare an array of commands
3296      $commands = array();
3297  
3298      // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3299      // Don't display the mark read / unread controls in this case.
3300      if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3301          $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3302          $text = $str->markunread;
3303          if (!$postisread) {
3304              $url->param('mark', 'read');
3305              $text = $str->markread;
3306          }
3307          if ($str->displaymode == FORUM_MODE_THREADED) {
3308              $url->param('parent', $post->parent);
3309          } else {
3310              $url->set_anchor('p'.$post->id);
3311          }
3312          $commands[] = array('url'=>$url, 'text'=>$text);
3313      }
3314  
3315      // Zoom in to the parent specifically
3316      if ($post->parent) {
3317          $url = new moodle_url($discussionlink);
3318          if ($str->displaymode == FORUM_MODE_THREADED) {
3319              $url->param('parent', $post->parent);
3320          } else {
3321              $url->set_anchor('p'.$post->parent);
3322          }
3323          $commands[] = array('url'=>$url, 'text'=>$str->parent);
3324      }
3325  
3326      // Hack for allow to edit news posts those are not displayed yet until they are displayed
3327      $age = time() - $post->created;
3328      if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3329          $age = 0;
3330      }
3331  
3332      if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3333          if (has_capability('moodle/course:manageactivities', $modcontext)) {
3334              // The first post in single simple is the forum description.
3335              $commands[] = array('url'=>new moodle_url('/course/modedit.php', array('update'=>$cm->id, 'sesskey'=>sesskey(), 'return'=>1)), 'text'=>$str->edit);
3336          }
3337      } else if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3338          $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3339      }
3340  
3341      if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3342          $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3343      }
3344  
3345      if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3346          // Do not allow deleting of first post in single simple type.
3347      } else if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3348          $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3349      }
3350  
3351      if ($reply) {
3352          $commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mformforum', array('reply'=>$post->id)), 'text'=>$str->reply);
3353      }
3354  
3355      if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3356          $p = array('postid' => $post->id);
3357          require_once($CFG->libdir.'/portfoliolib.php');
3358          $button = new portfolio_add_button();
3359          $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), 'mod_forum');
3360          if (empty($attachments)) {
3361              $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3362          } else {
3363              $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3364          }
3365  
3366          $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3367          if (!empty($porfoliohtml)) {
3368              $commands[] = $porfoliohtml;
3369          }
3370      }
3371      // Finished building commands
3372  
3373  
3374      // Begin output
3375  
3376      $output  = '';
3377  
3378      if ($istracked) {
3379          if ($postisread) {
3380              $forumpostclass = ' read';
3381          } else {
3382              $forumpostclass = ' unread';
3383              $output .= html_writer::tag('a', '', array('name'=>'unread'));
3384          }
3385      } else {
3386          // ignore trackign status if not tracked or tracked param missing
3387          $forumpostclass = '';
3388      }
3389  
3390      $topicclass = '';
3391      if (empty($post->parent)) {
3392          $topicclass = ' firstpost starter';
3393      }
3394  
3395      $postbyuser = new stdClass;
3396      $postbyuser->post = $post->subject;
3397      $postbyuser->user = $postuser->fullname;
3398      $discussionbyuser = get_string('postbyuser', 'forum', $postbyuser);
3399      $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3400      $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass,
3401                                                     'role' => 'region',
3402                                                     'aria-label' => $discussionbyuser));
3403      $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3404      $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3405      $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3406      $output .= html_writer::end_tag('div');
3407  
3408  
3409      $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3410  
3411      $postsubject = $post->subject;
3412      if (empty($post->subjectnoformat)) {
3413          $postsubject = format_string($postsubject);
3414      }
3415      $output .= html_writer::tag('div', $postsubject, array('class'=>'subject',
3416                                                             'role' => 'heading',
3417                                                             'aria-level' => '2'));
3418  
3419      $by = new stdClass();
3420      $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3421      $by->date = userdate($post->modified);
3422      $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author',
3423                                                                                         'role' => 'heading',
3424                                                                                         'aria-level' => '2'));
3425  
3426      $output .= html_writer::end_tag('div'); //topic
3427      $output .= html_writer::end_tag('div'); //row
3428  
3429      $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3430      $output .= html_writer::start_tag('div', array('class'=>'left'));
3431  
3432      $groupoutput = '';
3433      if ($groups) {
3434          $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3435      }
3436      if (empty($groupoutput)) {
3437          $groupoutput = '&nbsp;';
3438      }
3439      $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3440  
3441      $output .= html_writer::end_tag('div'); //left side
3442      $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3443      $output .= html_writer::start_tag('div', array('class'=>'content'));
3444  
3445      $options = new stdClass;
3446      $options->para    = false;
3447      $options->trusted = $post->messagetrust;
3448      $options->context = $modcontext;
3449      if ($shortenpost) {
3450          // Prepare shortened version by filtering the text then shortening it.
3451          $postclass    = 'shortenedpost';
3452          $postcontent  = format_text($post->message, $post->messageformat, $options);
3453          $postcontent  = shorten_text($postcontent, $CFG->forum_shortpost);
3454          $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3455          $postcontent .= html_writer::tag('div', '('.get_string('numwords', 'moodle', count_words($post->message)).')',
3456              array('class'=>'post-word-count'));
3457      } else {
3458          // Prepare whole post
3459          $postclass    = 'fullpost';
3460          $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3461          if (!empty($highlight)) {
3462              $postcontent = highlight($highlight, $postcontent);
3463          }
3464          if (!empty($forum->displaywordcount)) {
3465              $postcontent .= html_writer::tag('div', get_string('numwords', 'moodle', count_words($post->message)),
3466                  array('class'=>'post-word-count'));
3467          }
3468          $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3469      }
3470  
3471      // Output the post content
3472      $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3473      $output .= html_writer::end_tag('div'); // Content
3474      $output .= html_writer::end_tag('div'); // Content mask
3475      $output .= html_writer::end_tag('div'); // Row
3476  
3477      $output .= html_writer::start_tag('div', array('class'=>'row side'));
3478      $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3479      $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3480  
3481      if (!empty($attachments)) {
3482          $output .= html_writer::tag('div', $attachments, array('class' => 'attachments'));
3483      }
3484  
3485      // Output ratings
3486      if (!empty($post->rating)) {
3487          $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3488      }
3489  
3490      // Output the commands
3491      $commandhtml = array();
3492      foreach ($commands as $command) {
3493          if (is_array($command)) {
3494              $commandhtml[] = html_writer::link($command['url'], $command['text']);
3495          } else {
3496              $commandhtml[] = $command;
3497          }
3498      }
3499      $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3500  
3501      // Output link to post if required
3502      if ($link && forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext)) {
3503          if ($post->replies == 1) {
3504              $replystring = get_string('repliesone', 'forum', $post->replies);
3505          } else {
3506              $replystring = get_string('repliesmany', 'forum', $post->replies);
3507          }
3508  
3509          $output .= html_writer::start_tag('div', array('class'=>'link'));
3510          $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3511          $output .= '&nbsp;('.$replystring.')';
3512          $output .= html_writer::end_tag('div'); // link
3513      }
3514  
3515      // Output footer if required
3516      if ($footer) {
3517          $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3518      }
3519  
3520      // Close remaining open divs
3521      $output .= html_writer::end_tag('div'); // content
3522      $output .= html_writer::end_tag('div'); // row
3523      $output .= html_writer::end_tag('div'); // forumpost
3524  
3525      // Mark the forum post as read if required
3526      if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3527          forum_tp_mark_post_read($USER->id, $post, $forum->id);
3528      }
3529  
3530      if ($return) {
3531          return $output;
3532      }
3533      echo $output;
3534      return;
3535  }
3536  
3537  /**
3538   * Return rating related permissions
3539   *
3540   * @param string $options the context id
3541   * @return array an associative array of the user's rating permissions
3542   */
3543  function forum_rating_permissions($contextid, $component, $ratingarea) {
3544      $context = context::instance_by_id($contextid, MUST_EXIST);
3545      if ($component != 'mod_forum' || $ratingarea != 'post') {
3546          // We don't know about this component/ratingarea so just return null to get the
3547          // default restrictive permissions.
3548          return null;
3549      }
3550      return array(
3551          'view'    => has_capability('mod/forum:viewrating', $context),
3552          'viewany' => has_capability('mod/forum:viewanyrating', $context),
3553          'viewall' => has_capability('mod/forum:viewallratings', $context),
3554          'rate'    => has_capability('mod/forum:rate', $context)
3555      );
3556  }
3557  
3558  /**
3559   * Validates a submitted rating
3560   * @param array $params submitted data
3561   *            context => object the context in which the rated items exists [required]
3562   *            component => The component for this module - should always be mod_forum [required]
3563   *            ratingarea => object the context in which the rated items exists [required]
3564   *            itemid => int the ID of the object being rated [required]
3565   *            scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
3566   *            rating => int the submitted rating [required]
3567   *            rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
3568   *            aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
3569   * @return boolean true if the rating is valid. Will throw rating_exception if not
3570   */
3571  function forum_rating_validate($params) {
3572      global $DB, $USER;
3573  
3574      // Check the component is mod_forum
3575      if ($params['component'] != 'mod_forum') {
3576          throw new rating_exception('invalidcomponent');
3577      }
3578  
3579      // Check the ratingarea is post (the only rating area in forum)
3580      if ($params['ratingarea'] != 'post') {
3581          throw new rating_exception('invalidratingarea');
3582      }
3583  
3584      // Check the rateduserid is not the current user .. you can't rate your own posts
3585      if ($params['rateduserid'] == $USER->id) {
3586          throw new rating_exception('nopermissiontorate');
3587      }
3588  
3589      // Fetch all the related records ... we need to do this anyway to call forum_user_can_see_post
3590      $post = $DB->get_record('forum_posts', array('id' => $params['itemid'], 'userid' => $params['rateduserid']), '*', MUST_EXIST);
3591      $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion), '*', MUST_EXIST);
3592      $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
3593      $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
3594      $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id , false, MUST_EXIST);
3595      $context = context_module::instance($cm->id);
3596  
3597      // Make sure the context provided is the context of the forum
3598      if ($context->id != $params['context']->id) {
3599          throw new rating_exception('invalidcontext');
3600      }
3601  
3602      if ($forum->scale != $params['scaleid']) {
3603          //the scale being submitted doesnt match the one in the database
3604          throw new rating_exception('invalidscaleid');
3605      }
3606  
3607      // check the item we're rating was created in the assessable time window
3608      if (!empty($forum->assesstimestart) && !empty($forum->assesstimefinish)) {
3609          if ($post->created < $forum->assesstimestart || $post->created > $forum->assesstimefinish) {
3610              throw new rating_exception('notavailable');
3611          }
3612      }
3613  
3614      //check that the submitted rating is valid for the scale
3615  
3616      // lower limit
3617      if ($params['rating'] < 0  && $params['rating'] != RATING_UNSET_RATING) {
3618          throw new rating_exception('invalidnum');
3619      }
3620  
3621      // upper limit
3622      if ($forum->scale < 0) {
3623          //its a custom scale
3624          $scalerecord = $DB->get_record('scale', array('id' => -$forum->scale));
3625          if ($scalerecord) {
3626              $scalearray = explode(',', $scalerecord->scale);
3627              if ($params['rating'] > count($scalearray)) {
3628                  throw new rating_exception('invalidnum');
3629              }
3630          } else {
3631              throw new rating_exception('invalidscaleid');
3632          }
3633      } else if ($params['rating'] > $forum->scale) {
3634          //if its numeric and submitted rating is above maximum
3635          throw new rating_exception('invalidnum');
3636      }
3637  
3638      // Make sure groups allow this user to see the item they're rating
3639      if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
3640          if (!groups_group_exists($discussion->groupid)) { // Can't find group
3641              throw new rating_exception('cannotfindgroup');//something is wrong
3642          }
3643  
3644          if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
3645              // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
3646              throw new rating_exception('notmemberofgroup');
3647          }
3648      }
3649  
3650      // perform some final capability checks
3651      if (!forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) {
3652          throw new rating_exception('nopermissiontorate');
3653      }
3654  
3655      return true;
3656  }
3657  
3658  
3659  /**
3660   * This function prints the overview of a discussion in the forum listing.
3661   * It needs some discussion information and some post information, these
3662   * happen to be combined for efficiency in the $post parameter by the function
3663   * that calls this one: forum_print_latest_discussions()
3664   *
3665   * @global object
3666   * @global object
3667   * @param object $post The post object (passed by reference for speed).
3668   * @param object $forum The forum object.
3669   * @param int $group Current group.
3670   * @param string $datestring Format to use for the dates.
3671   * @param boolean $cantrack Is tracking enabled for this forum.
3672   * @param boolean $forumtracked Is the user tracking this forum.
3673   * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3674   */
3675  function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3676                                          $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3677  
3678      global $COURSE, $USER, $CFG, $OUTPUT;
3679  
3680      static $rowcount;
3681      static $strmarkalldread;
3682  
3683      if (empty($modcontext)) {
3684          if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3685              print_error('invalidcoursemodule');
3686          }
3687          $modcontext = context_module::instance($cm->id);
3688      }
3689  
3690      if (!isset($rowcount)) {
3691          $rowcount = 0;
3692          $strmarkalldread = get_string('markalldread', 'forum');
3693      } else {
3694          $rowcount = ($rowcount + 1) % 2;
3695      }
3696  
3697      $post->subject = format_string($post->subject,true);
3698  
3699      echo "\n\n";
3700      echo '<tr class="discussion r'.$rowcount.'">';
3701  
3702      // Topic
3703      echo '<td class="topic starter">';
3704      echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3705      echo "</td>\n";
3706  
3707      // Picture
3708      $postuser = new stdClass();
3709      $postuserfields = explode(',', user_picture::fields());
3710      $postuser = username_load_fields_from_object($postuser, $post, null, $postuserfields);
3711      $postuser->id = $post->userid;
3712      echo '<td class="picture">';
3713      echo $OUTPUT->user_picture($postuser, array('courseid'=>$forum->course));
3714      echo "</td>\n";
3715  
3716      // User name
3717      $fullname = fullname($postuser, has_capability('moodle/site:viewfullnames', $modcontext));
3718      echo '<td class="author">';
3719      echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
3720      echo "</td>\n";
3721  
3722      // Group picture
3723      if ($group !== -1) {  // Groups are active - group is a group data object or NULL
3724          echo '<td class="picture group">';
3725          if (!empty($group->picture) and empty($group->hidepicture)) {
3726              if ($canviewparticipants && $COURSE->groupmode) {
3727                  $picturelink = true;
3728              } else {
3729                  $picturelink = false;
3730              }
3731              print_group_picture($group, $forum->course, false, false, $picturelink);
3732          } else if (isset($group->id)) {
3733              if ($canviewparticipants && $COURSE->groupmode) {
3734                  echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
3735              } else {
3736                  echo $group->name;
3737              }
3738          }
3739          echo "</td>\n";
3740      }
3741  
3742      if (has_capability('mod/forum:viewdiscussion', $modcontext)) {   // Show the column with replies
3743          echo '<td class="replies">';
3744          echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3745          echo $post->replies.'</a>';
3746          echo "</td>\n";
3747  
3748          if ($cantrack) {
3749              echo '<td class="replies">';
3750              if ($forumtracked) {
3751                  if ($post->unread > 0) {
3752                      echo '<span class="unread">';
3753                      echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
3754                      echo $post->unread;
3755                      echo '</a>';
3756                      echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3757                           $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
3758                           '<img src="'.$OUTPUT->pix_url('t/markasread') . '" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
3759                      echo '</span>';
3760                  } else {
3761                      echo '<span class="read">';
3762                      echo $post->unread;
3763                      echo '</span>';
3764                  }
3765              } else {
3766                  echo '<span class="read">';
3767                  echo '-';
3768                  echo '</span>';
3769              }
3770              echo "</td>\n";
3771          }
3772      }
3773  
3774      echo '<td class="lastpost">';
3775      $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified;  // Just in case
3776      $parenturl = '';
3777      $usermodified = new stdClass();
3778      $usermodified->id = $post->usermodified;
3779      $usermodified = username_load_fields_from_object($usermodified, $post, 'um');
3780  
3781      // Show link to last poster and their post if user can see them.
3782      if ($canviewparticipants) {
3783          echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
3784               fullname($usermodified).'</a><br />';
3785          $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
3786      }
3787  
3788      echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
3789            userdate($usedate, $datestring).'</a>';
3790      echo "</td>\n";
3791  
3792      // is_guest should be used here as this also checks whether the user is a guest in the current course.
3793      // Guests and visitors cannot subscribe - only enrolled users.
3794      if ((!is_guest($modcontext, $USER) && isloggedin()) && has_capability('mod/forum:viewdiscussion', $modcontext)) {
3795          // Discussion subscription.
3796          if (\mod_forum\subscriptions::is_subscribable($forum)) {
3797              echo '<td class="discussionsubscription">';
3798              echo forum_get_discussion_subscription_icon($forum, $post->discussion);
3799              echo '</td>';
3800          }
3801      }
3802  
3803      echo "</tr>\n\n";
3804  
3805  }
3806  
3807  /**
3808   * Return the markup for the discussion subscription toggling icon.
3809   *
3810   * @param stdClass $forum The forum object.
3811   * @param int $discussionid The discussion to create an icon for.
3812   * @return string The generated markup.
3813   */
3814  function forum_get_discussion_subscription_icon($forum, $discussionid, $returnurl = null, $includetext = false) {
3815      global $USER, $OUTPUT, $PAGE;
3816  
3817      if ($returnurl === null && $PAGE->url) {
3818          $returnurl = $PAGE->url->out();
3819      }
3820  
3821      $o = '';
3822      $subscriptionstatus = \mod_forum\subscriptions::is_subscribed($USER->id, $forum, $discussionid);
3823      $subscriptionlink = new moodle_url('/mod/forum/subscribe.php', array(
3824          'sesskey' => sesskey(),
3825          'id' => $forum->id,
3826          'd' => $discussionid,
3827          'returnurl' => $returnurl,
3828      ));
3829  
3830      if ($includetext) {
3831          $o .= $subscriptionstatus ? get_string('subscribed', 'mod_forum') : get_string('notsubscribed', 'mod_forum');
3832      }
3833  
3834      if ($subscriptionstatus) {
3835          $output = $OUTPUT->pix_icon('t/subscribed', get_string('clicktounsubscribe', 'forum'), 'mod_forum');
3836          if ($includetext) {
3837              $output .= get_string('subscribed', 'mod_forum');
3838          }
3839  
3840          return html_writer::link($subscriptionlink, $output, array(
3841                  'title' => get_string('clicktounsubscribe', 'forum'),
3842                  'class' => 'discussiontoggle iconsmall',
3843                  'data-forumid' => $forum->id,
3844                  'data-discussionid' => $discussionid,
3845                  'data-includetext' => $includetext,
3846              ));
3847  
3848      } else {
3849          $output = $OUTPUT->pix_icon('t/unsubscribed', get_string('clicktosubscribe', 'forum'), 'mod_forum');
3850          if ($includetext) {
3851              $output .= get_string('notsubscribed', 'mod_forum');
3852          }
3853  
3854          return html_writer::link($subscriptionlink, $output, array(
3855                  'title' => get_string('clicktosubscribe', 'forum'),
3856                  'class' => 'discussiontoggle iconsmall',
3857                  'data-forumid' => $forum->id,
3858                  'data-discussionid' => $discussionid,
3859                  'data-includetext' => $includetext,
3860              ));
3861      }
3862  }
3863  
3864  /**
3865   * Return a pair of spans containing classes to allow the subscribe and
3866   * unsubscribe icons to be pre-loaded by a browser.
3867   *
3868   * @return string The generated markup
3869   */
3870  function forum_get_discussion_subscription_icon_preloaders() {
3871      $o = '';
3872      $o .= html_writer::span('&nbsp;', 'preload-subscribe');
3873      $o .= html_writer::span('&nbsp;', 'preload-unsubscribe');
3874      return $o;
3875  }
3876  
3877  /**
3878   * Print the drop down that allows the user to select how they want to have
3879   * the discussion displayed.
3880   *
3881   * @param int $id forum id if $forumtype is 'single',
3882   *              discussion id for any other forum type
3883   * @param mixed $mode forum layout mode
3884   * @param string $forumtype optional
3885   */
3886  function forum_print_mode_form($id, $mode, $forumtype='') {
3887      global $OUTPUT;
3888      if ($forumtype == 'single') {
3889          $select = new single_select(new moodle_url("/mod/forum/view.php", array('f'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3890          $select->set_label(get_string('displaymode', 'forum'), array('class' => 'accesshide'));
3891          $select->class = "forummode";
3892      } else {
3893          $select = new single_select(new moodle_url("/mod/forum/discuss.php", array('d'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3894          $select->set_label(get_string('displaymode', 'forum'), array('class' => 'accesshide'));
3895      }
3896      echo $OUTPUT->render($select);
3897  }
3898  
3899  /**
3900   * @global object
3901   * @param object $course
3902   * @param string $search
3903   * @return string
3904   */
3905  function forum_search_form($course, $search='') {
3906      global $CFG, $OUTPUT;
3907  
3908      $output  = '<div class="forumsearch">';
3909      $output .= '<form action="'.$CFG->wwwroot.'/mod/forum/search.php" style="display:inline">';
3910      $output .= '<fieldset class="invisiblefieldset">';
3911      $output .= $OUTPUT->help_icon('search');
3912      $output .= '<label class="accesshide" for="search" >'.get_string('search', 'forum').'</label>';
3913      $output .= '<input id="search" name="search" type="text" size="18" value="'.s($search, true).'" />';
3914      $output .= '<label class="accesshide" for="searchforums" >'.get_string('searchforums', 'forum').'</label>';
3915      $output .= '<input id="searchforums" value="'.get_string('searchforums', 'forum').'" type="submit" />';
3916      $output .= '<input name="id" type="hidden" value="'.$course->id.'" />';
3917      $output .= '</fieldset>';
3918      $output .= '</form>';
3919      $output .= '</div>';
3920  
3921      return $output;
3922  }
3923  
3924  
3925  /**
3926   * @global object
3927   * @global object
3928   */
3929  function forum_set_return() {
3930      global $CFG, $SESSION;
3931  
3932      if (! isset($SESSION->fromdiscussion)) {
3933          if (!empty($_SERVER['HTTP_REFERER'])) {
3934              $referer = $_SERVER['HTTP_REFERER'];
3935          } else {
3936              $referer = "";
3937          }
3938          // If the referer is NOT a login screen then save it.
3939          if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) {
3940              $SESSION->fromdiscussion = $_SERVER["HTTP_REFERER"];
3941          }
3942      }
3943  }
3944  
3945  
3946  /**
3947   * @global object
3948   * @param string $default
3949   * @return string
3950   */
3951  function forum_go_back_to($default) {
3952      global $SESSION;
3953  
3954      if (!empty($SESSION->fromdiscussion)) {
3955          $returnto = $SESSION->fromdiscussion;
3956          unset($SESSION->fromdiscussion);
3957          return $returnto;
3958      } else {
3959          return $default;
3960      }
3961  }
3962  
3963  /**
3964   * Given a discussion object that is being moved to $forumto,
3965   * this function checks all posts in that discussion
3966   * for attachments, and if any are found, these are
3967   * moved to the new forum directory.
3968   *
3969   * @global object
3970   * @param object $discussion
3971   * @param int $forumfrom source forum id
3972   * @param int $forumto target forum id
3973   * @return bool success
3974   */
3975  function forum_move_attachments($discussion, $forumfrom, $forumto) {
3976      global $DB;
3977  
3978      $fs = get_file_storage();
3979  
3980      $newcm = get_coursemodule_from_instance('forum', $forumto);
3981      $oldcm = get_coursemodule_from_instance('forum', $forumfrom);
3982  
3983      $newcontext = context_module::instance($newcm->id);
3984      $oldcontext = context_module::instance($oldcm->id);
3985  
3986      // loop through all posts, better not use attachment flag ;-)
3987      if ($posts = $DB->get_records('forum_posts', array('discussion'=>$discussion->id), '', 'id, attachment')) {
3988          foreach ($posts as $post) {
3989              $fs->move_area_files_to_new_context($oldcontext->id,
3990                      $newcontext->id, 'mod_forum', 'post', $post->id);
3991              $attachmentsmoved = $fs->move_area_files_to_new_context($oldcontext->id,
3992                      $newcontext->id, 'mod_forum', 'attachment', $post->id);
3993              if ($attachmentsmoved > 0 && $post->attachment != '1') {
3994                  // Weird - let's fix it
3995                  $post->attachment = '1';
3996                  $DB->update_record('forum_posts', $post);
3997              } else if ($attachmentsmoved == 0 && $post->attachment != '') {
3998                  // Weird - let's fix it
3999                  $post->attachment = '';
4000                  $DB->update_record('forum_posts', $post);
4001              }
4002          }
4003      }
4004  
4005      return true;
4006  }
4007  
4008  /**
4009   * Returns attachments as formated text/html optionally with separate images
4010   *
4011   * @global object
4012   * @global object
4013   * @global object
4014   * @param object $post
4015   * @param object $cm
4016   * @param string $type html/text/separateimages
4017   * @return mixed string or array of (html text withouth images and image HTML)
4018   */
4019  function forum_print_attachments($post, $cm, $type) {
4020      global $CFG, $DB, $USER, $OUTPUT;
4021  
4022      if (empty($post->attachment)) {
4023          return $type !== 'separateimages' ? '' : array('', '');
4024      }
4025  
4026      if (!in_array($type, array('separateimages', 'html', 'text'))) {
4027          return $type !== 'separateimages' ? '' : array('', '');
4028      }
4029  
4030      if (!$context = context_module::instance($cm->id)) {
4031          return $type !== 'separateimages' ? '' : array('', '');
4032      }
4033      $strattachment = get_string('attachment', 'forum');
4034  
4035      $fs = get_file_storage();
4036  
4037      $imagereturn = '';
4038      $output = '';
4039  
4040      $canexport = !empty($CFG->enableportfolios) && (has_capability('mod/forum:exportpost', $context) || ($post->userid == $USER->id && has_capability('mod/forum:exportownpost', $context)));
4041  
4042      if ($canexport) {
4043          require_once($CFG->libdir.'/portfoliolib.php');
4044      }
4045  
4046      // We retrieve all files according to the time that they were created.  In the case that several files were uploaded
4047      // at the sametime (e.g. in the case of drag/drop upload) we revert to using the filename.
4048      $files = $fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id, "filename", false);
4049      if ($files) {
4050          if ($canexport) {
4051              $button = new portfolio_add_button();
4052          }
4053          foreach ($files as $file) {
4054              $filename = $file->get_filename();
4055              $mimetype = $file->get_mimetype();
4056              $iconimage = $OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file), 'moodle', array('class' => 'icon'));
4057              $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$context->id.'/mod_forum/attachment/'.$post->id.'/'.$filename);
4058  
4059              if ($type == 'html') {
4060                  $output .= "<a href=\"$path\">$iconimage</a> ";
4061                  $output .= "<a href=\"$path\">".s($filename)."</a>";
4062                  if ($canexport) {
4063                      $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
4064                      $button->set_format_by_file($file);
4065                      $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
4066                  }
4067                  $output .= "<br />";
4068  
4069              } else if ($type == 'text') {
4070                  $output .= "$strattachment ".s($filename).":\n$path\n";
4071  
4072              } else { //'returnimages'
4073                  if (in_array($mimetype, array('image/gif', 'image/jpeg', 'image/png'))) {
4074                      // Image attachments don't get printed as links
4075                      $imagereturn .= "<br /><img src=\"$path\" alt=\"\" />";
4076                      if ($canexport) {
4077                          $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
4078                          $button->set_format_by_file($file);
4079                          $imagereturn .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
4080                      }
4081                  } else {
4082                      $output .= "<a href=\"$path\">$iconimage</a> ";
4083                      $output .= format_text("<a href=\"$path\">".s($filename)."</a>", FORMAT_HTML, array('context'=>$context));
4084                      if ($canexport) {
4085                          $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
4086                          $button->set_format_by_file($file);
4087                          $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
4088                      }
4089                      $output .= '<br />';
4090                  }
4091              }
4092  
4093              if (!empty($CFG->enableplagiarism)) {
4094                  require_once($CFG->libdir.'/plagiarismlib.php');
4095                  $output .= plagiarism_get_links(array('userid' => $post->userid,
4096                      'file' => $file,
4097                      'cmid' => $cm->id,
4098                      'course' => $post->course,
4099                      'forum' => $post->forum));
4100                  $output .= '<br />';
4101              }
4102          }
4103      }
4104  
4105      if ($type !== 'separateimages') {
4106          return $output;
4107  
4108      } else {
4109          return array($output, $imagereturn);
4110      }
4111  }
4112  
4113  ////////////////////////////////////////////////////////////////////////////////
4114  // File API                                                                   //
4115  ////////////////////////////////////////////////////////////////////////////////
4116  
4117  /**
4118   * Lists all browsable file areas
4119   *
4120   * @package  mod_forum
4121   * @category files
4122   * @param stdClass $course course object
4123   * @param stdClass $cm course module object
4124   * @param stdClass $context context object
4125   * @return array
4126   */
4127  function forum_get_file_areas($course, $cm, $context) {
4128      return array(
4129          'attachment' => get_string('areaattachment', 'mod_forum'),
4130          'post' => get_string('areapost', 'mod_forum'),
4131      );
4132  }
4133  
4134  /**
4135   * File browsing support for forum module.
4136   *
4137   * @package  mod_forum
4138   * @category files
4139   * @param stdClass $browser file browser object
4140   * @param stdClass $areas file areas
4141   * @param stdClass $course course object
4142   * @param stdClass $cm course module
4143   * @param stdClass $context context module
4144   * @param string $filearea file area
4145   * @param int $itemid item ID
4146   * @param string $filepath file path
4147   * @param string $filename file name
4148   * @return file_info instance or null if not found
4149   */
4150  function forum_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
4151      global $CFG, $DB, $USER;
4152  
4153      if ($context->contextlevel != CONTEXT_MODULE) {
4154          return null;
4155      }
4156  
4157      // filearea must contain a real area
4158      if (!isset($areas[$filearea])) {
4159          return null;
4160      }
4161  
4162      // Note that forum_user_can_see_post() additionally allows access for parent roles
4163      // and it explicitly checks qanda forum type, too. One day, when we stop requiring
4164      // course:managefiles, we will need to extend this.
4165      if (!has_capability('mod/forum:viewdiscussion', $context)) {
4166          return null;
4167      }
4168  
4169      if (is_null($itemid)) {
4170          require_once($CFG->dirroot.'/mod/forum/locallib.php');
4171          return new forum_file_info_container($browser, $course, $cm, $context, $areas, $filearea);
4172      }
4173  
4174      static $cached = array();
4175      // $cached will store last retrieved post, discussion and forum. To make sure that the cache
4176      // is cleared between unit tests we check if this is the same session
4177      if (!isset($cached['sesskey']) || $cached['sesskey'] != sesskey()) {
4178          $cached = array('sesskey' => sesskey());
4179      }
4180  
4181      if (isset($cached['post']) && $cached['post']->id == $itemid) {
4182          $post = $cached['post'];
4183      } else if ($post = $DB->get_record('forum_posts', array('id' => $itemid))) {
4184          $cached['post'] = $post;
4185      } else {
4186          return null;
4187      }
4188  
4189      if (isset($cached['discussion']) && $cached['discussion']->id == $post->discussion) {
4190          $discussion = $cached['discussion'];
4191      } else if ($discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion))) {
4192          $cached['discussion'] = $discussion;
4193      } else {
4194          return null;
4195      }
4196  
4197      if (isset($cached['forum']) && $cached['forum']->id == $cm->instance) {
4198          $forum = $cached['forum'];
4199      } else if ($forum = $DB->get_record('forum', array('id' => $cm->instance))) {
4200          $cached['forum'] = $forum;
4201      } else {
4202          return null;
4203      }
4204  
4205      $fs = get_file_storage();
4206      $filepath = is_null($filepath) ? '/' : $filepath;
4207      $filename = is_null($filename) ? '.' : $filename;
4208      if (!($storedfile = $fs->get_file($context->id, 'mod_forum', $filearea, $itemid, $filepath, $filename))) {
4209          return null;
4210      }
4211  
4212      // Checks to see if the user can manage files or is the owner.
4213      // TODO MDL-33805 - Do not use userid here and move the capability check above.
4214      if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
4215          return null;
4216      }
4217      // Make sure groups allow this user to see this file
4218      if ($discussion->groupid > 0 && !has_capability('moodle/site:accessallgroups', $context)) {
4219          $groupmode = groups_get_activity_groupmode($cm, $course);
4220          if ($groupmode == SEPARATEGROUPS && !groups_is_member($discussion->groupid)) {
4221              return null;
4222          }
4223      }
4224  
4225      // Make sure we're allowed to see it...
4226      if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4227          return null;
4228      }
4229  
4230      $urlbase = $CFG->wwwroot.'/pluginfile.php';
4231      return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
4232  }
4233  
4234  /**
4235   * Serves the forum attachments. Implements needed access control ;-)
4236   *
4237   * @package  mod_forum
4238   * @category files
4239   * @param stdClass $course course object
4240   * @param stdClass $cm course module object
4241   * @param stdClass $context context object
4242   * @param string $filearea file area
4243   * @param array $args extra arguments
4244   * @param bool $forcedownload whether or not force download
4245   * @param array $options additional options affecting the file serving
4246   * @return bool false if file not found, does not return if found - justsend the file
4247   */
4248  function forum_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
4249      global $CFG, $DB;
4250  
4251      if ($context->contextlevel != CONTEXT_MODULE) {
4252          return false;
4253      }
4254  
4255      require_course_login($course, true, $cm);
4256  
4257      $areas = forum_get_file_areas($course, $cm, $context);
4258  
4259      // filearea must contain a real area
4260      if (!isset($areas[$filearea])) {
4261          return false;
4262      }
4263  
4264      $postid = (int)array_shift($args);
4265  
4266      if (!$post = $DB->get_record('forum_posts', array('id'=>$postid))) {
4267          return false;
4268      }
4269  
4270      if (!$discussion = $DB->get_record('forum_discussions', array('id'=>$post->discussion))) {
4271          return false;
4272      }
4273  
4274      if (!$forum = $DB->get_record('forum', array('id'=>$cm->instance))) {
4275          return false;
4276      }
4277  
4278      $fs = get_file_storage();
4279      $relativepath = implode('/', $args);
4280      $fullpath = "/$context->id/mod_forum/$filearea/$postid/$relativepath";
4281      if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
4282          return false;
4283      }
4284  
4285      // Make sure groups allow this user to see this file
4286      if ($discussion->groupid > 0) {
4287          $groupmode = groups_get_activity_groupmode($cm, $course);
4288          if ($groupmode == SEPARATEGROUPS) {
4289              if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
4290                  return false;
4291              }
4292          }
4293      }
4294  
4295      // Make sure we're allowed to see it...
4296      if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4297          return false;
4298      }
4299  
4300      // finally send the file
4301      send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security!
4302  }
4303  
4304  /**
4305   * If successful, this function returns the name of the file
4306   *
4307   * @global object
4308   * @param object $post is a full post record, including course and forum
4309   * @param object $forum
4310   * @param object $cm
4311   * @param mixed $mform
4312   * @param string $unused
4313   * @return bool
4314   */
4315  function forum_add_attachment($post, $forum, $cm, $mform=null, $unused=null) {
4316      global $DB;
4317  
4318      if (empty($mform)) {
4319          return false;
4320      }
4321  
4322      if (empty($post->attachments)) {
4323          return true;   // Nothing to do
4324      }
4325  
4326      $context = context_module::instance($cm->id);
4327  
4328      $info = file_get_draft_area_info($post->attachments);
4329      $present = ($info['filecount']>0) ? '1' : '';
4330      file_save_draft_area_files($post->attachments, $context->id, 'mod_forum', 'attachment', $post->id,
4331              mod_forum_post_form::attachment_options($forum));
4332  
4333      $DB->set_field('forum_posts', 'attachment', $present, array('id'=>$post->id));
4334  
4335      return true;
4336  }
4337  
4338  /**
4339   * Add a new post in an existing discussion.
4340   *
4341   * @global object
4342   * @global object
4343   * @global object
4344   * @param object $post
4345   * @param mixed $mform
4346   * @param string $unused formerly $message, renamed in 2.8 as it was unused.
4347   * @return int
4348   */
4349  function forum_add_new_post($post, $mform, $unused = null) {
4350      global $USER, $CFG, $DB;
4351  
4352      $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
4353      $forum      = $DB->get_record('forum', array('id' => $discussion->forum));
4354      $cm         = get_coursemodule_from_instance('forum', $forum->id);
4355      $context    = context_module::instance($cm->id);
4356  
4357      $post->created    = $post->modified = time();
4358      $post->mailed     = FORUM_MAILED_PENDING;
4359      $post->userid     = $USER->id;
4360      $post->attachment = "";
4361      if (!isset($post->totalscore)) {
4362          $post->totalscore = 0;
4363      }
4364      if (!isset($post->mailnow)) {
4365          $post->mailnow    = 0;
4366      }
4367  
4368      $post->id = $DB->insert_record("forum_posts", $post);
4369      $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id,
4370              mod_forum_post_form::editor_options($context, null), $post->message);
4371      $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
4372      forum_add_attachment($post, $forum, $cm, $mform);
4373  
4374      // Update discussion modified date
4375      $DB->set_field("forum_discussions", "timemodified", $post->modified, array("id" => $post->discussion));
4376      $DB->set_field("forum_discussions", "usermodified", $post->userid, array("id" => $post->discussion));
4377  
4378      if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4379          forum_tp_mark_post_read($post->userid, $post, $post->forum);
4380      }
4381  
4382      // Let Moodle know that assessable content is uploaded (eg for plagiarism detection)
4383      forum_trigger_content_uploaded_event($post, $cm, 'forum_add_new_post');
4384  
4385      return $post->id;
4386  }
4387  
4388  /**
4389   * Update a post
4390   *
4391   * @global object
4392   * @global object
4393   * @global object
4394   * @param object $post
4395   * @param mixed $mform
4396   * @param string $message
4397   * @return bool
4398   */
4399  function forum_update_post($post, $mform, &$message) {
4400      global $USER, $CFG, $DB;
4401  
4402      $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
4403      $forum      = $DB->get_record('forum', array('id' => $discussion->forum));
4404      $cm         = get_coursemodule_from_instance('forum', $forum->id);
4405      $context    = context_module::instance($cm->id);
4406  
4407      $post->modified = time();
4408  
4409      $DB->update_record('forum_posts', $post);
4410  
4411      $discussion->timemodified = $post->modified; // last modified tracking
4412      $discussion->usermodified = $post->userid;   // last modified tracking
4413  
4414      if (!$post->parent) {   // Post is a discussion starter - update discussion title and times too
4415          $discussion->name      = $post->subject;
4416          $discussion->timestart = $post->timestart;
4417          $discussion->timeend   = $post->timeend;
4418      }
4419      $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id,
4420              mod_forum_post_form::editor_options($context, $post->id), $post->message);
4421      $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
4422  
4423      $DB->update_record('forum_discussions', $discussion);
4424  
4425      forum_add_attachment($post, $forum, $cm, $mform, $message);
4426  
4427      if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4428          forum_tp_mark_post_read($post->userid, $post, $post->forum);
4429      }
4430  
4431      // Let Moodle know that assessable content is uploaded (eg for plagiarism detection)
4432      forum_trigger_content_uploaded_event($post, $cm, 'forum_update_post');
4433  
4434      return true;
4435  }
4436  
4437  /**
4438   * Given an object containing all the necessary data,
4439   * create a new discussion and return the id
4440   *
4441   * @param object $post
4442   * @param mixed $mform
4443   * @param string $unused
4444   * @param int $userid
4445   * @return object
4446   */
4447  function forum_add_discussion($discussion, $mform=null, $unused=null, $userid=null) {
4448      global $USER, $CFG, $DB;
4449  
4450      $timenow = time();
4451  
4452      if (is_null($userid)) {
4453          $userid = $USER->id;
4454      }
4455  
4456      // The first post is stored as a real post, and linked
4457      // to from the discuss entry.
4458  
4459      $forum = $DB->get_record('forum', array('id'=>$discussion->forum));
4460      $cm    = get_coursemodule_from_instance('forum', $forum->id);
4461  
4462      $post = new stdClass();
4463      $post->discussion    = 0;
4464      $post->parent        = 0;
4465      $post->userid        = $userid;
4466      $post->created       = $timenow;
4467      $post->modified      = $timenow;
4468      $post->mailed        = FORUM_MAILED_PENDING;
4469      $post->subject       = $discussion->name;
4470      $post->message       = $discussion->message;
4471      $post->messageformat = $discussion->messageformat;
4472      $post->messagetrust  = $discussion->messagetrust;
4473      $post->attachments   = isset($discussion->attachments) ? $discussion->attachments : null;
4474      $post->forum         = $forum->id;     // speedup
4475      $post->course        = $forum->course; // speedup
4476      $post->mailnow       = $discussion->mailnow;
4477  
4478      $post->id = $DB->insert_record("forum_posts", $post);
4479  
4480      // TODO: Fix the calling code so that there always is a $cm when this function is called
4481      if (!empty($cm->id) && !empty($discussion->itemid)) {   // In "single simple discussions" this may not exist yet
4482          $context = context_module::instance($cm->id);
4483          $text = file_save_draft_area_files($discussion->itemid, $context->id, 'mod_forum', 'post', $post->id,
4484                  mod_forum_post_form::editor_options($context, null), $post->message);
4485          $DB->set_field('forum_posts', 'message', $text, array('id'=>$post->id));
4486      }
4487  
4488      // Now do the main entry for the discussion, linking to this first post
4489  
4490      $discussion->firstpost    = $post->id;
4491      $discussion->timemodified = $timenow;
4492      $discussion->usermodified = $post->userid;
4493      $discussion->userid       = $userid;
4494      $discussion->assessed     = 0;
4495  
4496      $post->discussion = $DB->insert_record("forum_discussions", $discussion);
4497  
4498      // Finally, set the pointer on the post.
4499      $DB->set_field("forum_posts", "discussion", $post->discussion, array("id"=>$post->id));
4500  
4501      if (!empty($cm->id)) {
4502          forum_add_attachment($post, $forum, $cm, $mform, $unused);
4503      }
4504  
4505      if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4506          forum_tp_mark_post_read($post->userid, $post, $post->forum);
4507      }
4508  
4509      // Let Moodle know that assessable content is uploaded (eg for plagiarism detection)
4510      if (!empty($cm->id)) {
4511          forum_trigger_content_uploaded_event($post, $cm, 'forum_add_discussion');
4512      }
4513  
4514      return $post->discussion;
4515  }
4516  
4517  
4518  /**
4519   * Deletes a discussion and handles all associated cleanup.
4520   *
4521   * @global object
4522   * @param object $discussion Discussion to delete
4523   * @param bool $fulldelete True when deleting entire forum
4524   * @param object $course Course
4525   * @param object $cm Course-module
4526   * @param object $forum Forum
4527   * @return bool
4528   */
4529  function forum_delete_discussion($discussion, $fulldelete, $course, $cm, $forum) {
4530      global $DB, $CFG;
4531      require_once($CFG->libdir.'/completionlib.php');
4532  
4533      $result = true;
4534  
4535      if ($posts = $DB->get_records("forum_posts", array("discussion" => $discussion->id))) {
4536          foreach ($posts as $post) {
4537              $post->course = $discussion->course;
4538              $post->forum  = $discussion->forum;
4539              if (!forum_delete_post($post, 'ignore', $course, $cm, $forum, $fulldelete)) {
4540                  $result = false;
4541              }
4542          }
4543      }
4544  
4545      forum_tp_delete_read_records(-1, -1, $discussion->id);
4546  
4547      // Discussion subscriptions must be removed before discussions because of key constraints.
4548      $DB->delete_records('forum_discussion_subs', array('discussion' => $discussion->id));
4549      if (!$DB->delete_records("forum_discussions", array("id" => $discussion->id))) {
4550          $result = false;
4551      }
4552  
4553      // Update completion state if we are tracking completion based on number of posts
4554      // But don't bother when deleting whole thing
4555      if (!$fulldelete) {
4556          $completion = new completion_info($course);
4557          if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC &&
4558             ($forum->completiondiscussions || $forum->completionreplies || $forum->completionposts)) {
4559              $completion->update_state($cm, COMPLETION_INCOMPLETE, $discussion->userid);
4560          }
4561      }
4562  
4563      return $result;
4564  }
4565  
4566  
4567  /**
4568   * Deletes a single forum post.
4569   *
4570   * @global object
4571   * @param object $post Forum post object
4572   * @param mixed $children Whether to delete children. If false, returns false
4573   *   if there are any children (without deleting the post). If true,
4574   *   recursively deletes all children. If set to special value 'ignore', deletes
4575   *   post regardless of children (this is for use only when deleting all posts
4576   *   in a disussion).
4577   * @param object $course Course
4578   * @param object $cm Course-module
4579   * @param object $forum Forum
4580   * @param bool $skipcompletion True to skip updating completion state if it
4581   *   would otherwise be updated, i.e. when deleting entire forum anyway.
4582   * @return bool
4583   */
4584  function forum_delete_post($post, $children, $course, $cm, $forum, $skipcompletion=false) {
4585      global $DB, $CFG;
4586      require_once($CFG->libdir.'/completionlib.php');
4587  
4588      $context = context_module::instance($cm->id);
4589  
4590      if ($children !== 'ignore' && ($childposts = $DB->get_records('forum_posts', array('parent'=>$post->id)))) {
4591         if ($children) {
4592             foreach ($childposts as $childpost) {
4593                 forum_delete_post($childpost, true, $course, $cm, $forum, $skipcompletion);
4594             }
4595         } else {
4596             return false;
4597         }
4598      }
4599  
4600      // Delete ratings.
4601      require_once($CFG->dirroot.'/rating/lib.php');
4602      $delopt = new stdClass;
4603      $delopt->contextid = $context->id;
4604      $delopt->component = 'mod_forum';
4605      $delopt->ratingarea = 'post';
4606      $delopt->itemid = $post->id;
4607      $rm = new rating_manager();
4608      $rm->delete_ratings($delopt);
4609  
4610      // Delete attachments.
4611      $fs = get_file_storage();
4612      $fs->delete_area_files($context->id, 'mod_forum', 'attachment', $post->id);
4613      $fs->delete_area_files($context->id, 'mod_forum', 'post', $post->id);
4614  
4615      // Delete cached RSS feeds.
4616      if (!empty($CFG->enablerssfeeds)) {
4617          require_once($CFG->dirroot.'/mod/forum/rsslib.php');
4618          forum_rss_delete_file($forum);
4619      }
4620  
4621      if ($DB->delete_records("forum_posts", array("id" => $post->id))) {
4622  
4623          forum_tp_delete_read_records(-1, $post->id);
4624  
4625      // Just in case we are deleting the last post
4626          forum_discussion_update_last_post($post->discussion);
4627  
4628          // Update completion state if we are tracking completion based on number of posts
4629          // But don't bother when deleting whole thing
4630  
4631          if (!$skipcompletion) {
4632              $completion = new completion_info($course);
4633              if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC &&
4634                 ($forum->completiondiscussions || $forum->completionreplies || $forum->completionposts)) {
4635                  $completion->update_state($cm, COMPLETION_INCOMPLETE, $post->userid);
4636              }
4637          }
4638  
4639          return true;
4640      }
4641      return false;
4642  }
4643  
4644  /**
4645   * Sends post content to plagiarism plugin
4646   * @param object $post Forum post object
4647   * @param object $cm Course-module
4648   * @param string $name
4649   * @return bool
4650  */
4651  function forum_trigger_content_uploaded_event($post, $cm, $name) {
4652      $context = context_module::instance($cm->id);
4653      $fs = get_file_storage();
4654      $files = $fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id, "timemodified", false);
4655      $params = array(
4656          'context' => $context,
4657          'objectid' => $post->id,
4658          'other' => array(
4659              'content' => $post->message,
4660              'pathnamehashes' => array_keys($files),
4661              'discussionid' => $post->discussion,
4662              'triggeredfrom' => $name,
4663          )
4664      );
4665      $event = \mod_forum\event\assessable_uploaded::create($params);
4666      $event->trigger();
4667      return true;
4668  }
4669  
4670  /**
4671   * @global object
4672   * @param object $post
4673   * @param bool $children
4674   * @return int
4675   */
4676  function forum_count_replies($post, $children=true) {
4677      global $DB;
4678      $count = 0;
4679  
4680      if ($children) {
4681          if ($childposts = $DB->get_records('forum_posts', array('parent' => $post->id))) {
4682             foreach ($childposts as $childpost) {
4683                 $count ++;                   // For this child
4684                 $count += forum_count_replies($childpost, true);
4685             }
4686          }
4687      } else {
4688          $count += $DB->count_records('forum_posts', array('parent' => $post->id));
4689      }
4690  
4691      return $count;
4692  }
4693  
4694  /**
4695   * Given a new post, subscribes or unsubscribes as appropriate.
4696   * Returns some text which describes what happened.
4697   *
4698   * @param object $fromform The submitted form
4699   * @param stdClass $forum The forum record
4700   * @param stdClass $discussion The forum discussion record
4701   * @return string
4702   */
4703  function forum_post_subscription($fromform, $forum, $discussion) {
4704      global $USER;
4705  
4706      if (\mod_forum\subscriptions::is_forcesubscribed($forum)) {
4707          return "";
4708      } else if (\mod_forum\subscriptions::subscription_disabled($forum)) {
4709          $subscribed = \mod_forum\subscriptions::is_subscribed($USER->id, $forum);
4710          if ($subscribed && !has_capability('moodle/course:manageactivities', context_course::instance($forum->course), $USER->id)) {
4711              // This user should not be subscribed to the forum.
4712              \mod_forum\subscriptions::unsubscribe_user($USER->id, $forum);
4713          }
4714          return "";
4715      }
4716  
4717      $info = new stdClass();
4718      $info->name  = fullname($USER);
4719      $info->discussion = format_string($discussion->name);
4720      $info->forum = format_string($forum->name);
4721  
4722      if ($fromform->discussionsubscribe) {
4723          if ($result = \mod_forum\subscriptions::subscribe_user_to_discussion($USER->id, $discussion)) {
4724              return html_writer::tag('p', get_string('discussionnowsubscribed', 'forum', $info));
4725          }
4726      } else {
4727          if ($result = \mod_forum\subscriptions::unsubscribe_user_from_discussion($USER->id, $discussion)) {
4728              return html_writer::tag('p', get_string('discussionnownotsubscribed', 'forum', $info));
4729          }
4730      }
4731  
4732      return '';
4733  }
4734  
4735  /**
4736   * Generate and return the subscribe or unsubscribe link for a forum.
4737   *
4738   * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
4739   * @param object $context the context object for this forum.
4740   * @param array $messages text used for the link in its various states
4741   *      (subscribed, unsubscribed, forcesubscribed or cantsubscribe).
4742   *      Any strings not passed in are taken from the $defaultmessages array
4743   *      at the top of the function.
4744   * @param bool $cantaccessagroup
4745   * @param bool $fakelink
4746   * @param bool $backtoindex
4747   * @param array $subscribed_forums
4748   * @return string
4749   */
4750  function forum_get_subscribe_link($forum, $context, $messages = array(), $cantaccessagroup = false, $fakelink=true, $backtoindex=false, $subscribed_forums=null) {
4751      global $CFG, $USER, $PAGE, $OUTPUT;
4752      $defaultmessages = array(
4753          'subscribed' => get_string('unsubscribe', 'forum'),
4754          'unsubscribed' => get_string('subscribe', 'forum'),
4755          'cantaccessgroup' => get_string('no'),
4756          'forcesubscribed' => get_string('everyoneissubscribed', 'forum'),
4757          'cantsubscribe' => get_string('disallowsubscribe','forum')
4758      );
4759      $messages = $messages + $defaultmessages;
4760  
4761      if (\mod_forum\subscriptions::is_forcesubscribed($forum)) {
4762          return $messages['forcesubscribed'];
4763      } else if (\mod_forum\subscriptions::subscription_disabled($forum) &&
4764              !has_capability('mod/forum:managesubscriptions', $context)) {
4765          return $messages['cantsubscribe'];
4766      } else if ($cantaccessagroup) {
4767          return $messages['cantaccessgroup'];
4768      } else {
4769          if (!is_enrolled($context, $USER, '', true)) {
4770              return '';
4771          }
4772  
4773          $subscribed = \mod_forum\subscriptions::is_subscribed($USER->id, $forum);
4774          if ($subscribed) {
4775              $linktext = $messages['subscribed'];
4776              $linktitle = get_string('subscribestop', 'forum');
4777          } else {
4778              $linktext = $messages['unsubscribed'];
4779              $linktitle = get_string('subscribestart', 'forum');
4780          }
4781  
4782          $options = array();
4783          if ($backtoindex) {
4784              $backtoindexlink = '&amp;backtoindex=1';
4785              $options['backtoindex'] = 1;
4786          } else {
4787              $backtoindexlink = '';
4788          }
4789          $link = '';
4790  
4791          if ($fakelink) {
4792              $PAGE->requires->js('/mod/forum/forum.js');
4793              $PAGE->requires->js_function_call('forum_produce_subscribe_link', array($forum->id, $backtoindexlink, $linktext, $linktitle));
4794              $link = "<noscript>";
4795          }
4796          $options['id'] = $forum->id;
4797          $options['sesskey'] = sesskey();
4798          $url = new moodle_url('/mod/forum/subscribe.php', $options);
4799          $link .= $OUTPUT->single_button($url, $linktext, 'get', array('title'=>$linktitle));
4800          if ($fakelink) {
4801              $link .= '</noscript>';
4802          }
4803  
4804          return $link;
4805      }
4806  }
4807  
4808  /**
4809   * Returns true if user created new discussion already
4810   *
4811   * @global object
4812   * @global object
4813   * @param int $forumid
4814   * @param int $userid
4815   * @return bool
4816   */
4817  function forum_user_has_posted_discussion($forumid, $userid) {
4818      global $CFG, $DB;
4819  
4820      $sql = "SELECT 'x'
4821                FROM {forum_discussions} d, {forum_posts} p
4822               WHERE d.forum = ? AND p.discussion = d.id AND p.parent = 0 and p.userid = ?";
4823  
4824      return $DB->record_exists_sql($sql, array($forumid, $userid));
4825  }
4826  
4827  /**
4828   * @global object
4829   * @global object
4830   * @param int $forumid
4831   * @param int $userid
4832   * @return array
4833   */
4834  function forum_discussions_user_has_posted_in($forumid, $userid) {
4835      global $CFG, $DB;
4836  
4837      $haspostedsql = "SELECT d.id AS id,
4838                              d.*
4839                         FROM {forum_posts} p,
4840                              {forum_discussions} d
4841                        WHERE p.discussion = d.id
4842                          AND d.forum = ?
4843                          AND p.userid = ?";
4844  
4845      return $DB->get_records_sql($haspostedsql, array($forumid, $userid));
4846  }
4847  
4848  /**
4849   * @global object
4850   * @global object
4851   * @param int $forumid
4852   * @param int $did
4853   * @param int $userid
4854   * @return bool
4855   */
4856  function forum_user_has_posted($forumid, $did, $userid) {
4857      global $DB;
4858  
4859      if (empty($did)) {
4860          // posted in any forum discussion?
4861          $sql = "SELECT 'x'
4862                    FROM {forum_posts} p
4863                    JOIN {forum_discussions} d ON d.id = p.discussion
4864                   WHERE p.userid = :userid AND d.forum = :forumid";
4865          return $DB->record_exists_sql($sql, array('forumid'=>$forumid,'userid'=>$userid));
4866      } else {
4867          return $DB->record_exists('forum_posts', array('discussion'=>$did,'userid'=>$userid));
4868      }
4869  }
4870  
4871  /**
4872   * Returns creation time of the first user's post in given discussion
4873   * @global object $DB
4874   * @param int $did Discussion id
4875   * @param int $userid User id
4876   * @return int|bool post creation time stamp or return false
4877   */
4878  function forum_get_user_posted_time($did, $userid) {
4879      global $DB;
4880  
4881      $posttime = $DB->get_field('forum_posts', 'MIN(created)', array('userid'=>$userid, 'discussion'=>$did));
4882      if (empty($posttime)) {
4883          return false;
4884      }
4885      return $posttime;
4886  }
4887  
4888  /**
4889   * @global object
4890   * @param object $forum
4891   * @param object $currentgroup
4892   * @param int $unused
4893   * @param object $cm
4894   * @param object $context
4895   * @return bool
4896   */
4897  function forum_user_can_post_discussion($forum, $currentgroup=null, $unused=-1, $cm=NULL, $context=NULL) {
4898  // $forum is an object
4899      global $USER;
4900  
4901      // shortcut - guest and not-logged-in users can not post
4902      if (isguestuser() or !isloggedin()) {
4903          return false;
4904      }
4905  
4906      if (!$cm) {
4907          debugging('missing cm', DEBUG_DEVELOPER);
4908          if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
4909              print_error('invalidcoursemodule');
4910          }
4911      }
4912  
4913      if (!$context) {
4914          $context = context_module::instance($cm->id);
4915      }
4916  
4917      if ($currentgroup === null) {
4918          $currentgroup = groups_get_activity_group($cm);
4919      }
4920  
4921      $groupmode = groups_get_activity_groupmode($cm);
4922  
4923      if ($forum->type == 'news') {
4924          $capname = 'mod/forum:addnews';
4925      } else if ($forum->type == 'qanda') {
4926          $capname = 'mod/forum:addquestion';
4927      } else {
4928          $capname = 'mod/forum:startdiscussion';
4929      }
4930  
4931      if (!has_capability($capname, $context)) {
4932          return false;
4933      }
4934  
4935      if ($forum->type == 'single') {
4936          return false;
4937      }
4938  
4939      if ($forum->type == 'eachuser') {
4940          if (forum_user_has_posted_discussion($forum->id, $USER->id)) {
4941              return false;
4942          }
4943      }
4944  
4945      if (!$groupmode or has_capability('moodle/site:accessallgroups', $context)) {
4946          return true;
4947      }
4948  
4949      if ($currentgroup) {
4950          return groups_is_member($currentgroup);
4951      } else {
4952          // no group membership and no accessallgroups means no new discussions
4953          // reverted to 1.7 behaviour in 1.9+,  buggy in 1.8.0-1.9.0
4954          return false;
4955      }
4956  }
4957  
4958  /**
4959   * This function checks whether the user can reply to posts in a forum
4960   * discussion. Use forum_user_can_post_discussion() to check whether the user
4961   * can start discussions.
4962   *
4963   * @global object
4964   * @global object
4965   * @uses DEBUG_DEVELOPER
4966   * @uses CONTEXT_MODULE
4967   * @uses VISIBLEGROUPS
4968   * @param object $forum forum object
4969   * @param object $discussion
4970   * @param object $user
4971   * @param object $cm
4972   * @param object $course
4973   * @param object $context
4974   * @return bool
4975   */
4976  function forum_user_can_post($forum, $discussion, $user=NULL, $cm=NULL, $course=NULL, $context=NULL) {
4977      global $USER, $DB;
4978      if (empty($user)) {
4979          $user = $USER;
4980      }
4981  
4982      // shortcut - guest and not-logged-in users can not post
4983      if (isguestuser($user) or empty($user->id)) {
4984          return false;
4985      }
4986  
4987      if (!isset($discussion->groupid)) {
4988          debugging('incorrect discussion parameter', DEBUG_DEVELOPER);
4989          return false;
4990      }
4991  
4992      if (!$cm) {
4993          debugging('missing cm', DEBUG_DEVELOPER);
4994          if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
4995              print_error('invalidcoursemodule');
4996          }
4997      }
4998  
4999      if (!$course) {
5000          debugging('missing course', DEBUG_DEVELOPER);
5001          if (!$course = $DB->get_record('course', array('id' => $forum->course))) {
5002              print_error('invalidcourseid');
5003          }
5004      }
5005  
5006      if (!$context) {
5007          $context = context_module::instance($cm->id);
5008      }
5009  
5010      // normal users with temporary guest access can not post, suspended users can not post either
5011      if (!is_viewing($context, $user->id) and !is_enrolled($context, $user->id, '', true)) {
5012          return false;
5013      }
5014  
5015      if ($forum->type == 'news') {
5016          $capname = 'mod/forum:replynews';
5017      } else {
5018          $capname = 'mod/forum:replypost';
5019      }
5020  
5021      if (!has_capability($capname, $context, $user->id)) {
5022          return false;
5023      }
5024  
5025      if (!$groupmode = groups_get_activity_groupmode($cm, $course)) {
5026          return true;
5027      }
5028  
5029      if (has_capability('moodle/site:accessallgroups', $context)) {
5030          return true;
5031      }
5032  
5033      if ($groupmode == VISIBLEGROUPS) {
5034          if ($discussion->groupid == -1) {
5035              // allow students to reply to all participants discussions - this was not possible in Moodle <1.8
5036              return true;
5037          }
5038          return groups_is_member($discussion->groupid);
5039  
5040      } else {
5041          //separate groups
5042          if ($discussion->groupid == -1) {
5043              return false;
5044          }
5045          return groups_is_member($discussion->groupid);
5046      }
5047  }
5048  
5049  /**
5050  * Check to ensure a user can view a timed discussion.
5051  *
5052  * @param object $discussion
5053  * @param object $user
5054  * @param object $context
5055  * @return boolean returns true if they can view post, false otherwise
5056  */
5057  function forum_user_can_see_timed_discussion($discussion, $user, $context) {
5058      global $CFG;
5059  
5060      // Check that the user can view a discussion that is normally hidden due to access times.
5061      if (!empty($CFG->forum_enabletimedposts)) {
5062          $time = time();
5063          if (($discussion->timestart != 0 && $discussion->timestart > $time)
5064              || ($discussion->timeend != 0 && $discussion->timeend < $time)) {
5065              if (!has_capability('mod/forum:viewhiddentimedposts', $context, $user->id)) {
5066                  return false;
5067              }
5068          }
5069      }
5070  
5071      return true;
5072  }
5073  
5074  /**
5075  * Check to ensure a user can view a group discussion.
5076  *
5077  * @param object $discussion
5078  * @param object $cm
5079  * @param object $context
5080  * @return boolean returns true if they can view post, false otherwise
5081  */
5082  function forum_user_can_see_group_discussion($discussion, $cm, $context) {
5083  
5084      // If it's a grouped discussion, make sure the user is a member.
5085      if ($discussion->groupid > 0) {
5086          $groupmode = groups_get_activity_groupmode($cm);
5087          if ($groupmode == SEPARATEGROUPS) {
5088              return groups_is_member($discussion->groupid) || has_capability('moodle/site:accessallgroups', $context);
5089          }
5090      }
5091  
5092      return true;
5093  }
5094  
5095  /**
5096   * @global object
5097   * @global object
5098   * @uses DEBUG_DEVELOPER
5099   * @param object $forum
5100   * @param object $discussion
5101   * @param object $context
5102   * @param object $user
5103   * @return bool
5104   */
5105  function forum_user_can_see_discussion($forum, $discussion, $context, $user=NULL) {
5106      global $USER, $DB;
5107  
5108      if (empty($user) || empty($user->id)) {
5109          $user = $USER;
5110      }
5111  
5112      // retrieve objects (yuk)
5113      if (is_numeric($forum)) {
5114          debugging('missing full forum', DEBUG_DEVELOPER);
5115          if (!$forum = $DB->get_record('forum',array('id'=>$forum))) {
5116              return false;
5117          }
5118      }
5119      if (is_numeric($discussion)) {
5120          debugging('missing full discussion', DEBUG_DEVELOPER);
5121          if (!$discussion = $DB->get_record('forum_discussions',array('id'=>$discussion))) {
5122              return false;
5123          }
5124      }
5125      if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5126          print_error('invalidcoursemodule');
5127      }
5128  
5129      if (!has_capability('mod/forum:viewdiscussion', $context)) {
5130          return false;
5131      }
5132  
5133      if (!forum_user_can_see_timed_discussion($discussion, $user, $context)) {
5134          return false;
5135      }
5136  
5137      if (!forum_user_can_see_group_discussion($discussion, $cm, $context)) {
5138          return false;
5139      }
5140  
5141      if ($forum->type == 'qanda' &&
5142              !forum_user_has_posted($forum->id, $discussion->id, $user->id) &&
5143              !has_capability('mod/forum:viewqandawithoutposting', $context)) {
5144          return false;
5145      }
5146      return true;
5147  }
5148  
5149  /**
5150   * @global object
5151   * @global object
5152   * @param object $forum
5153   * @param object $discussion
5154   * @param object $post
5155   * @param object $user
5156   * @param object $cm
5157   * @return bool
5158   */
5159  function forum_user_can_see_post($forum, $discussion, $post, $user=NULL, $cm=NULL) {
5160      global $CFG, $USER, $DB;
5161  
5162      // Context used throughout function.
5163      $modcontext = context_module::instance($cm->id);
5164  
5165      // retrieve objects (yuk)
5166      if (is_numeric($forum)) {
5167          debugging('missing full forum', DEBUG_DEVELOPER);
5168          if (!$forum = $DB->get_record('forum',array('id'=>$forum))) {
5169              return false;
5170          }
5171      }
5172  
5173      if (is_numeric($discussion)) {
5174          debugging('missing full discussion', DEBUG_DEVELOPER);
5175          if (!$discussion = $DB->get_record('forum_discussions',array('id'=>$discussion))) {
5176              return false;
5177          }
5178      }
5179      if (is_numeric($post)) {
5180          debugging('missing full post', DEBUG_DEVELOPER);
5181          if (!$post = $DB->get_record('forum_posts',array('id'=>$post))) {
5182              return false;
5183          }
5184      }
5185  
5186      if (!isset($post->id) && isset($post->parent)) {
5187          $post->id = $post->parent;
5188      }
5189  
5190      if (!$cm) {
5191          debugging('missing cm', DEBUG_DEVELOPER);
5192          if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5193              print_error('invalidcoursemodule');
5194          }
5195      }
5196  
5197      if (empty($user) || empty($user->id)) {
5198          $user = $USER;
5199      }
5200  
5201      $canviewdiscussion = !empty($cm->cache->caps['mod/forum:viewdiscussion']) || has_capability('mod/forum:viewdiscussion', $modcontext, $user->id);
5202      if (!$canviewdiscussion && !has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), context_user::instance($post->userid))) {
5203          return false;
5204      }
5205  
5206      if (isset($cm->uservisible)) {
5207          if (!$cm->uservisible) {
5208              return false;
5209          }
5210      } else {
5211          if (!\core_availability\info_module::is_user_visible($cm, $user->id, false)) {
5212              return false;
5213          }
5214      }
5215  
5216      if (!forum_user_can_see_timed_discussion($discussion, $user, $modcontext)) {
5217          return false;
5218      }
5219  
5220      if (!forum_user_can_see_group_discussion($discussion, $cm, $modcontext)) {
5221          return false;
5222      }
5223  
5224      if ($forum->type == 'qanda') {
5225          $firstpost = forum_get_firstpost_from_discussion($discussion->id);
5226          $userfirstpost = forum_get_user_posted_time($discussion->id, $user->id);
5227  
5228          return (($userfirstpost !== false && (time() - $userfirstpost >= $CFG->maxeditingtime)) ||
5229                  $firstpost->id == $post->id || $post->userid == $user->id || $firstpost->userid == $user->id ||
5230                  has_capability('mod/forum:viewqandawithoutposting', $modcontext, $user->id));
5231      }
5232      return true;
5233  }
5234  
5235  
5236  /**
5237   * Prints the discussion view screen for a forum.
5238   *
5239   * @global object
5240   * @global object
5241   * @param object $course The current course object.
5242   * @param object $forum Forum to be printed.
5243   * @param int $maxdiscussions .
5244   * @param string $displayformat The display format to use (optional).
5245   * @param string $sort Sort arguments for database query (optional).
5246   * @param int $groupmode Group mode of the forum (optional).
5247   * @param void $unused (originally current group)
5248   * @param int $page Page mode, page to display (optional).
5249   * @param int $perpage The maximum number of discussions per page(optional)
5250   * @param boolean $subscriptionstatus Whether the user is currently subscribed to the discussion in some fashion.
5251   *
5252   */
5253  function forum_print_latest_discussions($course, $forum, $maxdiscussions = -1, $displayformat = 'plain', $sort = '',
5254                                          $currentgroup = -1, $groupmode = -1, $page = -1, $perpage = 100, $cm = null) {
5255      global $CFG, $USER, $OUTPUT;
5256  
5257      if (!$cm) {
5258          if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5259              print_error('invalidcoursemodule');
5260          }
5261      }
5262      $context = context_module::instance($cm->id);
5263  
5264      if (empty($sort)) {
5265          $sort = "d.timemodified DESC";
5266      }
5267  
5268      $olddiscussionlink = false;
5269  
5270   // Sort out some defaults
5271      if ($perpage <= 0) {
5272          $perpage = 0;
5273          $page    = -1;
5274      }
5275  
5276      if ($maxdiscussions == 0) {
5277          // all discussions - backwards compatibility
5278          $page    = -1;
5279          $perpage = 0;
5280          if ($displayformat == 'plain') {
5281              $displayformat = 'header';  // Abbreviate display by default
5282          }
5283  
5284      } else if ($maxdiscussions > 0) {
5285          $page    = -1;
5286          $perpage = $maxdiscussions;
5287      }
5288  
5289      $fullpost = false;
5290      if ($displayformat == 'plain') {
5291          $fullpost = true;
5292      }
5293  
5294  
5295  // Decide if current user is allowed to see ALL the current discussions or not
5296  
5297  // First check the group stuff
5298      if ($currentgroup == -1 or $groupmode == -1) {
5299          $groupmode    = groups_get_activity_groupmode($cm, $course);
5300          $currentgroup = groups_get_activity_group($cm);
5301      }
5302  
5303      $groups = array(); //cache
5304  
5305  // If the user can post discussions, then this is a good place to put the
5306  // button for it. We do not show the button if we are showing site news
5307  // and the current user is a guest.
5308  
5309      $canstart = forum_user_can_post_discussion($forum, $currentgroup, $groupmode, $cm, $context);
5310      if (!$canstart and $forum->type !== 'news') {
5311          if (isguestuser() or !isloggedin()) {
5312              $canstart = true;
5313          }
5314          if (!is_enrolled($context) and !is_viewing($context)) {
5315              // allow guests and not-logged-in to see the button - they are prompted to log in after clicking the link
5316              // normal users with temporary guest access see this button too, they are asked to enrol instead
5317              // do not show the button to users with suspended enrolments here
5318              $canstart = enrol_selfenrol_available($course->id);
5319          }
5320      }
5321  
5322      if ($canstart) {
5323          echo '<div class="singlebutton forumaddnew">';
5324          echo "<form id=\"newdiscussionform\" method=\"get\" action=\"$CFG->wwwroot/mod/forum/post.php\">";
5325          echo '<div>';
5326          echo "<input type=\"hidden\" name=\"forum\" value=\"$forum->id\" />";
5327          switch ($forum->type) {
5328              case 'news':
5329              case 'blog':
5330                  $buttonadd = get_string('addanewtopic', 'forum');
5331                  break;
5332              case 'qanda':
5333                  $buttonadd = get_string('addanewquestion', 'forum');
5334                  break;
5335              default:
5336                  $buttonadd = get_string('addanewdiscussion', 'forum');
5337                  break;
5338          }
5339          echo '<input type="submit" value="'.$buttonadd.'" />';
5340          echo '</div>';
5341          echo '</form>';
5342          echo "</div>\n";
5343  
5344      } else if (isguestuser() or !isloggedin() or $forum->type == 'news' or
5345          $forum->type == 'qanda' and !has_capability('mod/forum:addquestion', $context) or
5346          $forum->type != 'qanda' and !has_capability('mod/forum:startdiscussion', $context)) {
5347          // no button and no info
5348  
5349      } else if ($groupmode and !has_capability('moodle/site:accessallgroups', $context)) {
5350          // inform users why they can not post new discussion
5351          if (!$currentgroup) {
5352              echo $OUTPUT->notification(get_string('cannotadddiscussionall', 'forum'));
5353          } else if (!groups_is_member($currentgroup)) {
5354              echo $OUTPUT->notification(get_string('cannotadddiscussion', 'forum'));
5355          }
5356      }
5357  
5358  // Get all the recent discussions we're allowed to see
5359  
5360      $getuserlastmodified = ($displayformat == 'header');
5361  
5362      if (! $discussions = forum_get_discussions($cm, $sort, $fullpost, null, $maxdiscussions, $getuserlastmodified, $page, $perpage) ) {
5363          echo '<div class="forumnodiscuss">';
5364          if ($forum->type == 'news') {
5365              echo '('.get_string('nonews', 'forum').')';
5366          } else if ($forum->type == 'qanda') {
5367              echo '('.get_string('noquestions','forum').')';
5368          } else {
5369              echo '('.get_string('nodiscussions', 'forum').')';
5370          }
5371          echo "</div>\n";
5372          return;
5373      }
5374  
5375  // If we want paging
5376      if ($page != -1) {
5377          ///Get the number of discussions found
5378          $numdiscussions = forum_get_discussions_count($cm);
5379  
5380          ///Show the paging bar
5381          echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
5382          if ($numdiscussions > 1000) {
5383              // saves some memory on sites with very large forums
5384              $replies = forum_count_discussion_replies($forum->id, $sort, $maxdiscussions, $page, $perpage);
5385          } else {
5386              $replies = forum_count_discussion_replies($forum->id);
5387          }
5388  
5389      } else {
5390          $replies = forum_count_discussion_replies($forum->id);
5391  
5392          if ($maxdiscussions > 0 and $maxdiscussions <= count($discussions)) {
5393              $olddiscussionlink = true;
5394          }
5395      }
5396  
5397      $canviewparticipants = has_capability('moodle/course:viewparticipants',$context);
5398  
5399      $strdatestring = get_string('strftimerecentfull');
5400  
5401      // Check if the forum is tracked.
5402      if ($cantrack = forum_tp_can_track_forums($forum)) {
5403          $forumtracked = forum_tp_is_tracked($forum);
5404      } else {
5405          $forumtracked = false;
5406      }
5407  
5408      if ($forumtracked) {
5409          $unreads = forum_get_discussions_unread($cm);
5410      } else {
5411          $unreads = array();
5412      }
5413  
5414      if ($displayformat == 'header') {
5415          echo '<table cellspacing="0" class="forumheaderlist">';
5416          echo '<thead>';
5417          echo '<tr>';
5418          echo '<th class="header topic" scope="col">'.get_string('discussion', 'forum').'</th>';
5419          echo '<th class="header author" colspan="2" scope="col">'.get_string('startedby', 'forum').'</th>';
5420          if ($groupmode > 0) {
5421              echo '<th class="header group" scope="col">'.get_string('group').'</th>';
5422          }
5423          if (has_capability('mod/forum:viewdiscussion', $context)) {
5424              echo '<th class="header replies" scope="col">'.get_string('replies', 'forum').'</th>';
5425              // If the forum can be tracked, display the unread column.
5426              if ($cantrack) {
5427                  echo '<th class="header replies" scope="col">'.get_string('unread', 'forum');
5428                  if ($forumtracked) {
5429                      echo '<a title="'.get_string('markallread', 'forum').
5430                           '" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
5431                           $forum->id.'&amp;mark=read&amp;returnpage=view.php">'.
5432                           '<img src="'.$OUTPUT->pix_url('t/markasread') . '" class="iconsmall" alt="'.get_string('markallread', 'forum').'" /></a>';
5433                  }
5434                  echo '</th>';
5435              }
5436          }
5437          echo '<th class="header lastpost" scope="col">'.get_string('lastpost', 'forum').'</th>';
5438          if ((!is_guest($context, $USER) && isloggedin()) && has_capability('mod/forum:viewdiscussion', $context)) {
5439              if (\mod_forum\subscriptions::is_subscribable($forum)) {
5440                  echo '<th class="header discussionsubscription" scope="col">';
5441                  echo forum_get_discussion_subscription_icon_preloaders();
5442                  echo '</th>';
5443              }
5444          }
5445          echo '</tr>';
5446          echo '</thead>';
5447          echo '<tbody>';
5448      }
5449  
5450      foreach ($discussions as $discussion) {
5451          if ($forum->type == 'qanda' && !has_capability('mod/forum:viewqandawithoutposting', $context) &&
5452              !forum_user_has_posted($forum->id, $discussion->discussion, $USER->id)) {
5453              $canviewparticipants = false;
5454          }
5455  
5456          if (!empty($replies[$discussion->discussion])) {
5457              $discussion->replies = $replies[$discussion->discussion]->replies;
5458              $discussion->lastpostid = $replies[$discussion->discussion]->lastpostid;
5459          } else {
5460              $discussion->replies = 0;
5461          }
5462  
5463          // SPECIAL CASE: The front page can display a news item post to non-logged in users.
5464          // All posts are read in this case.
5465          if (!$forumtracked) {
5466              $discussion->unread = '-';
5467          } else if (empty($USER)) {
5468              $discussion->unread = 0;
5469          } else {
5470              if (empty($unreads[$discussion->discussion])) {
5471                  $discussion->unread = 0;
5472              } else {
5473                  $discussion->unread = $unreads[$discussion->discussion];
5474              }
5475          }
5476  
5477          if (isloggedin()) {
5478              $ownpost = ($discussion->userid == $USER->id);
5479          } else {
5480              $ownpost=false;
5481          }
5482          // Use discussion name instead of subject of first post
5483          $discussion->subject = $discussion->name;
5484  
5485          switch ($displayformat) {
5486              case 'header':
5487                  if ($groupmode > 0) {
5488                      if (isset($groups[$discussion->groupid])) {
5489                          $group = $groups[$discussion->groupid];
5490                      } else {
5491                          $group = $groups[$discussion->groupid] = groups_get_group($discussion->groupid);
5492                      }
5493                  } else {
5494                      $group = -1;
5495                  }
5496                  forum_print_discussion_header($discussion, $forum, $group, $strdatestring, $cantrack, $forumtracked,
5497                      $canviewparticipants, $context);
5498              break;
5499              default:
5500                  $link = false;
5501  
5502                  if ($discussion->replies) {
5503                      $link = true;
5504                  } else {
5505                      $modcontext = context_module::instance($cm->id);
5506                      $link = forum_user_can_see_discussion($forum, $discussion, $modcontext, $USER);
5507                  }
5508  
5509                  $discussion->forum = $forum->id;
5510  
5511                  forum_print_post($discussion, $discussion, $forum, $cm, $course, $ownpost, 0, $link, false,
5512                          '', null, true, $forumtracked);
5513              break;
5514          }
5515      }
5516  
5517      if ($displayformat == "header") {
5518          echo '</tbody>';
5519          echo '</table>';
5520      }
5521  
5522      if ($olddiscussionlink) {
5523          if ($forum->type == 'news') {
5524              $strolder = get_string('oldertopics', 'forum');
5525          } else {
5526              $strolder = get_string('olderdiscussions', 'forum');
5527          }
5528          echo '<div class="forumolddiscuss">';
5529          echo '<a href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'&amp;showall=1">';
5530          echo $strolder.'</a> ...</div>';
5531      }
5532  
5533      if ($page != -1) { ///Show the paging bar
5534          echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
5535      }
5536  }
5537  
5538  
5539  /**
5540   * Prints a forum discussion
5541   *
5542   * @uses CONTEXT_MODULE
5543   * @uses FORUM_MODE_FLATNEWEST
5544   * @uses FORUM_MODE_FLATOLDEST
5545   * @uses FORUM_MODE_THREADED
5546   * @uses FORUM_MODE_NESTED
5547   * @param stdClass $course
5548   * @param stdClass $cm
5549   * @param stdClass $forum
5550   * @param stdClass $discussion
5551   * @param stdClass $post
5552   * @param int $mode
5553   * @param mixed $canreply
5554   * @param bool $canrate
5555   */
5556  function forum_print_discussion($course, $cm, $forum, $discussion, $post, $mode, $canreply=NULL, $canrate=false) {
5557      global $USER, $CFG;
5558  
5559      require_once($CFG->dirroot.'/rating/lib.php');
5560  
5561      $ownpost = (isloggedin() && $USER->id == $post->userid);
5562  
5563      $modcontext = context_module::instance($cm->id);
5564      if ($canreply === NULL) {
5565          $reply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
5566      } else {
5567          $reply = $canreply;
5568      }
5569  
5570      // $cm holds general cache for forum functions
5571      $cm->cache = new stdClass;
5572      $cm->cache->groups      = groups_get_all_groups($course->id, 0, $cm->groupingid);
5573      $cm->cache->usersgroups = array();
5574  
5575      $posters = array();
5576  
5577      // preload all posts - TODO: improve...
5578      if ($mode == FORUM_MODE_FLATNEWEST) {
5579          $sort = "p.created DESC";
5580      } else {
5581          $sort = "p.created ASC";
5582      }
5583  
5584      $forumtracked = forum_tp_is_tracked($forum);
5585      $posts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
5586      $post = $posts[$post->id];
5587  
5588      foreach ($posts as $pid=>$p) {
5589          $posters[$p->userid] = $p->userid;
5590      }
5591  
5592      // preload all groups of ppl that posted in this discussion
5593      if ($postersgroups = groups_get_all_groups($course->id, $posters, $cm->groupingid, 'gm.id, gm.groupid, gm.userid')) {
5594          foreach($postersgroups as $pg) {
5595              if (!isset($cm->cache->usersgroups[$pg->userid])) {
5596                  $cm->cache->usersgroups[$pg->userid] = array();
5597              }
5598              $cm->cache->usersgroups[$pg->userid][$pg->groupid] = $pg->groupid;
5599          }
5600          unset($postersgroups);
5601      }
5602  
5603      //load ratings
5604      if ($forum->assessed != RATING_AGGREGATE_NONE) {
5605          $ratingoptions = new stdClass;
5606          $ratingoptions->context = $modcontext;
5607          $ratingoptions->component = 'mod_forum';
5608          $ratingoptions->ratingarea = 'post';
5609          $ratingoptions->items = $posts;
5610          $ratingoptions->aggregate = $forum->assessed;//the aggregation method
5611          $ratingoptions->scaleid = $forum->scale;
5612          $ratingoptions->userid = $USER->id;
5613          if ($forum->type == 'single' or !$discussion->id) {
5614              $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/view.php?id=$cm->id";
5615          } else {
5616              $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id";
5617          }
5618          $ratingoptions->assesstimestart = $forum->assesstimestart;
5619          $ratingoptions->assesstimefinish = $forum->assesstimefinish;
5620  
5621          $rm = new rating_manager();
5622          $posts = $rm->get_ratings($ratingoptions);
5623      }
5624  
5625  
5626      $post->forum = $forum->id;   // Add the forum id to the post object, later used by forum_print_post
5627      $post->forumtype = $forum->type;
5628  
5629      $post->subject = format_string($post->subject);
5630  
5631      $postread = !empty($post->postread);
5632  
5633      forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, false,
5634                           '', '', $postread, true, $forumtracked);
5635  
5636      switch ($mode) {
5637          case FORUM_MODE_FLATOLDEST :
5638          case FORUM_MODE_FLATNEWEST :
5639          default:
5640              forum_print_posts_flat($course, $cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts);
5641              break;
5642  
5643          case FORUM_MODE_THREADED :
5644              forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, 0, $reply, $forumtracked, $posts);
5645              break;
5646  
5647          case FORUM_MODE_NESTED :
5648              forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
5649              break;
5650      }
5651  }
5652  
5653  
5654  /**
5655   * @global object
5656   * @global object
5657   * @uses FORUM_MODE_FLATNEWEST
5658   * @param object $course
5659   * @param object $cm
5660   * @param object $forum
5661   * @param object $discussion
5662   * @param object $post
5663   * @param object $mode
5664   * @param bool $reply
5665   * @param bool $forumtracked
5666   * @param array $posts
5667   * @return void
5668   */
5669  function forum_print_posts_flat($course, &$cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts) {
5670      global $USER, $CFG;
5671  
5672      $link  = false;
5673  
5674      if ($mode == FORUM_MODE_FLATNEWEST) {
5675          $sort = "ORDER BY created DESC";
5676      } else {
5677          $sort = "ORDER BY created ASC";
5678      }
5679  
5680      foreach ($posts as $post) {
5681          if (!$post->parent) {
5682              continue;
5683          }
5684          $post->subject = format_string($post->subject);
5685          $ownpost = ($USER->id == $post->userid);
5686  
5687          $postread = !empty($post->postread);
5688  
5689          forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5690                               '', '', $postread, true, $forumtracked);
5691      }
5692  }
5693  
5694  /**
5695   * @todo Document this function
5696   *
5697   * @global object
5698   * @global object
5699   * @uses CONTEXT_MODULE
5700   * @return void
5701   */
5702  function forum_print_posts_threaded($course, &$cm, $forum, $discussion, $parent, $depth, $reply, $forumtracked, $posts) {
5703      global $USER, $CFG;
5704  
5705      $link  = false;
5706  
5707      if (!empty($posts[$parent->id]->children)) {
5708          $posts = $posts[$parent->id]->children;
5709  
5710          $modcontext       = context_module::instance($cm->id);
5711          $canviewfullnames = has_capability('moodle/site:viewfullnames', $modcontext);
5712  
5713          foreach ($posts as $post) {
5714  
5715              echo '<div class="indent">';
5716              if ($depth > 0) {
5717                  $ownpost = ($USER->id == $post->userid);
5718                  $post->subject = format_string($post->subject);
5719  
5720                  $postread = !empty($post->postread);
5721  
5722                  forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5723                                       '', '', $postread, true, $forumtracked);
5724              } else {
5725                  if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
5726                      echo "</div>\n";
5727                      continue;
5728                  }
5729                  $by = new stdClass();
5730                  $by->name = fullname($post, $canviewfullnames);
5731                  $by->date = userdate($post->modified);
5732  
5733                  if ($forumtracked) {
5734                      if (!empty($post->postread)) {
5735                          $style = '<span class="forumthread read">';
5736                      } else {
5737                          $style = '<span class="forumthread unread">';
5738                      }
5739                  } else {
5740                      $style = '<span class="forumthread">';
5741                  }
5742                  echo $style."<a name=\"$post->id\"></a>".
5743                       "<a href=\"discuss.php?d=$post->discussion&amp;parent=$post->id\">".format_string($post->subject,true)."</a> ";
5744                  print_string("bynameondate", "forum", $by);
5745                  echo "</span>";
5746              }
5747  
5748              forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, $depth-1, $reply, $forumtracked, $posts);
5749              echo "</div>\n";
5750          }
5751      }
5752  }
5753  
5754  /**
5755   * @todo Document this function
5756   * @global object
5757   * @global object
5758   * @return void
5759   */
5760  function forum_print_posts_nested($course, &$cm, $forum, $discussion, $parent, $reply, $forumtracked, $posts) {
5761      global $USER, $CFG;
5762  
5763      $link  = false;
5764  
5765      if (!empty($posts[$parent->id]->children)) {
5766          $posts = $posts[$parent->id]->children;
5767  
5768          foreach ($posts as $post) {
5769  
5770              echo '<div class="indent">';
5771              if (!isloggedin()) {
5772                  $ownpost = false;
5773              } else {
5774                  $ownpost = ($USER->id == $post->userid);
5775              }
5776  
5777              $post->subject = format_string($post->subject);
5778              $postread = !empty($post->postread);
5779  
5780              forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5781                                   '', '', $postread, true, $forumtracked);
5782              forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
5783              echo "</div>\n";
5784          }
5785      }
5786  }
5787  
5788  /**
5789   * Returns all forum posts since a given time in specified forum.
5790   *
5791   * @todo Document this functions args
5792   * @global object
5793   * @global object
5794   * @global object
5795   * @global object
5796   */
5797  function forum_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0)  {
5798      global $CFG, $COURSE, $USER, $DB;
5799  
5800      if ($COURSE->id == $courseid) {
5801          $course = $COURSE;
5802      } else {
5803          $course = $DB->get_record('course', array('id' => $courseid));
5804      }
5805  
5806      $modinfo = get_fast_modinfo($course);
5807  
5808      $cm = $modinfo->cms[$cmid];
5809      $params = array($timestart, $cm->instance);
5810  
5811      if ($userid) {
5812          $userselect = "AND u.id = ?";
5813          $params[] = $userid;
5814      } else {
5815          $userselect = "";
5816      }
5817  
5818      if ($groupid) {
5819          $groupselect = "AND d.groupid = ?";
5820          $params[] = $groupid;
5821      } else {
5822          $groupselect = "";
5823      }
5824  
5825      $allnames = get_all_user_name_fields(true, 'u');
5826      if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
5827                                                d.timestart, d.timeend, d.userid AS duserid,
5828                                                $allnames, u.email, u.picture, u.imagealt, u.email
5829                                           FROM {forum_posts} p
5830                                                JOIN {forum_discussions} d ON d.id = p.discussion
5831                                                JOIN {forum} f             ON f.id = d.forum
5832                                                JOIN {user} u              ON u.id = p.userid
5833                                          WHERE p.created > ? AND f.id = ?
5834                                                $userselect $groupselect
5835                                       ORDER BY p.id ASC", $params)) { // order by initial posting date
5836           return;
5837      }
5838  
5839      $groupmode       = groups_get_activity_groupmode($cm, $course);
5840      $cm_context      = context_module::instance($cm->id);
5841      $viewhiddentimed = has_capability('mod/forum:viewhiddentimedposts', $cm_context);
5842      $accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context);
5843  
5844      $printposts = array();
5845      foreach ($posts as $post) {
5846  
5847          if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
5848            and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
5849              if (!$viewhiddentimed) {
5850                  continue;
5851              }
5852          }
5853  
5854          if ($groupmode) {
5855              if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or $accessallgroups) {
5856                  // oki (Open discussions have groupid -1)
5857              } else {
5858                  // separate mode
5859                  if (isguestuser()) {
5860                      // shortcut
5861                      continue;
5862                  }
5863  
5864                  if (!in_array($post->groupid, $modinfo->get_groups($cm->groupingid))) {
5865                      continue;
5866                  }
5867              }
5868          }
5869  
5870          $printposts[] = $post;
5871      }
5872  
5873      if (!$printposts) {
5874          return;
5875      }
5876  
5877      $aname = format_string($cm->name,true);
5878  
5879      foreach ($printposts as $post) {
5880          $tmpactivity = new stdClass();
5881  
5882          $tmpactivity->type         = 'forum';
5883          $tmpactivity->cmid         = $cm->id;
5884          $tmpactivity->name         = $aname;
5885          $tmpactivity->sectionnum   = $cm->sectionnum;
5886          $tmpactivity->timestamp    = $post->modified;
5887  
5888          $tmpactivity->content = new stdClass();
5889          $tmpactivity->content->id         = $post->id;
5890          $tmpactivity->content->discussion = $post->discussion;
5891          $tmpactivity->content->subject    = format_string($post->subject);
5892          $tmpactivity->content->parent     = $post->parent;
5893  
5894          $tmpactivity->user = new stdClass();
5895          $additionalfields = array('id' => 'userid', 'picture', 'imagealt', 'email');
5896          $additionalfields = explode(',', user_picture::fields());
5897          $tmpactivity->user = username_load_fields_from_object($tmpactivity->user, $post, null, $additionalfields);
5898          $tmpactivity->user->id = $post->userid;
5899  
5900          $activities[$index++] = $tmpactivity;
5901      }
5902  
5903      return;
5904  }
5905  
5906  /**
5907   * @todo Document this function
5908   * @global object
5909   */
5910  function forum_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
5911      global $CFG, $OUTPUT;
5912  
5913      if ($activity->content->parent) {
5914          $class = 'reply';
5915      } else {
5916          $class = 'discussion';
5917      }
5918  
5919      echo '<table border="0" cellpadding="3" cellspacing="0" class="forum-recent">';
5920  
5921      echo "<tr><td class=\"userpicture\" valign=\"top\">";
5922      echo $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid));
5923      echo "</td><td class=\"$class\">";
5924  
5925      if ($activity->content->parent) {
5926          $class = 'title';
5927      } else {
5928          // Bold the title of new discussions so they stand out.
5929          $class = 'title bold';
5930      }
5931      echo "<div class=\"{$class}\">";
5932      if ($detail) {
5933          $aname = s($activity->name);
5934          echo "<img src=\"" . $OUTPUT->pix_url('icon', $activity->type) . "\" ".
5935               "class=\"icon\" alt=\"{$aname}\" />";
5936      }
5937      echo "<a href=\"$CFG->wwwroot/mod/forum/discuss.php?d={$activity->content->discussion}"
5938           ."#p{$activity->content->id}\">{$activity->content->subject}</a>";
5939      echo '</div>';
5940  
5941      echo '<div class="user">';
5942      $fullname = fullname($activity->user, $viewfullnames);
5943      echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&amp;course=$courseid\">"
5944           ."{$fullname}</a> - ".userdate($activity->timestamp);
5945      echo '</div>';
5946        echo "</td></tr></table>";
5947  
5948      return;
5949  }
5950  
5951  /**
5952   * recursively sets the discussion field to $discussionid on $postid and all its children
5953   * used when pruning a post
5954   *
5955   * @global object
5956   * @param int $postid
5957   * @param int $discussionid
5958   * @return bool
5959   */
5960  function forum_change_discussionid($postid, $discussionid) {
5961      global $DB;
5962      $DB->set_field('forum_posts', 'discussion', $discussionid, array('id' => $postid));
5963      if ($posts = $DB->get_records('forum_posts', array('parent' => $postid))) {
5964          foreach ($posts as $post) {
5965              forum_change_discussionid($post->id, $discussionid);
5966          }
5967      }
5968      return true;
5969  }
5970  
5971  /**
5972   * Prints the editing button on subscribers page
5973   *
5974   * @global object
5975   * @global object
5976   * @param int $courseid
5977   * @param int $forumid
5978   * @return string
5979   */
5980  function forum_update_subscriptions_button($courseid, $forumid) {
5981      global $CFG, $USER;
5982  
5983      if (!empty($USER->subscriptionsediting)) {
5984          $string = get_string('turneditingoff');
5985          $edit = "off";
5986      } else {
5987          $string = get_string('turneditingon');
5988          $edit = "on";
5989      }
5990  
5991      return "<form method=\"get\" action=\"$CFG->wwwroot/mod/forum/subscribers.php\">".
5992             "<input type=\"hidden\" name=\"id\" value=\"$forumid\" />".
5993             "<input type=\"hidden\" name=\"edit\" value=\"$edit\" />".
5994             "<input type=\"submit\" value=\"$string\" /></form>";
5995  }
5996  
5997  // Functions to do with read tracking.
5998  
5999  /**
6000   * Mark posts as read.
6001   *
6002   * @global object
6003   * @global object
6004   * @param object $user object
6005   * @param array $postids array of post ids
6006   * @return boolean success
6007   */
6008  function forum_tp_mark_posts_read($user, $postids) {
6009      global $CFG, $DB;
6010  
6011      if (!forum_tp_can_track_forums(false, $user)) {
6012          return true;
6013      }
6014  
6015      $status = true;
6016  
6017      $now = time();
6018      $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
6019  
6020      if (empty($postids)) {
6021          return true;
6022  
6023      } else if (count($postids) > 200) {
6024          while ($part = array_splice($postids, 0, 200)) {
6025              $status = forum_tp_mark_posts_read($user, $part) && $status;
6026          }
6027          return $status;
6028      }
6029  
6030      list($usql, $postidparams) = $DB->get_in_or_equal($postids, SQL_PARAMS_NAMED, 'postid');
6031  
6032      $insertparams = array(
6033          'userid1' => $user->id,
6034          'userid2' => $user->id,
6035          'userid3' => $user->id,
6036          'firstread' => $now,
6037          'lastread' => $now,
6038          'cutoffdate' => $cutoffdate,
6039      );
6040      $params = array_merge($postidparams, $insertparams);
6041  
6042      if ($CFG->forum_allowforcedreadtracking) {
6043          $trackingsql = "AND (f.trackingtype = ".FORUM_TRACKING_FORCED."
6044                          OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL))";
6045      } else {
6046          $trackingsql = "AND ((f.trackingtype = ".FORUM_TRACKING_OPTIONAL."  OR f.trackingtype = ".FORUM_TRACKING_FORCED.")
6047                              AND tf.id IS NULL)";
6048      }
6049  
6050      // First insert any new entries.
6051      $sql = "INSERT INTO {forum_read} (userid, postid, discussionid, forumid, firstread, lastread)
6052  
6053              SELECT :userid1, p.id, p.discussion, d.forum, :firstread, :lastread
6054                  FROM {forum_posts} p
6055                      JOIN {forum_discussions} d       ON d.id = p.discussion
6056                      JOIN {forum} f                   ON f.id = d.forum
6057                      LEFT JOIN {forum_track_prefs} tf ON (tf.userid = :userid2 AND tf.forumid = f.id)
6058                      LEFT JOIN {forum_read} fr        ON (
6059                              fr.userid = :userid3
6060                          AND fr.postid = p.id
6061                          AND fr.discussionid = d.id
6062                          AND fr.forumid = f.id
6063                      )
6064                  WHERE p.id $usql
6065                      AND p.modified >= :cutoffdate
6066                      $trackingsql
6067                      AND fr.id IS NULL";
6068  
6069      $status = $DB->execute($sql, $params) && $status;
6070  
6071      // Then update all records.
6072      $updateparams = array(
6073          'userid' => $user->id,
6074          'lastread' => $now,
6075      );
6076      $params = array_merge($postidparams, $updateparams);
6077      $status = $DB->set_field_select('forum_read', 'lastread', $now, '
6078                  userid      =  :userid
6079              AND lastread    <> :lastread
6080              AND postid      ' . $usql,
6081              $params) && $status;
6082  
6083      return $status;
6084  }
6085  
6086  /**
6087   * Mark post as read.
6088   * @global object
6089   * @global object
6090   * @param int $userid
6091   * @param int $postid
6092   */
6093  function forum_tp_add_read_record($userid, $postid) {
6094      global $CFG, $DB;
6095  
6096      $now = time();
6097      $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
6098  
6099      if (!$DB->record_exists('forum_read', array('userid' => $userid, 'postid' => $postid))) {
6100          $sql = "INSERT INTO {forum_read} (userid, postid, discussionid, forumid, firstread, lastread)
6101  
6102                  SELECT ?, p.id, p.discussion, d.forum, ?, ?
6103                    FROM {forum_posts} p
6104                         JOIN {forum_discussions} d ON d.id = p.discussion
6105                   WHERE p.id = ? AND p.modified >= ?";
6106          return $DB->execute($sql, array($userid, $now, $now, $postid, $cutoffdate));
6107  
6108      } else {
6109          $sql = "UPDATE {forum_read}
6110                     SET lastread = ?
6111                   WHERE userid = ? AND postid = ?";
6112          return $DB->execute($sql, array($now, $userid, $userid));
6113      }
6114  }
6115  
6116  /**
6117   * If its an old post, do nothing. If the record exists, the maintenance will clear it up later.
6118   *
6119   * @return bool
6120   */
6121  function forum_tp_mark_post_read($userid, $post, $forumid) {
6122      if (!forum_tp_is_post_old($post)) {
6123          return forum_tp_add_read_record($userid, $post->id);
6124      } else {
6125          return true;
6126      }
6127  }
6128  
6129  /**
6130   * Marks a whole forum as read, for a given user
6131   *
6132   * @global object
6133   * @global object
6134   * @param object $user
6135   * @param int $forumid
6136   * @param int|bool $groupid
6137   * @return bool
6138   */
6139  function forum_tp_mark_forum_read($user, $forumid, $groupid=false) {
6140      global $CFG, $DB;
6141  
6142      $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6143  
6144      $groupsel = "";
6145      $params = array($user->id, $forumid, $cutoffdate);
6146  
6147      if ($groupid !== false) {
6148          $groupsel = " AND (d.groupid = ? OR d.groupid = -1)";
6149          $params[] = $groupid;
6150      }
6151  
6152      $sql = "SELECT p.id
6153                FROM {forum_posts} p
6154                     LEFT JOIN {forum_discussions} d ON d.id = p.discussion
6155                     LEFT JOIN {forum_read} r        ON (r.postid = p.id AND r.userid = ?)
6156               WHERE d.forum = ?
6157                     AND p.modified >= ? AND r.id is NULL
6158                     $groupsel";
6159  
6160      if ($posts = $DB->get_records_sql($sql, $params)) {
6161          $postids = array_keys($posts);
6162          return forum_tp_mark_posts_read($user, $postids);
6163      }
6164  
6165      return true;
6166  }
6167  
6168  /**
6169   * Marks a whole discussion as read, for a given user
6170   *
6171   * @global object
6172   * @global object
6173   * @param object $user
6174   * @param int $discussionid
6175   * @return bool
6176   */
6177  function forum_tp_mark_discussion_read($user, $discussionid) {
6178      global $CFG, $DB;
6179  
6180      $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6181  
6182      $sql = "SELECT p.id
6183                FROM {forum_posts} p
6184                     LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6185               WHERE p.discussion = ?
6186                     AND p.modified >= ? AND r.id is NULL";
6187  
6188      if ($posts = $DB->get_records_sql($sql, array($user->id, $discussionid, $cutoffdate))) {
6189          $postids = array_keys($posts);
6190          return forum_tp_mark_posts_read($user, $postids);
6191      }
6192  
6193      return true;
6194  }
6195  
6196  /**
6197   * @global object
6198   * @param int $userid
6199   * @param object $post
6200   */
6201  function forum_tp_is_post_read($userid, $post) {
6202      global $DB;
6203      return (forum_tp_is_post_old($post) ||
6204              $DB->record_exists('forum_read', array('userid' => $userid, 'postid' => $post->id)));
6205  }
6206  
6207  /**
6208   * @global object
6209   * @param object $post
6210   * @param int $time Defautls to time()
6211   */
6212  function forum_tp_is_post_old($post, $time=null) {
6213      global $CFG;
6214  
6215      if (is_null($time)) {
6216          $time = time();
6217      }
6218      return ($post->modified < ($time - ($CFG->forum_oldpostdays * 24 * 3600)));
6219  }
6220  
6221  /**
6222   * Returns the count of records for the provided user and course.
6223   * Please note that group access is ignored!
6224   *
6225   * @global object
6226   * @global object
6227   * @param int $userid
6228   * @param int $courseid
6229   * @return array
6230   */
6231  function forum_tp_get_course_unread_posts($userid, $courseid) {
6232      global $CFG, $DB;
6233  
6234      $now = round(time(), -2); // DB cache friendliness.
6235      $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 60 * 60);
6236      $params = array($userid, $userid, $courseid, $cutoffdate, $userid);
6237  
6238      if (!empty($CFG->forum_enabletimedposts)) {
6239          $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
6240          $params[] = $now;
6241          $params[] = $now;
6242      } else {
6243          $timedsql = "";
6244      }
6245  
6246      if ($CFG->forum_allowforcedreadtracking) {
6247          $trackingsql = "AND (f.trackingtype = ".FORUM_TRACKING_FORCED."
6248                              OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL
6249                                  AND (SELECT trackforums FROM {user} WHERE id = ?) = 1))";
6250      } else {
6251          $trackingsql = "AND ((f.trackingtype = ".FORUM_TRACKING_OPTIONAL." OR f.trackingtype = ".FORUM_TRACKING_FORCED.")
6252                              AND tf.id IS NULL
6253                              AND (SELECT trackforums FROM {user} WHERE id = ?) = 1)";
6254      }
6255  
6256      $sql = "SELECT f.id, COUNT(p.id) AS unread
6257                FROM {forum_posts} p
6258                     JOIN {forum_discussions} d       ON d.id = p.discussion
6259                     JOIN {forum} f                   ON f.id = d.forum
6260                     JOIN {course} c                  ON c.id = f.course
6261                     LEFT JOIN {forum_read} r         ON (r.postid = p.id AND r.userid = ?)
6262                     LEFT JOIN {forum_track_prefs} tf ON (tf.userid = ? AND tf.forumid = f.id)
6263               WHERE f.course = ?
6264                     AND p.modified >= ? AND r.id is NULL
6265                     $trackingsql
6266                     $timedsql
6267            GROUP BY f.id";
6268  
6269      if ($return = $DB->get_records_sql($sql, $params)) {
6270          return $return;
6271      }
6272  
6273      return array();
6274  }
6275  
6276  /**
6277   * Returns the count of records for the provided user and forum and [optionally] group.
6278   *
6279   * @global object
6280   * @global object
6281   * @global object
6282   * @param object $cm
6283   * @param object $course
6284   * @return int
6285   */
6286  function forum_tp_count_forum_unread_posts($cm, $course) {
6287      global $CFG, $USER, $DB;
6288  
6289      static $readcache = array();
6290  
6291      $forumid = $cm->instance;
6292  
6293      if (!isset($readcache[$course->id])) {
6294          $readcache[$course->id] = array();
6295          if ($counts = forum_tp_get_course_unread_posts($USER->id, $course->id)) {
6296              foreach ($counts as $count) {
6297                  $readcache[$course->id][$count->id] = $count->unread;
6298              }
6299          }
6300      }
6301  
6302      if (empty($readcache[$course->id][$forumid])) {
6303          // no need to check group mode ;-)
6304          return 0;
6305      }
6306  
6307      $groupmode = groups_get_activity_groupmode($cm, $course);
6308  
6309      if ($groupmode != SEPARATEGROUPS) {
6310          return $readcache[$course->id][$forumid];
6311      }
6312  
6313      if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) {
6314          return $readcache[$course->id][$forumid];
6315      }
6316  
6317      require_once($CFG->dirroot.'/course/lib.php');
6318  
6319      $modinfo = get_fast_modinfo($course);
6320  
6321      $mygroups = $modinfo->get_groups($cm->groupingid);
6322  
6323      // add all groups posts
6324      $mygroups[-1] = -1;
6325  
6326      list ($groups_sql, $groups_params) = $DB->get_in_or_equal($mygroups);
6327  
6328      $now = round(time(), -2); // db cache friendliness
6329      $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
6330      $params = array($USER->id, $forumid, $cutoffdate);
6331  
6332      if (!empty($CFG->forum_enabletimedposts)) {
6333          $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
6334          $params[] = $now;
6335          $params[] = $now;
6336      } else {
6337          $timedsql = "";
6338      }
6339  
6340      $params = array_merge($params, $groups_params);
6341  
6342      $sql = "SELECT COUNT(p.id)
6343                FROM {forum_posts} p
6344                     JOIN {forum_discussions} d ON p.discussion = d.id
6345                     LEFT JOIN {forum_read} r   ON (r.postid = p.id AND r.userid = ?)
6346               WHERE d.forum = ?
6347                     AND p.modified >= ? AND r.id is NULL
6348                     $timedsql
6349                     AND d.groupid $groups_sql";
6350  
6351      return $DB->get_field_sql($sql, $params);
6352  }
6353  
6354  /**
6355   * Deletes read records for the specified index. At least one parameter must be specified.
6356   *
6357   * @global object
6358   * @param int $userid
6359   * @param int $postid
6360   * @param int $discussionid
6361   * @param int $forumid
6362   * @return bool
6363   */
6364  function forum_tp_delete_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
6365      global $DB;
6366      $params = array();
6367  
6368      $select = '';
6369      if ($userid > -1) {
6370          if ($select != '') $select .= ' AND ';
6371          $select .= 'userid = ?';
6372          $params[] = $userid;
6373      }
6374      if ($postid > -1) {
6375          if ($select != '') $select .= ' AND ';
6376          $select .= 'postid = ?';
6377          $params[] = $postid;
6378      }
6379      if ($discussionid > -1) {
6380          if ($select != '') $select .= ' AND ';
6381          $select .= 'discussionid = ?';
6382          $params[] = $discussionid;
6383      }
6384      if ($forumid > -1) {
6385          if ($select != '') $select .= ' AND ';
6386          $select .= 'forumid = ?';
6387          $params[] = $forumid;
6388      }
6389      if ($select == '') {
6390          return false;
6391      }
6392      else {
6393          return $DB->delete_records_select('forum_read', $select, $params);
6394      }
6395  }
6396  /**
6397   * Get a list of forums not tracked by the user.
6398   *
6399   * @global object
6400   * @global object
6401   * @param int $userid The id of the user to use.
6402   * @param int $courseid The id of the course being checked.
6403   * @return mixed An array indexed by forum id, or false.
6404   */
6405  function forum_tp_get_untracked_forums($userid, $courseid) {
6406      global $CFG, $DB;
6407  
6408      if ($CFG->forum_allowforcedreadtracking) {
6409          $trackingsql = "AND (f.trackingtype = ".FORUM_TRACKING_OFF."
6410                              OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND (ft.id IS NOT NULL
6411                                  OR (SELECT trackforums FROM {user} WHERE id = ?) = 0)))";
6412      } else {
6413          $trackingsql = "AND (f.trackingtype = ".FORUM_TRACKING_OFF."
6414                              OR ((f.trackingtype = ".FORUM_TRACKING_OPTIONAL." OR f.trackingtype = ".FORUM_TRACKING_FORCED.")
6415                                  AND (ft.id IS NOT NULL
6416                                      OR (SELECT trackforums FROM {user} WHERE id = ?) = 0)))";
6417      }
6418  
6419      $sql = "SELECT f.id
6420                FROM {forum} f
6421                     LEFT JOIN {forum_track_prefs} ft ON (ft.forumid = f.id AND ft.userid = ?)
6422               WHERE f.course = ?
6423                     $trackingsql";
6424  
6425      if ($forums = $DB->get_records_sql($sql, array($userid, $courseid, $userid))) {
6426          foreach ($forums as $forum) {
6427              $forums[$forum->id] = $forum;
6428          }
6429          return $forums;
6430  
6431      } else {
6432          return array();
6433      }
6434  }
6435  
6436  /**
6437   * Determine if a user can track forums and optionally a particular forum.
6438   * Checks the site settings, the user settings and the forum settings (if
6439   * requested).
6440   *
6441   * @global object
6442   * @global object
6443   * @global object
6444   * @param mixed $forum The forum object to test, or the int id (optional).
6445   * @param mixed $userid The user object to check for (optional).
6446   * @return boolean
6447   */
6448  function forum_tp_can_track_forums($forum=false, $user=false) {
6449      global $USER, $CFG, $DB;
6450  
6451      // if possible, avoid expensive
6452      // queries
6453      if (empty($CFG->forum_trackreadposts)) {
6454          return false;
6455      }
6456  
6457      if ($user === false) {
6458          $user = $USER;
6459      }
6460  
6461      if (isguestuser($user) or empty($user->id)) {
6462          return false;
6463      }
6464  
6465      if ($forum === false) {
6466          if ($CFG->forum_allowforcedreadtracking) {
6467              // Since we can force tracking, assume yes without a specific forum.
6468              return true;
6469          } else {
6470              return (bool)$user->trackforums;
6471          }
6472      }
6473  
6474      // Work toward always passing an object...
6475      if (is_numeric($forum)) {
6476          debugging('Better use proper forum object.', DEBUG_DEVELOPER);
6477          $forum = $DB->get_record('forum', array('id' => $forum), '', 'id,trackingtype');
6478      }
6479  
6480      $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
6481      $forumforced = ($forum->trackingtype == FORUM_TRACKING_FORCED);
6482  
6483      if ($CFG->forum_allowforcedreadtracking) {
6484          // If we allow forcing, then forced forums takes procidence over user setting.
6485          return ($forumforced || ($forumallows  && (!empty($user->trackforums) && (bool)$user->trackforums)));
6486      } else {
6487          // If we don't allow forcing, user setting trumps.
6488          return ($forumforced || $forumallows)  && !empty($user->trackforums);
6489      }
6490  }
6491  
6492  /**
6493   * Tells whether a specific forum is tracked by the user. A user can optionally
6494   * be specified. If not specified, the current user is assumed.
6495   *
6496   * @global object
6497   * @global object
6498   * @global object
6499   * @param mixed $forum If int, the id of the forum being checked; if object, the forum object
6500   * @param int $userid The id of the user being checked (optional).
6501   * @return boolean
6502   */
6503  function forum_tp_is_tracked($forum, $user=false) {
6504      global $USER, $CFG, $DB;
6505  
6506      if ($user === false) {
6507          $user = $USER;
6508      }
6509  
6510      if (isguestuser($user) or empty($user->id)) {
6511          return false;
6512      }
6513  
6514      // Work toward always passing an object...
6515      if (is_numeric($forum)) {
6516          debugging('Better use proper forum object.', DEBUG_DEVELOPER);
6517          $forum = $DB->get_record('forum', array('id' => $forum));
6518      }
6519  
6520      if (!forum_tp_can_track_forums($forum, $user)) {
6521          return false;
6522      }
6523  
6524      $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
6525      $forumforced = ($forum->trackingtype == FORUM_TRACKING_FORCED);
6526      $userpref = $DB->get_record('forum_track_prefs', array('userid' => $user->id, 'forumid' => $forum->id));
6527  
6528      if ($CFG->forum_allowforcedreadtracking) {
6529          return $forumforced || ($forumallows && $userpref === false);
6530      } else {
6531          return  ($forumallows || $forumforced) && $userpref === false;
6532      }
6533  }
6534  
6535  /**
6536   * @global object
6537   * @global object
6538   * @param int $forumid
6539   * @param int $userid
6540   */
6541  function forum_tp_start_tracking($forumid, $userid=false) {
6542      global $USER, $DB;
6543  
6544      if ($userid === false) {
6545          $userid = $USER->id;
6546      }
6547  
6548      return $DB->delete_records('forum_track_prefs', array('userid' => $userid, 'forumid' => $forumid));
6549  }
6550  
6551  /**
6552   * @global object
6553   * @global object
6554   * @param int $forumid
6555   * @param int $userid
6556   */
6557  function forum_tp_stop_tracking($forumid, $userid=false) {
6558      global $USER, $DB;
6559  
6560      if ($userid === false) {
6561          $userid = $USER->id;
6562      }
6563  
6564      if (!$DB->record_exists('forum_track_prefs', array('userid' => $userid, 'forumid' => $forumid))) {
6565          $track_prefs = new stdClass();
6566          $track_prefs->userid = $userid;
6567          $track_prefs->forumid = $forumid;
6568          $DB->insert_record('forum_track_prefs', $track_prefs);
6569      }
6570  
6571      return forum_tp_delete_read_records($userid, -1, -1, $forumid);
6572  }
6573  
6574  
6575  /**
6576   * Clean old records from the forum_read table.
6577   * @global object
6578   * @global object
6579   * @return void
6580   */
6581  function forum_tp_clean_read_records() {
6582      global $CFG, $DB;
6583  
6584      if (!isset($CFG->forum_oldpostdays)) {
6585          return;
6586      }
6587  // Look for records older than the cutoffdate that are still in the forum_read table.
6588      $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6589  
6590      //first get the oldest tracking present - we need tis to speedup the next delete query
6591      $sql = "SELECT MIN(fp.modified) AS first
6592                FROM {forum_posts} fp
6593                     JOIN {forum_read} fr ON fr.postid=fp.id";
6594      if (!$first = $DB->get_field_sql($sql)) {
6595          // nothing to delete;
6596          return;
6597      }
6598  
6599      // now delete old tracking info
6600      $sql = "DELETE
6601                FROM {forum_read}
6602               WHERE postid IN (SELECT fp.id
6603                                  FROM {forum_posts} fp
6604                                 WHERE fp.modified >= ? AND fp.modified < ?)";
6605      $DB->execute($sql, array($first, $cutoffdate));
6606  }
6607  
6608  /**
6609   * Sets the last post for a given discussion
6610   *
6611   * @global object
6612   * @global object
6613   * @param into $discussionid
6614   * @return bool|int
6615   **/
6616  function forum_discussion_update_last_post($discussionid) {
6617      global $CFG, $DB;
6618  
6619  // Check the given discussion exists
6620      if (!$DB->record_exists('forum_discussions', array('id' => $discussionid))) {
6621          return false;
6622      }
6623  
6624  // Use SQL to find the last post for this discussion
6625      $sql = "SELECT id, userid, modified
6626                FROM {forum_posts}
6627               WHERE discussion=?
6628               ORDER BY modified DESC";
6629  
6630  // Lets go find the last post
6631      if (($lastposts = $DB->get_records_sql($sql, array($discussionid), 0, 1))) {
6632          $lastpost = reset($lastposts);
6633          $discussionobject = new stdClass();
6634          $discussionobject->id           = $discussionid;
6635          $discussionobject->usermodified = $lastpost->userid;
6636          $discussionobject->timemodified = $lastpost->modified;
6637          $DB->update_record('forum_discussions', $discussionobject);
6638          return $lastpost->id;
6639      }
6640  
6641  // To get here either we couldn't find a post for the discussion (weird)
6642  // or we couldn't update the discussion record (weird x2)
6643      return false;
6644  }
6645  
6646  
6647  /**
6648   * List the actions that correspond to a view of this module.
6649   * This is used by the participation report.
6650   *
6651   * Note: This is not used by new logging system. Event with
6652   *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
6653   *       be considered as view action.
6654   *
6655   * @return array
6656   */
6657  function forum_get_view_actions() {
6658      return array('view discussion', 'search', 'forum', 'forums', 'subscribers', 'view forum');
6659  }
6660  
6661  /**
6662   * List the actions that correspond to a post of this module.
6663   * This is used by the participation report.
6664   *
6665   * Note: This is not used by new logging system. Event with
6666   *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
6667   *       will be considered as post action.
6668   *
6669   * @return array
6670   */
6671  function forum_get_post_actions() {
6672      return array('add discussion','add post','delete discussion','delete post','move discussion','prune post','update post');
6673  }
6674  
6675  /**
6676   * Returns a warning object if a user has reached the number of posts equal to
6677   * the warning/blocking setting, or false if there is no warning to show.
6678   *
6679   * @param int|stdClass $forum the forum id or the forum object
6680   * @param stdClass $cm the course module
6681   * @return stdClass|bool returns an object with the warning information, else
6682   *         returns false if no warning is required.
6683   */
6684  function forum_check_throttling($forum, $cm = null) {
6685      global $CFG, $DB, $USER;
6686  
6687      if (is_numeric($forum)) {
6688          $forum = $DB->get_record('forum', array('id' => $forum), '*', MUST_EXIST);
6689      }
6690  
6691      if (!is_object($forum)) {
6692          return false; // This is broken.
6693      }
6694  
6695      if (!$cm) {
6696          $cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course, false, MUST_EXIST);
6697      }
6698  
6699      if (empty($forum->blockafter)) {
6700          return false;
6701      }
6702  
6703      if (empty($forum->blockperiod)) {
6704          return false;
6705      }
6706  
6707      $modcontext = context_module::instance($cm->id);
6708      if (has_capability('mod/forum:postwithoutthrottling', $modcontext)) {
6709          return false;
6710      }
6711  
6712      // Get the number of posts in the last period we care about.
6713      $timenow = time();
6714      $timeafter = $timenow - $forum->blockperiod;
6715      $numposts = $DB->count_records_sql('SELECT COUNT(p.id) FROM {forum_posts} p
6716                                          JOIN {forum_discussions} d
6717                                          ON p.discussion = d.id WHERE d.forum = ?
6718                                          AND p.userid = ? AND p.created > ?', array($forum->id, $USER->id, $timeafter));
6719  
6720      $a = new stdClass();
6721      $a->blockafter = $forum->blockafter;
6722      $a->numposts = $numposts;
6723      $a->blockperiod = get_string('secondstotime'.$forum->blockperiod);
6724  
6725      if ($forum->blockafter <= $numposts) {
6726          $warning = new stdClass();
6727          $warning->canpost = false;
6728          $warning->errorcode = 'forumblockingtoomanyposts';
6729          $warning->module = 'error';
6730          $warning->additional = $a;
6731          $warning->link = $CFG->wwwroot . '/mod/forum/view.php?f=' . $forum->id;
6732  
6733          return $warning;
6734      }
6735  
6736      if ($forum->warnafter <= $numposts) {
6737          $warning = new stdClass();
6738          $warning->canpost = true;
6739          $warning->errorcode = 'forumblockingalmosttoomanyposts';
6740          $warning->module = 'forum';
6741          $warning->additional = $a;
6742          $warning->link = null;
6743  
6744          return $warning;
6745      }
6746  }
6747  
6748  /**
6749   * Throws an error if the user is no longer allowed to post due to having reached
6750   * or exceeded the number of posts specified in 'Post threshold for blocking'
6751   * setting.
6752   *
6753   * @since Moodle 2.5
6754   * @param stdClass $thresholdwarning the warning information returned
6755   *        from the function forum_check_throttling.
6756   */
6757  function forum_check_blocking_threshold($thresholdwarning) {
6758      if (!empty($thresholdwarning) && !$thresholdwarning->canpost) {
6759          print_error($thresholdwarning->errorcode,
6760                      $thresholdwarning->module,
6761                      $thresholdwarning->link,
6762                      $thresholdwarning->additional);
6763      }
6764  }
6765  
6766  
6767  /**
6768   * Removes all grades from gradebook
6769   *
6770   * @global object
6771   * @global object
6772   * @param int $courseid
6773   * @param string $type optional
6774   */
6775  function forum_reset_gradebook($courseid, $type='') {
6776      global $CFG, $DB;
6777  
6778      $wheresql = '';
6779      $params = array($courseid);
6780      if ($type) {
6781          $wheresql = "AND f.type=?";
6782          $params[] = $type;
6783      }
6784  
6785      $sql = "SELECT f.*, cm.idnumber as cmidnumber, f.course as courseid
6786                FROM {forum} f, {course_modules} cm, {modules} m
6787               WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id AND f.course=? $wheresql";
6788  
6789      if ($forums = $DB->get_records_sql($sql, $params)) {
6790          foreach ($forums as $forum) {
6791              forum_grade_item_update($forum, 'reset');
6792          }
6793      }
6794  }
6795  
6796  /**
6797   * This function is used by the reset_course_userdata function in moodlelib.
6798   * This function will remove all posts from the specified forum
6799   * and clean up any related data.
6800   *
6801   * @global object
6802   * @global object
6803   * @param $data the data submitted from the reset course.
6804   * @return array status array
6805   */
6806  function forum_reset_userdata($data) {
6807      global $CFG, $DB;
6808      require_once($CFG->dirroot.'/rating/lib.php');
6809  
6810      $componentstr = get_string('modulenameplural', 'forum');
6811      $status = array();
6812  
6813      $params = array($data->courseid);
6814  
6815      $removeposts = false;
6816      $typesql     = "";
6817      if (!empty($data->reset_forum_all)) {
6818          $removeposts = true;
6819          $typesstr    = get_string('resetforumsall', 'forum');
6820          $types       = array();
6821      } else if (!empty($data->reset_forum_types)){
6822          $removeposts = true;
6823          $typesql     = "";
6824          $types       = array();
6825          $forum_types_all = forum_get_forum_types_all();
6826          foreach ($data->reset_forum_types as $type) {
6827              if (!array_key_exists($type, $forum_types_all)) {
6828                  continue;
6829              }
6830              $typesql .= " AND f.type=?";
6831              $types[] = $forum_types_all[$type];
6832              $params[] = $type;
6833          }
6834          $typesstr = get_string('resetforums', 'forum').': '.implode(', ', $types);
6835      }
6836      $alldiscussionssql = "SELECT fd.id
6837                              FROM {forum_discussions} fd, {forum} f
6838                             WHERE f.course=? AND f.id=fd.forum";
6839  
6840      $allforumssql      = "SELECT f.id
6841                              FROM {forum} f
6842                             WHERE f.course=?";
6843  
6844      $allpostssql       = "SELECT fp.id
6845                              FROM {forum_posts} fp, {forum_discussions} fd, {forum} f
6846                             WHERE f.course=? AND f.id=fd.forum AND fd.id=fp.discussion";
6847  
6848      $forumssql = $forums = $rm = null;
6849  
6850      if( $removeposts || !empty($data->reset_forum_ratings) ) {
6851          $forumssql      = "$allforumssql $typesql";
6852          $forums = $forums = $DB->get_records_sql($forumssql, $params);
6853          $rm = new rating_manager();
6854          $ratingdeloptions = new stdClass;
6855          $ratingdeloptions->component = 'mod_forum';
6856          $ratingdeloptions->ratingarea = 'post';
6857      }
6858  
6859      if ($removeposts) {
6860          $discussionssql = "$alldiscussionssql $typesql";
6861          $postssql       = "$allpostssql $typesql";
6862  
6863          // now get rid of all attachments
6864          $fs = get_file_storage();
6865          if ($forums) {
6866              foreach ($forums as $forumid=>$unused) {
6867                  if (!$cm = get_coursemodule_from_instance('forum', $forumid)) {
6868                      continue;
6869                  }
6870                  $context = context_module::instance($cm->id);
6871                  $fs->delete_area_files($context->id, 'mod_forum', 'attachment');
6872                  $fs->delete_area_files($context->id, 'mod_forum', 'post');
6873  
6874                  //remove ratings
6875                  $ratingdeloptions->contextid = $context->id;
6876                  $rm->delete_ratings($ratingdeloptions);
6877              }
6878          }
6879  
6880          // first delete all read flags
6881          $DB->delete_records_select('forum_read', "forumid IN ($forumssql)", $params);
6882  
6883          // remove tracking prefs
6884          $DB->delete_records_select('forum_track_prefs', "forumid IN ($forumssql)", $params);
6885  
6886          // remove posts from queue
6887          $DB->delete_records_select('forum_queue', "discussionid IN ($discussionssql)", $params);
6888  
6889          // all posts - initial posts must be kept in single simple discussion forums
6890          $DB->delete_records_select('forum_posts', "discussion IN ($discussionssql) AND parent <> 0", $params); // first all children
6891          $DB->delete_records_select('forum_posts', "discussion IN ($discussionssql AND f.type <> 'single') AND parent = 0", $params); // now the initial posts for non single simple
6892  
6893          // finally all discussions except single simple forums
6894          $DB->delete_records_select('forum_discussions', "forum IN ($forumssql AND f.type <> 'single')", $params);
6895  
6896          // remove all grades from gradebook
6897          if (empty($data->reset_gradebook_grades)) {
6898              if (empty($types)) {
6899                  forum_reset_gradebook($data->courseid);
6900              } else {
6901                  foreach ($types as $type) {
6902                      forum_reset_gradebook($data->courseid, $type);
6903                  }
6904              }
6905          }
6906  
6907          $status[] = array('component'=>$componentstr, 'item'=>$typesstr, 'error'=>false);
6908      }
6909  
6910      // remove all ratings in this course's forums
6911      if (!empty($data->reset_forum_ratings)) {
6912          if ($forums) {
6913              foreach ($forums as $forumid=>$unused) {
6914                  if (!$cm = get_coursemodule_from_instance('forum', $forumid)) {
6915                      continue;
6916                  }
6917                  $context = context_module::instance($cm->id);
6918  
6919                  //remove ratings
6920                  $ratingdeloptions->contextid = $context->id;
6921                  $rm->delete_ratings($ratingdeloptions);
6922              }
6923          }
6924  
6925          // remove all grades from gradebook
6926          if (empty($data->reset_gradebook_grades)) {
6927              forum_reset_gradebook($data->courseid);
6928          }
6929      }
6930  
6931      // remove all digest settings unconditionally - even for users still enrolled in course.
6932      if (!empty($data->reset_forum_digests)) {
6933          $DB->delete_records_select('forum_digests', "forum IN ($allforumssql)", $params);
6934          $status[] = array('component' => $componentstr, 'item' => get_string('resetdigests', 'forum'), 'error' => false);
6935      }
6936  
6937      // remove all subscriptions unconditionally - even for users still enrolled in course
6938      if (!empty($data->reset_forum_subscriptions)) {
6939          $DB->delete_records_select('forum_subscriptions', "forum IN ($allforumssql)", $params);
6940          $DB->delete_records_select('forum_discussion_subs', "forum IN ($allforumssql)", $params);
6941          $status[] = array('component' => $componentstr, 'item' => get_string('resetsubscriptions', 'forum'), 'error' => false);
6942      }
6943  
6944      // remove all tracking prefs unconditionally - even for users still enrolled in course
6945      if (!empty($data->reset_forum_track_prefs)) {
6946          $DB->delete_records_select('forum_track_prefs', "forumid IN ($allforumssql)", $params);
6947          $status[] = array('component'=>$componentstr, 'item'=>get_string('resettrackprefs','forum'), 'error'=>false);
6948      }
6949  
6950      /// updating dates - shift may be negative too
6951      if ($data->timeshift) {
6952          shift_course_mod_dates('forum', array('assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid);
6953          $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
6954      }
6955  
6956      return $status;
6957  }
6958  
6959  /**
6960   * Called by course/reset.php
6961   *
6962   * @param $mform form passed by reference
6963   */
6964  function forum_reset_course_form_definition(&$mform) {
6965      $mform->addElement('header', 'forumheader', get_string('modulenameplural', 'forum'));
6966  
6967      $mform->addElement('checkbox', 'reset_forum_all', get_string('resetforumsall','forum'));
6968  
6969      $mform->addElement('select', 'reset_forum_types', get_string('resetforums', 'forum'), forum_get_forum_types_all(), array('multiple' => 'multiple'));
6970      $mform->setAdvanced('reset_forum_types');
6971      $mform->disabledIf('reset_forum_types', 'reset_forum_all', 'checked');
6972  
6973      $mform->addElement('checkbox', 'reset_forum_digests', get_string('resetdigests','forum'));
6974      $mform->setAdvanced('reset_forum_digests');
6975  
6976      $mform->addElement('checkbox', 'reset_forum_subscriptions', get_string('resetsubscriptions','forum'));
6977      $mform->setAdvanced('reset_forum_subscriptions');
6978  
6979      $mform->addElement('checkbox', 'reset_forum_track_prefs', get_string('resettrackprefs','forum'));
6980      $mform->setAdvanced('reset_forum_track_prefs');
6981      $mform->disabledIf('reset_forum_track_prefs', 'reset_forum_all', 'checked');
6982  
6983      $mform->addElement('checkbox', 'reset_forum_ratings', get_string('deleteallratings'));
6984      $mform->disabledIf('reset_forum_ratings', 'reset_forum_all', 'checked');
6985  }
6986  
6987  /**
6988   * Course reset form defaults.
6989   * @return array
6990   */
6991  function forum_reset_course_form_defaults($course) {
6992      return array('reset_forum_all'=>1, 'reset_forum_digests' => 0, 'reset_forum_subscriptions'=>0, 'reset_forum_track_prefs'=>0, 'reset_forum_ratings'=>1);
6993  }
6994  
6995  /**
6996   * Returns array of forum layout modes
6997   *
6998   * @return array
6999   */
7000  function forum_get_layout_modes() {
7001      return array (FORUM_MODE_FLATOLDEST => get_string('modeflatoldestfirst', 'forum'),
7002                    FORUM_MODE_FLATNEWEST => get_string('modeflatnewestfirst', 'forum'),
7003                    FORUM_MODE_THREADED   => get_string('modethreaded', 'forum'),
7004                    FORUM_MODE_NESTED     => get_string('modenested', 'forum'));
7005  }
7006  
7007  /**
7008   * Returns array of forum types chooseable on the forum editing form
7009   *
7010   * @return array
7011   */
7012  function forum_get_forum_types() {
7013      return array ('general'  => get_string('generalforum', 'forum'),
7014                    'eachuser' => get_string('eachuserforum', 'forum'),
7015                    'single'   => get_string('singleforum', 'forum'),
7016                    'qanda'    => get_string('qandaforum', 'forum'),
7017                    'blog'     => get_string('blogforum', 'forum'));
7018  }
7019  
7020  /**
7021   * Returns array of all forum layout modes
7022   *
7023   * @return array
7024   */
7025  function forum_get_forum_types_all() {
7026      return array ('news'     => get_string('namenews','forum'),
7027                    'social'   => get_string('namesocial','forum'),
7028                    'general'  => get_string('generalforum', 'forum'),
7029                    'eachuser' => get_string('eachuserforum', 'forum'),
7030                    'single'   => get_string('singleforum', 'forum'),
7031                    'qanda'    => get_string('qandaforum', 'forum'),
7032                    'blog'     => get_string('blogforum', 'forum'));
7033  }
7034  
7035  /**
7036   * Returns all other caps used in module
7037   *
7038   * @return array
7039   */
7040  function forum_get_extra_capabilities() {
7041      return array('moodle/site:accessallgroups', 'moodle/site:viewfullnames', 'moodle/site:trustcontent', 'moodle/rating:view', 'moodle/rating:viewany', 'moodle/rating:viewall', 'moodle/rating:rate');
7042  }
7043  
7044  /**
7045   * Adds module specific settings to the settings block
7046   *
7047   * @param settings_navigation $settings The settings navigation object
7048   * @param navigation_node $forumnode The node to add module settings to
7049   */
7050  function forum_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $forumnode) {
7051      global $USER, $PAGE, $CFG, $DB, $OUTPUT;
7052  
7053      $forumobject = $DB->get_record("forum", array("id" => $PAGE->cm->instance));
7054      if (empty($PAGE->cm->context)) {
7055          $PAGE->cm->context = context_module::instance($PAGE->cm->instance);
7056      }
7057  
7058      // for some actions you need to be enrolled, beiing admin is not enough sometimes here
7059      $enrolled = is_enrolled($PAGE->cm->context, $USER, '', false);
7060      $activeenrolled = is_enrolled($PAGE->cm->context, $USER, '', true);
7061  
7062      $canmanage  = has_capability('mod/forum:managesubscriptions', $PAGE->cm->context);
7063      $subscriptionmode = \mod_forum\subscriptions::get_subscription_mode($forumobject);
7064      $cansubscribe = $activeenrolled && !\mod_forum\subscriptions::is_forcesubscribed($forumobject) &&
7065              (!\mod_forum\subscriptions::subscription_disabled($forumobject) || $canmanage);
7066  
7067      if ($canmanage) {
7068          $mode = $forumnode->add(get_string('subscriptionmode', 'forum'), null, navigation_node::TYPE_CONTAINER);
7069  
7070          $allowchoice = $mode->add(get_string('subscriptionoptional', 'forum'), new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'mode'=>FORUM_CHOOSESUBSCRIBE, 'sesskey'=>sesskey())), navigation_node::TYPE_SETTING);
7071          $forceforever = $mode->add(get_string("subscriptionforced", "forum"), new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'mode'=>FORUM_FORCESUBSCRIBE, 'sesskey'=>sesskey())), navigation_node::TYPE_SETTING);
7072          $forceinitially = $mode->add(get_string("subscriptionauto", "forum"), new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'mode'=>FORUM_INITIALSUBSCRIBE, 'sesskey'=>sesskey())), navigation_node::TYPE_SETTING);
7073          $disallowchoice = $mode->add(get_string('subscriptiondisabled', 'forum'), new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'mode'=>FORUM_DISALLOWSUBSCRIBE, 'sesskey'=>sesskey())), navigation_node::TYPE_SETTING);
7074  
7075          switch ($subscriptionmode) {
7076              case FORUM_CHOOSESUBSCRIBE : // 0
7077                  $allowchoice->action = null;
7078                  $allowchoice->add_class('activesetting');
7079                  break;
7080              case FORUM_FORCESUBSCRIBE : // 1
7081                  $forceforever->action = null;
7082                  $forceforever->add_class('activesetting');
7083                  break;
7084              case FORUM_INITIALSUBSCRIBE : // 2
7085                  $forceinitially->action = null;
7086                  $forceinitially->add_class('activesetting');
7087                  break;
7088              case FORUM_DISALLOWSUBSCRIBE : // 3
7089                  $disallowchoice->action = null;
7090                  $disallowchoice->add_class('activesetting');
7091                  break;
7092          }
7093  
7094      } else if ($activeenrolled) {
7095  
7096          switch ($subscriptionmode) {
7097              case FORUM_CHOOSESUBSCRIBE : // 0
7098                  $notenode = $forumnode->add(get_string('subscriptionoptional', 'forum'));
7099                  break;
7100              case FORUM_FORCESUBSCRIBE : // 1
7101                  $notenode = $forumnode->add(get_string('subscriptionforced', 'forum'));
7102                  break;
7103              case FORUM_INITIALSUBSCRIBE : // 2
7104                  $notenode = $forumnode->add(get_string('subscriptionauto', 'forum'));
7105                  break;
7106              case FORUM_DISALLOWSUBSCRIBE : // 3
7107                  $notenode = $forumnode->add(get_string('subscriptiondisabled', 'forum'));
7108                  break;
7109          }
7110      }
7111  
7112      if ($cansubscribe) {
7113          if (\mod_forum\subscriptions::is_subscribed($USER->id, $forumobject)) {
7114              $linktext = get_string('unsubscribe', 'forum');
7115          } else {
7116              $linktext = get_string('subscribe', 'forum');
7117          }
7118          $url = new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'sesskey'=>sesskey()));
7119          $forumnode->add($linktext, $url, navigation_node::TYPE_SETTING);
7120      }
7121  
7122      if (has_capability('mod/forum:viewsubscribers', $PAGE->cm->context)){
7123          $url = new moodle_url('/mod/forum/subscribers.php', array('id'=>$forumobject->id));
7124          $forumnode->add(get_string('showsubscribers', 'forum'), $url, navigation_node::TYPE_SETTING);
7125      }
7126  
7127      if ($enrolled && forum_tp_can_track_forums($forumobject)) { // keep tracking info for users with suspended enrolments
7128          if ($forumobject->trackingtype == FORUM_TRACKING_OPTIONAL
7129                  || ((!$CFG->forum_allowforcedreadtracking) && $forumobject->trackingtype == FORUM_TRACKING_FORCED)) {
7130              if (forum_tp_is_tracked($forumobject)) {
7131                  $linktext = get_string('notrackforum', 'forum');
7132              } else {
7133                  $linktext = get_string('trackforum', 'forum');
7134              }
7135              $url = new moodle_url('/mod/forum/settracking.php', array(
7136                      'id' => $forumobject->id,
7137                      'sesskey' => sesskey(),
7138                  ));
7139              $forumnode->add($linktext, $url, navigation_node::TYPE_SETTING);
7140          }
7141      }
7142  
7143      if (!isloggedin() && $PAGE->course->id == SITEID) {
7144          $userid = guest_user()->id;
7145      } else {
7146          $userid = $USER->id;
7147      }
7148  
7149      $hascourseaccess = ($PAGE->course->id == SITEID) || can_access_course($PAGE->course, $userid);
7150      $enablerssfeeds = !empty($CFG->enablerssfeeds) && !empty($CFG->forum_enablerssfeeds);
7151  
7152      if ($enablerssfeeds && $forumobject->rsstype && $forumobject->rssarticles && $hascourseaccess) {
7153  
7154          if (!function_exists('rss_get_url')) {
7155              require_once("$CFG->libdir/rsslib.php");
7156          }
7157  
7158          if ($forumobject->rsstype == 1) {
7159              $string = get_string('rsssubscriberssdiscussions','forum');
7160          } else {
7161              $string = get_string('rsssubscriberssposts','forum');
7162          }
7163  
7164          $url = new moodle_url(rss_get_url($PAGE->cm->context->id, $userid, "mod_forum", $forumobject->id));
7165          $forumnode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', ''));
7166      }
7167  }
7168  
7169  /**
7170   * Adds information about unread messages, that is only required for the course view page (and
7171   * similar), to the course-module object.
7172   * @param cm_info $cm Course-module object
7173   */
7174  function forum_cm_info_view(cm_info $cm) {
7175      global $CFG;
7176  
7177      if (forum_tp_can_track_forums()) {
7178          if ($unread = forum_tp_count_forum_unread_posts($cm, $cm->get_course())) {
7179              $out = '<span class="unread"> <a href="' . $cm->url . '">';
7180              if ($unread == 1) {
7181                  $out .= get_string('unreadpostsone', 'forum');
7182              } else {
7183                  $out .= get_string('unreadpostsnumber', 'forum', $unread);
7184              }
7185              $out .= '</a></span>';
7186              $cm->set_after_link($out);
7187          }
7188      }
7189  }
7190  
7191  /**
7192   * Return a list of page types
7193   * @param string $pagetype current page type
7194   * @param stdClass $parentcontext Block's parent context
7195   * @param stdClass $currentcontext Current context of block
7196   */
7197  function forum_page_type_list($pagetype, $parentcontext, $currentcontext) {
7198      $forum_pagetype = array(
7199          'mod-forum-*'=>get_string('page-mod-forum-x', 'forum'),
7200          'mod-forum-view'=>get_string('page-mod-forum-view', 'forum'),
7201          'mod-forum-discuss'=>get_string('page-mod-forum-discuss', 'forum')
7202      );
7203      return $forum_pagetype;
7204  }
7205  
7206  /**
7207   * Gets all of the courses where the provided user has posted in a forum.
7208   *
7209   * @global moodle_database $DB The database connection
7210   * @param stdClass $user The user who's posts we are looking for
7211   * @param bool $discussionsonly If true only look for discussions started by the user
7212   * @param bool $includecontexts If set to trye contexts for the courses will be preloaded
7213   * @param int $limitfrom The offset of records to return
7214   * @param int $limitnum The number of records to return
7215   * @return array An array of courses
7216   */
7217  function forum_get_courses_user_posted_in($user, $discussionsonly = false, $includecontexts = true, $limitfrom = null, $limitnum = null) {
7218      global $DB;
7219  
7220      // If we are only after discussions we need only look at the forum_discussions
7221      // table and join to the userid there. If we are looking for posts then we need
7222      // to join to the forum_posts table.
7223      if (!$discussionsonly) {
7224          $subquery = "(SELECT DISTINCT fd.course
7225                           FROM {forum_discussions} fd
7226                           JOIN {forum_posts} fp ON fp.discussion = fd.id
7227                          WHERE fp.userid = :userid )";
7228      } else {
7229          $subquery= "(SELECT DISTINCT fd.course
7230                           FROM {forum_discussions} fd
7231                          WHERE fd.userid = :userid )";
7232      }
7233  
7234      $params = array('userid' => $user->id);
7235  
7236      // Join to the context table so that we can preload contexts if required.
7237      if ($includecontexts) {
7238          $ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
7239          $ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
7240          $params['contextlevel'] = CONTEXT_COURSE;
7241      } else {
7242          $ctxselect = '';
7243          $ctxjoin = '';
7244      }
7245  
7246      // Now we need to get all of the courses to search.
7247      // All courses where the user has posted within a forum will be returned.
7248      $sql = "SELECT c.* $ctxselect
7249              FROM {course} c
7250              $ctxjoin
7251              WHERE c.id IN ($subquery)";
7252      $courses = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
7253      if ($includecontexts) {
7254          array_map('context_helper::preload_from_record', $courses);
7255      }
7256      return $courses;
7257  }
7258  
7259  /**
7260   * Gets all of the forums a user has posted in for one or more courses.
7261   *
7262   * @global moodle_database $DB
7263   * @param stdClass $user
7264   * @param array $courseids An array of courseids to search or if not provided
7265   *                       all courses the user has posted within
7266   * @param bool $discussionsonly If true then only forums where the user has started
7267   *                       a discussion will be returned.
7268   * @param int $limitfrom The offset of records to return
7269   * @param int $limitnum The number of records to return
7270   * @return array An array of forums the user has posted within in the provided courses
7271   */
7272  function forum_get_forums_user_posted_in($user, array $courseids = null, $discussionsonly = false, $limitfrom = null, $limitnum = null) {
7273      global $DB;
7274  
7275      if (!is_null($courseids)) {
7276          list($coursewhere, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'courseid');
7277          $coursewhere = ' AND f.course '.$coursewhere;
7278      } else {
7279          $coursewhere = '';
7280          $params = array();
7281      }
7282      $params['userid'] = $user->id;
7283      $params['forum'] = 'forum';
7284  
7285      if ($discussionsonly) {
7286          $join = 'JOIN {forum_discussions} ff ON ff.forum = f.id';
7287      } else {
7288          $join = 'JOIN {forum_discussions} fd ON fd.forum = f.id
7289                   JOIN {forum_posts} ff ON ff.discussion = fd.id';
7290      }
7291  
7292      $sql = "SELECT f.*, cm.id AS cmid
7293                FROM {forum} f
7294                JOIN {course_modules} cm ON cm.instance = f.id
7295                JOIN {modules} m ON m.id = cm.module
7296                JOIN (
7297                    SELECT f.id
7298                      FROM {forum} f
7299                      {$join}
7300                     WHERE ff.userid = :userid
7301                  GROUP BY f.id
7302                     ) j ON j.id = f.id
7303               WHERE m.name = :forum
7304                   {$coursewhere}";
7305  
7306      $courseforums = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
7307      return $courseforums;
7308  }
7309  
7310  /**
7311   * Returns posts made by the selected user in the requested courses.
7312   *
7313   * This method can be used to return all of the posts made by the requested user
7314   * within the given courses.
7315   * For each course the access of the current user and requested user is checked
7316   * and then for each post access to the post and forum is checked as well.
7317   *
7318   * This function is safe to use with usercapabilities.
7319   *
7320   * @global moodle_database $DB
7321   * @param stdClass $user The user whose posts we want to get
7322   * @param array $courses The courses to search
7323   * @param bool $musthaveaccess If set to true errors will be thrown if the user
7324   *                             cannot access one or more of the courses to search
7325   * @param bool $discussionsonly If set to true only discussion starting posts
7326   *                              will be returned.
7327   * @param int $limitfrom The offset of records to return
7328   * @param int $limitnum The number of records to return
7329   * @return stdClass An object the following properties
7330   *               ->totalcount: the total number of posts made by the requested user
7331   *                             that the current user can see.
7332   *               ->courses: An array of courses the current user can see that the
7333   *                          requested user has posted in.
7334   *               ->forums: An array of forums relating to the posts returned in the
7335   *                         property below.
7336   *               ->posts: An array containing the posts to show for this request.
7337   */
7338  function forum_get_posts_by_user($user, array $courses, $musthaveaccess = false, $discussionsonly = false, $limitfrom = 0, $limitnum = 50) {
7339      global $DB, $USER, $CFG;
7340  
7341      $return = new stdClass;
7342      $return->totalcount = 0;    // The total number of posts that the current user is able to view
7343      $return->courses = array(); // The courses the current user can access
7344      $return->forums = array();  // The forums that the current user can access that contain posts
7345      $return->posts = array();   // The posts to display
7346  
7347      // First up a small sanity check. If there are no courses to check we can
7348      // return immediately, there is obviously nothing to search.
7349      if (empty($courses)) {
7350          return $return;
7351      }
7352  
7353      // A couple of quick setups
7354      $isloggedin = isloggedin();
7355      $isguestuser = $isloggedin && isguestuser();
7356      $iscurrentuser = $isloggedin && $USER->id == $user->id;
7357  
7358      // Checkout whether or not the current user has capabilities over the requested
7359      // user and if so they have the capabilities required to view the requested
7360      // users content.
7361      $usercontext = context_user::instance($user->id, MUST_EXIST);
7362      $hascapsonuser = !$iscurrentuser && $DB->record_exists('role_assignments', array('userid' => $USER->id, 'contextid' => $usercontext->id));
7363      $hascapsonuser = $hascapsonuser && has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), $usercontext);
7364  
7365      // Before we actually search each course we need to check the user's access to the
7366      // course. If the user doesn't have the appropraite access then we either throw an
7367      // error if a particular course was requested or we just skip over the course.
7368      foreach ($courses as $course) {
7369          $coursecontext = context_course::instance($course->id, MUST_EXIST);
7370          if ($iscurrentuser || $hascapsonuser) {
7371              // If it is the current user, or the current user has capabilities to the
7372              // requested user then all we need to do is check the requested users
7373              // current access to the course.
7374              // Note: There is no need to check group access or anything of the like
7375              // as either the current user is the requested user, or has granted
7376              // capabilities on the requested user. Either way they can see what the
7377              // requested user posted, although its VERY unlikely in the `parent` situation
7378              // that the current user will be able to view the posts in context.
7379              if (!is_viewing($coursecontext, $user) && !is_enrolled($coursecontext, $user)) {
7380                  // Need to have full access to a course to see the rest of own info
7381                  if ($musthaveaccess) {
7382                      print_error('errorenrolmentrequired', 'forum');
7383                  }
7384                  continue;
7385              }
7386          } else {
7387              // Check whether the current user is enrolled or has access to view the course
7388              // if they don't we immediately have a problem.
7389              if (!can_access_course($course)) {
7390                  if ($musthaveaccess) {
7391                      print_error('errorenrolmentrequired', 'forum');
7392                  }
7393                  continue;
7394              }
7395  
7396              // Check whether the requested user is enrolled or has access to view the course
7397              // if they don't we immediately have a problem.
7398              if (!can_access_course($course, $user)) {
7399                  if ($musthaveaccess) {
7400                      print_error('notenrolled', 'forum');
7401                  }
7402                  continue;
7403              }
7404  
7405              // If groups are in use and enforced throughout the course then make sure
7406              // we can meet in at least one course level group.
7407              // Note that we check if either the current user or the requested user have
7408              // the capability to access all groups. This is because with that capability
7409              // a user in group A could post in the group B forum. Grrrr.
7410              if (groups_get_course_groupmode($course) == SEPARATEGROUPS && $course->groupmodeforce
7411                && !has_capability('moodle/site:accessallgroups', $coursecontext) && !has_capability('moodle/site:accessallgroups', $coursecontext, $user->id)) {
7412                  // If its the guest user to bad... the guest user cannot access groups
7413                  if (!$isloggedin or $isguestuser) {
7414                      // do not use require_login() here because we might have already used require_login($course)
7415                      if ($musthaveaccess) {
7416                          redirect(get_login_url());
7417                      }
7418                      continue;
7419                  }
7420                  // Get the groups of the current user
7421                  $mygroups = array_keys(groups_get_all_groups($course->id, $USER->id, $course->defaultgroupingid, 'g.id, g.name'));
7422                  // Get the groups the requested user is a member of
7423                  $usergroups = array_keys(groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid, 'g.id, g.name'));
7424                  // Check whether they are members of the same group. If they are great.
7425                  $intersect = array_intersect($mygroups, $usergroups);
7426                  if (empty($intersect)) {
7427                      // But they're not... if it was a specific course throw an error otherwise
7428                      // just skip this course so that it is not searched.
7429                      if ($musthaveaccess) {
7430                          print_error("groupnotamember", '', $CFG->wwwroot."/course/view.php?id=$course->id");
7431                      }
7432                      continue;
7433                  }
7434              }
7435          }
7436          // Woo hoo we got this far which means the current user can search this
7437          // this course for the requested user. Although this is only the course accessibility
7438          // handling that is complete, the forum accessibility tests are yet to come.
7439          $return->courses[$course->id] = $course;
7440      }
7441      // No longer beed $courses array - lose it not it may be big
7442      unset($courses);
7443  
7444      // Make sure that we have some courses to search
7445      if (empty($return->courses)) {
7446          // If we don't have any courses to search then the reality is that the current
7447          // user doesn't have access to any courses is which the requested user has posted.
7448          // Although we do know at this point that the requested user has posts.
7449          if ($musthaveaccess) {
7450              print_error('permissiondenied');
7451          } else {
7452              return $return;
7453          }
7454      }
7455  
7456      // Next step: Collect all of the forums that we will want to search.
7457      // It is important to note that this step isn't actually about searching, it is
7458      // about determining which forums we can search by testing accessibility.
7459      $forums = forum_get_forums_user_posted_in($user, array_keys($return->courses), $discussionsonly);
7460  
7461      // Will be used to build the where conditions for the search
7462      $forumsearchwhere = array();
7463      // Will be used to store the where condition params for the search
7464      $forumsearchparams = array();
7465      // Will record forums where the user can freely access everything
7466      $forumsearchfullaccess = array();
7467      // DB caching friendly
7468      $now = round(time(), -2);
7469      // For each course to search we want to find the forums the user has posted in
7470      // and providing the current user can access the forum create a search condition
7471      // for the forum to get the requested users posts.
7472      foreach ($return->courses as $course) {
7473          // Now we need to get the forums
7474          $modinfo = get_fast_modinfo($course);
7475          if (empty($modinfo->instances['forum'])) {
7476              // hmmm, no forums? well at least its easy... skip!
7477              continue;
7478          }
7479          // Iterate
7480          foreach ($modinfo->get_instances_of('forum') as $forumid => $cm) {
7481              if (!$cm->uservisible or !isset($forums[$forumid])) {
7482                  continue;
7483              }
7484              // Get the forum in question
7485              $forum = $forums[$forumid];
7486  
7487              // This is needed for functionality later on in the forum code. It is converted to an object
7488              // because the cm_info is readonly from 2.6. This is a dirty hack because some other parts of the
7489              // code were expecting an writeable object. See {@link forum_print_post()}.
7490              $forum->cm = new stdClass();
7491              foreach ($cm as $key => $value) {
7492                  $forum->cm->$key = $value;
7493              }
7494  
7495              // Check that either the current user can view the forum, or that the
7496              // current user has capabilities over the requested user and the requested
7497              // user can view the discussion
7498              if (!has_capability('mod/forum:viewdiscussion', $cm->context) && !($hascapsonuser && has_capability('mod/forum:viewdiscussion', $cm->context, $user->id))) {
7499                  continue;
7500              }
7501  
7502              // This will contain forum specific where clauses
7503              $forumsearchselect = array();
7504              if (!$iscurrentuser && !$hascapsonuser) {
7505                  // Make sure we check group access
7506                  if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $cm->context)) {
7507                      $groups = $modinfo->get_groups($cm->groupingid);
7508                      $groups[] = -1;
7509                      list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
7510                      $forumsearchparams = array_merge($forumsearchparams, $groupid_params);
7511                      $forumsearchselect[] = "d.groupid $groupid_sql";
7512                  }
7513  
7514                  // hidden timed discussions
7515                  if (!empty($CFG->forum_enabletimedposts) && !has_capability('mod/forum:viewhiddentimedposts', $cm->context)) {
7516                      $forumsearchselect[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
7517                      $forumsearchparams['userid'.$forumid] = $user->id;
7518                      $forumsearchparams['timestart'.$forumid] = $now;
7519                      $forumsearchparams['timeend'.$forumid] = $now;
7520                  }
7521  
7522                  // qanda access
7523                  if ($forum->type == 'qanda' && !has_capability('mod/forum:viewqandawithoutposting', $cm->context)) {
7524                      // We need to check whether the user has posted in the qanda forum.
7525                      $discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $user->id);
7526                      if (!empty($discussionspostedin)) {
7527                          $forumonlydiscussions = array();  // Holds discussion ids for the discussions the user is allowed to see in this forum.
7528                          foreach ($discussionspostedin as $d) {
7529                              $forumonlydiscussions[] = $d->id;
7530                          }
7531                          list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forumonlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
7532                          $forumsearchparams = array_merge($forumsearchparams, $discussionid_params);
7533                          $forumsearchselect[] = "(d.id $discussionid_sql OR p.parent = 0)";
7534                      } else {
7535                          $forumsearchselect[] = "p.parent = 0";
7536                      }
7537  
7538                  }
7539  
7540                  if (count($forumsearchselect) > 0) {
7541                      $forumsearchwhere[] = "(d.forum = :forum{$forumid} AND ".implode(" AND ", $forumsearchselect).")";
7542                      $forumsearchparams['forum'.$forumid] = $forumid;
7543                  } else {
7544                      $forumsearchfullaccess[] = $forumid;
7545                  }
7546              } else {
7547                  // The current user/parent can see all of their own posts
7548                  $forumsearchfullaccess[] = $forumid;
7549              }
7550          }
7551      }
7552  
7553      // If we dont have any search conditions, and we don't have any forums where
7554      // the user has full access then we just return the default.
7555      if (empty($forumsearchwhere) && empty($forumsearchfullaccess)) {
7556          return $return;
7557      }
7558  
7559      // Prepare a where condition for the full access forums.
7560      if (count($forumsearchfullaccess) > 0) {
7561          list($fullidsql, $fullidparams) = $DB->get_in_or_equal($forumsearchfullaccess, SQL_PARAMS_NAMED, 'fula');
7562          $forumsearchparams = array_merge($forumsearchparams, $fullidparams);
7563          $forumsearchwhere[] = "(d.forum $fullidsql)";
7564      }
7565  
7566      // Prepare SQL to both count and search.
7567      // We alias user.id to useridx because we forum_posts already has a userid field and not aliasing this would break
7568      // oracle and mssql.
7569      $userfields = user_picture::fields('u', null, 'useridx');
7570      $countsql = 'SELECT COUNT(*) ';
7571      $selectsql = 'SELECT p.*, d.forum, d.name AS discussionname, '.$userfields.' ';
7572      $wheresql = implode(" OR ", $forumsearchwhere);
7573  
7574      if ($discussionsonly) {
7575          if ($wheresql == '') {
7576              $wheresql = 'p.parent = 0';
7577          } else {
7578              $wheresql = 'p.parent = 0 AND ('.$wheresql.')';
7579          }
7580      }
7581  
7582      $sql = "FROM {forum_posts} p
7583              JOIN {forum_discussions} d ON d.id = p.discussion
7584              JOIN {user} u ON u.id = p.userid
7585             WHERE ($wheresql)
7586               AND p.userid = :userid ";
7587      $orderby = "ORDER BY p.modified DESC";
7588      $forumsearchparams['userid'] = $user->id;
7589  
7590      // Set the total number posts made by the requested user that the current user can see
7591      $return->totalcount = $DB->count_records_sql($countsql.$sql, $forumsearchparams);
7592      // Set the collection of posts that has been requested
7593      $return->posts = $DB->get_records_sql($selectsql.$sql.$orderby, $forumsearchparams, $limitfrom, $limitnum);
7594  
7595      // We need to build an array of forums for which posts will be displayed.
7596      // We do this here to save the caller needing to retrieve them themselves before
7597      // printing these forums posts. Given we have the forums already there is
7598      // practically no overhead here.
7599      foreach ($return->posts as $post) {
7600          if (!array_key_exists($post->forum, $return->forums)) {
7601              $return->forums[$post->forum] = $forums[$post->forum];
7602          }
7603      }
7604  
7605      return $return;
7606  }
7607  
7608  /**
7609   * Set the per-forum maildigest option for the specified user.
7610   *
7611   * @param stdClass $forum The forum to set the option for.
7612   * @param int $maildigest The maildigest option.
7613   * @param stdClass $user The user object. This defaults to the global $USER object.
7614   * @throws invalid_digest_setting thrown if an invalid maildigest option is provided.
7615   */
7616  function forum_set_user_maildigest($forum, $maildigest, $user = null) {
7617      global $DB, $USER;
7618  
7619      if (is_number($forum)) {
7620          $forum = $DB->get_record('forum', array('id' => $forum));
7621      }
7622  
7623      if ($user === null) {
7624          $user = $USER;
7625      }
7626  
7627      $course  = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
7628      $cm      = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
7629      $context = context_module::instance($cm->id);
7630  
7631      // User must be allowed to see this forum.
7632      require_capability('mod/forum:viewdiscussion', $context, $user->id);
7633  
7634      // Validate the maildigest setting.
7635      $digestoptions = forum_get_user_digest_options($user);
7636  
7637      if (!isset($digestoptions[$maildigest])) {
7638          throw new moodle_exception('invaliddigestsetting', 'mod_forum');
7639      }
7640  
7641      // Attempt to retrieve any existing forum digest record.
7642      $subscription = $DB->get_record('forum_digests', array(
7643          'userid' => $user->id,
7644          'forum' => $forum->id,
7645      ));
7646  
7647      // Create or Update the existing maildigest setting.
7648      if ($subscription) {
7649          if ($maildigest == -1) {
7650              $DB->delete_records('forum_digests', array('forum' => $forum->id, 'userid' => $user->id));
7651          } else if ($maildigest !== $subscription->maildigest) {
7652              // Only update the maildigest setting if it's changed.
7653  
7654              $subscription->maildigest = $maildigest;
7655              $DB->update_record('forum_digests', $subscription);
7656          }
7657      } else {
7658          if ($maildigest != -1) {
7659              // Only insert the maildigest setting if it's non-default.
7660  
7661              $subscription = new stdClass();
7662              $subscription->forum = $forum->id;
7663              $subscription->userid = $user->id;
7664              $subscription->maildigest = $maildigest;
7665              $subscription->id = $DB->insert_record('forum_digests', $subscription);
7666          }
7667      }
7668  }
7669  
7670  /**
7671   * Determine the maildigest setting for the specified user against the
7672   * specified forum.
7673   *
7674   * @param Array $digests An array of forums and user digest settings.
7675   * @param stdClass $user The user object containing the id and maildigest default.
7676   * @param int $forumid The ID of the forum to check.
7677   * @return int The calculated maildigest setting for this user and forum.
7678   */
7679  function forum_get_user_maildigest_bulk($digests, $user, $forumid) {
7680      if (isset($digests[$forumid]) && isset($digests[$forumid][$user->id])) {
7681          $maildigest = $digests[$forumid][$user->id];
7682          if ($maildigest === -1) {
7683              $maildigest = $user->maildigest;
7684          }
7685      } else {
7686          $maildigest = $user->maildigest;
7687      }
7688      return $maildigest;
7689  }
7690  
7691  /**
7692   * Retrieve the list of available user digest options.
7693   *
7694   * @param stdClass $user The user object. This defaults to the global $USER object.
7695   * @return array The mapping of values to digest options.
7696   */
7697  function forum_get_user_digest_options($user = null) {
7698      global $USER;
7699  
7700      // Revert to the global user object.
7701      if ($user === null) {
7702          $user = $USER;
7703      }
7704  
7705      $digestoptions = array();
7706      $digestoptions['0']  = get_string('emaildigestoffshort', 'mod_forum');
7707      $digestoptions['1']  = get_string('emaildigestcompleteshort', 'mod_forum');
7708      $digestoptions['2']  = get_string('emaildigestsubjectsshort', 'mod_forum');
7709  
7710      // We need to add the default digest option at the end - it relies on
7711      // the contents of the existing values.
7712      $digestoptions['-1'] = get_string('emaildigestdefault', 'mod_forum',
7713              $digestoptions[$user->maildigest]);
7714  
7715      // Resort the options to be in a sensible order.
7716      ksort($digestoptions);
7717  
7718      return $digestoptions;
7719  }
7720  
7721  /**
7722   * Determine the current context if one was not already specified.
7723   *
7724   * If a context of type context_module is specified, it is immediately
7725   * returned and not checked.
7726   *
7727   * @param int $forumid The ID of the forum
7728   * @param context_module $context The current context.
7729   * @return context_module The context determined
7730   */
7731  function forum_get_context($forumid, $context = null) {
7732      global $PAGE;
7733  
7734      if (!$context || !($context instanceof context_module)) {
7735          // Find out forum context. First try to take current page context to save on DB query.
7736          if ($PAGE->cm && $PAGE->cm->modname === 'forum' && $PAGE->cm->instance == $forumid
7737                  && $PAGE->context->contextlevel == CONTEXT_MODULE && $PAGE->context->instanceid == $PAGE->cm->id) {
7738              $context = $PAGE->context;
7739          } else {
7740              $cm = get_coursemodule_from_instance('forum', $forumid);
7741              $context = \context_module::instance($cm->id);
7742          }
7743      }
7744  
7745      return $context;
7746  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1