[ Index ]

PHP Cross Reference of Unnamed Project

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1