diff --git a/lib/enrollib.php b/lib/enrollib.php index 86ef59f8a98..9f6795daf19 100644 --- a/lib/enrollib.php +++ b/lib/enrollib.php @@ -228,11 +228,35 @@ function enrol_check_plugins($user) { * The courses has to be visible and enrolments has to be active, * timestart and timeend restrictions are ignored. * + * This function calls {@see enrol_get_shared_courses()} setting checkexistsonly + * to true. + * * @param stdClass|int $user1 * @param stdClass|int $user2 * @return bool */ function enrol_sharing_course($user1, $user2) { + return enrol_get_shared_courses($user1, $user2, false, true); +} + +/** + * Returns any courses shared by the two users + * + * The courses has to be visible and enrolments has to be active, + * timestart and timeend restrictions are ignored. + * + * @global moodle_database $DB + * @param stdClass|int $user1 + * @param stdClass|int $user2 + * @param bool $preloadcontexts If set to true contexts for the returned courses + * will be preloaded. + * @param bool $checkexistsonly If set to true then this function will return true + * if the users share any courses and false if not. + * @return array|bool An array of courses that both users are enrolled in OR if + * $checkexistsonly set returns true if the users share any courses + * and false if not. + */ +function enrol_get_shared_courses($user1, $user2, $preloadcontexts = false, $checkexistsonly = false) { global $DB, $CFG; $user1 = !empty($user1->id) ? $user1->id : $user1; @@ -253,14 +277,33 @@ function enrol_sharing_course($user1, $user2) { $params['user1'] = $user1; $params['user2'] = $user2; - $sql = "SELECT DISTINCT 'x' - FROM {enrol} e - JOIN {user_enrolments} ue1 ON (ue1.enrolid = e.id AND ue1.status = :active1 AND ue1.userid = :user1) - JOIN {user_enrolments} ue2 ON (ue2.enrolid = e.id AND ue2.status = :active2 AND ue2.userid = :user2) - JOIN {course} c ON (c.id = e.courseid AND c.visible = 1) - WHERE e.status = :enabled AND e.enrol $plugins"; + $ctxselect = ''; + $ctxjoin = ''; + if ($preloadcontexts) { + list($ctxselect, $ctxjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx'); + } - return $DB->record_exists_sql($sql, $params); + $sql = "SELECT c.* $ctxselect + FROM {course} c + JOIN ( + SELECT DISTINCT c.id + FROM {enrol} e + JOIN {user_enrolments} ue1 ON (ue1.enrolid = e.id AND ue1.status = :active1 AND ue1.userid = :user1) + JOIN {user_enrolments} ue2 ON (ue2.enrolid = e.id AND ue2.status = :active2 AND ue2.userid = :user2) + JOIN {course} c ON (c.id = e.courseid AND c.visible = 1) + WHERE e.status = :enabled AND e.enrol $plugins + ) ec ON ec.id = c.id + $ctxjoin"; + + if ($checkexistsonly) { + return $DB->record_exists_sql($sql, $params); + } else { + $courses = $DB->get_records_sql($sql, $params); + if ($preloadcontexts) { + array_map('context_instance_preload', $courses); + } + return $courses; + } } /** @@ -1566,4 +1609,4 @@ abstract class enrol_plugin { public function get_bulk_operations() { return array(); } -} \ No newline at end of file +} diff --git a/mod/forum/lang/en/forum.php b/mod/forum/lang/en/forum.php index 13d2e4ebe9b..201b1bf4ccc 100644 --- a/mod/forum/lang/en/forum.php +++ b/mod/forum/lang/en/forum.php @@ -73,6 +73,7 @@ $string['cannottrack'] = 'Could not stop tracking that forum'; $string['cannotunsubscribe'] = 'Could not unsubscribe you from that forum'; $string['cannotupdatepost'] = 'You can not update this post'; $string['cannotviewpostyet'] = 'You cannot read other students questions in this discussion yet because you haven\'t posted'; +$string['cannotviewusersposts'] = 'There are no posts made by this user that you are able to view.'; $string['cleanreadtime'] = 'Mark old posts as read hour'; $string['completiondiscussions'] = 'Student must create discussions:'; $string['completiondiscussionsgroup'] = 'Require discussions'; @@ -122,6 +123,7 @@ $string['discussionname'] = 'Discussion name'; $string['discussions'] = 'Discussions'; $string['discussionsstartedby'] = 'Discussions started by {$a}'; $string['discussionsstartedbyrecent'] = 'Discussions recently started by {$a}'; +$string['discussionsstartedbyuserincourse'] = 'Discussions started by {$a->fullname} in {$a->coursename}'; $string['discussthistopic'] = 'Discuss this topic'; $string['displayend'] = 'Display end'; $string['displayend_help'] = 'This setting specifies whether a forum post should be hidden after a certain date. Note that administrators can always view forum posts.'; @@ -136,6 +138,7 @@ $string['editing'] = 'Editing'; $string['emptymessage'] = 'Something was wrong with your post. Perhaps you left it blank, or the attachment was too big. Your changes have NOT been saved.'; $string['erroremptymessage'] = 'Post message cannot be empty'; $string['erroremptysubject'] = 'Post subject cannot be empty.'; +$string['errorenrolmentrequired'] = 'You must be enrolled in this course to access this content'; $string['errorwhiledelete'] = 'An error occurred while deleting record.'; $string['everyonecanchoose'] = 'Everyone can choose to be subscribed'; $string['everyonecannowchoose'] = 'Everyone can now choose to be subscribed'; @@ -240,7 +243,8 @@ $string['nameteacher'] = 'Teacher forum'; $string['newforumposts'] = 'New forum posts'; $string['noattachments'] = 'There are no attachments to this post'; $string['nodiscussions'] = 'There are no discussion topics yet in this forum'; -$string['nodiscussionsstartedby'] = 'No discussions started by this user'; +$string['nodiscussionsstartedby'] = '{$a} has not started any discussions'; +$string['nodiscussionsstartedbyyou'] = 'You haven\'t started any discussions yet'; $string['noguestpost'] = 'Sorry, guests are not allowed to post.'; $string['noguesttracking'] = 'Sorry, guests are not allowed to set tracking options.'; $string['nomorepostscontaining'] = 'No more posts containing \'{$a}\' were found'; @@ -250,6 +254,8 @@ $string['nopermissiontosubscribe'] = 'You do not have the permission to view for $string['nopermissiontoview'] = 'You do not have permissions to view this post'; $string['nopostforum'] = 'Sorry, you are not allowed to post to this forum'; $string['noposts'] = 'No posts'; +$string['nopostsmadebyuser'] = '{$a} has made no posts'; +$string['nopostsmadebyyou'] = 'You haven\'t made any posts'; $string['nopostscontaining'] = 'No posts containing \'{$a}\' were found'; $string['noquestions'] = 'There are no questions yet in this forum'; $string['nosubscribers'] = 'There are no subscribers yet for this forum'; @@ -294,6 +300,8 @@ $string['postrating1'] = 'Mostly separate knowing'; $string['postrating2'] = 'Separate and connected'; $string['postrating3'] = 'Mostly connected knowing'; $string['posts'] = 'Posts'; +$string['postsmadebyuser'] = 'Posts made by {$a}'; +$string['postsmadebyuserincourse'] = 'Posts made by {$a->fullname} in {$a->coursename}'; $string['posttoforum'] = 'Post to forum'; $string['postupdated'] = 'Your post was updated'; $string['potentialsubscribers'] = 'Potential subscribers'; diff --git a/mod/forum/lib.php b/mod/forum/lib.php index 68cfc095368..80c4edf9557 100644 --- a/mod/forum/lib.php +++ b/mod/forum/lib.php @@ -5071,15 +5071,9 @@ function forum_user_can_see_post($forum, $discussion, $post, $user=NULL, $cm=NUL $user = $USER; } - if (isset($cm->cache->caps['mod/forum:viewdiscussion'])) { - if (!$cm->cache->caps['mod/forum:viewdiscussion']) { - return false; - } - } else { - $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id); - if (!has_capability('mod/forum:viewdiscussion', $modcontext, $user->id)) { - return false; - } + $canviewdiscussion = !empty($cm->cache->caps['mod/forum:viewdiscussion']) || has_capability('mod/forum:viewdiscussion', get_context_instance(CONTEXT_MODULE, $cm->id), $user->id); + if (!$canviewdiscussion && !has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), get_context_instance(CONTEXT_USER, $post->userid))) { + return false; } if (isset($cm->uservisible)) { @@ -7981,3 +7975,386 @@ function forum_page_type_list($pagetype, $parentcontext, $currentcontext) { ); return $forum_pagetype; } + +/** + * Gets all of the courses where the provided user has posted in a forum. + * + * @global moodle_database $DB The database connection + * @param stdClass $user The user who's posts we are looking for + * @param bool $discussionsonly If true only look for discussions started by the user + * @param bool $includecontexts If set to trye contexts for the courses will be preloaded + * @param int $limitfrom The offset of records to return + * @param int $limitnum The number of records to return + * @return array An array of courses + */ +function forum_get_courses_user_posted_in($user, $discussionsonly = false, $includecontexts = true, $limitfrom = null, $limitnum = null) { + global $DB; + + // If we are only after discussions we need only look at the forum_discussions + // table and join to the userid there. If we are looking for posts then we need + // to join to the forum_posts table. + if (!$discussionsonly) { + $joinsql = 'JOIN {forum_discussions} fd ON fd.course = c.id + JOIN {forum_posts} fp ON fp.discussion = fd.id'; + $wheresql = 'fp.userid = :userid'; + $params = array('userid' => $user->id); + } else { + $joinsql = 'JOIN {forum_discussions} fd ON fd.course = c.id'; + $wheresql = 'fd.userid = :userid'; + $params = array('userid' => $user->id); + } + + // Join to the context table so that we can preload contexts if required. + if ($includecontexts) { + list($ctxselect, $ctxjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx'); + } else { + $ctxselect = ''; + $ctxjoin = ''; + } + + // Now we need to get all of the courses to search. + // All courses where the user has posted within a forum will be returned. + $sql = "SELECT DISTINCT c.* $ctxselect + FROM {course} c + $joinsql + $ctxjoin + WHERE $wheresql"; + $courses = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum); + if ($includecontexts) { + array_map('context_instance_preload', $courses); + } + return $courses; +} + +/** + * Gets all of the forums a user has posted in for one or more courses. + * + * @global moodle_database $DB + * @param stdClass $user + * @param array $courseids An array of courseids to search or if not provided + * all courses the user has posted within + * @param bool $discussionsonly If true then only forums where the user has started + * a discussion will be returned. + * @param int $limitfrom The offset of records to return + * @param int $limitnum The number of records to return + * @return array An array of forums the user has posted within in the provided courses + */ +function forum_get_forums_user_posted_in($user, array $courseids = null, $discussionsonly = false, $limitfrom = null, $limitnum = null) { + global $DB; + + $where = array("m.name = 'forum'"); + $params = array(); + if (!is_null($courseids)) { + list($coursewhere, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'courseid'); + $where[] = 'f.course '.$coursewhere; + } + if (!$discussionsonly) { + $joinsql = 'JOIN {forum_discussions} fd ON fd.forum = f.id + JOIN {forum_posts} fp ON fp.discussion = fd.id'; + $where[] = 'fp.userid = :userid'; + } else { + $joinsql = 'JOIN {forum_discussions} fd ON fd.forum = f.id'; + $where[] = 'fd.userid = :userid'; + } + $params['userid'] = $user->id; + $wheresql = join(' AND ', $where); + + $sql = "SELECT DISTINCT f.*, cm.id AS cmid + FROM {forum} f + JOIN {course_modules} cm ON cm.instance = f.id + JOIN {modules} m ON m.id = cm.module + $joinsql + WHERE $wheresql"; + $courseforums = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum); + return $courseforums; +} + +/** + * Returns posts made by the selected user in the requested courses. + * + * This method can be used to return all of the posts made by the requested user + * within the given courses. + * For each course the access of the current user and requested user is checked + * and then for each post access to the post and forum is checked as well. + * + * This function is safe to use with usercapabilities. + * + * @global moodle_database $DB + * @param stdClass $user The user whose posts we want to get + * @param array $courses The courses to search + * @param bool $musthaveaccess If set to true errors will be thrown if the user + * cannot access one or more of the courses to search + * @param bool $discussionsonly If set to true only discussion starting posts + * will be returned. + * @param int $limitfrom The offset of records to return + * @param int $limitnum The number of records to return + * @return stdClass An object the following properties + * ->totalcount: the total number of posts made by the requested user + * that the current user can see. + * ->courses: An array of courses the current user can see that the + * requested user has posted in. + * ->forums: An array of forums relating to the posts returned in the + * property below. + * ->posts: An array containing the posts to show for this request. + */ +function forum_get_posts_by_user($user, array $courses, $musthaveaccess = false, $discussionsonly = false, $limitfrom = 0, $limitnum = 50) { + global $DB, $USER; + + $return = new stdClass; + $return->totalcount = 0; // The total number of posts that the current user is able to view + $return->courses = array(); // The courses the current user can access + $return->forums = array(); // The forums that the current user can access that contain posts + $return->posts = array(); // The posts to display + + // First up a small sanity check. If there are no courses to check we can + // return immediately, there is obviously nothing to search. + if (empty($courses)) { + return $return; + } + + // A couple of quick setups + $isloggedin = isloggedin(); + $isguestuser = $isloggedin && isguestuser(); + $iscurrentuser = $isloggedin && $USER->id == $user->id; + + // Checkout whether or not the current user has capabilities over the requested + // user and if so they have the capabilities required to view the requested + // users content. + $usercontext = get_context_instance(CONTEXT_USER, $user->id, MUST_EXIST); + $hascapsonuser = !$iscurrentuser && $DB->record_exists('role_assignments', array('userid' => $USER->id, 'contextid' => $usercontext->id)); + $hascapsonuser = $hascapsonuser && has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), $usercontext); + + // Before we actually search each course we need to check the user's access to the + // course. If the user doesn't have the appropraite access then we either throw an + // error if a particular course was requested or we just skip over the course. + foreach ($courses as $course) { + $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST); + if ($iscurrentuser || $hascapsonuser) { + // If it is the current user, or the current user has capabilities to the + // requested user then all we need to do is check the requested users + // current access to the course. + // Note: There is no need to check group access or anything of the like + // as either the current user is the requested user, or has granted + // capabilities on the requested user. Either way they can see what the + // requested user posted, although its VERY unlikely in the `parent` situation + // that the current user will be able to view the posts in context. + if (!is_viewing($coursecontext, $user) && !is_enrolled($coursecontext, $user)) { + // Need to have full access to a course to see the rest of own info + if ($musthaveaccess) { + print_error('errorenrolmentrequired', 'forum'); + } + continue; + } + } else { + // Check whether the current user is enrolled or has access to view the course + // if they don't we immediately have a problem. + if (!can_access_course($coursecontext)) { + if ($musthaveaccess) { + print_error('errorenrolmentrequired', 'forum'); + } + continue; + } + + // Check whether the requested user is enrolled or has access to view the course + // if they don't we immediately have a problem. + if (!can_access_course($coursecontext, $user)) { + if ($musthaveaccess) { + print_error('notenrolled', 'forum'); + } + continue; + } + + // If groups are in use and enforced throughout the course then make sure + // we can meet in at least one course level group. + // Note that we check if either the current user or the requested user have + // the capability to access all groups. This is because with that capability + // a user in group A could post in the group B forum. Grrrr. + if (groups_get_course_groupmode($course) == SEPARATEGROUPS && $course->groupmodeforce + && !has_capability('moodle/site:accessallgroups', $coursecontext) && !has_capability('moodle/site:accessallgroups', $coursecontext, $user->id)) { + // If its the guest user to bad... the guest user cannot access groups + if (!$isloggedin or $isguestuser) { + // do not use require_login() here because we might have already used require_login($course) + if ($musthaveaccess) { + redirect(get_login_url()); + } + continue; + } + // Get the groups of the current user + $mygroups = array_keys(groups_get_all_groups($course->id, $USER->id, $course->defaultgroupingid, 'g.id, g.name')); + // Get the groups the requested user is a member of + $usergroups = array_keys(groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid, 'g.id, g.name')); + // Check whether they are members of the same group. If they are great. + $intersect = array_intersect($mygroups, $usergroups); + if (empty($intersect)) { + // But they're not... if it was a specific course throw an error otherwise + // just skip this course so that it is not searched. + if ($musthaveaccess) { + print_error("groupnotamember", '', $CFG->wwwroot."/course/view.php?id=$course->id"); + } + continue; + } + } + } + // Woo hoo we got this far which means the current user can search this + // this course for the requested user. Although this is only the course accessibility + // handling that is complete, the forum accessibility tests are yet to come. + $return->courses[$course->id] = $course; + } + // No longer beed $courses array - lose it not it may be big + unset($courses); + + // Make sure that we have some courses to search + if (empty($return->courses)) { + // If we don't have any courses to search then the reality is that the current + // user doesn't have access to any courses is which the requested user has posted. + // Although we do know at this point that the requested user has posts. + if ($musthaveaccess) { + print_error('permissiondenied'); + } else { + return $return; + } + } + + // Next step: Collect all of the forums that we will want to search. + // It is important to note that this step isn't actually about searching, it is + // about determining which forums we can search by testing accessibility. + $forums = forum_get_forums_user_posted_in($user, array_keys($return->courses), $discussionsonly); + + // Will be used to build the where conditions for the search + $forumsearchwhere = array(); + // Will be used to store the where condition params for the search + $forumsearchparams = array(); + // Will record forums where the user can freely access everything + $forumsearchfullaccess = array(); + // DB caching friendly + $now = round(time(), -2); + // For each course to search we want to find the forums the user has posted in + // and providing the current user can access the forum create a search condition + // for the forum to get the requested users posts. + foreach ($return->courses as $course) { + // Now we need to get the forums + $modinfo = get_fast_modinfo($course); + if (empty($modinfo->instances['forum'])) { + // hmmm, no forums? well at least its easy... skip! + continue; + } + // Iterate + foreach ($modinfo->get_instances_of('forum') as $forumid => $cm) { + if (!$cm->uservisible or !isset($forums[$forumid])) { + continue; + } + // Get the forum in question + $forum = $forums[$forumid]; + // This is needed for functionality later on in the forum code.... + $forum->cm = $cm; + + // Check that either the current user can view the forum, or that the + // current user has capabilities over the requested user and the requested + // user can view the discussion + if (!has_capability('mod/forum:viewdiscussion', $cm->context) && !($hascapsonuser && has_capability('mod/forum:viewdiscussion', $cm->context, $user->id))) { + continue; + } + + // This will contain forum specific where clauses + $forumsearchselect = array(); + if (!$iscurrentuser && !$hascapsonuser) { + // Make sure we check group access + if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $cm->context)) { + $groups = $modinfo->get_groups($cm->groupingid); + $groups[] = -1; + list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_'); + $forumsearchparams = array_merge($forumsearchparams, $groupid_params); + $forumsearchselect[] = "d.groupid $groupid_sql"; + } + + // hidden timed discussions + if (!empty($CFG->forum_enabletimedposts) && !has_capability('mod/forum:viewhiddentimedposts', $cm->context)) { + $forumsearchselect[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))"; + $forumsearchparams['userid'.$forumid] = $user->id; + $forumsearchparams['timestart'.$forumid] = $now; + $forumsearchparams['timeend'.$forumid] = $now; + } + + // qanda access + if ($forum->type == 'qanda' && !has_capability('mod/forum:viewqandawithoutposting', $cm->context)) { + // We need to check whether the user has posted in the qanda forum. + $discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $user->id); + if (!empty($discussionspostedin)) { + $forumonlydiscussions = array(); // Holds discussion ids for the discussions the user is allowed to see in this forum. + foreach ($discussionspostedin as $d) { + $forumonlydiscussions[] = $d->id; + } + list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forumonlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_'); + $forumsearchparams = array_merge($forumsearchparams, $discussionid_params); + $forumsearchselect[] = "(d.id $discussionid_sql OR p.parent = 0)"; + } else { + $forumsearchselect[] = "p.parent = 0"; + } + + } + + if (count($forumsearchselect) > 0) { + $forumsearchwhere[] = "(d.forum = :forum{$forumid} AND ".implode(" AND ", $forumsearchselect).")"; + $forumsearchparams['forum'.$forumid] = $forumid; + } else { + $forumsearchfullaccess[] = $forumid; + } + } else { + // The current user/parent can see all of their own posts + $forumsearchfullaccess[] = $forumid; + } + } + } + + // If we dont have any search conditions, and we don't have any forums where + // the user has full access then we just return the default. + if (empty($forumsearchwhere) && empty($forumsearchfullaccess)) { + return $return; + } + + // Prepare a where condition for the full access forums. + if (count($forumsearchfullaccess) > 0) { + list($fullidsql, $fullidparams) = $DB->get_in_or_equal($forumsearchfullaccess, SQL_PARAMS_NAMED, 'fula'); + $forumsearchparams = array_merge($forumsearchparams, $fullidparams); + $forumsearchwhere[] = "(d.forum $fullidsql)"; + } + + // Prepare SQL to both count and search + $userfields = user_picture::fields('u', null, 'userid'); + $countsql = 'SELECT COUNT(*) '; + $selectsql = 'SELECT p.*, d.forum, d.name AS discussionname, '.$userfields.' '; + $wheresql = implode(" OR ", $forumsearchwhere); + + if ($discussionsonly) { + if ($wheresql == '') { + $wheresql = 'p.parent = 0'; + } else { + $wheresql = 'p.parent = 0 AND ('.$wheresql.')'; + } + } + + $sql = "FROM {forum_posts} p + JOIN {forum_discussions} d ON d.id = p.discussion + JOIN {user} u ON u.id = p.userid + WHERE ($wheresql) + AND p.userid = :userid "; + $orderby = "ORDER BY p.modified DESC"; + $forumsearchparams['userid'] = $user->id; + + // Set the total number posts made by the requested user that the current user can see + $return->totalcount = $DB->count_records_sql($countsql.$sql, $forumsearchparams); + // Set the collection of posts that has been requested + $return->posts = $DB->get_records_sql($selectsql.$sql.$orderby, $forumsearchparams, $limitfrom, $limitnum); + + // We need to build an array of forums for which posts will be displayed. + // We do this here to save the caller needing to retrieve them themselves before + // printing these forums posts. Given we have the forums already there is + // practically no overhead here. + foreach ($return->posts as $post) { + if (!array_key_exists($post->forum, $return->forums)) { + $return->forums[$post->forum] = $forums[$post->forum]; + } + } + + return $return; +} \ No newline at end of file diff --git a/mod/forum/user.php b/mod/forum/user.php index 4714b34bc5c..48c2e76af37 100644 --- a/mod/forum/user.php +++ b/mod/forum/user.php @@ -23,194 +23,319 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -require_once('../../config.php'); -require_once('lib.php'); +require_once(dirname(dirname(dirname(__FILE__))).'/config.php'); +require_once($CFG->dirroot.'/mod/forum/lib.php'); +require_once($CFG->dirroot.'/rating/lib.php'); -// Course ID -$course = optional_param('course', SITEID, PARAM_INT); -// User ID -$id = optional_param('id', 0, PARAM_INT); -$mode = optional_param('mode', 'posts', PARAM_ALPHA); -$page = optional_param('page', 0, PARAM_INT); -$perpage = optional_param('perpage', 5, PARAM_INT); +$courseid = optional_param('course', null, PARAM_INT); // Limit the posts to just this course +$userid = optional_param('id', $USER->id, PARAM_INT); // User id whose posts we want to view +$mode = optional_param('mode', 'posts', PARAM_ALPHA); // The mode to use. Either posts or discussions +$page = optional_param('page', 0, PARAM_INT); // The page number to display +$perpage = optional_param('perpage', 5, PARAM_INT); // The number of posts to display per page -$url = new moodle_url('/mod/forum/user.php'); -if ($course !== SITEID) { - $url->param('course', $course); +if (empty($userid)) { + if (!isloggedin()) { + require_login(); + } + $userid = $USER->id; } -if ($id !== 0) { - $url->param('id', $id); + +$discussionsonly = ($mode !== 'posts'); +$isspecificcourse = !is_null($courseid); +$iscurrentuser = ($USER->id == $userid); + +$url = new moodle_url('/mod/forum/user.php', array('id' => $userid)); +if ($isspecificcourse) { + $url->param('course', $courseid); } -if ($mode !== 'posts') { - $url->param('mode', $mode); +if ($discussionsonly) { + $url->param('mode', 'discussions'); } + $PAGE->set_url($url); $PAGE->set_pagelayout('standard'); -if (empty($id)) { // See your own profile by default - require_login(); - $id = $USER->id; +if ($page != 0) { + $url->param('page', $page); +} +if ($perpage != 5) { + $url->param('perpage', $perpage); } -$user = $DB->get_record("user", array("id" => $id), '*', MUST_EXIST); -$course = $DB->get_record("course", array("id" => $course), '*', MUST_EXIST); +add_to_log(($isspecificcourse)?$courseid:SITEID, "forum", "user report", 'user.php?'.$url->get_query_string(), $userid); -$syscontext = get_context_instance(CONTEXT_SYSTEM); -$usercontext = get_context_instance(CONTEXT_USER, $id); - -// do not force parents to enrol -if (!$DB->get_record('role_assignments', array('userid' => $USER->id, 'contextid' => $usercontext->id))) { - require_course_login($course); -} else { - $PAGE->set_course($course); +$user = $DB->get_record("user", array("id" => $userid), '*', MUST_EXIST); +$usercontext = get_context_instance(CONTEXT_USER, $user->id, MUST_EXIST); +// Check if the requested user is the guest user +if (isguestuser($user)) { + // The guest user cannot post, so it is not possible to view any posts. + // May as well just bail aggressively here. + print_error('invaliduserid'); } - +// Make sure the user has not been deleted if ($user->deleted) { + $PAGE->set_title(get_string('userdeleted')); + $PAGE->set_context(get_system_context()); echo $OUTPUT->header(); - echo $OUTPUT->heading(get_string('userdeleted')); + echo $OUTPUT->heading($PAGE->title); echo $OUTPUT->footer(); die; } -add_to_log($course->id, "forum", "user report", - "user.php?course=$course->id&id=$user->id&mode=$mode", "$user->id"); +$isloggedin = isloggedin(); +$isguestuser = $isloggedin && isguestuser(); +$isparent = !$iscurrentuser && $DB->record_exists('role_assignments', array('userid'=>$USER->id, 'contextid'=>$usercontext->id)); +$hasparentaccess = $isparent && has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), $usercontext); -$strforumposts = get_string('forumposts', 'forum'); -$strparticipants = get_string('participants'); -$strmode = get_string($mode, 'forum'); -$fullname = fullname($user, has_capability('moodle/site:viewfullnames', $syscontext)); - -$link = null; -if (has_capability('moodle/course:viewparticipants', get_context_instance(CONTEXT_COURSE, $course->id)) || has_capability('moodle/site:viewparticipants', $syscontext)) { - $link = new moodle_url('/user/index.php',array('id'=>$course->id)); -} - -$PAGE->navigation->extend_for_user($user); -$PAGE->navigation->set_userid_for_parent_checks($id); // see MDL-25805 for reasons and for full commit reference for reversal when fixed. -$PAGE->set_title("$course->shortname: $fullname: $strmode"); -$PAGE->set_heading($course->fullname); -echo $OUTPUT->header(); -echo $OUTPUT->heading($fullname); - -switch ($mode) { - case 'posts' : - $searchterms = array('userid:'.$user->id); - $extrasql = ''; - break; - - default: - $searchterms = array('userid:'.$user->id); - $extrasql = 'AND p.parent = 0'; - break; -} - -echo '
'; - -if ($course->id == SITEID) { - $searchcourse = SITEID; - if (empty($CFG->forceloginforprofiles) or (isloggedin() and !isguestuser() and !is_web_crawler())) { - // Search throughout the whole site. - $searchcourse = 0; - } -} else { - // Search only for posts the user made in this course. - $searchcourse = $course->id; -} - -// Get the posts. -$posts = forum_search_posts($searchterms, $searchcourse, $page*$perpage, $perpage, $totalcount, $extrasql); -if ($posts) { - - require_once($CFG->dirroot.'/rating/lib.php'); - - $baseurl = new moodle_url('user.php', array('id' => $user->id, 'course' => $course->id, 'mode' => $mode, 'perpage' => $perpage)); - echo $OUTPUT->paging_bar($totalcount, $page, $perpage, $baseurl); - - $discussions = array(); - $forums = array(); - - //todo Rather than retrieving the ratings for each post individually it would be nice to do them in groups - //however this requires creating arrays of posts with each array containing all of the posts from a particular forum, - //retrieving the ratings then reassembling them all back into a single array sorted by post.modified (descending) - $rm = new rating_manager(); - $ratingoptions = new stdClass; - $ratingoptions->component = 'mod_forum'; - $ratingoptions->ratingarea = 'post'; - - foreach ($posts as $post) { - - if (!isset($discussions[$post->discussion])) { - if (! $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion))) { - print_error('invaliddiscussionid', 'forum'); - } - $discussions[$post->discussion] = $discussion; - } else { - $discussion = $discussions[$post->discussion]; - } - - if (!isset($forums[$discussion->forum])) { - $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST); - $forum->cm = get_coursemodule_from_instance('forum', $forum->id, 0, false, MUST_EXIST); - $forum->context = get_context_instance(CONTEXT_MODULE, $forum->cm->id); - $forums[$discussion->forum] = $forum; - } else { - $forum = $forums[$discussion->forum]; - } - - $forumurl = new moodle_url('/mod/forum/view.php', array('id' => $forum->cm->id)); - $discussionurl = new moodle_url('/mod/forum/discuss.php', array('d' => $discussion->id)); - - // load ratings - if ($forum->assessed != RATING_AGGREGATE_NONE) { - $ratingoptions->context = $forum->context; - $ratingoptions->items = array($post); - $ratingoptions->aggregate = $forum->assessed;//the aggregation method - $ratingoptions->scaleid = $forum->scale; - $ratingoptions->userid = $user->id; - $ratingoptions->assesstimestart = $forum->assesstimestart; - $ratingoptions->assesstimefinish = $forum->assesstimefinish; - if ($forum->type == 'single' or !$discussion->id) { - $ratingoptions->returnurl = $forumurl; - } else { - $ratingoptions->returnurl = $discussionurl; - } - - $updatedpost = $rm->get_ratings($ratingoptions); - //updating the array this way because we're iterating over a collection and updating them one by one - $posts[$updatedpost[0]->id] = $updatedpost[0]; - } - - $fullsubjects = array(); - if ($course->id == SITEID && has_capability('moodle/site:config', $syscontext)) { - $postcoursename = $DB->get_field('course', 'shortname', array('id'=>$forum->course)); - $courseurl = new moodle_url('/course/view.php', array('id' => $forum->course)); - $fullsubjects[] = html_writer::link($courseurl, $postcoursename); - } - $fullsubjects[] = html_writer::link($forumurl, format_string($forum->name, true)); - if ($forum->type != 'single') { - $fullsubjects[] .= html_writer::link($discussionurl, format_string($discussion->name, true)); - if ($post->parent != 0) { - $parenturl = new moodle_url('/mod/forum/discuss.php', array('d' => $post->discussion, 'parent' => $post->id)); - $fullsubjects[] .= html_writer::link($parenturl, format_string($post->subject, true)); - } - } - - $post->subject = join(' -> ', $fullsubjects); - $discussionurl->set_anchor('p'.$post->id); - $fulllink = html_writer::link($discussionurl, get_string("postincontext", "forum")); - - forum_print_post($post, $discussion, $forum, $forum->cm, $course, false, false, false, $fulllink); - echo "
"; - } - - echo $OUTPUT->paging_bar($totalcount, $page, $perpage, $baseurl); -} else { - if ($mode == 'posts') { - echo $OUTPUT->heading(get_string('noposts', 'forum')); +// Check whether a specific course has been requested +if ($isspecificcourse) { + // Get the requested course and its context + $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); + $coursecontext = get_context_instance(CONTEXT_COURSE, $courseid, MUST_EXIST); + // We have a specific course to search, which we will also assume we are within. + if ($hasparentaccess) { + // A `parent` role won't likely have access to the course so we won't attempt + // to enter it. We will however still make them jump through the normal + // login hoops + require_login(); + $PAGE->set_context($coursecontext); + $PAGE->set_course($course); } else { - echo $OUTPUT->heading(get_string('nodiscussionsstartedby', 'forum')); + // Enter the course we are searching + require_login($course); } -} -echo '
'; -echo $OUTPUT->footer(); + // Get the course ready for access checks + $courses = array($courseid => $course); +} else { + // We are going to search for all of the users posts in all courses! + // a general require login here as we arn't actually within any course. + require_login(); + $PAGE->set_context(get_system_context()); + // Now we need to get all of the courses to search. + // All courses where the user has posted within a forum will be returned. + $courses = forum_get_courses_user_posted_in($user, $discussionsonly); +} + +// Get the posts by the requested user that the current user can access. +$result = forum_get_posts_by_user($user, $courses, $isspecificcourse, $discussionsonly, ($page * $perpage), $perpage); + +// Check whether there are not posts to display. +if (empty($result->posts)) { + // Ok no posts to display means that either the user has not posted or there + // are no posts made by the requested user that the current user is able to + // see. + // In either case we need to decide whether we can show personal information + // about the requested user to the current user so we will execute some checks + + // First check the obvious, its the current user, a specific course has been + // provided (require_login has been called), or they have a course contact role. + // True to any of those and the current user can see the details of the + // requested user. + $canviewuser = ($iscurrentuser || $isspecificcourse || empty($CFG->forceloginforprofiles) || has_coursecontact_role($userid)); + // Next we'll check the caps, if the current user has the view details and a + // specific course has been requested, or if they have the view all details + $canviewuser = ($canviewuser || ($isspecificcourse && has_capability('moodle/user:viewdetails', $coursecontext) || has_capability('moodle/user:viewalldetails', $usercontext))); + + // If none of the above was true the next step is to check a shared relation + // through some course + if (!$canviewuser) { + // Get all of the courses that the users have in common + $sharedcourses = enrol_get_shared_courses($USER->id, $user->id, true); + foreach ($sharedcourses as $sharedcourse) { + // Check the view cap within the course context + if (has_capability('moodle/user:viewdetails', get_context_instance(CONTEXT_COURSE, $sharedcourse->id))) { + $canviewuser = true; + break; + } + } + unset($sharedcourses); + } + + // Prepare the page title + $pagetitle = get_string('noposts', 'mod_forum'); + + // Get the page heading + if ($isspecificcourse) { + $pageheading = format_string($course->shortname, true, array('context' => $coursecontext)); + } else { + $pageheading = get_string('pluginname', 'mod_forum'); + } + + // Next we need to set up the loading of the navigation and choose a message + // to display to the current user. + if ($iscurrentuser) { + // No need to extend the navigation it happens automatically for the + // current user. + if ($discussionsonly) { + $notification = get_string('nodiscussionsstartedbyyou', 'forum'); + } else { + $notification = get_string('nopostsmadebyyou', 'forum'); + } + } else if ($canviewuser) { + $PAGE->navigation->extend_for_user($user); + $PAGE->navigation->set_userid_for_parent_checks($user->id); // see MDL-25805 for reasons and for full commit reference for reversal when fixed. + $fullname = fullname($user); + if ($discussionsonly) { + $notification = get_string('nodiscussionsstartedby', 'forum', $fullname); + } else { + $notification = get_string('nopostsmadebyuser', 'forum', $fullname); + } + } else { + // Don't extend the navigation it would be giving out information that + // the current uesr doesn't have access to. + $notification = get_string('cannotviewusersposts', 'forum'); + if ($isspecificcourse) { + $url = new moodle_url('/course/view.php', array('id' => $courseid)); + } else { + $url = new moodle_url('/'); + } + navigation_node::override_active_url($url); + } + + // Display a page letting the user know that there's nothing to display; + $PAGE->set_title($pagetitle); + $PAGE->set_heading($pageheading); + echo $OUTPUT->header(); + echo $OUTPUT->heading($pagetitle); + echo $OUTPUT->notification($notification); + if (!$url->compare($PAGE->url)) { + echo $OUTPUT->continue_button($url); + } + echo $OUTPUT->footer(); + die; +} + +// Post output will contain an entry containing HTML to display each post by the +// time we are done. +$postoutput = array(); + +$discussions = array(); +foreach ($result->posts as $post) { + $discussions[] = $post->discussion; +} +$discussions = $DB->get_records_list('forum_discussions', 'id', array_unique($discussions)); + +//todo Rather than retrieving the ratings for each post individually it would be nice to do them in groups +//however this requires creating arrays of posts with each array containing all of the posts from a particular forum, +//retrieving the ratings then reassembling them all back into a single array sorted by post.modified (descending) +$rm = new rating_manager(); +$ratingoptions = new stdClass; +$ratingoptions->component = 'mod_forum'; +$ratingoptions->ratingarea = 'post'; +foreach ($result->posts as $post) { + if (!isset($result->forums[$post->forum]) || !isset($discussions[$post->discussion])) { + // Something very VERY dodgy has happened if we end up here + continue; + } + $forum = $result->forums[$post->forum]; + $cm = $forum->cm; + $discussion = $discussions[$post->discussion]; + $course = $result->courses[$discussion->course]; + + $forumurl = new moodle_url('/mod/forum/view.php', array('id' => $cm->id)); + $discussionurl = new moodle_url('/mod/forum/discuss.php', array('d' => $post->discussion)); + + // load ratings + if ($forum->assessed != RATING_AGGREGATE_NONE) { + $ratingoptions->context = $cm->context; + $ratingoptions->items = array($post); + $ratingoptions->aggregate = $forum->assessed;//the aggregation method + $ratingoptions->scaleid = $forum->scale; + $ratingoptions->userid = $user->id; + $ratingoptions->assesstimestart = $forum->assesstimestart; + $ratingoptions->assesstimefinish = $forum->assesstimefinish; + if ($forum->type == 'single' or !$post->discussion) { + $ratingoptions->returnurl = $forumurl; + } else { + $ratingoptions->returnurl = $discussionurl; + } + + $updatedpost = $rm->get_ratings($ratingoptions); + //updating the array this way because we're iterating over a collection and updating them one by one + $result->posts[$updatedpost[0]->id] = $updatedpost[0]; + } + + $courseshortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id))); + $forumname = format_string($forum->name, true, array('context' => $cm->context)); + + $fullsubjects = array(); + if (!$isspecificcourse && !$hasparentaccess) { + $fullsubjects[] = html_writer::link(new moodle_url('/course/view.php', array('id' => $course->id)), $courseshortname); + $fullsubjects[] = html_writer::link($forumurl, $forumname); + } else { + $fullsubjects[] = html_writer::tag('span', $courseshortname); + $fullsubjects[] = html_writer::tag('span', $forumname); + } + if ($forum->type != 'single') { + $discussionname = format_string($discussion->name, true, array('context' => $cm->context)); + if (!$isspecificcourse && !$hasparentaccess) { + $fullsubjects[] .= html_writer::link($discussionurl, $discussionname); + } else { + $fullsubjects[] .= html_writer::tag('span', $discussionname); + } + if ($post->parent != 0) { + $postname = format_string($post->subject, true, array('context' => $cm->context)); + if (!$isspecificcourse && !$hasparentaccess) { + $fullsubjects[] .= html_writer::link(new moodle_url('/mod/forum/discuss.php', array('d' => $post->discussion, 'parent' => $post->id)), $postname); + } else { + $fullsubjects[] .= html_writer::tag('span', $postname); + } + } + } + $post->subject = join(' -> ', $fullsubjects); + // This is really important, if the strings are formatted again all the links + // we've added will be lost. + $post->subjectnoformat = true; + $discussionurl->set_anchor('p'.$post->id); + $fulllink = html_writer::link($discussionurl, get_string("postincontext", "forum")); + + $postoutput[] = forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false, $fulllink, '', null, true, null, true); +} + +$userfullname = fullname($user); + +if ($discussionsonly) { + $inpageheading = get_string('discussionsstartedby', 'mod_forum', $userfullname); +} else { + $inpageheading = get_string('postsmadebyuser', 'mod_forum', $userfullname); +} +if ($isspecificcourse) { + $a = new stdClass; + $a->fullname = $userfullname; + $a->coursename = format_string($course->shortname, true, array('context' => $coursecontext)); + $pageheading = $a->coursename; + if ($discussionsonly) { + $pagetitle = get_string('discussionsstartedbyuserincourse', 'mod_forum', $a); + } else { + $pagetitle = get_string('postsmadebyuserincourse', 'mod_forum', $a); + } +} else { + $pagetitle = $inpageheading; + $pageheading = $userfullname; +} + +$PAGE->set_title($pagetitle); +$PAGE->set_heading($pagetitle); +$PAGE->navigation->extend_for_user($user); +$PAGE->navigation->set_userid_for_parent_checks($user->id); // see MDL-25805 for reasons and for full commit reference for reversal when fixed. + +echo $OUTPUT->header(); +echo $OUTPUT->heading($inpageheading); +echo html_writer::start_tag('div', array('class' => 'user-content')); + +if (!empty($postoutput)) { + echo $OUTPUT->paging_bar($result->totalcount, $page, $perpage, $url); + foreach ($postoutput as $post) { + echo $post; + echo html_writer::empty_tag('br'); + } + echo $OUTPUT->paging_bar($result->totalcount, $page, $perpage, $url); +} else if ($discussionsonly) { + echo $OUTPUT->heading(get_string('nodiscussionsstartedby', 'forum', $userfullname)); +} else { + echo $OUTPUT->heading(get_string('noposts', 'forum')); +} + +echo html_writer::end_tag('div'); +echo $OUTPUT->footer(); diff --git a/user/view.php b/user/view.php index d1dab93594e..ce82f8ed4cc 100644 --- a/user/view.php +++ b/user/view.php @@ -58,10 +58,6 @@ if (isguestuser($user)) { if (!empty($CFG->forceloginforprofiles)) { require_login(); // we can not log in to course due to the parent hack bellow - if (isguestuser()) { - $SESSION->wantsurl = $PAGE->url->out(false); - redirect(get_login_url()); - } } $PAGE->set_context($coursecontext); @@ -96,7 +92,7 @@ $fullname = fullname($user, has_capability('moodle/site:viewfullnames', $coursec /// Now test the actual capabilities and enrolment in course if ($currentuser) { // me - if (!is_enrolled($coursecontext) and !is_viewing($coursecontext)) { // Need to have full access to a course to see the rest of own info + if (!is_viewing($coursecontext) && !is_enrolled($coursecontext)) { // Need to have full access to a course to see the rest of own info echo $OUTPUT->header(); echo $OUTPUT->heading(get_string('notenrolled', '', $fullname)); if (!empty($_SERVER['HTTP_REFERER'])) {