/**
  * Internal method. Queries for a number of forums, including additional
  * data about unread posts etc. Returns the database result.
  * @param array $cmids If specified, array of course-module IDs of desired
  *   forums
  * @param object $course If specified, course object
  * @param int $userid User ID, 0 = current user
  * @param int $unread Type of unread data to obtain (UNREAD_xx constant).
  * @param array $groups Array of group IDs to which the given user belongs
  *   (may be null if unread data not required)
  * @param array $aagforums Array of forums in which the user has
  *   'access all groups' (may be null if unread data not required)
  * @param array $viewhiddenforums Array of forums in which the user has
  *   'view hidden discussions' (may be null if unread data not required)
  * @return array Array of row objects
  */
 private static function query_forums($cmids = array(), $course = null, $userid, $unread, $groups, $aagforums, $viewhiddenforums)
 {
     global $DB, $CFG, $USER;
     if (!count($cmids) && !$course) {
         throw new coding_exception("mod_forumng::query_forums requires course id or cmids");
     }
     if (count($cmids)) {
         list($in, $conditionsparams) = mod_forumng_utils::get_in_array_sql('cm.id', $cmids);
         $conditions = $in;
     } else {
         $conditions = "f.course = ?";
         $conditionsparams = array($course->id);
     }
     $singleforum = count($cmids) == 1 ? reset($cmids) : false;
     list($inviewhiddenforums, $inviewhiddenforumsparams) = mod_forumng_utils::get_in_array_sql('fd.forumngid', $viewhiddenforums);
     list($cfdinviewhiddenforums, $inviewhiddenforumsparams) = mod_forumng_utils::get_in_array_sql('cfd.forumngid', $viewhiddenforums);
     // This array of additional results is used later if combining
     // standard results with single-forum calls.
     $plusresult = array();
     // For read tracking, we get a count of total number of posts in
     // forum, and total number of read posts in the forum (this
     // is so we can display the number of UNread posts, but the query
     // works that way around because it will return 0 if no read
     // information is stored).
     if ($unread != self::UNREAD_NONE && self::enabled_read_tracking()) {
         // Work out when unread status ends
         $endtime = time() - $CFG->forumng_readafterdays * 24 * 3600;
         if (!$userid) {
             $userid = $USER->id;
         }
         list($ingroups, $ingroupsparams) = mod_forumng_utils::get_in_array_sql('fd.groupid', $groups);
         list($inaagforums, $inaagforumsparams) = mod_forumng_utils::get_in_array_sql('fd.forumngid', $aagforums);
         $restrictionsql = '';
         $restrictionparams = array();
         if ($singleforum) {
             // If it is for a single forum, get the restriction from the
             // forum type
             $forum = self::get_from_cmid($singleforum, self::CLONE_DIRECT);
             $type = $forum->get_type();
             if ($type->has_unread_restriction()) {
                 list($value, $restrictionparams) = $type->get_unread_restriction_sql($forum);
                 if ($value) {
                     $restrictionsql = 'AND ' . $value;
                 }
             }
         } else {
             // When it is not for a single forum, we can only group together
             // results for types that do not place restrictions on the
             // unread count.
             $modinfo = get_fast_modinfo($course);
             $okayids = array();
             if (array_key_exists('forumng', $modinfo->instances)) {
                 foreach ($modinfo->instances['forumng'] as $info) {
                     if (count($cmids) && !in_array($info->id, $cmids)) {
                         continue;
                     }
                     $type = self::get_type_from_modinfo_info($info);
                     if (forumngtype::get_new($type)->has_unread_restriction()) {
                         // This one's a problem! Do it individually
                         $problemresults = self::query_forums(array($info->id), null, $userid, $unread, $groups, $aagforums, $viewhiddenforums);
                         foreach ($problemresults as $problemresult) {
                             $plusresult[$problemresult->f_id] = $problemresult;
                         }
                     } else {
                         $okayids[] = $info->id;
                     }
                 }
             }
             if (count($okayids) == 0) {
                 // There are no 'normal' forums, so return result so far
                 // after sorting it
                 uasort($plusresult, 'mod_forumng::sort_mod_forumng_result');
                 return $plusresult;
             } else {
                 // Fall through to normal calculation, but change conditions
                 // to include only the 'normal' forums
                 list($in, $inparams) = mod_forumng_utils::get_in_array_sql('cm.id', $okayids);
                 $conditions .= " AND " . $in;
                 $conditionsparams = array_merge($conditionsparams, $inparams);
             }
         }
         $indreadpart = '';
         $indreadparms = array();
         $indreadwhere = '';
         // Get individual posts unread if manual read marking (on unread discussions only).
         if (!mod_forumng::mark_read_automatically($userid)) {
             $indreadpart = "INNER JOIN {forumng_posts} fp ON fp.discussionid = discussions.id\n                                 LEFT JOIN {forumng_read_posts} frp ON frp.postid = fp.id AND frp.userid = ?";
             $indreadwhere = "AND frp.id IS NULL\n                                 AND ((fp.edituserid IS NOT NULL AND fp.edituserid <> ?)\n                                       OR (fp.edituserid IS NULL AND fp.userid <> ?))\n                                 AND fp.deleted = ?\n                                 AND fp.oldversion = ?\n                                 AND fp.modified > ?\n                                 AND (discussions.time IS NULL OR fp.modified > discussions.time)";
             $indreadparms = array($userid, $userid, $userid, 0, 0, $endtime);
         }
         // NOTE fpfirst is used only by forum types, not here
         $now = time();
         $sharedquerypart = "\n        FROM\n     (SELECT fd.id, fr.time, fd.forumngid\n        FROM {forumng_discussions} fd\n  INNER JOIN {forumng_posts} fplast ON fd.lastpostid = fplast.id\n  INNER JOIN {forumng_posts} fpfirst ON fd.postid = fpfirst.id\n   LEFT JOIN {forumng_read} fr ON fd.id = fr.discussionid AND fr.userid = ?\n  INNER JOIN {course_modules} cm2 ON cm2.instance = fd.forumngid\n             AND cm2.module = (SELECT id FROM {modules} WHERE name = 'forumng')\n       WHERE fplast.modified > ?\n         AND (\n             (fd.groupid IS NULL)\n             OR ({$ingroups})\n             OR cm2.groupmode = " . VISIBLEGROUPS . "\n             OR ({$inaagforums})\n         )\n         AND fd.deleted = 0\n         AND (\n             ((fd.timestart = 0 OR fd.timestart <= ?)\n             AND (fd.timeend = 0 OR fd.timeend > ? OR ({$inviewhiddenforums})))\n         )\n         AND ((fplast.edituserid IS NOT NULL AND fplast.edituserid <> ?)\n          OR fplast.userid <> ?)\n         AND (fr.time IS NULL OR fplast.modified > fr.time)\n    {$restrictionsql}\n    ) discussions\n    {$indreadpart}\n       WHERE discussions.forumngid = f.id\n    {$indreadwhere}";
         $sharedqueryparams = array_merge(array($userid, $endtime), $ingroupsparams, $inaagforumsparams, array($now, $now), $inviewhiddenforumsparams, array($userid, $userid), $restrictionparams, $indreadparms);
         // Note: There is an unusual case in which this number can
         // be inaccurate. It is to do with ignoring messages the user
         // posted. We consider a discussion as 'not unread' if the last
         // message is by current user. In actual fact, a discussion could
         // contain unread messages if messages were posted by other users
         // after this user viewed the forum last, but before they posted
         // their reply. Since this should be an infrequent occurrence I
         // believe this behaviour is acceptable.
         if ($unread == self::UNREAD_BINARY) {
             // Query to get 0/1 unread discussions count
             $readtracking = self::select_exists("SELECT 1 {$sharedquerypart}") . "AS f_hasunreaddiscussions";
             $readtrackingparams = $sharedqueryparams;
         } else {
             // Query to get full unread discussions count
             $readtracking = "\n(SELECT\n    COUNT(DISTINCT discussions.id)\n{$sharedquerypart}\n) AS f_numunreaddiscussions";
             $readtrackingparams = $sharedqueryparams;
         }
     } else {
         $readtracking = "NULL AS numreadposts, NULL AS timeread";
         $readtrackingparams = array();
     }
     $now = time();
     $orderby = "LOWER(f.name)";
     // Main query. This retrieves:
     // - Full forum fields
     // - Basic course-module and course data (not whole tables)
     // - Discussion count
     // - Unread data, if enabled
     // - User subscription data
     $result = $DB->get_records_sql($sql = "\nSELECT\n    " . mod_forumng_utils::select_mod_forumng_fields('f') . ",\n    " . mod_forumng_utils::select_course_module_fields('cm') . ",\n    " . mod_forumng_utils::select_course_fields('c') . ",\n    (SELECT COUNT(1)\n        FROM {forumng_discussions} cfd\n        WHERE cfd.forumngid = f.id AND cfd.deleted = 0\n        AND (\n            ((cfd.timestart = 0 OR cfd.timestart <= ?)\n            AND (cfd.timeend = 0 OR cfd.timeend > ?))\n            OR ({$cfdinviewhiddenforums})\n        )\n        ) AS f_numdiscussions,\n    {$readtracking}\nFROM\n    {forumng} f\n    INNER JOIN {course_modules} cm ON cm.instance = f.id\n        AND cm.module = (SELECT id from {modules} WHERE name = 'forumng')\n    INNER JOIN {course} c ON c.id = f.course\nWHERE\n    {$conditions}\nORDER BY\n    {$orderby}", array_merge(array($now, $now), $inviewhiddenforumsparams, $readtrackingparams, $conditionsparams));
     if (count($plusresult) > 0) {
         foreach ($plusresult as $key => $value) {
             $result[$key] = $value;
         }
         uasort($result, 'mod_forumng::sort_mod_forumng_result');
     }
     return $result;
 }
 /**
  * Obtains a search document given the ousearch parameters.
  * @param object $document Object containing fields from the ousearch documents table
  * @return mixed False if object can't be found, otherwise object containing the following
  *   fields: ->content, ->title, ->url, ->activityname, ->activityurl,
  *   and optionally ->extrastrings array, ->data, ->hide
  */
 public static function search_get_page($document)
 {
     global $DB, $CFG, $USER;
     // Implemented directly in SQL for performance, rather than using the
     // objects themselves
     $result = $DB->get_record_sql("\nSELECT\n    fp.message AS content, fp.subject, firstpost.subject AS firstpostsubject,\n    firstpost.id AS firstpostid, fd.id AS discussionid,\n    f.name AS activityname, cm.id AS cmid, fd.timestart, fd.timeend,\n    f.shared AS shared, f.type AS forumtype\nFROM\n    {forumng_posts} fp\n    INNER JOIN {forumng_discussions} fd ON fd.id = fp.discussionid\n    INNER JOIN {forumng_posts} firstpost ON fd.postid = firstpost.id\n    INNER JOIN {forumng} f ON fd.forumngid = f.id\n    INNER JOIN {course_modules} cm ON cm.instance = f.id AND cm.course = f.course\n    INNER JOIN {modules} m ON cm.module = m.id\nWHERE\n    fp.id = ? AND m.name = 'forumng'", array($document->intref1), '*', IGNORE_MISSING);
     if (!$result) {
         return false;
     }
     // Title is either the post subject or Re: plus the discussion subject
     // if the post subject is blank
     $result->title = $result->subject;
     if (is_null($result->title)) {
         $result->title = get_string('re', 'forumng', $result->firstpostsubject);
     }
     // Link is to value in url if present, otherwise to original forum
     $cloneparam = $result->cmid;
     if ($result->shared) {
         global $FORUMNG_CLONE_MAP;
         if (!empty($FORUMNG_CLONE_MAP)) {
             $cloneparam = $FORUMNG_CLONE_MAP[$result->cmid]->id;
             $clonebit = '&amp;clone=' . $cloneparam;
         } else {
             $clonebit = '&amp;clone=' . ($cloneparam = optional_param('clone', $result->cmid, PARAM_INT));
         }
     } else {
         $clonebit = '';
     }
     // Work out URL to post
     $result->url = $CFG->wwwroot . '/mod/forumng/discuss.php?d=' . $result->discussionid . $clonebit . '#p' . $document->intref1;
     // Activity URL
     $result->activityurl = $CFG->wwwroot . '/mod/forumng/view.php?id=' . $result->cmid . $clonebit;
     // Hide results outside their time range (unless current user can see)
     $now = time();
     if ($now < $result->timestart || $result->timeend && $now >= $result->timeend && !has_capability('mod/forumng:viewallposts', context_module::instance($result->cmid))) {
         $result->hide = true;
     }
     // Handle annoying forum types that hide discussions
     $type = forumngtype::get_new($result->forumtype);
     if ($type->has_unread_restriction()) {
         // TODO The name of the _unread_restriction should be _discussion_restriction.
         // This is going to be slow, we need to load the discussion
         $discussion = mod_forumng_discussion::get_from_id($result->discussionid, $cloneparam);
         if (!$type->can_view_discussion($discussion, $USER->id)) {
             $result->hide = true;
         }
     }
     return $result;
 }