AutorÃa | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle 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.//// Moodle 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/>./*** Classes to enforce the various access rules that can apply to a activity.** @package block_activity_results* @copyright 2009 Tim Hunt* @copyright 2015 Stephen Bourget* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/defined('MOODLE_INTERNAL') || die();require_once($CFG->dirroot . '/lib/grade/constants.php');require_once($CFG->dirroot . '/course/lib.php');define('B_ACTIVITYRESULTS_NAME_FORMAT_FULL', 1);define('B_ACTIVITYRESULTS_NAME_FORMAT_ID', 2);define('B_ACTIVITYRESULTS_NAME_FORMAT_ANON', 3);define('B_ACTIVITYRESULTS_GRADE_FORMAT_PCT', 1);define('B_ACTIVITYRESULTS_GRADE_FORMAT_FRA', 2);define('B_ACTIVITYRESULTS_GRADE_FORMAT_ABS', 3);define('B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE', 4);/*** Block activity_results class definition.** This block can be added to a course page or a activity page to display of list of* the best/worst students/groups in a particular activity.** @package block_activity_results* @copyright 2009 Tim Hunt* @copyright 2015 Stephen Bourget* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/class block_activity_results extends block_base {/*** Core function used to initialize the block.*/public function init() {$this->title = get_string('pluginname', 'block_activity_results');}/*** Allow the block to have a configuration page** @return boolean*/public function has_config() {return true;}/*** Core function, specifies where the block can be used.* @return array*/public function applicable_formats() {return array('course-view' => true, 'mod' => true);}/*** If this block belongs to a activity context, then return that activity's id.* Otherwise, return 0.* @return stdclass the activity record.*/public function get_owning_activity() {global $DB;// Set some defaults.$result = new stdClass();$result->id = 0;if (empty($this->instance->parentcontextid)) {return $result;}$parentcontext = context::instance_by_id($this->instance->parentcontextid);if ($parentcontext->contextlevel != CONTEXT_MODULE) {return $result;}$cm = get_coursemodule_from_id($this->page->cm->modname, $parentcontext->instanceid);if (!$cm) {return $result;}// Get the grade_items id.$rec = $DB->get_record('grade_items', array('iteminstance' => $cm->instance, 'itemmodule' => $this->page->cm->modname));if (!$rec) {return $result;}// See if it is a gradable activity.if (($rec->gradetype != GRADE_TYPE_VALUE) && ($rec->gradetype != GRADE_TYPE_SCALE)) {return $result;}return $rec;}/*** Used to save the form config data* @param stdclass $data* @param bool $nolongerused*/public function instance_config_save($data, $nolongerused = false) {global $DB;if (empty($data->activitygradeitemid)) {// Figure out info about parent module.$info = $this->get_owning_activity();$data->activitygradeitemid = $info->id;if ($info->id < 1) {// No activity was selected.$info->itemmodule = '';$info->iteminstance = '';} else {$data->activityparent = $info->itemmodule;$data->activityparentid = $info->iteminstance;}} else {// Lookup info about the parent module (we have the id from mdl_grade_items.$info = $DB->get_record('grade_items', array('id' => $data->activitygradeitemid));$data->activityparent = $info->itemmodule;$data->activityparentid = $info->iteminstance;}parent::instance_config_save($data);}/*** Used to generate the content for the block.* @return string*/public function get_content() {global $USER, $CFG, $DB;if ($this->content !== null) {return $this->content;}$this->content = new stdClass;$this->content->text = '';$this->content->footer = '';if (empty($this->instance)) {return $this->content;}// We are configured so use the configuration.if (!empty($this->config->activitygradeitemid)) {// We are configured.$activitygradeitemid = $this->config->activitygradeitemid;// Lookup the module in the grade_items table.$activity = $DB->get_record('grade_items', array('id' => $activitygradeitemid));if (empty($activity)) {// Activity does not exist.$this->content->text = get_string('error_emptyactivityrecord', 'block_activity_results');return $this->content;}$courseid = $activity->courseid;$inactivity = false;} else {// Not configured.$activitygradeitemid = 0;}// Check to see if we are in the moule we are displaying results for.if (!empty($this->config->activitygradeitemid)) {if ($this->get_owning_activity()->id == $this->config->activitygradeitemid) {$inactivity = true;} else {$inactivity = false;}}// Activity ID is missing.if (empty($activitygradeitemid)) {$this->content->text = get_string('error_emptyactivityid', 'block_activity_results');return $this->content;}// Check to see if we are configured.if (empty($this->config->showbest) && empty($this->config->showworst)) {$this->content->text = get_string('configuredtoshownothing', 'block_activity_results');return $this->content;}// Check to see if it is a supported grade type.if (empty($activity->gradetype) || ($activity->gradetype != GRADE_TYPE_VALUE && $activity->gradetype != GRADE_TYPE_SCALE)) {$this->content->text = get_string('error_unsupportedgradetype', 'block_activity_results');return $this->content;}// Get the grades for this activity.$sql = 'SELECT * FROM {grade_grades}WHERE itemid = ? AND finalgrade is not NULLORDER BY finalgrade, timemodified DESC';$grades = $DB->get_records_sql($sql, array( $activitygradeitemid));if (empty($grades) || $activity->hidden) {// No grades available, The block will hide itself in this case.return $this->content;}// Set up results.$groupmode = NOGROUPS;$best = array();$worst = array();if (!empty($this->config->nameformat)) {$nameformat = $this->config->nameformat;} else {$nameformat = B_ACTIVITYRESULTS_NAME_FORMAT_FULL;}// Get $cm and context.if ($inactivity) {$cm = $this->page->cm;$context = $this->page->context;} else {$cm = get_coursemodule_from_instance($activity->itemmodule, $activity->iteminstance, $courseid);$context = context_module::instance($cm->id);}if (!empty($this->config->usegroups)) {$groupmode = groups_get_activity_groupmode($cm);if ($groupmode == SEPARATEGROUPS && has_capability('moodle/site:accessallgroups', $context)) {// If you have the ability to see all groups then lets show them.$groupmode = VISIBLEGROUPS;}}switch ($groupmode) {case VISIBLEGROUPS:// Display group-mode results.$groups = groups_get_all_groups($courseid);if (empty($groups)) {// No groups exist, sorry.$this->content->text = get_string('error_nogroupsexist', 'block_activity_results');return $this->content;}// Find out all the userids which have a submitted grade.$userids = array();$gradeforuser = array();foreach ($grades as $grade) {$userids[] = $grade->userid;$gradeforuser[$grade->userid] = (float)$grade->finalgrade;}// Now find which groups these users belong in.list($usertest, $params) = $DB->get_in_or_equal($userids);$params[] = $courseid;$usergroups = $DB->get_records_sql('SELECT gm.id, gm.userid, gm.groupid, g.nameFROM {groups} gLEFT JOIN {groups_members} gm ON g.id = gm.groupidWHERE gm.userid ' . $usertest . ' AND g.courseid = ?', $params);// Now, iterate the grades again and sum them up for each group.$groupgrades = array();foreach ($usergroups as $usergroup) {if (!isset($groupgrades[$usergroup->groupid])) {$groupgrades[$usergroup->groupid] = array('sum' => (float)$gradeforuser[$usergroup->userid],'number' => 1,'group' => $usergroup->name);} else {$groupgrades[$usergroup->groupid]['sum'] += $gradeforuser[$usergroup->userid];$groupgrades[$usergroup->groupid]['number'] += 1;}}foreach ($groupgrades as $groupid => $groupgrade) {$groupgrades[$groupid]['average'] = $groupgrades[$groupid]['sum'] / $groupgrades[$groupid]['number'];}// Sort groupgrades according to average grade, ascending.uasort($groupgrades, function($a, $b) {if ($a["average"] == $b["average"]) {return 0;}return ($a["average"] > $b["average"] ? 1 : -1);});// How many groups do we have with graded member submissions to show?$numbest = empty($this->config->showbest) ? 0 : min($this->config->showbest, count($groupgrades));$numworst = empty($this->config->showworst) ? 0 : min($this->config->showworst, count($groupgrades) - $numbest);// Collect all the group results we are going to use in $best and $worst.$remaining = $numbest;$groupgrade = end($groupgrades);while ($remaining--) {$best[key($groupgrades)] = $groupgrade['average'];$groupgrade = prev($groupgrades);}$remaining = $numworst;$groupgrade = reset($groupgrades);while ($remaining--) {$worst[key($groupgrades)] = $groupgrade['average'];$groupgrade = next($groupgrades);}// Ready for output!if ($activity->gradetype == GRADE_TYPE_SCALE) {// We must display the results using scales.$gradeformat = B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE;// Preload the scale.$scale = $this->get_scale($activity->scaleid);} else if (intval(empty($this->config->gradeformat))) {$gradeformat = B_ACTIVITYRESULTS_GRADE_FORMAT_PCT;} else {$gradeformat = $this->config->gradeformat;}// Generate the header.$this->content->text .= $this->activity_link($activity, $cm);if ($nameformat == B_ACTIVITYRESULTS_NAME_FORMAT_FULL) {if (has_capability('moodle/course:managegroups', $context)) {$grouplink = $CFG->wwwroot.'/group/overview.php?id='.$courseid.'&group=';} else if (course_can_view_participants($context)) {$grouplink = $CFG->wwwroot.'/user/index.php?id='.$courseid.'&group=';} else {$grouplink = '';}}$rank = 0;if (!empty($best)) {$this->content->text .= '<table class="grades"><caption class="pb-0"><h6>';if ($numbest == 1) {$this->content->text .= get_string('bestgroupgrade', 'block_activity_results');} else {$this->content->text .= get_string('bestgroupgrades', 'block_activity_results', $numbest);}$this->content->text .= '</h6></caption><colgroup class="number" />';$this->content->text .= '<colgroup class="name" /><colgroup class="grade" /><tbody>';foreach ($best as $groupid => $averagegrade) {switch ($nameformat) {case B_ACTIVITYRESULTS_NAME_FORMAT_ANON:case B_ACTIVITYRESULTS_NAME_FORMAT_ID:$thisname = get_string('group');break;default:case B_ACTIVITYRESULTS_NAME_FORMAT_FULL:if ($grouplink) {$thisname = '<a href="'.$grouplink.$groupid.'">'.$groupgrades[$groupid]['group'].'</a>';} else {$thisname = $groupgrades[$groupid]['group'];}break;}$this->content->text .= '<tr><td>'.(++$rank).'.</td><td>'.$thisname.'</td><td>';switch ($gradeformat) {case B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE:// Round answer up and locate appropriate scale.$answer = (round($averagegrade, 0, PHP_ROUND_HALF_UP) - 1);if (isset($scale[$answer])) {$this->content->text .= $scale[$answer];} else {// Value is not in the scale.$this->content->text .= get_string('unknown', 'block_activity_results');}break;case B_ACTIVITYRESULTS_GRADE_FORMAT_FRA:$this->content->text .= $this->activity_format_grade($averagegrade). '/' . $this->activity_format_grade($activity->grademax);break;case B_ACTIVITYRESULTS_GRADE_FORMAT_ABS:$this->content->text .= $this->activity_format_grade($averagegrade);break;default:case B_ACTIVITYRESULTS_GRADE_FORMAT_PCT:$this->content->text .= $this->activity_format_grade((float)$averagegrade /(float)$activity->grademax * 100).'%';break;}$this->content->text .= '</td></tr>';}$this->content->text .= '</tbody></table>';}$rank = 0;if (!empty($worst)) {$worst = array_reverse($worst, true);$this->content->text .= '<table class="grades"><caption class="pb-0"><h6>';if ($numworst == 1) {$this->content->text .= get_string('worstgroupgrade', 'block_activity_results');} else {$this->content->text .= get_string('worstgroupgrades', 'block_activity_results', $numworst);}$this->content->text .= '</h6></caption><colgroup class="number" />';$this->content->text .= '<colgroup class="name" /><colgroup class="grade" /><tbody>';foreach ($worst as $groupid => $averagegrade) {switch ($nameformat) {case B_ACTIVITYRESULTS_NAME_FORMAT_ANON:case B_ACTIVITYRESULTS_NAME_FORMAT_ID:$thisname = get_string('group');break;default:case B_ACTIVITYRESULTS_NAME_FORMAT_FULL:if ($grouplink) {$thisname = '<a href="'.$grouplink.$groupid.'">'.$groupgrades[$groupid]['group'].'</a>';} else {$thisname = $groupgrades[$groupid]['group'];}break;}$this->content->text .= '<tr><td>'.(++$rank).'.</td><td>'.$thisname.'</td><td>';switch ($gradeformat) {case B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE:// Round answer up and locate appropriate scale.$answer = (round($averagegrade, 0, PHP_ROUND_HALF_UP) - 1);if (isset($scale[$answer])) {$this->content->text .= $scale[$answer];} else {// Value is not in the scale.$this->content->text .= get_string('unknown', 'block_activity_results');}break;case B_ACTIVITYRESULTS_GRADE_FORMAT_FRA:$this->content->text .= $this->activity_format_grade($averagegrade). '/' . $this->activity_format_grade($activity->grademax);break;case B_ACTIVITYRESULTS_GRADE_FORMAT_ABS:$this->content->text .= $this->activity_format_grade($averagegrade);break;default:case B_ACTIVITYRESULTS_GRADE_FORMAT_PCT:$this->content->text .= $this->activity_format_grade((float)$averagegrade /(float)$activity->grademax * 100).'%';break;}$this->content->text .= '</td></tr>';}$this->content->text .= '</tbody></table>';}break;case SEPARATEGROUPS:// This is going to be just like no-groups mode, only we 'll filter// out the grades from people not in our group.if (!isloggedin()) {// Not logged in, so show nothing.return $this->content;}$mygroups = groups_get_all_groups($courseid, $USER->id);if (empty($mygroups)) {// Not member of a group, show nothing.return $this->content;}// Get users from the same groups as me.list($grouptest, $params) = $DB->get_in_or_equal(array_keys($mygroups));$mygroupsusers = $DB->get_records_sql_menu('SELECT DISTINCT userid, 1 FROM {groups_members} WHERE groupid ' . $grouptest,$params);// Filter out the grades belonging to other users, and proceed as if there were no groups.foreach ($grades as $key => $grade) {if (!isset($mygroupsusers[$grade->userid])) {unset($grades[$key]);}}// No break, fall through to the default case now we have filtered the $grades array.default:case NOGROUPS:// Single user mode.$numbest = empty($this->config->showbest) ? 0 : min($this->config->showbest, count($grades));$numworst = empty($this->config->showworst) ? 0 : min($this->config->showworst, count($grades) - $numbest);// Collect all the usernames we are going to need.$remaining = $numbest;$grade = end($grades);while ($remaining--) {$best[$grade->userid] = $grade->id;$grade = prev($grades);}$remaining = $numworst;$grade = reset($grades);while ($remaining--) {$worst[$grade->userid] = $grade->id;$grade = next($grades);}if (empty($best) && empty($worst)) {// Nothing to show, for some reason...return $this->content;}// Now grab all the users from the database.$userids = array_merge(array_keys($best), array_keys($worst));$fields = array_merge(array('id', 'idnumber'), \core_user\fields::get_name_fields());$fields = implode(',', $fields);$users = $DB->get_records_list('user', 'id', $userids, '', $fields);// If configured to view user idnumber, ensure current user can see it.$extrafields = \core_user\fields::for_identity($this->context)->get_required_fields();$canviewidnumber = (array_search('idnumber', $extrafields) !== false);// Ready for output!if ($activity->gradetype == GRADE_TYPE_SCALE) {// We must display the results using scales.$gradeformat = B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE;// Preload the scale.$scale = $this->get_scale($activity->scaleid);} else if (intval(empty($this->config->gradeformat))) {$gradeformat = B_ACTIVITYRESULTS_GRADE_FORMAT_PCT;} else {$gradeformat = $this->config->gradeformat;}// Generate the header.$this->content->text .= $this->activity_link($activity, $cm);$rank = 0;if (!empty($best)) {$this->content->text .= '<table class="grades"><caption class="pb-0"><h6>';if ($numbest == 1) {$this->content->text .= get_string('bestgrade', 'block_activity_results');} else {$this->content->text .= get_string('bestgrades', 'block_activity_results', $numbest);}$this->content->text .= '</h6></caption><colgroup class="number" />';$this->content->text .= '<colgroup class="name" /><colgroup class="grade" /><tbody>';foreach ($best as $userid => $gradeid) {switch ($nameformat) {case B_ACTIVITYRESULTS_NAME_FORMAT_ID:$thisname = get_string('user');if ($canviewidnumber) {$thisname .= ' ' . s($users[$userid]->idnumber);}break;case B_ACTIVITYRESULTS_NAME_FORMAT_ANON:$thisname = get_string('user');break;default:case B_ACTIVITYRESULTS_NAME_FORMAT_FULL:if (has_capability('moodle/user:viewdetails', $context)) {$thisname = html_writer::link(new moodle_url('/user/view.php',array('id' => $userid, 'course' => $courseid)), fullname($users[$userid]));} else {$thisname = fullname($users[$userid]);}break;}$this->content->text .= '<tr><td>'.(++$rank).'.</td><td>'.$thisname.'</td><td>';switch ($gradeformat) {case B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE:// Round answer up and locate appropriate scale.$answer = (round($grades[$gradeid]->finalgrade, 0, PHP_ROUND_HALF_UP) - 1);if (isset($scale[$answer])) {$this->content->text .= $scale[$answer];} else {// Value is not in the scale.$this->content->text .= get_string('unknown', 'block_activity_results');}break;case B_ACTIVITYRESULTS_GRADE_FORMAT_FRA:$this->content->text .= $this->activity_format_grade($grades[$gradeid]->finalgrade);$this->content->text .= '/'.$this->activity_format_grade($activity->grademax);break;case B_ACTIVITYRESULTS_GRADE_FORMAT_ABS:$this->content->text .= $this->activity_format_grade($grades[$gradeid]->finalgrade);break;default:case B_ACTIVITYRESULTS_GRADE_FORMAT_PCT:if ($activity->grademax) {$this->content->text .= $this->activity_format_grade((float)$grades[$gradeid]->finalgrade /(float)$activity->grademax * 100).'%';} else {$this->content->text .= '--%';}break;}$this->content->text .= '</td></tr>';}$this->content->text .= '</tbody></table>';}$rank = 0;if (!empty($worst)) {$worst = array_reverse($worst, true);$this->content->text .= '<table class="grades"><caption class="pb-0"><h6>';if ($numbest == 1) {$this->content->text .= get_string('worstgrade', 'block_activity_results');} else {$this->content->text .= get_string('worstgrades', 'block_activity_results', $numworst);}$this->content->text .= '</h6></caption><colgroup class="number" />';$this->content->text .= '<colgroup class="name" /><colgroup class="grade" /><tbody>';foreach ($worst as $userid => $gradeid) {switch ($nameformat) {case B_ACTIVITYRESULTS_NAME_FORMAT_ID:$thisname = get_string('user');if ($canviewidnumber) {$thisname .= ' ' . s($users[$userid]->idnumber);};break;case B_ACTIVITYRESULTS_NAME_FORMAT_ANON:$thisname = get_string('user');break;default:case B_ACTIVITYRESULTS_NAME_FORMAT_FULL:if (has_capability('moodle/user:viewdetails', $context)) {$thisname = html_writer::link(new moodle_url('/user/view.php',array('id' => $userid, 'course' => $courseid)), fullname($users[$userid]));} else {$thisname = fullname($users[$userid]);}break;}$this->content->text .= '<tr><td>'.(++$rank).'.</td><td>'.$thisname.'</td><td>';switch ($gradeformat) {case B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE:// Round answer up and locate appropriate scale.$answer = (round($grades[$gradeid]->finalgrade, 0, PHP_ROUND_HALF_UP) - 1);if (isset($scale[$answer])) {$this->content->text .= $scale[$answer];} else {// Value is not in the scale.$this->content->text .= get_string('unknown', 'block_activity_results');}break;case B_ACTIVITYRESULTS_GRADE_FORMAT_FRA:$this->content->text .= $this->activity_format_grade($grades[$gradeid]->finalgrade);$this->content->text .= '/'.$this->activity_format_grade($activity->grademax);break;case B_ACTIVITYRESULTS_GRADE_FORMAT_ABS:$this->content->text .= $this->activity_format_grade($grades[$gradeid]->finalgrade);break;default:case B_ACTIVITYRESULTS_GRADE_FORMAT_PCT:if ($activity->grademax) {$this->content->text .= $this->activity_format_grade((float)$grades[$gradeid]->finalgrade /(float)$activity->grademax * 100).'%';} else {$this->content->text .= '--%';}break;}$this->content->text .= '</td></tr>';}$this->content->text .= '</tbody></table>';}break;}return $this->content;}/*** Allows the block to be added multiple times to a single page* @return boolean*/public function instance_allow_multiple() {return true;}/*** Formats the grade to the specified decimal points* @param float $grade* @return string*/private function activity_format_grade($grade) {if (is_null($grade)) {return get_string('notyetgraded', 'block_activity_results');}return format_float($grade, $this->config->decimalpoints);}/*** Generates the Link to the activity module when displayed outside of the module.* @param stdclass $activity* @param stdclass $cm* @return string*/private function activity_link($activity, $cm) {$o = html_writer::start_tag('h5');$o .= html_writer::link(new moodle_url('/mod/'.$activity->itemmodule.'/view.php',array('id' => $cm->id)), format_string(($activity->itemname), true, ['context' => context_module::instance($cm->id)]));$o .= html_writer::end_tag('h5');return $o;}/*** Generates a numeric array of scale entries* @param int $scaleid* @return array*/private function get_scale($scaleid) {global $DB;$scaletext = $DB->get_field('scale', 'scale', array('id' => $scaleid), IGNORE_MISSING);$scale = explode ( ',', $scaletext);return $scale;}/*** Return the plugin config settings for external functions.** @return stdClass the configs for both the block instance and plugin* @since Moodle 3.8*/public function get_config_for_external() {// Return all settings for all users since it is safe (no private keys, etc..).$instanceconfigs = !empty($this->config) ? $this->config : new stdClass();$pluginconfigs = get_config('block_activity_results');return (object) ['instance' => $instanceconfigs,'plugin' => $pluginconfigs,];}}