static function delete_old_posts()
 {
     global $CFG;
     // Check if deletion is turned off
     if (empty($CFG->forumng_permanentdeletion)) {
         return;
     }
     mtrace('Beginning forum deleted/edit message cleanup...');
     // Work out how long ago things have to have been 'deleted' before we
     // permanently delete them
     $deletebefore = time() - $CFG->forumng_permanentdeletion;
     // Handle all posts which were deleted (that long ago) or which are in
     // discussions which were deleted (that long ago)
     $mainquery = "\nFROM\n    {$CFG->prefix}forumng_posts fp\n    INNER JOIN {$CFG->prefix}forumng_discussions fd ON fd.id = fp.discussionid\n    INNER JOIN {$CFG->prefix}forumng f ON fd.forumid = f.id\nWHERE\n    (fp.deleted<>0 AND fp.deleted<{$deletebefore})\n    OR (fp.oldversion<>0 AND fp.modified<{$deletebefore})\n    OR (fd.deleted<>0 AND fd.deleted<{$deletebefore})";
     $idquery = "SELECT fp.id {$mainquery} ";
     $before = microtime(true);
     mtrace('Message search: ', '');
     $count = count_records_sql("SELECT COUNT(1) {$mainquery}");
     mtrace(round(microtime(true) - $before, 1) . 's');
     if ($count == 0) {
         mtrace("No old deleted / edited messages to clean up.");
     } else {
         mtrace("Permanently deleting {$count} old deleted / edited messages.");
     }
     if ($count) {
         $before = microtime(true);
         mtrace('Database post deletion: ', '');
         forum_utils::start_transaction();
         // Delete all ratings
         forum_utils::execute_sql("DELETE FROM {$CFG->prefix}forumng_ratings WHERE postid IN ({$idquery})");
         // Find all attachments
         $attachmentrecords = forum_utils::get_records_sql("\nSELECT\n    fp.id AS postid, fd.id AS discussionid, f.id AS forumid, f.course AS courseid\n{$mainquery}\n    AND fp.attachments<>0");
         // Delete all posts
         forum_utils::update_with_subquery_grrr_mysql("DELETE FROM {$CFG->prefix}forumng_posts WHERE id %'IN'%", $idquery);
         // Now delete all discussions
         forum_utils::execute_sql("DELETE FROM {$CFG->prefix}forumng_discussions " . "WHERE deleted<>0 AND deleted<{$deletebefore}");
         mtrace(round(microtime(true) - $before, 1) . 's');
         $before = microtime(true);
         mtrace('Filesystem attachment deletion: ', '');
         // OK, now delete attachments (this is done last in case the db update
         // failed and gets rolled back)
         foreach ($attachmentrecords as $attachmentrecord) {
             $folder = forum_post::get_any_attachment_folder($attachmentrecord->courseid, $attachmentrecord->forumid, $attachmentrecord->discussionid, $attachmentrecord->postid);
             // Delete post folder
             if (!remove_dir($folder)) {
                 mtrace("\nError deleting post folder: {$folder}");
                 // But don't stop because we can't undo earlier changes
             }
         }
         // Get list of all discussions that might need deleting
         $discussions = array();
         foreach ($attachmentrecords as $attachmentrecord) {
             // This will ensure we have only one entry per discussion
             $discussions[$attachmentrecord->discussionid] = $attachmentrecord;
         }
         // Delete discussion folder only if empty (there might be other
         // not-yet-deleted posts)
         foreach ($discussions as $attachmentrecord) {
             $folder = forum_discussion::get_attachment_folder($attachmentrecord->courseid, $attachmentrecord->forumid, $attachmentrecord->discussionid);
             $handle = @opendir($folder);
             if (!$handle) {
                 mtrace("\nError opening discussion folder: {$folder}");
                 continue;
             }
             $gotfiles = false;
             while (($file = readdir($handle)) !== false) {
                 if ($file != '.' && $file != '..') {
                     $gotfiles = true;
                     break;
                 }
             }
             closedir($handle);
             if (!$gotfiles) {
                 if (!rmdir($folder)) {
                     mtrace("\nError deleting discussion folder: {$folder}");
                 }
             }
         }
         forum_utils::finish_transaction();
         mtrace(round(microtime(true) - $before, 1) . 's');
     }
 }
 /**
  * This special function is required only because of the OU shared
  * activities system. On the shared activities course, modinfo is not
  * available. We can provide a fake version, but only if specific IDs
  * are given
  * @param object $course Moodle course object
  * @param array $specificids List of course-module IDs; empty array = all
  * @return object Moodle modinfo object
  * @throws forum_exception If this is shared activities course and you're
  *   trying to list all forums on it
  */
 private static function get_modinfo_special($course, $specificids = array())
 {
     global $CFG, $FORUMNG_CACHE;
     $modinfo = get_fast_modinfo($course);
     if (class_exists('ouflags') && !count($modinfo->cms)) {
         // OU shared activities system requires a hack here so that this
         // can work on the shared activities course, which doesn't have
         // modinfo. It can only work if specific IDs are listed.
         if (count($specificids)) {
             if (!empty($FORUMNG_CACHE->modinfo_special) && $FORUMNG_CACHE->modinfo_special->specificids == $specificids) {
                 return $FORUMNG_CACHE->modinfo_special->modinfo;
             }
             $inorequals = forum_utils::in_or_equals($specificids);
             $modinfo->cms = forum_utils::get_records_sql("\nSELECT\n    cm.*, m.name AS modname, f.type AS forumtype\nFROM\n    {$CFG->prefix}course_modules cm\n    INNER JOIN {$CFG->prefix}modules m ON m.id = cm.module\n    INNER JOIN {$CFG->prefix}forumng f ON f.id = cm.instance\nWHERE\n    cm.id {$inorequals} AND m.name='forumng'");
             $modinfo->instances['forumng'] = $modinfo->cms;
             $FORUMNG_CACHE->modinfo_special->modinfo = $modinfo;
             $FORUMNG_CACHE->modinfo_special->specificids = $specificids;
         } else {
             throw new forum_exception('Cannot get_course_forums on ' . 'shared activities course without specific ID list');
         }
     }
     return $modinfo;
 }
 /**
  * Obtains a list of forums on the given course which can be converted.
  * The requirements for this are that they must have a supported forum
  * type and there must not be an existing ForumNG with the same name.
  * @param object $course
  * @return array Array of id=>name of convertable forums
  */
 public static function get_convertible_forums($course)
 {
     global $CFG;
     return forum_utils::get_records_sql("\nSELECT cm.id, f.name \nFROM\n    {$CFG->prefix}forum f\n    INNER JOIN {$CFG->prefix}course_modules cm ON cm.instance=f.id \n      AND cm.module = (SELECT id FROM {$CFG->prefix}modules WHERE name='forum')\n    LEFT JOIN {$CFG->prefix}forumng fng ON fng.name=f.name AND fng.course=f.course\nWHERE\n    cm.course={$course->id} AND f.course={$course->id} \n    AND f.type='general'\n    AND fng.id IS NULL");
 }
 /**
  * Internal function. Queries for posts.
  * @param string $where Where clause (fp is alias for post table)
  * @param string $order Sort order; the default is fp.id - note this is preferable
  *   to fp.timecreated because it works correctly if there are two posts in
  *   the same second
  * @param bool $ratings True if ratings should be included in the query
  * @param bool $flags True if flags should be included in the query
  * @param bool $effectivesubjects True if the query should include the
  *   (complicated!) logic to obtain the 'effective subject'. This may result
  *   in additional queries afterward for posts which are very deeply nested.
  * @param int $userid 0 = current user (at present this is only used for
  *   flags)
  * @return array Resulting posts as array of Moodle records, empty array
  *   if none
  */
 static function query_posts($where, $order = 'fp.id', $ratings = true, $flags = false, $effectivesubjects = false, $userid = 0, $joindiscussion = false, $discussionsubject = false, $limitfrom = '', $limitnum = '')
 {
     global $CFG, $USER;
     $userid = forum_utils::get_real_userid($userid);
     // We include ratings if these are enabled, otherwise save the database
     // some effort and don't bother
     if ($ratings) {
         $ratingsquery = ",\n(SELECT AVG(rating) FROM {$CFG->prefix}forumng_ratings\n    WHERE postid=fp.id) AS averagerating,\n(SELECT COUNT(1) FROM {$CFG->prefix}forumng_ratings\n    WHERE postid=fp.id) AS numratings,\n(SELECT rating FROM {$CFG->prefix}forumng_ratings\n    WHERE postid=fp.id AND userid={$USER->id}) AS ownrating";
     } else {
         $ratingsquery = '';
     }
     if ($flags) {
         $flagsjoin = "\n    LEFT JOIN {$CFG->prefix}forumng_flags ff ON ff.postid = fp.id AND ff.userid = {$userid}";
         $flagsquery = ",ff.flagged";
     } else {
         $flagsjoin = '';
         $flagsquery = '';
     }
     if ($joindiscussion) {
         $discussionjoin = "\n    INNER JOIN {$CFG->prefix}forumng_discussions fd ON fp.discussionid = fd.id";
         $discussionquery = ',' . forum_utils::select_discussion_fields('fd');
         if ($discussionsubject) {
             $discussionjoin .= "\n    INNER JOIN {$CFG->prefix}forumng_posts fdfp ON fd.postid = fdfp.id";
             $discussionquery .= ', fdfp.subject AS fd_subject';
         }
     } else {
         $discussionjoin = '';
         $discussionquery = '';
     }
     if ($effectivesubjects) {
         $maxdepth = self::PARENTPOST_DEPTH_PER_QUERY;
         $subjectsjoin = '';
         $subjectsquery = ", p{$maxdepth}.parentpostid AS nextparent ";
         for ($depth = 2; $depth <= $maxdepth; $depth++) {
             $subjectsquery .= ", p{$depth}.subject AS s{$depth}, p{$depth}.deleted AS d{$depth}";
             $prev = 'p' . ($depth - 1);
             if ($prev == 'p1') {
                 $prev = 'fp';
             }
             $subjectsjoin .= "LEFT JOIN {$CFG->prefix}forumng_posts p{$depth}\n                    ON p{$depth}.id = {$prev}.parentpostid ";
         }
     } else {
         $subjectsjoin = '';
         $subjectsquery = '';
     }
     // Retrieve posts from discussion with incorporated user information
     // and ratings info if specified
     $results = forum_utils::get_records_sql("\nSELECT\n    fp.*,\n    " . forum_utils::select_username_fields('u') . ",\n    " . forum_utils::select_username_fields('eu') . ",\n    " . forum_utils::select_username_fields('du') . "\n    {$ratingsquery}\n    {$flagsquery}\n    {$subjectsquery}\n    {$discussionquery}\nFROM\n    {$CFG->prefix}forumng_posts fp\n    INNER JOIN {$CFG->prefix}user u ON fp.userid=u.id\n    LEFT JOIN {$CFG->prefix}user eu ON fp.edituserid=eu.id\n    LEFT JOIN {$CFG->prefix}user du ON fp.deleteuserid=du.id    \n    {$discussionjoin}\n    {$flagsjoin}\n    {$subjectsjoin}\nWHERE\n    {$where}\nORDER BY\n    {$order}\n", $limitfrom, $limitnum);
     if ($effectivesubjects) {
         // Figure out the effective subject for each result
         foreach ($results as $result) {
             $got = false;
             if ($result->subject !== null) {
                 $result->effectivesubject = $result->subject;
                 $got = true;
                 continue;
             }
             for ($depth = 2; $depth <= $maxdepth; $depth++) {
                 $var = "s{$depth}";
                 $var2 = "d{$depth}";
                 if (!$got && $result->{$var} !== null && $result->{$var2} == 0) {
                     $result->effectivesubject = get_string('re', 'forumng', $result->{$var});
                     $got = true;
                 }
                 unset($result->{$var});
                 unset($result->{$var2});
             }
             if (!$got) {
                 // Do extra queries to pick up subjects for posts where it
                 // was unknown within the default depth. We can use the
                 // 'nextparent' to get the ID of the parent post of the last
                 // one that we checked already
                 $result->effectivesubject = self::inner_get_recursive_subject($result->nextparent);
             }
         }
     }
     return $results;
 }