diff --git a/mod/workshop/allocation/manual/lib.php b/mod/workshop/allocation/manual/lib.php index f9b3eb22da4..24df2c7f8f3 100644 --- a/mod/workshop/allocation/manual/lib.php +++ b/mod/workshop/allocation/manual/lib.php @@ -61,6 +61,13 @@ class workshop_manual_allocator implements workshop_allocator { global $PAGE; $mode = optional_param('mode', 'display', PARAM_ALPHA); + $perpage = optional_param('perpage', null, PARAM_INT); + + if ($perpage and $perpage > 0 and $perpage <= 1000) { + require_sesskey(); + set_user_preference('workshopallocation_manual_perpage', $perpage); + redirect($PAGE->url); + } $result = new workshop_allocation_result($this); @@ -141,9 +148,9 @@ class workshop_manual_allocator implements workshop_allocator { $output = $PAGE->get_renderer('workshopallocation_manual'); - $pagingvar = 'page'; - $page = optional_param($pagingvar, 0, PARAM_INT); - $perpage = 10; // todo let the user modify this + $page = optional_param('page', 0, PARAM_INT); + $perpage = get_user_preferences('workshopallocation_manual_perpage', 10); + $groupid = groups_get_activity_group($this->workshop->cm, true); $hlauthorid = -1; // highlight this author $hlreviewerid = -1; // highlight this reviewer @@ -200,24 +207,20 @@ class workshop_manual_allocator implements workshop_allocator { } } - // fetch the list of ids of all workshop participants - this may get really long so fetch just id - $participants = get_users_by_capability($PAGE->context, array('mod/workshop:submit', 'mod/workshop:peerassess'), - 'u.id', 'u.lastname,u.firstname,u.id', '', '', '', '', false, false, true); - - $numofparticipants = count($participants); // we will need later for the pagination + // fetch the list of ids of all workshop participants + $numofparticipants = $this->workshop->count_participants(false, $groupid); + $participants = $this->workshop->get_participants(false, $groupid, $perpage * $page, $perpage); if ($hlauthorid > 0 and $hlreviewerid > 0) { // display just those two users $participants = array_intersect_key($participants, array($hlauthorid => null, $hlreviewerid => null)); $button = $output->single_button($PAGE->url, get_string('showallparticipants', 'workshopallocation_manual'), 'get'); } else { - // slice the list of participants according to the current page - $participants = array_slice($participants, $page * $perpage, $perpage, true); $button = ''; } // this will hold the information needed to display user names and pictures - $userinfo = $DB->get_records_list('user', 'id', array_keys($participants), '', user_picture::fields()); + $userinfo = $participants; // load the participants' submissions $submissions = $this->workshop->get_submissions(array_keys($participants)); @@ -315,6 +318,7 @@ class workshop_manual_allocator implements workshop_allocator { // prepare data to be rendered $data = new workshopallocation_manual_allocations(); + $data->workshop = $this->workshop; $data->allocations = $allocations; $data->userinfo = $userinfo; $data->authors = $this->workshop->get_potential_authors(); @@ -323,11 +327,15 @@ class workshop_manual_allocator implements workshop_allocator { $data->hlreviewerid = $hlreviewerid; $data->selfassessment = $this->workshop->useselfassessment; - // prepare paging bar - $pagingbar = new paging_bar($numofparticipants, $page, $perpage, $PAGE->url, $pagingvar); - $pagingbarout = $output->render($pagingbar); + // prepare the group selector + $groupselector = $output->container(groups_print_activity_menu($this->workshop->cm, $PAGE->url, true), 'groupwidget'); - return $pagingbarout . $output->render($message) . $output->render($data) . $button . $pagingbarout; + // prepare paging bar + $pagingbar = new paging_bar($numofparticipants, $page, $perpage, $PAGE->url, 'page'); + $pagingbarout = $output->render($pagingbar); + $perpageselector = $output->perpage_selector($perpage); + + return $groupselector . $pagingbarout . $output->render($message) . $output->render($data) . $button . $pagingbarout . $perpageselector; } /** @@ -350,11 +358,28 @@ class workshop_manual_allocator implements workshop_allocator { * @see workshop_manual_allocator::ui() */ class workshopallocation_manual_allocations implements renderable { + + /** @var workshop module instance */ + public $workshop; + + /** @var array of stdClass, indexed by userid, properties userid, submissionid, (array)reviewedby, (array)reviewerof */ public $allocations; + + /** @var array of stdClass contains the data needed to display the user name and picture */ public $userinfo; + + /* var array of stdClass potential authors */ public $authors; + + /* var array of stdClass potential reviewers */ public $reviewers; + + /* var int the id of the user to highlight as the author */ public $hlauthorid; + + /* var int the id of the user to highlight as the reviewer */ public $hlreviewerid; + + /* var bool should the selfassessment be allowed */ public $selfassessment; } diff --git a/mod/workshop/allocation/manual/renderer.php b/mod/workshop/allocation/manual/renderer.php index 25f53917362..4bcbf9ada81 100644 --- a/mod/workshop/allocation/manual/renderer.php +++ b/mod/workshop/allocation/manual/renderer.php @@ -31,6 +31,9 @@ defined('MOODLE_INTERNAL') || die(); */ class workshopallocation_manual_renderer extends mod_workshop_renderer { + /** @var workshop module instance */ + protected $workshop; + //////////////////////////////////////////////////////////////////////////// // External rendering API //////////////////////////////////////////////////////////////////////////// @@ -42,6 +45,9 @@ class workshopallocation_manual_renderer extends mod_workshop_renderer { * @return string html code */ protected function render_workshopallocation_manual_allocations(workshopallocation_manual_allocations $data) { + + $this->workshop = $data->workshop; + $allocations = $data->allocations; // array prepared array of all allocations data $userinfo = $data->userinfo; // names and pictures of all required users $authors = $data->authors; // array potential reviewees @@ -101,7 +107,8 @@ class workshopallocation_manual_renderer extends mod_workshop_renderer { if (is_null($allocation->submissionid)) { $o .= $this->output->container(get_string('nosubmissionfound', 'workshop'), 'info'); } else { - $o .= $this->output->container(format_string($allocation->submissiontitle), 'title'); + $link = $this->workshop->submission_url($allocation->submissionid); + $o .= $this->output->container(html_writer::link($link, format_string($allocation->submissiontitle)), 'title'); if (is_null($allocation->submissiongrade)) { $o .= $this->output->container(get_string('nogradeyet', 'workshop'), array('grade', 'missing')); } else { diff --git a/mod/workshop/assessment.php b/mod/workshop/assessment.php index 35756cc8ba1..851f290456c 100644 --- a/mod/workshop/assessment.php +++ b/mod/workshop/assessment.php @@ -63,6 +63,21 @@ $canoverridegrades = has_capability('mod/workshop:overridegrades', $worksho $isreviewer = ($USER->id == $assessment->reviewerid); $isauthor = ($USER->id == $submission->authorid); +if ($canviewallsubmissions) { + // check this flag against the group membership yet + if (groups_get_activity_groupmode($workshop->cm) == SEPARATEGROUPS) { + // user must have accessallgroups or share at least one group with the submission author + if (!has_capability('moodle/site:accessallgroups', $workshop->context)) { + $usersgroups = groups_get_activity_allowed_groups($workshop->cm); + $authorsgroups = groups_get_all_groups($workshop->course->id, $submission->authorid, $workshop->cm->groupingid, 'g.id'); + $sharedgroups = array_intersect_key($usersgroups, $authorsgroups); + if (empty($sharedgroups)) { + $canviewallsubmissions = false; + } + } + } +} + if ($isreviewer or $isauthor or ($canviewallassessments and $canviewallsubmissions)) { // such a user can continue } else { diff --git a/mod/workshop/db/access.php b/mod/workshop/db/access.php index 59ae764c722..e3e42c4f17d 100644 --- a/mod/workshop/db/access.php +++ b/mod/workshop/db/access.php @@ -156,8 +156,9 @@ $capabilities = array( ) ), - // Ability to view a work submitted by an other user. Applies to the user's group only - // or - if the user is allowed to access all groups - applies to any submission + // Ability to view the work submitted by an other user. In separate groups mode, + // the user has to be allowed to access all groups or be the member of the + // submission author's group. 'mod/workshop:viewallsubmissions' => array( 'captype' => 'read', 'contextlevel' => CONTEXT_MODULE, @@ -168,8 +169,9 @@ $capabilities = array( ) ), - // Ability to view published submission when the workshop is closed. Applies to the user's group only - // or - if the user is allowed to access all groups - applies to any submission + // Ability to view published submission when the workshop is closed. Group mode + // restrictions do not apply here, published submissions are available in all + // groups even in the separate groups mode. 'mod/workshop:viewpublishedsubmissions' => array( 'captype' => 'read', 'contextlevel' => CONTEXT_MODULE, @@ -193,8 +195,11 @@ $capabilities = array( ) ), - // Ability to always view the assessments of other users' work and the calculated grades, regardless the phase. - // Applies to the user's group only or - if the user is allowed to access all groups - applies to any assessment + // Ability to always view the assessments of other users' work and the calculated grades, + // regardless the phase. The separate groups membership is checked against the submission + // author only, not against the reviewer. In other words, if the user has this capability + // and is allowed to see some submission, then they are implicitly allowed to see all + // assessments of that submissions even if they do not share a group with the reviewer. 'mod/workshop:viewallassessments' => array( 'captype' => 'read', 'contextlevel' => CONTEXT_MODULE, diff --git a/mod/workshop/lang/en/workshop.php b/mod/workshop/lang/en/workshop.php index 79a83c3204e..e4d1b17a7fd 100644 --- a/mod/workshop/lang/en/workshop.php +++ b/mod/workshop/lang/en/workshop.php @@ -33,7 +33,7 @@ $string['allocation'] = 'Submission allocation'; $string['allocationdone'] = 'Allocation done'; $string['allocationerror'] = 'Allocation error'; $string['allocationconfigured'] = 'Allocation configured'; -$string['allsubmissions'] = 'All submissions'; +$string['allsubmissions'] = 'All submissions ({$a})'; $string['alreadygraded'] = 'Already graded'; $string['areainstructauthors'] = 'Instructions for submission'; $string['areainstructreviewers'] = 'Instructions for assessment'; @@ -144,6 +144,7 @@ $string['gradinggradecalculated'] = 'Calculated grade for assessment'; $string['gradinggradeof'] = 'Grade for assessment (of {$a})'; $string['gradinggradeover'] = 'Override grade for assessment'; $string['gradingsettings'] = 'Grading settings'; +$string['groupnoallowed'] = 'You are not allowed to access any group in this workshop'; $string['chooseuser'] = 'Choose user...'; $string['iamsure'] = 'Yes, I am sure'; $string['info'] = 'Info'; @@ -195,6 +196,8 @@ $string['saveandclose'] = 'Save and close'; $string['saveandcontinue'] = 'Save and continue editing'; $string['saveandpreview'] = 'Save and preview'; $string['selfassessmentdisabled'] = 'Self-assessment disabled'; +$string['showingperpage'] = 'Showing {$a} items per page'; +$string['showingperpagechange'] = 'Change ...'; $string['someuserswosubmission'] = 'There is at least one author who has not yet submitted their work'; $string['sortasc'] = 'Ascending sort'; $string['sortdesc'] = 'Descending sort'; diff --git a/mod/workshop/lib.php b/mod/workshop/lib.php index cc732f9d940..6abaf73df22 100644 --- a/mod/workshop/lib.php +++ b/mod/workshop/lib.php @@ -306,7 +306,19 @@ function workshop_user_complete($course, $user, $mod, $workshop) { } if (has_capability('mod/workshop:viewallsubmissions', $workshop->context)) { - if ($submission = $workshop->get_submission_by_author($user->id)) { + $canviewsubmission = true; + if (groups_get_activity_groupmode($workshop->cm) == SEPARATEGROUPS) { + // user must have accessallgroups or share at least one group with the submission author + if (!has_capability('moodle/site:accessallgroups', $workshop->context)) { + $usersgroups = groups_get_activity_allowed_groups($workshop->cm); + $authorsgroups = groups_get_all_groups($workshop->course->id, $user->id, $workshop->cm->groupingid, 'g.id'); + $sharedgroups = array_intersect_key($usersgroups, $authorsgroups); + if (empty($sharedgroups)) { + $canviewsubmission = false; + } + } + } + if ($canviewsubmission and $submission = $workshop->get_submission_by_author($user->id)) { $title = format_string($submission->title); $url = $workshop->submission_url($submission->id); $link = html_writer::link($url, $title); diff --git a/mod/workshop/locallib.php b/mod/workshop/locallib.php index 09374e849ac..60ff66702e8 100644 --- a/mod/workshop/locallib.php +++ b/mod/workshop/locallib.php @@ -380,40 +380,181 @@ class workshop { //////////////////////////////////////////////////////////////////////////////// /** - * Fetches all users with the capability mod/workshop:submit in the current context + * Fetches all enrolled users with the capability mod/workshop:submit in the current workshop * - * The returned objects contain id, lastname and firstname properties and are ordered by lastname,firstname + * The returned objects contain properties required by user_picture and are ordered by lastname, firstname. + * Only users with the active enrolment are returned. * - * @todo handle with limits and groups - * @param bool $musthavesubmission If true, return only users who have already submitted. All possible authors otherwise. - * @return array array[userid] => stdclass{->id ->lastname ->firstname} + * @param bool $musthavesubmission if true, return only users who have already submitted + * @param int $groupid 0 means ignore groups, any other value limits the result by group id + * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set) + * @param int $limitnum return a subset containing this number of records (optional, required if $limitfrom is set) + * @return array array[userid] => stdClass */ - public function get_potential_authors($musthavesubmission=true) { - $users = get_users_by_capability($this->context, 'mod/workshop:submit', - 'u.id,u.lastname,u.firstname', 'u.lastname,u.firstname,u.id', '', '', '', '', false, false, true); - if ($musthavesubmission) { - $users = array_intersect_key($users, $this->users_with_submission(array_keys($users))); + public function get_potential_authors($musthavesubmission=true, $groupid=0, $limitfrom=0, $limitnum=0) { + global $DB; + + list($sql, $params) = $this->get_users_with_capability_sql('mod/workshop:submit', $musthavesubmission, $groupid); + + if (empty($sql)) { + return array(); } - return $users; + + $sql .= " ORDER BY lastname ASC, firstname ASC, id ASC"; + + return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum); } /** - * Fetches all users with the capability mod/workshop:peerassess in the current context + * Returns the total number of users that would be fetched by {@link self::get_potential_authors()} * - * The returned objects contain id, lastname and firstname properties and are ordered by lastname,firstname - * - * @todo handle with limits and groups - * @param bool $musthavesubmission If true, return only users who have already submitted. All possible users otherwise. - * @return array array[userid] => stdclass{->id ->lastname ->firstname} + * @param bool $musthavesubmission if true, count only users who have already submitted + * @param int $groupid 0 means ignore groups, any other value limits the result by group id + * @return int */ - public function get_potential_reviewers($musthavesubmission=false) { - $users = get_users_by_capability($this->context, 'mod/workshop:peerassess', - 'u.id, u.lastname, u.firstname', 'u.lastname,u.firstname,u.id', '', '', '', '', false, false, true); - if ($musthavesubmission) { - // users without their own submission can not be reviewers - $users = array_intersect_key($users, $this->users_with_submission(array_keys($users))); + public function count_potential_authors($musthavesubmission=true, $groupid=0) { + global $DB; + + list($sql, $params) = $this->get_users_with_capability_sql('mod/workshop:submit', $musthavesubmission, $groupid); + + if (empty($sql)) { + return 0; } - return $users; + + $sql = "SELECT COUNT(*) + FROM ($sql) tmp"; + + return $DB->count_records_sql($sql, $params); + } + + /** + * Fetches all enrolled users with the capability mod/workshop:peerassess in the current workshop + * + * The returned objects contain properties required by user_picture and are ordered by lastname, firstname. + * Only users with the active enrolment are returned. + * + * @param bool $musthavesubmission if true, return only users who have already submitted + * @param int $groupid 0 means ignore groups, any other value limits the result by group id + * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set) + * @param int $limitnum return a subset containing this number of records (optional, required if $limitfrom is set) + * @return array array[userid] => stdClass + */ + public function get_potential_reviewers($musthavesubmission=false, $groupid=0, $limitfrom=0, $limitnum=0) { + global $DB; + + list($sql, $params) = $this->get_users_with_capability_sql('mod/workshop:peerassess', $musthavesubmission, $groupid); + + if (empty($sql)) { + return array(); + } + + $sql .= " ORDER BY lastname ASC, firstname ASC, id ASC"; + + return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum); + } + + /** + * Returns the total number of users that would be fetched by {@link self::get_potential_reviewers()} + * + * @param bool $musthavesubmission if true, count only users who have already submitted + * @param int $groupid 0 means ignore groups, any other value limits the result by group id + * @return int + */ + public function count_potential_reviewers($musthavesubmission=false, $groupid=0) { + global $DB; + + list($sql, $params) = $this->get_users_with_capability_sql('mod/workshop:peerassess', $musthavesubmission, $groupid); + + if (empty($sql)) { + return 0; + } + + $sql = "SELECT COUNT(*) + FROM ($sql) tmp"; + + return $DB->count_records_sql($sql, $params); + } + + /** + * Fetches all enrolled users that are authors or reviewers (or both) in the current workshop + * + * The returned objects contain properties required by user_picture and are ordered by lastname, firstname. + * Only users with the active enrolment are returned. + * + * @see self::get_potential_authors() + * @see self::get_potential_reviewers() + * @param bool $musthavesubmission if true, return only users who have already submitted + * @param int $groupid 0 means ignore groups, any other value limits the result by group id + * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set) + * @param int $limitnum return a subset containing this number of records (optional, required if $limitfrom is set) + * @return array array[userid] => stdClass + */ + public function get_participants($musthavesubmission=false, $groupid=0, $limitfrom=0, $limitnum=0) { + global $DB; + + list($sql, $params) = $this->get_participants_sql($musthavesubmission, $groupid); + + if (empty($sql)) { + return array(); + } + + $sql .= " ORDER BY lastname ASC, firstname ASC, id ASC"; + + return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum); + } + + /** + * Returns the total number of records that would be returned by {@link self::get_participants()} + * + * @param bool $musthavesubmission if true, return only users who have already submitted + * @param int $groupid 0 means ignore groups, any other value limits the result by group id + * @return int + */ + public function count_participants($musthavesubmission=false, $groupid=0) { + global $DB; + + list($sql, $params) = $this->get_participants_sql($musthavesubmission, $groupid); + + if (empty($sql)) { + return 0; + } + + $sql = "SELECT COUNT(*) + FROM ($sql) tmp"; + + return $DB->count_records_sql($sql, $params); + } + + /** + * Checks if the given user is an actively enrolled participant in the workshop + * + * @param int $userid, defaults to the current $USER + * @return boolean + */ + public function is_participant($userid=null) { + global $USER, $DB; + + if (is_null($userid)) { + $userid = $USER->id; + } + + list($sql, $params) = $this->get_participants_sql(); + + if (empty($sql)) { + return false; + } + + $sql = "SELECT COUNT(*) + FROM {user} uxx + JOIN ({$sql}) pxx ON uxx.id = pxx.id + WHERE uxx.id = :uxxid"; + $params['uxxid'] = $userid; + + if ($DB->count_records_sql($sql, $params)) { + return true; + } + + return false; } /** @@ -479,28 +620,71 @@ class workshop { return $DB->get_records_sql($sql, $params); } + /** + * Returns the total number of records that would be returned by {@link self::get_submissions()} + * + * @param mixed $authorid int|array|'all' If set to [array of] integer, return submission[s] of the given user[s] only + * @param int $groupid If non-zero, return only submissions by authors in the specified group + * @return int number of records + */ + public function count_submissions($authorid='all', $groupid=0) { + global $DB; + + $params = array('workshopid' => $this->id); + $sql = "SELECT COUNT(s.id) + FROM {workshop_submissions} s + JOIN {user} u ON (s.authorid = u.id)"; + if ($groupid) { + $sql .= " JOIN {groups_members} gm ON (gm.userid = u.id AND gm.groupid = :groupid)"; + $params['groupid'] = $groupid; + } + $sql .= " WHERE s.example = 0 AND s.workshopid = :workshopid"; + + if ('all' === $authorid) { + // no additional conditions + } elseif (!empty($authorid)) { + list($usql, $uparams) = $DB->get_in_or_equal($authorid, SQL_PARAMS_NAMED); + $sql .= " AND authorid $usql"; + $params = array_merge($params, $uparams); + } else { + // $authorid is empty + return 0; + } + + return $DB->count_records_sql($sql, $params); + } + + /** * Returns submissions from this workshop * * Fetches data from {workshop_submissions} and adds some useful information from other * tables. Does not return textual fields to prevent possible memory lack issues. * + * @see self::count_submissions() * @param mixed $authorid int|array|'all' If set to [array of] integer, return submission[s] of the given user[s] only + * @param int $groupid If non-zero, return only submissions by authors in the specified group + * @param int $limitfrom Return a subset of records, starting at this point (optional) + * @param int $limitnum Return a subset containing this many records in total (optional, required if $limitfrom is set) * @return array of records or an empty array */ - public function get_submissions($authorid='all') { + public function get_submissions($authorid='all', $groupid=0, $limitfrom=0, $limitnum=0) { global $DB; $authorfields = user_picture::fields('u', null, 'authoridx', 'author'); $gradeoverbyfields = user_picture::fields('t', null, 'gradeoverbyx', 'over'); + $params = array('workshopid' => $this->id); $sql = "SELECT s.id, s.workshopid, s.example, s.authorid, s.timecreated, s.timemodified, s.title, s.grade, s.gradeover, s.gradeoverby, s.published, $authorfields, $gradeoverbyfields FROM {workshop_submissions} s - INNER JOIN {user} u ON (s.authorid = u.id) - LEFT JOIN {user} t ON (s.gradeoverby = t.id) + JOIN {user} u ON (s.authorid = u.id)"; + if ($groupid) { + $sql .= " JOIN {groups_members} gm ON (gm.userid = u.id AND gm.groupid = :groupid)"; + $params['groupid'] = $groupid; + } + $sql .= " LEFT JOIN {user} t ON (s.gradeoverby = t.id) WHERE s.example = 0 AND s.workshopid = :workshopid"; - $params = array('workshopid' => $this->id); if ('all' === $authorid) { // no additional conditions @@ -514,7 +698,7 @@ class workshop { } $sql .= " ORDER BY u.lastname, u.firstname"; - return $DB->get_records_sql($sql, $params); + return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum); } /** @@ -1422,18 +1606,18 @@ class workshop { * Prepares data object with all workshop grades to be rendered * * @param int $userid the user we are preparing the report for - * @param mixed $groups single group or array of groups - only show users who are in one of these group(s). Defaults to all + * @param int $groupid if non-zero, prepare the report for the given group only * @param int $page the current page (for the pagination) * @param int $perpage participants per page (for the pagination) * @param string $sortby lastname|firstname|submissiontitle|submissiongrade|gradinggrade * @param string $sorthow ASC|DESC * @return stdclass data for the renderer */ - public function prepare_grading_report_data($userid, $groups, $page, $perpage, $sortby, $sorthow) { + public function prepare_grading_report_data($userid, $groupid, $page, $perpage, $sortby, $sorthow) { global $DB; $canviewall = has_capability('mod/workshop:viewallassessments', $this->context, $userid); - $isparticipant = has_any_capability(array('mod/workshop:submit', 'mod/workshop:peerassess'), $this->context, $userid); + $isparticipant = $this->is_participant($userid); if (!$canviewall and !$isparticipant) { // who the hell is this? @@ -1450,9 +1634,7 @@ class workshop { // get the list of user ids to be displayed if ($canviewall) { - // fetch the list of ids of all workshop participants - this may get really long so fetch just id - $participants = get_users_by_capability($this->context, array('mod/workshop:submit', 'mod/workshop:peerassess'), - 'u.id', '', '', '', $groups, '', false, false, true); + $participants = $this->get_participants(false, $groupid); } else { // this is an ordinary workshop participant (aka student) - display the report just for him/her $participants = array($userid => (object)array('id' => $userid)); @@ -2087,32 +2269,84 @@ class workshop { } /** - * Given a list of user ids, returns the filtered one containing just ids of users with own submission + * Returns SQL to fetch all enrolled users with the given capability in the current workshop * - * Example submissions are ignored. + * The returned array consists of string $sql and the $params array. Note that the $sql can be + * empty if groupmembersonly is enabled and the associated grouping is empty. * - * @param array $userids - * @return array + * @param string $capability the name of the capability + * @param bool $musthavesubmission ff true, return only users who have already submitted + * @param int $groupid 0 means ignore groups, any other value limits the result by group id + * @return array of (string)sql, (array)params */ - protected function users_with_submission(array $userids) { - global $DB; + protected function get_users_with_capability_sql($capability, $musthavesubmission, $groupid) { + global $CFG; + /** @var int static counter used to generate unique parameter holders */ + static $inc = 0; + $inc++; - if (empty($userids)) { - return array(); - } - $userswithsubmission = array(); - list($usql, $uparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); - $sql = "SELECT id,authorid - FROM {workshop_submissions} - WHERE example = 0 AND workshopid = :workshopid AND authorid $usql"; - $params = array('workshopid' => $this->id); - $params = array_merge($params, $uparams); - $submissions = $DB->get_records_sql($sql, $params); - foreach ($submissions as $submission) { - $userswithsubmission[$submission->authorid] = true; + // if the caller requests all groups and we are in groupmembersonly mode, use the + // recursive call of itself to get users from all groups in the grouping + if (empty($groupid) and !empty($CFG->enablegroupmembersonly) and $this->cm->groupmembersonly) { + $groupingid = $this->cm->groupingid; + $groupinggroupids = array_keys(groups_get_all_groups($this->cm->course, 0, $this->cm->groupingid, 'g.id')); + $sql = array(); + $params = array(); + foreach ($groupinggroupids as $groupinggroupid) { + if ($groupinggroupid > 0) { // just in case in order not to fall into the endless loop + list($gsql, $gparams) = $this->get_users_with_capability_sql($capability, $musthavesubmission, $groupinggroupid); + $sql[] = $gsql; + $params = array_merge($params, $gparams); + } + } + $sql = implode(PHP_EOL." UNION ".PHP_EOL, $sql); + return array($sql, $params); } - return $userswithsubmission; + list($esql, $params) = get_enrolled_sql($this->context, $capability, $groupid, true); + + $userfields = user_picture::fields('u'); + + $sql = "SELECT $userfields + FROM {user} u + JOIN ($esql) je ON (je.id = u.id AND u.deleted = 0) "; + + if ($musthavesubmission) { + $sql .= " JOIN {workshop_submissions} ws ON (ws.authorid = u.id AND ws.example = 0 AND ws.workshopid = :workshopid{$inc}) "; + $params['workshopid'.$inc] = $this->id; + } + + return array($sql, $params); + } + + /** + * Returns SQL statement that can be used to fetch all actively enrolled participants in the workshop + * + * @param bool $musthavesubmission if true, return only users who have already submitted + * @param int $groupid 0 means ignore groups, any other value limits the result by group id + * @return array of (string)sql, (array)params + */ + protected function get_participants_sql($musthavesubmission=false, $groupid=0) { + + list($sql1, $params1) = $this->get_users_with_capability_sql('mod/workshop:submit', $musthavesubmission, $groupid); + list($sql2, $params2) = $this->get_users_with_capability_sql('mod/workshop:peerassess', $musthavesubmission, $groupid); + + if (empty($sql1) or empty($sql2)) { + if (empty($sql1) and empty($sql2)) { + return array('', array()); + } else if (empty($sql1)) { + $sql = $sql2; + $params = $params2; + } else { + $sql = $sql1; + $params = $params1; + } + } else { + $sql = $sql1.PHP_EOL." UNION ".PHP_EOL.$sql2; + $params = array_merge($params1, $params2); + } + + return array($sql, $params); } /** @@ -2303,8 +2537,7 @@ class workshop_user_plan implements renderable { $task = new stdclass(); $task->title = get_string('allocate', 'workshop'); $task->link = $workshop->allocation_url(); - $numofauthors = count(get_users_by_capability($workshop->context, 'mod/workshop:submit', 'u.id', '', '', '', - '', '', false, true)); + $numofauthors = $workshop->count_potential_authors(false); $numofsubmissions = $DB->count_records('workshop_submissions', array('workshopid'=>$workshop->id, 'example'=>0)); $sql = 'SELECT COUNT(s.id) AS nonallocated FROM {workshop_submissions} s @@ -2475,7 +2708,7 @@ class workshop_user_plan implements renderable { $phase->title = get_string('phaseevaluation', 'workshop'); $phase->tasks = array(); if (has_capability('mod/workshop:overridegrades', $workshop->context)) { - $expected = count($workshop->get_potential_authors(false)); + $expected = $workshop->count_potential_authors(false); $calculated = $DB->count_records_select('workshop_submissions', 'workshopid = ? AND (grade IS NOT NULL OR gradeover IS NOT NULL)', array($workshop->id)); $task = new stdclass(); @@ -2491,7 +2724,7 @@ class workshop_user_plan implements renderable { } $phase->tasks['calculatesubmissiongrade'] = $task; - $expected = count($workshop->get_potential_reviewers(false)); + $expected = $workshop->count_potential_reviewers(false); $calculated = $DB->count_records_select('workshop_aggregations', 'workshopid = ? AND gradinggrade IS NOT NULL', array($workshop->id)); $task = new stdclass(); diff --git a/mod/workshop/renderer.php b/mod/workshop/renderer.php index 1747a44f472..d7cb903cb2a 100644 --- a/mod/workshop/renderer.php +++ b/mod/workshop/renderer.php @@ -690,6 +690,30 @@ class mod_workshop_renderer extends plugin_renderer_base { return $this->render_workshop_assessment($assessment); } + /** + * Renders a perpage selector for workshop listings + * + * The scripts using this have to define the $PAGE->url prior to calling this + * and deal with eventually submitted value themselves. + * + * @param int $current current value of the perpage parameter + * @return string HTML + */ + public function perpage_selector($current=10) { + + $options = array(); + foreach (array(10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 1000) as $option) { + if ($option != $current) { + $options[$option] = $option; + } + } + $select = new single_select($this->page->url, 'perpage', $options, '', array('' => get_string('showingperpagechange', 'mod_workshop'))); + $select->label = get_string('showingperpage', 'mod_workshop', $current); + $select->method = 'post'; + + return $this->output->container($this->output->render($select), 'perpagewidget'); + } + //////////////////////////////////////////////////////////////////////////// // Internal rendering helper methods //////////////////////////////////////////////////////////////////////////// diff --git a/mod/workshop/styles.css b/mod/workshop/styles.css index aea0cf8f842..ba99b03d8a7 100644 --- a/mod/workshop/styles.css +++ b/mod/workshop/styles.css @@ -18,6 +18,16 @@ text-align: left; } +.path-mod-workshop .groupwidget { + text-align:center; + margin: 0.75 auto; +} + +.path-mod-workshop .perpagewidget { + text-align:center; + margin: 0.75 auto; +} + /** * Submission - one line summary display */ diff --git a/mod/workshop/submission.php b/mod/workshop/submission.php index 9e2c2cd0cbd..eb10b8fd90d 100644 --- a/mod/workshop/submission.php +++ b/mod/workshop/submission.php @@ -87,6 +87,21 @@ if ($submission->id and !$workshop->modifying_submission_allowed($USER->id)) { $editable = false; } +if ($canviewall) { + // check this flag against the group membership yet + if (groups_get_activity_groupmode($workshop->cm) == SEPARATEGROUPS) { + // user must have accessallgroups or share at least one group with the submission author + if (!has_capability('moodle/site:accessallgroups', $workshop->context)) { + $usersgroups = groups_get_activity_allowed_groups($workshop->cm); + $authorsgroups = groups_get_all_groups($workshop->course->id, $submission->authorid, $workshop->cm->groupingid, 'g.id'); + $sharedgroups = array_intersect_key($usersgroups, $authorsgroups); + if (empty($sharedgroups)) { + $canviewall = false; + } + } + } +} + if ($editable and $workshop->useexamples and $workshop->examplesmode == workshop::EXAMPLES_BEFORE_SUBMISSION and !has_capability('mod/workshop:manageexamples', $workshop->context)) { // check that all required examples have been assessed by the user diff --git a/mod/workshop/view.php b/mod/workshop/view.php index 999894dcf37..e5817dd263f 100644 --- a/mod/workshop/view.php +++ b/mod/workshop/view.php @@ -34,6 +34,10 @@ require_once($CFG->libdir.'/completionlib.php'); $id = optional_param('id', 0, PARAM_INT); // course_module ID, or $w = optional_param('w', 0, PARAM_INT); // workshop instance ID $editmode = optional_param('editmode', null, PARAM_BOOL); +$page = optional_param('page', 0, PARAM_INT); +$perpage = optional_param('perpage', null, PARAM_INT); +$sortby = optional_param('sortby', 'lastname', PARAM_ALPHA); +$sorthow = optional_param('sorthow', 'ASC', PARAM_ALPHA); if ($id) { $cm = get_coursemodule_from_id('workshop', $id, 0, false, MUST_EXIST); @@ -63,6 +67,12 @@ $PAGE->set_url($workshop->view_url()); $PAGE->set_title($workshop->name); $PAGE->set_heading($course->fullname); +if ($perpage and $perpage > 0 and $perpage <= 1000) { + require_sesskey(); + set_user_preference('workshop_perpage', $perpage); + redirect($PAGE->url); +} + $output = $PAGE->get_renderer('mod_workshop'); $userplan = new workshop_user_plan($workshop, $USER->id); @@ -172,15 +182,43 @@ case workshop::PHASE_SUBMISSION: } if (has_capability('mod/workshop:viewallsubmissions', $PAGE->context)) { - $shownames = has_capability('mod/workshop:viewauthornames', $PAGE->context); - print_collapsible_region_start('', 'workshop-viewlet-allsubmissions', get_string('allsubmissions', 'workshop')); + $groupmode = groups_get_activity_groupmode($workshop->cm); + $groupid = groups_get_activity_group($workshop->cm, true); + + if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $workshop->context)) { + $allowedgroups = groups_get_activity_allowed_groups($workshop->cm); + if (empty($allowedgroups)) { + echo $output->container(get_string('groupnoallowed', 'mod_workshop'), 'groupwidget error'); + break; + } + if (! in_array($groupid, array_keys($allowedgroups))) { + echo $output->container(get_string('groupnotamember', 'core_group'), 'groupwidget error'); + break; + } + } + + $countsubmissions = $workshop->count_submissions('all', $groupid); + $perpage = get_user_preferences('workshop_perpage', 10); + $pagingbar = new paging_bar($countsubmissions, $page, $perpage, $PAGE->url, 'page'); + + print_collapsible_region_start('', 'workshop-viewlet-allsubmissions', get_string('allsubmissions', 'workshop', $countsubmissions)); echo $output->box_start('generalbox allsubmissions'); - if (! $submissions = $workshop->get_submissions('all')) { + echo $output->container(groups_print_activity_menu($workshop->cm, $PAGE->url, true), 'groupwidget'); + + if ($countsubmissions == 0) { echo $output->container(get_string('nosubmissions', 'workshop'), 'nosubmissions'); + + } else { + $submissions = $workshop->get_submissions('all', $groupid, $page * $perpage, $perpage); + $shownames = has_capability('mod/workshop:viewauthornames', $workshop->context); + echo $output->render($pagingbar); + foreach ($submissions as $submission) { + echo $output->render($workshop->prepare_submission_summary($submission, $shownames)); + } + echo $output->render($pagingbar); + echo $output->perpage_selector($perpage); } - foreach ($submissions as $submission) { - echo $output->render($workshop->prepare_submission_summary($submission, $shownames)); - } + echo $output->box_end(); print_collapsible_region_end(); } @@ -214,19 +252,16 @@ case workshop::PHASE_ASSESSMENT: } if (has_capability('mod/workshop:viewallassessments', $PAGE->context)) { - $page = optional_param('page', 0, PARAM_INT); - $sortby = optional_param('sortby', 'lastname', PARAM_ALPHA); - $sorthow = optional_param('sorthow', 'ASC', PARAM_ALPHA); - $perpage = 10; // todo let the user modify this - $groups = ''; // todo let the user choose the group - $PAGE->set_url($PAGE->url, compact('sortby', 'sorthow', 'page')); // TODO: this is suspicious - $data = $workshop->prepare_grading_report_data($USER->id, $groups, $page, $perpage, $sortby, $sorthow); + $perpage = get_user_preferences('workshop_perpage', 10); + $groupid = groups_get_activity_group($workshop->cm, true); + $data = $workshop->prepare_grading_report_data($USER->id, $groupid, $page, $perpage, $sortby, $sorthow); if ($data) { $showauthornames = has_capability('mod/workshop:viewauthornames', $workshop->context); $showreviewernames = has_capability('mod/workshop:viewreviewernames', $workshop->context); // prepare paging bar - $pagingbar = new paging_bar($data->totalcount, $page, $perpage, $PAGE->url, 'page'); + $baseurl = new moodle_url($PAGE->url, array('sortby' => $sortby, 'sorthow' => $sorthow)); + $pagingbar = new paging_bar($data->totalcount, $page, $perpage, $baseurl, 'page'); // grading report display options $reportopts = new stdclass(); @@ -237,9 +272,15 @@ case workshop::PHASE_ASSESSMENT: $reportopts->showsubmissiongrade = false; $reportopts->showgradinggrade = false; + print_collapsible_region_start('', 'workshop-viewlet-gradereport', get_string('gradesreport', 'workshop')); + echo $output->box_start('generalbox gradesreport'); + echo $output->container(groups_print_activity_menu($workshop->cm, $PAGE->url, true), 'groupwidget'); echo $output->render($pagingbar); echo $output->render(new workshop_grading_report($data, $reportopts)); echo $output->render($pagingbar); + echo $output->perpage_selector($perpage); + echo $output->box_end(); + print_collapsible_region_end(); } } if (trim($workshop->instructreviewers)) { @@ -346,13 +387,9 @@ case workshop::PHASE_ASSESSMENT: break; case workshop::PHASE_EVALUATION: if (has_capability('mod/workshop:viewallassessments', $PAGE->context)) { - $page = optional_param('page', 0, PARAM_INT); - $sortby = optional_param('sortby', 'lastname', PARAM_ALPHA); - $sorthow = optional_param('sorthow', 'ASC', PARAM_ALPHA); - $perpage = 10; // todo let the user modify this - $groups = ''; // todo let the user choose the group - $PAGE->set_url($PAGE->url, compact('sortby', 'sorthow', 'page')); // TODO: this is suspicious - $data = $workshop->prepare_grading_report_data($USER->id, $groups, $page, $perpage, $sortby, $sorthow); + $perpage = get_user_preferences('workshop_perpage', 10); + $groupid = groups_get_activity_group($workshop->cm, true); + $data = $workshop->prepare_grading_report_data($USER->id, $groupid, $page, $perpage, $sortby, $sorthow); if ($data) { $showauthornames = has_capability('mod/workshop:viewauthornames', $workshop->context); $showreviewernames = has_capability('mod/workshop:viewreviewernames', $workshop->context); @@ -366,7 +403,8 @@ case workshop::PHASE_EVALUATION: } // prepare paging bar - $pagingbar = new paging_bar($data->totalcount, $page, $perpage, $PAGE->url, 'page'); + $baseurl = new moodle_url($PAGE->url, array('sortby' => $sortby, 'sorthow' => $sorthow)); + $pagingbar = new paging_bar($data->totalcount, $page, $perpage, $baseurl, 'page'); // grading report display options $reportopts = new stdclass(); @@ -377,9 +415,15 @@ case workshop::PHASE_EVALUATION: $reportopts->showsubmissiongrade = true; $reportopts->showgradinggrade = true; + print_collapsible_region_start('', 'workshop-viewlet-gradereport', get_string('gradesreport', 'workshop')); + echo $output->box_start('generalbox gradesreport'); + echo $output->container(groups_print_activity_menu($workshop->cm, $PAGE->url, true), 'groupwidget'); echo $output->render($pagingbar); echo $output->render(new workshop_grading_report($data, $reportopts)); echo $output->render($pagingbar); + echo $output->perpage_selector($perpage); + echo $output->box_end(); + print_collapsible_region_end(); } } if (has_capability('mod/workshop:overridegrades', $workshop->context)) { @@ -454,19 +498,16 @@ case workshop::PHASE_EVALUATION: break; case workshop::PHASE_CLOSED: if (has_capability('mod/workshop:viewallassessments', $PAGE->context)) { - $page = optional_param('page', 0, PARAM_INT); - $sortby = optional_param('sortby', 'lastname', PARAM_ALPHA); - $sorthow = optional_param('sorthow', 'ASC', PARAM_ALPHA); - $perpage = 10; // todo let the user modify this - $groups = ''; // todo let the user choose the group - $PAGE->set_url($PAGE->url, compact('sortby', 'sorthow', 'page')); // TODO: this is suspicious - $data = $workshop->prepare_grading_report_data($USER->id, $groups, $page, $perpage, $sortby, $sorthow); + $perpage = get_user_preferences('workshop_perpage', 10); + $groupid = groups_get_activity_group($workshop->cm, true); + $data = $workshop->prepare_grading_report_data($USER->id, $groupid, $page, $perpage, $sortby, $sorthow); if ($data) { $showauthornames = has_capability('mod/workshop:viewauthornames', $workshop->context); $showreviewernames = has_capability('mod/workshop:viewreviewernames', $workshop->context); // prepare paging bar - $pagingbar = new paging_bar($data->totalcount, $page, $perpage, $PAGE->url, 'page'); + $baseurl = new moodle_url($PAGE->url, array('sortby' => $sortby, 'sorthow' => $sorthow)); + $pagingbar = new paging_bar($data->totalcount, $page, $perpage, $baseurl, 'page'); // grading report display options $reportopts = new stdclass(); @@ -478,9 +519,13 @@ case workshop::PHASE_CLOSED: $reportopts->showgradinggrade = true; print_collapsible_region_start('', 'workshop-viewlet-gradereport', get_string('gradesreport', 'workshop')); + echo $output->box_start('generalbox gradesreport'); + echo $output->container(groups_print_activity_menu($workshop->cm, $PAGE->url, true), 'groupwidget'); echo $output->render($pagingbar); echo $output->render(new workshop_grading_report($data, $reportopts)); echo $output->render($pagingbar); + echo $output->perpage_selector($perpage); + echo $output->box_end(); print_collapsible_region_end(); } }