/**
  * Executes a database update in such a way that it will work in MySQL,
  * when the update uses a subquery that refers to the table being updated.
  * @param string $update Update query with the special string %'IN'% at the
  *   point where the IN clause should go, i.e. replacing 'IN (SELECT id ...)' 
  * @param string $inids Query that selects a column (which must be named
  *   id), i.e. 'SELECT id ...'
  */
 public static function update_with_subquery_grrr_mysql($update, $inids)
 {
     global $CFG;
     if (preg_match('~^mysql~', $CFG->dbtype)) {
         // MySQL is a PoS so the update can't directly run (you can't update
         // a table based on a subquery that refers to the table). Instead,
         // we do the same thing but with a separate update using an IN clause.
         // This might theoretically run into problems if you had a really huge
         // set of forums with frequent posts (so that the IN size exceeds
         // MySQL query limit) however the limits appear to be generous enough
         // that this is unlikely.
         $ids = array();
         $rs = forum_utils::get_recordset_sql($inids);
         while ($rec = rs_fetch_next_record($rs)) {
             $ids[] = $rec->id;
         }
         rs_close($rs);
         if (count($ids) > 0) {
             $update = str_replace("%'IN'%", forum_utils::in_or_equals($ids), $update);
             forum_utils::execute_sql($update);
         }
     } else {
         // With a decent database we can do the update and query in one,
         // avoiding the need to transfer an ID list around.
         forum_utils::execute_sql(str_replace("%'IN'%", "IN ({$inids})", $update));
     }
 }
 /**
  * Obtains group info for a user in this discussion. Group info may be
  * cached in the discussion object in order to reduce DB queries.
  * @param int $userid User ID (must be a user who has posts in this discussion)
  *   May be 0 to pre-cache the data without returning anything
  * @param bool $cacheall If true, obtains data for all users in the
  *   discussion and caches it; set false if only one user's information
  *   is likely to be required, to do a single query
  * @return array Array of group objects containing id, name, picture
  *   (empty if none). False if $userid was 0.
  * @throws forum_exception If user is not in this discussion
  */
 public function get_user_groups($userid, $cacheall = true)
 {
     global $CFG;
     // If there is no cached data yet, and we are supposed to cache it,
     // then cache it now
     if (!$this->groupscache && $cacheall) {
         $this->groupscache = array();
         // Get list of users in discussion and initialise empty cache
         $userids = array();
         $this->get_root_post()->list_all_user_ids($userids);
         $userids = array_keys($userids);
         $inorequals = forum_utils::in_or_equals($userids);
         foreach ($userids as $auserid) {
             $this->groupscache[$auserid] = array();
         }
         // Basic IDs
         $courseid = $this->get_forum()->get_course_id();
         $discussionid = $this->get_id();
         // Grouping restriction
         if ($groupingid = $this->get_forum()->get_grouping()) {
             $groupingjoin = "INNER JOIN {$CFG->prefix}groupings_groups gg ON gg.groupid=g.id";
             $groupingcheck = "AND gg.groupingid = {$groupingid}";
         } else {
             $groupingjoin = $groupingcheck = '';
         }
         // Do query
         $rs = forum_utils::get_recordset_sql("\nSELECT\n    gm.userid, g.id, g.name, g.picture, g.hidepicture\nFROM\n    {$CFG->prefix}groups_members gm\n    INNER JOIN {$CFG->prefix}groups g ON g.id=gm.groupid\n    {$groupingjoin}\nWHERE\n    g.courseid={$courseid}\n    {$groupingcheck}\n    AND gm.userid {$inorequals}\n ");
         while ($rec = rs_fetch_next_record($rs)) {
             $auserid = $rec->userid;
             unset($rec->userid);
             $this->groupscache[$auserid][] = $rec;
         }
         rs_close($rs);
         // Update cached version to include this data
         if ($this->incache) {
             $this->cache($this->incache->userid);
         }
     }
     // If caller only wants to cache data, return false
     if (!$userid) {
         return false;
     }
     // If there is cached data, use it
     if ($this->groupscache) {
         if (!array_key_exists($userid, $this->groupscache)) {
             throw new forum_exception("Unknown discussion user");
         }
         return $this->groupscache[$userid];
     }
     // Otherwise make a query just for this user
     $groups = groups_get_all_groups($this->get_forum()->get_course_id(), $userid, $this->get_course_module()->groupingid);
     return $groups ? $groups : array();
 }
 /**
  * Splits this post to become a new discussion
  * @param $newsubject
  * @param bool $log True to log action
  * @return int ID of new discussion
  */
 function split($newsubject, $log = true)
 {
     global $CFG;
     $this->require_children();
     // Begin a transaction
     forum_utils::start_transaction();
     $olddiscussion = $this->get_discussion();
     // Create new discussion
     $newest = null;
     $this->find_newest_child($newest);
     $newdiscussionid = $olddiscussion->clone_for_split($this->get_id(), $newest->get_id());
     // Update all child posts
     $list = array();
     $this->list_child_ids($list);
     unset($list[0]);
     // Don't include this post itself
     if (count($list) > 0) {
         $inorequals = forum_utils::in_or_equals($list);
         forum_utils::execute_sql("\nUPDATE\n    {$CFG->prefix}forumng_posts\nSET\n    discussionid = {$newdiscussionid}\nWHERE\n    id {$inorequals}");
     }
     // Update this post
     $changes = new stdClass();
     $changes->id = $this->get_id();
     $changes->subject = addslashes($newsubject);
     $changes->parentpostid = null;
     //When split the post, reset the important to 0 so that it is not highlighted.
     $changes->important = 0;
     // Note don't update modified time, or it makes this post unread,
     // which isn't very helpful
     $changes->discussionid = $newdiscussionid;
     forum_utils::update_record('forumng_posts', $changes);
     // Update read data if relevant
     if (forum::enabled_read_tracking() && $newest->get_modified() >= forum::get_read_tracking_deadline()) {
         $rs = forum_utils::get_recordset_sql("\nSELECT\n    userid, time\nFROM\n    {$CFG->prefix}forumng_read\nWHERE\n    discussionid = " . $olddiscussion->get_id() . "\n    AND time >= " . $this->get_created());
         while ($rec = rs_fetch_next_record($rs)) {
             $rec->discussionid = $newdiscussionid;
             forum_utils::insert_record('forumng_read', $rec);
         }
         rs_close($rs);
     }
     $olddiscussion->possible_lastpost_change();
     // Move attachments
     $olddiscussionfolder = $olddiscussion->get_attachment_folder();
     $newdiscussionfolder = $olddiscussion->get_attachment_folder(0, 0, $newdiscussionid);
     if (is_dir($olddiscussionfolder)) {
         // Put this post back on the list
         $list[0] = $this->get_id();
         // Loop through all posts; move attachments if present
         $madenewfolder = false;
         foreach ($list as $id) {
             $oldfolder = $olddiscussionfolder . '/' . $id;
             $newfolder = $newdiscussionfolder . '/' . $id;
             if (is_dir($oldfolder)) {
                 if (!$madenewfolder) {
                     check_dir_exists($newfolder, true, true);
                     $madenewfolder = true;
                 }
                 forum_utils::rename($oldfolder, $newfolder);
             }
         }
     }
     if ($log) {
         $this->log('split post');
     }
     forum_utils::finish_transaction();
     $this->get_discussion()->uncache();
     // If discussion-based completion is turned on, this may enable someone
     // to complete
     if ($this->get_forum()->get_completion_discussions()) {
         $this->update_completion(true);
     }
     return $newdiscussionid;
 }
 /**
  * Gets all users within this forum who are supposed to be 'monitored'
  * (that means users who are in the monitorroles setting).
  *
  * Note: In Moodle 2 we should be able to replace this with getting the
  * enrolled users for the course, I think?
  * @param int $groupid Group ID or ALL_GROUPS/NO_GROUPS to get all users
  */
 function get_monitored_users($groupid)
 {
     global $CFG;
     if ($groupid > 0) {
         //Get all users from the chosen group
         $sql = "SELECT u.id, u.lastname, u.firstname, u.username, gm.groupid\n                FROM " . $CFG->prefix . "groups_members gm\n                JOIN " . $CFG->prefix . "user u ON u.id = gm.userid\n                WHERE gm.groupid = {$groupid} \n                ORDER BY u.lastname, u.firstname";
         if ($users = get_records_sql($sql)) {
             return $users;
         }
     } else {
         // Get roleids from the monitor roles setting
         if (!($roleids = forum_utils::safe_explode(',', $CFG->forumng_monitorroles))) {
             return array();
         }
         $roleidfind = forum_utils::in_or_equals($roleids);
         $context = $this->get_context();
         $contextids = forum_utils::safe_explode('/', $context->path);
         $contextidfind = forum_utils::in_or_equals($contextids);
         $sql = "SELECT u.id, u.lastname, u.firstname, u.username\n                    FROM " . $CFG->prefix . "role_assignments ra\n                    JOIN " . $CFG->prefix . "user u ON u.id = ra.userid\n                    WHERE ra.roleid {$roleidfind} AND ra.contextid {$contextidfind}\n                    ORDER BY u.lastname, u.firstname";
         if ($users = get_records_sql($sql)) {
             return $users;
         }
     }
     return array();
 }
