Search moodle.org's
Developer Documentation


  • Bug fixes for general core bugs in 2.8.x ended 9 November 2015 (12 months).
  • Bug fixes for security issues in 2.8.x ended 9 May 2016 (18 months).
  • minimum PHP 5.4.4 (always use latest PHP 5.4.x or 5.5.x on Windows - http://windows.php.net/download/), PHP 7 is NOT supported
  • /mod/forum/ -> lib.php (source)

    Differences Between: [Versions 28 and 29] [Versions 28 and 30] [Versions 28 and 31] [Versions 28 and 32] [Versions 28 and 33] [Versions 28 and 34] [Versions 28 and 35] [Versions 28 and 36] [Versions 28 and 37]

       1  <?php
       2  // This file is part of Moodle - http://moodle.org/
       3  //
       4  // Moodle is free software: you can redistribute it and/or modify
       5  // it under the terms of the GNU General Public License as published by
       6  // the Free Software Foundation, either version 3 of the License, or
       7  // (at your option) any later version.
       8  //
       9  // Moodle is distributed in the hope that it will be useful,
      10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12  // GNU General Public License for more details.
      13  //
      14  // You should have received a copy of the GNU General Public License
      15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
      16  
      17  /**
      18   * @package   mod_forum
      19   * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
      20   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      21   */
      22  
      23  defined('MOODLE_INTERNAL') || die();
      24  
      25  /** Include required files */
      26  require_once (__DIR__ . '/deprecatedlib.php');
      27  require_once($CFG->libdir.'/filelib.php');
      28  require_once($CFG->libdir.'/eventslib.php');
      29  
      30  /// CONSTANTS ///////////////////////////////////////////////////////////
      31  
      32  define('FORUM_MODE_FLATOLDEST', 1);
      33  define('FORUM_MODE_FLATNEWEST', -1);
      34  define('FORUM_MODE_THREADED', 2);
      35  define('FORUM_MODE_NESTED', 3);
      36  
      37  define('FORUM_CHOOSESUBSCRIBE', 0);
      38  define('FORUM_FORCESUBSCRIBE', 1);
      39  define('FORUM_INITIALSUBSCRIBE', 2);
      40  define('FORUM_DISALLOWSUBSCRIBE',3);
      41  
      42  /**
      43   * FORUM_TRACKING_OFF - Tracking is not available for this forum.
      44   */
      45  define('FORUM_TRACKING_OFF', 0);
      46  
      47  /**
      48   * FORUM_TRACKING_OPTIONAL - Tracking is based on user preference.
      49   */
      50  define('FORUM_TRACKING_OPTIONAL', 1);
      51  
      52  /**
      53   * FORUM_TRACKING_FORCED - Tracking is on, regardless of user setting.
      54   * Treated as FORUM_TRACKING_OPTIONAL if $CFG->forum_allowforcedreadtracking is off.
      55   */
      56  define('FORUM_TRACKING_FORCED', 2);
      57  
      58  define('FORUM_MAILED_PENDING', 0);
      59  define('FORUM_MAILED_SUCCESS', 1);
      60  define('FORUM_MAILED_ERROR', 2);
      61  
      62  if (!defined('FORUM_CRON_USER_CACHE')) {
      63      /** Defines how many full user records are cached in forum cron. */
      64      define('FORUM_CRON_USER_CACHE', 5000);
      65  }
      66  
      67  /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
      68  
      69  /**
      70   * Given an object containing all the necessary data,
      71   * (defined by the form in mod_form.php) this function
      72   * will create a new instance and return the id number
      73   * of the new instance.
      74   *
      75   * @param stdClass $forum add forum instance
      76   * @param mod_forum_mod_form $mform
      77   * @return int intance id
      78   */
      79  function forum_add_instance($forum, $mform = null) {
      80      global $CFG, $DB;
      81  
      82      $forum->timemodified = time();
      83  
      84      if (empty($forum->assessed)) {
      85          $forum->assessed = 0;
      86      }
      87  
      88      if (empty($forum->ratingtime) or empty($forum->assessed)) {
      89          $forum->assesstimestart  = 0;
      90          $forum->assesstimefinish = 0;
      91      }
      92  
      93      $forum->id = $DB->insert_record('forum', $forum);
      94      $modcontext = context_module::instance($forum->coursemodule);
      95  
      96      if ($forum->type == 'single') {  // Create related discussion.
      97          $discussion = new stdClass();
      98          $discussion->course        = $forum->course;
      99          $discussion->forum         = $forum->id;
     100          $discussion->name          = $forum->name;
     101          $discussion->assessed      = $forum->assessed;
     102          $discussion->message       = $forum->intro;
     103          $discussion->messageformat = $forum->introformat;
     104          $discussion->messagetrust  = trusttext_trusted(context_course::instance($forum->course));
     105          $discussion->mailnow       = false;
     106          $discussion->groupid       = -1;
     107  
     108          $message = '';
     109  
     110          $discussion->id = forum_add_discussion($discussion, null, $message);
     111  
     112          if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
     113              // Ugly hack - we need to copy the files somehow.
     114              $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
     115              $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
     116  
     117              $options = array('subdirs'=>true); // Use the same options as intro field!
     118              $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
     119              $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
     120          }
     121      }
     122  
     123      forum_grade_item_update($forum);
     124  
     125      return $forum->id;
     126  }
     127  
     128  /**
     129   * Handle changes following the creation of a forum instance.
     130   * This function is typically called by the course_module_created observer.
     131   *
     132   * @param object $context the forum context
     133   * @param stdClass $forum The forum object
     134   * @return void
     135   */
     136  function forum_instance_created($context, $forum) {
     137      if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
     138          $users = \mod_forum\subscriptions::get_potential_subscribers($context, 0, 'u.id, u.email');
     139          foreach ($users as $user) {
     140              \mod_forum\subscriptions::subscribe_user($user->id, $forum, $context);
     141          }
     142      }
     143  }
     144  
     145  /**
     146   * Given an object containing all the necessary data,
     147   * (defined by the form in mod_form.php) this function
     148   * will update an existing instance with new data.
     149   *
     150   * @global object
     151   * @param object $forum forum instance (with magic quotes)
     152   * @return bool success
     153   */
     154  function forum_update_instance($forum, $mform) {
     155      global $DB, $OUTPUT, $USER;
     156  
     157      $forum->timemodified = time();
     158      $forum->id           = $forum->instance;
     159  
     160      if (empty($forum->assessed)) {
     161          $forum->assessed = 0;
     162      }
     163  
     164      if (empty($forum->ratingtime) or empty($forum->assessed)) {
     165          $forum->assesstimestart  = 0;
     166          $forum->assesstimefinish = 0;
     167      }
     168  
     169      $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
     170  
     171      // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
     172      // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
     173      // for count and sum aggregation types the grade we check to make sure they do not exceed the scale (i.e. max score) when calculating the grade
     174      if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
     175          forum_update_grades($forum); // recalculate grades for the forum
     176      }
     177  
     178      if ($forum->type == 'single') {  // Update related discussion and post.
     179          $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
     180          if (!empty($discussions)) {
     181              if (count($discussions) > 1) {
     182                  echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
     183              }
     184              $discussion = array_pop($discussions);
     185          } else {
     186              // try to recover by creating initial discussion - MDL-16262
     187              $discussion = new stdClass();
     188              $discussion->course          = $forum->course;
     189              $discussion->forum           = $forum->id;
     190              $discussion->name            = $forum->name;
     191              $discussion->assessed        = $forum->assessed;
     192              $discussion->message         = $forum->intro;
     193              $discussion->messageformat   = $forum->introformat;
     194              $discussion->messagetrust    = true;
     195              $discussion->mailnow         = false;
     196              $discussion->groupid         = -1;
     197  
     198              $message = '';
     199  
     200              forum_add_discussion($discussion, null, $message);
     201  
     202              if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
     203                  print_error('cannotadd', 'forum');
     204              }
     205          }
     206          if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
     207              print_error('cannotfindfirstpost', 'forum');
     208          }
     209  
     210          $cm         = get_coursemodule_from_instance('forum', $forum->id);
     211          $modcontext = context_module::instance($cm->id, MUST_EXIST);
     212  
     213          $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
     214          $post->subject       = $forum->name;
     215          $post->message       = $forum->intro;
     216          $post->messageformat = $forum->introformat;
     217          $post->messagetrust  = trusttext_trusted($modcontext);
     218          $post->modified      = $forum->timemodified;
     219          $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities.
     220  
     221          if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
     222              // Ugly hack - we need to copy the files somehow.
     223              $options = array('subdirs'=>true); // Use the same options as intro field!
     224              $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
     225          }
     226  
     227          $DB->update_record('forum_posts', $post);
     228          $discussion->name = $forum->name;
     229          $DB->update_record('forum_discussions', $discussion);
     230      }
     231  
     232      $DB->update_record('forum', $forum);
     233  
     234      $modcontext = context_module::instance($forum->coursemodule);
     235      if (($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) && ($oldforum->forcesubscribe <> $forum->forcesubscribe)) {
     236          $users = \mod_forum\subscriptions::get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
     237          foreach ($users as $user) {
     238              \mod_forum\subscriptions::subscribe_user($user->id, $forum, $modcontext);
     239          }
     240      }
     241  
     242      forum_grade_item_update($forum);
     243  
     244      return true;
     245  }
     246  
     247  
     248  /**
     249   * Given an ID of an instance of this module,
     250   * this function will permanently delete the instance
     251   * and any data that depends on it.
     252   *
     253   * @global object
     254   * @param int $id forum instance id
     255   * @return bool success
     256   */
     257  function forum_delete_instance($id) {
     258      global $DB;
     259  
     260      if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
     261          return false;
     262      }
     263      if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
     264          return false;
     265      }
     266      if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
     267          return false;
     268      }
     269  
     270      $context = context_module::instance($cm->id);
     271  
     272      // now get rid of all files
     273      $fs = get_file_storage();
     274      $fs->delete_area_files($context->id);
     275  
     276      $result = true;
     277  
     278      // Delete digest and subscription preferences.
     279      $DB->delete_records('forum_digests', array('forum' => $forum->id));
     280      $DB->delete_records('forum_subscriptions', array('forum'=>$forum->id));
     281      $DB->delete_records('forum_discussion_subs', array('forum' => $forum->id));
     282  
     283      if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
     284          foreach ($discussions as $discussion) {
     285              if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
     286                  $result = false;
     287              }
     288          }
     289      }
     290  
     291      forum_tp_delete_read_records(-1, -1, -1, $forum->id);
     292  
     293      if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
     294          $result = false;
     295      }
     296  
     297      forum_grade_item_delete($forum);
     298  
     299      return $result;
     300  }
     301  
     302  
     303  /**
     304   * Indicates API features that the forum supports.
     305   *
     306   * @uses FEATURE_GROUPS
     307   * @uses FEATURE_GROUPINGS
     308   * @uses FEATURE_MOD_INTRO
     309   * @uses FEATURE_COMPLETION_TRACKS_VIEWS
     310   * @uses FEATURE_COMPLETION_HAS_RULES
     311   * @uses FEATURE_GRADE_HAS_GRADE
     312   * @uses FEATURE_GRADE_OUTCOMES
     313   * @param string $feature
     314   * @return mixed True if yes (some features may use other values)
     315   */
     316  function forum_supports($feature) {
     317      switch($feature) {
     318          case FEATURE_GROUPS:                  return true;
     319          case FEATURE_GROUPINGS:               return true;
     320          case FEATURE_MOD_INTRO:               return true;
     321          case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
     322          case FEATURE_COMPLETION_HAS_RULES:    return true;
     323          case FEATURE_GRADE_HAS_GRADE:         return true;
     324          case FEATURE_GRADE_OUTCOMES:          return true;
     325          case FEATURE_RATE:                    return true;
     326          case FEATURE_BACKUP_MOODLE2:          return true;
     327          case FEATURE_SHOW_DESCRIPTION:        return true;
     328          case FEATURE_PLAGIARISM:              return true;
     329  
     330          default: return null;
     331      }
     332  }
     333  
     334  
     335  /**
     336   * Obtains the automatic completion state for this forum based on any conditions
     337   * in forum settings.
     338   *
     339   * @global object
     340   * @global object
     341   * @param object $course Course
     342   * @param object $cm Course-module
     343   * @param int $userid User ID
     344   * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
     345   * @return bool True if completed, false if not. (If no conditions, then return
     346   *   value depends on comparison type)
     347   */
     348  function forum_get_completion_state($course,$cm,$userid,$type) {
     349      global $CFG,$DB;
     350  
     351      // Get forum details
     352      if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
     353          throw new Exception("Can't find forum {$cm->instance}");
     354      }
     355  
     356      $result=$type; // Default return value
     357  
     358      $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
     359      $postcountsql="
     360  SELECT
     361      COUNT(1)
     362  FROM
     363      {forum_posts} fp
     364      INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
     365  WHERE
     366      fp.userid=:userid AND fd.forum=:forumid";
     367  
     368      if ($forum->completiondiscussions) {
     369          $value = $forum->completiondiscussions <=
     370                   $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
     371          if ($type == COMPLETION_AND) {
     372              $result = $result && $value;
     373          } else {
     374              $result = $result || $value;
     375          }
     376      }
     377      if ($forum->completionreplies) {
     378          $value = $forum->completionreplies <=
     379                   $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
     380          if ($type==COMPLETION_AND) {
     381              $result = $result && $value;
     382          } else {
     383              $result = $result || $value;
     384          }
     385      }
     386      if ($forum->completionposts) {
     387          $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
     388          if ($type == COMPLETION_AND) {
     389              $result = $result && $value;
     390          } else {
     391              $result = $result || $value;
     392          }
     393      }
     394  
     395      return $result;
     396  }
     397  
     398  /**
     399   * Create a message-id string to use in the custom headers of forum notification emails
     400   *
     401   * message-id is used by email clients to identify emails and to nest conversations
     402   *
     403   * @param int $postid The ID of the forum post we are notifying the user about
     404   * @param int $usertoid The ID of the user being notified
     405   * @param string $hostname The server's hostname
     406   * @return string A unique message-id
     407   */
     408  function forum_get_email_message_id($postid, $usertoid, $hostname) {
     409      return '<'.hash('sha256',$postid.'to'.$usertoid).'@'.$hostname.'>';
     410  }
     411  
     412  /**
     413   * Removes properties from user record that are not necessary
     414   * for sending post notifications.
     415   * @param stdClass $user
     416   * @return void, $user parameter is modified
     417   */
     418  function forum_cron_minimise_user_record(stdClass $user) {
     419  
     420      // We store large amount of users in one huge array,
     421      // make sure we do not store info there we do not actually need
     422      // in mail generation code or messaging.
     423  
     424      unset($user->institution);
     425      unset($user->department);
     426      unset($user->address);
     427      unset($user->city);
     428      unset($user->url);
     429      unset($user->currentlogin);
     430      unset($user->description);
     431      unset($user->descriptionformat);
     432  }
     433  
     434  /**
     435   * Function to be run periodically according to the scheduled task.
     436   *
     437   * Finds all posts that have yet to be mailed out, and mails them
     438   * out to all subscribers as well as other maintance tasks.
     439   *
     440   * NOTE: Since 2.7.2 this function is run by scheduled task rather
     441   * than standard cron.
     442   *
     443   * @todo MDL-44734 The function will be split up into seperate tasks.
     444   */
     445  function forum_cron() {
     446      global $CFG, $USER, $DB;
     447  
     448      $site = get_site();
     449  
     450      // All users that are subscribed to any post that needs sending,
     451      // please increase $CFG->extramemorylimit on large sites that
     452      // send notifications to a large number of users.
     453      $users = array();
     454      $userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!!
     455  
     456      // Status arrays.
     457      $mailcount  = array();
     458      $errorcount = array();
     459  
     460      // caches
     461      $discussions        = array();
     462      $forums             = array();
     463      $courses            = array();
     464      $coursemodules      = array();
     465      $subscribedusers    = array();
     466      $messageinboundhandlers = array();
     467  
     468      // Posts older than 2 days will not be mailed.  This is to avoid the problem where
     469      // cron has not been running for a long time, and then suddenly people are flooded
     470      // with mail from the past few weeks or months
     471      $timenow   = time();
     472      $endtime   = $timenow - $CFG->maxeditingtime;
     473      $starttime = $endtime - 48 * 3600;   // Two days earlier
     474  
     475      // Get the list of forum subscriptions for per-user per-forum maildigest settings.
     476      $digestsset = $DB->get_recordset('forum_digests', null, '', 'id, userid, forum, maildigest');
     477      $digests = array();
     478      foreach ($digestsset as $thisrow) {
     479          if (!isset($digests[$thisrow->forum])) {
     480              $digests[$thisrow->forum] = array();
     481          }
     482          $digests[$thisrow->forum][$thisrow->userid] = $thisrow->maildigest;
     483      }
     484      $digestsset->close();
     485  
     486      // Create the generic messageinboundgenerator.
     487      $messageinboundgenerator = new \core\message\inbound\address_manager();
     488      $messageinboundgenerator->set_handler('\mod_forum\message\inbound\reply_handler');
     489  
     490      if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
     491          // Mark them all now as being mailed.  It's unlikely but possible there
     492          // might be an error later so that a post is NOT actually mailed out,
     493          // but since mail isn't crucial, we can accept this risk.  Doing it now
     494          // prevents the risk of duplicated mails, which is a worse problem.
     495  
     496          if (!forum_mark_old_posts_as_mailed($endtime)) {
     497              mtrace('Errors occurred while trying to mark some posts as being mailed.');
     498              return false;  // Don't continue trying to mail them, in case we are in a cron loop
     499          }
     500  
     501          // checking post validity, and adding users to loop through later
     502          foreach ($posts as $pid => $post) {
     503  
     504              $discussionid = $post->discussion;
     505              if (!isset($discussions[$discussionid])) {
     506                  if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
     507                      $discussions[$discussionid] = $discussion;
     508                      \mod_forum\subscriptions::fill_subscription_cache($discussion->forum);
     509                      \mod_forum\subscriptions::fill_discussion_subscription_cache($discussion->forum);
     510  
     511                  } else {
     512                      mtrace('Could not find discussion ' . $discussionid);
     513                      unset($posts[$pid]);
     514                      continue;
     515                  }
     516              }
     517              $forumid = $discussions[$discussionid]->forum;
     518              if (!isset($forums[$forumid])) {
     519                  if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
     520                      $forums[$forumid] = $forum;
     521                  } else {
     522                      mtrace('Could not find forum '.$forumid);
     523                      unset($posts[$pid]);
     524                      continue;
     525                  }
     526              }
     527              $courseid = $forums[$forumid]->course;
     528              if (!isset($courses[$courseid])) {
     529                  if ($course = $DB->get_record('course', array('id' => $courseid))) {
     530                      $courses[$courseid] = $course;
     531                  } else {
     532                      mtrace('Could not find course '.$courseid);
     533                      unset($posts[$pid]);
     534                      continue;
     535                  }
     536              }
     537              if (!isset($coursemodules[$forumid])) {
     538                  if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
     539                      $coursemodules[$forumid] = $cm;
     540                  } else {
     541                      mtrace('Could not find course module for forum '.$forumid);
     542                      unset($posts[$pid]);
     543                      continue;
     544                  }
     545              }
     546  
     547              // Save the Inbound Message datakey here to reduce DB queries later.
     548              $messageinboundgenerator->set_data($pid);
     549              $messageinboundhandlers[$pid] = $messageinboundgenerator->fetch_data_key();
     550  
     551              // Caching subscribed users of each forum.
     552              if (!isset($subscribedusers[$forumid])) {
     553                  $modcontext = context_module::instance($coursemodules[$forumid]->id);
     554                  if ($subusers = \mod_forum\subscriptions::fetch_subscribed_users($forums[$forumid], 0, $modcontext, 'u.*', true)) {
     555  
     556                      foreach ($subusers as $postuser) {
     557                          // this user is subscribed to this forum
     558                          $subscribedusers[$forumid][$postuser->id] = $postuser->id;
     559                          $userscount++;
     560                          if ($userscount > FORUM_CRON_USER_CACHE) {
     561                              // Store minimal user info.
     562                              $minuser = new stdClass();
     563                              $minuser->id = $postuser->id;
     564                              $users[$postuser->id] = $minuser;
     565                          } else {
     566                              // Cache full user record.
     567                              forum_cron_minimise_user_record($postuser);
     568                              $users[$postuser->id] = $postuser;
     569                          }
     570                      }
     571                      // Release memory.
     572                      unset($subusers);
     573                      unset($postuser);
     574                  }
     575              }
     576              $mailcount[$pid] = 0;
     577              $errorcount[$pid] = 0;
     578          }
     579      }
     580  
     581      if ($users && $posts) {
     582  
     583          $urlinfo = parse_url($CFG->wwwroot);
     584          $hostname = $urlinfo['host'];
     585  
     586          foreach ($users as $userto) {
     587              // Terminate if processing of any account takes longer than 2 minutes.
     588              core_php_time_limit::raise(120);
     589  
     590              mtrace('Processing user ' . $userto->id);
     591  
     592              // Init user caches - we keep the cache for one cycle only, otherwise it could consume too much memory.
     593              if (isset($userto->username)) {
     594                  $userto = clone($userto);
     595              } else {
     596                  $userto = $DB->get_record('user', array('id' => $userto->id));
     597                  forum_cron_minimise_user_record($userto);
     598              }
     599              $userto->viewfullnames = array();
     600              $userto->canpost       = array();
     601              $userto->markposts     = array();
     602  
     603              // Setup this user so that the capabilities are cached, and environment matches receiving user.
     604              cron_setup_user($userto);
     605  
     606              // Reset the caches.
     607              foreach ($coursemodules as $forumid => $unused) {
     608                  $coursemodules[$forumid]->cache       = new stdClass();
     609                  $coursemodules[$forumid]->cache->caps = array();
     610                  unset($coursemodules[$forumid]->uservisible);
     611              }
     612  
     613              foreach ($posts as $pid => $post) {
     614                  $discussion = $discussions[$post->discussion];
     615                  $forum      = $forums[$discussion->forum];
     616                  $course     = $courses[$forum->course];
     617                  $cm         =& $coursemodules[$forum->id];
     618  
     619                  // Do some checks to see if we can bail out now.
     620  
     621                  // Only active enrolled users are in the list of subscribers.
     622                  // This does not necessarily mean that the user is subscribed to the forum or to the discussion though.
     623                  if (!isset($subscribedusers[$forum->id][$userto->id])) {
     624                      // The user does not subscribe to this forum.
     625                      continue;
     626                  }
     627  
     628                  if (!\mod_forum\subscriptions::is_subscribed($userto->id, $forum, $post->discussion, $coursemodules[$forum->id])) {
     629                      // The user does not subscribe to this forum, or to this specific discussion.
     630                      continue;
     631                  }
     632  
     633                  if ($subscriptiontime = \mod_forum\subscriptions::fetch_discussion_subscription($forum->id, $userto->id)) {
     634                      // Skip posts if the user subscribed to the discussion after it was created.
     635                      if (isset($subscriptiontime[$post->discussion]) && ($subscriptiontime[$post->discussion] > $post->created)) {
     636                          continue;
     637                      }
     638                  }
     639  
     640                  // Don't send email if the forum is Q&A and the user has not posted.
     641                  // Initial topics are still mailed.
     642                  if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
     643                      mtrace('Did not email ' . $userto->id.' because user has not posted in discussion');
     644                      continue;
     645                  }
     646  
     647                  // Get info about the sending user.
     648                  if (array_key_exists($post->userid, $users)) {
     649                      // We might know the user already.
     650                      $userfrom = $users[$post->userid];
     651                      if (!isset($userfrom->idnumber)) {
     652                          // Minimalised user info, fetch full record.
     653                          $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
     654                          forum_cron_minimise_user_record($userfrom);
     655                      }
     656  
     657                  } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
     658                      forum_cron_minimise_user_record($userfrom);
     659                      // Fetch only once if possible, we can add it to user list, it will be skipped anyway.
     660                      if ($userscount <= FORUM_CRON_USER_CACHE) {
     661                          $userscount++;
     662                          $users[$userfrom->id] = $userfrom;
     663                      }
     664                  } else {
     665                      mtrace('Could not find user ' . $post->userid . ', author of post ' . $post->id . '. Unable to send message.');
     666                      continue;
     667                  }
     668  
     669                  // Note: If we want to check that userto and userfrom are not the same person this is probably the spot to do it.
     670  
     671                  // Setup global $COURSE properly - needed for roles and languages.
     672                  cron_setup_user($userto, $course);
     673  
     674                  // Fill caches.
     675                  if (!isset($userto->viewfullnames[$forum->id])) {
     676                      $modcontext = context_module::instance($cm->id);
     677                      $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
     678                  }
     679                  if (!isset($userto->canpost[$discussion->id])) {
     680                      $modcontext = context_module::instance($cm->id);
     681                      $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
     682                  }
     683                  if (!isset($userfrom->groups[$forum->id])) {
     684                      if (!isset($userfrom->groups)) {
     685                          $userfrom->groups = array();
     686                          if (isset($users[$userfrom->id])) {
     687                              $users[$userfrom->id]->groups = array();
     688                          }
     689                      }
     690                      $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
     691                      if (isset($users[$userfrom->id])) {
     692                          $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
     693                      }
     694                  }
     695  
     696                  // Make sure groups allow this user to see this email.
     697                  if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {
     698                      // Groups are being used.
     699                      if (!groups_group_exists($discussion->groupid)) {
     700                          // Can't find group - be safe and don't this message.
     701                          continue;
     702                      }
     703  
     704                      if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
     705                          // Do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS.
     706                          continue;
     707                      }
     708                  }
     709  
     710                  // Make sure we're allowed to see the post.
     711                  if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm)) {
     712                      mtrace('User ' . $userto->id .' can not see ' . $post->id . '. Not sending message.');
     713                      continue;
     714                  }
     715  
     716                  // OK so we need to send the email.
     717  
     718                  // Does the user want this post in a digest?  If so postpone it for now.
     719                  $maildigest = forum_get_user_maildigest_bulk($digests, $userto, $forum->id);
     720  
     721                  if ($maildigest > 0) {
     722                      // This user wants the mails to be in digest form.
     723                      $queue = new stdClass();
     724                      $queue->userid       = $userto->id;
     725                      $queue->discussionid = $discussion->id;
     726                      $queue->postid       = $post->id;
     727                      $queue->timemodified = $post->created;
     728                      $DB->insert_record('forum_queue', $queue);
     729                      continue;
     730                  }
     731  
     732                  // Prepare to actually send the post now, and build up the content.
     733  
     734                  $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
     735  
     736                  $userfrom->customheaders = array (
     737                      // Headers to make emails easier to track.
     738                      'List-Id: "'        . $cleanforumname . '" <moodleforum' . $forum->id . '@' . $hostname.'>',
     739                      'List-Help: '       . $CFG->wwwroot . '/mod/forum/view.php?f=' . $forum->id,
     740                      'Message-ID: '      . forum_get_email_message_id($post->id, $userto->id, $hostname),
     741                      'X-Course-Id: '     . $course->id,
     742                      'X-Course-Name: '   . format_string($course->fullname, true),
     743  
     744                      // Headers to help prevent auto-responders.
     745                      'Precedence: Bulk',
     746                      'X-Auto-Response-Suppress: All',
     747                      'Auto-Submitted: auto-generated',
     748                  );
     749  
     750                  if ($post->parent) {
     751                      // This post is a reply, so add headers for threading (see MDL-22551).
     752                      $userfrom->customheaders[] = 'In-Reply-To: ' . forum_get_email_message_id($post->parent, $userto->id, $hostname);
     753                      $userfrom->customheaders[] = 'References: '  . forum_get_email_message_id($post->parent, $userto->id, $hostname);
     754                  }
     755  
     756                  $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
     757  
     758                  // Generate a reply-to address from using the Inbound Message handler.
     759                  $replyaddress = null;
     760                  if ($userto->canpost[$discussion->id] && array_key_exists($post->id, $messageinboundhandlers)) {
     761                      $messageinboundgenerator->set_data($post->id, $messageinboundhandlers[$post->id]);
     762                      $replyaddress = $messageinboundgenerator->generate($userto->id);
     763                  }
     764  
     765                  $a = new stdClass();
     766                  $a->courseshortname = $shortname;
     767                  $a->forumname = $cleanforumname;
     768                  $a->subject = format_string($post->subject, true);
     769                  $postsubject = html_to_text(get_string('postmailsubject', 'forum', $a), 0);
     770                  $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false,
     771                          $replyaddress);
     772                  $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
     773                          $replyaddress);
     774  
     775                  // Send the post now!
     776                  mtrace('Sending ', '');
     777  
     778                  $eventdata = new stdClass();
     779                  $eventdata->component           = 'mod_forum';
     780                  $eventdata->name                = 'posts';
     781                  $eventdata->userfrom            = $userfrom;
     782                  $eventdata->userto              = $userto;
     783                  $eventdata->subject             = $postsubject;
     784                  $eventdata->fullmessage         = $posttext;
     785                  $eventdata->fullmessageformat   = FORMAT_PLAIN;
     786                  $eventdata->fullmessagehtml     = $posthtml;
     787                  $eventdata->notification        = 1;
     788                  $eventdata->replyto             = $replyaddress;
     789  
     790                  // If forum_replytouser is not set then send mail using the noreplyaddress.
     791                  if (empty($CFG->forum_replytouser)) {
     792                      // Clone userfrom as it is referenced by $users.
     793                      $cloneduserfrom = clone($userfrom);
     794                      $cloneduserfrom->email = $CFG->noreplyaddress;
     795                      $eventdata->userfrom = $cloneduserfrom;
     796                  }
     797  
     798                  $smallmessagestrings = new stdClass();
     799                  $smallmessagestrings->user          = fullname($userfrom);
     800                  $smallmessagestrings->forumname     = "$shortname: " . format_string($forum->name, true) . ": " . $discussion->name;
     801                  $smallmessagestrings->message       = $post->message;
     802  
     803                  // Make sure strings are in message recipients language.
     804                  $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
     805  
     806                  $contexturl = new moodle_url('/mod/forum/discuss.php', array('d' => $discussion->id), 'p' . $post->id);
     807                  $eventdata->contexturl = $contexturl->out();
     808                  $eventdata->contexturlname = $discussion->name;
     809  
     810                  $mailresult = message_send($eventdata);
     811                  if (!$mailresult) {
     812                      mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
     813                              " ($userto->email) .. not trying again.");
     814                      $errorcount[$post->id]++;
     815                  } else {
     816                      $mailcount[$post->id]++;
     817  
     818                      // Mark post as read if forum_usermarksread is set off.
     819                      if (!$CFG->forum_usermarksread) {
     820                          $userto->markposts[$post->id] = $post->id;
     821                      }
     822                  }
     823  
     824                  mtrace('post ' . $post->id . ': ' . $post->subject);
     825              }
     826  
     827              // Mark processed posts as read.
     828              forum_tp_mark_posts_read($userto, $userto->markposts);
     829              unset($userto);
     830          }
     831      }
     832  
     833      if ($posts) {
     834          foreach ($posts as $post) {
     835              mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
     836              if ($errorcount[$post->id]) {
     837                  $DB->set_field('forum_posts', 'mailed', FORUM_MAILED_ERROR, array('id' => $post->id));
     838              }
     839          }
     840      }
     841  
     842      // release some memory
     843      unset($subscribedusers);
     844      unset($mailcount);
     845      unset($errorcount);
     846  
     847      cron_setup_user();
     848  
     849      $sitetimezone = $CFG->timezone;
     850  
     851      // Now see if there are any digest mails waiting to be sent, and if we should send them
     852  
     853      mtrace('Starting digest processing...');
     854  
     855      core_php_time_limit::raise(300); // terminate if not able to fetch all digests in 5 minutes
     856  
     857      if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
     858          set_config('digestmailtimelast', 0);
     859      }
     860  
     861      $timenow = time();
     862      $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
     863  
     864      // Delete any really old ones (normally there shouldn't be any)
     865      $weekago = $timenow - (7 * 24 * 3600);
     866      $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
     867      mtrace ('Cleaned old digest records');
     868  
     869      if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
     870  
     871          mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
     872  
     873          $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
     874  
     875          if ($digestposts_rs->valid()) {
     876  
     877              // We have work to do
     878              $usermailcount = 0;
     879  
     880              //caches - reuse the those filled before too
     881              $discussionposts = array();
     882              $userdiscussions = array();
     883  
     884              foreach ($digestposts_rs as $digestpost) {
     885                  if (!isset($posts[$digestpost->postid])) {
     886                      if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
     887                          $posts[$digestpost->postid] = $post;
     888                      } else {
     889                          continue;
     890                      }
     891                  }
     892                  $discussionid = $digestpost->discussionid;
     893                  if (!isset($discussions[$discussionid])) {
     894                      if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
     895                          $discussions[$discussionid] = $discussion;
     896                      } else {
     897                          continue;
     898                      }
     899                  }
     900                  $forumid = $discussions[$discussionid]->forum;
     901                  if (!isset($forums[$forumid])) {
     902                      if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
     903                          $forums[$forumid] = $forum;
     904                      } else {
     905                          continue;
     906                      }
     907                  }
     908  
     909                  $courseid = $forums[$forumid]->course;
     910                  if (!isset($courses[$courseid])) {
     911                      if ($course = $DB->get_record('course', array('id' => $courseid))) {
     912                          $courses[$courseid] = $course;
     913                      } else {
     914                          continue;
     915                      }
     916                  }
     917  
     918                  if (!isset($coursemodules[$forumid])) {
     919                      if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
     920                          $coursemodules[$forumid] = $cm;
     921                      } else {
     922                          continue;
     923                      }
     924                  }
     925                  $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
     926                  $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
     927              }
     928              $digestposts_rs->close(); /// Finished iteration, let's close the resultset
     929  
     930              // Data collected, start sending out emails to each user
     931              foreach ($userdiscussions as $userid => $thesediscussions) {
     932  
     933                  core_php_time_limit::raise(120); // terminate if processing of any account takes longer than 2 minutes
     934  
     935                  cron_setup_user();
     936  
     937                  mtrace(get_string('processingdigest', 'forum', $userid), '... ');
     938  
     939                  // First of all delete all the queue entries for this user
     940                  $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
     941  
     942                  // Init user caches - we keep the cache for one cycle only,
     943                  // otherwise it would unnecessarily consume memory.
     944                  if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
     945                      $userto = clone($users[$userid]);
     946                  } else {
     947                      $userto = $DB->get_record('user', array('id' => $userid));
     948                      forum_cron_minimise_user_record($userto);
     949                  }
     950                  $userto->viewfullnames = array();
     951                  $userto->canpost       = array();
     952                  $userto->markposts     = array();
     953  
     954                  // Override the language and timezone of the "current" user, so that
     955                  // mail is customised for the receiver.
     956                  cron_setup_user($userto);
     957  
     958                  $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
     959  
     960                  $headerdata = new stdClass();
     961                  $headerdata->sitename = format_string($site->fullname, true);
     962                  $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
     963  
     964                  $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
     965                  $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
     966  
     967                  $posthtml = "<head>";
     968  /*                foreach ($CFG->stylesheets as $stylesheet) {
     969                      //TODO: MDL-21120
     970                      $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
     971                  }*/
     972                  $posthtml .= "</head>\n<body id=\"email\">\n";
     973                  $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
     974  
     975                  foreach ($thesediscussions as $discussionid) {
     976  
     977                      core_php_time_limit::raise(120);   // to be reset for each post
     978  
     979                      $discussion = $discussions[$discussionid];
     980                      $forum      = $forums[$discussion->forum];
     981                      $course     = $courses[$forum->course];
     982                      $cm         = $coursemodules[$forum->id];
     983  
     984                      //override language
     985                      cron_setup_user($userto, $course);
     986  
     987                      // Fill caches
     988                      if (!isset($userto->viewfullnames[$forum->id])) {
     989                          $modcontext = context_module::instance($cm->id);
     990                          $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
     991                      }
     992                      if (!isset($userto->canpost[$discussion->id])) {
     993                          $modcontext = context_module::instance($cm->id);
     994                          $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
     995                      }
     996  
     997                      $strforums      = get_string('forums', 'forum');
     998                      $canunsubscribe = ! \mod_forum\subscriptions::is_forcesubscribed($forum);
     999                      $canreply       = $userto->canpost[$discussion->id];
    1000                      $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
    1001  
    1002                      $posttext .= "\n \n";
    1003                      $posttext .= '=====================================================================';
    1004                      $posttext .= "\n \n";
    1005                      $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
    1006                      if ($discussion->name != $forum->name) {
    1007                          $posttext  .= " -> ".format_string($discussion->name,true);
    1008                      }
    1009                      $posttext .= "\n";
    1010                      $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
    1011                      $posttext .= "\n";
    1012  
    1013                      $posthtml .= "<p><font face=\"sans-serif\">".
    1014                      "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
    1015                      "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
    1016                      "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
    1017                      if ($discussion->name == $forum->name) {
    1018                          $posthtml .= "</font></p>";
    1019                      } else {
    1020                          $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
    1021                      }
    1022                      $posthtml .= '<p>';
    1023  
    1024                      $postsarray = $discussionposts[$discussionid];
    1025                      sort($postsarray);
    1026  
    1027                      foreach ($postsarray as $postid) {
    1028                          $post = $posts[$postid];
    1029  
    1030                          if (array_key_exists($post->userid, $users)) { // we might know him/her already
    1031                              $userfrom = $users[$post->userid];
    1032                              if (!isset($userfrom->idnumber)) {
    1033                                  $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
    1034                                  forum_cron_minimise_user_record($userfrom);
    1035                              }
    1036  
    1037                          } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
    1038                              forum_cron_minimise_user_record($userfrom);
    1039                              if ($userscount <= FORUM_CRON_USER_CACHE) {
    1040                                  $userscount++;
    1041                                  $users[$userfrom->id] = $userfrom;
    1042                              }
    1043  
    1044                          } else {
    1045                              mtrace('Could not find user '.$post->userid);
    1046                              continue;
    1047                          }
    1048  
    1049                          if (!isset($userfrom->groups[$forum->id])) {
    1050                              if (!isset($userfrom->groups)) {
    1051                                  $userfrom->groups = array();
    1052                                  if (isset($users[$userfrom->id])) {
    1053                                      $users[$userfrom->id]->groups = array();
    1054                                  }
    1055                              }
    1056                              $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
    1057                              if (isset($users[$userfrom->id])) {
    1058                                  $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
    1059                              }
    1060                          }
    1061  
    1062                          // Headers to help prevent auto-responders.
    1063                          $userfrom->customheaders = array(
    1064                                  "Precedence: Bulk",
    1065                                  'X-Auto-Response-Suppress: All',
    1066                                  'Auto-Submitted: auto-generated',
    1067                              );
    1068  
    1069                          $maildigest = forum_get_user_maildigest_bulk($digests, $userto, $forum->id);
    1070                          if ($maildigest == 2) {
    1071                              // Subjects and link only
    1072                              $posttext .= "\n";
    1073                              $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
    1074                              $by = new stdClass();
    1075                              $by->name = fullname($userfrom);
    1076                              $by->date = userdate($post->modified);
    1077                              $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
    1078                              $posttext .= "\n---------------------------------------------------------------------";
    1079  
    1080                              $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
    1081                              $posthtml .= '<div><a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'#p'.$post->id.'">'.format_string($post->subject,true).'</a> '.get_string("bynameondate", "forum", $by).'</div>';
    1082  
    1083                          } else {
    1084                              // The full treatment
    1085                              $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
    1086                              $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
    1087  
    1088                          // Create an array of postid's for this user to mark as read.
    1089                              if (!$CFG->forum_usermarksread) {
    1090                                  $userto->markposts[$post->id] = $post->id;
    1091                              }
    1092                          }
    1093                      }
    1094                      $footerlinks = array();
    1095                      if ($canunsubscribe) {
    1096                          $footerlinks[] = "<a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">" . get_string("unsubscribe", "forum") . "</a>";
    1097                      } else {
    1098                          $footerlinks[] = get_string("everyoneissubscribed", "forum");
    1099                      }
    1100                      $footerlinks[] = "<a href='{$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}'>" . get_string("digestmailpost", "forum") . '</a>';
    1101                      $posthtml .= "\n<div class='mdl-right'><font size=\"1\">" . implode('&nbsp;', $footerlinks) . '</font></div>';
    1102                      $posthtml .= '<hr size="1" noshade="noshade" /></p>';
    1103                  }
    1104                  $posthtml .= '</body>';
    1105  
    1106                  if (empty($userto->mailformat) || $userto->mailformat != 1) {
    1107                      // This user DOESN'T want to receive HTML
    1108                      $posthtml = '';
    1109                  }
    1110  
    1111                  $attachment = $attachname='';
    1112                  // Directly email forum digests rather than sending them via messaging, use the
    1113                  // site shortname as 'from name', the noreply address will be used by email_to_user.
    1114                  $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname);
    1115  
    1116                  if (!$mailresult) {
    1117                      mtrace("ERROR: mod/forum/cron.php: Could not send out digest mail to user $userto->id ".
    1118                          "($userto->email)... not trying again.");
    1119                  } else {
    1120                      mtrace("success.");
    1121                      $usermailcount++;
    1122  
    1123                      // Mark post as read if forum_usermarksread is set off
    1124                      forum_tp_mark_posts_read($userto, $userto->markposts);
    1125                  }
    1126              }
    1127          }
    1128      /// We have finishied all digest emails, update $CFG->digestmailtimelast
    1129          set_config('digestmailtimelast', $timenow);
    1130      }
    1131  
    1132      cron_setup_user();
    1133  
    1134      if (!empty($usermailcount)) {
    1135          mtrace(get_string('digestsentusers', 'forum', $usermailcount));
    1136      }
    1137  
    1138      if (!empty($CFG->forum_lastreadclean)) {
    1139          $timenow = time();
    1140          if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
    1141              set_config('forum_lastreadclean', $timenow);
    1142              mtrace('Removing old forum read tracking info...');
    1143              forum_tp_clean_read_records();
    1144          }
    1145      } else {
    1146          set_config('forum_lastreadclean', time());
    1147      }
    1148  
    1149      return true;
    1150  }
    1151  
    1152  /**
    1153   * Builds and returns the body of the email notification in plain text.
    1154   *
    1155   * @global object
    1156   * @global object
    1157   * @uses CONTEXT_MODULE
    1158   * @param object $course
    1159   * @param object $cm
    1160   * @param object $forum
    1161   * @param object $discussion
    1162   * @param object $post
    1163   * @param object $userfrom
    1164   * @param object $userto
    1165   * @param boolean $bare
    1166   * @param string $replyaddress The inbound address that a user can reply to the generated e-mail with. [Since 2.8].
    1167   * @return string The email body in plain text format.
    1168   */
    1169  function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false, $replyaddress = null) {
    1170      global $CFG, $USER;
    1171  
    1172      $modcontext = context_module::instance($cm->id);
    1173  
    1174      if (!isset($userto->viewfullnames[$forum->id])) {
    1175          $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
    1176      } else {
    1177          $viewfullnames = $userto->viewfullnames[$forum->id];
    1178      }
    1179  
    1180      if (!isset($userto->canpost[$discussion->id])) {
    1181          $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
    1182      } else {
    1183          $canreply = $userto->canpost[$discussion->id];
    1184      }
    1185  
    1186      $by = New stdClass;
    1187      $by->name = fullname($userfrom, $viewfullnames);
    1188      $by->date = userdate($post->modified, "", $userto->timezone);
    1189  
    1190      $strbynameondate = get_string('bynameondate', 'forum', $by);
    1191  
    1192      $strforums = get_string('forums', 'forum');
    1193  
    1194      $canunsubscribe = !\mod_forum\subscriptions::is_forcesubscribed($forum);
    1195  
    1196      $posttext = '';
    1197  
    1198      if (!$bare) {
    1199          $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
    1200          $posttext  .= "$shortname -> $strforums -> ".format_string($forum->name,true);
    1201  
    1202          if ($discussion->name != $forum->name) {
    1203              $posttext  .= " -> ".format_string($discussion->name,true);
    1204          }
    1205      }
    1206  
    1207      // add absolute file links
    1208      $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
    1209  
    1210      $posttext .= "\n";
    1211      $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
    1212      $posttext .= "\n---------------------------------------------------------------------\n";
    1213      $posttext .= format_string($post->subject,true);
    1214      if ($bare) {
    1215          $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
    1216      }
    1217      $posttext .= "\n".$strbynameondate."\n";
    1218      $posttext .= "---------------------------------------------------------------------\n";
    1219      $posttext .= format_text_email($post->message, $post->messageformat);
    1220      $posttext .= "\n\n";
    1221      $posttext .= forum_print_attachments($post, $cm, "text");
    1222  
    1223      if (!$bare) {
    1224          if ($canreply) {
    1225              $posttext .= "---------------------------------------------------------------------\n";
    1226              $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
    1227              $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
    1228          }
    1229  
    1230          if ($canunsubscribe) {
    1231              if (\mod_forum\subscriptions::is_subscribed($userto->id, $forum, null, $cm)) {
    1232                  // If subscribed to this forum, offer the unsubscribe link.
    1233                  $posttext .= "\n---------------------------------------------------------------------\n";
    1234                  $posttext .= get_string("unsubscribe", "forum");
    1235                  $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
    1236              }
    1237              // Always offer the unsubscribe from discussion link.
    1238              $posttext .= "\n---------------------------------------------------------------------\n";
    1239              $posttext .= get_string("unsubscribediscussion", "forum");
    1240              $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id&d=$discussion->id\n";
    1241          }
    1242      }
    1243  
    1244      $posttext .= "\n---------------------------------------------------------------------\n";
    1245      $posttext .= get_string("digestmailpost", "forum");
    1246      $posttext .= ": {$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}\n";
    1247  
    1248      if ($replyaddress) {
    1249          $posttext .= "\n\n" . get_string('replytopostbyemail', 'mod_forum');
    1250      }
    1251  
    1252      return $posttext;
    1253  }
    1254  
    1255  /**
    1256   * Builds and returns the body of the email notification in html format.
    1257   *
    1258   * @global object
    1259   * @param object $course
    1260   * @param object $cm
    1261   * @param object $forum
    1262   * @param object $discussion
    1263   * @param object $post
    1264   * @param object $userfrom
    1265   * @param object $userto
    1266   * @param string $replyaddress The inbound address that a user can reply to the generated e-mail with. [Since 2.8].
    1267   * @return string The email text in HTML format
    1268   */
    1269  function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $replyaddress = null) {
    1270      global $CFG;
    1271  
    1272      if ($userto->mailformat != 1) {  // Needs to be HTML
    1273          return '';
    1274      }
    1275  
    1276      if (!isset($userto->canpost[$discussion->id])) {
    1277          $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
    1278      } else {
    1279          $canreply = $userto->canpost[$discussion->id];
    1280      }
    1281  
    1282      $strforums = get_string('forums', 'forum');
    1283      $canunsubscribe = ! \mod_forum\subscriptions::is_forcesubscribed($forum);
    1284      $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
    1285  
    1286      $posthtml = '<head>';
    1287  /*    foreach ($CFG->stylesheets as $stylesheet) {
    1288          //TODO: MDL-21120
    1289          $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
    1290      }*/
    1291      $posthtml .= '</head>';
    1292      $posthtml .= "\n<body id=\"email\">\n\n";
    1293  
    1294      $posthtml .= '<div class="navbar">'.
    1295      '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
    1296      '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
    1297      '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
    1298      if ($discussion->name == $forum->name) {
    1299          $posthtml .= '</div>';
    1300      } else {
    1301          $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
    1302                       format_string($discussion->name,true).'</a></div>';
    1303      }
    1304      $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
    1305  
    1306      if ($replyaddress) {
    1307          $posthtml .= html_writer::tag('p', get_string('replytopostbyemail', 'mod_forum'));
    1308      }
    1309  
    1310      $footerlinks = array();
    1311      if ($canunsubscribe) {
    1312          if (\mod_forum\subscriptions::is_subscribed($userto->id, $forum, null, $cm)) {
    1313              // If subscribed to this forum, offer the unsubscribe link.
    1314              $unsublink = new moodle_url('/mod/forum/subscribe.php', array('id' => $forum->id));
    1315              $footerlinks[] = html_writer::link($unsublink, get_string('unsubscribe', 'mod_forum'));
    1316          }
    1317          // Always offer the unsubscribe from discussion link.
    1318          $unsublink = new moodle_url('/mod/forum/subscribe.php', array(
    1319                  'id' => $forum->id,
    1320                  'd' => $discussion->id,
    1321              ));
    1322          $footerlinks[] = html_writer::link($unsublink, get_string('unsubscribediscussion', 'mod_forum'));
    1323  
    1324          $footerlinks[] = '<a href="' . $CFG->wwwroot . '/mod/forum/unsubscribeall.php">' . get_string('unsubscribeall', 'forum') . '</a>';
    1325      }
    1326      $footerlinks[] = "<a href='{$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}'>" . get_string('digestmailpost', 'forum') . '</a>';
    1327      $posthtml .= '<hr /><div class="mdl-align unsubscribelink">' . implode('&nbsp;', $footerlinks) . '</div>';
    1328  
    1329      $posthtml .= '</body>';
    1330  
    1331      return $posthtml;
    1332  }
    1333  
    1334  
    1335  /**
    1336   *
    1337   * @param object $course
    1338   * @param object $user
    1339   * @param object $mod TODO this is not used in this function, refactor
    1340   * @param object $forum
    1341   * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
    1342   */
    1343  function forum_user_outline($course, $user, $mod, $forum) {
    1344      global $CFG;
    1345      require_once("$CFG->libdir/gradelib.php");
    1346      $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
    1347      if (empty($grades->items[0]->grades)) {
    1348          $grade = false;
    1349      } else {
    1350          $grade = reset($grades->items[0]->grades);
    1351      }
    1352  
    1353      $count = forum_count_user_posts($forum->id, $user->id);
    1354  
    1355      if ($count && $count->postcount > 0) {
    1356          $result = new stdClass();
    1357          $result->info = get_string("numposts", "forum", $count->postcount);
    1358          $result->time = $count->lastpost;
    1359          if ($grade) {
    1360              $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
    1361          }
    1362          return $result;
    1363      } else if ($grade) {
    1364          $result = new stdClass();
    1365          $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
    1366  
    1367          //datesubmitted == time created. dategraded == time modified or time overridden
    1368          //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
    1369          //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
    1370          if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
    1371              $result->time = $grade->dategraded;
    1372          } else {
    1373              $result->time = $grade->datesubmitted;
    1374          }
    1375  
    1376          return $result;
    1377      }
    1378      return NULL;
    1379  }
    1380  
    1381  
    1382  /**
    1383   * @global object
    1384   * @global object
    1385   * @param object $coure
    1386   * @param object $user
    1387   * @param object $mod
    1388   * @param object $forum
    1389   */
    1390  function forum_user_complete($course, $user, $mod, $forum) {
    1391      global $CFG,$USER, $OUTPUT;
    1392      require_once("$CFG->libdir/gradelib.php");
    1393  
    1394      $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
    1395      if (!empty($grades->items[0]->grades)) {
    1396          $grade = reset($grades->items[0]->grades);
    1397          echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
    1398          if ($grade->str_feedback) {
    1399              echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
    1400          }
    1401      }
    1402  
    1403      if ($posts = forum_get_user_posts($forum->id, $user->id)) {
    1404  
    1405          if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
    1406              print_error('invalidcoursemodule');
    1407          }
    1408          $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
    1409  
    1410          foreach ($posts as $post) {
    1411              if (!isset($discussions[$post->discussion])) {
    1412                  continue;
    1413              }
    1414              $discussion = $discussions[$post->discussion];
    1415  
    1416              forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
    1417          }
    1418      } else {
    1419          echo "<p>".get_string("noposts", "forum")."</p>";
    1420      }
    1421  }
    1422  
    1423  /**
    1424   * Filters the forum discussions according to groups membership and config.
    1425   *
    1426   * @since  Moodle 2.8, 2.7.1, 2.6.4
    1427   * @param  array $discussions Discussions with new posts array
    1428   * @return array Forums with the number of new posts
    1429   */
    1430  function forum_filter_user_groups_discussions($discussions) {
    1431  
    1432      // Group the remaining discussions posts by their forumid.
    1433      $filteredforums = array();
    1434  
    1435      // Discard not visible groups.
    1436      foreach ($discussions as $discussion) {
    1437  
    1438          // Course data is already cached.
    1439          $instances = get_fast_modinfo($discussion->course)->get_instances();
    1440          $forum = $instances['forum'][$discussion->forum];
    1441  
    1442          // Continue if the user should not see this discussion.
    1443          if (!forum_is_user_group_discussion($forum, $discussion->groupid)) {
    1444              continue;
    1445          }
    1446  
    1447          // Grouping results by forum.
    1448          if (empty($filteredforums[$forum->instance])) {
    1449              $filteredforums[$forum->instance] = new stdClass();
    1450              $filteredforums[$forum->instance]->id = $forum->id;
    1451              $filteredforums[$forum->instance]->count = 0;
    1452          }
    1453          $filteredforums[$forum->instance]->count += $discussion->count;
    1454  
    1455      }
    1456  
    1457      return $filteredforums;
    1458  }
    1459  
    1460  /**
    1461   * Returns whether the discussion group is visible by the current user or not.
    1462   *
    1463   * @since Moodle 2.8, 2.7.1, 2.6.4
    1464   * @param cm_info $cm The discussion course module
    1465   * @param int $discussiongroupid The discussion groupid
    1466   * @return bool
    1467   */
    1468  function forum_is_user_group_discussion(cm_info $cm, $discussiongroupid) {
    1469  
    1470      if ($discussiongroupid == -1 || $cm->effectivegroupmode != SEPARATEGROUPS) {
    1471          return true;
    1472      }
    1473  
    1474      if (isguestuser()) {
    1475          return false;
    1476      }
    1477  
    1478      if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id)) ||
    1479              in_array($discussiongroupid, $cm->get_modinfo()->get_groups($cm->groupingid))) {
    1480          return true;
    1481      }
    1482  
    1483      return false;
    1484  }
    1485  
    1486  /**
    1487   * @global object
    1488   * @global object
    1489   * @global object
    1490   * @param array $courses
    1491   * @param array $htmlarray
    1492   */
    1493  function forum_print_overview($courses,&$htmlarray) {
    1494      global $USER, $CFG, $DB, $SESSION;
    1495  
    1496      if (empty($courses) || !is_array($courses) || count($courses) == 0) {
    1497          return array();
    1498      }
    1499  
    1500      if (!$forums = get_all_instances_in_courses('forum',$courses)) {
    1501          return;
    1502      }
    1503  
    1504      // Courses to search for new posts
    1505      $coursessqls = array();
    1506      $params = array();
    1507      foreach ($courses as $course) {
    1508  
    1509          // If the user has never entered into the course all posts are pending
    1510          if ($course->lastaccess == 0) {
    1511              $coursessqls[] = '(d.course = ?)';
    1512              $params[] = $course->id;
    1513  
    1514          // Only posts created after the course last access
    1515          } else {
    1516              $coursessqls[] = '(d.course = ? AND p.created > ?)';
    1517              $params[] = $course->id;
    1518              $params[] = $course->lastaccess;
    1519          }
    1520      }
    1521      $params[] = $USER->id;
    1522      $coursessql = implode(' OR ', $coursessqls);
    1523  
    1524      $sql = "SELECT d.id, d.forum, d.course, d.groupid, COUNT(*) as count "
    1525                  .'FROM {forum_discussions} d '
    1526                  .'JOIN {forum_posts} p ON p.discussion = d.id '
    1527                  ."WHERE ($coursessql) "
    1528                  .'AND p.userid != ? '
    1529                  .'GROUP BY d.id, d.forum, d.course, d.groupid '
    1530                  .'ORDER BY d.course, d.forum';
    1531  
    1532      // Avoid warnings.
    1533      if (!$discussions = $DB->get_records_sql($sql, $params)) {
    1534          $discussions = array();
    1535      }
    1536  
    1537      $forumsnewposts = forum_filter_user_groups_discussions($discussions);
    1538  
    1539      // also get all forum tracking stuff ONCE.
    1540      $trackingforums = array();
    1541      foreach ($forums as $forum) {
    1542          if (forum_tp_can_track_forums($forum)) {
    1543              $trackingforums[$forum->id] = $forum;
    1544          }
    1545      }
    1546  
    1547      if (count($trackingforums) > 0) {
    1548          $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
    1549          $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
    1550              ' FROM {forum_posts} p '.
    1551              ' JOIN {forum_discussions} d ON p.discussion = d.id '.
    1552              ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
    1553          $params = array($USER->id);
    1554  
    1555          foreach ($trackingforums as $track) {
    1556              $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
    1557              $params[] = $track->id;
    1558              if (isset($SESSION->currentgroup[$track->course])) {
    1559                  $groupid =  $SESSION->currentgroup[$track->course];
    1560              } else {
    1561                  // get first groupid
    1562                  $groupids = groups_get_all_groups($track->course, $USER->id);
    1563                  if ($groupids) {
    1564                      reset($groupids);
    1565                      $groupid = key($groupids);
    1566                      $SESSION->currentgroup[$track->course] = $groupid;
    1567                  } else {
    1568                      $groupid = 0;
    1569                  }
    1570                  unset($groupids);
    1571              }
    1572              $params[] = $groupid;
    1573          }
    1574          $sql = substr($sql,0,-3); // take off the last OR
    1575          $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
    1576          $params[] = $cutoffdate;
    1577  
    1578          if (!$unread = $DB->get_records_sql($sql, $params)) {
    1579              $unread = array();
    1580          }
    1581      } else {
    1582          $unread = array();
    1583      }
    1584  
    1585      if (empty($unread) and empty($forumsnewposts)) {
    1586          return;
    1587      }
    1588  
    1589      $strforum = get_string('modulename','forum');
    1590  
    1591      foreach ($forums as $forum) {
    1592          $str = '';
    1593          $count = 0;
    1594          $thisunread = 0;
    1595          $showunread = false;
    1596          // either we have something from logs, or trackposts, or nothing.
    1597          if (array_key_exists($forum->id, $forumsnewposts) && !empty($forumsnewposts[$forum->id])) {
    1598              $count = $forumsnewposts[$forum->id]->count;
    1599          }
    1600          if (array_key_exists($forum->id,$unread)) {
    1601              $thisunread = $unread[$forum->id]->count;
    1602              $showunread = true;
    1603          }
    1604          if ($count > 0 || $thisunread > 0) {
    1605              $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
    1606                  $forum->name.'</a></div>';
    1607              $str .= '<div class="info"><span class="postsincelogin">';
    1608              $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
    1609              if (!empty($showunread)) {
    1610                  $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
    1611              }
    1612              $str .= '</div></div>';
    1613          }
    1614          if (!empty($str)) {
    1615              if (!array_key_exists($forum->course,$htmlarray)) {
    1616                  $htmlarray[$forum->course] = array();
    1617              }
    1618              if (!array_key_exists('forum',$htmlarray[$forum->course])) {
    1619                  $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
    1620              }
    1621              $htmlarray[$forum->course]['forum'] .= $str;
    1622          }
    1623      }
    1624  }
    1625  
    1626  /**
    1627   * Given a course and a date, prints a summary of all the new
    1628   * messages posted in the course since that date
    1629   *
    1630   * @global object
    1631   * @global object
    1632   * @global object
    1633   * @uses CONTEXT_MODULE
    1634   * @uses VISIBLEGROUPS
    1635   * @param object $course
    1636   * @param bool $viewfullnames capability
    1637   * @param int $timestart
    1638   * @return bool success
    1639   */
    1640  function forum_print_recent_activity($course, $viewfullnames, $timestart) {
    1641      global $CFG, $USER, $DB, $OUTPUT;
    1642  
    1643      // do not use log table if possible, it may be huge and is expensive to join with other tables
    1644  
    1645      $allnamefields = user_picture::fields('u', null, 'duserid');
    1646      if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
    1647                                                d.timestart, d.timeend, $allnamefields
    1648                                           FROM {forum_posts} p
    1649                                                JOIN {forum_discussions} d ON d.id = p.discussion
    1650                                                JOIN {forum} f             ON f.id = d.forum
    1651                                                JOIN {user} u              ON u.id = p.userid
    1652                                          WHERE p.created > ? AND f.course = ?
    1653                                       ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
    1654           return false;
    1655      }
    1656  
    1657      $modinfo = get_fast_modinfo($course);
    1658  
    1659      $groupmodes = array();
    1660      $cms    = array();
    1661  
    1662      $strftimerecent = get_string('strftimerecent');
    1663  
    1664      $printposts = array();
    1665      foreach ($posts as $post) {
    1666          if (!isset($modinfo->instances['forum'][$post->forum])) {
    1667              // not visible
    1668              continue;
    1669          }
    1670          $cm = $modinfo->instances['forum'][$post->forum];
    1671          if (!$cm->uservisible) {
    1672              continue;
    1673          }
    1674          $context = context_module::instance($cm->id);
    1675  
    1676          if (!has_capability('mod/forum:viewdiscussion', $context)) {
    1677              continue;
    1678          }
    1679  
    1680          if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
    1681            and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
    1682              if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
    1683                  continue;
    1684              }
    1685          }
    1686  
    1687          // Check that the user can see the discussion.
    1688          if (forum_is_user_group_discussion($cm, $post->groupid)) {
    1689              $printposts[] = $post;
    1690          }
    1691  
    1692      }
    1693      unset($posts);
    1694  
    1695      if (!$printposts) {
    1696          return false;
    1697      }
    1698  
    1699      echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
    1700      echo "\n<ul class='unlist'>\n";
    1701  
    1702      foreach ($printposts as $post) {
    1703          $subjectclass = empty($post->parent) ? ' bold' : '';
    1704  
    1705          echo '<li><div class="head">'.
    1706                 '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
    1707                 '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
    1708               '</div>';
    1709          echo '<div class="info'.$subjectclass.'">';
    1710          if (empty($post->parent)) {
    1711              echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
    1712          } else {
    1713              echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
    1714          }
    1715          $post->subject = break_up_long_words(format_string($post->subject, true));
    1716          echo $post->subject;
    1717          echo "</a>\"</div></li>\n";
    1718      }
    1719  
    1720      echo "</ul>\n";
    1721  
    1722      return true;
    1723  }
    1724  
    1725  /**
    1726   * Return grade for given user or all users.
    1727   *
    1728   * @global object
    1729   * @global object
    1730   * @param object $forum
    1731   * @param int $userid optional user id, 0 means all users
    1732   * @return array array of grades, false if none
    1733   */
    1734  function forum_get_user_grades($forum, $userid = 0) {
    1735      global $CFG;
    1736  
    1737      require_once($CFG->dirroot.'/rating/lib.php');
    1738  
    1739      $ratingoptions = new stdClass;
    1740      $ratingoptions->component = 'mod_forum';
    1741      $ratingoptions->ratingarea = 'post';
    1742  
    1743      //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
    1744      $ratingoptions->modulename = 'forum';
    1745      $ratingoptions->moduleid   = $forum->id;
    1746      $ratingoptions->userid = $userid;
    1747      $ratingoptions->aggregationmethod = $forum->assessed;
    1748      $ratingoptions->scaleid = $forum->scale;
    1749      $ratingoptions->itemtable = 'forum_posts';
    1750      $ratingoptions->itemtableusercolumn = 'userid';
    1751  
    1752      $rm = new rating_manager();
    1753      return $rm->get_user_grades($ratingoptions);
    1754  }
    1755  
    1756  /**
    1757   * Update activity grades
    1758   *
    1759   * @category grade
    1760   * @param object $forum
    1761   * @param int $userid specific user only, 0 means all
    1762   * @param boolean $nullifnone return null if grade does not exist
    1763   * @return void
    1764   */
    1765  function forum_update_grades($forum, $userid=0, $nullifnone=true) {
    1766      global $CFG, $DB;
    1767      require_once($CFG->libdir.'/gradelib.php');
    1768  
    1769      if (!$forum->assessed) {
    1770          forum_grade_item_update($forum);
    1771  
    1772      } else if ($grades = forum_get_user_grades($forum, $userid)) {
    1773          forum_grade_item_update($forum, $grades);
    1774  
    1775      } else if ($userid and $nullifnone) {
    1776          $grade = new stdClass();
    1777          $grade->userid   = $userid;
    1778          $grade->rawgrade = NULL;
    1779          forum_grade_item_update($forum, $grade);
    1780  
    1781      } else {
    1782          forum_grade_item_update($forum);
    1783      }
    1784  }
    1785  
    1786  /**
    1787   * Create/update grade item for given forum
    1788   *
    1789   * @category grade
    1790   * @uses GRADE_TYPE_NONE
    1791   * @uses GRADE_TYPE_VALUE
    1792   * @uses GRADE_TYPE_SCALE
    1793   * @param stdClass $forum Forum object with extra cmidnumber
    1794   * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
    1795   * @return int 0 if ok
    1796   */
    1797  function forum_grade_item_update($forum, $grades=NULL) {
    1798      global $CFG;
    1799      if (!function_exists('grade_update')) { //workaround for buggy PHP versions
    1800          require_once($CFG->libdir.'/gradelib.php');
    1801      }
    1802  
    1803      $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
    1804  
    1805      if (!$forum->assessed or $forum->scale == 0) {
    1806          $params['gradetype'] = GRADE_TYPE_NONE;
    1807  
    1808      } else if ($forum->scale > 0) {
    1809          $params['gradetype'] = GRADE_TYPE_VALUE;
    1810          $params['grademax']  = $forum->scale;
    1811          $params['grademin']  = 0;
    1812  
    1813      } else if ($forum->scale < 0) {
    1814          $params['gradetype'] = GRADE_TYPE_SCALE;
    1815          $params['scaleid']   = -$forum->scale;
    1816      }
    1817  
    1818      if ($grades  === 'reset') {
    1819          $params['reset'] = true;
    1820          $grades = NULL;
    1821      }
    1822  
    1823      return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
    1824  }
    1825  
    1826  /**
    1827   * Delete grade item for given forum
    1828   *
    1829   * @category grade
    1830   * @param stdClass $forum Forum object
    1831   * @return grade_item
    1832   */
    1833  function forum_grade_item_delete($forum) {
    1834      global $CFG;
    1835      require_once($CFG->libdir.'/gradelib.php');
    1836  
    1837      return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
    1838  }
    1839  
    1840  
    1841  /**
    1842   * This function returns if a scale is being used by one forum
    1843   *
    1844   * @global object
    1845   * @param int $forumid
    1846   * @param int $scaleid negative number
    1847   * @return bool
    1848   */
    1849  function forum_scale_used ($forumid,$scaleid) {
    1850      global $DB;
    1851      $return = false;
    1852  
    1853      $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
    1854  
    1855      if (!empty($rec) && !empty($scaleid)) {
    1856          $return = true;
    1857      }
    1858  
    1859      return $return;
    1860  }
    1861  
    1862  /**
    1863   * Checks if scale is being used by any instance of forum
    1864   *
    1865   * This is used to find out if scale used anywhere
    1866   *
    1867   * @global object
    1868   * @param $scaleid int
    1869   * @return boolean True if the scale is used by any forum
    1870   */
    1871  function forum_scale_used_anywhere($scaleid) {
    1872      global $DB;
    1873      if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
    1874          return true;
    1875      } else {
    1876          return false;
    1877      }
    1878  }
    1879  
    1880  // SQL FUNCTIONS ///////////////////////////////////////////////////////////
    1881  
    1882  /**
    1883   * Gets a post with all info ready for forum_print_post
    1884   * Most of these joins are just to get the forum id
    1885   *
    1886   * @global object
    1887   * @global object
    1888   * @param int $postid
    1889   * @return mixed array of posts or false
    1890   */
    1891  function forum_get_post_full($postid) {
    1892      global $CFG, $DB;
    1893  
    1894      $allnames = get_all_user_name_fields(true, 'u');
    1895      return $DB->get_record_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
    1896                               FROM {forum_posts} p
    1897                                    JOIN {forum_discussions} d ON p.discussion = d.id
    1898                                    LEFT JOIN {user} u ON p.userid = u.id
    1899                              WHERE p.id = ?", array($postid));
    1900  }
    1901  
    1902  /**
    1903   * Gets all posts in discussion including top parent.
    1904   *
    1905   * @global object
    1906   * @global object
    1907   * @global object
    1908   * @param int $discussionid
    1909   * @param string $sort
    1910   * @param bool $tracking does user track the forum?
    1911   * @return array of posts
    1912   */
    1913  function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
    1914      global $CFG, $DB, $USER;
    1915  
    1916      $tr_sel  = "";
    1917      $tr_join = "";
    1918      $params = array();
    1919  
    1920      if ($tracking) {
    1921          $now = time();
    1922          $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
    1923          $tr_sel  = ", fr.id AS postread";
    1924          $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
    1925          $params[] = $USER->id;
    1926      }
    1927  
    1928      $allnames = get_all_user_name_fields(true, 'u');
    1929      $params[] = $discussionid;
    1930      if (!$posts = $DB->get_records_sql("SELECT p.*, $allnames, u.email, u.picture, u.imagealt $tr_sel
    1931                                       FROM {forum_posts} p
    1932                                            LEFT JOIN {user} u ON p.userid = u.id
    1933                                            $tr_join
    1934                                      WHERE p.discussion = ?
    1935                                   ORDER BY $sort", $params)) {
    1936          return array();
    1937      }
    1938  
    1939      foreach ($posts as $pid=>$p) {
    1940          if ($tracking) {
    1941              if (forum_tp_is_post_old($p)) {
    1942                   $posts[$pid]->postread = true;
    1943              }
    1944          }
    1945          if (!$p->parent) {
    1946              continue;
    1947          }
    1948          if (!isset($posts[$p->parent])) {
    1949              continue; // parent does not exist??
    1950          }
    1951          if (!isset($posts[$p->parent]->children)) {
    1952              $posts[$p->parent]->children = array();
    1953          }
    1954          $posts[$p->parent]->children[$pid] =& $posts[$pid];
    1955      }
    1956  
    1957      return $posts;
    1958  }
    1959  
    1960  /**
    1961   * An array of forum objects that the user is allowed to read/search through.
    1962   *
    1963   * @global object
    1964   * @global object
    1965   * @global object
    1966   * @param int $userid
    1967   * @param int $courseid if 0, we look for forums throughout the whole site.
    1968   * @return array of forum objects, or false if no matches
    1969   *         Forum objects have the following attributes:
    1970   *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
    1971   *         viewhiddentimedposts
    1972   */
    1973  function forum_get_readable_forums($userid, $courseid=0) {
    1974  
    1975      global $CFG, $DB, $USER;
    1976      require_once($CFG->dirroot.'/course/lib.php');
    1977  
    1978      if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
    1979          print_error('notinstalled', 'forum');
    1980      }
    1981  
    1982      if ($courseid) {
    1983          $courses = $DB->get_records('course', array('id' => $courseid));
    1984      } else {
    1985          // If no course is specified, then the user can see SITE + his courses.
    1986          $courses1 = $DB->get_records('course', array('id' => SITEID));
    1987          $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
    1988          $courses = array_merge($courses1, $courses2);
    1989      }
    1990      if (!$courses) {
    1991          return array();
    1992      }
    1993  
    1994      $readableforums = array();
    1995  
    1996      foreach ($courses as $course) {
    1997  
    1998          $modinfo = get_fast_modinfo($course);
    1999  
    2000          if (empty($modinfo->instances['forum'])) {
    2001              // hmm, no forums?
    2002              continue;
    2003          }
    2004  
    2005          $courseforums = $DB->get_records('forum', array('course' => $course->id));
    2006  
    2007          foreach ($modinfo->instances['forum'] as $forumid => $cm) {
    2008              if (!$cm->uservisible or !isset($courseforums[$forumid])) {
    2009                  continue;
    2010              }
    2011              $context = context_module::instance($cm->id);
    2012              $forum = $courseforums[$forumid];
    2013              $forum->context = $context;
    2014              $forum->cm = $cm;
    2015  
    2016              if (!has_capability('mod/forum:viewdiscussion', $context)) {
    2017                  continue;
    2018              }
    2019  
    2020           /// group access
    2021              if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
    2022  
    2023                  $forum->onlygroups = $modinfo->get_groups($cm->groupingid);
    2024                  $forum->onlygroups[] = -1;
    2025              }
    2026  
    2027          /// hidden timed discussions
    2028              $forum->viewhiddentimedposts = true;
    2029              if (!empty($CFG->forum_enabletimedposts)) {
    2030                  if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
    2031                      $forum->viewhiddentimedposts = false;
    2032                  }
    2033              }
    2034  
    2035          /// qanda access
    2036              if ($forum->type == 'qanda'
    2037                      && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
    2038  
    2039                  // We need to check whether the user has posted in the qanda forum.
    2040                  $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
    2041                                                      // the user is allowed to see in this forum.
    2042                  if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
    2043                      foreach ($discussionspostedin as $d) {
    2044                          $forum->onlydiscussions[] = $d->id;
    2045                      }
    2046                  }
    2047              }
    2048  
    2049              $readableforums[$forum->id] = $forum;
    2050          }
    2051  
    2052          unset($modinfo);
    2053  
    2054      } // End foreach $courses
    2055  
    2056      return $readableforums;
    2057  }
    2058  
    2059  /**
    2060   * Returns a list of posts found using an array of search terms.
    2061   *
    2062   * @global object
    2063   * @global object
    2064   * @global object
    2065   * @param array $searchterms array of search terms, e.g. word +word -word
    2066   * @param int $courseid if 0, we search through the whole site
    2067   * @param int $limitfrom
    2068   * @param int $limitnum
    2069   * @param int &$totalcount
    2070   * @param string $extrasql
    2071   * @return array|bool Array of posts found or false
    2072   */
    2073  function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
    2074                              &$totalcount, $extrasql='') {
    2075      global $CFG, $DB, $USER;
    2076      require_once($CFG->libdir.'/searchlib.php');
    2077  
    2078      $forums = forum_get_readable_forums($USER->id, $courseid);
    2079  
    2080      if (count($forums) == 0) {
    2081          $totalcount = 0;
    2082          return false;
    2083      }
    2084  
    2085      $now = round(time(), -2); // db friendly
    2086  
    2087      $fullaccess = array();
    2088      $where = array();
    2089      $params = array();
    2090  
    2091      foreach ($forums as $forumid => $forum) {
    2092          $select = array();
    2093  
    2094          if (!$forum->viewhiddentimedposts) {
    2095              $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
    2096              $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
    2097          }
    2098  
    2099          $cm = $forum->cm;
    2100          $context = $forum->context;
    2101  
    2102          if ($forum->type == 'qanda'
    2103              && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
    2104              if (!empty($forum->onlydiscussions)) {
    2105                  list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
    2106                  $params = array_merge($params, $discussionid_params);
    2107                  $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
    2108              } else {
    2109                  $select[] = "p.parent = 0";
    2110              }
    2111          }
    2112  
    2113          if (!empty($forum->onlygroups)) {
    2114              list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
    2115              $params = array_merge($params, $groupid_params);
    2116              $select[] = "d.groupid $groupid_sql";
    2117          }
    2118  
    2119          if ($select) {
    2120              $selects = implode(" AND ", $select);
    2121              $where[] = "(d.forum = :forum{$forumid} AND $selects)";
    2122              $params['forum'.$forumid] = $forumid;
    2123          } else {
    2124              $fullaccess[] = $forumid;
    2125          }
    2126      }
    2127  
    2128      if ($fullaccess) {
    2129          list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
    2130          $params = array_merge($params, $fullid_params);
    2131          $where[] = "(d.forum $fullid_sql)";
    2132      }
    2133  
    2134      $selectdiscussion = "(".implode(" OR ", $where).")";
    2135  
    2136      $messagesearch = '';
    2137      $searchstring = '';
    2138  
    2139      // Need to concat these back together for parser to work.
    2140      foreach($searchterms as $searchterm){
    2141          if ($searchstring != '') {
    2142              $searchstring .= ' ';
    2143          }
    2144          $searchstring .= $searchterm;
    2145      }
    2146  
    2147      // We need to allow quoted strings for the search. The quotes *should* be stripped
    2148      // by the parser, but this should be examined carefully for security implications.
    2149      $searchstring = str_replace("\\\"","\"",$searchstring);
    2150      $parser = new search_parser();
    2151      $lexer = new search_lexer($parser);
    2152  
    2153      if ($lexer->parse($searchstring)) {
    2154          $parsearray = $parser->get_parsed_array();
    2155      // Experimental feature under 1.8! MDL-8830
    2156      // Use alternative text searches if defined
    2157      // This feature only works under mysql until properly implemented for other DBs
    2158      // Requires manual creation of text index for forum_posts before enabling it:
    2159      // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
    2160      // Experimental feature under 1.8! MDL-8830
    2161          if (!empty($CFG->forum_usetextsearches)) {
    2162              list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
    2163                                                   'p.userid', 'u.id', 'u.firstname',
    2164                                                   'u.lastname', 'p.modified', 'd.forum');
    2165          } else {
    2166              list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
    2167                                                   'p.userid', 'u.id', 'u.firstname',
    2168                                                   'u.lastname', 'p.modified', 'd.forum');
    2169          }
    2170          $params = array_merge($params, $msparams);
    2171      }
    2172  
    2173      $fromsql = "{forum_posts} p,
    2174                    {forum_discussions} d,
    2175                    {user} u";
    2176  
    2177      $selectsql = " $messagesearch
    2178                 AND p.discussion = d.id
    2179                 AND p.userid = u.id
    2180                 AND $selectdiscussion
    2181                     $extrasql";
    2182  
    2183      $countsql = "SELECT COUNT(*)
    2184                     FROM $fromsql
    2185                    WHERE $selectsql";
    2186  
    2187      $allnames = get_all_user_name_fields(true, 'u');
    2188      $searchsql = "SELECT p.*,
    2189                           d.forum,
    2190                           $allnames,
    2191                           u.email,
    2192                           u.picture,
    2193                           u.imagealt
    2194                      FROM $fromsql
    2195                     WHERE $selectsql
    2196                  ORDER BY p.modified DESC";
    2197  
    2198      $totalcount = $DB->count_records_sql($countsql, $params);
    2199  
    2200      return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
    2201  }
    2202  
    2203  /**
    2204   * Returns a list of all new posts that have not been mailed yet
    2205   *
    2206   * @param int $starttime posts created after this time
    2207   * @param int $endtime posts created before this
    2208   * @param int $now used for timed discussions only
    2209   * @return array
    2210   */
    2211  function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
    2212      global $CFG, $DB;
    2213  
    2214      $params = array();
    2215      $params['mailed'] = FORUM_MAILED_PENDING;
    2216      $params['ptimestart'] = $starttime;
    2217      $params['ptimeend'] = $endtime;
    2218      $params['mailnow'] = 1;
    2219  
    2220      if (!empty($CFG->forum_enabletimedposts)) {
    2221          if (empty($now)) {
    2222              $now = time();
    2223          }
    2224          $timedsql = "AND (d.timestart < :dtimestart AND (d.timeend = 0 OR d.timeend > :dtimeend))";
    2225          $params['dtimestart'] = $now;
    2226          $params['dtimeend'] = $now;
    2227      } else {
    2228          $timedsql = "";
    2229      }
    2230  
    2231      return $DB->get_records_sql("SELECT p.*, d.course, d.forum
    2232                                   FROM {forum_posts} p
    2233                                   JOIN {forum_discussions} d ON d.id = p.discussion
    2234                                   WHERE p.mailed = :mailed
    2235                                   AND p.created >= :ptimestart
    2236                                   AND (p.created < :ptimeend OR p.mailnow = :mailnow)
    2237                                   $timedsql
    2238                                   ORDER BY p.modified ASC", $params);
    2239  }
    2240  
    2241  /**
    2242   * Marks posts before a certain time as being mailed already
    2243   *
    2244   * @global object
    2245   * @global object
    2246   * @param int $endtime
    2247   * @param int $now Defaults to time()
    2248   * @return bool
    2249   */
    2250  function forum_mark_old_posts_as_mailed($endtime, $now=null) {
    2251      global $CFG, $DB;
    2252  
    2253      if (empty($now)) {
    2254          $now = time();
    2255      }
    2256  
    2257      $params = array();
    2258      $params['mailedsuccess'] = FORUM_MAILED_SUCCESS;
    2259      $params['now'] = $now;
    2260      $params['endtime'] = $endtime;
    2261      $params['mailnow'] = 1;
    2262      $params['mailedpending'] = FORUM_MAILED_PENDING;
    2263  
    2264      if (empty($CFG->forum_enabletimedposts)) {
    2265          return $DB->execute("UPDATE {forum_posts}
    2266                               SET mailed = :mailedsuccess
    2267                               WHERE (created < :endtime OR mailnow = :mailnow)
    2268                               AND mailed = :mailedpending", $params);
    2269      } else {
    2270          return $DB->execute("UPDATE {forum_posts}
    2271                               SET mailed = :mailedsuccess
    2272                               WHERE discussion NOT IN (SELECT d.id
    2273                                                        FROM {forum_discussions} d
    2274                                                        WHERE d.timestart > :now)
    2275                               AND (created < :endtime OR mailnow = :mailnow)
    2276                               AND mailed = :mailedpending", $params);
    2277      }
    2278  }
    2279  
    2280  /**
    2281   * Get all the posts for a user in a forum suitable for forum_print_post
    2282   *
    2283   * @global object
    2284   * @global object
    2285   * @uses CONTEXT_MODULE
    2286   * @return array
    2287   */
    2288  function forum_get_user_posts($forumid, $userid) {
    2289      global $CFG, $DB;
    2290  
    2291      $timedsql = "";
    2292      $params = array($forumid, $userid);
    2293  
    2294      if (!empty($CFG->forum_enabletimedposts)) {
    2295          $cm = get_coursemodule_from_instance('forum', $forumid);
    2296          if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
    2297              $now = time();
    2298              $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
    2299              $params[] = $now;
    2300              $params[] = $now;
    2301          }
    2302      }
    2303  
    2304      $allnames = get_all_user_name_fields(true, 'u');
    2305      return $DB->get_records_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
    2306                                FROM {forum} f
    2307                                     JOIN {forum_discussions} d ON d.forum = f.id
    2308                                     JOIN {forum_posts} p       ON p.discussion = d.id
    2309                                     JOIN {user} u              ON u.id = p.userid
    2310                               WHERE f.id = ?
    2311                                     AND p.userid = ?
    2312                                     $timedsql
    2313                            ORDER BY p.modified ASC", $params);
    2314  }
    2315  
    2316  /**
    2317   * Get all the discussions user participated in
    2318   *
    2319   * @global object
    2320   * @global object
    2321   * @uses CONTEXT_MODULE
    2322   * @param int $forumid
    2323   * @param int $userid
    2324   * @return array Array or false
    2325   */
    2326  function forum_get_user_involved_discussions($forumid, $userid) {
    2327      global $CFG, $DB;
    2328  
    2329      $timedsql = "";
    2330      $params = array($forumid, $userid);
    2331      if (!empty($CFG->forum_enabletimedposts)) {
    2332          $cm = get_coursemodule_from_instance('forum', $forumid);
    2333          if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
    2334              $now = time();
    2335              $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
    2336              $params[] = $now;
    2337              $params[] = $now;
    2338          }
    2339      }
    2340  
    2341      return $DB->get_records_sql("SELECT DISTINCT d.*
    2342                                FROM {forum} f
    2343                                     JOIN {forum_discussions} d ON d.forum = f.id
    2344                                     JOIN {forum_posts} p       ON p.discussion = d.id
    2345                               WHERE f.id = ?
    2346                                     AND p.userid = ?
    2347                                     $timedsql", $params);
    2348  }
    2349  
    2350  /**
    2351   * Get all the posts for a user in a forum suitable for forum_print_post
    2352   *
    2353   * @global object
    2354   * @global object
    2355   * @param int $forumid
    2356   * @param int $userid
    2357   * @return array of counts or false
    2358   */
    2359  function forum_count_user_posts($forumid, $userid) {
    2360      global $CFG, $DB;
    2361  
    2362      $timedsql = "";
    2363      $params = array($forumid, $userid);
    2364      if (!empty($CFG->forum_enabletimedposts)) {
    2365          $cm = get_coursemodule_from_instance('forum', $forumid);
    2366          if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
    2367              $now = time();
    2368              $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
    2369              $params[] = $now;
    2370              $params[] = $now;
    2371          }
    2372      }
    2373  
    2374      return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
    2375                               FROM {forum} f
    2376                                    JOIN {forum_discussions} d ON d.forum = f.id
    2377                                    JOIN {forum_posts} p       ON p.discussion = d.id
    2378                                    JOIN {user} u              ON u.id = p.userid
    2379                              WHERE f.id = ?
    2380                                    AND p.userid = ?
    2381                                    $timedsql", $params);
    2382  }
    2383  
    2384  /**
    2385   * Given a log entry, return the forum post details for it.
    2386   *
    2387   * @global object
    2388   * @global object
    2389   * @param object $log
    2390   * @return array|null
    2391   */
    2392  function forum_get_post_from_log($log) {
    2393      global $CFG, $DB;
    2394  
    2395      $allnames = get_all_user_name_fields(true, 'u');
    2396      if ($log->action == "add post") {
    2397  
    2398          return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
    2399                                   FROM {forum_discussions} d,
    2400                                        {forum_posts} p,
    2401                                        {forum} f,
    2402                                        {user} u
    2403                                  WHERE p.id = ?
    2404                                    AND d.id = p.discussion
    2405                                    AND p.userid = u.id
    2406                                    AND u.deleted <> '1'
    2407                                    AND f.id = d.forum", array($log->info));
    2408  
    2409  
    2410      } else if ($log->action == "add discussion") {
    2411  
    2412          return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
    2413                                   FROM {forum_discussions} d,
    2414                                        {forum_posts} p,
    2415                                        {forum} f,
    2416                                        {user} u
    2417                                  WHERE d.id = ?
    2418                                    AND d.firstpost = p.id
    2419                                    AND p.userid = u.id
    2420                                    AND u.deleted <> '1'
    2421                                    AND f.id = d.forum", array($log->info));
    2422      }
    2423      return NULL;
    2424  }
    2425  
    2426  /**
    2427   * Given a discussion id, return the first post from the discussion
    2428   *
    2429   * @global object
    2430   * @global object
    2431   * @param int $dicsussionid
    2432   * @return array
    2433   */
    2434  function forum_get_firstpost_from_discussion($discussionid) {
    2435      global $CFG, $DB;
    2436  
    2437      return $DB->get_record_sql("SELECT p.*
    2438                               FROM {forum_discussions} d,
    2439                                    {forum_posts} p
    2440                              WHERE d.id = ?
    2441                                AND d.firstpost = p.id ", array($discussionid));
    2442  }
    2443  
    2444  /**
    2445   * Returns an array of counts of replies to each discussion
    2446   *
    2447   * @global object
    2448   * @global object
    2449   * @param int $forumid
    2450   * @param string $forumsort
    2451   * @param int $limit
    2452   * @param int $page
    2453   * @param int $perpage
    2454   * @return array
    2455   */
    2456  function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
    2457      global $CFG, $DB;
    2458  
    2459      if ($limit > 0) {
    2460          $limitfrom = 0;
    2461          $limitnum  = $limit;
    2462      } else if ($page != -1) {
    2463          $limitfrom = $page*$perpage;
    2464          $limitnum  = $perpage;
    2465      } else {
    2466          $limitfrom = 0;
    2467          $limitnum  = 0;
    2468      }
    2469  
    2470      if ($forumsort == "") {
    2471          $orderby = "";
    2472          $groupby = "";
    2473  
    2474      } else {
    2475          $orderby = "ORDER BY $forumsort";
    2476          $groupby = ", ".strtolower($forumsort);
    2477          $groupby = str_replace('desc', '', $groupby);
    2478          $groupby = str_replace('asc', '', $groupby);
    2479      }
    2480  
    2481      if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
    2482          $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
    2483                    FROM {forum_posts} p
    2484                         JOIN {forum_discussions} d ON p.discussion = d.id
    2485                   WHERE p.parent > 0 AND d.forum = ?
    2486                GROUP BY p.discussion";
    2487          return $DB->get_records_sql($sql, array($forumid));
    2488  
    2489      } else {
    2490          $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
    2491                    FROM {forum_posts} p
    2492                         JOIN {forum_discussions} d ON p.discussion = d.id
    2493                   WHERE d.forum = ?
    2494                GROUP BY p.discussion $groupby $orderby";
    2495          return $DB->get_records_sql($sql, array($forumid), $limitfrom, $limitnum);
    2496      }
    2497  }
    2498  
    2499  /**
    2500   * @global object
    2501   * @global object
    2502   * @global object
    2503   * @staticvar array $cache
    2504   * @param object $forum
    2505   * @param object $cm
    2506   * @param object $course
    2507   * @return mixed
    2508   */
    2509  function forum_count_discussions($forum, $cm, $course) {
    2510      global $CFG, $DB, $USER;
    2511  
    2512      static $cache = array();
    2513  
    2514      $now = round(time(), -2); // db cache friendliness
    2515  
    2516      $params = array($course->id);
    2517  
    2518      if (!isset($cache[$course->id])) {
    2519          if (!empty($CFG->forum_enabletimedposts)) {
    2520              $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
    2521              $params[] = $now;
    2522              $params[] = $now;
    2523          } else {
    2524              $timedsql = "";
    2525          }
    2526  
    2527          $sql = "SELECT f.id, COUNT(d.id) as dcount
    2528                    FROM {forum} f
    2529                         JOIN {forum_discussions} d ON d.forum = f.id
    2530                   WHERE f.course = ?
    2531                         $timedsql
    2532                GROUP BY f.id";
    2533  
    2534          if ($counts = $DB->get_records_sql($sql, $params)) {
    2535              foreach ($counts as $count) {
    2536                  $counts[$count->id] = $count->dcount;
    2537              }
    2538              $cache[$course->id] = $counts;
    2539          } else {
    2540              $cache[$course->id] = array();
    2541          }
    2542      }
    2543  
    2544      if (empty($cache[$course->id][$forum->id])) {
    2545          return 0;
    2546      }
    2547  
    2548      $groupmode = groups_get_activity_groupmode($cm, $course);
    2549  
    2550      if ($groupmode != SEPARATEGROUPS) {
    2551          return $cache[$course->id][$forum->id];
    2552      }
    2553  
    2554      if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) {
    2555          return $cache[$course->id][$forum->id];
    2556      }
    2557  
    2558      require_once($CFG->dirroot.'/course/lib.php');
    2559  
    2560      $modinfo = get_fast_modinfo($course);
    2561  
    2562      $mygroups = $modinfo->get_groups($cm->groupingid);
    2563  
    2564      // add all groups posts
    2565      $mygroups[-1] = -1;
    2566  
    2567      list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
    2568      $params[] = $forum->id;
    2569  
    2570      if (!empty($CFG->forum_enabletimedposts)) {
    2571          $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
    2572          $params[] = $now;
    2573          $params[] = $now;
    2574      } else {
    2575          $timedsql = "";
    2576      }
    2577  
    2578      $sql = "SELECT COUNT(d.id)
    2579                FROM {forum_discussions} d
    2580               WHERE d.groupid $mygroups_sql AND d.forum = ?
    2581                     $timedsql";
    2582  
    2583      return $DB->get_field_sql($sql, $params);
    2584  }
    2585  
    2586  /**
    2587   * Get all discussions in a forum
    2588   *
    2589   * @global object
    2590   * @global object
    2591   * @global object
    2592   * @uses CONTEXT_MODULE
    2593   * @uses VISIBLEGROUPS
    2594   * @param object $cm
    2595   * @param string $forumsort
    2596   * @param bool $fullpost
    2597   * @param int $unused
    2598   * @param int $limit
    2599   * @param bool $userlastmodified
    2600   * @param int $page
    2601   * @param int $perpage
    2602   * @return array
    2603   */
    2604  function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
    2605      global $CFG, $DB, $USER;
    2606  
    2607      $timelimit = '';
    2608  
    2609      $now = round(time(), -2);
    2610      $params = array($cm->instance);
    2611  
    2612      $modcontext = context_module::instance($cm->id);
    2613  
    2614      if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
    2615          return array();
    2616      }
    2617  
    2618      if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
    2619  
    2620          if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
    2621              $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
    2622              $params[] = $now;
    2623              $params[] = $now;
    2624              if (isloggedin()) {
    2625                  $timelimit .= " OR d.userid = ?";
    2626                  $params[] = $USER->id;
    2627              }
    2628              $timelimit .= ")";
    2629          }
    2630      }
    2631  
    2632      if ($limit > 0) {
    2633          $limitfrom = 0;
    2634          $limitnum  = $limit;
    2635      } else if ($page != -1) {
    2636          $limitfrom = $page*$perpage;
    2637          $limitnum  = $perpage;
    2638      } else {
    2639          $limitfrom = 0;
    2640          $limitnum  = 0;
    2641      }
    2642  
    2643      $groupmode    = groups_get_activity_groupmode($cm);
    2644      $currentgroup = groups_get_activity_group($cm);
    2645  
    2646      if ($groupmode) {
    2647          if (empty($modcontext)) {
    2648              $modcontext = context_module::instance($cm->id);
    2649          }
    2650  
    2651          if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
    2652              if ($currentgroup) {
    2653                  $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
    2654                  $params[] = $currentgroup;
    2655              } else {
    2656                  $groupselect = "";
    2657              }
    2658  
    2659          } else {
    2660              //seprate groups without access all
    2661              if ($currentgroup) {
    2662                  $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
    2663                  $params[] = $currentgroup;
    2664              } else {
    2665                  $groupselect = "AND d.groupid = -1";
    2666              }
    2667          }
    2668      } else {
    2669          $groupselect = "";
    2670      }
    2671  
    2672  
    2673      if (empty($forumsort)) {
    2674          $forumsort = "d.timemodified DESC";
    2675      }
    2676      if (empty($fullpost)) {
    2677          $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
    2678      } else {
    2679          $postdata = "p.*";
    2680      }
    2681  
    2682      if (empty($userlastmodified)) {  // We don't need to know this
    2683          $umfields = "";
    2684          $umtable  = "";
    2685      } else {
    2686          $umfields = ', ' . get_all_user_name_fields(true, 'um', null, 'um');
    2687          $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
    2688      }
    2689  
    2690      $allnames = get_all_user_name_fields(true, 'u');
    2691      $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend, $allnames,
    2692                     u.email, u.picture, u.imagealt $umfields
    2693                FROM {forum_discussions} d
    2694                     JOIN {forum_posts} p ON p.discussion = d.id
    2695                     JOIN {user} u ON p.userid = u.id
    2696                     $umtable
    2697               WHERE d.forum = ? AND p.parent = 0
    2698                     $timelimit $groupselect
    2699            ORDER BY $forumsort";
    2700      return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
    2701  }
    2702  
    2703  /**
    2704   * Gets the neighbours (previous and next) of a discussion.
    2705   *
    2706   * The calculation is based on the timemodified of the discussion and does not handle
    2707   * the neighbours having an identical timemodified. The reason is that we do not have any
    2708   * other mean to sort the records, e.g. we cannot use IDs as a greater ID can have a lower
    2709   * timemodified.
    2710   *
    2711   * Please note that this does not check whether or not the discussion passed is accessible
    2712   * by the user, it simply uses it as a reference to find the neighbours. On the other hand,
    2713   * the returned neighbours are checked and are accessible to the current user.
    2714   *
    2715   * @param object $cm The CM record.
    2716   * @param object $discussion The discussion record.
    2717   * @return array That always contains the keys 'prev' and 'next'. When there is a result
    2718   *               they contain the record with minimal information such as 'id' and 'name'.
    2719   *               When the neighbour is not found the value is false.
    2720   */
    2721  function forum_get_discussion_neighbours($cm, $discussion) {
    2722      global $CFG, $DB, $USER;
    2723  
    2724      if ($cm->instance != $discussion->forum) {
    2725          throw new coding_exception('Discussion is not part of the same forum.');
    2726      }
    2727  
    2728      $neighbours = array('prev' => false, 'next' => false);
    2729      $now = round(time(), -2);
    2730      $params = array();
    2731  
    2732      $modcontext = context_module::instance($cm->id);
    2733      $groupmode    = groups_get_activity_groupmode($cm);
    2734      $currentgroup = groups_get_activity_group($cm);
    2735  
    2736      // Users must fulfill timed posts.
    2737      $timelimit = '';
    2738      if (!empty($CFG->forum_enabletimedposts)) {
    2739          if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
    2740              $timelimit = ' AND ((d.timestart <= :tltimestart AND (d.timeend = 0 OR d.timeend > :tltimeend))';
    2741              $params['tltimestart'] = $now;
    2742              $params['tltimeend'] = $now;
    2743              if (isloggedin()) {
    2744                  $timelimit .= ' OR d.userid = :tluserid';
    2745                  $params['tluserid'] = $USER->id;
    2746              }
    2747              $timelimit .= ')';
    2748          }
    2749      }
    2750  
    2751      // Limiting to posts accessible according to groups.
    2752      $groupselect = '';
    2753      if ($groupmode) {
    2754          if ($groupmode == VISIBLEGROUPS || has_capability('moodle/site:accessallgroups', $modcontext)) {
    2755              if ($currentgroup) {
    2756                  $groupselect = 'AND (d.groupid = :groupid OR d.groupid = -1)';
    2757                  $params['groupid'] = $currentgroup;
    2758              }
    2759          } else {
    2760              if ($currentgroup) {
    2761                  $groupselect = 'AND (d.groupid = :groupid OR d.groupid = -1)';
    2762                  $params['groupid'] = $currentgroup;
    2763              } else {
    2764                  $groupselect = 'AND d.groupid = -1';
    2765              }
    2766          }
    2767      }
    2768  
    2769      $params['forumid'] = $cm->instance;
    2770      $params['discid'] = $discussion->id;
    2771      $params['disctimemodified'] = $discussion->timemodified;
    2772  
    2773      $sql = "SELECT d.id, d.name, d.timemodified, d.groupid, d.timestart, d.timeend
    2774                FROM {forum_discussions} d
    2775               WHERE d.forum = :forumid
    2776                 AND d.id <> :discid
    2777                     $timelimit
    2778                     $groupselect";
    2779  
    2780      $prevsql = $sql . " AND d.timemodified < :disctimemodified
    2781                     ORDER BY d.timemodified DESC";
    2782  
    2783      $nextsql = $sql . " AND d.timemodified > :disctimemodified
    2784                     ORDER BY d.timemodified ASC";
    2785  
    2786      $neighbours['prev'] = $DB->get_record_sql($prevsql, $params, IGNORE_MULTIPLE);
    2787      $neighbours['next'] = $DB->get_record_sql($nextsql, $params, IGNORE_MULTIPLE);
    2788  
    2789      return $neighbours;
    2790  }
    2791  
    2792  /**
    2793   *
    2794   * @global object
    2795   * @global object
    2796   * @global object
    2797   * @uses CONTEXT_MODULE
    2798   * @uses VISIBLEGROUPS
    2799   * @param object $cm
    2800   * @return array
    2801   */
    2802  function forum_get_discussions_unread($cm) {
    2803      global $CFG, $DB, $USER;
    2804  
    2805      $now = round(time(), -2);
    2806      $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
    2807  
    2808      $params = array();
    2809      $groupmode    = groups_get_activity_groupmode($cm);
    2810      $currentgroup = groups_get_activity_group($cm);
    2811  
    2812      if ($groupmode) {
    2813          $modcontext = context_module::instance($cm->id);
    2814  
    2815          if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
    2816              if ($currentgroup) {
    2817                  $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
    2818                  $params['currentgroup'] = $currentgroup;
    2819              } else {
    2820                  $groupselect = "";
    2821              }
    2822  
    2823          } else {
    2824              //separate groups without access all
    2825              if ($currentgroup) {
    2826                  $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
    2827                  $params['currentgroup'] = $currentgroup;
    2828              } else {
    2829                  $groupselect = "AND d.groupid = -1";
    2830              }
    2831          }
    2832      } else {
    2833          $groupselect = "";
    2834      }
    2835  
    2836      if (!empty($CFG->forum_enabletimedposts)) {
    2837          $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
    2838          $params['now1'] = $now;
    2839          $params['now2'] = $now;
    2840      } else {
    2841          $timedsql = "";
    2842      }
    2843  
    2844      $sql = "SELECT d.id, COUNT(p.id) AS unread
    2845                FROM {forum_discussions} d
    2846                     JOIN {forum_posts} p     ON p.discussion = d.id
    2847                     LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
    2848               WHERE d.forum = {$cm->instance}
    2849                     AND p.modified >= :cutoffdate AND r.id is NULL
    2850                     $groupselect
    2851                     $timedsql
    2852            GROUP BY d.id";
    2853      $params['cutoffdate'] = $cutoffdate;
    2854  
    2855      if ($unreads = $DB->get_records_sql($sql, $params)) {
    2856          foreach ($unreads as $unread) {
    2857              $unreads[$unread->id] = $unread->unread;
    2858          }
    2859          return $unreads;
    2860      } else {
    2861          return array();
    2862      }
    2863  }
    2864  
    2865  /**
    2866   * @global object
    2867   * @global object
    2868   * @global object
    2869   * @uses CONEXT_MODULE
    2870   * @uses VISIBLEGROUPS
    2871   * @param object $cm
    2872   * @return array
    2873   */
    2874  function forum_get_discussions_count($cm) {
    2875      global $CFG, $DB, $USER;
    2876  
    2877      $now = round(time(), -2);
    2878      $params = array($cm->instance);
    2879      $groupmode    = groups_get_activity_groupmode($cm);
    2880      $currentgroup = groups_get_activity_group($cm);
    2881  
    2882      if ($groupmode) {
    2883          $modcontext = context_module::instance($cm->id);
    2884  
    2885          if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
    2886              if ($currentgroup) {
    2887                  $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
    2888                  $params[] = $currentgroup;
    2889              } else {
    2890                  $groupselect = "";
    2891              }
    2892  
    2893          } else {
    2894              //seprate groups without access all
    2895              if ($currentgroup) {
    2896                  $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
    2897                  $params[] = $currentgroup;
    2898              } else {
    2899                  $groupselect = "AND d.groupid = -1";
    2900              }
    2901          }
    2902      } else {
    2903          $groupselect = "";
    2904      }
    2905  
    2906      $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
    2907  
    2908      $timelimit = "";
    2909  
    2910      if (!empty($CFG->forum_enabletimedposts)) {
    2911  
    2912          $modcontext = context_module::instance($cm->id);
    2913  
    2914          if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
    2915              $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
    2916              $params[] = $now;
    2917              $params[] = $now;
    2918              if (isloggedin()) {
    2919                  $timelimit .= " OR d.userid = ?";
    2920                  $params[] = $USER->id;
    2921              }
    2922              $timelimit .= ")";
    2923          }
    2924      }
    2925  
    2926      $sql = "SELECT COUNT(d.id)
    2927                FROM {forum_discussions} d
    2928                     JOIN {forum_posts} p ON p.discussion = d.id
    2929               WHERE d.forum = ? AND p.parent = 0
    2930                     $groupselect $timelimit";
    2931  
    2932      return $DB->get_field_sql($sql, $params);
    2933  }
    2934  
    2935  
    2936  // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
    2937  
    2938  
    2939  /**
    2940   * @global object
    2941   * @global object
    2942   * @param int $courseid
    2943   * @param string $type
    2944   */
    2945  function forum_get_course_forum($courseid, $type) {
    2946  // How to set up special 1-per-course forums
    2947      global $CFG, $DB, $OUTPUT, $USER;
    2948  
    2949      if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
    2950          // There should always only be ONE, but with the right combination of
    2951          // errors there might be more.  In this case, just return the oldest one (lowest ID).
    2952          foreach ($forums as $forum) {
    2953              return $forum;   // ie the first one
    2954          }
    2955      }
    2956  
    2957      // Doesn't exist, so create one now.
    2958      $forum = new stdClass();
    2959      $forum->course = $courseid;
    2960      $forum->type = "$type";
    2961      if (!empty($USER->htmleditor)) {
    2962          $forum->introformat = $USER->htmleditor;
    2963      }
    2964      switch ($forum->type) {
    2965          case "news":
    2966              $forum->name  = get_string("namenews", "forum");
    2967              $forum->intro = get_string("intronews", "forum");
    2968              $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
    2969              $forum->assessed = 0;
    2970              if ($courseid == SITEID) {
    2971                  $forum->name  = get_string("sitenews");
    2972                  $forum->forcesubscribe = 0;
    2973              }
    2974              break;
    2975          case "social":
    2976              $forum->name  = get_string("namesocial", "forum");
    2977              $forum->intro = get_string("introsocial", "forum");
    2978              $forum->assessed = 0;
    2979              $forum->forcesubscribe = 0;
    2980              break;
    2981          case "blog":
    2982              $forum->name = get_string('blogforum', 'forum');
    2983              $forum->intro = get_string('introblog', 'forum');
    2984              $forum->assessed = 0;
    2985              $forum->forcesubscribe = 0;
    2986              break;
    2987          default:
    2988              echo $OUTPUT->notification("That forum type doesn't exist!");
    2989              return false;
    2990              break;
    2991      }
    2992  
    2993      $forum->timemodified = time();
    2994      $forum->id = $DB->insert_record("forum", $forum);
    2995  
    2996      if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
    2997          echo $OUTPUT->notification("Could not find forum module!!");
    2998          return false;
    2999      }
    3000      $mod = new stdClass();
    3001      $mod->course = $courseid;
    3002      $mod->module = $module->id;
    3003      $mod->instance = $forum->id;
    3004      $mod->section = 0;
    3005      include_once("$CFG->dirroot/course/lib.php");
    3006      if (! $mod->coursemodule = add_course_module($mod) ) {
    3007          echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
    3008          return false;
    3009      }
    3010      $sectionid = course_add_cm_to_section($courseid, $mod->coursemodule, 0);
    3011      return $DB->get_record("forum", array("id" => "$forum->id"));
    3012  }
    3013  
    3014  
    3015  /**
    3016   * Given the data about a posting, builds up the HTML to display it and
    3017   * returns the HTML in a string.  This is designed for sending via HTML email.
    3018   *
    3019   * @global object
    3020   * @param object $course
    3021   * @param object $cm
    3022   * @param object $forum
    3023   * @param object $discussion
    3024   * @param object $post
    3025   * @param object $userform
    3026   * @param object $userto
    3027   * @param bool $ownpost
    3028   * @param bool $reply
    3029   * @param bool $link
    3030   * @param bool $rate
    3031   * @param string $footer
    3032   * @return string
    3033   */
    3034  function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
    3035                                $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
    3036  
    3037      global $CFG, $OUTPUT;
    3038  
    3039      $modcontext = context_module::instance($cm->id);
    3040  
    3041      if (!isset($userto->viewfullnames[$forum->id])) {
    3042          $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
    3043      } else {
    3044          $viewfullnames = $userto->viewfullnames[$forum->id];
    3045      }
    3046  
    3047      // add absolute file links
    3048      $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
    3049  
    3050      // format the post body
    3051      $options = new stdClass();
    3052      $options->para = true;
    3053      $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
    3054  
    3055      $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
    3056  
    3057      $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
    3058      $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
    3059      $output .= '</td>';
    3060  
    3061      if ($post->parent) {
    3062          $output .= '<td class="topic">';
    3063      } else {
    3064          $output .= '<td class="topic starter">';
    3065      }
    3066      $output .= '<div class="subject">'.format_string($post->subject).'</div>';
    3067  
    3068      $fullname = fullname($userfrom, $viewfullnames);
    3069      $by = new stdClass();
    3070      $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
    3071      $by->date = userdate($post->modified, '', $userto->timezone);
    3072      $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
    3073  
    3074      $output .= '</td></tr>';
    3075  
    3076      $output .= '<tr><td class="left side" valign="top">';
    3077  
    3078      if (isset($userfrom->groups)) {
    3079          $groups = $userfrom->groups[$forum->id];
    3080      } else {
    3081          $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
    3082      }
    3083  
    3084      if ($groups) {
    3085          $output .= print_group_picture($groups, $course->id, false, true, true);
    3086      } else {
    3087          $output .= '&nbsp;';
    3088      }
    3089  
    3090      $output .= '</td><td class="content">';
    3091  
    3092      $attachments = forum_print_attachments($post, $cm, 'html');
    3093      if ($attachments !== '') {
    3094          $output .= '<div class="attachments">';
    3095          $output .= $attachments;
    3096          $output .= '</div>';
    3097      }
    3098  
    3099      $output .= $formattedtext;
    3100  
    3101  // Commands
    3102      $commands = array();
    3103  
    3104      if ($post->parent) {
    3105          $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
    3106                        $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
    3107      }
    3108  
    3109      if ($reply) {
    3110          $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
    3111                        get_string('reply', 'forum').'</a>';
    3112      }
    3113  
    3114      $output .= '<div class="commands">';
    3115      $output .= implode(' | ', $commands);
    3116      $output .= '</div>';
    3117  
    3118  // Context link to post if required
    3119      if ($link) {
    3120          $output .= '<div class="link">';
    3121          $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
    3122                       get_string('postincontext', 'forum').'</a>';
    3123          $output .= '</div>';
    3124      }
    3125  
    3126      if ($footer) {
    3127          $output .= '<div class="footer">'.$footer.'</div>';
    3128      }
    3129      $output .= '</td></tr></table>'."\n\n";
    3130  
    3131      return $output;
    3132  }
    3133  
    3134  /**
    3135   * Print a forum post
    3136   *
    3137   * @global object
    3138   * @global object
    3139   * @uses FORUM_MODE_THREADED
    3140   * @uses PORTFOLIO_FORMAT_PLAINHTML
    3141   * @uses PORTFOLIO_FORMAT_FILE
    3142   * @uses PORTFOLIO_FORMAT_RICHHTML
    3143   * @uses PORTFOLIO_ADD_TEXT_LINK
    3144   * @uses CONTEXT_MODULE
    3145   * @param object $post The post to print.
    3146   * @param object $discussion
    3147   * @param object $forum
    3148   * @param object $cm
    3149   * @param object $course
    3150   * @param boolean $ownpost Whether this post belongs to the current user.
    3151   * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
    3152   * @param boolean $link Just print a shortened version of the post as a link to the full post.
    3153   * @param string $footer Extra stuff to print after the message.
    3154   * @param string $highlight Space-separated list of terms to highlight.
    3155   * @param int $post_read true, false or -99. If we already know whether this user
    3156   *          has read this post, pass that in, otherwise, pass in -99, and this
    3157   *          function will work it out.
    3158   * @param boolean $dummyifcantsee When forum_user_can_see_post says that
    3159   *          the current user can't see this post, if this argument is true
    3160   *          (the default) then print a dummy 'you can't see this post' post.
    3161   *          If false, don't output anything at all.
    3162   * @param bool|null $istracked
    3163   * @return void
    3164   */
    3165  function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
    3166                            $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
    3167      global $USER, $CFG, $OUTPUT;
    3168  
    3169      require_once($CFG->libdir . '/filelib.php');
    3170  
    3171      // String cache
    3172      static $str;
    3173  
    3174      $modcontext = context_module::instance($cm->id);
    3175  
    3176      $post->course = $course->id;
    3177      $post->forum  = $forum->id;
    3178      $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
    3179      if (!empty($CFG->enableplagiarism)) {
    3180          require_once($CFG->libdir.'/plagiarismlib.php');
    3181          $post->message .= plagiarism_get_links(array('userid' => $post->userid,
    3182              'content' => $post->message,
    3183              'cmid' => $cm->id,
    3184              'course' => $post->course,
    3185              'forum' => $post->forum));
    3186      }
    3187  
    3188      // caching
    3189      if (!isset($cm->cache)) {
    3190          $cm->cache = new stdClass;
    3191      }
    3192  
    3193      if (!isset($cm->cache->caps)) {
    3194          $cm->cache->caps = array();
    3195          $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
    3196          $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
    3197          $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
    3198          $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
    3199          $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
    3200          $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
    3201          $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
    3202          $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
    3203          $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
    3204      }
    3205  
    3206      if (!isset($cm->uservisible)) {
    3207          $cm->uservisible = \core_availability\info_module::is_user_visible($cm, 0, false);
    3208      }
    3209  
    3210      if ($istracked && is_null($postisread)) {
    3211          $postisread = forum_tp_is_post_read($USER->id, $post);
    3212      }
    3213  
    3214      if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
    3215          $output = '';
    3216          if (!$dummyifcantsee) {
    3217              if ($return) {
    3218                  return $output;
    3219              }
    3220              echo $output;
    3221              return;
    3222          }
    3223          $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
    3224          $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix',
    3225                                                         'role' => 'region',
    3226                                                         'aria-label' => get_string('hiddenforumpost', 'forum')));
    3227          $output .= html_writer::start_tag('div', array('class'=>'row header'));
    3228          $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
    3229          if ($post->parent) {
    3230              $output .= html_writer::start_tag('div', array('class'=>'topic'));
    3231          } else {
    3232              $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
    3233          }
    3234          $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class' => 'subject',
    3235                                                                                             'role' => 'header')); // Subject.
    3236          $output .= html_writer::tag('div', get_string('forumauthorhidden', 'forum'), array('class' => 'author',
    3237                                                                                             'role' => 'header')); // Author.
    3238          $output .= html_writer::end_tag('div');
    3239          $output .= html_writer::end_tag('div'); // row
    3240          $output .= html_writer::start_tag('div', array('class'=>'row'));
    3241          $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
    3242          $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
    3243          $output .= html_writer::end_tag('div'); // row
    3244          $output .= html_writer::end_tag('div'); // forumpost
    3245  
    3246          if ($return) {
    3247              return $output;
    3248          }
    3249          echo $output;
    3250          return;
    3251      }
    3252  
    3253      if (empty($str)) {
    3254          $str = new stdClass;
    3255          $str->edit         = get_string('edit', 'forum');
    3256          $str->delete       = get_string('delete', 'forum');
    3257          $str->reply        = get_string('reply', 'forum');
    3258          $str->parent       = get_string('parent', 'forum');
    3259          $str->pruneheading = get_string('pruneheading', 'forum');
    3260          $str->prune        = get_string('prune', 'forum');
    3261          $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
    3262          $str->markread     = get_string('markread', 'forum');
    3263          $str->markunread   = get_string('markunread', 'forum');
    3264      }
    3265  
    3266      $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
    3267  
    3268      // Build an object that represents the posting user
    3269      $postuser = new stdClass;
    3270      $postuserfields = explode(',', user_picture::fields());
    3271      $postuser = username_load_fields_from_object($postuser, $post, null, $postuserfields);
    3272      $postuser->id = $post->userid;
    3273      $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
    3274      $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
    3275  
    3276      // Prepare the groups the posting user belongs to
    3277      if (isset($cm->cache->usersgroups)) {
    3278          $groups = array();
    3279          if (isset($cm->cache->usersgroups[$post->userid])) {
    3280              foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
    3281                  $groups[$gid] = $cm->cache->groups[$gid];
    3282              }
    3283          }
    3284      } else {
    3285          $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
    3286      }
    3287  
    3288      // Prepare the attachements for the post, files then images
    3289      list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
    3290  
    3291      // Determine if we need to shorten this post
    3292      $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
    3293  
    3294  
    3295      // Prepare an array of commands
    3296      $commands = array();
    3297  
    3298      // SPECIAL CASE: The front page can display a news item post to non-logged in users.
    3299      // Don't display the mark read / unread controls in this case.
    3300      if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
    3301          $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
    3302          $text = $str->markunread;
    3303          if (!$postisread) {
    3304              $url->param('mark', 'read');
    3305              $text = $str->markread;
    3306          }
    3307          if ($str->displaymode == FORUM_MODE_THREADED) {
    3308              $url->param('parent', $post->parent);
    3309          } else {
    3310              $url->set_anchor('p'.$post->id);
    3311          }
    3312          $commands[] = array('url'=>$url, 'text'=>$text);
    3313      }
    3314  
    3315      // Zoom in to the parent specifically
    3316      if ($post->parent) {
    3317          $url = new moodle_url($discussionlink);
    3318          if ($str->displaymode == FORUM_MODE_THREADED) {
    3319              $url->param('parent', $post->parent);
    3320          } else {
    3321              $url->set_anchor('p'.$post->parent);
    3322          }
    3323          $commands[] = array('url'=>$url, 'text'=>$str->parent);
    3324      }
    3325  
    3326      // Hack for allow to edit news posts those are not displayed yet until they are displayed
    3327      $age = time() - $post->created;
    3328      if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
    3329          $age = 0;
    3330      }
    3331  
    3332      if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
    3333          if (has_capability('moodle/course:manageactivities', $modcontext)) {
    3334              // The first post in single simple is the forum description.
    3335              $commands[] = array('url'=>new moodle_url('/course/modedit.php', array('update'=>$cm->id, 'sesskey'=>sesskey(), 'return'=>1)), 'text'=>$str->edit);
    3336          }
    3337      } else if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
    3338          $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
    3339      }
    3340  
    3341      if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
    3342          $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
    3343      }
    3344  
    3345      if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
    3346          // Do not allow deleting of first post in single simple type.
    3347      } else if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
    3348          $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
    3349      }
    3350  
    3351      if ($reply) {
    3352          $commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mformforum', array('reply'=>$post->id)), 'text'=>$str->reply);
    3353      }
    3354  
    3355      if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
    3356          $p = array('postid' => $post->id);
    3357          require_once($CFG->libdir.'/portfoliolib.php');
    3358          $button = new portfolio_add_button();
    3359          $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), 'mod_forum');
    3360          if (empty($attachments)) {
    3361              $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
    3362          } else {
    3363              $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
    3364          }
    3365  
    3366          $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
    3367          if (!empty($porfoliohtml)) {
    3368              $commands[] = $porfoliohtml;
    3369          }
    3370      }
    3371      // Finished building commands
    3372  
    3373  
    3374      // Begin output
    3375  
    3376      $output  = '';
    3377  
    3378      if ($istracked) {
    3379          if ($postisread) {
    3380              $forumpostclass = ' read';
    3381          } else {
    3382              $forumpostclass = ' unread';
    3383              $output .= html_writer::tag('a', '', array('name'=>'unread'));
    3384          }
    3385      } else {
    3386          // ignore trackign status if not tracked or tracked param missing
    3387          $forumpostclass = '';
    3388      }
    3389  
    3390      $topicclass = '';
    3391      if (empty($post->parent)) {
    3392          $topicclass = ' firstpost starter';
    3393      }
    3394  
    3395      $postbyuser = new stdClass;
    3396      $postbyuser->post = $post->subject;
    3397      $postbyuser->user = $postuser->fullname;
    3398      $discussionbyuser = get_string('postbyuser', 'forum', $postbyuser);
    3399      $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
    3400      $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass,
    3401                                                     'role' => 'region',
    3402                                                     'aria-label' => $discussionbyuser));
    3403      $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
    3404      $output .= html_writer::start_tag('div', array('class'=>'left picture'));
    3405      $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
    3406      $output .= html_writer::end_tag('div');
    3407  
    3408  
    3409      $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
    3410  
    3411      $postsubject = $post->subject;
    3412      if (empty($post->subjectnoformat)) {
    3413          $postsubject = format_string($postsubject);
    3414      }
    3415      $output .= html_writer::tag('div', $postsubject, array('class'=>'subject',
    3416                                                             'role' => 'heading',
    3417                                                             'aria-level' => '2'));
    3418  
    3419      $by = new stdClass();
    3420      $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
    3421      $by->date = userdate($post->modified);
    3422      $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author',
    3423                                                                                         'role' => 'heading',
    3424                                                                                         'aria-level' => '2'));
    3425  
    3426      $output .= html_writer::end_tag('div'); //topic
    3427      $output .= html_writer::end_tag('div'); //row
    3428  
    3429      $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
    3430      $output .= html_writer::start_tag('div', array('class'=>'left'));
    3431  
    3432      $groupoutput = '';
    3433      if ($groups) {
    3434          $groupoutput = print_group_picture($groups, $course->id, false, true, true);
    3435      }
    3436      if (empty($groupoutput)) {
    3437          $groupoutput = '&nbsp;';
    3438      }
    3439      $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
    3440  
    3441      $output .= html_writer::end_tag('div'); //left side
    3442      $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
    3443      $output .= html_writer::start_tag('div', array('class'=>'content'));
    3444  
    3445      $options = new stdClass;
    3446      $options->para    = false;
    3447      $options->trusted = $post->messagetrust;
    3448      $options->context = $modcontext;
    3449      if ($shortenpost) {
    3450          // Prepare shortened version by filtering the text then shortening it.
    3451          $postclass    = 'shortenedpost';
    3452          $postcontent  = format_text($post->message, $post->messageformat, $options);
    3453          $postcontent  = shorten_text($postcontent, $CFG->forum_shortpost);
    3454          $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
    3455          $postcontent .= html_writer::tag('div', '('.get_string('numwords', 'moodle', count_words($post->message)).')',
    3456              array('class'=>'post-word-count'));
    3457      } else {
    3458          // Prepare whole post
    3459          $postclass    = 'fullpost';
    3460          $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
    3461          if (!empty($highlight)) {
    3462              $postcontent = highlight($highlight, $postcontent);
    3463          }
    3464          if (!empty($forum->displaywordcount)) {
    3465              $postcontent .= html_writer::tag('div', get_string('numwords', 'moodle', count_words($post->message)),
    3466                  array('class'=>'post-word-count'));
    3467          }
    3468          $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
    3469      }
    3470  
    3471      // Output the post content
    3472      $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
    3473      $output .= html_