Current Path : /var/www/ljmtc/cbt/mod/grouptool/ |
Current File : /var/www/ljmtc/cbt/mod/grouptool/locallib.php |
<?php // This file is part of mod_grouptool for Moodle - http://moodle.org/ // // It is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // It is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Contains class mod_grouptool with most of grouptool's logic. * * @package mod_grouptool * @author Philipp Hager * @author Hannes Laimer * @copyright 2014 Academic Moodle Cooperation {@link http://www.academic-moodle-cooperation.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot.'/mod/grouptool/definitions.php'); require_once($CFG->libdir.'/formslib.php'); require_once($CFG->dirroot.'/mod/grouptool/lib.php'); require_once($CFG->dirroot.'/group/lib.php'); require_once($CFG->dirroot.'/cohort/lib.php'); require_once($CFG->libdir.'/gradelib.php'); require_once($CFG->libdir.'/grade/grade_grade.php'); require_once($CFG->libdir.'/pdflib.php'); /** * class containing most of the logic used in grouptool-module * * @package mod_grouptool * @author Philipp Hager * @copyright 2014 Academic Moodle Cooperation {@link http://www.academic-moodle-cooperation.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class mod_grouptool { /** @var object */ protected $cm; /** @var object */ protected $course; /** @var object */ protected $grouptool; /** @var object instance's context record */ protected $context; /** * filter all groups */ const FILTER_ALL = 0; /** * filter active groups */ const FILTER_ACTIVE = 1; /** * filter inactive groups */ const FILTER_INACTIVE = 2; /** * NAME_TAGS - the tags available for grouptool's group naming schemes */ const NAME_TAGS = ['[firstname]', '[lastname]', '[idnumber]', '[username]', '@', '#']; /** * HIDE_GROUPMEMBERS - never show groupmembers no matter what... */ const HIDE_GROUPMEMBERS = GROUPTOOL_HIDE_GROUPMEMBERS; /** * SHOW_GROUPMEMBERS_AFTER_DUE - show groupmembers after due date */ const SHOW_GROUPMEMBERS_AFTER_DUE = GROUPTOOL_SHOW_GROUPMEMBERS_AFTER_DUE; /** * SHOW_GROUPMEMBERS_AFTER_DUE - show members of own group(s) after due date */ const SHOW_OWN_GROUPMEMBERS_AFTER_DUE = GROUPTOOL_SHOW_OWN_GROUPMEMBERS_AFTER_DUE; /** * SHOW_OWN_GROUPMEMBERS_AFTER_REG - show members of own group(s) immediately after registration */ const SHOW_OWN_GROUPMEMBERS_AFTER_REG = GROUPTOOL_SHOW_OWN_GROUPMEMBERS_AFTER_REG; /** * SHOW_GROUPMEMBERS - show groupmembers no matter what... */ const SHOW_GROUPMEMBERS = GROUPTOOL_SHOW_GROUPMEMBERS; /** * Constructor for the grouptool class * * If cmid is set create the cm, course, checkmark objects. * * @param int $cmid the current course module id - not set for new grouptools * @param stdClass $grouptool usually null, but if we have it we pass it to save db access * @param stdClass $cm usually null, but if we have it we pass it to save db access * @param stdClass $course usually null, but if we have it we pass it to save db access * @throws \coding_exception * @throws \dml_exception * @throws \moodle_exception */ public function __construct($cmid, $grouptool=null, $cm=null, $course=null) { global $DB; if ($cmid == 'staticonly') { // Use static functions only! return; } if (!empty($cm)) { $this->cm = $cm; } else if (! $this->cm = get_coursemodule_from_id('grouptool', $cmid)) { print_error('invalidcoursemodule'); } $this->context = context_module::instance($this->cm->id); if ($course) { $this->course = $course; } else if (! $this->course = $DB->get_record('course', ['id' => $this->cm->course])) { print_error('invalidid', 'grouptool'); } if ($grouptool) { $this->grouptool = $grouptool; } else if (! $this->grouptool = $DB->get_record('grouptool', ['id' => $this->cm->instance])) { print_error('invalidid', 'grouptool'); } $this->grouptool->cmidnumber = $this->cm->idnumber; $this->grouptool->course = $this->course->id; /* * visibility handled by require_login() with $cm parameter * get current group only when really needed */ } /** * Return the grouptools name * * @return string the name */ public function get_name() { return $this->grouptool->name; } /** * Return Grouptool's settings * * @return object Grouptool's DB record */ public function get_settings() { return $this->grouptool; } /** * Return Grouptool's multiple registrations settings * * @return array [allow_multiple, choose_min, choose_max] */ public function get_reg_settings() { return [$this->grouptool->allow_multiple, $this->grouptool->choose_min, $this->grouptool->choose_max]; } /** * Translates top level tabs. * * @param tabobject[] $tabs * @param string|null $tab */ public static function translate_top_level_tabs(array $tabs, string $tab = null) { global $SESSION; // Now translate, if there's a top level tab chosen, which has just child-tabs! switch ($tab) { case 'users': case 'administration': if (!empty($tabs[$tab]->subtree)) { $tab = key($tabs[$tab]->subtree); $SESSION->mod_grouptool->currenttab = $tab; } break; } } /** * Print a message along with button choices for Continue/Cancel * * If a string or moodle_url is given instead of a single_button, method defaults to post. * If cancel=null only continue button is displayed! * * @param string $message The question to ask the user * @param single_button|moodle_url|string $continue The single_button component representing the * Continue answer. Can also be a moodle_url * or string URL * @param single_button|moodle_url|string $cancel The single_button component representing the * Cancel answer. Can also be a moodle_url or * string URL * @return string HTML fragment * @throws coding_exception * @throws moodle_exception */ public function confirm($message, $continue, $cancel = null) { global $OUTPUT; if (!($continue instanceof single_button)) { if (is_string($continue)) { $url = new moodle_url($continue); $continue = new single_button($url, get_string('continue'), 'post', true); } else if ($continue instanceof moodle_url) { $continue = new single_button($continue, get_string('continue'), 'post', true); } else { throw new coding_exception('The continue param to grouptool::confirm() must be either a'. ' URL (string/moodle_url) or a single_button instance.'); } } if (!($cancel instanceof single_button)) { if (is_string($cancel)) { $cancel = new single_button(new moodle_url($cancel), get_string('cancel'), 'get'); } else if ($cancel instanceof moodle_url) { $cancel = new single_button($cancel, get_string('cancel'), 'get'); } else if ($cancel == null) { $cancel = null; } else { throw new coding_exception('The cancel param to grouptool::confirm() must be either a'. ' URL (string/moodle_url), single_button instance or null.'); } } $output = $OUTPUT->box_start('generalbox modal modal-dialog modal-in-page show', 'notice'); $output .= $OUTPUT->box_start('modal-content', 'modal-content'); $output .= $OUTPUT->box_start('modal-header', 'modal-header'); $output .= html_writer::tag('h4', get_string('confirm')); $output .= $OUTPUT->box_end(); $output .= $OUTPUT->box_start('modal-body', 'modal-body'); $output .= html_writer::tag('p', $message); $output .= $OUTPUT->box_end(); $output .= $OUTPUT->box_start('modal-footer', 'modal-footer'); $cancel = ($cancel != null) ? $OUTPUT->render($cancel) : ""; $output .= html_writer::tag('div', $OUTPUT->render($continue) . $cancel, ['class' => 'buttons']); $output .= $OUTPUT->box_end(); $output .= $OUTPUT->box_end(); $output .= $OUTPUT->box_end(); return $output; } /** * Parse a group name for characters to replace * * @param string $namescheme The scheme used for building group names * @param int $groupnumber The number of the group to be used in the parsed format string * @param stdClass|array $members optional object or array of objects containing data of members * for the tags to be replaced with * @param int $digits optional number of digits for from-to-group-creation * @return string the parsed format string */ private function groups_parse_name($namescheme, $groupnumber, $members = null, $digits = 0) { $tags = ['firstname', 'lastname', 'idnumber', 'username']; $pregsearch = "#\[(".implode("|", $tags).")\]#"; if (preg_match($pregsearch, $namescheme) > 0) { if ($members != null) { $data = []; if (is_array($members)) { foreach ($tags as $key => $tag) { foreach ($members as $member) { if (!empty($member->$tag)) { if (isset($data[$key]) && $data[$key] != "") { $data[$key] .= "-"; } else if (!isset($data[$key])) { $data[$key] = ""; } $data[$key] .= substr($member->$tag, 0, 3); } } if (empty($data[$key])) { $data[$key] = "no".$tag."#"; } } } else { foreach ($tags as $key => $tag) { if (!empty($members->$tag)) { $data[$key] = $members->$tag; } else { $data[$key] = "no".$tag."#"; } } } foreach ($tags as $key => $tag) { $tags[$key] = "[".$tag."]"; } $namescheme = str_replace($tags, $data, $namescheme); } else { foreach ($tags as $key => $tag) { $tags[$key] = "[".$tag."]"; } $namescheme = str_replace($tags, "", $namescheme); } } if (strstr($namescheme, '@') !== false) { // Convert $groupnumber to a character series! if ($groupnumber > GROUPTOOL_BEP) { $nexttempnumber = $groupnumber; $string = ""; $orda = ord('A'); $ordz = ord('Z'); do { $tempnumber = $nexttempnumber; $mod = ($tempnumber) % ($ordz - $orda + 1); $letter = chr($orda + $mod); $string .= $letter; $nexttempnumber = floor(($tempnumber) / ($ordz - $orda + 1)) - 1; } while ($tempnumber >= ($ordz - $orda + 1)); $namescheme = str_replace('@', strrev($string), $namescheme); } else { $letter = 'A'; for ($i = 0; $i < $groupnumber; $i++) { $letter++; } $namescheme = str_replace('@', $letter, $namescheme); } } if (strstr($namescheme, '#') !== false) { if ($digits != 0) { $format = '%0'.$digits.'d'; } else { $format = '%d'; } $namescheme = str_replace('#', sprintf($format, $groupnumber + 1), $namescheme); } return $namescheme; } /** * Adds all missin agrp-entries for this instance! * * @throws coding_exception * @throws dml_exception */ public function add_missing_agrps() { global $DB; // Get all course's group-IDs! $groupids = groups_get_all_groups($this->course->id, 0, 0, 'g.id'); $groupids = array_keys($groupids); // Get all group-IDs which have active group entries! $ok = $DB->get_fieldset_select('grouptool_agrps', "DISTINCT groupid", "grouptoolid = ?", [$this->grouptool->id]); $missing = array_diff($groupids, $ok); if (!empty($missing)) { $added = []; foreach ($missing as $cur) { $newgrp = $this->add_agrp_entry($cur); $added[] = $newgrp->id; } if (!empty($added)) { // Set them inactive! list($addedsql, $addedparams) = $DB->get_in_or_equal($added); $DB->set_field_select('grouptool_agrps', 'active', 0, "id ".$addedsql, $addedparams); } } } /** * Adds an agrp-entry for newly created group! * * @param int $groupid Group ID to add agrp entry for! * @return stdClass (new) agrp record * @throws dml_exception */ protected function add_agrp_entry($groupid) { global $DB; // Insert into agrp-table! $newagrp = new stdClass(); $newagrp->groupid = $groupid; $newagrp->grouptoolid = $this->grouptool->id; $newagrp->sort_order = 999999; if ($this->grouptool->allow_reg == true) { $newagrp->active = 1; } else { $newagrp->active = 0; } $attr = [ 'grouptoolid' => $this->grouptool->id, 'groupid' => $groupid ]; if (!$DB->record_exists('grouptool_agrps', $attr)) { $newagrp->id = $DB->insert_record('grouptool_agrps', $newagrp, true); } else { /* This is also the case if eventhandlers work properly * because group gets allready created in eventhandler */ $newagrp->id = $DB->get_field('grouptool_agrps', 'id', $attr); if ($this->grouptool->allow_reg == true) { $DB->set_field('grouptool_agrps', 'active', 1, ['id' => $newagrp->id]); } } return $newagrp; } /** * Create moodle-groups and also create non-active entries for the created groups * for this instance * * @param stdClass $data data from administration-form with all settings for group creation * @param stdClass[] $users which users to registrate in the created groups * @param int $userpergrp how many users should be registrated per group * @param int $numgrps how many groups should be created * @param bool $previewonly optional only show preview of created groups * @return array ( 0 => error, 1 => message ) * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ private function create_groups($data, $users, $userpergrp, $numgrps, $previewonly = false) { global $DB, $USER; require_capability('mod/grouptool:create_groups', $this->context); $namestouse = []; // Allocate members from the selected role to groups! $usercnt = count($users); switch ($data->allocateby) { case 'random': srand($data->seed); shuffle($users); break; case 'no': case 'lastname': uasort($users, function($a, $b) { $cmp = strcmp($a->lastname, $b->lastname); if ($cmp == 0) { return strcmp($a->firstname, $b->firstname); } if ($cmp == 0) { $cmp = strcmp($a->idnumber, $b->idnumber); } return $cmp; }); break; case 'firstname': uasort($users, function($a, $b) { $cmp = strcmp($a->firstname, $b->firstname); if ($cmp == 0) { $cmp = strcmp($a->lastname, $b->lastname); } if ($cmp == 0) { $cmp = strcmp($a->idnumber, $b->idnumber); } return $cmp; }); break; case 'idnumber': uasort($users, function($a, $b) { $cmp = strcmp($a->idnumber, $b->idnumber); if ($cmp == 0) { $cmp = strcmp($a->lastname, $b->lastname); } if ($cmp == 0) { $cmp = strcmp($a->firstname, $b->firstname); } return $cmp; }); break; default: print_error('unknoworder'); } $groups = []; // Allocate the users - all groups equal count first! for ($i = 0; $i < $numgrps; $i++) { $groups[$i] = []; $groups[$i]['members'] = []; if ($data->allocateby == 'no') { continue; // Do not allocate users! } for ($j = 0; $j < $userpergrp; $j++) { if (empty($users)) { break 2; } $user = array_shift($users); $groups[$i]['members'][$user->id] = $user; } } // Now distribute the rest! if ($data->allocateby != 'no') { for ($i = 0; $i < $numgrps; $i++) { if (empty($users)) { break 1; } $user = array_shift($users); $groups[$i]['members'][$user->id] = $user; } } // Every member is there, so we can parse the name! $digits = ceil(log10($numgrps)); for ($i = 0; $i < $numgrps; $i++) { $groups[$i]['name'] = $this->groups_parse_name(trim($data->namingscheme), $i, $groups[$i]['members'], $digits); } if ($previewonly) { $error = false; $table = new html_table(); if ($data->allocateby == 'no') { $table->head = [get_string('groupscount', 'group', $numgrps)]; $table->size = ['100%']; $table->align = ['left']; } else { $table->head = [ get_string('groupscount', 'group', $numgrps), get_string('groupmembers', 'group'), get_string('usercounttotal', 'group', $usercnt) ]; $table->size = ['20%', '70%', '10%']; $table->align = ['left', 'left', 'center']; } $table->data = []; foreach ($groups as $group) { $line = []; if (groups_get_group_by_name($this->course->id, $group['name']) || in_array($group['name'], $namestouse)) { $error = true; if (in_array($group['name'], $namestouse)) { $line[] = '<span class="late">'. get_string('nameschemenotunique', 'grouptool', $group['name']).'</span>'; } else { $line[] = '<span class="late">'. get_string('groupnameexists', 'group', $group['name']).'</span>'; } } else { $line[] = $group['name']; $namestouse[] = $group['name']; } if ($data->allocateby != 'no') { $unames = []; foreach ($group['members'] as $user) { $unames[] = fullname($user); } $line[] = implode(', ', $unames); $line[] = count($group['members']); } $table->data[] = $line; } return [0 => $error, 1 => html_writer::table($table)]; } else { $grouping = null; $createdgrouping = 0; $createdgroups = []; $failed = false; // Prepare grouping! if (!empty($data->grouping)) { if ($data->grouping < 0) { $grouping = new stdClass(); $grouping->courseid = $this->course->id; $grouping->name = trim($data->groupingname); $grouping->id = groups_create_grouping($grouping); $createdgrouping = $grouping->id; } else { $grouping = groups_get_grouping($data->grouping); } } // Trigger group_creation_started event. $groupingid = !empty($grouping) ? $grouping->id : 0; switch ($data->mode) { case GROUPTOOL_GROUPS_AMOUNT: \mod_grouptool\event\group_creation_started::create_groupamount($this->cm, $data->namingscheme, $data->numberofgroups, $groupingid)->trigger(); break; case GROUPTOOL_MEMBERS_AMOUNT: \mod_grouptool\event\group_creation_started::create_memberamount($this->cm, $data->namingscheme, $data->numberofmembers, $groupingid)->trigger(); break; } // Save the groups data! foreach ($groups as $group) { if (groups_get_group_by_name($this->course->id, $group['name'])) { $error = get_string('groupnameexists', 'group', $group['name']); $failed = true; continue; } $newgroup = new stdClass(); $newgroup->courseid = $this->course->id; $newgroup->name = $group['name']; $newgroup->enablemessaging = $data->enablegroupmessaging == 1 ? 1 : null; $groupid = groups_create_group($newgroup); $newagrp = $this->add_agrp_entry($groupid); $createdgroups[] = $groupid; foreach ($group['members'] as $user) { groups_add_member($groupid, $user->id); $usrreg = new stdClass(); $usrreg->userid = $user->id; $usrreg->agrpid = $newagrp->id; $usrreg->timestamp = time(); $usrreg->modified_by = $USER->id; $attr = [ 'userid' => $user->id, 'agrpid' => $newagrp->id ]; if (!$DB->record_exists('grouptool_registered', $attr)) { $DB->insert_record('grouptool_registered', $usrreg); } else { $DB->set_field('grouptool_registered', 'modified_by', $USER->id, $attr); } } if ($grouping) { groups_assign_grouping($grouping->id, $groupid); } } if ($failed) { foreach ($createdgroups as $groupid) { groups_delete_group($groupid); } if ($createdgrouping) { groups_delete_grouping($createdgrouping); } } else { // Trigger agrps updated via groupcreation event. $groupingid = !empty($grouping) ? $grouping->id : 0; \mod_grouptool\event\agrps_updated::create_groupcreation($this->cm, $data->namingscheme, $numgrps, $groupingid)->trigger(); } } if (empty($failed)) { $preview = get_string('groups_created', 'grouptool'); } else if (empty($preview)) { if (!empty($error)) { $preview = $error; } else { $preview = get_string('group_creation_failed', 'grouptool'); } } return [$failed, $preview]; } /** * Create moodle-groups and also create non-active entries for the created groups * for this instance also used for creation of N groups with M members! * * @param stdClass $data data from administration-form with all settings for group creation * @param bool $previewonly optional only show preview of created groups * @return array ( 0 => error, 1 => message ) * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ private function create_fromto_groups($data, $previewonly = false) { global $DB, $OUTPUT; require_capability('mod/grouptool:create_groups', $this->context); $groups = []; // Every member is there, so we can parse the name! for ($i = clean_param($data->from, PARAM_INT); $i <= clean_param($data->to, PARAM_INT); $i++) { $groups[] = $this->groups_parse_name(trim($data->namingscheme), $i - 1, null, clean_param($data->digits, PARAM_INT)); } if ($previewonly) { $error = false; $table = new html_table(); $table->head = [ get_string('groupscount', 'group', (clean_param($data->to, PARAM_INT) - clean_param($data->from, PARAM_INT) + 1)) ]; $table->size = ['100%']; $table->align = ['left']; $table->data = []; $createdgroups = []; foreach ($groups as $group) { $line = []; if (groups_get_group_by_name($this->course->id, $group) || in_array($group, $createdgroups)) { $error = true; if (in_array($group, $createdgroups)) { $line[] = '<span class="late">'. get_string('nameschemenotunique', 'grouptool', $group).'</span>'; } else { $line[] = '<span class="late">'. get_string('groupnameexists', 'group', $group).'</span>'; } } else { $line[] = $group; $createdgroups[] = $group; } $table->data[] = $line; } // Notification if activation of group size is imminent! if (empty($error) && !empty($data->numberofmembers)) { $a = new stdClass(); $a->field = get_string('number_of_members', 'grouptool'); $a->globalsize = $this->grouptool->grpsize; if ($data->numberofmembers != $this->grouptool->grpsize && !$this->grouptool->use_size) { echo $OUTPUT->notification(get_string('groupsize_gets_enabled', 'grouptool', $a), 'info'); } } return [0 => $error, 1 => html_writer::table($table)]; } else { $grouping = null; $createdgrouping = null; $createdgroups = []; $failed = false; // Prepare grouping! if (!empty($data->grouping)) { if ($data->grouping < 0) { $grouping = new stdClass(); $grouping->courseid = $this->course->id; $grouping->name = trim($data->groupingname); $grouping->id = groups_create_grouping($grouping); $createdgrouping = $grouping->id; } else { $grouping = groups_get_grouping($data->grouping); } } // Trigger group creation started event. $groupingid = !empty($grouping->id) ? $grouping->id : 0; \mod_grouptool\event\group_creation_started::create_fromto($this->cm, $data->namingscheme, $data->from, $data->to, $groupingid)->trigger(); // Save the groups data! $error = ''; foreach ($groups as $group) { if (groups_get_group_by_name($this->course->id, $group)) { $error = get_string('groupnameexists', 'group', $group); $failed = true; break; } $newgroup = new stdClass(); $newgroup->courseid = $this->course->id; $newgroup->name = $group; $newgroup->enablemessaging = $data->enablegroupmessaging == 1 ? 1 : null; $groupid = groups_create_group($newgroup); // Insert into agrp-table! $newagrp = $this->add_agrp_entry($groupid); if (!empty($data->numberofmembers) && ($data->numberofmembers != $this->grouptool->grpsize)) { $DB->set_field('grouptool_agrps', 'grpsize', $data->numberofmembers, ['id' => $newagrp->id]); } $createdgroups[] = $groupid; if ($grouping) { groups_assign_grouping($grouping->id, $groupid); } } if ($failed) { foreach ($createdgroups as $groupid) { groups_delete_group($groupid); } if ($createdgrouping) { groups_delete_grouping($createdgrouping); } return [ 0 => $failed, 1 => get_string('group_creation_failed', 'grouptool').html_writer::empty_tag('br').$error ]; } else { // Activate group size if we already used it when creating groups! if (!empty($data->numberofmembers)) { $this->grouptool->use_size = true; $DB->update_record('grouptool', $this->grouptool); } $numgrps = clean_param($data->to, PARAM_INT) - clean_param($data->from, PARAM_INT) + 1; // Trigger agrps updated via groupcreation event. $groupingid = !empty($grouping) ? $grouping->id : 0; \mod_grouptool\event\agrps_updated::create_groupcreation($this->cm, $data->namingscheme, $numgrps, $groupingid)->trigger(); return [0 => $failed, 1 => get_string('groups_created', 'grouptool')]; } } } /** * Create a moodle group for each of the users in $users * * @param stdClass[] $users array of users-objects for which to create the groups * @param string $namescheme scheme determining how to name the created groups * @param int $grouping -1 => create new grouping, * 0 => no grouping, * >0 => assign groups to grouping with that id * @param string $groupingname optional name for created grouping * @param bool $previewonly optional only show preview of created groups * @param int $enablegroupmessaging optional enable messaging within group (default: no) * @return array ( 0 => error, 1 => message ) * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ private function create_one_person_groups($users, $namescheme = "[idnumber]", $grouping = 0, $groupingname = null, $previewonly = false, $enablegroupmessaging=0) { global $DB, $USER; require_capability('mod/grouptool:create_groups', $this->context); // Allocate members from the selected role to groups! $usercnt = count($users); // Prepare group data! $groups = []; $i = 0; $digits = ceil(log10(count($users))); foreach ($users as $user) { $groups[$i] = []; $groups[$i]['name'] = $this->groups_parse_name(trim($namescheme), $i, $user, $digits); $groups[$i]['member'] = $user; $i++; } if ($previewonly) { $error = false; $table = new html_table(); $table->head = [ get_string('groupscount', 'group', $usercnt), get_string('groupmembers', 'group') ]; $table->size = ['30%', '70%']; $table->align = ['left', 'left']; $table->data = []; $groupnames = []; foreach ($groups as $group) { $line = []; if (groups_get_group_by_name($this->course->id, $group['name']) || in_array($group['name'], $groupnames)) { $error = true; if (in_array($group['name'], $groupnames)) { $line[] = '<span class="late">'. get_string('nameschemenotunique', 'grouptool', $group['name']).'</span>'; } else { $line[] = '<span class="late">'. get_string('groupnameexists', 'group', $group['name']).'</span>'; } } else { $groupnames[] = $group['name']; $line[] = $group['name']; } $line[] = fullname($group['member']); $table->data[] = $line; } return [0 => $error, 1 => html_writer::table($table)]; } else { $createdgrouping = null; $createdgroups = []; $failed = false; // Prepare grouping! if (!empty($grouping)) { if ($grouping < 0) { $grouping = new stdClass(); $grouping->courseid = $this->course->id; $grouping->name = trim($groupingname); $grouping->id = groups_create_grouping($grouping); $createdgrouping = $grouping->id; } else { $grouping = groups_get_grouping($grouping); } } // Trigger group_creation_started event. $groupingid = !empty($grouping) ? $grouping->id : 0; \mod_grouptool\event\group_creation_started::create_person($this->cm, $namescheme, $groupingid)->trigger(); // Save the groups data! $error = ''; foreach ($groups as $group) { if (groups_get_group_by_name($this->course->id, $group['name'])) { $error = get_string('groupnameexists', 'group', $group['name']); $failed = true; break; } $newgroup = new stdClass(); $newgroup->courseid = $this->course->id; $newgroup->name = $group['name']; $newgroup->enablemessaging = $enablegroupmessaging == 1 ? 1 : null; $groupid = groups_create_group($newgroup); // Insert into agrp-table! $newagrp = new stdClass(); $newagrp->groupid = $groupid; $newagrp->grouptoolid = $this->grouptool->id; $newagrp->sort_order = 999999; if ($this->grouptool->allow_reg == true) { $newagrp->active = 1; } else { $newagrp->active = 0; } if (!$DB->record_exists('grouptool_agrps', [ 'grouptoolid' => $this->grouptool->id, 'groupid' => $groupid ])) { $newagrp->id = $DB->insert_record('grouptool_agrps', $newagrp, true); } else { /* This is also the case if eventhandlers work properly * because group gets allready created in eventhandler */ $newagrp->id = $DB->get_field('grouptool_agrps', 'id', [ 'grouptoolid' => $this->grouptool->id, 'groupid' => $groupid ]); if ($this->grouptool->allow_reg == true) { $DB->set_field('grouptool_agrps', 'active', 1, ['id' => $newagrp->id]); } } $createdgroups[] = $groupid; groups_add_member($groupid, $group['member']->id); $usrreg = new stdClass(); $usrreg->userid = $group['member']->id; $usrreg->agrpid = $newagrp->id; $usrreg->timestamp = time(); $usrreg->modified_by = $USER->id; $attr = [ 'userid' => $group['member']->id, 'agrpid' => $newagrp->id ]; if (!$DB->record_exists('grouptool_registered', $attr)) { $DB->insert_record('grouptool_registered', $usrreg); } else { $DB->set_field('grouptool_registered', 'modified_by', $USER->id, $attr); } if ($grouping) { groups_assign_grouping($grouping->id, $groupid); } } if ($failed) { foreach ($createdgroups as $groupid) { groups_delete_group($groupid); } if ($createdgrouping) { groups_delete_grouping($createdgrouping); } return [ 0 => $failed, 1 => get_string('group_creation_failed', 'grouptool').html_writer::empty_tag('br').$error ]; } else { // Trigger agrps updated via groupcreation event. $groupingid = !empty($grouping) ? $grouping->id : 0; \mod_grouptool\event\agrps_updated::create_groupcreation($this->cm, $namescheme, count($groups), $groupingid)->trigger(); return [0 => $failed, 1 => get_string('groups_created', 'grouptool')]; } } } /** * Create a grouping for each selected groupmoodle-groups * * Uses $SESSION->grouptool->view_administration->use_all to determin if groupings for all * or just selected groups should be created and also uses * $SESSION->grouptool->view_administration->grouplist[$group->id]['active'] * to determin which groups have been selected * * @param int $courseid optional id of course to create for * @param bool $previewonly optional only show preview of created groups * @return array ( 0 => error, 1 => message ) * @throws coding_exception * @throws moodle_exception * @throws required_capability_exception */ private function create_group_groupings($courseid = null, $previewonly = false) { global $SESSION, $OUTPUT; require_capability('mod/grouptool:create_groupings', $this->context); // Create groupings! $created = []; $error = false; $return = ""; $table = new html_table(); $table->attributes['class'] = 'centeredblock'; $table->head = [ new html_table_cell(get_string('grouping', 'group')), new html_table_cell(get_string('info').'/'. get_string('groups')) ]; // Get all course-groups! if ($courseid == null) { if (isset($this->course->id)) { $courseid = $this->course->id; } else { print_error('coursemisconf'); } } $groups = groups_get_all_groups($courseid); $ids = []; foreach ($groups as $group) { $row = [new html_table_cell($group->name)]; $active = $SESSION->grouptool->view_administration->grouplist[$group->id]['active']; if (empty($SESSION->grouptool->view_administration->use_all) && !$active) { continue; } $groupid = $group->id; if (groups_get_grouping_by_name($courseid, $group->name)) { // Creation of grouping failed! if ($previewonly) { $text = get_string('grouping_exists_error_prev', 'grouptool'); } else { $text = get_string('grouping_exists_error', 'grouptool'); } $cell = new html_table_cell($OUTPUT->notification($text, \core\output\notification::NOTIFY_ERROR)); $row[] = $cell; $error = true; } else { $ids[] = $group->id; $groupingid = groups_create_grouping($group); if ($groupingid) { if (!groups_assign_grouping($groupingid, $groupid)) { if ($previewonly) { $text = get_string('group_assign_error_prev', 'grouptool'); } else { $text = get_string('group_assign_error', 'grouptool'); } $cell = new html_table_cell($OUTPUT->notification($text, \core\output\notification::NOTIFY_ERROR)); $row[] = $cell; $error = true; } else { if ($previewonly) { $content = $group->name; } else { $content = $OUTPUT->notification(get_string('grouping_creation_success', 'grouptool', $group->name), \core\output\notification::NOTIFY_SUCCESS); } $cell = new html_table_cell($content); $row[] = $cell; $created[] = $groupingid; } } else { if ($previewonly) { $text = get_string('grouping_creation_error_prev', 'grouptool'); } else { $text = get_string('grouping_creation_error', 'grouptool'); } $cell = new html_table_cell($OUTPUT->notification($text, \core\output\notification::NOTIFY_ERROR)); $row[] = $cell; $error = true; } } $table->data[] = new html_table_row($row); $return = html_writer::table($table); } if ($previewonly || ($error && !$previewonly)) { // Undo everything! foreach ($created as $groupingid) { $groupingsgroups = groups_get_all_groups($courseid, 0, $groupingid); foreach ($groupingsgroups as $group) { groups_unassign_grouping($groupingid, $group->id); } groups_delete_grouping($groupingid); } } else if (!$previewonly) { // Trigger the event! \mod_grouptool\event\groupings_created::create_from_object($this->cm, $ids)->trigger(); } return [0 => $error, 1 => $return]; } /** * Create a grouping for all selected moodle-groups * * Uses $SESSION->grouptool->view_administration->use_all to determin if groupings for all * or just selected groups should be created and also uses * $SESSION->grouptool->view_administration->grouplist[$group->id]['active'] * to determin which groups have been selected * * @param int $target -1 for new grouping or groupingid * @param string $name name for new grouping if $target = -1 * @param bool $previewonly optional only show preview of created groups * @return array ( 0 => error, 1 => message ) * @throws coding_exception * @throws moodle_exception * @throws required_capability_exception */ private function update_grouping($target, $name = null, $previewonly = false) { global $SESSION, $OUTPUT; $error = false; $return = ""; require_capability('mod/grouptool:create_groupings', $this->context); if (isset($this->course->id)) { $courseid = $this->course->id; } else { $courseid = 0; print_error('coursemisconf'); } if ($target == -1) { if (groups_get_grouping_by_name($courseid, $name)) { // Creation of grouping failed! if ($previewonly) { $text = get_string('grouping_exists_error_prev', 'grouptool'); } else { $text = get_string('grouping_exists_error', 'grouptool'); } return [0 => true, 1 => $OUTPUT->notification($text, \core\output\notification::NOTIFY_ERROR)]; } else { if (empty($previewonly)) { // Create grouping and set as target. $grouping = new stdClass(); $grouping->name = $name; $grouping->courseid = $courseid; $target = groups_create_grouping($grouping); $return = $OUTPUT->notification(get_string('grouping_creation_only_success', 'grouptool'), \core\output\notification::NOTIFY_SUCCESS); } else { $return = $OUTPUT->notification(get_string('grouping_creation_only_success_prev', 'grouptool'), \core\output\notification::NOTIFY_INFO); } } } $ids = []; if (!empty($target)) { $groups = groups_get_all_groups($courseid); $success = []; $failure = []; foreach ($groups as $group) { $active = $SESSION->grouptool->view_administration->grouplist[$group->id]['active']; if (empty($SESSION->grouptool->view_administration->use_all) && !$active) { continue; } $groupid = $group->id; if (!groups_assign_grouping($target, $groupid)) { $failure[] = $group->name; $error = true; } else { $success[] = $group->name; } } if ($previewonly) { if (!empty($success)) { $return .= $OUTPUT->notification(get_string('grouping_assign_success_prev', 'grouptool'). html_writer::empty_tag('br').implode(', ', $success), \core\output\notification::NOTIFY_INFO); } if ($error) { $return .= $OUTPUT->notification(get_string('grouping_assign_error_prev', 'grouptool'). html_writer::empty_tag('br').implode(', ', $failure), \core\output\notification::NOTIFY_ERROR); } } else { $return .= $OUTPUT->notification(get_string('grouping_assign_success', 'grouptool') .html_writer::empty_tag('br') .implode(', ', $success), \core\output\notification::NOTIFY_SUCCESS); if ($error) { $return .= $OUTPUT->notification(get_string('grouping_assign_error', 'grouptool') .html_writer::empty_tag('br') .implode(', ', $failure), \core\output\notification::NOTIFY_ERROR); } } } if (!$previewonly) { // Trigger the event! \mod_grouptool\event\groupings_created::create_from_object($this->cm, $ids)->trigger(); } return [0 => $error, 1 => $return]; } /** * Outputs the content of the administration tab and manages actions taken in this tab * * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function view_administration() { global $SESSION, $OUTPUT, $PAGE, $DB, $USER, $CFG; $output = $PAGE->get_renderer('mod_grouptool'); // Repair possibly missing agrps... $this->add_missing_agrps(); $id = $this->cm->id; $context = context_course::instance($this->course->id); // Get applicable roles! $rolenames = []; if ($roles = get_profile_roles($context)) { foreach ($roles as $role) { $rolenames[$role->id] = strip_tags(role_get_name($role, $context)); } } $filter = optional_param('filter', null, PARAM_INT); if ($filter !== null) { set_user_preference('mod_grouptool_group_filter', $filter, $USER->id); } else { $filter = get_user_preferences('mod_grouptool_group_filter', self::FILTER_ACTIVE, $USER->id); } $inactivetabs = []; $filtertabs['active'] = new tabobject(self::FILTER_ACTIVE, $CFG->wwwroot.'/mod/grouptool/view.php?id='.$id. '&tab=group_admin&filter='.self::FILTER_ACTIVE, get_string('active', 'grouptool'), '', false); $filtertabs['inactive'] = new tabobject(self::FILTER_INACTIVE, $CFG->wwwroot.'/mod/grouptool/view.php?id='.$id. '&tab=group_admin&filter='.self::FILTER_INACTIVE, get_string('inactive'), '', false); $filtertabs['all'] = new tabobject(self::FILTER_ALL, $CFG->wwwroot.'/mod/grouptool/view.php?id='.$id. '&tab=group_admin&filter='.self::FILTER_ALL, get_string('all'), '', false); $bulkaction = optional_param('bulkaction', null, PARAM_ALPHA); $selected = optional_param_array('selected', [], PARAM_INT); $dialog = false; if ($bulkaction && $selected && optional_param('start_bulkaction', 0, PARAM_BOOL)) { switch ($bulkaction) { case 'activate': // ...also via ajax bulk action? // Activate now! $groups = optional_param_array('selected', null, PARAM_INT); if (!empty($groups)) { list($grpsql, $grpparams) = $DB->get_in_or_equal($groups); $DB->set_field_select("grouptool_agrps", "active", 1, " grouptoolid = ? AND groupid ".$grpsql, array_merge([$this->cm->instance], $grpparams)); } echo $OUTPUT->notification(get_string('activated_groups', 'grouptool'), \core\output\notification::NOTIFY_SUCCESS); break; case 'deactivate': // ...also via ajax bulk action? // Deactivate now! $groups = optional_param_array('selected', null, PARAM_INT); if (!empty($groups)) { list($grpsql, $grpparams) = $DB->get_in_or_equal($groups); $DB->set_field_select("grouptool_agrps", "active", 0, " grouptoolid = ? AND groupid ".$grpsql, array_merge([$this->cm->instance], $grpparams)); } echo $OUTPUT->notification(get_string('deactivated_groups', 'grouptool'), \core\output\notification::NOTIFY_SUCCESS); break; case 'delete': // ...also via ajax bulk action? // Show confirmation dialogue! if (optional_param('confirm', 0, PARAM_BOOL)) { $groups = optional_param_array('selected', null, PARAM_INT); $groups = $DB->get_records_list('groups', 'id', $groups); foreach ($groups as $group) { groups_delete_group($group); } echo $OUTPUT->notification(get_string('successfully_deleted_groups', 'grouptool'), \core\output\notification::NOTIFY_SUCCESS); } else { $cancel = new moodle_url($PAGE->url, ['tab' => 'group_admin']); $params = ['confirm' => 1, 'bulkaction' => 'delete', 'start_bulkaction' => 1]; $text = get_string('confirm_delete', 'grouptool').html_writer::start_tag('ul'); $groups = $DB->get_records_list('groups', 'id', $selected); foreach ($selected as $select) { $params['selected['.$select.']'] = $select; $text .= html_writer::tag('li', $groups[$select]->name); } $text .= html_writer::end_tag('ul'); $continue = new moodle_url($cancel, $params); echo $this->confirm($text, $continue, $cancel); echo $OUTPUT->footer(); $dialog = true; } break; case 'grouping': // Show grouping creation form! $selected = optional_param_array('selected', [], PARAM_INT); $mform = new \mod_grouptool\groupings_creation_form(null, [ 'id' => $id, 'selected' => $selected ]); $groups = $DB->get_records_list('groups', 'id', $selected); if ($mform->is_cancelled()) { $bulkaction = null; $selected = []; } else if ($fromform = $mform->get_data()) { // Some groupings should be created... if ($fromform->target == -2) { // One new grouping per group! foreach ($groups as $group) { $grouping = new stdClass(); if (!$grouping->id = groups_get_grouping_by_name($this->course->id, $group->name)) { $grouping = new stdClass(); $grouping->courseid = $this->course->id; $grouping->name = $group->name; $grouping->id = groups_create_grouping($grouping); } // Insert group! groups_assign_grouping($grouping->id, $group->id); } } else if ($fromform->target == -1) { // One new grouping! // Create grouping if it doesn't exist... $grouping = new stdClass(); if (!$grouping->id = groups_get_grouping_by_name($this->course->id, $fromform->name)) { $grouping = new stdClass(); $grouping->courseid = $this->course->id; $grouping->name = trim($fromform->name); $grouping->id = groups_create_grouping($grouping); } // Insert groups! foreach ($groups as $group) { groups_assign_grouping($grouping->id, $group->id); } } else if ($fromform->target > 0) { // Existing Grouping! $grouping = groups_get_grouping($fromform->target); if ($grouping) { foreach ($groups as $group) { groups_assign_grouping($grouping->id, $group->id); } } } // ...redirect to show sortlist again! $url = new moodle_url('/mod/grouptool/view.php', [ 'id' => $this->cm->id, 'tab' => 'group_admin', 'filter' => $filter ]); echo $OUTPUT->notification(get_string('groupings_created_and_groups_added', 'grouptool'), \core\output\notification::NOTIFY_SUCCESS); } else { $mform->display(); $dialog = true; } break; } } if (!$dialog) { echo html_writer::tag('div', $OUTPUT->tabtree($filtertabs, $filter, $inactivetabs), ['id' => 'filtertabs']); } // Check if everything has been confirmed, so we can finally start working! if (optional_param('confirm', 0, PARAM_BOOL)) { if (isset($SESSION->grouptool->view_administration->createGroupings)) { require_capability('mod/grouptool:create_groupings', $this->context); $target = required_param('target', PARAM_INT); switch ($target) { // ...grpg_target | grpg_groupingname | use_all (0 sel | 1 all). case 0: // Invalid - no action! TODO Add message! $preview = ''; break; case -2: // One grouping per group! list(, $preview) = $this->create_group_groupings(); break; case -1: // One new grouping for all! list(, $preview) = $this->update_grouping($target, required_param('name', PARAM_ALPHANUMEXT)); break; default: list(, $preview) = $this->update_grouping($target); break; } $preview = html_writer::tag('div', $preview, ['class' => 'centered']); echo $OUTPUT->box($preview, 'generalbox'); } unset($SESSION->grouptool->view_administration); } if ($rename = optional_param('rename', 0, PARAM_INT)) { // Show Rename Form! $gform = new \mod_grouptool\group_rename_form(null, [ 'id' => $this->cm->id, 'instance' => $this->cm->instance, 'rename' => $rename ]); if (!$gform->is_cancelled() && $fromform = $gform->get_data()) { $group = new stdClass(); $group->id = $fromform->rename; $group->name = $fromform->name; $group->courseid = $fromform->courseid; groups_update_group($group); } else if (!$gform->is_cancelled()) { $data = new stdClass(); $data->name = $DB->get_field('groups', 'name', ['id' => $rename]); $gform->set_data($data); $gform->display(); echo $OUTPUT->footer(); die; } } if ($resize = optional_param('resize', 0, PARAM_INT)) { // Show Resize Form! $gform = new \mod_grouptool\group_resize_form(null, [ 'id' => $this->cm->id, 'instance' => $this->cm->instance, 'resize' => $resize ]); if (!$gform->is_cancelled() && $fromform = $gform->get_data()) { if (empty($fromform->size)) { $DB->set_field('grouptool_agrps', 'grpsize', null, [ 'groupid' => $fromform->resize, 'grouptoolid' => $this->cm->instance ]); } else { $group = new stdClass(); $group->id = $DB->get_field('grouptool_agrps', 'id', [ 'groupid' => $fromform->resize, 'grouptoolid' => $this->cm->instance ]); $group->grpsize = $fromform->size; $DB->update_record('grouptool_agrps', $group); } } else if (!$gform->is_cancelled()) { $data = new stdClass(); $data->size = $DB->get_field('grouptool_agrps', 'grpsize', [ 'groupid' => $resize, 'grouptoolid' => $this->cm->instance ]); $gform->set_data($data); $gform->display(); echo $OUTPUT->footer(); die; } } if ($delete = optional_param('delete', 0, PARAM_INT)) { if (!optional_param('confirm', 0, PARAM_BOOL)) { // Show Confirm! $cancel = new moodle_url($PAGE->url); $continue = new moodle_url($cancel, [ 'confirm' => 1, 'delete' => $delete ]); $cancel = new single_button($cancel, get_string('no'), 'post'); $continue = new single_button($continue, get_string('yes'), 'post'); $confirmtext = get_string('confirm_delete', 'grouptool'); echo $this->confirm($confirmtext, $continue, $cancel); echo $OUTPUT->footer(); die; } else { // Delete it! groups_delete_group($delete); } } if ($toggle = optional_param('toggle', 0, PARAM_INT)) { if (!empty($toggle)) { $conditions = ['grouptoolid' => $this->cm->instance, 'groupid' => $toggle]; if (!$DB->record_exists('grouptool_agrps', $conditions)) { echo $OUTPUT->box($OUTPUT->notification(get_string('group_not_found', 'grouptool'), \core\output\notification::NOTIFY_ERROR), 'generalbox'); } else { $record = $DB->get_record('grouptool_agrps', $conditions); if (!empty($record->active)) { $DB->set_field('grouptool_agrps', 'active', 0, $conditions); } else { $DB->set_field('grouptool_agrps', 'active', 1, $conditions); } } } } if (!$dialog || !optional_param('start_bulkaction', 0, PARAM_BOOL)) { // Show form! $formaction = new moodle_url('/mod/grouptool/view.php', [ 'id' => $this->cm->id, 'tab' => 'group_admin', 'filter' => $filter ]); $mform = new MoodleQuickForm('bulk', 'post', $formaction, ''); $mform->addElement('hidden', 'sesskey'); $mform->setDefault('sesskey', sesskey()); $sortlist = new \mod_grouptool\output\sortlist($this->course->id, $this->cm, $filter); $sortlistcontroller = new \mod_grouptool\output\sortlist_controller($sortlist); $mform->addElement('html', $output->render($sortlistcontroller)); $mform->addElement('html', $output->render($sortlist)); $actions = [ '' => get_string('choose', 'grouptool'), 'activate' => get_string('setactive', 'grouptool'), 'deactivate' => get_string('setinactive', 'grouptool') ]; if (!($this->grouptool->ifgroupdeleted === GROUPTOOL_RECREATE_GROUP) && !$DB->record_exists('grouptool', ['course' => $this->cm->course, 'ifgroupdeleted' => GROUPTOOL_RECREATE_GROUP])) { $actions['delete'] = get_string('delete'); } $actions['grouping'] = get_string('createinsertgrouping', 'grouptool'); $grp = []; $grp[] =& $mform->createElement('static', 'with_selection', '', get_string('with_selection', 'grouptool')); $grp[] =& $mform->createElement('select', 'bulkaction', '', $actions); $grp[] =& $mform->createElement('submit', 'start_bulkaction', get_string('start', 'grouptool')); $mform->addGroup($grp, 'actiongrp', '', ' ', false); $mform->disable_form_change_checker(); $mform->display(); switch ($filter) { case self::FILTER_ACTIVE: $curfilter = 'active'; break; case self::FILTER_INACTIVE: $curfilter = 'inactive'; break; default: case self::FILTER_ALL: $curfilter = 'all'; break; } $params = ['cmid' => $this->cm->id, 'filter' => $curfilter, 'filterall' => GROUPTOOL_FILTER_ALL, 'globalsize' => $this->grouptool->grpsize, 'usesize' => (bool)$this->grouptool->use_size]; $PAGE->requires->js_call_amd('mod_grouptool/administration', 'initializer', $params); } } /** * Outputs the content of the creation tab and manages actions taken in this tab * * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function view_creation() { global $SESSION, $OUTPUT; $id = $this->cm->id; $context = context_course::instance($this->course->id); // Get applicable roles! $rolenames = []; if ($roles = get_profile_roles($context)) { foreach ($roles as $role) { $rolenames[$role->id] = strip_tags(role_get_name($role, $context)); } } // Check if everything has been confirmed, so we can finally start working! if (optional_param('confirm', 0, PARAM_BOOL)) { if (isset($SESSION->grouptool->view_administration->createGroups)) { require_capability('mod/grouptool:create_groups', $this->context); // Create groups! $data = $SESSION->grouptool->view_administration; $error = false; $preview = ''; switch ($data->mode) { case GROUPTOOL_GROUPS_AMOUNT: // Allocate members from the selected role to groups! switch ($data->allocateby) { default: print_error('unknoworder'); case 'no': case 'random': case 'lastname': $orderby = 'lastname, firstname'; break; case 'firstname': $orderby = 'firstname, lastname'; break; case 'idnumber': $orderby = 'idnumber'; break; } $users = groups_get_potential_members($this->course->id, $data->roleid, $data->cohortid, $orderby); $usercnt = count($users); $numgrps = $data->numberofgroups; $userpergrp = floor($usercnt / $numgrps); list($error, $preview) = $this->create_groups($data, $users, $userpergrp, $numgrps); break; case GROUPTOOL_MEMBERS_AMOUNT: // Allocate members from the selected role to groups! switch ($data->allocateby) { default: print_error('unknoworder'); case 'no': case 'random': case 'lastname': $orderby = 'lastname, firstname'; break; case 'firstname': $orderby = 'firstname, lastname'; break; case 'idnumber': $orderby = 'idnumber'; break; } $users = groups_get_potential_members($this->course->id, $data->roleid, $data->cohortid, $orderby); $usercnt = count($users); $numgrps = ceil($usercnt / $data->numberofmembers); $userpergrp = $data->numberofmembers; if (!empty($data->nosmallgroups) and $usercnt % $data->numberofmembers != 0) { /* * If there would be one group with a small number of member * reduce the number of groups */ $missing = $userpergrp * $numgrps - $usercnt; if ($missing > $userpergrp * (1 - GROUPTOOL_AUTOGROUP_MIN_RATIO)) { // Spread the users from the last small group! $numgrps--; $userpergrp = floor($usercnt / $numgrps); } } list($error, $preview) = $this->create_groups($data, $users, $userpergrp, $numgrps); break; case GROUPTOOL_1_PERSON_GROUPS: $users = groups_get_potential_members($this->course->id, $data->roleid, $data->cohortid); if (!isset($data->groupingname)) { $data->groupingname = null; } list($error, $prev) = $this->create_one_person_groups($users, $data->namingscheme, $data->grouping, $data->groupingname, false, $data->enablegroupmessaging); $preview = $prev; break; case GROUPTOOL_N_M_GROUPS: /* Shortcut here: create_fromto_groups does exactly what we want, * with from = 1 and to = number of groups to create! */ $data->from = 1; $data->to = $data->numberofgroups; $data->digits = 1; case GROUPTOOL_FROMTO_GROUPS: if (!isset($data->groupingname)) { $data->groupingname = null; } list($error, $preview) = $this->create_fromto_groups($data); break; } if (!$error && has_capability('mod/grouptool:administrate_groups', $this->context)) { $linktext = '<i class="fa fa-long-arrow-right" aria-hidden="true"></i>' . get_string('group_administration', 'grouptool'); $urlparams = [ 'id' => $this->cm->id, 'tab' => 'group_admin' ]; $preview .= html_writer::link(new moodle_url('/mod/grouptool/view.php', $urlparams), $linktext, [ 'class' => 'ml-1' ]); } $preview = $OUTPUT->notification($preview, $error ? \core\output\notification::NOTIFY_ERROR : \core\output\notification::NOTIFY_SUCCESS); echo $OUTPUT->box(html_writer::tag('div', $preview, ['class' => 'centered']), 'generalbox'); } unset($SESSION->grouptool->view_administration); } // Create the form-object! $showgrpsize = $this->grouptool->use_size; $mform = new \mod_grouptool\group_creation_form(null, [ 'id' => $id, 'roles' => $rolenames, 'show_grpsize' => $showgrpsize ]); unset($showgrpsize); if ($fromform = $mform->get_data()) { require_capability('mod/grouptool:create_groups', $this->context); // Save submitted data in session and show confirmation dialog! if (!isset($SESSION->grouptool)) { $SESSION->grouptool = new stdClass(); } if (!isset($SESSION->grouptool->view_administration)) { $SESSION->grouptool->view_administration = new stdClass(); } $SESSION->grouptool->view_administration = $fromform; $data = $SESSION->grouptool->view_administration; $preview = ""; $error = false; switch ($data->mode) { case GROUPTOOL_GROUPS_AMOUNT: // Allocate members from the selected role to groups! switch ($data->allocateby) { default: print_error('unknoworder'); case 'no': case 'random': case 'lastname': $orderby = 'lastname, firstname'; break; case 'firstname': $orderby = 'firstname, lastname'; break; case 'idnumber': $orderby = 'idnumber'; break; } $users = groups_get_potential_members($this->course->id, $data->roleid, $data->cohortid, $orderby); $usercnt = count($users); $numgrps = clean_param($data->numberofgroups, PARAM_INT); $userpergrp = floor($usercnt / $numgrps); list($error, $preview) = $this->create_groups($data, $users, $userpergrp, $numgrps, true); break; case GROUPTOOL_MEMBERS_AMOUNT: // Allocate members from the selected role to groups! switch ($data->allocateby) { default: print_error('unknoworder'); case 'no': case 'random': case 'lastname': $orderby = 'lastname, firstname'; break; case 'firstname': $orderby = 'firstname, lastname'; break; case 'idnumber': $orderby = 'idnumber'; break; } $users = groups_get_potential_members($this->course->id, $data->roleid, $data->cohortid, $orderby); $usercnt = count($users); $numgrps = ceil($usercnt / $data->numberofmembers); $userpergrp = clean_param($data->numberofmembers, PARAM_INT); if (!empty($data->nosmallgroups) and $usercnt % clean_param($data->numberofmembers, PARAM_INT) != 0) { /* * If there would be one group with a small number of member * reduce the number of groups */ $missing = $userpergrp * $numgrps - $usercnt; if ($missing > $userpergrp * (1 - GROUPTOOL_AUTOGROUP_MIN_RATIO)) { // Spread the users from the last small group! $numgrps--; $userpergrp = floor($usercnt / $numgrps); } } list($error, $preview) = $this->create_groups($data, $users, $userpergrp, $numgrps, true); break; case GROUPTOOL_1_PERSON_GROUPS: $users = groups_get_potential_members($this->course->id, $data->roleid, $data->cohortid); if (!isset($data->groupingname)) { $data->groupingname = null; } list($error, $prev) = $this->create_one_person_groups($users, $data->namingscheme, $data->grouping, $data->groupingname, true, $data->enablegroupmessaging); $preview = $prev; break; case GROUPTOOL_N_M_GROUPS: /* Shortcut here: create_fromto_groups does exactly what we want, * with from = 1 and to = number of groups to create! */ $data->from = 1; $data->to = $data->numberofgroups; $data->digits = 1; case GROUPTOOL_FROMTO_GROUPS: if (!isset($data->groupingname)) { $data->groupingname = null; } list($error, $preview) = $this->create_fromto_groups($data, true); break; } $preview = html_writer::tag('div', $preview, ['class' => 'centered']); $tab = required_param('tab', PARAM_ALPHANUMEXT); if ($error) { $text = get_string('create_groups_confirm_problem', 'grouptool'); $url = new moodle_url("view.php?id=$id&tab=".$tab); $back = new single_button($url, get_string('back'), 'post'); $confirmboxcontent = $this->confirm($text, $back); } else { $continue = "view.php?id=$id&tab=".$tab."&confirm=true"; $cancel = "view.php?id=$id&tab=".$tab; $text = get_string('create_groups_confirm', 'grouptool'); $confirmboxcontent = $this->confirm($text, $continue, $cancel); } echo $OUTPUT->heading(get_string('preview'), 2, 'centered'). $OUTPUT->box($preview, 'generalbox'). $confirmboxcontent; } else { $mform->display(); } } /** * returns table used in group-grading form * * TODO use templates and load via AJAX (AMD core/fragment) * * @param int $activity ID of activity to get/set grades from/for * @param bool $mygroupsonly limit source-grades to those given by current user * @param bool $incompleteonly show only groups which have not-graded members * @param int $filter GROUPTOOL_FILTER_ALL => all groups * GROUPTOOL_FILTER_NONCONFLICTING => groups with exactly 1 graded member * >0 => id of single group * @param int[] $selected array with ids of groups/users to copy grades to as keys (depends on filter) * @param int[] $missingsource optional array with ids of entries for whom no source has been selected * (just to display a clue to select a source) * @return string HTML Fragment containing checkbox-controller and dependencies * @throws coding_exception * @throws moodle_exception * @throws required_capability_exception */ private function get_grading_table($activity, $mygroupsonly, $incompleteonly, $filter, $selected, $missingsource = []) { global $OUTPUT, $USER, $PAGE; // If he want's to grade all he needs the corresponding capability! if (!$mygroupsonly) { require_capability('mod/grouptool:grade', $this->context); } else if (!has_capability('mod/grouptool:grade', $this->context)) { /* * if he want's to grad his own he needs either capability to grade all * or to grade his own at least */ require_capability('mod/grouptool:grade_own_submission', $this->context); } $grouping = optional_param('grouping', null, PARAM_INT); $table = new html_table(); if ($activity == 0) { return $OUTPUT->box($OUTPUT->notification(get_string('chooseactivity', 'grouptool'), \core\output\notification::NOTIFY_ERROR), 'generalbox centered'); } // General table settings! $table->attributes['class'] .= ' table table-hover grading_gradingtable'; $tablepostfix = ""; $tablecolumns = []; $tableheaders = []; // Determine what mode we have to interpret the selected items the right way! if ($filter == GROUPTOOL_FILTER_ALL || $filter == GROUPTOOL_FILTER_NONCONFLICTING) { // Multiple groups? $tablecolumns = [ 'select', 'name', 'gradeinfo' ]; $button = html_writer::tag('button', get_string('copy', 'grouptool'), [ 'name' => 'copygrades', 'type' => 'submit', 'value' => 'true', 'class' => 'btn btn-primary' ]); $buttontext = get_string('copy_refgrades_feedback', 'grouptool'); $tableheaders = [ '', get_string('name'), get_string('reference_grade_feedback', 'grouptool') ]; $groups = groups_get_all_groups($this->course->id, 0, $grouping); $cmtouse = get_coursemodule_from_id('', $activity, $this->course->id); foreach ($groups as $group) { $error = ""; $groupmembers = groups_get_members($group->id); // Get grading info for all group members! $gradinginfo = grade_get_grades($this->course->id, 'mod', $cmtouse->modname, $cmtouse->instance, array_keys($groupmembers)); $gradeinfo = []; if (in_array($group->id, $missingsource)) { $error = ' error'; $gradeinfo[] = html_writer::tag('div', get_string('missing_source_selection', 'grouptool')); } $userwithgrades = []; foreach ($groupmembers as $key => $groupmember) { if (!empty($gradinginfo->items[0]->grades[$groupmember->id]->dategraded) && (!$mygroupsonly || $gradinginfo->items[0]->grades[$groupmember->id]->usermodified == $USER->id)) { $userwithgrades[] = $key; } } if ((count($userwithgrades) != 1) && ($filter == GROUPTOOL_FILTER_NONCONFLICTING)) { /* * skip groups with more than 1 grade and groups without grade * if only nonconflicting should be reviewed */ continue; } if ((count($userwithgrades) == count($groupmembers)) && ($incompleteonly == 1)) { // Skip groups fully graded if it's wished! continue; } foreach ($userwithgrades as $key) { $finalgrade = $gradinginfo->items[0]->grades[$key]; if (!empty($finalgrade->dategraded)) { $grademax = $gradinginfo->items[0]->grademax; $finalgrade->formatted_grade = round($finalgrade->grade, 2) .' / ' . round($grademax, 2); $radioattr = [ 'name' => 'source['.$group->id.']', 'value' => $groupmembers[$key]->id, 'type' => 'radio', 'class' => 'form-check-input' ]; if (count($userwithgrades) == 1) { $radioattr['disabled'] = 'disabled'; $radioattr['checked'] = 'checked'; $gradeinfocont = html_writer::empty_tag('input', $radioattr); unset($radioattr['disabled']); $radioattr['type'] = 'hidden'; $gradeinfocont .= html_writer::empty_tag('input', $radioattr); } else if (count($userwithgrades) > 1) { $gradeinfocont = html_writer::empty_tag('input', $radioattr); } else { $gradeinfocont = ''; } $gradeinfocont .= ' '.fullname($groupmembers[$key])." (".$finalgrade->formatted_grade; if (strip_tags($finalgrade->str_feedback) != "") { $gradeinfocont .= " ".shorten_text(strip_tags($finalgrade->str_feedback), 15); } $gradeinfocont .= ")"; $label = html_writer::tag('label', $gradeinfocont, [ 'class' => 'form-check-label gradinginfo'. $groupmembers[$key]->id ]); $gradeinfo[] = html_writer::tag('div', $label, ['class' => 'form-check']); } } $selectattr = [ 'type' => 'checkbox', 'name' => 'selected[]', 'value' => $group->id, 'class' => 'form-check-input' ]; $checkboxcontroller = optional_param('select', '', PARAM_ALPHA); if ((count($groupmembers) <= 1) || count($userwithgrades) == 0) { $selectattr['disabled'] = 'disabled'; unset($selectattr['checked']); } else if ($checkboxcontroller == 'all') { $selectattr['checked'] = "checked"; } else if ($checkboxcontroller == 'none') { unset($selectattr['checked']); } else if (isset($selected[$group->id]) && $selected[$group->id] == 1) { $selectattr['checked'] = "checked"; } $checkbox = html_writer::tag('label', html_writer::empty_tag('input', $selectattr), ['class' => 'form-check-label']); $select = new html_table_cell(html_writer::tag('div', $checkbox, ['class' => 'form-check'])); $name = new html_table_cell($group->name); if (empty($gradeinfo)) { $gradeinfo = new html_table_cell(get_string('no_grades_present', 'grouptool')); } else { $gradeinfo = new html_table_cell(implode("\n", $gradeinfo)); } $row = new html_table_row([$select, $name, $gradeinfo]); $tmpclass = $row->attributes['class']; $row->attributes['class'] = isset($tmpclass) ? $tmpclass.$error : $tmpclass; unset($tmpclass); $data[] = $row; } $tablepostfix = html_writer::tag('div', $buttontext, ['class' => 'center centered']); $tablepostfix .= html_writer::tag('div', $button, ['class' => 'centered center']); } else if ($filter > 0) { // Single group? $tablecolumns = [ 'select', 'fullname', 'idnumber', 'grade', 'feedback', 'copybutton' ]; $tableheaders = [ get_string('target', 'grouptool'), get_string('fullname'), get_string('idnumber'), get_string('grade'), get_string('feedback'), get_string('source', 'grouptool') ]; $groupmembers = groups_get_members($filter); // Get grading info for all groupmembers! $cmtouse = get_coursemodule_from_id('', $activity, $this->course->id); $gradinginfo = grade_get_grades($this->course->id, 'mod', $cmtouse->modname, $cmtouse->instance, array_keys($groupmembers)); if (isset($gradinginfo->items[0])) { foreach ($groupmembers as $groupmember) { $row = []; $finalgrade = $gradinginfo->items[0]->grades[$groupmember->id]; $grademax = $gradinginfo->items[0]->grademax; $finalgrade->formatted_grade = round($finalgrade->grade, 2) .' / ' . round($grademax, 2); $checkboxcontroller = optional_param('select', '', PARAM_ALPHA); if ($checkboxcontroller == 'all') { $checked = true; } else if ($checkboxcontroller == 'none') { $checked = false; } else { $checked = (isset($selected[$groupmember->id]) && ($selected[$groupmember->id] == 1)) ? true : false; } $checkbox = html_writer::tag('label', html_writer::checkbox('selected[]', $groupmember->id, $checked, '', ['class' => 'checkbox form-check-element']), ['class' => 'form-check-label']); $row[] = new html_table_cell(html_writer::tag('div', $checkbox, ['class' => 'form-check'])); $row[] = html_writer::tag('div', fullname($groupmember), ['class' => 'fullname'.$groupmember->id]); $row[] = html_writer::tag('div', $groupmember->idnumber, ['class' => 'idnumber'.$groupmember->id]); $row[] = html_writer::tag('div', $finalgrade->formatted_grade, ['class' => 'grade'.$groupmember->id]); $row[] = html_writer::tag('div', shorten_text(strip_tags($finalgrade->str_feedback), 15), ['class' => 'feedback'.$groupmember->id]); if ($mygroupsonly && ($finalgrade->usermodified != $USER->id)) { $row[] = html_writer::tag('div', get_string('not_graded_by_me', 'grouptool')); } else { $row[] = html_writer::tag('button', get_string('copygrade', 'grouptool'), [ 'type' => 'submit', 'name' => 'source', 'value' => $groupmember->id, 'class' => 'btn btn-primary' ]); } $data[] = $row; } } else { return $OUTPUT->box($OUTPUT->notification(get_string('no_grades_present', 'grouptool'), \core\output\notification::NOTIFY_ERROR), 'generalbox centered'); } } else { print_error('uknown filter-value'); } if (empty($data)) { if ($filter == GROUPTOOL_FILTER_ALL) { return $OUTPUT->box($OUTPUT->notification(get_string('no_data_to_display', 'grouptool'), \core\output\notification::NOTIFY_ERROR), 'generalbox centered'); } else if ($filter == GROUPTOOL_FILTER_NONCONFLICTING) { return $OUTPUT->box($OUTPUT->notification(get_string('no_conflictfree_to_display', 'grouptool'), \core\output\notification::NOTIFY_ERROR), 'centered'). $this->get_grading_table($activity, $mygroupsonly, $incompleteonly, GROUPTOOL_FILTER_ALL, $selected, $missingsource); } else { return $OUTPUT->box($OUTPUT->notification(get_string('no_groupmembers_to_display', 'grouptool'), \core\output\notification::NOTIFY_ERROR), 'centered'). $this->get_grading_table($activity, $mygroupsonly, $incompleteonly, GROUPTOOL_FILTER_ALL, $selected, $missingsource); } } $table->colclasses = $tablecolumns; // Instead of the strings an array of html_table_cells can be set as head! $table->head = $tableheaders; // Instead of the strings an array of html_table_cells can be used for the rows! $table->data = $data; $overwrite = optional_param('overwrite', 0, PARAM_BOOL); $grouping = optional_param('grouping', 0, PARAM_INT); $baseurl = new \moodle_url($PAGE->url, [ 'activity' => $activity, 'mygroups_only' => $mygroupsonly, 'incomplete_only' => $incompleteonly, 'filter' => $filter, 'overwrite' => $overwrite, 'grouping' => $grouping ]); $selectallurl = new \moodle_url($baseurl, ['select' => 'all']); $selectnoneurl = new \moodle_url($baseurl, ['select' => 'none']); $links = get_string('select').' '. \html_writer::link($selectallurl, get_string('all'), ['class' => 'select_all']).'/'. \html_writer::link($selectnoneurl, get_string('none'), ['class' => 'select_none']); $checkboxcontroller = html_writer::tag('div', $links, ['class' => 'checkboxcontroller']); return $checkboxcontroller.html_writer::table($table).$tablepostfix; } /** * copies the grades from the source(s) to the target(s) for the selected activity * * @param int $activity ID of activity to get/set grades from/for * @param bool $mygroupsonly limit source-grades to those given by current user * @param int[] $selected array with ids of groups/users to copy grades to as keys (depends on filter) * @param int[] $source optional array with ids of entries for whom no source has been selected * (just to display a clue to select a source) * @param bool $overwrite optional overwrite existing grades (std: false) * @param bool $previewonly optional just return preview data * @return array ($error, $message) * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ private function copy_grades($activity, $mygroupsonly, $selected, $source, $overwrite = false, $previewonly = false) { global $DB, $USER; $error = false; // If he want's to grade all he needs the corresponding capability! if (!$mygroupsonly) { require_capability('mod/grouptool:grade', $this->context); } else if (!has_capability('mod/grouptool:grade', $this->context)) { /* * if he wants to grade his own (=submissions where he graded at least 1 group member) * he needs either capability to grade all or to grade his own at least */ require_capability('mod/grouptool:grade_own_submission', $this->context); } $cmtouse = get_coursemodule_from_id('', $activity, $this->course->id); if (!$cmtouse) { return [true, get_string('couremodule_misconfigured')]; } if ($previewonly) { $previewtable = new html_table(); $previewtable->attributes['class'] = 'table table-hover grading_previewtable'; } else { $previewtable = new stdClass(); } $info = ""; $gradeitems = grade_item::fetch_all([ 'itemtype' => 'mod', 'itemmodule' => $cmtouse->modname, 'iteminstance' => $cmtouse->instance ]); // TODO #3310 should we support multiple grade items per activity module soon? do { // Right now, we just work with the first grade item! $gradeitem = current($gradeitems); } while (!empty($gradeitem->itemnumber) && next($gradeitems)); if (is_array($source)) { // Then we are in multigroup mode (filter = 0 || -1)! $sourceusers = $DB->get_records_list('user', 'id', $source); $groups = groups_get_all_groups($this->course->id); $previewtable->head = [ get_string('groups')." (".count($selected).")", get_string('fullname'), get_string('grade'), get_string('feedback') ]; foreach ($selected as $group) { $groupinfo = ""; $grouprows = []; $sourcegrade = grade_grade::fetch_users_grades($gradeitem, $source[$group], false); $sourcegrade = reset($sourcegrade); $sourcegrade->load_optional_fields(); $origteacher = $DB->get_record('user', ['id' => $sourcegrade->usermodified]); $formattedgrade = round($sourcegrade->finalgrade, 2) .' / ' . round($gradeitem->grademax, 2); $groupmembers = groups_get_members($group); $targetgrades = grade_grade::fetch_users_grades($gradeitem, array_keys($groupmembers), true); $propertiestocopy = ['rawgrade', 'finalgrade', 'feedback', 'feedbackformat']; foreach ($targetgrades as $currentgrade) { if ($currentgrade->id == $sourcegrade->id) { continue; } if (!$overwrite && ($currentgrade->finalgrade != null)) { if ($previewonly) { $rowcells = []; if (empty($grouprows)) { $rowcells[] = new html_table_cell($groups[$group]->name."\n". html_writer::empty_tag('br'). "(".(count($groupmembers) - 1).")"); } $fullname = fullname($groupmembers[$currentgrade->userid]); $rowcells[] = new html_table_cell($fullname); $cell = new html_table_cell(get_string('skipped', 'grouptool')); $cell->colspan = 2; $rowcells[] = $cell; $row = new html_table_row(); $row->cells = $rowcells; if (empty($grouprows)) { $row->attributes['class'] .= ' firstgrouprow'; } $grouprows[] = $row; } continue; } $currentgrade->load_optional_fields(); foreach ($propertiestocopy as $property) { $currentgrade->$property = $sourcegrade->$property; } $details = [ 'student' => fullname($sourceusers[$source[$group]]), 'teacher' => fullname($origteacher), 'date' => userdate($sourcegrade->get_dategraded(), get_string('strftimedatetimeshort')), 'feedback' => $sourcegrade->feedback ]; $currentgrade->feedback = format_text(get_string('copied_grade_feedback', 'grouptool', $details), $currentgrade->feedbackformat); $currentgrade->usermodified = $USER->id; if ($previewonly) { $rowcells = []; if (empty($grouprows)) { $rowcells[] = new html_table_cell($groups[$group]->name."\n". html_writer::empty_tag('br'). "(".count($groupmembers).")"); } $fullname = fullname($groupmembers[$currentgrade->userid]); $rowcells[] = new html_table_cell($fullname); $rowcells[] = new html_table_cell($formattedgrade); $rowcells[] = new html_table_cell($currentgrade->feedback); $row = new html_table_row(); $row->cells = $rowcells; if (empty($grouprows)) { $row->attributes['class'] .= ' firstgrouprow'; } $grouprows[] = $row; } else { if (function_exists ('grouptool_copy_'.$cmtouse->modname.'_grades')) { $copyfunction = 'grouptool_copy_'.$cmtouse->modname.'_grades'; $copyfunction($cmtouse->instance, $sourcegrade->userid, $currentgrade->userid); } if ($currentgrade->id) { $noerror = $currentgrade->update(); } else { $noerror = $currentgrade->insert(); } $currentgrade->set_overridden(true, false); $fullname = fullname($groupmembers[$currentgrade->userid]); if ($noerror) { $groupinfo .= html_writer::tag('span', '✓ '.$fullname. " (".$formattedgrade.")", ['class' => 'notifysuccess']); } else { $error = true; $groupinfo .= html_writer::tag('span', '✗ '.$fullname. " (".$formattedgrade.")", ['class' => 'notifyproblem']); } } } if ($previewonly) { $grouprows[0]->cells[0]->rowspan = count($grouprows); if (!is_array($previewtable->data)) { $previewtable->data = []; } $previewtable->data = array_merge($previewtable->data, $grouprows); } else { $grpinfo = ""; $grpinfo .= html_writer::tag('div', $groups[$group]->name." (". count($groupmembers)."): ".$groupinfo); $data = [ 'student' => fullname($sourceusers[$source[$group]]), 'teacher' => fullname($origteacher), 'date' => userdate($sourcegrade->get_dategraded(), get_string('strftimedatetimeshort')), 'feedback' => $sourcegrade->feedback ]; $temp = get_string('copied_grade_feedback', 'grouptool', $data); $grpinfo .= html_writer::tag('div', $formattedgrade.html_writer::empty_tag('br'). format_text($temp, $sourcegrade->feedbackformat)); $info .= html_writer::tag('div', $grpinfo, ['class' => 'box1embottom']); // Trigger the event! $logdata = new stdClass(); $logdata->groupid = $group; $logdata->cmtouse = $cmtouse->id; \mod_grouptool\event\group_graded::create_direct($this->cm, $logdata)->trigger(); } } } else { $sourceuser = $DB->get_record('user', ['id' => $source]); $targetusers = $DB->get_records_list('user', 'id', $selected); $sourcegrade = grade_grade::fetch_users_grades($gradeitem, [$source], false); $sourcegrade = reset($sourcegrade); $origteacher = $DB->get_record('user', ['id' => $sourcegrade->usermodified]); $formattedgrade = round($sourcegrade->finalgrade, 2).' / ' . round($gradeitem->grademax, 2); $targetgrades = grade_grade::fetch_users_grades($gradeitem, $selected, true); $propertiestocopy = ['rawgrade', 'finalgrade', 'feedback', 'feedbackformat']; $nameinfo = ""; $grouprows = []; if ($previewonly) { $count = in_array($source, $selected) ? count($selected) - 1 : count($selected); $previewtable->head = [ '', get_string('fullname')." (".$count.")", get_string('grade'), get_string('feedback') ]; $previewtable->attributes['class'] = 'table table-hover grading_previewtable'; } else { $info .= html_writer::start_tag('div'); } foreach ($targetgrades as $currentgrade) { if ($currentgrade->id == $sourcegrade->id) { continue; } if (!$overwrite && ($currentgrade->rawgrade != null)) { if ($previewonly) { $rowcells = []; if (empty($grouprows)) { $rowcells[] = new html_table_cell(get_string('users')); } $fullname = fullname($targetusers[$currentgrade->userid]); $rowcells[] = new html_table_cell($fullname); $cell = new html_table_cell(get_string('skipped', 'grouptool')); $cell->colspan = 2; $rowcells[] = $cell; $row = new html_table_row(); $row->cells = $rowcells; if (empty($grouprows)) { $row->attributes['class'] .= ' firstgrouprow'; } $grouprows[] = $row; } continue; } $currentgrade->load_optional_fields(); foreach ($propertiestocopy as $property) { $currentgrade->$property = $sourcegrade->$property; } $details = [ 'student' => fullname($sourceuser), 'teacher' => fullname($origteacher), 'date' => userdate($sourcegrade->get_dategraded(), get_string('strftimedatetimeshort')), 'feedback' => $sourcegrade->feedback ]; $currentgrade->feedback = format_text(get_string('copied_grade_feedback', 'grouptool', $details), $currentgrade->feedbackformat); $currentgrade->usermodified = $USER->id; if ($previewonly) { $rowcells = []; if (empty($grouprows)) { $rowcells[] = new html_table_cell(get_string('users')); } $fullname = fullname($targetusers[$currentgrade->userid]); $rowcells[] = new html_table_cell($fullname); $rowcells[] = new html_table_cell($formattedgrade); $rowcells[] = new html_table_cell(format_text($currentgrade->feedback, $currentgrade->feedbackformat)); $row = new html_table_row(); $row->cells = $rowcells; if (empty($grouprows)) { $row->attributes['class'] .= ' firstgrouprow'; } $grouprows[] = $row; } else { if ($nameinfo != "") { $nameinfo .= ", "; } if ($currentgrade->id) { $noerror = $currentgrade->update(); } else { $noerror = $currentgrade->insert(); } $currentgrade->set_overridden(true, false); $fullname = fullname($targetusers[$currentgrade->userid]); if (function_exists ('grouptool_copy_'.$cmtouse->modname.'_grades')) { $copyfunction = 'grouptool_copy_'.$cmtouse->modname.'_grades'; $copyfunction($cmtouse->instance, $sourcegrade->userid, $currentgrade->userid); } if ($noerror) { $nameinfo .= html_writer::tag('span', '✓ '.$fullname, ['class' => 'notifysuccess']); } else { $error = true; $nameinfo .= html_writer::tag('span', '✗ '.$fullname, ['class' => 'notifyproblem']); } } } if ($previewonly) { $grouprows[0]->cells[0]->rowspan = count($grouprows); $previewtable->data = $grouprows; } else { $info .= $nameinfo.html_writer::end_tag('div'); $details = [ 'student' => fullname($sourceuser), 'teacher' => fullname($origteacher), 'date' => userdate($sourcegrade->get_dategraded(), get_string('strftimedatetimeshort')), 'feedback' => $sourcegrade->feedback ]; $info .= html_writer::tag('div', get_string('grade').": ". $formattedgrade.html_writer::empty_tag('br'). format_text(get_string('copied_grade_feedback', 'grouptool', $details), $sourcegrade->feedbackformat), ['class' => 'gradeinfo']); } if (!$previewonly) { // Trigger the event! $logdata = new stdClass(); $logdata->source = $source; $logdata->selected = $selected; $logdata->cmtouse = $cmtouse->id; \mod_grouptool\event\group_graded::create_without_groupid($this->cm, $logdata)->trigger(); } } if ($previewonly) { return [ $error, html_writer::tag('div', html_writer::table($previewtable), ['class' => 'centeredblock']) ]; } else { return [$error, html_writer::tag('div', $info, ['class' => 'centeredblock'])]; } } /** * view grading-tab * * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function view_grading() { global $PAGE, $OUTPUT, $USER; if (!has_capability('mod/grouptool:grade', $this->context) && !has_capability('mod/groputool:grade_own_groups', $this->context)) { print_error('nopermissions'); return; } $refreshtable = optional_param('refresh_table', 0, PARAM_BOOL); $activity = optional_param('activity', null, PARAM_INT); // This is the coursemodule-ID. // Show only groups with grades given by current user! $mygroupsonly = optional_param('mygroups_only', null, PARAM_BOOL); if (!has_capability('mod/grouptool:grade', $this->context)) { $mygroupsonly = 1; } if ($mygroupsonly != null) { set_user_preference('mod_grouptool_mygroups_only', $mygroupsonly, $USER->id); } // Show only groups with missing grades (groups with at least 1 not-graded member)! $incompleteonly = optional_param('incomplete_only', 0, PARAM_BOOL); $overwrite = optional_param('overwrite', 0, PARAM_BOOL); // Here -1 = nonconflicting, 0 = all or groupid for certain group! $filter = optional_param('filter', GROUPTOOL_FILTER_NONCONFLICTING, PARAM_INT); // Steps: 0 = show, 1 = confirm, 2 = action! $step = optional_param('step', 0, PARAM_INT); if ($refreshtable) { // If it was just a refresh, reset step! $step = 0; } $grouping = optional_param('grouping', 0, PARAM_INT); if ($filter > 0) { if ($step == 2) { $source = optional_param('source', null, PARAM_INT); // Serialized data TODO: better PARAM_TYPE? $selected = optional_param('selected', null, PARAM_RAW); if (!empty($selected)) { $selected = unserialize($selected); } } else { if ($refreshtable) { // Otherwise we get problems here, if we refresh and change from multiple groups to a single group! $source = null; } else { $source = optional_param('source', null, PARAM_INT); } $selected = optional_param_array('selected', null, PARAM_INT); if (!empty($source) && !$refreshtable) { $step = 1; } } } else { if ($step == 2) { $source = optional_param('source', null, PARAM_RAW); if (!empty($source)) { $source = unserialize($source); } $selected = optional_param('selected', null, PARAM_RAW); if (!empty($selected)) { $selected = unserialize($selected); } } else { $source = optional_param_array('source', [], PARAM_INT); $selected = optional_param_array('selected', [], PARAM_INT); $copygroups = optional_param('copygrades', 0, PARAM_BOOL); if ($copygroups && !$refreshtable) { $step = 1; } } } $confirm = optional_param('confirm', 0, PARAM_BOOL); if (($step == 2) && !$confirm) { $step = 0; } // Reset process if some evil hacker tried to do smth! if (!$confirm && (!data_submitted() || !confirm_sesskey())) { $step = 0; } if (!empty($mygroupsonly)) { $mygroupsonly = get_user_preferences('mod_grouptool_mygroups_only', 1, $USER->id); } $missingsource = []; if ($step == 1) { // Show confirm message! if ($filter > 0) { // Single group mode! if (is_array($selected) && in_array($source, $selected)) { foreach ($selected as $key => $cmp) { if ($cmp == $source) { unset($selected[$key]); } } } if (!empty($selected)) { list(, $preview) = $this->copy_grades($activity, $mygroupsonly, $selected, $source, $overwrite, true); $continue = new moodle_url("view.php?id=".$this->cm->id, [ 'tab' => 'grading', 'confirm' => 'true', 'sesskey' => sesskey(), 'step' => '2', 'activity' => $activity, 'mygroups_only' => $mygroupsonly, 'overwrite' => $overwrite, 'selected' => serialize($selected), 'source' => serialize($source) ]); $cancel = new moodle_url("view.php?id=".$this->cm->id, [ 'tab' => 'grading', 'confirm' => 'false', 'sesskey' => sesskey(), 'step' => '2', 'activity' => $activity, 'mygroups_only' => $mygroupsonly, 'overwrite' => $overwrite, 'selected' => serialize($selected), 'source' => serialize($source) ]); $preview = $OUTPUT->heading(get_string('preview'), 2, 'centered').$preview; if ($overwrite) { echo $preview.$this->confirm(get_string('copy_grades_overwrite_confirm', 'grouptool'), $continue, $cancel); } else { echo $preview.$this->confirm(get_string('copy_grades_confirm', 'grouptool'), $continue, $cancel); } } else { $boxcontent = $OUTPUT->notification(get_string('no_target_selected', 'grouptool'), \core\output\notification::NOTIFY_ERROR); echo $OUTPUT->box($boxcontent, 'generalbox'); unset($boxcontent); $step = 0; } } else if ($filter == GROUPTOOL_FILTER_ALL || $filter == GROUPTOOL_FILTER_NONCONFLICTING) { // All or nonconflicting mode? foreach ($selected as $key => $grp) { // If no grade is choosen add this group to missing-source-list! if (empty($source[$grp])) { $missingsource[] = $grp; } } if (!empty($selected) && (count($missingsource) == 0)) { list(, $preview) = $this->copy_grades($activity, $mygroupsonly, $selected, $source, $overwrite, true); $continue = new moodle_url("view.php?id=".$this->cm->id, [ 'tab' => 'grading', 'confirm' => 'true', 'sesskey' => sesskey(), 'activity' => $activity, 'mygroups_only' => $mygroupsonly, 'overwrite' => $overwrite, 'step' => '2', 'selected' => serialize($selected), 'source' => serialize($source) ]); $cancel = new moodle_url("view.php?id=".$this->cm->id, [ 'tab' => 'grading', 'confirm' => 'false', 'sesskey' => sesskey(), 'activity' => $activity, 'mygroups_only' => $mygroupsonly, 'overwrite' => $overwrite, 'step' => '2', 'selected' => serialize($selected), 'source' => serialize($source) ]); $preview = $OUTPUT->heading(get_string('preview'), 2, 'centered').$preview; if ($overwrite) { echo $preview.$this->confirm(get_string('copy_grades_overwrite_confirm', 'grouptool'), $continue, $cancel); } else { echo $preview.$this->confirm(get_string('copy_grades_confirm', 'grouptool'), $continue, $cancel); } } else { if (empty($selected)) { $boxcontent = $OUTPUT->notification(get_string('no_target_selected', 'grouptool'), \core\output\notification::NOTIFY_ERROR); echo $OUTPUT->box($boxcontent, 'generalbox'); unset($boxcontent); $step = 0; } if (count($missingsource) != 0) { $boxcontent = $OUTPUT->notification(get_string('sources_missing', 'grouptool'), \core\output\notification::NOTIFY_ERROR); echo $OUTPUT->box($boxcontent, 'generalbox'); unset($boxcontent); $step = 0; } } } else { print_error('wrong parameter'); } } if ($step == 2) { // Do action and continue with showing the form! // if there was an error? list($error, $info) = $this->copy_grades($activity, $mygroupsonly, $selected, $source, $overwrite); if ($error) { $boxcontent = $OUTPUT->notification(get_string('copy_grades_errors', 'grouptool'), \core\output\notification::NOTIFY_ERROR).$info; echo $OUTPUT->box($boxcontent, 'generalbox tumargin'); unset($boxcontent); } else { $boxcontent = $OUTPUT->notification(get_string('copy_grades_success', 'grouptool'), \core\output\notification::NOTIFY_SUCCESS).$info; echo $OUTPUT->box($boxcontent, 'generalbox tumargin'); unset($boxcontent); } } if ($step != 1 || count($missingsource)) { // Show form if step is either 0 or 2! // Prepare form content! if ($filter > 0) { $table = $this->get_grading_table($activity, $mygroupsonly, $incompleteonly, $filter, $selected); } else { $table = $this->get_grading_table($activity, $mygroupsonly, $incompleteonly, $filter, $selected, $missingsource); } $formdata = ['id' => $this->cm->id, 'course' => $this->course, 'mygroupsonly' => $mygroupsonly, 'incompleteonly' => $incompleteonly, 'overwrite' => $overwrite, 'grouping' => $grouping, 'filter' => $filter, 'table' => $table]; $mform = new \mod_grouptool\grading_form($PAGE->url, $formdata, 'post', '', ['class' => 'mform', 'id' => 'grading_form', 'name' => 'grading_form']); $params = new stdClass(); $params->lang = current_language(); $params->contextid = $this->context->id; $PAGE->requires->js_call_amd('mod_grouptool/grading', 'initializer', [$params]); $mform->display(); } } /** * gets data about active groups for this instance * * @param bool $includeregs optional include registered users in returned object * @param bool $includequeues optional include queued users in returned object * @param int $agrpid optional filter by a single active-groupid from {grouptool_agrps}.id * @param int $groupid optional filter by a single group-id from {groups}.id * @param int $groupingid optional filter by a single grouping-id * @param bool $indexbygroup optional index returned array by {groups}.id * instead of {grouptool_agrps}.id * @param bool $includeinactive optional include also inactive groups - despite the method being called get_active_groups()! * @return array of objects containing all necessary information about chosen active groups * @throws dml_exception * @throws required_capability_exception */ public function get_active_groups($includeregs=false, $includequeues=false, $agrpid=0, $groupid=0, $groupingid=0, $indexbygroup=true, $includeinactive = false) { global $DB; require_capability('mod/grouptool:view_groups', $this->context); $params = ['grouptoolid' => $this->cm->instance]; if (!empty($agrpid)) { $agrpidwhere = " AND agrp.id = :agroup"; $params['agroup'] = $agrpid; } else { $agrpidwhere = ""; } if (!empty($groupid)) { $groupidwhere = " AND grp.id = :groupid"; $params['groupid'] = $groupid; } else { $groupidwhere = ""; } if (!empty($groupingid)) { $groupingidwhere = " AND grpgs.id = :groupingid"; $params['groupingid'] = $groupingid; } else { $groupingidwhere = ""; } if (!empty($this->grouptool->use_size)) { if (false) { $sizesql = " ".$this->grouptool->grpsize." grpsize,"; } else { $grouptoolgrpsize = get_config('mod_grouptool', 'grpsize'); $grpsize = (!empty($this->grouptool->grpsize) ? $this->grouptool->grpsize : $grouptoolgrpsize); if (empty($grpsize)) { $grpsize = 3; } $sizesql = " COALESCE(agrp.grpsize, ".$grpsize.") AS grpsize,"; } } else { $sizesql = ""; } if ($indexbygroup) { $idstring = "grp.id AS id, agrp.id AS agrpid"; } else { $idstring = "agrp.id AS agrpid, grp.id AS id"; } $params['agrpgrptlid'] = $this->cm->instance; if (!$includeinactive) { $active = " AND agrp.active = 1 "; } else { $active = ""; } $groupdata = $DB->get_records_sql(" SELECT ".$idstring.", MAX(grp.name) AS name,".$sizesql." MAX(agrp.sort_order) AS sort_order, agrp.active AS active FROM {groups} grp LEFT JOIN {grouptool_agrps} agrp ON agrp.groupid = grp.id AND agrp.grouptoolid = :agrpgrptlid LEFT JOIN {groupings_groups} ON {groupings_groups}.groupid = grp.id LEFT JOIN {groupings} grpgs ON {groupings_groups}.groupingid = grpgs.id WHERE agrp.grouptoolid = :grouptoolid ".$active. $agrpidwhere.$groupidwhere.$groupingidwhere." GROUP BY grp.id, agrp.id ORDER BY sort_order ASC, name ASC", $params); if (!empty($groupdata)) { foreach ($groupdata as $key => $group) { $groupingids = $DB->get_fieldset_select('groupings_groups', 'groupingid', 'groupid = ?', [$group->id]); if (!empty($groupingids)) { $groupdata[$key]->classes = implode(',', $groupingids); } else { $groupdata[$key]->classes = ''; } } if ((!empty($this->grouptool->use_size)) || ($this->grouptool->use_queue && $includequeues) || ($includeregs)) { $keys = array_keys($groupdata); foreach ($keys as $key) { $groupdata[$key]->queued = null; if ($includequeues && $this->grouptool->use_queue) { $attr = ['agrpid' => $groupdata[$key]->agrpid]; $groupdata[$key]->queued = (array)$DB->get_records('grouptool_queued', $attr); } $groupdata[$key]->registered = null; if ($includeregs) { $params = ['agrpid' => $groupdata[$key]->agrpid]; $where = "agrpid = :agrpid AND modified_by >= 0"; $groupdata[$key]->registered = $DB->get_records_select('grouptool_registered', $where, $params); $params['modifierid'] = -1; $where = "agrpid = :agrpid AND modified_by = :modifierid"; $groupdata[$key]->marked = $DB->get_records_select('grouptool_registered', $where, $params); $groupdata[$key]->moodle_members = groups_get_members($groupdata[$key]->id); } } unset($key); } } else { $groupdata = []; } return $groupdata; } /** * Fills the group as much as possible with entries from the queue. * Usefull for group size changes or if someone is removed from the group or unregisters him-/herself * * @param int $agrpid Active group to fill * @return bool true if everything went fine! * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ public function fill_from_queue($agrpid) { global $DB, $CFG, $OUTPUT; if (empty($this->grouptool->use_queue)) { return true; } $groupdata = $this->get_active_groups(true, true, $agrpid); $groupdata = reset($groupdata); if (empty($groupdata->queued)) { return true; } $agrpids = $DB->get_fieldset_sql('SELECT id FROM {grouptool_agrps} WHERE grouptoolid = ?', [$this->grouptool->id]); list($agrpssql, $agrpsparam) = $DB->get_in_or_equal($agrpids); $sql = "SELECT queued.id, MAX(agrp.groupid) AS groupid, MAX(queued.agrpid) AS agrpid, MAX(queued.userid) AS userid, MAX(queued.timestamp) AS timestamp, (COUNT(DISTINCT reg.id) < ?) AS priority FROM {grouptool_queued} queued LEFT JOIN {grouptool_agrps} agrp ON agrp.id = queued.agrpid LEFT JOIN {grouptool_registered} reg ON queued.userid = reg.userid AND reg.agrpid ".$agrpssql." WHERE queued.agrpid = ? GROUP BY queued.id ORDER BY priority DESC, queued.timestamp ASC"; $params = array_merge([$this->grouptool->choose_min], $agrpsparam, [$agrpid]); $records = $DB->get_records_sql($sql, $params); if (empty($records) || count($records) == 0) { return true; } $message = new stdClass(); $message->groupname = $groupdata->name; foreach ($records as $record) { if (!empty($this->grouptool->use_size) && ($groupdata->grpsize <= count($groupdata->registered))) { return true; } $newrecord = clone $record; unset($newrecord->id); $newrecord->modified_by = $newrecord->userid; $newrecord->id = $DB->insert_record('grouptool_registered', $newrecord); $groupdata->registered[] = $newrecord; if (!empty($this->grouptool->immediate_reg)) { groups_add_member($groupdata->id, $newrecord->userid); } $allowm = $this->grouptool->allow_multiple; $usrregcnt = $this->get_user_reg_count($newrecord->userid); $max = $this->grouptool->choose_max; if (($allowm && ( $usrregcnt >= $max) ) || !$allowm) { $agrps = $this->get_active_groups(false, false, 0, 0, 0, false); $agrpids = array_keys($agrps); list($sql, $params) = $DB->get_in_or_equal($agrpids); $records = $DB->get_records_sql("SELECT queued.*, agrp.groupid FROM {grouptool_queued} queued JOIN {grouptool_agrps} agrp ON queued.agrpid = agrp.id WHERE userid = ? AND agrpid ".$sql, array_merge([$newrecord->userid], $params)); $DB->delete_records_select('grouptool_queued', ' userid = ? AND agrpid '.$sql, array_merge([$newrecord->userid], $params)); foreach ($records as $cur) { // Trigger the event! \mod_grouptool\event\queue_entry_deleted::create_limit_violation($this->cm, $cur)->trigger(); } } $context = (object)[ 'course' => $this->course, 'courseurl' => $CFG->wwwroot."/course/view.php?id=".$this->course->id, 'coursegrouptoolsurl' => $CFG->wwwroot."/mod/grouptool/index.php?id=".$this->course->id, 'grouptoolurl' => $CFG->wwwroot."/mod/grouptool/view.php?id=".$this->cm->id, 'grouptoolname' => format_string($this->grouptool->name, true), 'groupname' => $groupdata->name, 'message' => get_string('register_you_in_group_success', 'grouptool', (object)[ 'groupname' => $groupdata->name ]), ]; $postsubject = $this->course->shortname.': '.get_string('modulenameplural', 'grouptool').': '. format_string($this->grouptool->name, true); $messageuser = $DB->get_record('user', ['id' => $newrecord->userid]); $moodlemessage = new \core\message\message(); $userfrom = core_user::get_noreply_user(); $moodlemessage->component = 'mod_grouptool'; $moodlemessage->name = 'grouptool_moveupreg'; $moodlemessage->courseid = $this->course->id; $moodlemessage->userfrom = $userfrom; $moodlemessage->userto = $messageuser; $moodlemessage->subject = $postsubject; $moodlemessage->fullmessage = get_string('registrationnotification', 'mod_grouptool', $context); $moodlemessage->fullmessageformat = FORMAT_HTML; $moodlemessage->fullmessagehtml = $OUTPUT->render_from_template('mod_grouptool/registrationnotification', $context); $moodlemessage->smallmessage = $context->message; $moodlemessage->notification = 1; $moodlemessage->contexturl = $CFG->wwwroot.'/mod/grouptool/view.php?id='.$this->cm->id; $moodlemessage->contexturlname = $this->grouptool->name; message_send($moodlemessage); $DB->delete_records('grouptool_queued', ['id' => $record->id]); // Trigger the event! // We fetched groupid above in SQL. \mod_grouptool\event\user_moved::promotion_from_queue($this->cm, $record, $newrecord)->trigger(); } return true; } /** * unregisters/unqueues a user from a certain active-group or throw an exception * * @param int $agrpid active-group-id to unregister/unqueue user from * @param int $userid user to unregister/unqueue * @param bool $previewonly (optional) don't act, just return a preview * @param bool $force (optional) ignore setting for allowing deregistration (needed for multi-deregistration) * @return string $message if everything went right * @throws \mod_grouptool\local\exception\notenoughregs If the user hasn't enough registrations! * @throws \mod_grouptool\local\exception\registration In any other case, where the user can't be unregistered! * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ protected function unregister_from_agrp($agrpid, $userid=0, $previewonly=false, $force=false) { global $USER, $DB; if (empty($userid)) { $userid = $USER->id; require_capability('mod/grouptool:register', $this->context); } $regopen = ($this->grouptool->allow_reg && (($this->grouptool->timedue == 0) || (time() < $this->grouptool->timedue)) && ($this->grouptool->timeavailable < time())); if (!$force && !$regopen && !has_capability('mod/grouptool:register_students', $this->context)) { throw new \mod_grouptool\local\exception\registration('reg_not_open'); } if (!$force && empty($this->grouptool->allow_unreg)) { throw new \mod_grouptool\local\exception\registration('unreg_not_allowed'); } $message = new stdClass(); if ($userid != $USER->id) { $userdata = $DB->get_record('user', ['id' => $userid]); $message->username = fullname($userdata); } $groupdata = $this->get_active_groups(true, true, $agrpid, 0, 0, true, true); if (count($groupdata) != 1) { throw new \mod_grouptool\local\exception\registration('error_getting_data'); } $groupdata = reset($groupdata); $message->groupname = $groupdata->name; $message->userid = $userid; $agrpids = $DB->get_fieldset_select('grouptool_agrps', 'id', "grouptoolid = ?", [$this->grouptool->id]); list($agrpsql, $params) = $DB->get_in_or_equal($agrpids); array_unshift($params, $userid); $userregs = $DB->count_records_select('grouptool_registered', "modified_by >= 0 AND userid = ? AND agrpid ".$agrpsql, $params); $userqueues = $DB->count_records_select('grouptool_queued', "userid = ? AND agrpid ".$agrpsql, $params); $min = $this->grouptool->allow_multiple ? $this->grouptool->choose_min : 0; if ($userregs + $userqueues <= $min) { if ($userid == $USER->id) { $text = 'you_have_too_less_regs'; } else { $text = 'user_has_too_less_regs'; } // Throw notenoughregs exception with custom description text! throw new \mod_grouptool\local\exception\notenoughregs($text, $message); } if ($this->get_rank_in_queue($groupdata->registered, $userid) != false) { // He is registered! if ($previewonly) { // Shortcut here, everything seems to be fine, enough for now! if ($userid == $USER->id) { return get_string('unreg_you_from_group', 'grouptool', $message); } else { return get_string('unreg_from_group', 'grouptool', $message); } } $records = $DB->get_records('grouptool_registered', [ 'agrpid' => $agrpid, 'userid' => $userid ]); $DB->delete_records('grouptool_registered', [ 'agrpid' => $agrpid, 'userid' => $userid ]); if (!$force && !empty($this->grouptool->immediate_reg)) { groups_remove_member($groupdata->id, $userid); } foreach ($records as $data) { // Trigger the event! $data->groupid = $groupdata->id; \mod_grouptool\event\registration_deleted::create_direct($this->cm, $data)->trigger(); } // Get next queued user and put him in the group (and delete queue entry)! if (!empty($this->grouptool->use_queue) && !empty($groupdata->queued)) { $this->fill_from_queue($agrpid); } if ($userid == $USER->id) { return get_string('unreg_you_from_group_success', 'grouptool', $message); } else { return get_string('unreg_from_group_success', 'grouptool', $message); } } if ($this->get_rank_in_queue($groupdata->queued, $userid) != false) { // He is queued! if ($previewonly) { // Shortcut here, everything seems to be fine, enough for now! if ($userid == $USER->id) { return get_string('unqueue_you_from_group', 'grouptool', $message); } else { return get_string('unqueue_from_group', 'grouptool', $message); } } $records = $DB->get_records('grouptool_queued', ['agrpid' => $agrpid, 'userid' => $userid]); $DB->delete_records('grouptool_queued', [ 'agrpid' => $agrpid, 'userid' => $userid ]); foreach ($records as $cur) { // Trigger the Event! $cur->groupid = $groupdata->id; \mod_grouptool\event\queue_entry_deleted::create_direct($this->cm, $cur)->trigger(); } if ($userid == $USER->id) { return get_string('unqueue_you_from_group_success', 'grouptool', $message); } else { return get_string('unqueue_from_group_success', 'grouptool', $message); } } // If we got here, the user was neither registered nor queued! if ($userid == $USER->id) { $text = get_string('you_are_not_in_queue_or_registered', 'grouptool', $message); } else { $text = get_string('not_in_queue_or_registered', 'grouptool', $message); } throw new \mod_grouptool\local\exception\registration($text); } /** * registers/queues a user in a certain active-group * * @param int $agrpid active-group-id to register/queue user to * @param int $userid user to register/queue * @param bool $previewonly optional don't act, just return a preview * @return string status message * @throws \mod_grouptool\local\exception\exceedgroupqueuelimit * @throws \mod_grouptool\local\exception\exceeduserqueuelimit * @throws \mod_grouptool\local\exception\exceeduserreglimit * @throws \mod_grouptool\local\exception\exceedgroupsize * @throws \mod_grouptool\local\exception\regpresent * @throws \mod_grouptool\local\exception\registration * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ protected function register_in_agrp($agrpid, $userid=0, $previewonly=false) { global $USER, $DB; if (empty($userid)) { $userid = $USER->id; require_capability('mod/grouptool:register', $this->context); } $regopen = ($this->grouptool->allow_reg && (($this->grouptool->timedue == 0) || (time() < $this->grouptool->timedue)) && ($this->grouptool->timeavailable <= time())); if (!$regopen && !has_capability('mod/grouptool:register_students', $this->context)) { throw new \mod_grouptool\local\exception\registration('reg_not_open'); } $message = new stdClass(); if ($userid != $USER->id) { $userdata = $DB->get_record('user', ['id' => $userid]); $message->username = fullname($userdata); } $groupdata = $this->get_active_groups(true, true, $agrpid); if (count($groupdata) != 1) { throw new \mod_grouptool\local\exception\registration('error_getting_data'); } $groupdata = current($groupdata); $message->groupname = $groupdata->name; $message->userid = $userid; if ($this->qualifies_for_groupchange($agrpid, $userid)) { if ($previewonly) { $return = $this->can_change_group($agrpid, $userid, $message); } else { $return = $this->change_group($agrpid, $userid, $message); // If we can register, we have to convert the other marks to registrations & queue entries! $this->convert_marks_to_regs($userid); } return $return; } try { // First we try to register the user! if ($previewonly) { $return = $this->can_be_registered($agrpid, $userid, $message); } else { $return = $this->add_registration($agrpid, $userid, $message); // If we can register, we have to convert the other marks to registrations & queue entries! $this->convert_marks_to_regs($userid); } return $return; } catch (\mod_grouptool\local\exception\exceedgroupsize $e) { if (!$this->grouptool->use_queue) { // Shortcut: throw the exception again, if we don't use queues! throw $e; } // There's no place left in the group, so we try to queue the user! try { if ($previewonly) { $return = $this->can_be_queued($agrpid, $userid, $message); } else { $return = $this->add_queue_entry($agrpid, $userid, $message); // If we can queue, we have to convert the other marks to registrations & queue entries! $this->convert_marks_to_regs($userid); } return $return; } catch (\mod_grouptool\local\exception\notenoughregs $e) { /* The user has not enough registrations, queue entries or marks, * so we try to mark the user! (Exceptions get handled above!) */ if ($previewonly) { list(, $return) = $this->can_be_marked($agrpid, $userid, $message); } else { $return = $this->mark_for_reg($agrpid, $userid, $message); } return $return; } } catch (\mod_grouptool\local\exception\notenoughregs $e) { /* The user has not enough registrations, queue entries or marks, * so we try to mark the user! (Exceptions get handled above!) */ if ($previewonly) { list(, $return) = $this->can_be_marked($agrpid, $userid, $message); } else { $return = $this->mark_for_reg($agrpid, $userid, $message); } return $return; } } /** * Check if user can change the group! Works different by returning 0 or 1! * * @param int $agrpid ID of the active group * @param int $userid (optional) ID of user to queue or null (then $USER->id is used) * @return bool whether or not user qualifies for a group change */ protected function qualifies_for_groupchange($agrpid, $userid) { // Not really used here, but at least empty values needed by can_change_group()! $message = new stdClass(); $message->username = ''; $message->groupname = ''; try { $this->can_change_group($agrpid, $userid, $message); } catch (Exception $e) { return false; } catch (Throwable $e) { return false; } return true; } /** * Check if user is already registered, queued or marked for registration, throw exception in that case! * * @param int $agrpid ID of the active group * @param int $userid ID of user to queue or null (then $USER->id is used) * @param stdClass $groupdata Object with group info * @param stdClass $message (optional) cached data for the language strings * @throws \mod_grouptool\local\exception\regpresent * @throws coding_exception * @throws dml_exception */ protected function check_reg_present($agrpid, $userid, $groupdata, $message) { global $USER; if ($this->grpmarked($agrpid, $userid)) { // Allready marked for registration!?! if ($userid != $USER->id) { throw new \mod_grouptool\local\exception\regpresent('already_marked', $message); } else { throw new \mod_grouptool\local\exception\regpresent('you_are_already_marked', $message); } } if (!empty($groupdata->registered) && $this->get_rank_in_queue($groupdata->registered, $userid) != false) { // We're sorry, but user's already registered in this group! if ($userid != $USER->id) { throw new \mod_grouptool\local\exception\regpresent('already_registered', $message); } else { throw new \mod_grouptool\local\exception\regpresent('you_are_already_registered', $message); } } if (!empty($groupdata->queued) && $this->get_rank_in_queue($groupdata->queued, $userid) != false) { // We're sorry, but user's already queued in this group! if ($userid != $USER->id) { throw new \mod_grouptool\local\exception\regpresent('already_queued', $message); } else { throw new \mod_grouptool\local\exception\regpresent('you_are_aleady_queued', $message); } } } /** * Check if user can change the group! Works different by returning 0 or 1! * * @param int $agrpid ID of the active group * @param int $userid ID of user to queue or null (then $USER->id is used) * @param stdClass $message cached data for the language strings * @param int $oldagrpid (optional) ID of former active group * @return string 'string' status message * @throws \mod_grouptool\local\exception\exceedgroupqueuelimit * @throws \mod_grouptool\local\exception\exceeduserreglimit * @throws \mod_grouptool\local\exception\exceeduserqueuelimit * @throws \mod_grouptool\local\exception\registration * @throws \mod_grouptool\local\exception\regpresent * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ protected function can_change_group($agrpid, $userid, $message, $oldagrpid = null) { global $USER; if ($userid === null) { $userid = $USER->id; } $groupdata = $this->get_active_groups(true, true, $agrpid); if (count($groupdata) != 1) { throw new \mod_grouptool\local\exception\registration('error_getting_data'); } $groupdata = reset($groupdata); if (empty($this->grouptool->allow_unreg)) { throw new \mod_grouptool\local\exception\registration('unreg_not_allowed'); } $this->check_reg_present($agrpid, $userid, $groupdata, $message); list($userregs, $userqueues, , , $max) = $this->check_users_regs_limits($userid, true); if (($oldagrpid === null) && !(($userqueues == 1 && $userregs == $max - 1) || ($userqueues + $userregs == 1 && $max == 1))) { // We can't determine a unique group to unreg the user from! He has to do it by manually! throw new \mod_grouptool\local\exception\registration('groupchange_from_non_unique_reg'); } if ($this->grouptool->use_size && !empty($groupdata->registered) && (count($groupdata->registered) >= $groupdata->grpsize)) { if (!$this->grouptool->use_queue) { // We can't register the user nor queue the user! throw new \mod_grouptool\local\exception\exceedgroupsize(); } else if (count($groupdata->queued) >= $this->grouptool->groups_queues_limit) { throw new \mod_grouptool\local\exception\exceedgroupqueuelimit(); } if ($this->grouptool->users_queues_limit && ($userqueues >= $this->grouptool->users_queues_limit) && ($userqueues != 1)) { // We can't queue him, due to exceeding his queue limit or not being able to determine which queue entry to unreg! throw new \mod_grouptool\local\exception\exceeduserqueuelimit(); } } // We have no 'you'-version of the string here! return get_string('change_group_to', 'grouptool', $message); } /** * Changes group for certain user. This is only possible if unreg is allowed and we can determine which group to change! * * @param int $agrpid ID of active group to change to * @param int $userid (optional) ID of user to change group for or null ($USER->id is used). * @param stdClass $message (optional) prepared message object containing username and groupname or null. * @param int $oldagrpid (optional) ID of former active group * @return string success message * @throws \mod_grouptool\local\exception\exceedgroupqueuelimit * @throws \mod_grouptool\local\exception\exceeduserreglimit * @throws \mod_grouptool\local\exception\exceeduserqueuelimit * @throws \mod_grouptool\local\exception\registration * @throws \mod_grouptool\local\exception\regpresent * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ protected function change_group($agrpid, $userid = null, $message = null, $oldagrpid = null) { global $DB, $USER; if ($userid === null) { $userid = $USER->id; } if ($message === null) { $message = new stdClass(); if ($userid != $USER->id) { $userdata = $DB->get_record('user', ['id' => $userid]); $message->username = fullname($userdata); } else { $message->username = fullname($USER); } $groupdata = $this->get_active_groups(false, false, $agrpid); if (count($groupdata) != 1) { throw new \mod_grouptool\local\exception\registration('error_getting_data'); } $groupdata = reset($groupdata); $message->groupname = $groupdata->name; } // Check if the user can be registered or queued with respect to max registrations being incremented by 1. $this->can_change_group($agrpid, $userid, $message, $oldagrpid); // Determine from which group to change and unregister from it! // We have to filter only active groups to ensure no problems counting userregs and -queues. $agrpids = $DB->get_fieldset_select('grouptool_agrps', 'id', "grouptoolid = ? AND active = 1", [$this->grouptool->id]); list($agrpsql, $params) = $DB->get_in_or_equal($agrpids); array_unshift($params, $userid); $userregs = $DB->get_records_select('grouptool_registered', "modified_by >= 0 AND userid = ? AND agrpid ".$agrpsql, $params); $userqueues = $DB->get_records_select('grouptool_queued', "userid = ? AND agrpid ".$agrpsql, $params); if ($oldagrpid !== null) { $sql = "SELECT queued.*, agrp.groupid FROM {grouptool_queued} queued JOIN {grouptool_agrps} agrp ON agrp.id = queued.agrpid WHERE userid = ? AND agrpid = ?"; if ($queue = $DB->get_record_sql($sql, [ 'userid' => $userid, 'agrpid' => $oldagrpid ], IGNORE_MISSING)) { $DB->delete_records('grouptool_queued', ['id' => $queue->id]); // Trigger the event! \mod_grouptool\event\queue_entry_deleted::create_direct($this->cm, $queue); // Let other queued be promoted to registered status! $this->fill_from_queue($queue->agrpid); } $sql = "SELECT reg.*, agrp.groupid FROM {grouptool_registered} reg JOIN {grouptool_agrps} agrp ON agrp.id = reg.agrpid WHERE userid = ? AND agrpid = ?"; if ($reg = $DB->get_record_sql($sql, [ 'userid' => $userid, 'agrpid' => $oldagrpid ], IGNORE_MISSING)) { $DB->delete_records('grouptool_registered', ['id' => $reg->id]); if (!empty($this->grouptool->immediate_reg)) { groups_remove_member($reg->groupid, $userid); } // Trigger the event! \mod_grouptool\event\registration_deleted::create_direct($this->cm, $reg); // Let other queued be promoted to registered status! $this->fill_from_queue($reg->agrpid); } } else if (count($userqueues) == 1) { // Delete his queue! $queues = $DB->get_records_sql("SELECT queued.*, agrp.groupid FROM {grouptool_queued} queued JOIN {grouptool_agrps} agrp ON agrp.id = queued.agrpid WHERE userid = ? AND agrpid ".$agrpsql, $params); $DB->delete_records_select('grouptool_queued', "userid = ? AND agrpid ".$agrpsql, $params); foreach ($queues as $cur) { // Trigger the event! \mod_grouptool\event\queue_entry_deleted::create_direct($this->cm, $cur); // Let other queued be promoted to registered status! $this->fill_from_queue($cur->agrpid); } } else if (count($userregs) == 1) { $oldgrp = $DB->get_field_sql("SELECT agrp.groupid FROM {grouptool_registered} reg JOIN {grouptool_agrps} agrp ON agrp.id = reg.agrpid WHERE reg.userid = ? AND reg.agrpid ".$agrpsql, $params, MUST_EXIST); $reg = $DB->get_record_select('grouptool_registered', "userid = ? AND agrpid ".$agrpsql, $params, '*', MUST_EXIST); $DB->delete_records_select('grouptool_registered', "userid = ? AND agrpid ".$agrpsql, $params); if (!empty($oldgrp) && !empty($this->grouptool->immediate_reg)) { groups_remove_member($oldgrp, $userid); } // Trigger the event! $reg->groupid = $oldgrp; \mod_grouptool\event\registration_deleted::create_direct($this->cm, $reg); // Let other queued be promoted to registered status! $this->fill_from_queue($reg->agrpid); } else { throw new \mod_grouptool\local\exception\registration(get_string('groupchange_from_non_unique_reg', 'grouptool')); } // Register him in the new group! try { // First we try to register the user! $return = $this->add_registration($agrpid, $userid, $message); // If we can register, we have to convert the other marks to registrations & queue entries! $this->convert_marks_to_regs($userid); return $return; } catch (\mod_grouptool\local\exception\exceedgroupsize $e) { if (!$this->grouptool->use_queue) { // Shortcut: throw the exception again, if we don't use queues! throw $e; } // There's no place left in the group, so we try to queue the user! $return = $this->add_queue_entry($agrpid, $userid, $message); // If we can queue, we have to convert the other marks to registrations & queue entries! $this->convert_marks_to_regs($userid); return $return; } } /** * Checks if user has to many, too less registrations and return values! * * @param int $userid User's ID * @param bool $change (optional) true if check is used for group change! * @return array $userregs, $userqueues, $marks, $min, $max * @throws \mod_grouptool\local\exception\exceeduserreglimit * @throws \mod_grouptool\local\exception\registration * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ protected function check_users_regs_limits($userid, $change=false) { global $DB; // We have to filter only active groups to ensure no problems counting userregs and -queues. $agrpids = $DB->get_fieldset_select('grouptool_agrps', 'id', "grouptoolid = ? AND active = 1", [$this->grouptool->id]); list($agrpsql, $params) = $DB->get_in_or_equal($agrpids); array_unshift($params, $userid); $userregs = $DB->count_records_select('grouptool_registered', "modified_by >= 0 AND userid = ? AND agrpid ".$agrpsql, $params); $userqueues = $DB->count_records_select('grouptool_queued', "userid = ? AND agrpid ".$agrpsql, $params); $marks = $this->count_user_marks($userid); $max = $this->grouptool->allow_multiple ? $this->grouptool->choose_max : 1; $min = $this->grouptool->allow_multiple ? $this->grouptool->choose_min : 0; if ($change) { if ($min > ($marks + $userregs + $userqueues)) { throw new \mod_grouptool\local\exception\registration('too_many_registrations'); } if ($max < ($marks + $userregs + $userqueues)) { throw new \mod_grouptool\local\exception\exceeduserreglimit(); } } else { if ($min <= ($marks + $userregs + $userqueues)) { throw new \mod_grouptool\local\exception\registration('too_many_registrations'); } if ($max <= ($marks + $userregs + $userqueues)) { throw new \mod_grouptool\local\exception\exceeduserreglimit(); } } return [$userregs, $userqueues, $marks, $min, $max]; } /** * Check if user can be marked for registration, else throw exception! * * @param int $agrpid ID of the active group * @param int $userid ID of user to queue or null (then $USER->id is used) * @param stdClass $message cached data for the language strings * @return array (queued, string) status message * @throws \mod_grouptool\local\exception\exceeduserreglimit * @throws \mod_grouptool\local\exception\registration * @throws \mod_grouptool\local\exception\regpresent * @throws \mod_grouptool\local\exception\exceedgroupsize * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ protected function can_be_marked($agrpid, $userid, $message) { global $USER; $groupdata = $this->get_active_groups(true, true, $agrpid); if (count($groupdata) != 1) { throw new \mod_grouptool\local\exception\registration('error_getting_data'); } $groupdata = reset($groupdata); $full = !empty($this->grouptool->groups_queues_limit) && (count($groupdata->queued) >= $this->grouptool->groups_queues_limit); if ($this->grouptool->use_size && (count($groupdata->registered) >= $groupdata->grpsize) && (!$this->grouptool->use_queue || $full)) { throw new \mod_grouptool\local\exception\exceedgroupsize(); } $this->check_reg_present($agrpid, $userid, $groupdata, $message); $this->check_users_regs_limits($userid); if ($this->grouptool->use_size && (count($groupdata->registered) >= $groupdata->grpsize)) { if ($userid != $USER->id) { return [1, get_string('queue_in_group', 'grouptool', $message)]; } else { return [1, get_string('queue_you_in_group', 'grouptool', $message)]; } } else { if ($userid != $USER->id) { return [0, get_string('register_in_group', 'grouptool', $message)]; } else { return [0, get_string('register_you_in_group', 'grouptool', $message)]; } } } /** * Allocates a place in the group. Used in case there are not enough registrations by now. * * @param int $agrpid ID of active group to mark registration for. * @param int $userid (optional) ID of user to mark registration for or null ($USER->id is used). * @param stdClass $message (optional) prepared message object containing username and groupname or null. * @return string success message * @throws \mod_grouptool\local\exception\exceeduserreglimit * @throws \mod_grouptool\local\exception\registration * @throws \mod_grouptool\local\exception\regpresent * @throws \mod_grouptool\local\exception\exceedgroupsize * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ protected function mark_for_reg($agrpid, $userid, $message) { global $DB, $USER; $groupdata = $this->get_active_groups(false, false, $agrpid); if (count($groupdata) != 1) { throw new \mod_grouptool\local\exception\registration('error_getting_data'); } $this->can_be_marked($agrpid, $userid, $message); $record = new stdClass(); $record->agrpid = $agrpid; $record->userid = $userid; $record->timestamp = time(); $record->modified_by = -1; $DB->insert_record('grouptool_registered', $record); if ($userid != $USER->id) { return get_string('place_allocated_in_group_success', 'grouptool', $message); } else { return get_string('your_place_allocated_in_group_success', 'grouptool', $message); } } /** * Silently converts all of user's marks to registrations and queue entries or throws exception! * * @param int $userid (optional) ID of user to mark registration for or null ($USER->id is used). * @throws \mod_grouptool\local\exception\exceeduserqueuelimit * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ protected function convert_marks_to_regs($userid) { global $DB, $USER; // Get user's marks! $usermarks = $this->get_user_marks($userid); $queues = 0; foreach ($usermarks as $cur) { if ($cur->type != 'reg') { $queues++; } } if (!empty($this->grouptool->users_queues_limit) && ($queues > $this->grouptool->users_queues_limit)) { throw new \mod_grouptool\local\exception\exceeduserqueuelimit(); } foreach ($usermarks as $cur) { if ($cur->type == 'reg') { unset($cur->type); $cur->modified_by = $USER->id; $DB->update_record('grouptool_registered', $cur); if ($this->grouptool->immediate_reg) { groups_add_member($cur->groupid, $userid); } } else { unset($cur->type); $DB->insert_record('grouptool_queued', $cur); $DB->delete_records('grouptool_registered', ['id' => $cur->id]); } } $this->delete_user_marks($userid); } /** * Check if user can be queued, else throw exception! * * @param int $agrpid ID of the active group * @param int $userid (optional) ID of user to queue or null (then $USER->id is used) * @param stdClass $message (optional) prepared message object containing username and groupname or null * @return string status message * @throws \mod_grouptool\local\exception\exceedgroupqueuelimit * @throws \mod_grouptool\local\exception\exceeduserqueuelimit * @throws \mod_grouptool\local\exception\exceeduserreglimit * @throws \mod_grouptool\local\exception\exceedgroupsize * @throws \mod_grouptool\local\exception\notenoughregs * @throws \mod_grouptool\local\exception\registration * @throws \mod_grouptool\local\exception\regpresent * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ protected function can_be_queued($agrpid, $userid = null, $message = null) { global $USER, $DB; // Shortcut if we don't use queues! if (!$this->grouptool->use_queue) { throw new \mod_grouptool\local\exception\exceedgroupsize(); } if ($userid === null) { $userid = $USER->id; } $groupdata = $this->get_active_groups(true, true, $agrpid); if (count($groupdata) != 1) { throw new \mod_grouptool\local\exception\registration('error_getting_data'); } $groupdata = reset($groupdata); if ($message === null) { $message = new stdClass(); if ($userid != $USER->id) { $userdata = $DB->get_record('user', ['id' => $userid]); $message->username = fullname($userdata); } else { $message->username = fullname($USER); } $message->groupname = $groupdata->name; } /* Get user's marks and also check if enough (queue) places are available, * otherwise display an info and remove marked entry. */ $usermarks = $this->get_user_marks($userid); $queues = $this->get_user_queues_count($userid); $queueswithmarks = $queues; foreach ($usermarks as $cur) { if ($cur->type != 'reg') { $queueswithmarks++; } } if ($this->grouptool->users_queues_limit && (($queueswithmarks > $this->grouptool->users_queues_limit) || ($queues >= $this->grouptool->users_queues_limit))) { throw new \mod_grouptool\local\exception\exceeduserqueuelimit(); } if ($this->grouptool->groups_queues_limit && (count($groupdata->queued) >= $this->grouptool->groups_queues_limit)) { throw new \mod_grouptool\local\exception\exceedgroupqueuelimit(); } $this->check_reg_present($agrpid, $userid, $groupdata, $message); // We have to filter only active groups to ensure no problems counting userregs and -queues. $userregs = $this->get_user_reg_count($userid); $marks = $this->count_user_marks($userid); $max = $this->grouptool->allow_multiple ? $this->grouptool->choose_max : 1; $min = $this->grouptool->allow_multiple ? $this->grouptool->choose_min : 0; if ($max <= ($marks + $userregs + $queues)) { throw new \mod_grouptool\local\exception\exceeduserreglimit(); } if ($min > ($marks + $userregs + $queues + 1)) { // Not enough registrations/queues/marks! throw new \mod_grouptool\local\exception\notenoughregs(); } if ($userid != $USER->id) { return get_string('queue_in_group', 'grouptool', $message); } else { return get_string('queue_you_in_group', 'grouptool', $message); } } /** * Add a queue entry for a certain user/agrp-combination. * * @param int $agrpid ID of the active group * @param int $userid ID of user to queue or null (then $USER->id is used) * @param stdClass $message prepared message object containing username and groupname or null * @return string status string * @throws \mod_grouptool\local\exception\exceedgroupqueuelimit * @throws \mod_grouptool\local\exception\exceeduserqueuelimit * @throws \mod_grouptool\local\exception\exceeduserreglimit * @throws \mod_grouptool\local\exception\exceedgroupsize * @throws \mod_grouptool\local\exception\notenoughregs * @throws \mod_grouptool\local\exception\registration * @throws \mod_grouptool\local\exception\regpresent * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ protected function add_queue_entry($agrpid, $userid, $message) { global $DB, $USER; $groupdata = $this->get_active_groups(false, false, $agrpid); if (count($groupdata) != 1) { throw new \mod_grouptool\local\exception\registration('error_getting_data'); } $groupdata = reset($groupdata); // This method throws exceptions, if user is not able to be queued! $this->can_be_queued($agrpid, $userid, $message); $record = new stdClass(); $record->agrpid = $agrpid; $record->userid = $userid; $record->timestamp = time(); $record->id = $DB->insert_record('grouptool_queued', $record); // Trigger the event! $record->groupid = $groupdata->id; \mod_grouptool\event\queue_entry_created::create_direct($this->cm, $record)->trigger(); if ($userid != $USER->id) { return get_string('queue_in_group_success', 'grouptool', $message); } else { return get_string('queue_you_in_group_success', 'grouptool', $message); } } /** * * Checks if a given count of userregs, queues and marks matches the limits for a given group * * @param stdClass $group Group which should be checked against the counts * @param int $userregs Count of group registrations of a user * @param int $queues Count of queue registrations of a user * @param int $marks Count of marks (inactive registrations) of a user * @throws \mod_grouptool\local\exception\exceedgroupsize * @throws \mod_grouptool\local\exception\exceeduserreglimit * @throws \mod_grouptool\local\exception\notenoughregs */ protected function check_can_be_registered($group, $userregs, $queues, $marks) { $max = $this->grouptool->allow_multiple ? $this->grouptool->choose_max : 1; $min = $this->grouptool->allow_multiple ? $this->grouptool->choose_min : 0; if ($this->grouptool->use_size && (count($group->registered) >= $group->grpsize)) { throw new \mod_grouptool\local\exception\exceedgroupsize(); } if ($max <= ($marks + $userregs + $queues)) { throw new \mod_grouptool\local\exception\exceeduserreglimit(); } if ($min > ($marks + $userregs + $queues + 1)) { // Not enough registrations/queues/marks! throw new \mod_grouptool\local\exception\notenoughregs(); } } /** * Checks if user can be registered, else throw exception! * * @param int $agrpid ID of the active group * @param int $userid ID of user to queue or null (then $USER->id is used) * @param stdClass $message prepared message object containing username and groupname or null * @return string status message * @throws \mod_grouptool\local\exception\exceedgroupsize * @throws \mod_grouptool\local\exception\exceeduserreglimit * @throws \mod_grouptool\local\exception\notenoughregs * @throws \mod_grouptool\local\exception\registration * @throws \mod_grouptool\local\exception\regpresent * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ protected function can_be_registered($agrpid, $userid, $message) { global $USER; if ($userid === null) { $userid = $USER->id; } $groupdata = $this->get_active_groups(true, true, $agrpid); if (count($groupdata) != 1) { throw new \mod_grouptool\local\exception\registration('error_getting_data'); } $groupdata = reset($groupdata); $this->check_reg_present($agrpid, $userid, $groupdata, $message); // Check if enough (queue) places are available, otherwise display an info and remove marked entry. $userregs = $this->get_user_reg_count($userid); $queues = $this->get_user_queues_count($userid); $marks = $this->count_user_marks($userid); $this->check_can_be_registered($groupdata, $userregs, $queues, $marks); if ($userid != $USER->id) { return get_string('register_in_group', 'grouptool', $message); } else { return get_string('register_you_in_group', 'grouptool', $message); } } /** * Add a registration for a certain user/agrp-combination. * * @param int $agrpid ID of the active group * @param int $userid (optional) ID of user to register or null (then $USER->id is used) * @param stdClass $message (optional) prepared message object containing username and groupname or null * @return string status message * @throws \mod_grouptool\local\exception\exceedgroupsize * @throws \mod_grouptool\local\exception\exceeduserreglimit * @throws \mod_grouptool\local\exception\notenoughregs * @throws \mod_grouptool\local\exception\registration * @throws \mod_grouptool\local\exception\regpresent * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ protected function add_registration($agrpid, $userid, $message) { global $DB, $USER; $groupdata = $this->get_active_groups(false, false, $agrpid); if (count($groupdata) != 1) { throw new \mod_grouptool\local\exception\registration('error_getting_data'); } $groupdata = reset($groupdata); /* This method throws exceptions if there is a problem */ $this->can_be_registered($agrpid, $userid, $message); $record = new stdClass(); $record->agrpid = $agrpid; $record->userid = $userid; $record->timestamp = time(); $record->modified_by = $USER->id; $record->id = $DB->insert_record('grouptool_registered', $record); if ($this->grouptool->immediate_reg) { groups_add_member($groupdata->id, $userid); } // Trigger the event! $record->groupid = $groupdata->id; \mod_grouptool\event\registration_created::create_direct($this->cm, $record)->trigger(); if ($userid != $USER->id) { return get_string('register_in_group_success', 'grouptool', $message); } else { return get_string('register_you_in_group_success', 'grouptool', $message); } } /** * returns number of queue-entries for a particular user in a particular grouptool-instance * * @param int $userid optional user for whom stats should be obtained? uses $USER->id if zero * @return int count of queues in specified instance for specified user * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ protected function get_user_queues_count($userid=0) { global $DB, $USER; if (empty($userid)) { $userid = $USER->id; } $agrps = $this->get_active_groups(); $keys = []; foreach ($agrps as $current) { $keys[] = $current->agrpid; } if (count($keys) == 0) { return 0; } list($sql, $params) = $DB->get_in_or_equal($keys); $params = array_merge([$userid], $params); return $DB->count_records_sql('SELECT count(id) FROM {grouptool_queued} WHERE userid = ? AND agrpid '.$sql, $params); } /** * returns number of reg-entries for a particular user in a particular grouptool-instance * * @param int $userid optional user for whom stats should be obtained? uses $USER->id if zero * @return int count of queues in specified instance for specified user * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ protected function get_user_reg_count($userid=0) { global $DB, $USER; if (empty($userid)) { $userid = $USER->id; } $agrps = $this->get_active_groups(); $keys = []; foreach ($agrps as $current) { $keys[] = $current->agrpid; } if (count($keys) == 0) { return 0; } list($sql, $params) = $DB->get_in_or_equal($keys); $params = array_merge([$userid], $params); return $DB->count_records_sql('SELECT count(id) FROM {grouptool_registered} WHERE modified_by >= 0 AND userid = ? AND agrpid '.$sql, $params); } /** * checks the found userdata, and return error rows if no user was found or multiple were fund * @param array $userinfo data that was found * @param array $user the data given by the user * @param array $importfields the fields which were checked * @return array rows for the table, possibly empty if exactly one user was found * @throws coding_exception */ private function check_userinfo($userinfo, $user, $importfields) { global $OUTPUT; $errorrows = []; if (empty($userinfo)) { $errorrows[0] = new html_table_row(); $errorrows[0]->cells[] = new html_table_cell($OUTPUT->notification( get_string('user_not_found', 'grouptool', $user), \core\output\notification::NOTIFY_ERROR)); } else if (count($userinfo) > 1) { foreach ($this->generate_multiple_users_table($userinfo, $importfields) as $tmprow) { $errorrows[] = $tmprow; } } return $errorrows; } /** * Searches users based on the information given and the fields to consider * @param array $importfields the fields to check * @param array $user the data for thse fields * @return array the found user/s * @throws dml_exception */ private function find_userinfo($importfields, $user) { global $DB; $userinfo = []; foreach ($importfields as $field) { $sql = 'SELECT * FROM {user} WHERE '.$DB->sql_like($field, ':userpattern'); $sql .= ' AND deleted = 0'; $param = ['userpattern' => $user]; $userinfo = $DB->get_records_sql($sql, $param); if (empty($userinfo)) { $param['userpattern'] = '%'.$user; $userinfo = $DB->get_records_sql($sql, $param); } else if (count($userinfo) == 1) { break; } if (empty($userinfo)) { $param['userpattern'] = $user.'%'; $userinfo = $DB->get_records_sql($sql, $param); } else if (count($userinfo) == 1) { break; } if (empty($userinfo)) { $param['userpattern'] = '%'.$user.'%'; $userinfo = $DB->get_records_sql($sql, $param); } else if (count($userinfo) == 1) { break; } if (!empty($userinfo) && count($userinfo) == 1) { break; } } return $userinfo; } /** * Generates the table with information about the users that were found multiple times * @param array $userinfo the users which were found * @param array $importfields the based on which those users were found * @return array table rows * @throws coding_exception */ private function generate_multiple_users_table($userinfo, $importfields) { global $OUTPUT; $tmprows = []; foreach ($userinfo as $currentuser) { $tmprow = new html_table_row(); $tmprow->cells = []; $tmprow->cells[] = new html_table_cell(fullname($currentuser)); foreach ($importfields as $curfield) { $tmprow->cells[] = new html_table_cell($currentuser->$curfield); } $tmprows[] = $tmprow; } $curkey = count($tmprows[0]->cells); $tmprows[0]->cells[$curkey] = new html_table_cell($OUTPUT->notification(get_string('found_multiple', 'grouptool'), \core\output\notification::NOTIFY_ERROR)); $tmprows[0]->cells[$curkey]->rowspan = count($tmprows); return $tmprows; } /** * Unregisters users from groups according to the passed parameters * * @param array $groups the groups from which to unreg. * @param string $data data that identifies the users * @param bool $unregfrommgroups also unreg. from moodle groups * @param bool $previewonly only preview * @return array * @throws \mod_grouptool\local\exception\notenoughregs * @throws \mod_grouptool\local\exception\registration * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ public function unregister($groups, $data, $unregfrommgroups = true, $previewonly = false) { global $DB, $OUTPUT; $message = ""; $error = false; $users = preg_split("/[ ,;\t\n\r]+/", $data); // Prevent selection of all users if one of the above defined characters are in the beginning! foreach ($users as $key => $user) { if (empty($user)) { unset($users[$key]); } } $groupinfo = []; foreach ($groups as $group) { $groupinfo[$group] = groups_get_group($group); } $unregistered = []; $agrp = []; $groupname = []; foreach ($groups as $group) { $agrp[$group] = $DB->get_field('grouptool_agrps', 'id', [ 'grouptoolid' => $this->grouptool->id, 'groupid' => $group ], IGNORE_MISSING); $groupname[$group] = $DB->get_field('groups', 'name', [ 'id' => $group ], IGNORE_MISSING); if (!$DB->record_exists('grouptool_agrps', [ 'grouptoolid' => $this->grouptool->id, 'groupid' => $group, 'active' => 1 ])) { $message .= $OUTPUT->notification(get_string('unregister_in_inactive_group_warning', 'grouptool', $groupname[$group]), \core\output\notification::NOTIFY_ERROR); } } if (false !== ($gtimportfields = get_config('mod_grouptool', 'importfields'))) { $importfields = explode(',', $gtimportfields); } else { $importfields = ['username', 'idnumber']; } $prevtable = new html_table(); $prevtable->attributes['class'] = 'importpreview table table-striped table-hover'; $prevtable->id = 'unregisterpreview'; $prevtable->head = [get_string('fullname')]; foreach ($importfields as $field) { $prevtable->head[] = get_string($field); } $prevtable->head[] = get_string('status'); $prevtable->data = []; $pbar = new progress_bar('unregisterprogress', 500, true); $count = count($users); $processed = 0; $pbar->update($processed, $count, get_string('unregister_progress_start', 'grouptool')); core_php_time_limit::raise(count($users) * 5); raise_memory_limit(MEMORY_HUGE); $followchangessetting = $DB->get_field('grouptool', 'ifmemberremoved', array('id' => $this->grouptool->id)); foreach ($users as $user) { $userinfo = $this->find_userinfo($importfields, $user); $pbar->update($processed, $count, get_string('import_progress_search', 'grouptool').' '.$user); $row = new html_table_row(); $errors = 0; foreach ($this->check_userinfo($userinfo, $user, $importfields) as $errorrow) { $prevtable->data[] = $errorrow; $errors++; $error = true; } if ($errors == 0) { $userinfo = reset($userinfo); $row->cells = [new html_table_cell(fullname($userinfo))]; foreach ($importfields as $curfield) { $row->cells[] = new html_table_cell(empty($userinfo->$curfield) ? '' : $userinfo->$curfield); } if (!is_enrolled($this->context, $userinfo->id)) { $userinfo->fullname = fullname($userinfo); if (empty($userinfo->deleted)) { $text = get_string('user_is_not_enrolled', 'grouptool', $userinfo); $row->cells[] = new html_table_cell($OUTPUT->notification($text, \core\output\notification::NOTIFY_ERROR)); } else { $text = get_string('user_is_deleted', 'grouptool', $userinfo); $row->cells[] = new html_table_cell($OUTPUT->notification($text, \core\output\notification::NOTIFY_ERROR)); } $error = true; continue; } foreach ($groups as $group) { $data = [ 'id' => $userinfo->id, 'idnumber' => $userinfo->idnumber, 'fullname' => fullname($userinfo), 'groupname' => $groupname[$group] ]; if (!$previewonly && $userinfo) { $pbar->update($processed, $count, get_string('unregister_progress_unregister', 'grouptool').' '.fullname($userinfo).'...'); if (!$DB->record_exists('grouptool_registered', [ 'agrpid' => $agrp[$group], 'userid' => $data['id'] ]) && !$DB->record_exists('grouptool_queued', [ 'agrpid' => $agrp[$group], 'userid' => $data['id'] ])) { $row->cells[] = get_string('unregister_user_not_in_group', 'grouptool', $data); $row->attributes['class'] = 'success'; continue; } if ($followchangessetting && $DB->record_exists('groups_members', [ 'groupid' => $group, 'userid' => $data['id'] ])) { $DB->delete_records('groups_members', [ 'groupid' => $group, 'userid' => $data['id'] ]); $time = time(); $DB->set_field('groups', 'timemodified', $time, array('id' => $group)); cache_helper::invalidate_by_definition('core', 'user_group_groupings', array(), array($data['id'])); $context = context_course::instance($this->grouptool->course); if ($conversation = \core_message\api::get_conversation_by_area('core_group', 'groups', $group, $context->id)) { \core_message\api::remove_members_from_conversation([$data['id']], $conversation->id); } } if ($unregfrommgroups) { $this->unregister_from_agrp($agrp[$group], $userinfo->id, false, true); } $unregistered[] = $userinfo->id; $row->cells[] = get_string('unregister_user', 'grouptool', $data); $row->attributes['class'] = 'success'; } else if ($userinfo) { if (!$DB->record_exists_select('grouptool_registered', "agrpid = :agrpid AND userid = :userid", ['agrpid' => $agrp[$group], 'userid' => $userinfo->id])) { $cell = get_string('unregister_conflict_user_not_in_group', 'grouptool', $data); $row->cells[] = $cell; $row->attributes['class'] = 'prevconflict'; } else { $row->cells[] = get_string('unregister_user_prev', 'grouptool', $data); $row->attributes['class'] = 'prevsuccess'; } } } } $prevtable->data[] = $row; unset($row); $processed++; } $processed++; if (!$previewonly) { $pbar->update($processed, $count, get_string('unregister_progress_completed', 'grouptool')); } else { $pbar->update($processed, $count, get_string('unregister_progress_preview_completed', 'grouptool')); } $message .= html_writer::table($prevtable); return [$error, $message]; } /** * helperfunction compares to objects using a particular timestamp-property * * @param stdClass $a object containing timestamp property * @param stdClass $b object containing timestamp property * @return int 0 if equal, +1 if $a->timestamp > $b->timestamp or -1 if otherwise */ private function cmptimestamp($a, $b) { if ($a->timestamp == $b->timestamp) { return 0; } else { return $a->timestamp > $b->timestamp ? 1 : -1; } } /** * returns rank in queue for a particular user * if $data is an array uses array (like queue/reg-info returned by {@see get_active_groups()}) * to determin rank otherwise if $data is an integer uses DB-query to get queue rank in * active group with id == $data * * @param int[]|int $data array with regs/queues for a group like returned by get_active_groups() or agrpid * @param int $userid user for whom data should be returned * @return int rank in queue/registration (registration only via $data-array) * @throws dml_exception */ private function get_rank_in_queue($data=0, $userid=0) { global $DB, $USER; if (is_array($data)) { // It's the queue itself! uasort($data, [$this, "cmptimestamp"]); $i = 1; foreach ($data as $entry) { if ($entry->userid == $userid) { return $i; } else { $i++; } } return false; } else if (!empty($data)) { // It's an active-group-id, so we gotta get the queue data! $params = [ 'agrpid' => $data, 'userid' => !empty($userid) ? $userid : $USER->id ]; $sql = "SELECT count(b.id) AS rank FROM {grouptool_queued} a INNER JOIN {grouptool_queued} b ON b.timestamp <= a.timestamp WHERE a.agrpid = :agrpid AND a.userid = :userid"; } else { return null; } return $DB->count_records_sql($sql, $params); } /** * returns object with information about registrations/queues for each group * (optional with userdata) * if $user == 0 no userdata is returned * else if $user == null data about $USERs registrations/queues is added * else data about $userids registrations/queues is added * * @param int $userid id of user for whom data should be added * or 0 (=$USER) or null (=no userdata) * @return stdClass object containing information about active groups * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ public function get_registration_stats($userid=null) { global $USER, $DB; $return = new stdClass(); $return->group_places = 0; $return->free_places = 0; $return->occupied_places = 0; $return->users = 0; $return->registered = []; $return->queued = []; $return->queued_users = 0; $return->reg_users = 0; switch ($userid) { case null: $userid = $USER->id; default: $groups = $this->get_active_groups(false, false); break; case 0: $groups = $this->get_active_groups(); break; } foreach ($groups as $group) { $group = $this->get_active_groups(true, true, $group->agrpid, $group->id); $group = current($group); if ($this->grouptool->use_size) { $return->group_places += $group->grpsize; } $return->occupied_places += count($group->registered); if ($userid != 0) { $regrank = $this->get_rank_in_queue($group->registered, $userid); if (!empty($regrank)) { $regdata = new stdClass(); $regdata->rank = $regrank; $regdata->grpname = $group->name; $regdata->agrpid = $group->agrpid; reset($group->registered); do { $current = current($group->registered); $regdata->timestamp = $current->timestamp; next($group->registered); } while ($current->userid != $userid); $regdata->id = $group->id; $return->registered[] = $regdata; } $queuerank = $this->get_rank_in_queue($group->queued, $userid); if (!empty($queuerank)) { $queuedata = new stdClass(); $queuedata->rank = $queuerank; $queuedata->grpname = $group->name; $queuedata->agrpid = $group->agrpid; reset($group->queued); do { $current = current($group->queued); $queuedata->timestamp = $current->timestamp; next($group->queued); } while ($current->userid != $userid); $queuedata->id = $group->id; $return->queued[] = $queuedata; } } } $return->free_places = ($this->grouptool->use_size) ? ($return->group_places - $return->occupied_places) : null; $return->users = count_enrolled_users($this->context, 'mod/grouptool:register'); $agrps = $DB->get_records('grouptool_agrps', ['grouptoolid' => $this->cm->instance, 'active' => 1]); if (is_array($agrps) && count($agrps) >= 1) { $agrpids = array_keys($agrps); list($inorequal, $params) = $DB->get_in_or_equal($agrpids); $sql = "SELECT count(DISTINCT userid) FROM {grouptool_registered} WHERE modified_by >= 0 AND agrpid ".$inorequal; $return->reg_users = $DB->count_records_sql($sql, $params); $sql = "SELECT count(DISTINCT userid) FROM {grouptool_queued} WHERE agrpid ".$inorequal; $return->queued_users = $DB->count_records_sql($sql, $params); } else { $return->reg_users = 0; } $return->notreg_users = $return->users - $return->reg_users; return $return; } /** * resolves queues by filling empty group places in defined order with students from the queue * * @todo there's a bug which prevents deletion of some queue entries, only happened on * development system with admin-users account several times, whatch out for the future * @todo sometimes not every queue entry is resolved, happened unregularly on development system * watch in production system * * @param bool $previewonly show only preview of actions * @return array ($error, $message) * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ public function resolve_queues($previewonly = false) { global $DB, $USER; $error = false; $returntext = ""; $status = []; // Trigger event! \mod_grouptool\event\dequeuing_started::create_from_object($this->cm)->trigger(); $grouptool = $this->grouptool; $context = $this->context; require_capability('mod/grouptool:register_students', $context); $agrps = $this->get_active_groups(false, false, 0, 0, 0, false); list($agrpsql, $params) = $DB->get_in_or_equal(array_keys($agrps), SQL_PARAMS_NAMED, 'reg'); list($agrpsql2, $params2) = $DB->get_in_or_equal(array_keys($agrps), SQL_PARAMS_NAMED, 'queue'); if (!empty($agrps)) { $agrpids = array_keys($agrps); list($agrpssql, $agrpsparam) = $DB->get_in_or_equal($agrpids); $agrpsfiltersql = " AND agrp.id ".$agrpssql; $agrpsfilterparams = array_merge([$grouptool->id], $agrpsparam); // Get queue-entries (sorted by timestamp)! if (!empty($grouptool->allow_multiple)) { $queuedsql = " WHERE queued.agrpid ".$agrpssql." "; $queuedparams = array_merge($agrpsparam, $agrpsparam); $queueentries = $DB->get_records_sql(" SELECT queued.id, MAX(queued.agrpid) AS agrpid, MAX(queued.userid) AS userid, MAX(queued.timestamp) AS timestamp, (COUNT(DISTINCT reg.id) < ?) AS priority FROM {grouptool_queued} queued LEFT JOIN {grouptool_registered} reg ON queued.userid = reg.userid AND reg.agrpid ".$agrpssql. " AND reg.modified_by >= 0 ".$queuedsql." GROUP BY queued.id ORDER BY priority DESC, queued.timestamp ASC", array_merge([$grouptool->choose_min], $queuedparams)); } else { $queuedsql = " WHERE queued.agrpid ".$agrpssql." "; $queuedparams = $agrpsparam; $queueentries = $DB->get_records_sql("SELECT *, '1' AS priority FROM {grouptool_queued} queued". $queuedsql. "ORDER BY timestamp ASC", $queuedparams); } $userregs = $DB->get_records_sql_menu('SELECT reg.userid, COUNT(DISTINCT reg.id) FROM {grouptool_registered} reg WHERE reg.agrpid '.$agrpssql.' AND modified_by >= 0 GROUP BY reg.userid', $agrpsparam); } else { return [true, get_string('no_active_groups', 'grouptool')]; } // Get group entries (sorted by sort-order)! $groupsdata = $DB->get_records_sql(" SELECT agrp.id AS id, MAX(agrp.groupid) AS groupid, MAX(agrp.grpsize) AS grpsize, COUNT(DISTINCT reg.id) AS registered FROM {grouptool_agrps} agrp LEFT JOIN {grouptool_registered} reg ON reg.agrpid = agrp.id AND modified_by >= 0 WHERE agrp.grouptoolid = ?".$agrpsfiltersql." GROUP BY agrp.id ORDER BY agrp.sort_order ASC", $agrpsfilterparams); $i = 0; if (!empty($groupsdata) && !empty($queueentries)) { $fullnames = $DB->get_records_sql_menu("SELECT DISTINCT u.id, ".$DB->sql_fullname()." FROM {user} u LEFT JOIN {grouptool_queued} q ON q.userid = u.id AND q.agrpid ".$agrpsql2." LEFT JOIN {grouptool_registered} r ON r.userid = u.id AND r.agrpid ".$agrpsql." WHERE (r.id IS NOT NULL OR q.id IS NOT NULL)", $params + $params2); $planned = new stdClass(); $curgroup = null; $maxregs = !empty($this->grouptool->allow_multiple) ? $this->grouptool->choose_max : 1; reset($groupsdata); $message = new stdClass(); foreach ($queueentries as $queue) { // Get first non-full group! while (($curgroup == null) || ($curgroup->grpsize <= $curgroup->registered)) { if ($curgroup === null) { $curgroup = current($groupsdata); } else { $curgroup = next($groupsdata); } if ($curgroup === false) { $error = true; $username = $DB->get_field('user', $DB->sql_fullname('firstname', 'lastname'), ['id' => $queue->userid]); $returntext .= html_writer::tag('div', get_string('all_groups_full', 'grouptool', $username), ['class' => 'error']); return [$error, $returntext]; } else { $tmpuseindividual = !empty($curgroup->grpsize); $curgroup->grpsize = $tmpuseindividual ? $curgroup->grpsize : $grouptool->grpsize; unset($tmpuseindividual); } } if (!isset($planned->{$queue->userid})) { $planned->{$queue->userid} = []; } // If user has got too many regs allready! if (!empty($userregs[$queue->userid]) && ($userregs[$queue->userid] >= $maxregs)) { $returntext .= html_writer::tag('div', get_string('too_many_regs', 'grouptool'), ['class' => 'error']); $error = true; // Continue with next user/queue-entry! continue; } while ($DB->record_exists('grouptool_registered', [ 'agrpid' => $curgroup->id, 'userid' => $queue->userid ]) || in_array($curgroup->id, $planned->{$queue->userid}) || $curgroup->registered >= $curgroup->grpsize) { $curgroup = next($groupsdata); $i++; if ($curgroup === false) { break; // No group left for this user! } } if ($curgroup !== false) { // Register him or mark as planed! if ($previewonly) { // Move user and get feedback! $curerror = 0; try { if ($queue->userid != $USER->id) { $userdata = $DB->get_record('user', ['id' => $queue->userid]); $message->username = fullname($userdata); } else { $message->username = fullname($USER); } $message->groupname = groups_get_group_name($curgroup->groupid); $curtext = $this->can_change_group($curgroup->id, $queue->userid, $message, $queue->agrpid); } catch (\mod_grouptool\local\exception\registration $e) { $curerror = 1; $curtext = $e->getMessage(); } if (!$curerror) { $planned->{$queue->userid}[] = $curgroup->id; } $class = $curerror ? 'error' : 'success'; $data = new stdClass(); $data->userid = $queue->userid; $data->user = $fullnames[$queue->userid]; $data->agrpid = $queue->agrpid; $data->to_group = groups_get_group_name($curgroup->groupid); $data->from_group = groups_get_group_name($groupsdata[$queue->agrpid]->groupid); $data->current_text = $curtext; $movetext = get_string('user_move_prev', 'grouptool', $data); $returntext .= html_writer::tag('div', $movetext, ['class' => $class]); if (!isset($status[$queue->userid])) { $status[$queue->userid] = new stdClass(); } $status[$queue->userid]->error = $curerror; $error = $error || $curerror; $curgroup->registered++; } else { // Move user and get feedback! $curerror = 0; try { $curtext = $this->change_group($curgroup->id, $queue->userid, null, $queue->agrpid); } catch (\mod_grouptool\local\exception\registration $e) { $curerror = 1; $curtext = $e->getMessage(); } $class = $curerror ? 'error' : 'success'; $data = new stdClass(); $data->userid = $queue->userid; $data->user = $fullnames[$queue->userid]; $data->agrpid = $queue->agrpid; $data->to_group = groups_get_group_name($curgroup->groupid); $data->from_group = groups_get_group_name($groupsdata[$queue->agrpid]->groupid); $data->current_text = $curtext; $movedtext = get_string('user_moved', 'grouptool', $data); $returntext .= html_writer::tag('div', $movedtext, ['class' => $class]); $curgroup->registered++; $error = $error || $curerror; $attr = [ 'id' => $queue->id, 'userid' => $queue->userid, 'agrpid' => $queue->agrpid ]; // Delete queue entry if successfull or print message! $DB->delete_records('grouptool_queued', $attr); // Log user moved! $queue->groupid = $DB->get_field('grouptool_agrps', 'groupid', ['id' => $queue->agrpid], MUST_EXIST); $to = new stdClass(); $to->agrpid = $curgroup->id; $to->userid = $queue->userid; $to->groupid = $DB->get_field('grouptool_agrps', 'groupid', ['id' => $curgroup->id], MUST_EXIST); $to->id = $DB->get_field('grouptool_registered', 'id', [ 'agrpid' => $to->agrpid, 'userid' => $to->userid ], MUST_EXIST); \mod_grouptool\event\user_moved::move($this->cm, $queue, $to)->trigger(); if ($DB->record_exists('grouptool_queued', $attr)) { $returntext .= "Could not delete!"; } } } while ($i !== 0) { $curgroup = prev($groupsdata); $i--; } } } if (empty($returntext)) { $returntext = get_string('no_queues_to_resolve', 'grouptool'); $error = false; } return [$error, $returntext]; } /** * Return all marks for the specified user * * The marks are the registation entries before they become active * (i.e. if not enough groups have been chosen). * * @param int $userid (optional) User-ID for which the marks should be returned * @return stdClass[] Users marks * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ public function get_user_marks($userid=0) { global $DB, $USER, $OUTPUT; if (empty($userid)) { $userid = $USER->id; } $agrps = $DB->get_fieldset_select('grouptool_agrps', 'id', 'grouptoolid = ?', [$this->cm->instance]); if (empty($agrps)) { return null; } list($agrpssql, $params) = $DB->get_in_or_equal($agrps); $params[] = $userid; $sql = 'SELECT reg.id, reg.agrpid, reg.userid, reg.timestamp, agrp.groupid FROM {grouptool_registered} reg JOIN {grouptool_agrps} agrp ON reg.agrpid = agrp.id WHERE reg.agrpid '.$agrpssql.' AND modified_by = -1 AND userid = ?'; $marks = $DB->get_records_sql($sql, $params); foreach ($marks as $id => $cur) { $groupdata = $this->get_active_groups(true, true, $cur->agrpid); $groupdata = current($groupdata); if ($this->grouptool->use_size) { $notfull = empty($this->grouptool->groups_queues_limit) || (count($groupdata->queued) < $this->grouptool->groups_queues_limit); if (count($groupdata->registered) < $groupdata->grpsize) { $marks[$id]->type = 'reg'; } else if ($this->grouptool->use_queue && $notfull) { $marks[$id]->type = 'queue'; } else { // Place occupied in the meanwhile, must look for another group! $info = new stdClass(); $info->grpname = groups_get_group_name($cur->groupid); $info->userid = $userid; echo $OUTPUT->notification(get_string('already_occupied', 'grouptool', $info), \core\output\notification::NOTIFY_ERROR); $DB->delete_records('grouptool_registered', ['id' => $id]); unset($marks[$id]); } } else { $marks[$id]->type = 'reg'; } } return $marks; } /** * Delete users marks * * @param int $userid (optional) User for whom the marks should be deleted * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ public function delete_user_marks($userid=0) { global $DB; $marks = $this->get_user_marks($userid); if (is_array($marks) && count($marks) > 0) { list($select, $params) = $DB->get_in_or_equal(array_keys($marks)); $select = 'id '.$select; $DB->delete_records_select('grouptool_registered', $select, $params); } } /** * Count users marks * * @param int $userid (optional) User for whom the marks should be counted * @return int amount of users marks * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ public function count_user_marks($userid=0) { $marks = $this->get_user_marks($userid); if (empty($marks)) { return 0; } return count($marks); } /** * Return if a group is already marked by a user * * @param int $agrpid activegroup id which should be checked * @param int $userid (optional) User for whom the group should be checked * @return bool true if marked * @throws dml_exception */ public function grpmarked($agrpid, $userid=0) { global $DB, $USER; if (empty($userid)) { $userid = $USER->id; } return $DB->record_exists('grouptool_registered', [ 'agrpid' => $agrpid, 'userid' => $userid, 'modified_by' => -1 ]); } /** * Return true if the registration is open, false otherwise! * * @return bool true if reg is open, false otherwise */ public function is_registration_open() { return ($this->grouptool->allow_reg && (($this->grouptool->timedue == 0) || (time() < $this->grouptool->timedue)) && (time() > $this->grouptool->timeavailable)); } /** * Returns the amount of registrations missing in this grouptool instance. * * @return int amount of missing registrations (includes queues!) * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ public function get_missing_registrations() { global $DB; list($esql, $params) = get_enrolled_sql($this->context, 'mod/grouptool:register'); $sql = "SELECT u.id FROM {user} u LEFT JOIN ($esql) eu ON eu.id=u.id WHERE u.deleted = 0 AND eu.id=u.id "; $users = $DB->get_records_sql($sql, $params); if (empty($users)) { return 0; } list($usql, $uparams) = $DB->get_in_or_equal(array_keys($users), SQL_PARAMS_NAMED, 'usr'); $min = $this->grouptool->allow_multiple ? $this->grouptool->choose_min : 1; if ($min == 0) { return 0; } $agrps = $this->get_active_groups(false, false, 0, 0, 0, false); $keys = array_keys($agrps); if (empty($keys)) { $keys = [-1]; } list($agrpsql, $params) = $DB->get_in_or_equal($keys, SQL_PARAMS_NAMED, 'agrp'); $params = array_merge($uparams, $params); $regs = $DB->get_records_sql_menu("SELECT u.id, count(r.id) FROM {user} u LEFT JOIN {grouptool_registered} r ON u.id = r.userid AND r.modified_by >= 0 AND r.agrpid ".$agrpsql." WHERE u.id ".$usql." GROUP BY u.id", $params); $queues = $DB->get_records_sql_menu("SELECT u.id, count(q.id) FROM {user} u LEFT JOIN {grouptool_queued} q ON u.id = q.userid AND q.agrpid ".$agrpsql." WHERE u.id ".$usql." GROUP BY u.id", $params); $missing = 0; foreach ($users as $user) { $userregs = $regs[$user->id] + $queues[$user->id]; if ($userregs < $min) { $missing += $min - $userregs; } } return $missing; } /** * view selfregistration-tab * * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function view_selfregistration() { global $OUTPUT, $DB, $USER, $PAGE; // Include js for filters. $USER->ajax_updatable_user_prefs['mod_grouptool_hideoccupied'] = true; $params = new StdClass(); $prefhideoccupied = get_user_preferences('mod_grouptool_hideoccupied', false); if ($prefhideoccupied === 'true') { $params->filterunoccupied = true; } else { $params->filterunoccupied = false; } $PAGE->requires->js_call_amd('mod_grouptool/filter', 'init', [$params]); $userid = $USER->id; $regopen = $this->is_registration_open(); // Process submitted form! $error = false; if (data_submitted() && confirm_sesskey() && optional_param('confirm', 0, PARAM_BOOL)) { // Execution has been confirmed! $hideform = 0; $action = optional_param('action', 'reg', PARAM_ALPHA); $confirmmessage = ''; if ($action == 'unreg') { require_capability('mod/grouptool:register', $this->context); $agrpid = required_param('group', PARAM_INT); // Unregister user and get feedback! try { $confirmmessage = $this->unregister_from_agrp($agrpid, $USER->id); } catch (\mod_grouptool\local\exception\registration $e) { $error = true; $confirmmessage = $e->getMessage(); } } else if ($action == 'reg') { require_capability('mod/grouptool:register', $this->context); $agrpid = required_param('group', PARAM_INT); // Register user and get feedback! try { $confirmmessage = $this->register_in_agrp($agrpid, $USER->id); } catch (\mod_grouptool\local\exception\registration $e) { $error = true; $confirmmessage = $e->getMessage(); } } else if ($action == 'resolvequeues') { require_capability('mod/grouptool:register_students', $this->context); list($error, $confirmmessage) = $this->resolve_queues(); if ($error == -1) { $error = true; } } if ($error === true) { echo $OUTPUT->notification($confirmmessage, \core\output\notification::NOTIFY_ERROR); } else { echo $OUTPUT->notification($confirmmessage, \core\output\notification::NOTIFY_SUCCESS); } } else if (data_submitted() && confirm_sesskey()) { // Display confirm-dialog! $hideform = 1; $reg = optional_param_array('reg', null, PARAM_INT); $action = false; $agrpid = -1; if ($reg != null) { $agrpid = array_keys($reg); $agrpid = reset($agrpid); $action = 'reg'; } $unreg = optional_param_array('unreg', null, PARAM_INT); if ($unreg != null) { $agrpid = array_keys($unreg); $agrpid = reset($agrpid); $action = 'unreg'; } $resolvequeues = optional_param('resolve_queues', 0, PARAM_BOOL); if (!empty($resolvequeues)) { $action = 'resolvequeues'; } $attr = []; if ($action == 'resolvequeues') { require_capability('mod/grouptool:register_students', $this->context); list($error, $confirmmessage) = $this->resolve_queues(true); // Try only! } else if ($action == 'unreg') { require_capability('mod/grouptool:register', $this->context); $attr['group'] = $agrpid; // Try only! try { $confirmmessage = $this->unregister_from_agrp($agrpid, $USER->id, true); } catch (\mod_grouptool\local\exception\registration $e) { $error = 1; $confirmmessage = $e->getMessage(); } } else { require_capability('mod/grouptool:register', $this->context); $action = 'reg'; $attr['group'] = $agrpid; // Try only! try { $confirmmessage = $this->register_in_agrp($agrpid, $USER->id, true); } catch (\mod_grouptool\local\exception\registration $e) { $error = 1; $confirmmessage = $e->getMessage(); } } $attr['confirm'] = '1'; $attr['action'] = $action; $attr['sesskey'] = sesskey(); $continue = new moodle_url($PAGE->url, $attr); $cancel = new moodle_url($PAGE->url); if (($error === true) && ($action != 'resolvequeues')) { $continue->remove_params('confirm', 'group'); $continue = new single_button($continue, get_string('continue'), 'get'); $cancel = null; } echo $this->confirm($confirmmessage, $continue, $cancel); } else { $hideform = 0; } if (empty($hideform)) { /* * we need a new moodle_url-Object because * $PAGE->url->param('sesskey', sesskey()); * won't set sesskey param in $PAGE->url?!? */ $url = new moodle_url($PAGE->url, ['sesskey' => sesskey()]); $mform = new MoodleQuickForm('registration_form', 'post', $url, '', ['id' => 'registration_form']); $regstat = $this->get_registration_stats($USER->id); if (!empty($this->grouptool->timedue) && (time() >= $this->grouptool->timedue) && has_capability('mod/grouptool:register_students', $this->context)) { if ($regstat->queued_users > 0) { // Insert queue-resolving button! $mform->addElement('header', 'resolveheader', get_string('resolve_queue_legend', 'grouptool')); $mform->addElement('submit', 'resolve_queues', get_string('resolve_queue', 'grouptool')); } } if (has_capability('mod/grouptool:view_description', $this->context)) { $mform->addElement('header', 'generalinfo', get_string('general_information', 'grouptool')); $mform->setExpanded('generalinfo'); if (!empty($this->grouptool->use_size)) { $placestats = $regstat->group_places.' '.get_string('total', 'grouptool'); } else { $placestats = '∞ '.get_string('total', 'grouptool'); } if (($regstat->free_places != null) && !empty($this->grouptool->use_size)) { $placestats .= ' / '.$regstat->free_places.' '. get_string('free', 'grouptool'); } else { $placestats .= ' / ∞ '.get_string('free', 'grouptool'); } if ($regstat->occupied_places != null) { $placestats .= ' / '.$regstat->occupied_places.' '. get_string('occupied', 'grouptool'); } $mform->addElement('static', 'group_places', get_string('group_places', 'grouptool'), $placestats); $mform->addHelpButton('group_places', 'group_places', 'grouptool'); $mform->addElement('static', 'number_of_students', get_string('number_of_students', 'grouptool'), $regstat->users); if (($this->grouptool->allow_multiple && (count($regstat->registered) < $this->grouptool->choose_min)) || (!$this->grouptool->allow_multiple && !count($regstat->registered))) { if ($this->grouptool->allow_multiple) { $missing = ($this->grouptool->choose_min - count($regstat->registered)); $stringlabel = ($missing > 1) ? 'registrations_missing' : 'registration_missing'; } else { $missing = 1; $stringlabel = 'registration_missing'; } $missingtext = get_string($stringlabel, 'grouptool', $missing); } else { $missingtext = ""; } if (!empty($regstat->registered)) { $regscumulative = []; foreach ($regstat->registered as $registration) { $regscumulative[] = $registration->grpname.' ('.$registration->rank.')'; } $mform->addElement('static', 'registrations', get_string('registrations', 'grouptool'), html_writer::tag('div', $missingtext).implode(', ', $regscumulative)); } else { $mform->addElement('static', 'registrations', get_string('registrations', 'grouptool'), html_writer::tag('div', $missingtext).get_string('not_registered', 'grouptool')); } if (!empty($regstat->queued)) { $queuescumulative = []; foreach ($regstat->queued as $queue) { $queuescumulative[] = $queue->grpname.' ('.$queue->rank.')'; } $mform->addElement('static', 'queues', get_string('queues', 'grouptool'), implode(', ', $queuescumulative)); } if (!empty($this->grouptool->allow_reg)) { if (!empty($this->grouptool->timeavailable)) { $mform->addElement('static', 'availabledate', get_string('availabledate', 'grouptool'), userdate($this->grouptool->timeavailable, get_string('strftimedatetime'))); } if (!empty($this->grouptool->timedue)) { $textdue = userdate($this->grouptool->timedue, get_string('strftimedatetime')); } else { $textdue = get_string('noregistrationdue', 'grouptool'); } $mform->addElement('static', 'registrationdue', get_string('registrationdue', 'grouptool'), $textdue); if (!empty($this->grouptool->allow_unreg)) { $unregtext = get_string('allowed', 'grouptool'); } else { $unregtext = get_string('not_permitted', 'grouptool'); } $mform->addElement('static', 'unreg', get_string('unreg_is', 'grouptool'), $unregtext); if (!empty($this->grouptool->allow_multiple)) { $minmaxtext = ''; if ($this->grouptool->choose_min && $this->grouptool->choose_max) { $data = [ 'min' => $this->grouptool->choose_min, 'max' => $this->grouptool->choose_max ]; $minmaxtext = get_string('choose_min_max_text', 'grouptool', $data); } else if ($this->grouptool->choose_min) { $minmaxtext = get_string('choose_min_text', 'grouptool', $this->grouptool->choose_min); } else if ($this->grouptool->choose_max) { $minmaxtext = get_string('choose_max_text', 'grouptool', $this->grouptool->choose_max); } $mform->addElement('static', 'minmax', get_string('choose_minmax_title', 'grouptool'), $minmaxtext); } if (!empty($this->grouptool->use_queue)) { $mform->addElement('static', 'queueing', get_string('queueing_is', 'grouptool'), get_string('active', 'grouptool')); } } // Intro-text if set! if (($this->grouptool->alwaysshowdescription || (time() > $this->grouptool->timeavailable)) && $this->grouptool->intro) { $intro = format_module_intro('grouptool', $this->grouptool, $this->cm->id); $mform->addElement('header', 'intro', get_string('intro', 'grouptool')); $mform->addElement('html', $OUTPUT->box($intro, 'generalbox')); } } $groups = $this->get_active_groups(true, true); // Preperation for loop. $userregs = $this->get_user_reg_count($userid); $userqueues = $this->get_user_queues_count($userid); $usermarks = $this->count_user_marks($userid); $min = $this->grouptool->allow_multiple ? $this->grouptool->choose_min : 0; $mform->addElement('header', 'groups', get_string('groups')); $mform->setExpanded('groups'); // Checkbox control for only unoccupied groups filter. $mform->addElement('html', '<div><label class="form-check-inline"> <input type="checkbox" name="filterunoccupied" id="filterunoccupied" class="form-check-input"> ' . get_string('filterunoccupied', 'grouptool') . '</label></div>'); // Student view! if (has_capability("mod/grouptool:view_groups", $this->context)) { // Prepare formular-content for registration-action! foreach ($groups as $key => &$group) { $registered = count($group->registered); $grpsize = ($this->grouptool->use_size) ? $group->grpsize : "∞"; $grouphtml = html_writer::tag('span', get_string('registered', 'grouptool'). ": ".$registered."/".$grpsize, ['class' => 'fillratio']); if ($this->grouptool->use_queue) { $queued = count($group->queued); $grouphtml .= html_writer::tag('span', get_string('queued', 'grouptool'). " ".$queued, ['class' => 'queued']); } // Could become a performance problem when groups fill up! if (!empty($group->registered)) { $regrank = $this->get_rank_in_queue($group->registered, $USER->id); } else { $regrank = false; } if (!empty($group->queued)) { $queuerank = $this->get_rank_in_queue($group->queued, $USER->id); } else { $queuerank = false; } // We have to determine if we can show the members link! $showmembers = $this->canshowmembers($group->agrpid, $regrank, $queuerank); if ($showmembers) { $grouphtml .= $this->render_members_link($group); } /* If we include inactive groups and there's someone registered in one of these, * the label gets displayed incorrectly. */ if (!empty($group->registered) && $this->is_registration_open() && $this->get_rank_in_queue($group->registered, $userid) != false) { // User is already registered --> unreg button! if ($this->grouptool->allow_unreg && has_capability('mod/grouptool:register', $this->context)) { $label = get_string('unreg', 'grouptool'); $buttonattr = [ 'type' => 'submit', 'name' => 'unreg['.$group->agrpid.']', 'value' => $group->agrpid, 'class' => 'unregbutton btn btn-secondary' ]; if ($regopen && ($userregs + $userqueues > $min)) { $grouphtml .= html_writer::tag('button', $label, $buttonattr); } } $grouphtml .= html_writer::tag('span', get_string('registered_on_rank', 'grouptool', $regrank), ['class' => 'rank']); } else if (!empty($group->queued) && $this->is_registration_open() && $this->get_rank_in_queue($group->queued, $userid) != false) { // We're sorry, but user's already queued in this group! if ($this->grouptool->allow_unreg && has_capability('mod/grouptool:register', $this->context)) { $label = get_string('unqueue', 'grouptool'); $buttonattr = [ 'type' => 'submit', 'name' => 'unreg['.$group->agrpid.']', 'value' => $group->agrpid, 'class' => 'unregbutton btn btn-secondary' ]; if ($regopen && ($userregs + $userqueues > $min)) { $grouphtml .= html_writer::tag('button', $label, $buttonattr); } } $grouphtml .= html_writer::tag('span', get_string('queued_on_rank', 'grouptool', $queuerank), ['class' => 'rank']); } else if ($this->grpmarked($group->agrpid)) { $grouphtml .= html_writer::tag('span', get_string('grp_marked', 'grouptool'), ['class' => 'rank']); } else if ($this->is_registration_open() && $this->qualifies_for_groupchange($group->agrpid, $USER->id) && has_capability('mod/grouptool:register', $this->context)) { // Groupchange! $label = get_string('change_group', 'grouptool'); if ($this->grouptool->use_size && count($group->registered) >= $group->grpsize) { $label .= ' (' . get_string('queue', 'grouptool') . ')'; $class = "btn-secondary"; } else { $class = "btn-primary"; } $buttonattr = [ 'type' => 'submit', 'name' => 'reg['.$group->agrpid.']', 'value' => $group->agrpid, 'class' => 'regbutton btn '.$class ]; $grouphtml .= html_writer::tag('button', $label, $buttonattr); } else if ($this->is_registration_open()) { $message = new stdClass(); $message->username = fullname($USER); $message->groupname = $group->name; $message->userid = $USER->id; try { try { // Can be registered? $this->check_can_be_registered($group, $userregs, $userqueues, $usermarks); if (has_capability('mod/grouptool:register', $this->context)) { // Register button! $label = get_string('register', 'grouptool'); $buttonattr = [ 'type' => 'submit', 'name' => 'reg[' . $group->agrpid . ']', 'value' => $group->agrpid, 'class' => 'regbutton btn btn-primary' ]; $grouphtml .= html_writer::tag('button', $label, $buttonattr); } } catch (\mod_grouptool\local\exception\exceedgroupsize $e) { if (!$this->grouptool->use_queue) { throw new \mod_grouptool\local\exception\exceedgroupsize(); } else { if (has_capability('mod/grouptool:register', $this->context)) { // There's no place left in the group, so we try to queue the user! $this->can_be_queued($group->agrpid, $USER->id, $message); // Queue button! $label = get_string('queue', 'grouptool'); $buttonattr = [ 'type' => 'submit', 'name' => 'reg[' . $group->agrpid . ']', 'value' => $group->agrpid, 'class' => 'queuebutton btn btn-secondary' ]; $grouphtml .= html_writer::tag('button', $label, $buttonattr); } } } catch (\mod_grouptool\local\exception\notenoughregs $e) { /* The user has not enough registrations, queue entries or marks, * so we try to mark the user! (Exceptions get handled above!) */ list($queued, ) = $this->can_be_marked($group->agrpid, $USER->id, $message); if (!$queued && has_capability('mod/grouptool:register', $this->context)) { // Register button! $label = get_string('register', 'grouptool'); $buttonattr = [ 'type' => 'submit', 'name' => 'reg['.$group->agrpid.']', 'value' => $group->agrpid, 'class' => 'regbutton btn btn-primary' ]; $grouphtml .= html_writer::tag('button', $label, $buttonattr); } else if (has_capability('mod/grouptool:register', $this->context)) { // Queue button! $label = get_string('queue', 'grouptool'); $buttonattr = [ 'type' => 'submit', 'name' => 'reg['.$group->agrpid.']', 'value' => $group->agrpid, 'class' => 'queuebutton btn btn-secondary' ]; $grouphtml .= html_writer::tag('button', $label, $buttonattr); } } } catch (\mod_grouptool\local\exception\exceedgroupqueuelimit $e) { // Group is full! $grouphtml .= html_writer::tag('div', get_string('fullgroup', 'grouptool'), ['class' => 'rank']); } catch (\mod_grouptool\local\exception\exceedgroupsize $e) { // Group is full! $grouphtml .= html_writer::tag('div', get_string('fullgroup', 'grouptool'), ['class' => 'rank']); } catch (\mod_grouptool\local\exception\exceeduserqueuelimit $e) { // Too many queues! $grouphtml .= html_writer::tag('div', get_string('max_queues_reached', 'grouptool'), ['class' => 'rank']); } catch (\mod_grouptool\local\exception\exceeduserreglimit $e) { $grouphtml .= html_writer::tag('div', get_string('max_regs_reached', 'grouptool'), ['class' => 'rank']); } catch (\mod_grouptool\local\exception\registration $e) { // No registration possible! $grouphtml .= html_writer::tag('div', '', ['class' => 'rank']); } } if ($regrank !== false) { $grouphtml = $OUTPUT->box(html_writer::tag('h2', $group->name, ['class' => 'panel-title']). html_writer::tag('div', $grouphtml, ['class' => 'panel-body']), 'generalbox group alert-success'); } else if ($queuerank !== false) { $grouphtml = $OUTPUT->box(html_writer::tag('h2', $group->name, ['class' => 'panel-title']). html_writer::tag('div', $grouphtml, ['class' => 'panel-body']), 'generalbox group alert-warning'); } else if (($this->grouptool->use_size) && ($registered >= $group->grpsize) && $regopen) { $grouphtml = $OUTPUT->box(html_writer::tag('h2', $group->name, ['class' => 'panel-title']). html_writer::tag('div', $grouphtml, ['class' => 'panel-body']), 'generalbox group alert-error group-full'); } else { $classes = 'generalbox group empty'; if (($this->grouptool->use_size) && ($registered >= $group->grpsize)) { $classes .= ' group-full'; } $grouphtml = $OUTPUT->box(html_writer::tag('h2', $group->name, ['class' => 'panel-title']). html_writer::tag('div', $grouphtml, ['class' => 'panel-body']), $classes); } $mform->addElement('html', $grouphtml); } } if ($this->grouptool->show_members) { $params = new stdClass(); $params->courseid = $this->grouptool->course; $params->showidnumber = has_capability('mod/grouptool:view_regs_group_view', $this->context) || has_capability('mod/grouptool:view_regs_course_view', $this->context); $helpicon = new help_icon('status', 'mod_grouptool'); // Add the help-icon-data to the form element as data-attribute so we use less params for the JS-call! $mform->updateAttributes(['data-statushelp' => json_encode($helpicon->export_for_template($OUTPUT))]); // Require the JS to show group members (just once)! $PAGE->requires->js_call_amd('mod_grouptool/memberspopup', 'initializer', [$params]); } $mform->display(); } } /** * Returns whether or not a user should be able to see the members of this active group. * Either if regrank or queuerank are not set, agrp has to be set! * * @param int|object $agrp Active group's DB ID or active group object * @param int|bool $regrank The registration rank in this active group * (false if not registered or null if it has to be determined for the current user) * @param int|bool $queuerank The queue rank in this active group * (false if not queued or null if it has to be determined for the current user) * @return bool true if user can show, false if not! * @throws coding_exception * @throws dml_exception */ public function canshowmembers($agrp = null, $regrank = null, $queuerank = null) { global $DB, $USER; if ($regrank === null || $queuerank === null) { if (is_numeric($agrp)) { $agrpid = $agrp; } else if (is_object($agrp) && isset($agrp->id)) { $agrpid = $agrp->id; } else { throw new coding_exception('$agrp has to be the active group ID or an object containing $agrp->id'); } if ($regrank === null) { $regrank = $DB->record_exists('grouptool_registered', ['userid' => $USER->id, 'agrpid' => $agrpid]); } if ($queuerank === null) { $queuerank = $DB->record_exists('grouptool_queued', ['userid' => $USER->id, 'agrpid' => $agrpid]); } } switch($this->grouptool->show_members) { case self::SHOW_GROUPMEMBERS: $showmembers = true; break; case self::SHOW_GROUPMEMBERS_AFTER_DUE: $showmembers = (time() > $this->grouptool->timedue); break; case self::SHOW_OWN_GROUPMEMBERS_AFTER_REG: $showmembers = ($regrank !== false) || ($queuerank !== false); break; case self::SHOW_OWN_GROUPMEMBERS_AFTER_DUE: $showmembers = (time() > $this->grouptool->timedue) && (($regrank !== false) || ($queuerank !== false)); break; default: case self::HIDE_GROUPMEMBERS: $showmembers = false; break; } return $showmembers; } /** * Force enrol a user in this course as student to be able to import into group or register for group! * * @param int $userid ID of user to force enrol! * @throws coding_exception Thrown if smthg very unexpected happened (couldn't instantiate manual enrol instance or similar) * @throws dml_exception */ protected function force_enrol_student($userid) { global $CFG, $DB; require_once($CFG->dirroot.'/enrol/manual/locallib.php'); require_once($CFG->libdir.'/accesslib.php'); if (!$enrolmanual = enrol_get_plugin('manual')) { throw new coding_exception(get_string('cant_enrol', 'grouptool')); } if (!$instance = $DB->get_record('enrol', [ 'courseid' => $this->course->id, 'enrol' => 'manual' ], '*', IGNORE_MISSING)) { if ($enrolmanual->add_default_instance($this->course)) { $instance = $DB->get_record('enrol', [ 'courseid' => $this->course->id, 'enrol' => 'manual' ], '*', MUST_EXIST); } } if ($instance != false) { $archroles = get_archetype_roles('student'); $archrole = array_shift($archroles); $enrolmanual->enrol_user($instance, $userid, $archrole->id, time()); } else { throw new coding_exception(get_string('cant_enrol', 'grouptool')); } } /** * import users into a certain moodle-group and enrole them if not allready enroled * * @param int[] $groups array of ids of groups to import into * @param stdClass $data from form in import tab (textfield with idnumbers and group-selection) * @param int[] $ignored which user ids to ignore when importing (used if conflicting users should be ignored) * @param bool $forceregistration Force registration in grouptool * @param bool $previewonly optional preview only, don't take any action * @return array ($error, $message) * @throws coding_exception * @throws dml_exception */ public function import($groups, $data, $ignored = [], $forceregistration = false, $previewonly = false) { global $DB, $OUTPUT, $USER; $message = ""; $error = false; $users = preg_split("/[ ,;\t\n\r]+/", $data); // Prevent selection of all users if one of the above defined characters are in the beginning! foreach ($users as $key => $user) { if (empty($user)) { unset($users[$key]); } } $groupinfo = []; foreach ($groups as $group) { $groupinfo[$group] = groups_get_group($group); } $imported = []; $columns = $DB->get_columns('user'); $agrp = []; foreach ($groups as $group) { $agrp[$group] = $DB->get_field('grouptool_agrps', 'id', [ 'grouptoolid' => $this->grouptool->id, 'groupid' => $group ], IGNORE_MISSING); if (!$DB->record_exists('grouptool_agrps', [ 'grouptoolid' => $this->grouptool->id, 'groupid' => $group, 'active' => 1 ])) { $message .= $OUTPUT->notification(get_string('import_in_inactive_group_warning', 'grouptool', $groupinfo[$group]->name), \core\output\notification::NOTIFY_ERROR); } // We use MAX to trick Postgres into thinking this is a full GROUP BY statement! $sql = ' SELECT agrps.id AS id, MAX(agrps.groupid) AS grpid, COUNT(regs.id) AS regs, MAX(grptl.grpsize) AS globalsize, MAX(agrps.grpsize) AS size, MAX(grptl.name) AS instancename FROM {grouptool_agrps} agrps JOIN {grouptool} grptl ON agrps.grouptoolid = grptl.id LEFT JOIN {grouptool_registered} regs ON agrps.id = regs.agrpid AND regs.modified_by >= 0 WHERE agrps.groupid = :grpid AND grptl.use_size = 1 AND agrps.active = 1 GROUP BY agrps.id '; $agrps = $DB->get_records_sql($sql, ['grpid' => $group]); $usercnt = count($users); foreach ($agrps as $cur) { if (!empty($cur->size)) { if (($cur->regs + $usercnt) > $cur->size) { $message .= html_writer::tag('div', $OUTPUT->notification(get_string('overflowwarning', 'grouptool', $cur), \core\output\notification::NOTIFY_ERROR)); } } else { if (($cur->regs + $usercnt) > $cur->globalsize) { $message .= html_writer::tag('div', $OUTPUT->notification(get_string('overflowwarning', 'grouptool', $cur), \core\output\notification::NOTIFY_ERROR)); } } } } if (false !== ($gtimportfields = get_config('mod_grouptool', 'importfields'))) { $importfields = explode(',', $gtimportfields); } else { $importfields = ['username', 'idnumber']; } $prevtable = new html_table(); $prevtable->attributes['class'] = 'importpreview table table-striped table-hover'; $prevtable->id = 'importpreview'; $prevtable->head = [get_string('fullname')]; foreach ($importfields as $field) { $prevtable->head[] = get_string($field); } $prevtable->head[] = get_string('status'); $prevtable->data = []; $pbar = new progress_bar('checkmarkimportprogress', 500, true); $count = count($users); $processed = 0; $pbar->update($processed, $count, get_string('import_progress_start', 'grouptool')); core_php_time_limit::raise(count($users) * 5); raise_memory_limit(MEMORY_HUGE); foreach ($users as $user) { $pbar->update($processed, $count, get_string('import_progress_search', 'grouptool').' '.$user); $userinfo = $this->find_userinfo($importfields, $user); $row = new html_table_row(); $errorrows = $this->check_userinfo($userinfo, $user, $importfields); if (!empty($errorrows)) { foreach ($errorrows as $r) { $prevtable->data[] = $r; } $error = true; } else { $userinfo = reset($userinfo); $row->cells = [new html_table_cell(fullname($userinfo))]; foreach ($importfields as $curfield) { $row->cells[] = new html_table_cell(empty($userinfo->$curfield) ? '' : $userinfo->$curfield); } if (!is_enrolled($this->context, $userinfo->id)) { // We have to catch deleted users now, give a message and continue! if (!empty($userinfo->deleted)) { $userinfo->fullname = fullname($userinfo); $text = get_string('user_is_deleted', 'grouptool', $userinfo); $row->cells[] = new html_table_cell($OUTPUT->notification($text, 'error')); $error = true; continue; } /* * if user's not enrolled already we force manual enrollment in course, * so we can add the user to the group */ try { $this->force_enrol_student($userinfo->id); } catch (Exception $e) { $row->cells[] = new html_table_cell($OUTPUT->notification($e->getMessage(), \core\output\notification::NOTIFY_ERROR)); } catch (Throwable $t) { $row->cells[] = new html_table_cell($OUTPUT->notification($t->getMessage(), \core\output\notification::NOTIFY_ERROR)); } } foreach ($groups as $group) { $data = [ 'id' => $userinfo->id, 'idnumber' => $userinfo->idnumber, 'fullname' => fullname($userinfo), 'groupname' => $groupinfo[$group]->name ]; if (!$previewonly && $userinfo) { $pbar->update($processed, $count, get_string('import_progress_import', 'grouptool').' '.fullname($userinfo).'...'); if (in_array($userinfo->id, $ignored[$group])) { // We ignore the user for this import in this group! $cell = new html_table_cell(get_string('import_skipped', 'grouptool', $data)); $cell->attributes['class'] = 'info'; $row->cells[] = $cell; continue; } if (!groups_add_member($group, $userinfo->id)) { $error = true; $notification = $OUTPUT->notification(get_string('import_user_problem', 'grouptool', $data), \core\output\notification::NOTIFY_ERROR); $row->cells[] = new html_table_cell($notification); $row->attributes['class'] = 'error'; } else { $imported[] = $userinfo->id; $row->cells[] = get_string('import_user', 'grouptool', $data); $row->attributes['class'] = 'success'; } if ($forceregistration && empty($agrp[$group])) { /* Registering in an non active Grouptool-group would cause problems * with incorrectly labeled buttons under certain circumstances. * We removed the automatic creation and registration in this newly inserted inactive group. * In no case, there should be a missing agrp entry anyway. */ $newgrpdata = $DB->get_record_sql('SELECT MAX(sort_order), MAX(grpsize) FROM {grouptool_agrps} WHERE grouptoolid = ?', [$this->grouptool->id]); // Insert agrp-entry for this group (even if it's not active)! $agrp[$group] = new stdClass(); $agrp[$group]->grouptoolid = $this->grouptool->id; $agrp[$group]->groupid = $group; $agrp[$group]->active = 0; $agrp[$group]->sort_order = $newgrpdata->sortorder + 1; $agrp[$group]->grpsize = $newgrpdata->grpsize; $agrp[$group]->id = $DB->insert_record('grouptool_agrps', $agrp[$group]); \mod_grouptool\event\agrp_created::create_from_object($this->cm, $agrp[$group])->trigger(); $notification = $OUTPUT->notification(get_string('import_in_inactive_group_rejected', 'grouptool', $agrp[$group]), \core\output\notification::NOTIFY_ERROR); $row->cells[] = $notification; $row->attributes['class'] = 'error'; $agrp[$group] = $agrp[$group]->id; } else if ($forceregistration && !empty($agrp[$group]) && !$DB->record_exists_select('grouptool_registered', "modified_by >= 0 AND agrpid = :agrpid AND userid = :userid", ['agrpid' => $agrp[$group], 'userid' => $userinfo->id])) { if ($reg = $DB->get_record('grouptool_registered', [ 'agrpid' => $agrp[$group], 'userid' => $userinfo->id, 'modified_by' => -1 ], IGNORE_MISSING)) { // If user is marked, we register him right now! $reg->modified_by = $USER->id; $DB->update_record('grouptool_registered', $reg); // TODO do we have to delete his marks and queues if theres enough registrations? } else { $reg = new stdClass(); $reg->agrpid = $agrp[$group]; $reg->userid = $userinfo->id; $reg->timestamp = time(); $reg->modified_by = $USER->id; // We don't need to log creation of registration, because we log import as whole! $reg->id = $DB->insert_record('grouptool_registered', $reg); } // Delete every queue entry here! $DB->delete_records('grouptool_queued', ['agrpid' => $agrp[$group], 'userid' => $userinfo->id]); \mod_grouptool\event\user_imported::import_forced($this->cm, $reg->id, $agrp[$group], $group, $userinfo->id)->trigger(); } else { // Delete every queue entry here! $DB->delete_records('grouptool_queued', ['agrpid' => $agrp[$group], 'userid' => $userinfo->id]); if (!$forceregistration) { // Trigger the event! \mod_grouptool\event\user_imported::import($this->cm, $group, $userinfo->id)->trigger(); } } } else if ($userinfo) { if ($DB->record_exists_select('grouptool_queued', "agrpid = :agrpid AND userid = :userid", ['agrpid' => $agrp[$group], 'userid' => $userinfo->id])) { $options = [ -1 => get_string('move_user', 'grouptool'), $userinfo->id => get_string('skip_user_import', 'grouptool') ]; $cell = get_string('import_conflict_user_queued', 'grouptool', $data). html_writer::tag('div', html_writer::select($options, "ignored_{$group}[]", -1, false)); $row->cells[] = $cell; $row->attributes['class'] = 'prevconflict'; } else { $row->cells[] = get_string('import_user_prev', 'grouptool', $data); $row->attributes['class'] = 'prevsuccess'; } } } } $prevtable->data[] = $row; unset($row); $processed++; } $processed++; if (!$previewonly) { $pbar->update($processed, $count, get_string('import_progress_completed', 'grouptool')); } else { $pbar->update($processed, $count, get_string('import_progress_preview_completed', 'grouptool')); } $message .= html_writer::table($prevtable); return [$error, $message]; } /** * view import-tab * * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ public function view_import() { global $PAGE, $OUTPUT; require_capability('mod/grouptool:register_students', $this->context); $id = $this->cm->id; $form = new \mod_grouptool\import_form(null, ['id' => $id]); if (optional_param('confirm', 0, PARAM_BOOL)) { $groups = required_param_array('group', PARAM_INT); $data = required_param('data', PARAM_NOTAGS); $forceregistration = optional_param('forceregistration', 0, PARAM_BOOL); $ignored = []; foreach ($groups as $group) { $ignored[$group] = optional_param_array("ignored_$group", [-1 => -1], PARAM_INT); } list($error, $message) = $this->import($groups, $data, $ignored, $forceregistration); if (!empty($error)) { $message = $OUTPUT->notification(get_string('ignored_not_found_users', 'grouptool'), \core\output\notification::NOTIFY_ERROR).html_writer::empty_tag('br').$message; } echo html_writer::tag('div', $message, ['class' => 'centered']); } if ($fromform = $form->get_data()) { // Display confirm message - so we "try" only! list($error, $confirmmessage) = $this->import($fromform->groups, $fromform->data, [], $fromform->forceregistration, true); $formdata = [ 'id' => $id, 'groups' => $fromform->groups, 'data' => $fromform->data, 'forceregistration' => $fromform->forceregistration, 'confirmmessage' => $confirmmessage ]; // The form data will be fetched through required_param()! TODO gotta refactor this in the future! $confirmform = new \mod_grouptool\import_confirm_form($PAGE->url, $formdata); echo $OUTPUT->heading(get_string('preview', 'grouptool'), 2, 'centered'); if ($error) { echo $OUTPUT->notification(get_string('ignoring_not_found_users', 'grouptool'), \core\output\notification::NOTIFY_ERROR); } $confirmform->display(); } else { $form->display(); } } /** * view unregister-tab * * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ public function view_unregister() { global $PAGE, $OUTPUT; require_capability('mod/grouptool:unregister_students', $this->context); $id = $this->cm->id; $form = new \mod_grouptool\unregister_form(null, ['id' => $id]); if (optional_param('confirm', 0, PARAM_BOOL)) { $groups = required_param_array('group', PARAM_INT); $data = required_param('data', PARAM_NOTAGS); $unregfrommgroups = optional_param('unregfrommgroups', 1, PARAM_BOOL); $ignored = []; foreach ($groups as $group) { $ignored[$group] = optional_param_array("ignored_$group", [-1 => -1], PARAM_INT); } list($error, $message) = $this->unregister($groups, $data, $unregfrommgroups, false); if (!empty($error)) { $message = $OUTPUT->notification(get_string('ignored_not_found_users_unregister', 'grouptool'), \core\output\notification::NOTIFY_ERROR).html_writer::empty_tag('br').$message; } echo html_writer::tag('div', $message, ['class' => 'centered']); } if ($fromform = $form->get_data()) { // Display confirm message - so we "try" only! list($error, $confirmmessage) = $this->unregister($fromform->groups, $fromform->data, $fromform->unregfrommgroups, true); $formdata = [ 'id' => $id, 'groups' => $fromform->groups, 'data' => $fromform->data, 'unregfrommgroups' => $fromform->unregfrommgroups, 'confirmmessage' => $confirmmessage ]; // The form data will be fetched through required_param()! TODO gotta refactor this in the future! $confirmform = new \mod_grouptool\unregister_confirm_form($PAGE->url, $formdata); echo $OUTPUT->heading(get_string('preview', 'grouptool'), 2, 'centered'); if ($error) { echo $OUTPUT->notification(get_string('ignoring_not_found_users', 'grouptool'), \core\output\notification::NOTIFY_ERROR); } $confirmform->display(); } else { $form->display(); } } /** * get all data necessary for displaying/exporting group-overview table * * @param int $groupingid optional get only this grouping * @param int $groupid optional get only this group (groupid not agroupid!) * @param bool $onlydata optional return object with raw data not html-fragment-string * @param bool $includeinactive optional include inactive groups too! * @return array|int|string either html-fragment representing table or raw data as object * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function group_overview_table($groupingid = 0, $groupid = 0, $onlydata = false, $includeinactive = false) { global $OUTPUT, $CFG, $DB; $orientation = optional_param('orientation', 0, PARAM_BOOL); $downloadurl = new moodle_url('/mod/grouptool/download.php', [ 'id' => $this->cm->id, 'groupingid' => $groupingid, 'groupid' => $groupid, 'orientation' => $orientation, 'sesskey' => sesskey(), 'tab' => 'overview', 'inactive' => $includeinactive ]); $return = []; // We just get an overview and fetch data later on a per group basis to save memory! $agrps = $this->get_active_groups(false, false, 0, $groupid, $groupingid, true, $includeinactive); $groupinfo = groups_get_all_groups($this->grouptool->course); $userinfo = []; $syncstatus = $this->get_sync_status(); if (!$onlydata && count($agrps)) { // Global-downloadlinks! echo $this->get_download_links($downloadurl); } foreach ($agrps as $agrp) { // We give each group 30 seconds (minimum) and hope it doesn't time out because of no output in case of download! core_php_time_limit::raise(30); $groupdata = new stdClass(); $groupdata->name = $groupinfo[$agrp->id]->name.($agrp->active ? '' : ' ('.get_string('inactive').')'); // Get all registered userids! $select = " agrpid = ? AND modified_by >= 0 "; $registered = $DB->get_fieldset_select('grouptool_registered', 'userid', $select, [$agrp->agrpid]); // Get all moodle-group-member-ids! $select = " groupid = ? "; $members = $DB->get_fieldset_select('groups_members', 'userid', $select, [$agrp->id]); // Get all registered users with moodle-group-membership! $absregs = array_intersect($registered, $members); // Get all registered users without moodle-group-membership! $gtregs = array_diff($registered, $members); // Get all moodle-group-members without registration! $mdlregs = array_diff($members, $registered); // Get all queued users! $select = " agrpid = ? "; $queued = $DB->get_fieldset_select('grouptool_queued', 'userid', $select, [$agrp->agrpid]); // We give additional 1 second per registration/queue/moodle entry in this group! core_php_time_limit::raise(30 * (count($registered) + count($members) + count($queued))); if (!empty($this->grouptool->use_size)) { if (!empty($agrp->grpsize)) { $size = $agrp->grpsize; $free = $agrp->grpsize - count($registered); } else { $size = !empty($this->grouptool->grpsize) ? $this->grouptool->grpsize : get_config('mod_grouptool', 'grpsize'); $free = ($size - count($registered)); } } else { $size = "∞"; $free = '∞'; } $groupdata->queued = count($queued); $groupdata->registered = count($registered); $groupdata->total = $groupdata->registered + $groupdata->queued; $groupdata->free = $free; $groupdata->reg_data = []; $groupdata->queue_data = []; $groupdata->inactive = !$agrp->active; if ($agrp->active) { $groupdata->uptodate = $syncstatus[1][$agrp->agrpid]->status === GROUPTOOL_UPTODATE; $groupdata->outdated = $syncstatus[1][$agrp->agrpid]->status !== GROUPTOOL_UPTODATE; } // User-ID will be added in template! $groupdata->userlink = $CFG->wwwroot . '/user/view.php?course=' . $this->grouptool->course . '&id='; $groupdata->groupid = $groupinfo[$agrp->id]->id; $groupdata->formattxt = GROUPTOOL_TXT; $groupdata->formatpdf = GROUPTOOL_PDF; $groupdata->formatxlsx = GROUPTOOL_XLSX; $groupdata->formatods = GROUPTOOL_ODS; $statushelp = new help_icon('status', 'mod_grouptool'); if (!$onlydata) { $groupdata->statushelp = $statushelp->export_for_template($OUTPUT); // Format will be added in template! $groupdownloadurl = new moodle_url($downloadurl, ['groupid' => $groupinfo[$agrp->id]->id]); $groupdata->downloadurl = $groupdownloadurl->out(false); } // We create a dummy user-object to get the fullname-format! $dummy = new stdClass(); $namefields = get_all_user_name_fields(); foreach ($namefields as $namefield) { $dummy->$namefield = $namefield; } $fullnameformat = fullname($dummy); // Now get the ones used in fullname in the correct order! $namefields = order_in_string($namefields, $fullnameformat); if (count($registered) + count($members) >= 1) { if (count($absregs) >= 1) { foreach ($absregs as $curuser) { if (!array_key_exists($curuser, $userinfo)) { $userinfo[$curuser] = $DB->get_record('user', ['id' => $curuser]); } $fullname = fullname($userinfo[$curuser]); $row = []; $row['userid'] = $curuser; $row['name'] = $fullname; $this->add_namefields_useridentity($row, $userinfo[$curuser]); // We set those in any case, because PDF and TXT export needs them anyway! $row['email'] = $userinfo[$curuser]->email; $row['idnumber'] = $userinfo[$curuser]->idnumber; $row['status'] = "✔"; $groupdata->reg_data[] = $row; $row = null; unset($row); } $regentry = null; unset($regentry); } if (count($gtregs) >= 1) { foreach ($gtregs as $curuser) { if (!array_key_exists($curuser, $userinfo)) { $userinfo[$curuser] = $DB->get_record('user', ['id' => $curuser]); } $fullname = fullname($userinfo[$curuser]); $row = []; $row['userid'] = $curuser; $row['name'] = $fullname; $this->add_namefields_useridentity($row, $userinfo[$curuser]); $row['email'] = $userinfo[$curuser]->email; $row['idnumber'] = $userinfo[$curuser]->idnumber; $row['status'] = "+"; $groupdata->reg_data[] = $row; $row = null; unset($row); } $regentry = null; unset($regentry); } if (count($mdlregs) >= 1) { foreach ($mdlregs as $curuser) { if (!array_key_exists($curuser, $userinfo)) { $userinfo[$curuser] = $DB->get_record('user', ['id' => $curuser]); } $fullname = fullname($userinfo[$curuser]); $row = []; $row['userid'] = $curuser; $row['name'] = $fullname; $this->add_namefields_useridentity($row, $userinfo[$curuser]); // We set those in any case, because PDF and TXT export needs them anyway! $row['email'] = $userinfo[$curuser]->email; $row['idnumber'] = $userinfo[$curuser]->idnumber; $row['status'] = "?"; $groupdata->mreg_data[] = $row; $row = null; unset($row); } $regentry = null; unset($regentry); } } if (count($queued) >= 1) { $queuedlist = $DB->get_records('grouptool_queued', ['agrpid' => $agrp->agrpid], 'timestamp ASC'); foreach ($queued as $curuser) { if (!array_key_exists($curuser, $userinfo)) { $userinfo[$curuser] = $DB->get_record('user', ['id' => $curuser]); } $fullname = fullname($userinfo[$curuser]); $rank = $this->get_rank_in_queue($queuedlist, $curuser); $row = []; $row['userid'] = $curuser; $row['rank'] = $rank; $row['name'] = $fullname; $this->add_namefields_useridentity($row, $userinfo[$curuser]); // We set those in any case, because PDF and TXT export needs them anyway! $row['email'] = $userinfo[$curuser]->email; $row['idnumber'] = $userinfo[$curuser]->idnumber; $groupdata->queue_data[] = $row; } } if (!$onlydata) { echo $OUTPUT->render_from_template('mod_grouptool/overviewgroup', $groupdata); } else { $return[] = $groupdata; } $groupdata = null; unset($groupdata); } if (count($agrps) == 0) { $boxcontent = $OUTPUT->notification(get_string('no_data_to_display', 'grouptool'), \core\output\notification::NOTIFY_ERROR); $return = $OUTPUT->box($boxcontent, 'generalbox centered'); if (!$onlydata) { echo $return; } } if ($onlydata) { return $return; } else { return 0; } } /** * Add additional user fields and useridentity fields to the row (at least adds idnumber and email to be displayed). * * @param mixed[] $row Associative array with table data for this user * @param stdClass $user the user's DB record */ protected function add_namefields_useridentity(&$row, $user) { global $CFG; $namefields = get_all_user_name_fields(); foreach ($namefields as $namefield) { if (!empty($user->$namefield)) { $row[$namefield] = $user->$namefield; } else { $row[$namefield] = ''; } } if (empty($CFG->showuseridentity)) { if (!empty($user->idnumber)) { $row['idnumber'] = $user->idnumber; } else { $row['idnumber'] = '-'; } if (!empty($user->email)) { $row['email'] = $user->email; } else { $row['email'] = '-'; } } else { $fields = explode(',', $CFG->showuseridentity); foreach ($fields as $field) { if (!empty($user->$field)) { $row[$field] = $user->$field; } else { $row[$field] = ''; } } } } /** * outputs generated pdf-file for overview (forces download) * * @param int $groupid optional get only this group * @param int $groupingid optional get only this grouping * @param bool $includeinactive optional include inactive groups too! * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function download_overview_pdf($groupid=0, $groupingid=0, $includeinactive=false) { $data = $this->group_overview_table($groupingid, $groupid, true, $includeinactive); $pdf = new \mod_grouptool\pdf(); $coursename = format_string($this->course->fullname, true, array('context' => context_module::instance($this->cm->id))); $timeavailable = $this->grouptool->timeavailable; $grouptoolname = $this->grouptool->name; $timedue = $this->grouptool->timedue; if (!empty($groupid)) { $viewname = groups_get_group_name($groupid); } else { if (!empty($groupingid)) { $viewname = groups_get_grouping_name($groupingid); } else { $viewname = get_string('all').' '.get_string('groups'); } } $pdf->set_overview_header_data($coursename, $grouptoolname, $timeavailable, $timedue, $viewname); if (count($data) > 0) { foreach ($data as $group) { $groupname = $group->name; $groupinfo = get_string('total').' '.$group->total.' / '. get_string('registered', 'grouptool').' '.$group->registered.' / '. get_string('queued', 'grouptool').' '.$group->queued.' / '. get_string('free', 'grouptool').' '.$group->free; $regdata = $group->reg_data; $queuedata = $group->queue_data; $mregdata = isset($group->mreg_data) ? $group->mreg_data : []; $pdf->add_grp_overview($groupname, $groupinfo, $regdata, $queuedata, $mregdata); $pdf->MultiCell(0, $pdf->getLastH(), '', 'B', 'L', false, 1, null, null, true, 1, true, false, $pdf->getLastH(), 'M', true); $pdf->MultiCell(0, $pdf->getLastH(), '', 'T', 'L', false, 1, null, null, true, 1, true, false, $pdf->getLastH(), 'M', true); } $pdf->SetFontSize(8); $pdf->MultiCell(0, $pdf->getLastH(), get_string('status', 'grouptool'), '', 'L', false, 1, null, null, true, 1, true, false, $pdf->getLastH(), 'M', true); foreach (explode("</li>", get_string('status_help', 'grouptool')) as $legendline) { $pdf->MultiCell(0, $pdf->getLastH(), strip_tags($legendline), '', 'L', false, 1, null, null, true, 1, true, false, $pdf->getLastH(), 'M', true); } } else { $pdf->MultiCell(0, $pdf->getLastH(), get_string('no_data_to_display', 'grouptool'), 'B', 'LRTB', false, 1, null, null, true, 1, true, false, $pdf->getLastH(), 'M', true); } if (!empty($groupid)) { $filename = $coursename . '_' . $grouptoolname . '_' . groups_get_group_name($groupid).'_'.get_string('overview', 'grouptool'); } else if (!empty($groupingid)) { $filename = $coursename . '_' . $grouptoolname . '_' . groups_get_grouping_name($groupingid).'_'.get_string('overview', 'grouptool'); } else { $filename = $coursename . '_' . $grouptoolname . '_' . get_string('group').' '.get_string('overview', 'grouptool'); } $filename = clean_filename("$filename.pdf"); $pdf->Output($filename, 'D'); exit(); } /** * returns raw data for overview * * @param int $groupid optional get only this group * @param int $groupingid optional get only this grouping * @param bool $includeinactive optional include inactive groups too! * @return array|int|string raw data * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function download_overview_raw($groupid=0, $groupingid=0, $includeinactive=false) { return $this->group_overview_table($groupid, $groupingid, true, $includeinactive); } /** * outputs generated txt-file for overview (forces download) * * @param int $groupid optional get only this group * @param int $groupingid optional get only this grouping * @param bool $includeinactive optional include inactive groups too! * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function download_overview_txt($groupid=0, $groupingid=0, $includeinactive=false) { ob_start(); $lines = []; $groups = $this->group_overview_table($groupingid, $groupid, true, $includeinactive); if (count($groups) > 0) { $lines[] = "*** ".get_string('status', 'grouptool')."\n"; foreach (explode("</li>", get_string('status_help', 'grouptool')) as $legendline) { $lines[] = "***\t".strip_tags($legendline); } $lines[] = ""; foreach ($groups as $group) { $lines[] = $group->name; $lines[] = "\t".get_string('total').' '.$group->total." / ". get_string('registered', 'grouptool').' '.$group->registered." / ". get_string('queued', 'grouptool').' '.$group->queued." / ". get_string('free', 'grouptool').' '.$group->free; if ($group->registered > 0) { $lines[] = "\t".get_string('registrations', 'grouptool'); foreach ($group->reg_data as $reg) { $lines[] = "\t\t".$reg['status']."\t".$reg['name']."\t".$reg['idnumber']. "\t".$reg['email']; } } else if (count($group->mreg_data) == 0) { $lines[] = "\t\t--".get_string('no_registrations', 'grouptool')."--"; } if (count($group->mreg_data) >= 1) { foreach ($group->mreg_data as $mreg) { $lines[] = "\t\t?\t".$mreg['name']."\t".$mreg['idnumber']."\t". $mreg['email']; } } if ($group->queued > 0) { $lines[] = "\t".get_string('queue', 'grouptool'); foreach ($group->queue_data as $queue) { $lines[] = "\t\t".$queue['rank']."\t".$queue['name']."\t". $queue['idnumber']."\t".$queue['email']; } } else { $lines[] = "\t\t--".get_string('nobody_queued', 'grouptool')."--"; } $lines[] = ""; } } else { $lines[] = get_string('no_data_to_display', 'grouptool'); } $filecontent = implode(GROUPTOOL_NL, $lines); $coursename = format_string($this->course->fullname, true, array('context' => context_module::instance($this->cm->id))); $grouptoolname = $this->grouptool->name; if (!empty($groupid)) { $filename = $coursename . '_' . $grouptoolname . '_' . groups_get_group_name($groupid).'_'.get_string('overview', 'grouptool'); } else if (!empty($groupingid)) { $filename = $coursename . '_' . $grouptoolname . '_' . groups_get_grouping_name($groupingid).'_'.get_string('overview', 'grouptool'); } else { $filename = $coursename . '_' . $grouptoolname . '_' . get_string('group').'_'.get_string('overview', 'grouptool'); } $filename = clean_filename("$filename.txt"); ob_clean(); header('Content-Type: text/plain'); header('Content-Length: ' . strlen($filecontent)); header('Content-Disposition: attachment; filename="'.str_replace([' ', '"'], ['_', ''], $filename). '"; filename*="'.rawurlencode($filename).'"'); header('Content-Transfer-Encoding: binary'); header('Content-Encoding: utf-8'); echo $filecontent; } /** * Fill workbook (either XLSX or ODS) with data * * @param MoodleExcelWorkbook|MoodleODSWorkbook $workbook workbook to put data into * @param stdClass[] $groups which groups from whom to include data * @param string[] $collapsed array with collapsed columns * @throws coding_exception */ private function overview_fill_workbook(&$workbook, $groups, $collapsed=[]) { global $CFG; if (count($groups) > 0) { $columnwidth = [7, 22, 14, 17]; // Unit: mm! $allgroupsworksheet = false; if (count($groups) > 1) { // General information? unused at the moment! $allgroupsworksheet = $workbook->add_worksheet(get_string('all')); // The standard column widths: 7 - 22 - 14 - 17! $allgroupsworksheet->set_column(0, 0, $columnwidth[0]); $allgroupsworksheet->set_column(1, 1, $columnwidth[1]); $allgroupsworksheet->set_column(2, 2, $columnwidth[2]); $allgroupsworksheet->set_column(3, 3, $columnwidth[3]); } $legendworksheet = $workbook->add_worksheet(get_string('status', 'grouptool').' '. get_string('help')); $legendworksheet->write_string(0, 0, get_string('status', 'grouptool').' '. get_string('help')); $line = 1; foreach (explode("</li>", get_string('status_help', 'grouptool')) as $legendline) { if (strstr($legendline, "</span>")) { $lineelements = explode("</span>", $legendline); $legendworksheet->write_string($line, 0, strip_tags($lineelements[0])); $legendworksheet->write_string($line, 1, strip_tags($lineelements[1])); $line++; } } // Add content for all groups! $groupworksheets = []; // Prepare formats! $headlineprop = [ 'size' => 14, 'bold' => 1, 'align' => 'center' ]; $headlineformat = $workbook->add_format($headlineprop); $groupinfoprop1 = [ 'size' => 10, 'bold' => 1, 'align' => 'left' ]; $groupinfoprop2 = $groupinfoprop1; unset($groupinfoprop2['bold']); $groupinfoprop2['italic'] = true; $groupinfoprop2['align'] = 'right'; $groupinfoformat1 = $workbook->add_format($groupinfoprop1); $groupinfoformat2 = $workbook->add_format($groupinfoprop2); $regheadprop = [ 'size' => 10, 'align' => 'center', 'bold' => 1, 'bottom' => 2 ]; $regentryprop = [ 'size' => 10, 'align' => 'left' ]; $queueentryprop = $regentryprop; $queueentryprop['italic'] = true; $queueentryprop['color'] = 'grey'; $regheadformat = $workbook->add_format($regheadprop); $regheadformat->set_right(1); $regheadlast = $workbook->add_format($regheadprop); $regentryformat = $workbook->add_format($regentryprop); $regentryformat->set_right(1); $regentryformat->set_top(1); $regentryformat->set_bottom(0); $regentrylast = $workbook->add_format($regentryprop); $regentrylast->set_top(1); $noregentriesformat = $workbook->add_format($regentryprop); $noregentriesformat->set_align('center'); $queueentryformat = $workbook->add_format($queueentryprop); $queueentryformat->set_right(1); $queueentryformat->set_top(1); $queueentryformat->set_bottom(false); $queueentrylast = $workbook->add_format($queueentryprop); $queueentrylast->set_top(1); $noqueueentriesformat = $workbook->add_format($queueentryprop); $noqueueentriesformat->set_align('center'); // We create a dummy user-object to get the fullname-format! $dummy = new stdClass(); $namefields = get_all_user_name_fields(); foreach ($namefields as $namefield) { $dummy->$namefield = $namefield; } $fullnameformat = fullname($dummy); // Now get the ones used in fullname in the correct order! $namefields = order_in_string($namefields, $fullnameformat); $columnwidth = [ 0 => 26, 'fullname' => 26, 'firstname' => 20, 'surname' => 20, 'email' => 35, 'registrations' => 47, 'queues_rank' => 7.5, 'queues_grp' => 47 ]; // Unit: mm! // Start row for groups general sheet! $j = 0; $columncount = 1 + count($namefields); if (!empty($CFG->showuseridentity)) { $fields = explode(',', $CFG->showuseridentity); $columncount += count($fields); } else { $columncount += 2; } foreach ($groups as $key => $group) { // Add worksheet for each group! $groupworksheets[$key] = $workbook->add_worksheet($group->name); $groupname = $group->name; $groupinfo = []; $groupinfo[] = [get_string('total'), $group->total]; $groupinfo[] = [get_string('registered', 'grouptool'), $group->registered]; $groupinfo[] = [get_string('queued', 'grouptool'), $group->queued]; $groupinfo[] = [get_string('free', 'grouptool'), $group->free]; $regdata = $group->reg_data; $queuedata = $group->queue_data; $mregdata = isset($group->mreg_data) ? $group->mreg_data : []; // Groupname as headline! $groupworksheets[$key]->write_string(0, 0, $groupname, $headlineformat); $groupworksheets[$key]->merge_cells(0, 0, 0, $columncount - 1); if ($allgroupsworksheet !== false) { $allgroupsworksheet->write_string($j, 0, $groupname, $headlineformat); $allgroupsworksheet->merge_cells($j, 0, $j, $columncount - 1); } // Groupinfo on top! $groupworksheets[$key]->write_string(2, 0, $groupinfo[0][0], $groupinfoformat1); $groupworksheets[$key]->merge_cells(2, 0, 2, 1); $groupworksheets[$key]->write(2, 2, $groupinfo[0][1], $groupinfoformat2); $groupworksheets[$key]->write_string(3, 0, $groupinfo[1][0], $groupinfoformat1); $groupworksheets[$key]->merge_cells(3, 0, 3, 1); $groupworksheets[$key]->write(3, 2, $groupinfo[1][1], $groupinfoformat2); $groupworksheets[$key]->write_string(4, 0, $groupinfo[2][0], $groupinfoformat1); $groupworksheets[$key]->merge_cells(4, 0, 4, 1); $groupworksheets[$key]->write(4, 2, $groupinfo[2][1], $groupinfoformat2); $groupworksheets[$key]->write_string(5, 0, $groupinfo[3][0], $groupinfoformat1); $groupworksheets[$key]->merge_cells(5, 0, 5, 1); $groupworksheets[$key]->write(5, 2, $groupinfo[3][1], $groupinfoformat2); if ($allgroupsworksheet !== false) { $allgroupsworksheet->write_string($j + 2, 0, $groupinfo[0][0], $groupinfoformat1); $allgroupsworksheet->merge_cells($j + 2, 0, $j + 2, 1); $allgroupsworksheet->write($j + 2, 2, $groupinfo[0][1], $groupinfoformat2); $allgroupsworksheet->write_string($j + 3, 0, $groupinfo[1][0], $groupinfoformat1); $allgroupsworksheet->merge_cells($j + 3, 0, $j + 3, 1); $allgroupsworksheet->write($j + 3, 2, $groupinfo[1][1], $groupinfoformat2); $allgroupsworksheet->write_string($j + 4, 0, $groupinfo[2][0], $groupinfoformat1); $allgroupsworksheet->merge_cells($j + 4, 0, $j + 4, 1); $allgroupsworksheet->write($j + 4, 2, $groupinfo[2][1], $groupinfoformat2); $allgroupsworksheet->write_string($j + 5, 0, $groupinfo[3][0], $groupinfoformat1); $allgroupsworksheet->merge_cells($j + 5, 0, $j + 5, 1); $allgroupsworksheet->write($j + 5, 2, $groupinfo[3][1], $groupinfoformat2); } // Registrations and queue headline! // First the headline! $k = 0; $groupworksheets[$key]->write_string(7, $k, get_string('status', 'grouptool'), $regheadformat); $k++; // ...k = 1! // First we output every namefield from used by fullname in exact the defined order! foreach ($namefields as $namefield) { $groupworksheets[$key]->write_string(7, $k, get_user_field_name($namefield), $regheadformat); $hidden = in_array($namefield, $collapsed) ? true : false; $columnwidth[$namefield] = empty($columnwidth[$namefield]) ? $columnwidth[0] : $columnwidth[$namefield]; $groupworksheets[$key]->set_column($k, $k, $columnwidth[$namefield], null, $hidden); $k++; } // ...k = n! if (!empty($CFG->showuseridentity)) { $fields = explode(',', $CFG->showuseridentity); $curfieldcount = 1; foreach ($fields as $field) { if ($curfieldcount == count($fields)) { $groupworksheets[$key]->write_string(7, $k, get_user_field_name($field), $regheadlast); } else { $groupworksheets[$key]->write_string(7, $k, get_user_field_name($field), $regheadformat); $curfieldcount++; } $hidden = in_array($field, $collapsed) ? true : false; $columnwidth[$field] = empty($columnwidth[$field]) ? $columnwidth[0] : $columnwidth[$field]; $groupworksheets[$key]->set_column($k, $k, $columnwidth[$field], null, $hidden); $k++; // ...k = n+x! } } else { $groupworksheets[$key]->write_string(7, $k, get_user_field_name('idnumber'), $regheadformat); $hidden = in_array('idnumber', $collapsed) ? true : false; $columnwidth['idnumber'] = empty($columnwidth['idnumber']) ? $columnwidth[0] : $columnwidth['idnumber']; $groupworksheets[$key]->set_column($k, $k, $columnwidth['idnumber'], null, $hidden); $k++; // ...k = n+1! $groupworksheets[$key]->write_string(7, $k, get_user_field_name('email'), $regheadlast); $hidden = in_array('email', $collapsed) ? true : false; $columnwidth['email'] = empty($columnwidth['email']) ? $columnwidth[0] : $columnwidth['email']; $groupworksheets[$key]->set_column($k, $k, $columnwidth['email'], null, $hidden); $k++; // ...k = n+2! } if ($allgroupsworksheet !== false) { $k = 0; $allgroupsworksheet->write_string($j + 7, $k, get_string('status', 'grouptool'), $regheadformat); $k++; // First we output every namefield from used by fullname in exact the defined order! foreach ($namefields as $namefield) { $allgroupsworksheet->write_string($j + 7, $k, get_user_field_name($namefield), $regheadformat); $hidden = in_array($namefield, $collapsed) ? true : false; $columnwidth[$namefield] = empty($columnwidth[$namefield]) ? $columnwidth[0] : $columnwidth[$namefield]; $allgroupsworksheet->set_column($k, $k, $columnwidth[$namefield], null, $hidden); $k++; } // ...k = n! if (!empty($CFG->showuseridentity)) { $fields = explode(',', $CFG->showuseridentity); $curfieldcount = 1; foreach ($fields as $field) { if ($curfieldcount == count($fields)) { $allgroupsworksheet->write_string($j + 7, $k, get_user_field_name($field), $regheadlast); } else { $allgroupsworksheet->write_string($j + 7, $k, get_user_field_name($field), $regheadformat); $curfieldcount++; } $hidden = in_array($field, $collapsed) ? true : false; $columnwidth[$field] = empty($columnwidth[$field]) ? $columnwidth[0] : $columnwidth[$field]; $allgroupsworksheet->set_column($k, $k, $columnwidth[$field], null, $hidden); $k++; // ...k = n+x! } } else { $allgroupsworksheet->write_string($j + 7, $k, get_user_field_name('idnumber'), $regheadformat); $hidden = in_array('idnumber', $collapsed) ? true : false; $columnwidth['idnumber'] = empty($columnwidth['idnumber']) ? $columnwidth[0] : $columnwidth['idnumber']; $allgroupsworksheet->set_column($k, $k, $columnwidth['idnumber'], null, $hidden); $k++; // ...k = n+1! $allgroupsworksheet->write_string($j + 7, $k, get_user_field_name('email'), $regheadlast); $hidden = in_array('email', $collapsed) ? true : false; $columnwidth['email'] = empty($columnwidth['email']) ? $columnwidth[0] : $columnwidth['email']; $allgroupsworksheet->set_column($k, $k, $columnwidth['email'], null, $hidden); $k++; // ...k = n+2! } } // Now the registrations! $i = 0; if (!empty($regdata)) { foreach ($regdata as $reg) { if ($i == 0) { $regentryformat->set_top(2); } else if ($i == 1) { $regentryformat->set_top(1); } $k = 0; $groupworksheets[$key]->write_string(8 + $i, $k, $reg['status'], $regentryformat); $k++; // First we output every namefield from used by fullname in exact the defined order! foreach ($namefields as $namefield) { $groupworksheets[$key]->write_string(8 + $i, $k, $reg[$namefield], $regentryformat); $k++; } // ...k = n! if (!empty($CFG->showuseridentity)) { $fields = explode(',', $CFG->showuseridentity); $curfieldcount = 1; foreach ($fields as $field) { if ($curfieldcount == count($fields)) { $groupworksheets[$key]->write_string(8 + $i, $k, $reg[$field], $regentrylast); } else { $groupworksheets[$key]->write_string(8 + $i, $k, $reg[$field], $regentryformat); $curfieldcount++; } $k++; // ...k = n+x! } } else { $groupworksheets[$key]->write_string(8 + $i, $k, $reg['idnumber'], $regentryformat); $k++; // ...k = n+1! $groupworksheets[$key]->write_string(8 + $i, $k, $reg['email'], $regentrylast); $k++; // ...k = n+2! } if ($allgroupsworksheet !== false) { $k = 0; $allgroupsworksheet->write_string($j + 8 + $i, $k, $reg['status'], $regentryformat); $k++; // First we output every namefield from used by fullname in exact the defined order! foreach ($namefields as $namefield) { $allgroupsworksheet->write_string($j + 8 + $i, $k, $reg[$namefield], $regentryformat); $k++; } // ...k = n! if (!empty($CFG->showuseridentity)) { $fields = explode(',', $CFG->showuseridentity); $curfieldcount = 1; foreach ($fields as $field) { if ($curfieldcount == count($fields)) { $allgroupsworksheet->write_string($j + 8 + $i, $k, $reg[$field], $regentrylast); } else { $allgroupsworksheet->write_string($j + 8 + $i, $k, $reg[$field], $regentryformat); $curfieldcount++; } $k++; // ...k = n+x! } } else { $allgroupsworksheet->write_string($j + 8 + $i, $k, $reg['idnumber'], $regentryformat); $k++; // ...k = n+1! $allgroupsworksheet->write_string($j + 8 + $i, $k, $reg['email'], $regentrylast); $k++; // ...k = n+2! } } $i++; } } else if (count($mregdata) == 0) { $groupworksheets[$key]->write_string(8 + $i, 0, get_string('no_registrations', 'grouptool'), $noregentriesformat); $groupworksheets[$key]->merge_cells(8 + $i, 0, 8 + $i, 3); if ($allgroupsworksheet !== false) { $allgroupsworksheet->write_string($j + 8 + $i, 0, get_string('no_registrations', 'grouptool'), $noregentriesformat); $allgroupsworksheet->merge_cells($j + 8 + $i, 0, $j + 8 + $i, 3); } $i++; } if (count($mregdata) >= 1) { foreach ($mregdata as $mreg) { if ($i == 0) { $regentryformat->set_top(2); } else if ($i == 1) { $regentryformat->set_top(1); } $k = 0; $groupworksheets[$key]->write_string(8 + $i, $k, '?', $regentryformat); $k++; // First we output every namefield from used by fullname in exact the defined order! foreach ($namefields as $namefield) { $groupworksheets[$key]->write_string(8 + $i, $k, $mreg[$namefield], $regentryformat); $k++; } // ...k = n! if (!empty($CFG->showuseridentity)) { $fields = explode(',', $CFG->showuseridentity); $curfieldcount = 1; foreach ($fields as $field) { if ($curfieldcount == count($fields)) { $groupworksheets[$key]->write_string(8 + $i, $k, $mreg[$field], $regentrylast); } else { $groupworksheets[$key]->write_string(8 + $i, $k, $mreg[$field], $regentryformat); $curfieldcount++; } $k++; // ...k = n+x! } } else { $groupworksheets[$key]->write_string(8 + $i, $k, $mreg['idnumber'], $regentryformat); $k++; // ...k = n+1! $groupworksheets[$key]->write_string(8 + $i, $k, $mreg['email'], $regentrylast); $k++; // ...k = n+2! } if ($allgroupsworksheet !== false) { $k = 0; $allgroupsworksheet->write_string($j + 8 + $i, $k, '?', $regentryformat); $k++; // First we output every namefield from used by fullname in exact the defined order! foreach ($namefields as $namefield) { $allgroupsworksheet->write_string($j + 8 + $i, $k, $mreg[$namefield], $regentryformat); $k++; } // ...k = n! if (!empty($CFG->showuseridentity)) { $fields = explode(',', $CFG->showuseridentity); $curfieldcount = 1; foreach ($fields as $field) { if ($curfieldcount == count($fields)) { $allgroupsworksheet->write_string($j + 8 + $i, $k, $mreg[$field], $regentrylast); } else { $allgroupsworksheet->write_string($j + 8 + $i, $k, $mreg[$field], $regentryformat); $curfieldcount++; } $k++; // ...k = n+x! } } else { $allgroupsworksheet->write_string($j + 8 + $i, $k, $mreg['idnumber'], $regentryformat); $k++; // ...k = n+1! $allgroupsworksheet->write_string($j + 8 + $i, $k, $mreg['email'], $regentrylast); $k++; // ...k = n+2! } } $i++; } } // Don't forget the queue! if (!empty($queuedata)) { foreach ($queuedata as $queue) { if ($i == 0) { $regentryformat->set_top(2); } else if ($i == 1) { $regentryformat->set_top(1); } $k = 0; $groupworksheets[$key]->write_string(8 + $i, $k, $queue['rank'], $regentryformat); $k++; // First we output every namefield from used by fullname in exact the defined order! foreach ($namefields as $namefield) { $groupworksheets[$key]->write_string(8 + $i, $k, $queue[$namefield], $regentryformat); $k++; } // ...k = n! if (!empty($CFG->showuseridentity)) { $fields = explode(',', $CFG->showuseridentity); $curfieldcount = 1; foreach ($fields as $field) { if ($curfieldcount == count($fields)) { $groupworksheets[$key]->write_string(8 + $i, $k, $queue[$field], $regentrylast); } else { $groupworksheets[$key]->write_string(8 + $i, $k, $queue[$field], $regentryformat); $curfieldcount++; } $k++; // ...k = n+x! } } else { $groupworksheets[$key]->write_string(8 + $i, $k, $queue['idnumber'], $regentryformat); $k++; // ...k = n+1! $groupworksheets[$key]->write_string(8 + $i, $k, $queue['email'], $regentrylast); $k++; // ...k = n+2! } if ($allgroupsworksheet !== false) { $k = 0; $allgroupsworksheet->write_string($j + 8 + $i, $k, $queue['rank'], $regentryformat); $k++; // First we output every namefield from used by fullname in exact the defined order! foreach ($namefields as $namefield) { $allgroupsworksheet->write_string($j + 8 + $i, $k, $queue[$namefield], $regentryformat); $k++; } // ...k = n! if (!empty($CFG->showuseridentity)) { $fields = explode(',', $CFG->showuseridentity); $curfieldcount = 1; foreach ($fields as $field) { if ($curfieldcount == count($fields)) { $allgroupsworksheet->write_string($j + 8 + $i, $k, $queue[$field], $regentrylast); } else { $allgroupsworksheet->write_string($j + 8 + $i, $k, $queue[$field], $regentryformat); $curfieldcount++; } $k++; // ...k = n+x! } } else { $allgroupsworksheet->write_string($j + 8 + $i, $k, $queue['idnumber'], $regentryformat); $k++; // ...k = n+1! $allgroupsworksheet->write_string($j + 8 + $i, $k, $queue['email'], $regentrylast); $k++; // ...k = n+2! } } $i++; } } else { $groupworksheets[$key]->write_string(8 + $i, 0, get_string('nobody_queued', 'grouptool'), $noqueueentriesformat); $groupworksheets[$key]->merge_cells(8 + $i, 0, 8 + $i, 3); if ($allgroupsworksheet !== false) { $allgroupsworksheet->write_string($j + 8 + $i, 0, get_string('nobody_queued', 'grouptool'), $noqueueentriesformat); $allgroupsworksheet->merge_cells($j + 8 + $i, 0, $j + 8 + $i, 3); } $i++; } $j += 9 + $i; // One row space between groups! } } } /** * outputs generated ods-file for overview (forces download) * * @param int $groupid optional get only this group * @param int $groupingid optional get only this grouping * @param bool $includeinactive optional include inactive groups too! * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function download_overview_ods($groupid=0, $groupingid=0, $includeinactive=false) { global $CFG; require_once($CFG->libdir . "/odslib.class.php"); $coursename = format_string($this->course->fullname, true, array('context' => context_module::instance($this->cm->id))); $grouptoolname = $this->grouptool->name; if (!empty($groupid)) { $filename = $coursename . '_' . $grouptoolname . '_' . groups_get_group_name($groupid).'_'.get_string('overview', 'grouptool'); } else if (!empty($groupingid)) { $filename = $coursename . '_' . $grouptoolname . '_' . groups_get_grouping_name($groupingid).'_'.get_string('overview', 'grouptool'); } else { $filename = $coursename . '_' . $grouptoolname . '_' . get_string('group').' '.get_string('overview', 'grouptool'); } $filename = clean_filename("$filename.ods"); $workbook = new MoodleODSWorkbook("-"); $groups = $this->group_overview_table($groupingid, $groupid, true, $includeinactive); $this->overview_fill_workbook($workbook, $groups); $workbook->send($filename); $workbook->close(); } /** * outputs generated xlsx-file for overview (forces download) * * @param int $groupid optional get only this group * @param int $groupingid optional get only this grouping * @param bool $includeinactive optional include inactive groups too! * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function download_overview_xlsx($groupid = 0, $groupingid = 0, $includeinactive=false) { global $CFG; require_once($CFG->libdir . "/excellib.class.php"); $coursename = format_string($this->course->fullname, true, array('context' => context_module::instance($this->cm->id))); $grouptoolname = $this->grouptool->name; if (!empty($groupid)) { $filename = clean_filename($coursename . '_' . $grouptoolname . '_' . groups_get_group_name($groupid).'_'. get_string('overview', 'grouptool')); } else if (!empty($groupingid)) { $filename = clean_filename($coursename . '_' . $grouptoolname . '_' . groups_get_grouping_name($groupingid).'_'. get_string('overview', 'grouptool')); } else { $filename = clean_filename($coursename . '_' . $grouptoolname . '_' . get_string('group').' '.get_string('overview', 'grouptool')); } $filename = clean_filename("$filename.xlsx"); $workbook = new MoodleExcelWorkbook("-", 'Excel2007'); $groups = $this->group_overview_table($groupingid, $groupid, true, $includeinactive); $this->overview_fill_workbook($workbook, $groups); $workbook->send($filename); $workbook->close(); } /** * get object containing informatino about syncronisation of active-groups with moodle-groups * * @param int $grouptoolid optional get stats for this grouptool-instance * uses $this->instance if zero * @return array (global out of sync, array of objects with sync-status for each group) * @throws dml_exception */ private function get_sync_status($grouptoolid = 0) { global $DB; $outofsync = false; if (empty($grouptoolid)) { $grouptoolid = $this->grouptool->id; } // We use MAX to trick postgres into thinking this is a full group_by statement! $sql = "SELECT agrps.id AS agrpid, MAX(agrps.groupid) AS groupid, COUNT(DISTINCT reg.userid) AS grptoolregs, COUNT(DISTINCT mreg.userid) AS mdlregs FROM {grouptool_agrps} agrps LEFT JOIN {grouptool_registered} reg ON agrps.id = reg.agrpid AND reg.modified_by >= 0 LEFT JOIN {groups_members} mreg ON agrps.groupid = mreg.groupid AND reg.userid = mreg.userid WHERE agrps.active = 1 AND agrps.grouptoolid = ? GROUP BY agrps.id"; $return = $DB->get_records_sql($sql, [$grouptoolid]); foreach ($return as $key => $group) { $return[$key]->status = ($group->grptoolregs > $group->mdlregs) ? GROUPTOOL_OUTDATED : GROUPTOOL_UPTODATE; $outofsync |= ($return[$key]->status == GROUPTOOL_OUTDATED); } return [$outofsync, $return]; } /** * push in grouptool registered users to moodle-groups * * @param int $groupid optional only for this group * @param int $groupingid optional only for this grouping * @param bool $previewonly optional get only the preview * @return array($error, $message) * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ public function push_registrations($groupid=0, $groupingid=0, $previewonly=false) { global $DB, $OUTPUT; // Trigger the event! \mod_grouptool\event\registration_push_started::create_from_object($this->cm)->trigger(); $userinfo = get_enrolled_users($this->context); $return = []; // Get active groups filtered by groupid, grouping_id, grouptoolid! $agrps = $this->get_active_groups(true, false, 0, $groupid, $groupingid); foreach ($agrps as $groupid => $agrp) { foreach ($agrp->registered as $reg) { $info = new stdClass(); if (!key_exists($reg->userid, $userinfo)) { $userinfo[$reg->userid] = $DB->get_record('user', ['id' => $reg->userid]); } $info->username = fullname($userinfo[$reg->userid]); $info->groupname = $agrp->name; if (!groups_is_member($groupid, $reg->userid)) { // Add to group if is not already! if (!$previewonly) { if (!is_enrolled($this->context, $reg->userid)) { /* * if user's not enrolled already we force manual enrollment in course, * so we can add the user to the group */ try { $this->force_enrol_student($reg->userid); } catch (Exception $e) { $return[] = $OUTPUT->notification($e->getMessage(), \core\output\notification::NOTIFY_ERROR); } catch (Throwable $t) { $return[] = $OUTPUT->notification($t->getMessage(), \core\output\notification::NOTIFY_ERROR); } } if (groups_add_member($groupid, $reg->userid)) { $return[] = html_writer::tag('div', get_string('added_member', 'grouptool', $info), ['class' => 'notifysuccess']); } else { $return[] = html_writer::tag('div', get_string('could_not_add', 'grouptool', $info), ['class' => 'notifyproblem']); } } else { $return[] = html_writer::tag('div', get_string('add_member', 'grouptool', $info), ['class' => 'notifysuccess']); } } else { $return[] = html_writer::tag('div', get_string('already_member', 'grouptool', $info), ['class' => 'ignored']); } } } switch (count($return)) { default: return [false, implode("<br />\n", $return)]; break; case 1: return [false, current($return)]; break; case 0: return [true, get_string('nothing_to_push', 'grouptool')]; break; } } /** * Render link for Member-List * * @param stdClass $group active group object, for which the members should be displayed * @return string HTML fragment * @throws coding_exception * @throws dml_exception * @throws moodle_exception */ private function render_members_link($group) { global $CFG, $DB; $output = get_string('show_members', 'grouptool'); // Now create the link around it - we need https on loginhttps pages! $url = new moodle_url($CFG->httpswwwroot.'/mod/grouptool/showmembers.php', [ 'agrpid' => $group->agrpid, 'contextid' => $this->context->id ]); $attributes = ['href' => $url, 'title' => get_string('show_members', 'grouptool')]; $id = html_writer::random_id('showmembers'); $attributes['id'] = $id; $attributes['data-name'] = $group->name; // Add data attributes for JS! $registered = []; if (!empty($group->registered)) { foreach ($group->registered as $cur) { $registered[] = $cur->userid; } } $members = array_keys($group->moodle_members); $queued = []; if (!empty($group->queued)) { foreach ($group->queued as $cur) { $queued[$cur->userid] = $cur->userid; } } // Get all registered users with moodle-group-membership! $absregs = array_intersect($registered, $members); $absregs = array_combine($absregs, $absregs); // Get all registered users without moodle-group-membership! $gtregs = array_diff($registered, $members); $gtregs = array_combine($gtregs, $gtregs); // Get all moodle-group-members without registration! $mdlregs = array_diff($members, $registered); $mdlregs = array_combine($mdlregs, $mdlregs); $showidnumber = has_capability('mod/grouptool:view_regs_group_view', $this->context) || has_capability('mod/grouptool:view_regs_course_view', $this->context); $userfields = get_all_user_name_fields(true); if ($showidnumber) { $fields = "id,idnumber,".$userfields; } else { $fields = "id,".$userfields; } // Cache needed user records right now! $users = $DB->get_records_list("user", 'id', $gtregs + $queued, null, $fields); $attributes['data-absregs'] = []; if (!empty($absregs)) { foreach ($absregs as $cur) { // These user records are fully fetched in $group->moodle_members! $attributes['data-absregs'][] = [ 'idnumber' => $showidnumber ? $group->moodle_members[$cur]->idnumber : '', 'fullname' => fullname($group->moodle_members[$cur]), 'id' => $cur ]; } } $attributes['data-absregs'] = json_encode($attributes['data-absregs']); $attributes['data-gtregs'] = []; if (!empty($gtregs)) { foreach ($gtregs as $cur) { $attributes['data-gtregs'][] = [ 'idnumber' => $showidnumber ? $users[$cur]->idnumber : '', 'fullname' => fullname($users[$cur]), 'id' => $cur ]; } } $attributes['data-gtregs'] = json_encode($attributes['data-gtregs']); $attributes['data-mregs'] = []; if (!empty($mdlregs)) { foreach ($mdlregs as $cur) { $attributes['data-mregs'][] = [ 'idnumber' => $showidnumber ? $group->moodle_members[$cur]->idnumber : '', 'fullname' => fullname($group->moodle_members[$cur]), 'id' => $cur ]; } } $attributes['data-mregs'] = json_encode($attributes['data-mregs']); $attributes['data-queued'] = []; if (!empty($queued)) { $queuedlist = $DB->get_records('grouptool_queued', ['agrpid' => $group->agrpid], 'timestamp ASC'); foreach ($queued as $cur) { $attributes['data-queued'][] = [ 'idnumber' => $showidnumber ? $users[$cur]->idnumber : '', 'fullname' => fullname($users[$cur]), 'id' => $cur, 'rank' => $this->get_rank_in_queue($queuedlist, $cur) ]; } } $attributes['data-queued'] = json_encode($attributes['data-queued']); $output = html_writer::tag('a', $output, $attributes); // And finally wrap in a span! return html_writer::tag('span', $output, ['class' => 'showmembers memberstooltip']); } /** * view overview tab * * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function view_overview() { global $PAGE, $OUTPUT; $groupid = optional_param('groupid', 0, PARAM_INT); $groupingid = optional_param('groupingid', 0, PARAM_INT); $orientation = optional_param('orientation', 0, PARAM_BOOL); $includeinactive = optional_param('inactive', 0, PARAM_BOOL); $url = new moodle_url($PAGE->url, [ 'sesskey' => sesskey(), 'groupid' => $groupid, 'groupingid' => $groupingid, 'orientation' => $orientation, 'inactive' => $includeinactive ]); // Process submitted form! if (data_submitted() && confirm_sesskey() && optional_param('confirm', 0, PARAM_BOOL)) { // Execution has been confirmed?! $hideform = 0; $pushtomdl = optional_param('pushtomdl', 0, PARAM_BOOL); if ($pushtomdl) { list($error, $message) = $this->push_registrations($groupid, $groupingid); if ($error) { echo $OUTPUT->notification($message, \core\output\notification::NOTIFY_ERROR); } else { echo $OUTPUT->notification($message, \core\output\notification::NOTIFY_SUCCESS); } } } else if (data_submitted() && confirm_sesskey()) { // Display confirm-dialog! $hideform = 1; $pushtomdl = optional_param('pushtomdl', 0, PARAM_BOOL); if ($pushtomdl) { // Try only! list($error, $message) = $this->push_registrations($groupid, $groupingid, true); $attr = []; $attr['confirm'] = 1; $attr['pushtomdl'] = 1; $attr['sesskey'] = sesskey(); $continue = new moodle_url($PAGE->url, $attr); $cancel = new moodle_url($PAGE->url); if ($error) { $continue->remove_params('confirm', 'group'); $continue = new single_button($continue, get_string('continue'), 'get'); $cancel = null; } echo $this->confirm($message, $continue, $cancel); } else { $hideform = 0; } } else { $hideform = 0; } if (!$hideform) { $groupingselect = $this->get_grouping_select($url, $groupingid); $groupselect = $this->get_groups_select($url, $groupingid, $groupid); $orientationselect = $this->get_orientation_select($url, $orientation); if ($includeinactive) { $inactivetext = get_string('inactivegroups_hide', 'grouptool'); $inactiveurl = new moodle_url($url, ['inactive' => 0]); } else { $inactivetext = get_string('inactivegroups_show', 'grouptool'); $inactiveurl = new moodle_url($url, ['inactive' => 1]); } $syncstatus = $this->get_sync_status(); if ($syncstatus[0]) { /* * Out of sync? --> show button to get registrations from grouptool to moodle * (just register not already registered persons and let the others be) */ $url = new moodle_url($PAGE->url, ['pushtomdl' => 1, 'sesskey' => sesskey()]); $button = new single_button($url, get_string('updatemdlgrps', 'grouptool'), 'post', true); echo $OUTPUT->box(html_writer::empty_tag('br'). $OUTPUT->render($button). html_writer::empty_tag('br'), 'generalbox centered'); } echo html_writer::tag('div', get_string('grouping', 'group').' '. $OUTPUT->render($groupingselect), ['class' => 'centered grouptool_overview_filter']). html_writer::tag('div', get_string('group', 'group').' '. $OUTPUT->render($groupselect), ['class' => 'centered grouptool_overview_filter']). html_writer::tag('div', get_string('orientation', 'grouptool').' '. $OUTPUT->render($orientationselect), ['class' => 'centered grouptool_overview_filter']). html_writer::tag('div', html_writer::link($inactiveurl, $inactivetext), ['class' => 'centered grouptool_overview_filter']); // If we don't only get the data, the output happens directly per group! $this->group_overview_table($groupingid, $groupid, false, $includeinactive); } } /** * Returns a single select to change currently selected grouping. * * @param moodle_url $url Base URL to use * @param int $groupingid Currently active grouping ID or 0 * @return single_select * @throws coding_exception */ protected function get_grouping_select($url, $groupingid) { $groupings = groups_get_all_groupings($this->course->id); $options = [0 => get_string('all')]; if (count($groupings)) { foreach ($groupings as $grouping) { $options[$grouping->id] = $grouping->name; } } return new single_select($url, 'groupingid', $options, $groupingid, false); } /** * Returns a single select to change currently selected group. * * @param moodle_url $url Base URL to use * @param int $groupingid Currently active grouping ID or 0 * @param int $groupid Currently active group ID or 0 * @return single_select * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ protected function get_groups_select($url, $groupingid, $groupid) { global $OUTPUT; $groups = $this->get_active_groups(false, false, 0, 0, $groupingid); $options = [0 => get_string('all')]; if (count($groups)) { foreach ($groups as $group) { $options[$group->id] = $group->name; } } if (!key_exists($groupid, $options)) { $groupid = 0; $url->param('groupid', 0); echo $OUTPUT->box($OUTPUT->notification(get_string('group_not_in_grouping', 'grouptool'). html_writer::empty_tag('br'). get_string('switched_to_all_groups', 'grouptool'), \core\output\notification::NOTIFY_ERROR), 'generalbox centered'); } return new single_select($url, 'groupid', $options, $groupid, false); } /** * Returns a single select to change currently selected page-orientation. * * @param moodle_url $url Base URL to use * @param int $orientation Currently active orientation * @return single_select * @throws coding_exception */ protected function get_orientation_select($url, $orientation) { static $options = null; if (!$options) { $options = [ 0 => get_string('portrait', 'grouptool'), 1 => get_string('landscape', 'grouptool') ]; } return new single_select($url, 'orientation', $options, $orientation, false); } /** * get information about particular users with their registrations/queues * * @param int $groupingid optional get only this grouping * @param int $groupid optional get only this group * @param int|array $userids optional get only this user(s) * @param stdClass[] $orderby array how data should be sorted (column as key and ASC/DESC as value) * @return stdClass[] array of objects records from DB with all necessary data * @throws coding_exception * @throws dml_exception * @throws required_capability_exception */ public function get_user_data($groupingid = 0, $groupid = 0, $userids = 0, $orderby = []) { global $DB, $OUTPUT; // After which table-fields can we sort? $sortable = ['firstname', 'lastname', 'idnumber', 'email']; // Indexed by agrpid! $agrps = $this->get_active_groups(false, false, 0, $groupid, $groupingid, false); $agrpids = array_keys($agrps); if (!empty($agrpids)) { list($agrpsql, $agrpparams) = $DB->get_in_or_equal($agrpids); } else { $agrpsql = ''; $agrpparams = []; echo $OUTPUT->box($OUTPUT->notification(get_string('no_groups_to_display', 'grouptool'), \core\output\notification::NOTIFY_ERROR), 'generalbox centered'); } if (!empty($userids)) { if (!is_array($userids)) { $userids = [$userids]; } list($usersql, $userparams) = $DB->get_in_or_equal($userids); } else { $usersql = ' LIKE *'; $userparams = []; } $extrauserfields = get_extra_user_fields_sql($this->context, 'u'); $mainuserfields = user_picture::fields('u', ['idnumber', 'email']); $orderbystring = ""; if (!empty($orderby)) { foreach ($orderby as $field => $direction) { if (in_array($field, $sortable)) { if ($orderbystring != "") { $orderbystring .= ", "; } else { $orderbystring .= " ORDER BY"; } $orderbystring .= " ".$field." ". ((!empty($direction) && $direction == 'ASC') ? 'ASC' : 'DESC'); } else { unset($orderby[$field]); } } } $sql = "SELECT $mainuserfields $extrauserfields ". "FROM {user} u ". "WHERE u.id ".$usersql. $orderbystring; $params = array_merge($userparams); $data = $DB->get_records_sql($sql, $params); // Add reg and queue data... if (!empty($agrpsql)) { foreach ($data as &$cur) { $sql = "SELECT agrps.id FROM {grouptool_registered} regs LEFT JOIN {grouptool_agrps} agrps ON regs.agrpid = agrps.id LEFT JOIN {groups} grps ON agrps.groupid = grps.id WHERE regs.modified_by >= 0 AND regs.userid = ? AND regs.agrpid ".$agrpsql; $params = array_merge([$cur->id], $agrpparams); $cur->regs = $DB->get_fieldset_sql($sql, $params); $sql = "SELECT agrps.id FROM {grouptool_queued} queued LEFT JOIN {grouptool_agrps} agrps ON queued.agrpid = agrps.id LEFT JOIN {groups} grps ON agrps.groupid = grps.id WHERE queued.userid = ? AND queued.agrpid ".$agrpsql; $params = array_merge([$cur->id], $agrpparams); $cur->queued = $DB->get_fieldset_sql($sql, $params); } } return $data; } /** * Return picture indicating sort-direction if data is primarily sorted by this column * or empty string if not * * @param stdClass[] $orderby array containing current state of sorting * @param string $search columnname to print sortpic for * @return string html fragment with sort-pic or empty string */ private function pic_if_sorted($orderby = [], $search = '') { global $OUTPUT; $keys = array_keys($orderby); if (reset($keys) == $search) { if ($orderby[$search] == 'ASC') { return $OUTPUT->pix_icon('t/up', 'sorted ASC'); } else { return $OUTPUT->pix_icon('t/down', 'sorted DESC'); } } return ""; } /** * returns collapselink (= symbol to show column or column-name and symbol to hide column) * * @param string[] $collapsed array with collapsed columns * @param string $search column-name to print link for * @return string html-fragment with icon to show column or column header text with icon to hide * column * @throws moodle_exception */ private function collapselink($collapsed = [], $search) { global $PAGE, $OUTPUT; if (in_array($search, $collapsed)) { $url = new moodle_url($PAGE->url, ['tshow' => $search]); $pic = $OUTPUT->pix_icon('t/switch_plus', 'show'); } else { $url = new moodle_url($PAGE->url, ['thide' => $search]); $pic = $OUTPUT->pix_icon('t/switch_minus', 'hide'); } return html_writer::tag('div', html_writer::link($url, $pic), ['class' => 'collapselink']); } /** * Returns nice download links for all formats based on downloadurl and groupid * * @param moodle_url $downloadurl The base download URL to use * @param int $groupid (optional) ID of group to use for the download or 0 for all groups download * @return string HTML snippet with download links encapsulated in DIV * @throws coding_exception * @throws moodle_exception */ protected function get_download_links($downloadurl, $groupid = 0) { if (has_capability('mod/grouptool:export', $this->context)) { $class = 'download'; if ($groupid) { $downloadurl = new moodle_url($downloadurl, ['groupid' => $groupid]); $downloadtxt = get_string('download'); } else { $downloadtxt = get_string('downloadall'); $class .= ' all'; } $txturl = new moodle_url($downloadurl, ['format' => GROUPTOOL_TXT]); $xlsxurl = new moodle_url($downloadurl, ['format' => GROUPTOOL_XLSX]); $pdfurl = new moodle_url($downloadurl, ['format' => GROUPTOOL_PDF]); $odsurl = new moodle_url($downloadurl, ['format' => GROUPTOOL_ODS]); $downloadlinks = html_writer::tag('span', $downloadtxt.":", ['class' => 'title']).' '. html_writer::link($txturl, '.TXT').' '. html_writer::link($xlsxurl, '.XLSX').' '. html_writer::link($pdfurl, '.PDF').' '. html_writer::link($odsurl, '.ODS'); return html_writer::tag('div', $downloadlinks, ['class' => $class]); } else { return ''; } } /** * get all data necessary for displaying/exporting userlist table * * @param int $groupingid optional get only this grouping * @param int $groupid optional get only this group (groupid not agroupid!) * @param bool $onlydata optional return object with raw data not html-fragment-string * @return stdClass[]|bool true if table is output or raw data as array of objects * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function userlist_table($groupingid = 0, $groupid = 0, $onlydata = false) { global $OUTPUT, $CFG, $DB, $PAGE, $SESSION; if (!isset($SESSION->mod_grouptool->userlist)) { $SESSION->mod_grouptool->userlist = new stdClass(); } // Handles order direction! if (!isset($SESSION->mod_grouptool->userlist->orderby)) { $SESSION->mod_grouptool->userlist->orderby = []; } $orderby = $SESSION->mod_grouptool->userlist->orderby; if ($tsort = optional_param('tsort', 0, PARAM_ALPHA)) { $olddir = 'DESC'; if (key_exists($tsort, $orderby)) { $olddir = $orderby[$tsort]; unset($orderby[$tsort]); } // Insert as first element and rebuild! $oldorderby = array_keys($orderby); $oldorderdir = array_values($orderby); array_unshift($oldorderby, $tsort); array_unshift($oldorderdir, (($olddir == 'DESC') ? 'ASC' : 'DESC')); $orderby = array_combine($oldorderby, $oldorderdir); $SESSION->mod_grouptool->userlist->orderby = $orderby; } // Handles collapsed columns! if (!isset($SESSION->mod_grouptool->userlist->collapsed)) { $SESSION->mod_grouptool->userlist->collapsed = []; } $collapsed = $SESSION->mod_grouptool->userlist->collapsed; if ($thide = optional_param('thide', 0, PARAM_ALPHA)) { if (!in_array($thide, $collapsed)) { array_push($collapsed, $thide); } $SESSION->mod_grouptool->userlist->collapsed = $collapsed; } if ($tshow = optional_param('tshow', 0, PARAM_ALPHA)) { foreach ($collapsed as $key => $value) { if ($value == $tshow) { unset($collapsed[$key]); } } $SESSION->mod_grouptool->userlist->collapsed = $collapsed; } $downloadurl = ''; if (!$onlydata) { flush(); $orientation = optional_param('orientation', 0, PARAM_BOOL); $downloadurl = new moodle_url('/mod/grouptool/download.php', [ 'id' => $this->cm->id, 'groupingid' => $groupingid, 'groupid' => $groupid, 'orientation' => $orientation, 'sesskey' => sesskey(), 'tab' => 'userlist' ]); } // Get all ppl that are allowed to register! list($esql, $params) = get_enrolled_sql($this->context, 'mod/grouptool:register'); $sql = "SELECT u.id FROM {user} u LEFT JOIN ($esql) eu ON eu.id=u.id WHERE u.deleted = 0 AND eu.id=u.id "; $groups = groups_get_all_groups($this->course->id, 0, $groupingid); if (!empty($groupingid) && !empty($groups)) { // Get all groupings groups! $ufields = user_picture::fields('u', ['idnumber']); $groupingusers = groups_get_grouping_members($groupingid, 'DISTINCT u.id, '.$ufields); if (empty($groupingusers)) { $groupingusers = []; } else { $groupingusers = array_keys($groupingusers); } list($groupssql, $groupsparams) = $DB->get_in_or_equal(array_keys($groups)); $groupingusers2 = $DB->get_fieldset_sql(" SELECT DISTINCT u.id FROM {user} u LEFT JOIN {grouptool_registered} reg ON u.id = reg.userid AND reg.modified_by >= 0 LEFT JOIN {grouptool_queued} queue ON u.id = queue.userid LEFT JOIN {grouptool_agrps} agrp ON reg.agrpid = agrp.id OR queue.agrpid = agrp.id WHERE agrp.groupid ".$groupssql, $groupsparams); $groupingusers = array_merge($groupingusers, $groupingusers2); if (empty($groupingusers)) { $userssql = " = :groupingparam"; $groupingparams = ['groupingparam' => -1]; } else { list($userssql, $groupingparams) = $DB->get_in_or_equal($groupingusers, SQL_PARAMS_NAMED); } // Extend sql to only include people registered in moodle-group/grouptool-group or queued in grouptool group! $sql .= " AND u.id ".$userssql; $params = array_merge($params, $groupingparams); } if (!empty($groupid)) { // Same as with groupingid but just with 1 group! // Get all group members! $ufields = user_picture::fields('u', ['idnumber']); $groupusers = groups_get_members($groupid, 'DISTINCT u.id, '.$ufields); if (empty($groupusers)) { $groupusers = []; } else { $groupusers = array_keys($groupusers); } $groupusers2 = $DB->get_fieldset_sql(" SELECT DISTINCT u.id FROM {user} u LEFT JOIN {grouptool_registered} reg ON u.id = reg.userid AND reg.modified_by >= 0 LEFT JOIN {grouptool_queued} queue ON u.id = queue.userid LEFT JOIN {grouptool_agrps} agrp ON reg.agrpid = agrp.id OR queue.agrpid = agrp.id WHERE agrp.groupid = ?", [$groupid]); $groupusers = array_merge($groupusers, $groupusers2); if (empty($groupusers)) { $userssql = " = :groupparam"; $groupparams = ['groupparam' => -1]; } else { list($userssql, $groupparams) = $DB->get_in_or_equal($groupusers, SQL_PARAMS_NAMED); } // Extend sql to only include people registered in moodle-group/grouptool-group or queued in grouptool group! $sql .= " AND u.id ".$userssql; $params = array_merge($params, $groupparams); } $users = $DB->get_records_sql($sql, $params); if (!$onlydata) { echo $this->get_download_links($downloadurl); flush(); } if (!empty($users)) { $users = array_keys($users); $userdata = $this->get_user_data($groupingid, $groupid, $users, $orderby); } else { if (!$onlydata) { echo $OUTPUT->box($OUTPUT->notification(get_string('no_users_to_display', 'grouptool'), \core\output\notification::NOTIFY_ERROR), 'centered generalbox'); } else { return get_string('no_users_to_display', 'grouptool'); } } $groupinfo = $this->get_active_groups(false, false, 0, $groupid, $groupingid, false); // We create a dummy user-object to get the fullname-format! $dummy = new stdClass(); $namefields = get_all_user_name_fields(); foreach ($namefields as $namefield) { $dummy->$namefield = $namefield; } $fullnameformat = fullname($dummy); // Now get the ones used in fullname in the correct order! $namefields = order_in_string($namefields, $fullnameformat); $head = []; $rows = []; if (!$onlydata) { echo html_writer::start_tag('table', ['class' => 'centeredblock userlist table table-striped table-hover table-condensed']); echo html_writer::start_tag('thead'); echo html_writer::start_tag('tr'); echo html_writer::tag('th', $this->collapselink($collapsed, 'picture'), ['class' => '']); flush(); if (!in_array('fullname', $collapsed)) { $firstnamelink = html_writer::link(new moodle_url($PAGE->url, ['tsort' => 'firstname']), get_string('firstname'). $this->pic_if_sorted($orderby, 'firstname')); $surnamelink = html_writer::link(new moodle_url($PAGE->url, ['tsort' => 'lastname']), get_string('lastname'). $this->pic_if_sorted($orderby, 'lastname')); $fullname = html_writer::tag('div', get_string('fullname'). html_writer::empty_tag('br'). $firstnamelink.' / '.$surnamelink); echo html_writer::tag('th', $fullname.$this->collapselink($collapsed, 'fullname'), ['class' => '']); } else { echo html_writer::tag('th', $this->collapselink($collapsed, 'fullname'), ['class' => '']); } if (!in_array('idnumber', $collapsed)) { $idnumberlink = html_writer::link(new moodle_url($PAGE->url, ['tsort' => 'idnumber']), get_string('idnumber'). $this->pic_if_sorted($orderby, 'idnumber')); echo html_writer::tag('th', $idnumberlink.$this->collapselink($collapsed, 'idnumber'), ['class' => '']); } else { echo html_writer::tag('th', $this->collapselink($collapsed, 'idnumber'), ['class' => '']); } if (!in_array('email', $collapsed)) { $emaillink = html_writer::link(new moodle_url($PAGE->url, ['tsort' => 'email']), get_string('email'). $this->pic_if_sorted($orderby, 'email')); echo html_writer::tag('th', $emaillink.$this->collapselink($collapsed, 'email'), ['class' => '']); } else { echo html_writer::tag('th', $this->collapselink($collapsed, 'email'), ['class' => '']); } if (!in_array('registrations', $collapsed)) { $registrationslink = get_string('registrations', 'grouptool'); echo html_writer::tag('th', $registrationslink. $this->collapselink($collapsed, 'registrations'), ['class' => '']); } else { echo html_writer::tag('th', $this->collapselink($collapsed, 'registrations'), ['class' => '']); } if (!in_array('queues', $collapsed)) { $queueslink = get_string('queues', 'grouptool').' ('.get_string('rank', 'grouptool').')'; echo html_writer::tag('th', $queueslink. $this->collapselink($collapsed, 'queues'), ['class' => '']); } else { echo html_writer::tag('th', $this->collapselink($collapsed, 'queues'), ['class' => '']); } echo html_writer::end_tag('tr'); echo html_writer::end_tag('thead'); } else { $head = ['name' => get_string('fullname')]; foreach ($namefields as $namefield) { $head[$namefield] = get_user_field_name($namefield); } if (empty($CFG->showuseridentity)) { $head['idnumber'] = get_user_field_name('idnumber'); } else { $fields = explode(',', $CFG->showuseridentity); foreach ($fields as $field) { $head[$field] = get_user_field_name($field); } } $head['idnumber'] = get_user_field_name('idnumber'); $head['email'] = get_user_field_name('email'); $head['registrations'] = get_string('registrations', 'grouptool'); $head['queues'] = get_string('queues', 'grouptool').' ('.get_string('rank', 'grouptool').')'; } if (!$onlydata) { echo html_writer::start_tag('tbody'); } if (!empty($userdata)) { core_php_time_limit::raise(5 * count($userdata)); foreach ($userdata as $key => $user) { if (!$onlydata) { echo html_writer::start_tag('tr', ['class' => '']); $userlink = new moodle_url($CFG->wwwroot.'/user/view.php', [ 'id' => $user->id, 'course' => $this->course->id ]); if (!in_array('picture', $collapsed)) { $picture = html_writer::link($userlink, $OUTPUT->user_picture($user)); echo html_writer::tag('td', $picture, ['class' => '']); } if (!in_array('fullname', $collapsed)) { $fullname = html_writer::link($userlink, fullname($user)); echo html_writer::tag('td', $fullname, ['class' => '']); } if (!in_array('idnumber', $collapsed)) { $idnumber = $user->idnumber; echo html_writer::tag('td', $idnumber, ['class' => '']); } if (!in_array('email', $collapsed)) { $email = $user->email; echo html_writer::tag('td', $email, ['class' => '']); } if (!in_array('registrations', $collapsed)) { if (!empty($user->regs)) { $registrations = []; foreach ($user->regs as $reg) { $grouplink = new moodle_url($PAGE->url, [ 'tab' => 'overview', 'groupid' => $groupinfo[$reg]->id ]); $registrations[] = html_writer::link($grouplink, $groupinfo[$reg]->name); } } else { $registrations = ['-']; } $registrations = implode(html_writer::empty_tag('br'), $registrations); echo html_writer::tag('td', $registrations, ['class' => '']); } if (!in_array('queues', $collapsed)) { if (!empty($user->queued)) { $queueentries = []; foreach ($user->queued as $queue) { $grouplink = new moodle_url($PAGE->url, [ 'tab' => 'overview', 'groupid' => $groupinfo[$queue]->id ]); $groupdata = $this->get_active_groups(false, true, $queue); $groupdata = current($groupdata); $rank = $this->get_rank_in_queue($groupdata->queued, $user->id); $groupdata = null; unset($groupdata); if (empty($rank)) { $rank = '*'; } $queueentries[] = html_writer::link($grouplink, $groupinfo[$queue]->name." (#".$rank.")"); } } else { $queueentries = ['-']; } $queueentries = implode(html_writer::empty_tag('br'), $queueentries); echo html_writer::tag('td', $queueentries, ['class' => '']); } else { $queueentries = ""; } echo html_writer::end_tag('tr'); flush(); $picture = null; unset($picture); $fullname = null; unset($fullname); $idnumber = null; unset($idnumber); $email = null; unset($email); $registrations = null; unset($registrations); $queueentries = null; unset($queueentries); } else { $row = []; $row['name'] = fullname($user); foreach ($namefields as $namefield) { $row[$namefield] = $user->$namefield; $user->namefield = null; unset($user->namefield); } $row['idnumber'] = $user->idnumber; $row['email'] = $user->email; if (empty($CFG->showuseridentity)) { $row['idnumber'] = $user->idnumber; $user->idnumber = null; unset($user->idnumber); $row['email'] = $user->email; $user->email = null; unset($user->email); } else { $fields = explode(',', $CFG->showuseridentity); foreach ($fields as $field) { $row[$field] = $user->$field; $user->$field = null; unset($user->$field); } } if (!empty($user->regs)) { $registrations = []; foreach ($user->regs as $reg) { $registrations[] = $groupinfo[$reg]->name; } $row['registrations'] = $registrations; } else { $row['registrations'] = []; } $user->regs = null; unset($user->regs); if (!empty($user->queued)) { $queueentries = []; foreach ($user->queued as $queue) { $groupdata = $this->get_active_groups(false, true, $queue); $groupdata = current($groupdata); $rank = $this->get_rank_in_queue($groupdata->queued, $user->id); if (empty($rank)) { $rank = '*'; } $queueentries[] = [ 'rank' => $rank, 'name' => $groupinfo[$queue]->name ]; } $row['queues'] = $queueentries; } else { $row['queues'] = []; } $user->queues = null; unset($user->queues); $rows[] = $row; $row = null; unset($row); } } } if (!$onlydata) { echo html_writer::end_tag('tbody'); echo html_writer::end_tag('table'); } else { return array_merge([$head], $rows); } return true; } /** * outputs generated pdf-file for userlist (forces download) * * @param int $groupid optional get only this group * @param int $groupingid optional get only this grouping * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function download_userlist_pdf($groupid=0, $groupingid=0) { $data = $this->userlist_table($groupingid, $groupid, true); $pdf = new \mod_grouptool\pdf(); $coursename = format_string($this->course->fullname, true, array('context' => context_module::instance($this->cm->id))); $timeavailable = $this->grouptool->timeavailable; $grouptoolname = $this->grouptool->name; $timedue = $this->grouptool->timedue; if (!empty($groupingid) || !empty($groupid)) { $viewname = ""; if (!empty($groupingid)) { $viewname .= groups_get_grouping_name($groupingid); } else { $viewname .= get_string('all'); } if ($viewname != "") { $viewname .= " / "; } if (!empty($groupid)) { $viewname .= groups_get_group_name($groupid); } else { $viewname .= get_string('all'); } } else { $viewname = get_string('all').' '.get_string('groups'); } $pdf->set_userlist_header_data($coursename, $grouptoolname, $timeavailable, $timedue, $viewname); if (count($data) > 1) { $user = reset($data); $name = $user['name']; $idnumber = $user['idnumber']; $email = $user['email']; $regdata = $user['registrations']; $queuedata = $user['queues']; $pdf->add_userdata($name, $idnumber, $email, $regdata, $queuedata, true); while (next($data)) { $user = current($data); $name = $user['name']; $idnumber = $user['idnumber']; $email = $user['email']; $regdata = $user['registrations']; $queuedata = $user['queues']; $pdf->add_userdata($name, $idnumber, $email, $regdata, $queuedata); } } else { $pdf->MultiCell(0, $pdf->getLastH(), get_string('no_data_to_display', 'grouptool'), 'B', 'LRTB', false, 1, null, null, true, 1, true, false, $pdf->getLastH(), 'M', true); } if (!empty($groupid)) { $filename = $coursename . '_' . $grouptoolname . '_' . groups_get_group_name($groupid).'_'.get_string('userlist', 'grouptool'); } else if (!empty($groupingid)) { $filename = $coursename . '_' . $grouptoolname . '_' . groups_get_grouping_name($groupingid).'_'.get_string('userlist', 'grouptool'); } else { $filename = $coursename . '_' . $grouptoolname . '_' . get_string('userlist', 'grouptool'); } $filename = clean_filename("$filename.pdf"); $pdf->Output($filename, 'D'); exit(); } /** * returns data for userlist * * @param int $groupid optional get only this group * @param int $groupingid optional get only this grouping * @return bool|object[] raw data * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function download_userlist_raw($groupid=0, $groupingid=0) { return $this->userlist_table($groupid, $groupingid, true); } /** * outputs generated txt-file for userlist (forces download) * * @param int $groupid optional get only this group * @param int $groupingid optional get only this grouping * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function download_userlist_txt($groupid=0, $groupingid=0) { ob_start(); $coursename = format_string($this->course->fullname, true, array('context' => context_module::instance($this->cm->id))); $grouptoolname = $this->grouptool->name; $lines = []; $users = $this->userlist_table($groupingid, $groupid, true); if (count($users) > 0) { foreach ($users as $key => $user) { if ($key == 0) { // Headline! $lines[] = get_string('fullname')."\t". get_string('idnumber')."\t". get_string('email')."\t". get_string('registrations', 'grouptool')."\t". get_string('queues', 'grouptool')." (".get_string('rank', 'grouptool').")"; } else { $rows = max([1, count($user['registrations']), count($user['queues'])]); for ($i = 0; $i < $rows; $i++) { $line = ""; if ($i == 0) { $line = $user['name']."\t".$user['idnumber']."\t".$user['email']; } else { $line = "\t\t"; } if ((count($user['registrations']) == 0) && ($i == 0)) { $line .= "\t".get_string('no_registrations', 'grouptool'); } else if (key_exists($i, $user['registrations'])) { $line .= "\t".$user['registrations'][$i]; } else { $line .= "\t"; } if ((count($user['queues']) == 0) && ($i == 0)) { $line .= "\t".get_string('nowhere_queued', 'grouptool'); } else if (key_exists($i, $user['queues'])) { $line .= "\t".$user['queues'][$i]['name']."(".$user['queues'][$i]['rank'].")"; } else { $line .= "\t"; } $lines[] = $line; } } } } else { $lines[] = get_string('no_data_to_display', 'grouptool'); } $filecontent = implode(GROUPTOOL_NL, $lines); if (!empty($groupid)) { $filename = $coursename . '_' . $grouptoolname . '_' . groups_get_group_name($groupid).'_'.get_string('userlist', 'grouptool'); } else if (!empty($groupingid)) { $filename = $coursename . '_' . $grouptoolname . '_' . groups_get_grouping_name($groupingid).'_'.get_string('userlist', 'grouptool'); } else { $filename = $coursename . '_' . $grouptoolname . '_' . get_string('userlist', 'grouptool'); } $filename = clean_filename("$filename.txt"); ob_clean(); header('Content-Type: text/plain'); header('Content-Length: ' . strlen($filecontent)); header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1! header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in past! header('Content-Disposition: attachment; filename="'.str_replace([' ', '"'], ['_', ''], $filename).'";'. ' filename*="'.rawurlencode($filename).'"'); header('Content-Transfer-Encoding: binary'); header('Content-Encoding: utf-8'); echo $filecontent; } /** * fills workbook (either XLSX or ODS) with data * * @param MoodleExcelWorkbook|MoodleODSWorkbook $workbook workbook to put data into * @param stdClass[] $data userdata with headline at index 0 * @param string[] $collapsed (optional) currently collapsed columns * @throws coding_exception */ protected function userlist_fill_workbook(&$workbook, $data, $collapsed=[]) { global $CFG; $orientation = optional_param('orientation', 0, PARAM_BOOL); if (count($data) > 0) { $worksheet = false; // Prepare formats! $headlineprop = [ 'size' => 12, 'bold' => 1, 'HAlign' => 'center', 'bottom' => 2, 'VAlign' => 'vcenter' ]; $headlineformat = $workbook->add_format($headlineprop); $headlineformat->set_right(1); $headlineformat->set_align('center'); $headlineformat->set_align('vcenter'); $headlinelast = $workbook->add_format($headlineprop); $headlinelast->set_align('center'); $headlinelast->set_align('vcenter'); $headlinelast->set_left(1); $headlinenb = $workbook->add_format($headlineprop); $headlinenb->set_align('center'); $headlinenb->set_align('vcenter'); unset($headlineprop['bottom']); $headlinenbb = $workbook->add_format($headlineprop); $headlinenbb->set_align('center'); $headlinenbb->set_align('vcenter'); $regentryprop = [ 'size' => 10, 'align' => 'left' ]; $queueentryprop = $regentryprop; $queueentryprop['italic'] = true; $queueentryprop['color'] = 'grey'; $regentryformat = $workbook->add_format($regentryprop); $regentryformat->set_right(1); $regentryformat->set_align('vcenter'); $regentrylast = $workbook->add_format($regentryprop); $regentrylast->set_align('vcenter'); $noregentriesformat = $workbook->add_format($regentryprop); $noregentriesformat->set_align('left'); $noregentriesformat->set_align('vcenter'); $noregentriesformat->set_right(1); $queueentryformat = $workbook->add_format($queueentryprop); $queueentryformat->set_right(1); $queueentryformat->set_align('vcenter'); $queueentrylast = $workbook->add_format($queueentryprop); $queueentrylast->set_align('vcenter'); $noqueueentriesformat = $workbook->add_format($queueentryprop); $noqueueentriesformat->set_align('left'); $noqueueentriesformat->set_align('vcenter'); // Start row for groups general sheet! $j = 0; // We create a dummy user-object to get the fullname-format! $dummy = new stdClass(); $namefields = get_all_user_name_fields(); foreach ($namefields as $namefield) { $dummy->$namefield = $namefield; } $fullnameformat = fullname($dummy); // Now get the ones used in fullname in the correct order! $namefields = order_in_string($namefields, $fullnameformat); $columnwidth = [ 0 => 26, 'fullname' => 26, 'firstname' => 20, 'surname' => 20, 'email' => 35, 'registrations' => 47, 'queues_grp' => 47, 'queues_rank' => 7.5 ]; // Unit: mm! foreach ($data as $key => $user) { if ($worksheet === false && count($data) > 1) { // General information? unused at the moment! $worksheet = $workbook->add_worksheet(get_string('all')); } if ($key == 0) { // Headline! $k = 0; // First we output every namefield from used by fullname in exact the defined order! foreach ($namefields as $namefield) { $worksheet->write_string($j, $k, get_user_field_name($namefield), $headlineformat); $worksheet->write_blank($j + 1, $k, $headlineformat); $worksheet->merge_cells($j, $k, $j + 1, $k); $hidden = in_array($namefield, $collapsed) ? true : false; $columnwidth[$namefield] = empty($columnwidth[$namefield]) ? $columnwidth[0] : $columnwidth[$namefield]; $worksheet->set_column($k, $k, $columnwidth[$namefield], null, $hidden); $k++; } // ...k = n! if (!empty($CFG->showuseridentity)) { $fields = explode(',', $CFG->showuseridentity); foreach ($fields as $field) { $worksheet->write_string($j, $k, get_user_field_name($field), $headlineformat); $worksheet->write_blank($j + 1, $k, $headlineformat); $hidden = in_array($field, $collapsed) ? true : false; $columnwidth[$field] = empty($columnwidth[$field]) ? $columnwidth[0] : $columnwidth[$field]; $worksheet->set_column($k, $k, $columnwidth[$field], null, $hidden); $worksheet->merge_cells($j, $k, $j + 1, $k); $k++; // ...k = n+x! } } else { $worksheet->write_string($j, $k, get_user_field_name('idnumber'), $headlineformat); $worksheet->write_blank($j + 1, $k, $headlineformat); $hidden = in_array('idnumber', $collapsed) ? true : false; $columnwidth['idnumber'] = empty($columnwidth['idnumber']) ? $columnwidth[0] : $columnwidth['idnumber']; $worksheet->set_column($k, $k, $columnwidth['idnumber'], null, $hidden); $worksheet->merge_cells($j, $k, $j + 1, $k); $k++; // ...k = n+1! $worksheet->write_string($j, $k, get_user_field_name('email'), $headlineformat); $worksheet->write_blank($j + 1, $k, $headlineformat); $hidden = in_array('email', $collapsed) ? true : false; $columnwidth['email'] = empty($columnwidth['email']) ? $columnwidth[0] : $columnwidth['email']; $worksheet->set_column($k, $k, $columnwidth['email'], null, $hidden); $worksheet->merge_cells($j, $k, $j + 1, $k); $k++; // ...k = n+2! } $worksheet->write_string($j, $k, $user['registrations'], $headlineformat); $worksheet->write_blank($j + 1, $k, $headlineformat); $hidden = in_array('registrations', $collapsed) ? true : false; $tmp = $columnwidth['registrations']; $columnwidth['registrations'] = empty($tmp) ? $columnwidth[0] : $tmp; unset($tmp); $worksheet->set_column($k, $k, $columnwidth['registrations'], null, $hidden); $worksheet->merge_cells($j, $k, $j + 1, $k); $k++; // ...k = n+3! $worksheet->write_string($j, $k, $user['queues'], $headlinenbb); $worksheet->write_blank($j, $k + 1, $headlinenbb); $hidden = in_array('queues', $collapsed) ? true : false; $columnwidth['queues_grp'] = empty($columnwidth['queues_grp']) ? $columnwidth[0] : $columnwidth['queues_grp']; $worksheet->set_column($k, $k, $columnwidth['queues_grp'], null, $hidden); $tmp = $columnwidth['queues_rank']; $columnwidth['queues_rank'] = empty($tmp) ? $columnwidth[0] : $tmp; unset($tmp); $worksheet->set_column($k + 1, $k + 1, $columnwidth['queues_rank'], null, $hidden); $worksheet->merge_cells($j, $k, $j, $k + 1); $worksheet->write_string($j + 1, $k, get_string('group', 'group'), $headlinenb); $worksheet->write_string($j + 1, $k + 1, get_string('rank', 'grouptool'), $headlinelast); $k += 2; // ...k = n+5! $rows = 2; } else { $k = 0; $rows = max([1, count($user['registrations']), count($user['queues'])]); // First we output every namefield from used by fullname in exact the defined order! foreach ($namefields as $namefield) { if (empty($user[$namefield])) { $user[$namefield] = ''; } $worksheet->write_string($j, $k, $user[$namefield], $regentryformat); if ($rows > 1) { $worksheet->merge_cells($j, $k, $j + $rows - 1, $k); } $k++; } // ...k = n! if (!empty($CFG->showuseridentity)) { $fields = explode(',', $CFG->showuseridentity); foreach ($fields as $field) { if (empty($user[$field])) { $worksheet->write_blank($j, $k, $regentryformat); } else { $worksheet->write_string($j, $k, $user[$field], $regentryformat); } if ($rows > 1) { $worksheet->merge_cells($j, $k, $j + $rows - 1, $k); } $k++; // ...k = n+x! } } else { $worksheet->write_string($j, $k, $user['idnumber'], $regentryformat); if ($rows > 1) { $worksheet->merge_cells($j, $k, $j + $rows - 1, $k); } $k++; // ...k = n+1! $worksheet->write_string($j, $k, $user['email'], $regentryformat); if ($rows > 1) { $worksheet->merge_cells($j, $k, $j + $rows - 1, $k); } $k++; // ...k = n+2! } for ($i = 0; $i < $rows; $i++) { if ($i != 0) { for ($m = 0; $m < $k; $m++) { // Write all the empty cells! $worksheet->write_blank($j + $i, $m, $regentryformat); } } if ((count($user['registrations']) == 0) && ($i == 0)) { $worksheet->write_string($j, $k, get_string('no_registrations', 'grouptool'), $noregentriesformat); if ($rows > 1) { $worksheet->merge_cells($j, $k, $j + $rows - 1, $k); } } else if (key_exists($i, $user['registrations'])) { $worksheet->write_string($j + $i, $k, $user['registrations'][$i], $regentryformat); } else { $worksheet->write_blank($j + $i, $k, $regentryformat); } if ((count($user['queues']) == 0) && ($i == 0)) { $worksheet->write_string($j, $k + 1, get_string('nowhere_queued', 'grouptool'), $noqueueentriesformat); $worksheet->merge_cells($j, $k + 1, $j + $rows - 1, $k + 2); } else if (key_exists($i, $user['queues'])) { $worksheet->write_string($j + $i, $k + 1, $user['queues'][$i]['name'], $queueentrylast); $worksheet->write_number($j + $i, $k + 2, $user['queues'][$i]['rank'], $queueentrylast); } else { $worksheet->write_blank($j + $i, $k + 1, $queueentrylast); $worksheet->write_blank($j + $i, $k + 2, $queueentrylast); } } $k += 3; } $j += $rows; // We use 1 row space between groups! } } } /** * outputs generated ods-file for userlist (forces download) * * @param int $groupid optional get only this group * @param int $groupingid optional get only this grouping * @param string[] $collapsed optional current array with collapsed columns * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function download_userlist_ods($groupid=0, $groupingid=0, $collapsed=[]) { global $CFG; require_once($CFG->libdir . "/odslib.class.php"); $coursename = format_string($this->course->fullname, true, array('context' => context_module::instance($this->cm->id))); $grouptoolname = $this->grouptool->name; $workbook = new MoodleODSWorkbook("-"); $data = $this->userlist_table($groupingid, $groupid, true); $this->userlist_fill_workbook($workbook, $data, $collapsed); if (!empty($groupid)) { $filename = $coursename . '_' . $grouptoolname . '_' . groups_get_group_name($groupid).'_'.get_string('userlist', 'grouptool'); } else if (!empty($groupingid)) { $filename = $coursename . '_' . $grouptoolname . '_' . groups_get_grouping_name($groupingid).'_'.get_string('userlist', 'grouptool'); } else { $filename = $coursename . '_' . $grouptoolname . '_' . get_string('userlist', 'grouptool'); } $filename = clean_filename("$filename.ods"); $workbook->send($filename); $workbook->close(); } /** * outputs generated xlsx-file for userlist (forces download) * * @param int $groupid optional get only this group * @param int $groupingid optional get only this grouping * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function download_userlist_xlsx($groupid = 0, $groupingid = 0) { global $CFG; require_once($CFG->libdir . "/excellib.class.php"); $coursename = format_string($this->course->fullname, true, array('context' => context_module::instance($this->cm->id))); $grouptoolname = $this->grouptool->name; $workbook = new MoodleExcelWorkbook("-", 'Excel2007'); $data = $this->userlist_table($groupingid, $groupid, true); $this->userlist_fill_workbook($workbook, $data); if (!empty($groupid)) { $filename = clean_filename($coursename . '_' . $grouptoolname . '_' . groups_get_group_name($groupid).'_'. get_string('userlist', 'grouptool')); } else if (!empty($groupingid)) { $filename = clean_filename($coursename . '_' . $grouptoolname . '_' . groups_get_grouping_name($groupingid).'_'. get_string('userlist', 'grouptool')); } else { $filename = clean_filename($coursename . '_' . $grouptoolname . '_' . get_string('userlist', 'grouptool')); } $filename = clean_filename("$filename.xlsx"); $workbook->send($filename); $workbook->close(); } /** * view userlist tab * * @throws coding_exception * @throws dml_exception * @throws moodle_exception * @throws required_capability_exception */ public function view_userlist() { global $PAGE, $OUTPUT; $groupid = optional_param('groupid', 0, PARAM_INT); $groupingid = optional_param('groupingid', 0, PARAM_INT); $orientation = optional_param('orientation', 0, PARAM_BOOL); $url = new moodle_url($PAGE->url, [ 'sesskey' => sesskey(), 'groupid' => $groupid, 'groupingid' => $groupingid, 'orientation' => $orientation ]); $groupings = groups_get_all_groupings($this->course->id); $options = [0 => get_string('all')]; if (count($groupings)) { foreach ($groupings as $grouping) { $options[$grouping->id] = $grouping->name; } } $groupingselect = $this->get_grouping_select($url, $groupingid); $groupselect = $this->get_groups_select($url, $groupingid, $groupid); $orientationselect = $this->get_orientation_select($url, $orientation); echo html_writer::tag('div', get_string('grouping', 'group').' '. $OUTPUT->render($groupingselect), ['class' => 'centered grouptool_userlist_filter']). html_writer::tag('div', get_string('group', 'group').' '. $OUTPUT->render($groupselect), ['class' => 'centered grouptool_userlist_filter']). html_writer::tag('div', get_string('orientation', 'grouptool').' '. $OUTPUT->render($orientationselect), ['class' => 'centered grouptool_userlist_filter']); flush(); $this->userlist_table($groupingid, $groupid); } }