MDL-28615 mod_forum: Fixed the users page that shows all of the users posts
authorSam Hemelryk <sam@moodle.com>
Thu, 8 Sep 2011 04:17:12 +0000 (16:17 +1200)
committerSam Hemelryk <sam@moodle.com>
Thu, 29 Sep 2011 01:20:58 +0000 (14:20 +1300)
lib/enrollib.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/user.php
user/view.php

index 86ef59f..19cdaf7 100644 (file)
@@ -263,6 +263,62 @@ function enrol_sharing_course($user1, $user2) {
     return $DB->record_exists_sql($sql, $params);
 }
 
+/**
+ * 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.
+ *
+ * @param stdClass|int $user1
+ * @param stdClass|int $user2
+ * @return array An array of courses that both users are enrolled in
+ */
+function enrol_get_shared_courses($user1, $user2, $preloadcontexts = false) {
+    global $DB, $CFG;
+
+    $user1 = !empty($user1->id) ? $user1->id : $user1;
+    $user2 = !empty($user2->id) ? $user2->id : $user2;
+
+    if (empty($user1) or empty($user2)) {
+        return false;
+    }
+
+    if (!$plugins = explode(',', $CFG->enrol_plugins_enabled)) {
+        return false;
+    }
+
+    list($plugins, $params) = $DB->get_in_or_equal($plugins, SQL_PARAMS_NAMED, 'ee');
+    $params['enabled'] = ENROL_INSTANCE_ENABLED;
+    $params['active1'] = ENROL_USER_ACTIVE;
+    $params['active2'] = ENROL_USER_ACTIVE;
+    $params['user1']   = $user1;
+    $params['user2']   = $user2;
+
+    $ctxselect = '';
+    $ctxjoin = '';
+    if ($preloadcontexts) {
+        list($ctxselect, $ctxjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
+    }
+
+    $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";
+    $courses = $DB->get_records_sql($sql, $params);
+    if ($preloadcontexts) {
+        array_map('context_instance_preload', $courses);
+    }
+
+    return $courses;
+}
+
 /**
  * This function adds necessary enrol plugins UI into the course edit form.
  *
@@ -1566,4 +1622,4 @@ abstract class enrol_plugin {
     public function get_bulk_operations() {
         return array();
     }
-}
\ No newline at end of file
+}
index 13d2e4e..201b1bf 100644 (file)
@@ -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';
index 68cfc09..f30a16c 100644 (file)
@@ -5071,14 +5071,16 @@ 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;
+    if (!has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), get_context_instance(CONTEXT_USER, $post->userid))) {
+        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;
+            }
         }
     }
 
@@ -7981,3 +7983,381 @@ 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 type $limitfrom The offset of records to return
+ * @param type $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 type $limitfrom The offset of records to return
+ * @param type $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.
+ * @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.
+                if (!array_intersect($mygroups, $usergroups)) {
+                    // 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();
+    // 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
index 4714b34..0e28719 100644 (file)
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-require_once('../../config.php');
-require_once('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);
-
-$url = new moodle_url('/mod/forum/user.php');
-if ($course !== SITEID) {
-    $url->param('course', $course);
-}
-if ($id !== 0) {
-    $url->param('id', $id);
-}
-if ($mode !== 'posts') {
-    $url->param('mode', $mode);
+require_once(dirname(dirname(dirname(__FILE__))).'/config.php');
+require_once($CFG->dirroot.'/mod/forum/lib.php');
+require_once($CFG->dirroot.'/rating/lib.php');
+
+$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
+
+if (empty($userid)) {
+    if (!isloggedin()) {
+        require_login();
+    }
+    $userid = $USER->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 ($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;
-}
-
-$user = $DB->get_record("user", array("id" => $id), '*', MUST_EXIST);
-$course = $DB->get_record("course", array("id" => $course), '*', MUST_EXIST);
+if ($page != 0) $url->param('page', $page);
+if ($perpage != 5) $url->param('perpage', $perpage);
 
-$syscontext = get_context_instance(CONTEXT_SYSTEM);
-$usercontext   = get_context_instance(CONTEXT_USER, $id);
+add_to_log(($isspecificcourse)?$courseid:SITEID, "forum", "user report", 'user.php?'.$url->get_query_string(), $userid);
 
-// 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&amp;id=$user->id&amp;mode=$mode", "$user->id");
-
-$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 '<div class="user-content">';
-
-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;
+$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);
+
+// 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 {
+        // Enter the course we are searching
+        require_login($course);
     }
+    // Get the course ready for access checks
+    $courses = array($courseid => $course);
 } 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');
+    // 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());
 
-    $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);
+    // 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);
+}
 
-    $discussions = array();
-    $forums      = array();
+// 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);
+    }
 
-    //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';
+    // Prepare the page title
+    $pagetitle = get_string('noposts', 'mod_forum');
 
-    foreach ($posts as $post) {
+    // Get the page heading
+    if ($isspecificcourse) {
+        $pageheading = format_string($course->shortname, true, array('context' => $coursecontext));
+    } else {
+        $pageheading = get_string('pluginname', 'mod_forum');
+    }
 
-        if (!isset($discussions[$post->discussion])) {
-            if (! $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion))) {
-                print_error('invaliddiscussionid', 'forum');
-            }
-            $discussions[$post->discussion] = $discussion;
+    // 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 {
-            $discussion = $discussions[$post->discussion];
+            $notification = get_string('nopostsmadebyyou', 'forum');
         }
-
-        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 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 {
-            $forum = $forums[$discussion->forum];
+            $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);
+    }
 
-        $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;
-            }
+    // 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);
+    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();
 
-            $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];
+$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;
         }
 
-        $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);
+        $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);
         }
-        $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));
+        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);
-        $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 "<br />";
     }
+    $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);
+}
 
-    echo $OUTPUT->paging_bar($totalcount, $page, $perpage, $baseurl);
+$userfullname = fullname($user);
+
+if ($discussionsonly) {
+    $inpageheading = get_string('discussionsstartedby', 'mod_forum', $userfullname);
 } else {
-    if ($mode == 'posts') {
-        echo $OUTPUT->heading(get_string('noposts', 'forum'));
+    $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 {
-        echo $OUTPUT->heading(get_string('nodiscussionsstartedby', 'forum'));
+        $pagetitle = get_string('postsmadebyuserincourse', 'mod_forum', $a);
     }
+} else {
+    $pagetitle = $inpageheading;
+    $pageheading = $userfullname;
 }
-echo '</div>';
-echo $OUTPUT->footer();
 
+$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();
index d1dab93..ce82f8e 100644 (file)
@@ -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'])) {