/**
 * Get search results.
 * @param object $course
 * @param string $author
 * @param int $daterangefrom
 * @param int $daterangeto
 * @param int $page
 * @param int $resultsperpage (FORUMNG_SEARCH_RESULTSPERPAGE used as constant)
 * @return object
 */
function forumng_get_results_for_all_forums($course, $author = null, $daterangefrom = 0, $daterangeto = 0, $page, $resultsperpage = FORUMNG_SEARCH_RESULTSPERPAGE)
{
    $before = microtime(true);
    global $CFG, $USER;
    // Get all forums
    $modinfo = get_fast_modinfo($course);
    $visibleforums = array();
    $accessallgroups = array();
    foreach ($modinfo->cms as $cmid => $cm) {
        if ($cm->modname === 'forumng' && $cm->uservisible) {
            $visibleforums[$cm->instance] = $cm->groupmode;
            // Check access all groups for this forum, if they have it, add to list
            //$forum = forum::get_from_cmid($cm->id, 0);
            $forum = forum::get_from_id($cm->instance, 0);
            if ($forum->get_group_mode() == SEPARATEGROUPS) {
                if (has_capability('moodle/site:accessallgroups', $forum->get_context())) {
                    $accessallgroups[] = $cm->instance;
                }
            }
        }
    }
    $forumids = array_keys($visibleforums);
    $separategroupsforumids = array_keys($visibleforums, SEPARATEGROUPS);
    $inforumids = forum_utils::in_or_equals($forumids);
    $inseparategroups = forum_utils::in_or_equals($separategroupsforumids);
    $inaccessallgroups = forum_utils::in_or_equals($accessallgroups);
    $where = "WHERE d.forumid {$inforumids}";
    $where .= " AND (NOT (d.forumid {$inseparategroups})";
    $where .= " OR d.forumid {$inaccessallgroups}";
    $where .= " OR gm.id IS NOT NULL";
    $where .= " OR d.groupid IS NULL)";
    // Note: Even if you have capability to view the deleted or timed posts,
    // we don't show them for consistency with the full-text search.
    $currenttime = time();
    $where .= " AND ({$currenttime} >= d.timestart OR d.timestart = 0)";
    $where .= " AND ({$currenttime} < d.timeend OR d.timeend = 0)";
    //exclude older post versions
    $where .= " AND p.oldversion=0 ";
    $where .= " AND d.deleted=0 AND p.deleted=0 ";
    if ($author) {
        $where .= forumng_get_author_sql($author);
    }
    if ($daterangefrom && !is_array($daterangefrom)) {
        $where .= " AND p.modified>={$daterangefrom}";
    }
    if ($daterangeto && !is_array($daterangeto)) {
        $where .= " AND p.modified<={$daterangeto}";
    }
    $sql = "SELECT p.modified, p.id, p.discussionid, gm.id AS useringroup, p.userid, p.parentpostid, \n            p.subject AS title, p.message AS summary, u.username, u.firstname, \n            u.lastname, d.forumid, d.groupid, d.postid AS discussionpostid\n            FROM {$CFG->prefix}forumng_posts p\n            INNER JOIN {$CFG->prefix}forumng_discussions d ON d.id = p.discussionid\n            INNER JOIN {$CFG->prefix}user u ON p.userid = u.id\n            LEFT JOIN {$CFG->prefix}groups_members gm ON gm.groupid = d.groupid AND gm.userid = {$USER->id}\n            {$where}\n            ORDER BY p.modified DESC, p.id ASC";
    $results = new stdClass();
    $results->success = 1;
    $results->numberofentries = 0;
    $results->done = 0;
    if (!($posts = get_records_sql($sql, $page, $resultsperpage))) {
        $posts = array();
    }
    foreach ($posts as $post) {
        if (!$post->title) {
            // Ideally we would get the parent post that has a subject, but
            // this could involve a while loop that might make numeroous
            // queries, so instead, let's just use the discussion subject
            $post->title = get_string('re', 'forumng', get_field('forumng_posts', 'subject', 'id', $post->discussionpostid));
        }
        $post->title = s(strip_tags($post->title));
        $post->summary = s(strip_tags(shorten_text($post->summary, 250)));
        $post->url = $CFG->wwwroot . "/mod/forumng/discuss.php?d={$post->discussionid}#p{$post->id}";
    }
    $results->results = $posts;
    $results->searchtime = microtime(true) - $before;
    $results->numberofentries = count($results->results);
    if (count($results->results) < $resultsperpage) {
        $results->done = 1;
    } elseif (!($extrapost = get_records_sql($sql, $page + $resultsperpage, 1))) {
        $results->done = 1;
    }
    return $results;
}