/** * 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; }