/** * Creates the mail queue and runs query to obtain list of posts that should * be mailed. * @param bool $tracetimes True if it should call mtrace to display * performance information */ function __construct($tracetimes) { global $DB, $CFG; $this->time = time(); $this->forum = null; $this->discussion = null; $this->storedrecord = null; $this->postcount = 0; // Check if an earlier run got aborted. In that case we mark all // messages as mailed anyway because it's better to skip some than // to send out double-posts. if ($pending = get_config('forumng', $this->get_pending_flag_name())) { $this->mark_mailed($pending); } // Note that we are mid-run set_config($this->get_pending_flag_name(), $this->time, 'forumng'); list($wheresql, $whereparams) = $this->get_query_where($this->time); $querychunk = $this->get_query_from() . $wheresql; $this->rs = $DB->get_recordset_sql($sql = "\nSELECT\n " . mod_forumng_utils::select_mod_forumng_fields('f') . ",\n " . mod_forumng_utils::select_discussion_fields('fd') . ",\n " . mod_forumng_utils::select_post_fields('discussionpost') . ",\n " . mod_forumng_utils::select_post_fields('fp') . ",\n " . mod_forumng_utils::select_post_fields('reply') . ",\n " . mod_forumng_utils::select_course_module_fields('cm') . ",\n " . mod_forumng_utils::select_context_fields('x') . ",\n " . mod_forumng_utils::select_username_fields('u', true) . ",\n " . mod_forumng_utils::select_username_fields('eu') . ",\n " . mod_forumng_utils::select_username_fields('replyu') . ",\n " . mod_forumng_utils::select_username_fields('replyeu') . ",\n " . mod_forumng_utils::select_course_fields('c') . ",\n clonecm.id AS cloneid\n{$querychunk}\nORDER BY\n clonecm.course, f.id, fd.id, fp.id", $whereparams); if (!empty($CFG->forumng_cronultradebug)) { $easyread = mod_forumng_utils::debug_query_for_reading($sql, $whereparams); mtrace("\n\n" . $easyread . "\n\n"); } }
/** * 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; }