ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç styles.css000064400000007301152266565660006625 0ustar00.path-grade-report-singleview div.reporttable { margin: 20px 0 30px 0; padding: 10px 10px; background-color: #f8f9fa; text-align: center; } .path-grade-report-singleview div.groupselector, .path-grade-report-singleview div.selectitems { display: block; text-align: right; clear: both; } .path-grade-report-singleview div.singleselect + div.singleselect select, .path-grade-report-singleview div.groupselector select { margin-right: 0; } .path-grade-report-singleview div.reporttable div.singleselect form div { text-align: center; } .path-grade-report-singleview div.reporttable table.reporttable { margin: 0 auto 15px auto; } .path-grade-report-singleview div.reporttable form div { text-align: left; } .path-grade-report-singleview div.reporttable h2 { text-align: left; } .path-grade-report-singleview .overrideexcludecheck { width: 14.75px; height: 12px; color: #2ca14f; } .path-grade-report-singleview .report-navigation .container { max-width: 1060px; margin-left: 0; } .path-grade-report-singleview .zero-state img { width: 185px; } .path-grade-report-singleview .reporttable thead th, .path-grade-report-singleview .reporttable thead td { white-space: nowrap; border: none; } .path-grade-report-singleview .reporttable tbody tr, .path-grade-report-singleview .reporttable tbody tr:nth-of-type(odd), .path-grade-report-singleview .reporttable tbody tr:hover { color: inherit; background-color: white; border-top: none; border-bottom: 1px solid #dee2e6; } .path-grade-report-singleview .reporttable tbody th, .path-grade-report-singleview .reporttable tbody td.range { white-space: nowrap; font-weight: normal; } .path-grade-report-singleview .reporttable tbody th > * { display: inline-block; vertical-align: middle; margin: 0 2px; } .path-grade-report-singleview .itemnav { font-size: small; display: inline; padding-bottom: 0.5em; } .path-grade-report-singleview .itemnav.previtem { float: left; } .path-grade-report-singleview div.nextitem { float: right; } .path-grade-report-singleview .reporttable { width: 100%; } .path-grade-report-singleview .reporttable .generaltable th, .path-grade-report-singleview .reporttable .generaltable td { text-align: left; vertical-align: middle; border: none; } .path-grade-report-singleview div.reporttable form div.singleview_bulk { display: inline-block; text-align: left; margin-bottom: 1em; } .path-grade-report-singleview .singleview_bulk div > *, .path-grade-report-singleview .singleview_bulk fieldset > * { display: inline-block; vertical-align: middle; margin: 0; } .path-grade-report-singleview .singleselect select, .path-grade-report-singleview div.reporttable form .singleview_bulk select, .path-grade-report-singleview div.reporttable form .singleview_bulk input { margin-left: 10px; margin-right: 10px; } #bulkinsertmodal { width: 181px; height: 39px; top: 309px; border-radius: 4px; } .path-grade-report-singleview .singleview_bulk > fieldset { display: block; } .path-grade-report-singleview div.reporttable form .singleview_bulk > div.enable { margin-bottom: 0.5em; text-align: left; } .searchresultitemscontainer { max-height: 150px; height: 150px; overflow: auto; } .path-grade-report-singleview .search-widget[data-searchtype="user"] .userinitials { width: 40px; height: 40px; } .path-grade-report-singleview .reporttable .gradeitem span.small { font-size: 70%; } .path-grade-report-singleview .reporttable div.categoryitem .gradeitemheader, .path-grade-report-singleview .reporttable div.courseitem .gradeitemheader { font-weight: bold; } renderer.php000064400000015207152266565660007113 0ustar00. /** * Renderer for the grade single view report. * * @package gradereport_singleview * @copyright 2022 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ use core\output\comboboxsearch; use gradereport_singleview\report\singleview; /** * Custom renderer for the single view report. * * To get an instance of this use the following code: * $renderer = $PAGE->get_renderer('gradereport_singleview'); * * @copyright 2022 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class gradereport_singleview_renderer extends plugin_renderer_base { /** * Renders the user selector trigger element. * * @param object $course The course object. * @param int|null $userid The user ID. * @param int|null $groupid The group ID. * @return string The raw HTML to render. */ public function users_selector(object $course, ?int $userid = null, ?int $groupid = null): string { $courserenderer = $this->page->get_renderer('core', 'course'); $resetlink = new moodle_url('/grade/report/singleview/index.php', [ 'id' => $course->id, 'group' => $groupid ?? 0, 'reset' => 1, ]); $usersearch = ''; if ($userid) { $user = core_user::get_user($userid); $usersearch = fullname($user); } return $courserenderer->render( new \core_course\output\actionbar\user_selector( course: $course, resetlink: $resetlink, userid: $userid, groupid: $groupid, usersearch: $usersearch ) ); } /** * Renders the grade items selector trigger element. * * @param object $course The course object. * @param int|null $gradeitemid The grade item ID. * @return string The raw HTML to render. */ public function grade_items_selector(object $course, ?int $gradeitemid = null): string { $data = [ 'name' => 'itemid', 'courseid' => $course->id, 'instance' => rand(), ]; // If a particular grade item option is selected (not in zero state). if ($gradeitemid) { $gradeitemname = grade_item::fetch(['id' => $gradeitemid])->get_name(true); $data['selectedoption'] = [ 'text' => $gradeitemname, ]; $data['itemid'] = $gradeitemid; } $sbody = $this->render_from_template('core/local/comboboxsearch/searchbody', [ 'courseid' => $course->id, 'currentvalue' => optional_param('gradesearchvalue', '', PARAM_NOTAGS), 'instance' => $data['instance'], ]); $dropdown = new comboboxsearch( false, $this->render_from_template('gradereport_singleview/grade_item_selector', $data), $sbody, 'grade-search h-100', 'gradesearchwidget h-100', 'gradesearchdropdown overflow-auto', null, true, get_string('selectagrade', 'gradereport_singleview'), 'itemid', $gradeitemid ); return $this->render($dropdown); } /** * Creates and renders previous/next user/grade item navigation. * * @param object $gpr grade plugin return tracking object * @param int $courseid The course ID. * @param \context_course $context Context of the report. * @param singleview $report The single view report class. * @param int|null $groupid Group ID * @param string $itemtype User or Grade item type * @param int $itemid Either User ID or Grade item ID * @return string The raw HTML to render. * @throws moodle_exception */ public function report_navigation(object $gpr, int $courseid, \context_course $context, singleview $report, ?int $groupid, string $itemtype, int $itemid): string { $navigation = ''; $options = $report->screen->options(); $optionkeys = array_keys($options); $optionitemid = array_shift($optionkeys); $relreport = new gradereport_singleview\report\singleview( $courseid, $gpr, $context, $report->screen->item_type(), $optionitemid ); $reloptions = $relreport->screen->options(); $reloptionssorting = array_keys($relreport->screen->options()); $i = array_search($itemid, $reloptionssorting); $navparams = ['item' => $itemtype, 'id' => $courseid, 'group' => $groupid]; // Determine directionality so that icons can be modified to suit language. $previousarrow = right_to_left() ? 'right' : 'left'; $nextarrow = right_to_left() ? 'left' : 'right'; if ($i > 0) { $navparams['itemid'] = $reloptionssorting[$i - 1]; $link = (new moodle_url('/grade/report/singleview/index.php', $navparams)) ->out(false); $navigationdata['previoususer'] = [ 'name' => $reloptions[$navparams['itemid']], 'url' => $link, 'previousarrow' => $previousarrow ]; } if ($i < count($reloptionssorting) - 1) { $navparams['itemid'] = $reloptionssorting[$i + 1]; $link = (new moodle_url('/grade/report/singleview/index.php', $navparams)) ->out(false); $navigationdata['nextuser'] = [ 'name' => $reloptions[$navparams['itemid']], 'url' => $link, 'nextarrow' => $nextarrow ]; } if ($report->screen->supports_paging()) { $navigationdata['perpageselect'] = $report->screen->perpage_select(); } if (isset($navigationdata)) { $navigation = $this->render_from_template('gradereport_singleview/report_navigation', $navigationdata); } return $navigation; } } db/services.php000064400000002610152266565660007507 0ustar00. /** * singleview grade report external functions and service definitions. * * @package gradereport_singleview * @copyright 2022 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; $functions = [ 'gradereport_singleview_get_grade_items_for_search_widget' => [ 'methodname' => 'get_grade_items_for_search_widget', 'classname' => 'gradereport_singleview\\external\\singleview', 'description' => 'Get the gradeitem/(s) for a course', 'type' => 'read', 'ajax' => true, 'capabilities' => 'gradereport/singleview:view', 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE], ], ]; db/access.php000064400000002301152266565660007122 0ustar00. /** * The gradebook simple view - Database file * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $capabilities = array( 'gradereport/singleview:view' => array( 'riskbitmask' => RISK_PERSONAL, 'captype' => 'read', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW, ) ) ); lang/en/gradereport_singleview.php000064400000010707152266565660013402 0ustar00. /** * Strings for component 'gradereport_singleview', language 'en'. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); // General Strings. $string['all_grades'] = 'All grades'; $string['assessmentname'] = 'Grade item'; $string['blanks'] = 'Empty grades'; $string['bulkappliesto'] = 'For'; $string['bulkinsertvalue'] = 'Insert new grade'; $string['bulklegend'] = 'Bulk insert grades'; $string['bulkchoice'] = 'Bulk insert lets you replace multiple grades at once. Choose the grades you want to replace:'; $string['bulkperform'] = 'Perform bulk insert'; $string['bulkfor'] = 'Grades for {$a}'; $string['entrypage'] = 'Grade user or grade item'; $string['exclude'] = 'Exclude'; $string['excludeall'] = 'Exclude all grades'; $string['excludeallgrades'] = 'Exclude all'; $string['excludefor'] = 'Exclude for {$a}'; $string['excludenone'] = 'Do not exclude any grades'; $string['excludenonegrades'] = 'Exclude none'; $string['eventgradereportviewed'] = 'Grade single view report viewed.'; $string['feedbackfor'] = 'Feedback for {$a}'; $string['gradefor'] = 'Grade for {$a}'; $string['gradeitem'] = 'Viewing grades for {$a}'; $string['gradeitemedit'] = 'Editing grades for {$a}'; $string['gradeuser'] = 'Viewing {$a}\'s grades'; $string['gradeuseredit'] = 'Editing {$a}\'s grades'; $string['gotonextreport'] = 'Go to next user report'; $string['gotopreviousreport'] = 'Go to previous user report'; $string['noscreens'] = 'Could not find a suitable single view screen.'; $string['gradeitemcannotbeoverridden'] = 'This grade item cannot be overridden.'; $string['itemsperpage'] = 'Items per page'; $string['notvalid'] = 'Not a valid Single view screen: {$a}'; $string['override'] = 'Override'; $string['overrideall'] = 'Override all grades'; $string['overrideallgrades'] = 'Override all'; $string['overridefor'] = 'Override for {$a}'; $string['overridenone'] = 'Do not override any grades'; $string['overridenonegrades'] = 'Override none'; $string['overridenoneconfirm'] = 'This will remove all previously entered overridden grades on this page when you save changes.'; $string['pluginname'] = 'Single view'; $string['privacy:metadata'] = 'The Grade single view report only shows data stored in other locations.'; $string['removeoverride'] = 'Remove grade overrides'; $string['removeoverridesave'] = 'Remove overrides'; $string['savegrades'] = 'Saving grades'; $string['save'] = 'Save'; $string['savegradessuccess'] = 'Grades were set for {$a} items'; $string['singleview:view'] = 'View single view'; $string['singleviewreport_user'] = 'Single view for this user'; $string['singleviewreport_gradeitem'] = 'Single view for this item'; $string['summarygrade'] = 'A table of users, with columns for range, grade, feedback, and whether to override or exclude a particular grade.'; $string['summaryuser'] = 'A table of grade items, with columns for grade category, range, grade, feedback, and whether to override or exclude a particular grade.'; $string['unsavedataalert'] = 'If you have unsaved changes on the page, you will lose them if you proceed with bulk insert grades.'; $string['unsavedataconfirm'] = 'I understand that my unsaved changes will be lost.'; $string['userselect'] = 'Select activity'; $string['ariareporttype'] = 'Select a report type to view'; $string['viewby'] = 'View by'; $string['viewsingleuser'] = 'Search for a user to view all their grades'; $string['viewsinglegradeitem'] = 'Select a grade item above'; $string['selectagrade'] = 'Select a grade item'; $string['selectgradeitemlink'] = 'Select a grade item'; $string['singleviewdescription'] = 'In single view you can override or exclude selected grades and bulk insert grades.'; $string['unsaveddatawarning'] = 'Unsaved data warning'; pix/zero_state.svg000064400000022607152266565660010276 0ustar00 pix/zero_state_user.svg000064400000037301152266565660011331 0ustar00 pix/zero_state_grade.svg000064400000025145152266565660011440 0ustar00 lib.php000064400000006657152266565660006064 0ustar00. /** * Singleview report generic functions * * @package gradereport_singleview * @copyright 2023 Ilya Tregubov * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Returns link to singleview report for the current element * * @param context_course $context Course context * @param int $courseid Course ID * @param array $element An array representing an element in the grade_tree * @param grade_plugin_return $gpr A grade_plugin_return object * @param string $mode Mode - gradeitem or user * @param ?stdClass $templatecontext Template context * @return stdClass|null */ function gradereport_singleview_get_report_link(context_course $context, int $courseid, array $element, grade_plugin_return $gpr, string $mode, ?stdClass $templatecontext): ?stdClass { $reportstring = get_string('singleviewreport_' . $mode, 'gradereport_singleview'); if (!isset($templatecontext)) { $templatecontext = new stdClass(); } if ($mode == 'gradeitem') { // View all grades items. // FIXME: MDL-52678 This is extremely hacky we should have an API for inserting grade column links. if (get_capability_info('gradereport/singleview:view')) { if (has_all_capabilities(['gradereport/singleview:view', 'moodle/grade:viewall', 'moodle/grade:edit'], $context)) { $url = new moodle_url('/grade/report/singleview/index.php', [ 'id' => $courseid, 'item' => 'grade', 'itemid' => $element['object']->id ]); $gpr->add_url_params($url); $templatecontext->reporturl0 = html_writer::link($url, $reportstring, ['class' => 'dropdown-item', 'aria-label' => $reportstring, 'role' => 'menuitem']); return $templatecontext; } } } else if ($mode == 'user') { // FIXME: MDL-52678 This get_capability_info is hacky and we should have an API for inserting grade row links instead. $canseesingleview = false; if (get_capability_info('gradereport/singleview:view')) { $canseesingleview = has_all_capabilities(['gradereport/singleview:view', 'moodle/grade:viewall', 'moodle/grade:edit'], $context); } if ($canseesingleview) { $url = new moodle_url('/grade/report/singleview/index.php', ['id' => $courseid, 'itemid' => $element['userid'], 'item' => 'user']); $gpr->add_url_params($url); $templatecontext->reporturl0 = html_writer::link($url, $reportstring, ['class' => 'dropdown-item', 'aria-label' => $reportstring, 'role' => 'menuitem']); return $templatecontext; } } return null; } version.php000064400000002101152266565660006757 0ustar00. /** * Standard version file * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'gradereport_singleview'; // Full name of the plugin (used for diagnostics). $plugin->version = 2025041400; $plugin->requires = 2025040800; classes/external/singleview.php000064400000007405152266565660012741 0ustar00. namespace gradereport_singleview\external; use context_course; use core_course_external; use core_external\external_function_parameters; use core_external\external_multiple_structure; use core_external\external_single_structure; use core_external\external_value; use core_external\external_warnings; use grade_tree; defined('MOODLE_INTERNAL') || die; require_once($CFG->dirroot.'/course/externallib.php'); require_once($CFG->dirroot.'/grade/lib.php'); /** * External grade report singleview API * * @package gradereport_singleview * @copyright 2022 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class singleview extends core_course_external { /** * Describes the parameters for get_users_for_course. * * @return external_function_parameters */ public static function get_grade_items_for_search_widget_parameters(): external_function_parameters { return new external_function_parameters ( [ 'courseid' => new external_value(PARAM_INT, 'Course Id', VALUE_REQUIRED) ] ); } /** * Given a course ID find the * * @param int $courseid * @return array Users and warnings to pass back to the calling widget. * @throws coding_exception * @throws invalid_parameter_exception * @throws moodle_exception * @throws restricted_context_exception */ protected static function get_grade_items_for_search_widget(int $courseid): array { global $PAGE, $USER, $CFG; $params = self::validate_parameters( self::get_grade_items_for_search_widget_parameters(), [ 'courseid' => $courseid, ] ); $warnings = []; $coursecontext = context_course::instance($params['courseid']); parent::validate_context($coursecontext); $gtree = new grade_tree($params['courseid'], false, true, null, !$CFG->enableoutcomes); $gradeableitems = $gtree->get_items(); $gradeitems = array_map(function ($gradeitem) use ($PAGE, $USER, $params) { $item = new \stdClass(); $item->id = $gradeitem->id; $item->name = $gradeitem->get_name(true); return $item; }, $gradeableitems); return [ 'gradeitems' => $gradeitems, 'warnings' => $warnings, ]; } /** * Returns description of what the user search for the widget should return. * * @return external_single_structure */ public static function get_grade_items_for_search_widget_returns(): external_single_structure { return new external_single_structure([ 'gradeitems' => new external_multiple_structure( new external_single_structure([ 'id' => new external_value(PARAM_INT, 'ID of the grade item', VALUE_OPTIONAL), 'name' => new external_value(PARAM_TEXT, 'The full name of the grade item', VALUE_OPTIONAL) ]) ), 'warnings' => new external_warnings(), ]); } } classes/privacy/provider.php000064400000003022152266565660012241 0ustar00. /** * Privacy Subsystem implementation for gradereport_singleview. * * @package gradereport_singleview * @copyright 2018 Sara Arjona * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for gradereport_singleview implementing null_provider. * * @copyright 2018 Sara Arjona * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } classes/local/screen/grade.php000064400000031251152266565660012372 0ustar00. /** * The screen with a list of users. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\screen; use context_course; use grade_report; use gradereport_singleview\local\ui\range; use gradereport_singleview\local\ui\bulk_insert; use grade_grade; use grade_item; use moodle_url; use pix_icon; use html_writer; use gradereport_singleview; defined('MOODLE_INTERNAL') || die; /** * The screen with a list of users. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class grade extends tablelike implements selectable_items, filterable_items { /** * Used for paging * @var int $totalitemcount */ private $totalitemcount = 0; /** * True if this is a manual grade item * @var bool $requiresextra */ private $requiresextra = false; /** * True if there are more users than our limit. * @var bool $requirepaging */ private $requirespaging = true; /** * To store UI element that generates a grade_item min/max range. * @var range; */ protected $range; /** * Returns a grade_item instance or false if none found. * @var grade_item|bool */ public $item; /** * True if $CFG->grade_overridecat is true * * @return bool */ public static function allowcategories(): bool { return get_config('moodle', 'grade_overridecat'); } /** * Filter the list excluding category items (if required)? * @param grade_item $item The grade item. * @return bool */ public static function filter($item): bool { return get_config('moodle', 'grade_overridecat') || !($item->is_course_item() || $item->is_category_item()); } /** * Get the label for the select box that chooses items for this page. * @return string */ public function select_label(): string { return get_string('selectuser', 'gradereport_singleview'); } /** * Get the description of this page * @return string */ public function description(): string { return get_string('users'); } /** * Convert this list of items into an options list * * @return array */ public function options(): array { $options = []; foreach ($this->items as $userid => $user) { $options[$userid] = fullname($user); } return $options; } /** * Return the type of the things in this list. * @return string */ public function item_type(): string { return 'user'; } /** * Get the original settings for this item * @return array */ public function original_definition(): array { return [ 'finalgrade', 'feedback', 'override', 'exclude' ]; } /** * Init this page * * @param bool $selfitemisempty True if we have not selected a user. */ public function init($selfitemisempty = false) { $this->items = grade_report::get_gradable_users($this->courseid, $this->groupid); $this->totalitemcount = count($this->items); if ($selfitemisempty) { return; } // If we change perpage on pagination we might end up with a page that doesn't exist. if ($this->perpage) { $numpages = intval($this->totalitemcount / $this->perpage) + 1; if ($numpages <= $this->page) { $this->page = 0; } } else { $this->page = 0; } $params = [ 'id' => $this->itemid, 'courseid' => $this->courseid ]; $this->item = grade_item::fetch($params); if (!self::filter($this->item)) { $this->items = []; $this->set_init_error(get_string('gradeitemcannotbeoverridden', 'gradereport_singleview')); } $this->requiresextra = !$this->item->is_manual_item(); $this->setup_structure(); $this->set_definition($this->original_definition()); $this->set_headers($this->original_headers()); } /** * Get the table headers * * @return array */ public function original_headers() { return [ get_string('fullnameuser', 'core'), '', // For filter icon. get_string('gradenoun'), get_string('range', 'grades'), get_string('feedback', 'grades'), get_string('override', 'gradereport_singleview'), get_string('exclude', 'gradereport_singleview'), ]; } /** * Format a row in the table * * @param stdClass $item * @return array */ public function format_line($item): array { global $OUTPUT; $grade = $this->fetch_grade_or_default($this->item, $item->id); $gradestatus = ''; // Show hidden icon if the grade is hidden and the user has permission to view hidden grades. $showhiddenicon = $grade->is_hidden() && has_capability('moodle/grade:viewhidden', context_course::instance($this->courseid)); $context = [ 'hidden' => $showhiddenicon, 'locked' => $grade->is_locked(), ]; if (in_array(true, $context)) { $context['classes'] = 'gradestatus'; $gradestatus = $OUTPUT->render_from_template('core_grades/status_icons', $context); } if (has_capability('moodle/site:viewfullnames', \context_course::instance($this->courseid))) { $fullname = fullname($item, true); } else { $fullname = fullname($item); } $item->imagealt = $fullname; $url = new moodle_url("/user/view.php", ['id' => $item->id, 'course' => $this->courseid]); $grade->label = $fullname; $userpic = $OUTPUT->user_picture($item, ['link' => false, 'visibletoscreenreaders' => false]); $formatteddefinition = $this->format_definition($grade); $line = [ html_writer::link($url, $userpic . $fullname), $this->get_user_action_menu($item), $formatteddefinition['finalgrade'] . $gradestatus, $this->item_range(), $formatteddefinition['feedback'], $formatteddefinition['override'], $formatteddefinition['exclude'], ]; $lineclasses = [ 'user', 'action', 'grade', 'range', ]; $outputline = []; $i = 0; foreach ($line as $key => $value) { $cell = new \html_table_cell($value); if ($isheader = $i == 0) { $cell->header = $isheader; $cell->scope = "row"; } if (array_key_exists($key, $lineclasses)) { $cell->attributes['class'] = $lineclasses[$key]; } $outputline[] = $cell; $i++; } return $outputline; } /** * Get the range ui element for this grade_item * * @return element; */ public function item_range() { if (empty($this->range)) { $this->range = new range($this->item); } return $this->range; } /** * Does this page require paging? * * @return bool */ public function supports_paging(): bool { return $this->requirespaging; } /** * Get the pager for this page. * * @return string */ public function pager(): string { global $OUTPUT; return $OUTPUT->paging_bar( $this->totalitemcount, $this->page, $this->perpage, new moodle_url('/grade/report/singleview/index.php', [ 'perpage' => $this->perpage, 'id' => $this->courseid, 'group' => $this->groupid, 'itemid' => $this->itemid, 'item' => 'grade' ]) ); } /** * Get the heading for this page. * * @return string */ public function heading(): string { global $PAGE; $headinglangstring = $PAGE->user_is_editing() ? 'gradeitemedit' : 'gradeitem'; return get_string($headinglangstring, 'gradereport_singleview', $this->item->get_name()); } /** * Get the summary for this table. * * @return string */ public function summary(): string { return get_string('summarygrade', 'gradereport_singleview'); } /** * Process the data from the form. * * @param array $data * @return \stdClass of warnings */ public function process($data): \stdClass { $bulk = new bulk_insert($this->item); // Bulk insert messages the data to be passed in // ie: for all grades of empty grades apply the specified value. if ($bulk->is_applied($data)) { $filter = $bulk->get_type($data); $insertvalue = $bulk->get_insert_value($data); // Appropriately massage data that may not exist. if ($this->supports_paging()) { $gradeitem = grade_item::fetch([ 'courseid' => $this->courseid, 'id' => $this->item->id ]); $null = $gradeitem->gradetype == GRADE_TYPE_SCALE ? -1 : ''; foreach ($this->items as $itemid => $item) { $field = "finalgrade_{$gradeitem->id}_{$itemid}"; if (isset($data->$field)) { continue; } $grade = grade_grade::fetch([ 'itemid' => $gradeitem->id, 'userid' => $itemid ]); $data->$field = empty($grade) ? $null : $grade->finalgrade; $data->{"old$field"} = $data->$field; } } foreach ($data as $varname => $value) { if (preg_match('/^oldoverride_(\d+)_(\d+)/', $varname, $matches)) { // If we've selected overriding all grades. if ($filter == 'all') { $override = "override_{$matches[1]}_{$matches[2]}"; $data->$override = '1'; } } if (!preg_match('/^finalgrade_(\d+)_/', $varname, $matches)) { continue; } $gradeitem = grade_item::fetch([ 'courseid' => $this->courseid, 'id' => $matches[1] ]); $isscale = ($gradeitem->gradetype == GRADE_TYPE_SCALE); $empties = (trim($value) === '' || ($isscale && $value == -1)); if ($filter == 'all' || $empties) { $data->$varname = ($isscale && empty($insertvalue)) ? -1 : $insertvalue; } } } return parent::process($data); } /** * Return the action menu HTML for the user item. * * @param \stdClass $user * @return mixed */ private function get_user_action_menu(\stdClass $user) { global $OUTPUT; $menuitems = []; $url = new moodle_url($this->format_link('user', $user->id)); $title = get_string('showallgrades', 'core_grades'); $menuitems[] = new \action_menu_link_secondary($url, null, $title); $menu = new \action_menu($menuitems); $label = get_string('actions'); $icon = $OUTPUT->pix_icon('i/moremenu', '') . \core\output\html_writer::span($label, 'visually-hidden d-inline-block'); $extraclasses = 'btn btn-icon d-flex'; $menu->set_menu_trigger($icon, $extraclasses); $menu->triggerattributes['title'] = $label; $menu->set_menu_left(); $menu->set_boundary('window'); return $OUTPUT->render($menu); } } classes/local/screen/filterable_items.php000064400000002644152266565660014626 0ustar00. /** * The gradebook interface for a filterable class. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\screen; defined('MOODLE_INTERNAL') || die; /** * The gradebook interface for a filterable class. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ interface filterable_items { /** * Return true/false if this item should be filtered. * @param mixed $item (user or grade_item) * @return bool */ public static function filter($item): bool; } classes/local/screen/grade_select.php000064400000005365152266565660013740 0ustar00. namespace gradereport_singleview\local\screen; /** * The grade search screen. * * @package gradereport_singleview * @copyright 2022 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class grade_select extends screen { public function init($selfitemisempty = false) { } /** * Return the HTML for the page. * * @return string */ public function html(): string { global $OUTPUT, $COURSE, $USER; $userlink = new \moodle_url('/grade/report/singleview/index.php', ['id' => $COURSE->id, 'item' => 'user_select']); $gradelink = new \moodle_url('/grade/report/singleview/index.php', ['id' => $COURSE->id, 'item' => 'grade_select']); $gpr = new \grade_plugin_return(['type' => 'report', 'plugin' => 'singleview', 'courseid' => $COURSE->id, 'userid' => $USER->id]); $context = [ 'courseid' => $gpr->courseid, 'imglink' => $OUTPUT->image_url('zero_state_grade', 'gradereport_singleview'), 'userzerolink' => $userlink->out(false), 'userselectactive' => false, 'gradezerolink' => $gradelink->out(false), 'gradeselectactive' => true, 'displaylabel' => true, 'groupmodeenabled' => $COURSE->groupmode, 'groupactionbaseurl' => 'index.php?item=grade_select', 'groupid' => $gpr->groupid ]; return $OUTPUT->render_from_template('gradereport_singleview/zero_state_grade', $context); } public function item_type(): ?string { return false; } /** * Should we show the base singlereport group selector? * @return bool */ public function display_group_selector(): bool { return false; } /** * Get the heading for the screen. * * @return string */ public function heading(): string { return ' '; } /** * Does this screen support paging? * * @return bool */ public function supports_paging(): bool { return false; } } classes/local/screen/user_select.php000064400000005415152266565660013630 0ustar00. namespace gradereport_singleview\local\screen; use gradereport_singleview; /** * The user search screen. * * @package gradereport_singleview * @copyright 2022 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class user_select extends screen { public function init($selfitemisempty = false) { } /** * Return the HTML for the page. * * @return string */ public function html(): string { global $OUTPUT, $COURSE, $USER; $userlink = new \moodle_url('/grade/report/singleview/index.php', ['id' => $COURSE->id, 'item' => 'user_select']); $gradelink = new \moodle_url('/grade/report/singleview/index.php', ['id' => $COURSE->id, 'item' => 'grade_select']); $gpr = new \grade_plugin_return(['type' => 'report', 'plugin' => 'singleview', 'courseid' => $COURSE->id, 'userid' => $USER->id]); $context = [ 'courseid' => $gpr->courseid, 'imglink' => $OUTPUT->image_url('zero_state_user', 'gradereport_singleview'), 'userzerolink' => $userlink->out(false), 'userselectactive' => true, 'gradezerolink' => $gradelink->out(false), 'gradeselectactive' => false, 'displaylabel' => true, 'groupmodeenabled' => $COURSE->groupmode, 'groupactionbaseurl' => 'index.php?item=user_select', 'groupid' => $gpr->groupid ]; return $OUTPUT->render_from_template('gradereport_singleview/zero_state_user', $context); } public function item_type(): ?string { return false; } /** * Should we show the base singlereport group selector? * @return bool */ public function display_group_selector(): bool { return false; } /** * Get the heading for the screen. * * @return string */ public function heading(): string { return ' '; } /** * Does this screen support paging? * * @return bool */ public function supports_paging(): bool { return false; } } classes/local/screen/selectable_items.php000064400000003360152266565660014614 0ustar00. /** * Interface for a list of selectable things. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\screen; defined('MOODLE_INTERNAL') || die; /** * Interface for a list of selectable things. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ interface selectable_items { /** * Get the description of this list * @return string */ public function description(): string; /** * Get the label for the select box that chooses items for this page. * @return string */ public function select_label(): string; /** * Get the list of options to show. * @return array */ public function options(): array; /** * Get type of things in the list (gradeitem or user) * @return string */ public function item_type(): string; } classes/local/screen/screen.php000064400000032667152266565660012603 0ustar00. /** * Abstract class used as a base for the 3 screens. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\screen; use context_course; use grade_report; use moodle_url; use html_writer; use grade_structure; use grade_grade; use grade_item; use stdClass; defined('MOODLE_INTERNAL') || die; require_once($CFG->dirroot . '/grade/report/lib.php'); /** * Abstract class used as a base for the 3 screens. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class screen { /** * The id of the course * @var int $courseid */ protected $courseid; /** * Either a user id or a grade_item id * @var int|null $itemid */ protected $itemid; /** * The currently set groupid (if set) * @var int $groupid */ protected $groupid; /** * The course context * @var context_course $context */ protected $context; /** * The page number * @var int $page */ protected $page; /** * Results per page * @var int $perpage */ protected $perpage; /** * List of items on the page, they could be users or grade_items * @var array $items */ protected $items; /** @var int Maximum number of students that can be shown on one page */ protected static $maxperpage = 1000; /** * List of allowed values for 'perpage' setting * @var array $validperpage */ protected static $validperpage = [20, 100]; /** * To store course data * @var stdClass */ protected $course; /** * General structure representing grade items in course * @var grade_structure */ protected $structure; /** * Constructor * * @param int $courseid The course id * @param int|null $itemid The item id * @param int|null $groupid The group id */ public function __construct(int $courseid, ?int $itemid, ?int $groupid = null) { global $DB; $this->courseid = $courseid; $this->itemid = $itemid; $this->groupid = $groupid; $this->context = context_course::instance($this->courseid); $this->course = $DB->get_record('course', ['id' => $courseid]); $this->page = optional_param('page', 0, PARAM_INT); $cache = \cache::make_from_params(\cache_store::MODE_SESSION, 'gradereport_singleview', 'perpage'); $perpage = optional_param('perpage', null, PARAM_INT); if ((!in_array($perpage, self::$validperpage) && $perpage != self::$maxperpage) && ($perpage !== 0)) { // Get from cache. $perpage = $cache->get(get_class($this)); } else { // Save to cache. $cache->set(get_class($this), $perpage); } if (isset($perpage) && $perpage) { $this->perpage = $perpage; } else { // Get from cache. $perpage = $cache->get(get_class($this)); $this->perpage = ($perpage === 0) ? $perpage : min(self::$validperpage); } $this->init(empty($itemid)); } /** * Cache the grade_structure class */ public function setup_structure() { $this->structure = new grade_structure(); $this->structure->modinfo = get_fast_modinfo($this->course); } /** * Create a nice link from a thing (user or grade_item). * * @param string $screen * @param int $itemid * @param bool|null $display Should we wrap this in an anchor ? * @return string The link */ public function format_link(string $screen, int $itemid, ?bool $display = null): string { $url = new moodle_url('/grade/report/singleview/index.php', [ 'id' => $this->courseid, 'item' => $screen, 'itemid' => $itemid, 'group' => $this->groupid, ]); if ($display) { return html_writer::link($url, $display); } else { return $url; } } /** * Get the grade_grade * * @param grade_item $item The grade_item * @param int $userid The user id * @return grade_grade */ public function fetch_grade_or_default(grade_item $item, int $userid): grade_grade { $grade = grade_grade::fetch([ 'itemid' => $item->id, 'userid' => $userid ]); if (!$grade) { $default = new stdClass; $default->userid = $userid; $default->itemid = $item->id; $default->feedback = ''; $grade = new grade_grade($default, false); } $grade->grade_item = $item; return $grade; } /** * Get the default heading for the screen. * * @return string */ public function heading(): string { return get_string('entrypage', 'gradereport_singleview'); } /** * Override this to init the screen. * * @param boolean $selfitemisempty True if no item has been selected yet. */ abstract public function init(bool $selfitemisempty = false); /** * Get the type of items in the list. * * @return null|string */ abstract public function item_type(): ?string; /** * Get the entire screen as a string. * * @return string */ abstract public function html(): string; /** * Does this screen support paging? * * @return bool */ public function supports_paging(): bool { return true; } /** * Default pager * * @return string */ public function pager(): string { return ''; } /** * Initialise the js for this screen. */ public function js() { global $PAGE; $module = [ 'name' => 'gradereport_singleview', 'fullpath' => '/grade/report/singleview/js/singleview.js', 'requires' => ['base', 'dom', 'event', 'event-simulate', 'io-base'] ]; $PAGE->requires->strings_for_js(['overridenoneconfirm', 'removeoverride', 'removeoverridesave'], 'gradereport_singleview'); $PAGE->requires->js_init_call('M.gradereport_singleview.init', [], false, $module); } /** * Process the data from a form submission. * * @param array|object $data * @return stdClass of warnings */ public function process($data): stdClass { $warnings = []; $fields = $this->definition(); // Avoiding execution timeouts when updating // a large amount of grades. $progress = 0; $progressbar = new \core\progress\display_if_slow(); $progressbar->start_html(); $progressbar->start_progress(get_string('savegrades', 'gradereport_singleview'), count((array) $data) - 1); $changecount = []; // This array is used to determine if the override should be excluded from being counted as a change. $ignorevalues = []; foreach ($data as $varname => $throw) { $progressbar->progress($progress); $progress++; if (preg_match("/(\w+)_(\d+)_(\d+)/", $varname, $matches)) { $itemid = $matches[2]; $userid = $matches[3]; } else { continue; } $gradeitem = grade_item::fetch([ 'id' => $itemid, 'courseid' => $this->courseid ]); if (preg_match('/^old[oe]{1}/', $varname)) { $elementname = preg_replace('/^old/', '', $varname); if (!isset($data->$elementname)) { // Decrease the progress because we've increased the // size of the array we are iterating through. $progress--; $data->$elementname = false; } } if (!in_array($matches[1], $fields)) { continue; } if (!$gradeitem) { continue; } $grade = $this->fetch_grade_or_default($gradeitem, $userid); $classname = '\\gradereport_singleview\\local\\ui\\' . $matches[1]; $element = new $classname($grade); $name = $element->get_name(); $oldname = "old$name"; $posted = $data->$name; $format = $element->determine_format(); if ($format->is_textbox() and trim($data->$name) === '') { $data->$name = null; } // Same value; skip. if (isset($data->$oldname) && $data->$oldname == $posted) { continue; } // If the user submits Exclude grade elements without the proper. // permissions then we should refuse to update. if ($matches[1] === 'exclude' && !has_capability('moodle/grade:manage', $this->context)){ $warnings[] = get_string('nopermissions', 'error', get_string('grade:manage', 'role')); continue; } $msg = $element->set($posted); // Value to check against our list of matchelements to ignore. $check = explode('_', $varname, 2); // Optional type. if (!empty($msg)) { $warnings[] = $msg; if ($element instanceof \gradereport_singleview\local\ui\finalgrade) { // Add this value to this list so that the override object that is coming next will also be skipped. $ignorevalues[$check[1]] = $check[1]; // This item wasn't changed so don't add to the changecount. continue; } } // Check to see if this value has already been skipped. if (array_key_exists($check[1], $ignorevalues)) { continue; } if (preg_match('/_(\d+)_(\d+)/', $varname, $matchelement)) { $changecount[$matchelement[0]] = 1; } } // Some post-processing. $eventdata = new stdClass; $eventdata->warnings = $warnings; $eventdata->post_data = $data; $eventdata->instance = $this; $eventdata->changecount = $changecount; $progressbar->end_html(); return $eventdata; } /** * By default, there are no options. * @return array */ public function options(): array { return []; } /** * Should we show the group selector? * @return bool */ public function display_group_selector(): bool { return true; } /** * Should we show the next prev selector? * @return bool */ public function supports_next_prev(): bool { return true; } /** * Load a valid list of users for this gradebook as the screen "items". * * @deprecated since Moodle 4.3 * @return array A list of enroled users. */ protected function load_users(): array { debugging('The function ' . __FUNCTION__ . '() is deprecated. Please use grade_report::get_gradable_users() instead.', DEBUG_DEVELOPER); return grade_report::get_gradable_users($this->courseid, $this->groupid); } /** * Allow selection of number of items to display per page. * @return string */ public function perpage_select(): string { global $PAGE, $OUTPUT; $url = new moodle_url($PAGE->url); $numusers = count($this->items); // Print per-page dropdown. $pagingoptions = self::$validperpage; if ($this->perpage) { $pagingoptions[] = $this->perpage; // To make sure the current preference is within the options. } $pagingoptions = array_unique($pagingoptions); sort($pagingoptions); $pagingoptions = array_combine($pagingoptions, $pagingoptions); if ($numusers > self::$maxperpage) { $pagingoptions[self::$maxperpage] = self::$maxperpage; } else { $pagingoptions['0'] = get_string('all'); } $perpagedata = [ 'baseurl' => $url->out(false), 'options' => [] ]; foreach ($pagingoptions as $key => $name) { $perpagedata['options'][] = [ 'name' => $name, 'value' => $key, 'selected' => $key == $this->perpage, ]; } // The number of students per page is always limited even if it is claimed to be unlimited. $this->perpage = $this->perpage ?: self::$maxperpage; $perpagedata['pagingbar'] = $this->pager(); return $OUTPUT->render_from_template('gradereport_singleview/perpage', $perpagedata); } } classes/local/screen/select.php000064400000015722152266565660012574 0ustar00. /** * The gradebook simple view - initial view to select your search options * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\screen; use gradereport_singleview; use moodle_url; defined('MOODLE_INTERNAL') || die; /** * The gradebook simple view - initial view to select your search options * * @deprecated since Moodle 4.3 * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class select extends screen { /** @var \stdClass course data. */ public $item; /** * Initialise this screen * * @deprecated since Moodle 4.3 * @param bool $selfitemisempty Has an item been selected (will be false) */ public function init($selfitemisempty = false) { global $DB; debugging('The function ' . __FUNCTION__ . '() is deprecated as part of the deprecation process of the ' . '\gradereport_singleview\local\screen\select class which is no longer used.', DEBUG_DEVELOPER); $roleids = explode(',', get_config('moodle', 'gradebookroles')); $this->items = []; foreach ($roleids as $roleid) { // Keeping the first user appearance. $this->items = $this->items + get_role_users( $roleid, $this->context, false, '', 'u.id, u.lastname, u.firstname', null, $this->groupid, $this->perpage * $this->page, $this->perpage ); } $this->item = $DB->get_record('course', ['id' => $this->courseid]); } /** * Get the type of items on this screen, not valid so return false. * * @deprecated since Moodle 4.3 * @return string|null */ public function item_type(): ?string { debugging('The function ' . __FUNCTION__ . '() is deprecated as part of the deprecation process of the ' . '\gradereport_singleview\local\screen\select class which is no longer used.', DEBUG_DEVELOPER); return false; } /** * Return the HTML for the page. * * @deprecated since Moodle 4.3 * @return string */ public function html(): string { global $OUTPUT, $COURSE; debugging('The function ' . __FUNCTION__ . '() is deprecated as part of the deprecation process of the ' . '\gradereport_singleview\local\screen\select class which is no longer used.', DEBUG_DEVELOPER); if ($this->itemid === null) { $userlink = new \moodle_url('/grade/report/singleview/index.php', ['id' => $COURSE->id, 'item' => 'user_select']); $gradelink = new \moodle_url('/grade/report/singleview/index.php', ['id' => $COURSE->id, 'item' => 'grade_select']); $context = [ 'courseid' => $COURSE->id, 'imglink' => $OUTPUT->image_url('zero_state', 'gradereport_singleview'), 'userzerolink' => $userlink->out(false), 'userselectactive' => false, 'gradezerolink' => $gradelink->out(false), 'gradeselectactive' => false, 'displaylabel' => false ]; return $OUTPUT->render_from_template('gradereport_singleview/zero_state', $context); } $html = ''; $types = gradereport_singleview\report\singleview::valid_screens(); foreach ($types as $type) { $classname = "gradereport_singleview\\local\\screen\\{$type}"; $screen = new $classname($this->courseid, null, $this->groupid); if (!$screen instanceof selectable_items) { continue; } $options = $screen->options(); if (empty($options)) { continue; } $params = [ 'id' => $this->courseid, 'item' => $screen->item_type(), 'group' => $this->groupid ]; $url = new moodle_url('/grade/report/singleview/index.php', $params); $select = new \single_select($url, 'itemid', $options, '', ['' => $screen->select_label()]); $select->set_label($screen->select_label(), ['class' => 'accesshide']); $html .= $OUTPUT->render($select); } $html = $OUTPUT->container($html, 'selectitems'); if (empty($html)) { $OUTPUT->notification(get_string('noscreens', 'gradereport_singleview')); } return $html; } /** * Should we show the next prev selector? * * @deprecated since Moodle 4.3 * @return bool */ public function supports_next_prev(): bool { debugging('The function ' . __FUNCTION__ . '() is deprecated as part of the deprecation process of the ' . '\gradereport_singleview\local\screen\select class which is no longer used.', DEBUG_DEVELOPER); return false; } /** * Should we show the base singlereport group selector? * * @deprecated since Moodle 4.3 * @return bool */ public function display_group_selector(): bool { debugging('The function ' . __FUNCTION__ . '() is deprecated as part of the deprecation process of the ' . '\gradereport_singleview\local\screen\select class which is no longer used.', DEBUG_DEVELOPER); if ($this->itemid === null) { return false; } return true; } /** * Get the heading for the screen. * * @deprecated since Moodle 4.3 * @return string */ public function heading(): string { debugging('The function ' . __FUNCTION__ . '() is deprecated as part of the deprecation process of the ' . '\gradereport_singleview\local\screen\select class which is no longer used.', DEBUG_DEVELOPER); return ' '; } /** * Does this screen support paging? * * @deprecated since Moodle 4.3 * @return bool */ public function supports_paging(): bool { debugging('The function ' . __FUNCTION__ . '() is deprecated as part of the deprecation process of the ' . '\gradereport_singleview\local\screen\select class which is no longer used.', DEBUG_DEVELOPER); return false; } } classes/local/screen/tablelike.php000064400000015364152266565660013253 0ustar00. /** * The gradebook simple view - base class for the table * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\screen; use gradereport_singleview\local\ui\be_readonly; use html_table; use html_writer; use stdClass; use grade_grade; use gradereport_singleview\local\ui\bulk_insert; defined('MOODLE_INTERNAL') || die; /** * The gradebook simple view - base class for the table * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class tablelike extends screen implements be_readonly { /** * A list of table headers * @var array $headers */ protected $headers = []; /** * A list of errors that mean we should not show the table * @var array $initerrors */ protected $initerrors = []; /** * Describes the columns in the table * @var array $definition */ protected $definition = []; /** * Total items * @var int $total */ protected $total; /** * Table tab index * @var int $index */ protected $index; /** * The grade item or user. * @var mixed $item */ protected $item; /** * Format a row of the table * * @var mixed $item * @return array */ abstract public function format_line($item): array; /** * Get the summary for this table. * * @return string */ abstract public function summary(): string; /** * Get the table headers * * @return array */ public function headers(): array { return $this->headers; } /** * Set the table headers * * @param array $overwrite New headers * @return tablelike This */ public function set_headers(array $overwrite): tablelike { $this->headers = $overwrite; return $this; } /** * Get the list of errors * * @return array */ public function init_errors(): array { return $this->initerrors; } /** * Set an error detected while building the page. * * @param string $mesg */ public function set_init_error(string $mesg) { $this->initerrors[] = $mesg; } /** * Get the table definition * * @return array The definition. */ public function definition(): array { return $this->definition; } /** * Set the table definition * * @param array $overwrite New definition * @return tablelike This */ public function set_definition(array $overwrite): tablelike { $this->definition = $overwrite; return $this; } /** * Get a element to generate the HTML for this table row * @param grade_grade $grade The grade. * @return array */ public function format_definition(grade_grade $grade): array { $line = []; foreach ($this->definition() as $i => $field) { // Table tab index. $tab = ($i * $this->total) + $this->index; $classname = '\\gradereport_singleview\\local\\ui\\' . $field; $html = new $classname($grade, $tab); if ($field == 'finalgrade' and !empty($this->structure)) { $html .= $this->structure->get_grade_action_menu($grade); } // Singleview users without proper permissions should be presented // disabled checkboxes for the Exclude grade attribute. if ($field == 'exclude' && !has_capability('moodle/grade:manage', $this->context)) { $html->disabled = true; } $line[$field] = $html; } return $line; } /** * Get the HTML for the whole table * @return string */ public function html(): string { global $OUTPUT; if (!empty($this->initerrors)) { $warnings = ''; foreach ($this->initerrors as $mesg) { $warnings .= $OUTPUT->notification($mesg); } return $warnings; } $table = new html_table(); $table->id = 'singleview-grades'; $table->head = $this->headers(); $summary = $this->summary(); if (!empty($summary)) { $table->caption = $summary; $table->captionhide = true; } // To be used for extra formatting. $this->index = 0; $this->total = count($this->items); foreach ($this->items as $item) { if ($this->index >= ($this->perpage * $this->page) && $this->index < ($this->perpage * ($this->page + 1))) { $table->data[] = $this->format_line($item); } $this->index++; } $data = new stdClass(); $data->table = $table; $data->instance = $this; $html = html_writer::table($table); return html_writer::div($html, 'reporttable position-relative'); } /** * Get the HTML for the bulk insert form * * @return string */ public function bulk_insert() { return html_writer::tag( 'div', (new bulk_insert($this->item))->html(), ['class' => 'singleview_bulk', 'hidden' => 'hidden'] ); } /** * Return true if this is read-only. * * @return bool */ public function is_readonly(): bool { global $USER; return empty($USER->editing); } /** * Get the buttons for saving changes. * @param bool $disabled If button is disabled * * @return array */ public function buttons(bool $disabled = false): array { global $OUTPUT; $params = ['type' => 'submit', 'value' => get_string('save', 'gradereport_singleview')]; if ($disabled) { $params['disabled'] = 'disabled'; } return [$OUTPUT->render_from_template('gradereport_singleview/button', $params)]; } } classes/local/screen/user.php000064400000033522152266565660012271 0ustar00. /** * The user screen. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\screen; use context_course; use grade_seq; use gradereport_singleview; use moodle_url; use pix_icon; use html_writer; use gradereport_singleview\local\ui\range; use gradereport_singleview\local\ui\bulk_insert; use grade_item; use grade_grade; use stdClass; defined('MOODLE_INTERNAL') || die; /** * The user screen. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class user extends tablelike implements selectable_items { /** @var array $categories A cache for grade_item categories */ private $categories = []; /** @var int $requirespaging Do we have more items than the paging limit? */ private $requirespaging = true; /** @var array get a valid user. */ public $item = []; /** * Get the label for the select box that chooses items for this page. * @return string */ public function select_label(): string { return get_string('selectgrade', 'gradereport_singleview'); } /** * Get the description for the screen. * * @return string */ public function description(): string { return get_string('gradeitems', 'grades'); } /** * Convert the list of items to a list of options. * * @return array */ public function options(): array { $result = []; foreach ($this->items as $itemid => $item) { $result[$itemid] = $item->get_name(); } return $result; } /** * Get the type of items on this screen. * * @return string */ public function item_type(): string { return 'grade'; } /** * Init the screen * * @param bool $selfitemisempty Have we selected an item yet? */ public function init($selfitemisempty = false) { if (!$selfitemisempty) { $validusers = \grade_report::get_gradable_users($this->courseid, $this->groupid); if (!isset($validusers[$this->itemid])) { // If the passed user id is not valid, show the first user from the list instead. $this->item = reset($validusers); $this->itemid = $this->item->id; } else { $this->item = $validusers[$this->itemid]; } } $seq = new grade_seq($this->courseid, true); $this->items = []; foreach ($seq->items as $itemid => $item) { if (grade::filter($item)) { $this->items[$itemid] = $item; } } // If we change perpage on pagination we might end up with a page that doesn't exist. if ($this->perpage) { $numpages = intval(count($this->items) / $this->perpage) + 1; if ($numpages <= $this->page) { $this->page = 0; } } else { $this->page = 0; } $this->requirespaging = count($this->items) > $this->perpage; $this->setup_structure(); $this->definition = [ 'finalgrade', 'feedback', 'override', 'exclude' ]; $this->set_headers($this->original_headers()); } /** * Get the list of headers for the table. * * @return array List of headers */ public function original_headers(): array { return [ get_string('assessmentname', 'gradereport_singleview'), '', // For filter icon. get_string('gradecategory', 'grades'), get_string('gradenoun'), get_string('range', 'grades'), get_string('feedback', 'grades'), get_string('override', 'gradereport_singleview'), get_string('exclude', 'gradereport_singleview'), ]; } /** * Format each row of the table. * * @param grade_item $item * @return array */ public function format_line($item): array { global $OUTPUT; $grade = $this->fetch_grade_or_default($item, $this->item->id); $gradestatus = ''; // Show hidden icon if the grade is hidden and the user has permission to view hidden grades. $showhiddenicon = ($grade->is_hidden() || $item->is_hidden()) && has_capability('moodle/grade:viewhidden', context_course::instance($item->courseid)); $context = [ 'hidden' => $showhiddenicon, 'locked' => $grade->is_locked(), ]; if (in_array(true, $context)) { $context['classes'] = 'gradestatus'; $gradestatus = $OUTPUT->render_from_template('core_grades/status_icons', $context); } // Create a fake gradetreeitem so we can call get_element_header(). // The type logic below is from grade_category->_get_children_recursion(). $gradetreeitem = []; $type = in_array($item->itemtype, ['course', 'category']) ? "{$item->itemtype}item" : 'item'; $gradetreeitem['type'] = $type; $gradetreeitem['object'] = $item; $gradetreeitem['userid'] = $this->item->id; $itemname = \grade_helper::get_element_header($gradetreeitem, true, false, false, false, true); $grade->label = $item->get_name(); $formatteddefinition = $this->format_definition($grade); $itemicon = html_writer::div($this->format_icon($item), 'me-1'); $itemtype = \html_writer::span(\grade_helper::get_element_type_string($gradetreeitem), 'd-block text-uppercase small dimmed_text'); $itemtitle = html_writer::div($itemname, 'rowtitle'); $itemcontent = html_writer::div($itemtype . $itemtitle); $line = [ html_writer::div($itemicon . $itemcontent, "{$type} d-flex align-items-center"), $this->get_item_action_menu($item), $this->category($item), $formatteddefinition['finalgrade'] . $gradestatus, new range($item), $formatteddefinition['feedback'], $formatteddefinition['override'], $formatteddefinition['exclude'], ]; $lineclasses = [ 'gradeitem', 'action', 'category', 'grade', 'range', ]; $outputline = []; $i = 0; foreach ($line as $key => $value) { $cell = new \html_table_cell($value); if ($isheader = $i == 0) { $cell->header = $isheader; $cell->scope = "row"; } if (array_key_exists($key, $lineclasses)) { $cell->attributes['class'] = $lineclasses[$key]; } $outputline[] = $cell; $i++; } return $outputline; } /** * Helper to get the icon for an item. * * @param grade_item $item * @return string */ private function format_icon($item): string { $element = ['type' => 'item', 'object' => $item]; return \grade_helper::get_element_icon($element); } /** * Return the action menu HTML for the grade item. * * @param grade_item $item * @return mixed */ private function get_item_action_menu(grade_item $item) { global $OUTPUT; $menuitems = []; $url = new moodle_url($this->format_link('grade', $item->id)); $title = get_string('showallgrades', 'core_grades'); $menuitems[] = new \action_menu_link_secondary($url, null, $title); $menu = new \action_menu($menuitems); $label = get_string('actions'); $icon = $OUTPUT->pix_icon('i/moremenu', '') . \core\output\html_writer::span($label, 'visually-hidden d-inline-block'); $extraclasses = 'btn btn-icon d-flex'; $menu->set_menu_trigger($icon, $extraclasses); $menu->triggerattributes['title'] = $label; $menu->set_menu_left(); $menu->set_boundary('window'); return $OUTPUT->render($menu); } /** * Helper to get the category for an item. * * @param grade_item $item * @return string */ private function category(grade_item $item): string { global $DB; if (empty($item->categoryid)) { if ($item->itemtype == 'course') { return $this->course->fullname; } $params = ['id' => $item->iteminstance]; $elem = $DB->get_record('grade_categories', $params); return $elem->fullname; } if (!isset($this->categories[$item->categoryid])) { $category = $item->get_parent_category(); $this->categories[$category->id] = $category; } return $this->categories[$item->categoryid]->get_name(); } /** * Get the heading for the page. * * @return string */ public function heading(): string { global $PAGE; $headinglangstring = $PAGE->user_is_editing() ? 'gradeuseredit' : 'gradeuser'; return get_string($headinglangstring, 'gradereport_singleview', fullname($this->item)); } /** * Get the summary for this table. * * @return string */ public function summary(): string { return get_string('summaryuser', 'gradereport_singleview'); } /** * Default pager * * @return string */ public function pager(): string { global $OUTPUT; if (!$this->supports_paging()) { return ''; } return $OUTPUT->paging_bar( count($this->items), $this->page, $this->perpage, new moodle_url('/grade/report/singleview/index.php', [ 'perpage' => $this->perpage, 'id' => $this->courseid, 'group' => $this->groupid, 'itemid' => $this->itemid, 'item' => 'user' ]) ); } /** * Does this page require paging? * * @return bool */ public function supports_paging(): bool { return $this->requirespaging; } /** * Process the data from the form. * * @param array $data * @return stdClass of warnings */ public function process($data): stdClass { $bulk = new bulk_insert($this->item); // Bulk insert messages the data to be passed in // ie: for all grades of empty grades apply the specified value. if ($bulk->is_applied($data)) { $filter = $bulk->get_type($data); $insertvalue = $bulk->get_insert_value($data); $userid = $this->item->id; foreach ($this->items as $gradeitemid => $gradeitem) { $null = $gradeitem->gradetype == GRADE_TYPE_SCALE ? -1 : ''; $field = "finalgrade_{$gradeitem->id}_{$this->itemid}"; if (isset($data->$field)) { continue; } $oldfinalgradefield = "oldfinalgrade_{$gradeitem->id}_{$this->itemid}"; // Bulk grade changes for all grades need to be processed and shouldn't be skipped if they had a previous grade. if ($gradeitem->is_course_item() || ($filter != 'all' && !empty($data->$oldfinalgradefield))) { if ($gradeitem->is_course_item()) { // The course total should not be overridden. unset($data->$field); unset($data->oldfinalgradefield); $oldoverride = "oldoverride_{$gradeitem->id}_{$this->itemid}"; unset($data->$oldoverride); $oldfeedback = "oldfeedback_{$gradeitem->id}_{$this->itemid}"; unset($data->$oldfeedback); } continue; } $grade = grade_grade::fetch([ 'itemid' => $gradeitemid, 'userid' => $userid ]); $data->$field = empty($grade) ? $null : $grade->finalgrade; $data->{"old$field"} = $data->$field; } foreach ($data as $varname => $value) { if (preg_match('/^oldoverride_(\d+)_(\d+)/', $varname, $matches)) { // If we've selected overriding all grades. if ($filter == 'all') { $override = "override_{$matches[1]}_{$matches[2]}"; $data->$override = '1'; } } if (!preg_match('/^finalgrade_(\d+)_(\d+)/', $varname, $matches)) { continue; } $gradeitem = grade_item::fetch([ 'courseid' => $this->courseid, 'id' => $matches[1], ]); $isscale = ($gradeitem->gradetype == GRADE_TYPE_SCALE); $empties = (trim($value ?? '') === '' || ($isscale && $value == -1)); if ($filter == 'all' || $empties) { $data->$varname = ($isscale && empty($insertvalue)) ? -1 : $insertvalue; } } } return parent::process($data); } } classes/local/ui/be_disabled.php000064400000002313152266565660012660 0ustar00. /** * be_disabled interface. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; defined('MOODLE_INTERNAL') || die; /** * Simple interface implemented to add behaviour that an element can be checked to see * if it should be disabled. */ interface be_disabled { /** * Am I disabled ? * * @return bool */ public function is_disabled(): bool; } classes/local/ui/exclude.php000064400000007631152266565660012104 0ustar00. /** * Class that represents the exclude checkbox on a grade_grade. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; defined('MOODLE_INTERNAL') || die; use context_course; use grade_grade; /** * Class that represents the exclude checkbox on a grade_grade. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class exclude extends grade_attribute_format implements be_checked, be_disabled, be_readonly { /** * The name of the input * @var string $name */ public $name = 'exclude'; /** * Is the checkbox disabled? * @var bool $disabled */ public $disabled = false; /** * Is it checked? * * @return bool */ public function is_checked(): bool { return $this->grade->is_excluded(); } /** * Is it disabled? * * @return bool */ public function is_disabled(): bool { return $this->disabled; } /** * Return true if this is read-only. * * @return bool */ public function is_readonly(): bool { global $USER; return empty($USER->editing); } /** * Generate the element used to render the UI * * @return element */ public function determine_format(): element { if (($this->grade->is_hidden() || $this->grade->grade_item->is_hidden()) && !has_capability('moodle/grade:viewhidden', context_course::instance($this->grade->grade_item->courseid))) { return new empty_element(); } return new checkbox_attribute( $this->get_name(), $this->get_label(), $this->is_checked(), $this->is_disabled(), $this->is_readonly() ); } /** * Get the label for the form input * * @return string */ public function get_label(): string { if (!isset($this->grade->label)) { $this->grade->label = ''; } return $this->grade->label; } /** * Set the value that was changed in the form. * * @param string $value The value to set. * @return mixed string|bool An error message or false. */ public function set($value) { if (empty($this->grade->id)) { if (empty($value)) { return false; } $gradeitem = $this->grade->grade_item; // Fill in arbitrary grade to be excluded. $gradeitem->update_final_grade( $this->grade->userid, null, 'singleview', null, FORMAT_MOODLE ); $gradeparams = [ 'userid' => $this->grade->userid, 'itemid' => $this->grade->itemid ]; $this->grade = grade_grade::fetch($gradeparams); $this->grade->grade_item = $gradeitem; } $state = !($value == 0); $this->grade->set_excluded($state); $this->grade->grade_item->get_parent_category()->force_regrading(); return false; } } classes/local/ui/dropdown_attribute.php000064400000007000152266565660014360 0ustar00. /** * Drop down list (select list) element * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; defined('MOODLE_INTERNAL') || die; /** * Drop down list (select list) element * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class dropdown_attribute extends element { /** * Who is selected? * @var string $selected */ private $selected; /** * List of options * @var array $options */ private $options; /** * Is this input disabled. * @var bool $isdisabled */ private $isdisabled; /** @var bool If this is a read-only input. */ private bool $isreadonly; /** * Constructor * * @param string $name The first bit of the name of this input. * @param array $options The options list for this select. * @param string $label The form label for this input. * @param string $selected The name of the selected item in this input. * @param bool $isdisabled Are we disabled? * @param bool $isreadonly If this is a read-only input. */ public function __construct( string $name, array $options, string $label, string $selected = '', bool $isdisabled = false, bool $isreadonly = false ) { $this->selected = $selected; $this->options = $options; $this->isdisabled = $isdisabled; $this->isreadonly = $isreadonly; parent::__construct($name, $selected, $label); } /** * Nasty function spreading dropdown logic around. * * @return bool */ public function is_dropdown(): bool { return true; } /** * Render this element as html. * * @return string */ public function html(): string { global $OUTPUT; $options = $this->options; $selected = $this->selected; $context = [ 'name' => $this->name, 'value' => $this->selected, 'text' => $options[$selected], 'disabled' => !empty($this->isdisabled), 'readonly' => $this->isreadonly, 'options' => array_map(function($option) use ($options, $selected) { return [ 'name' => $options[$option], 'value' => $option, 'selected' => $selected == $option ]; }, array_keys($options)), 'label' => get_string('gradefor', 'gradereport_singleview', $this->label), ]; return $OUTPUT->render_from_template('gradereport_singleview/dropdown_attribute', $context); } } classes/local/ui/unique_name.php000064400000002472152266565660012757 0ustar00. /** * A form element with a name field. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; defined('MOODLE_INTERNAL') || die; /** * A form element with a name field. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ interface unique_name { /** * Get the name for this form element * @return string */ public function get_name(): string; } classes/local/ui/grade_attribute_format.php000064400000004533152266565660015166 0ustar00. /** * Abstract class for a form element representing something about a grade_grade. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; use grade_grade; defined('MOODLE_INTERNAL') || die; /** * Abstract class for a form element representing something about a grade_grade. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class grade_attribute_format extends attribute_format implements unique_name { /** * The first part of the name attribute of the form input * @var string $name */ public $name; /** * The label of the input * @var null|string $label */ public $label; /** * The grade_grade of the input * @var grade_grade $grade */ public $grade; /** * Constructor * * @param grade_grade $grade The grade_grade we are editing. */ public function __construct($grade = 0) { $this->grade = $grade; } /** * Get a unique name for this form input * * @return string The form input name attribute. */ public function get_name(): string { return "{$this->name}_{$this->grade->itemid}_{$this->grade->userid}"; } /** * Should be overridden by the child class to save the value returned in this input. * * @param string $value The value from the form. * @return string Any error message */ abstract public function set($value); } classes/local/ui/empty_element.php000064400000003277152266565660013324 0ustar00. /** * Element that just generates some text. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; defined('MOODLE_INTERNAL') || die; /** * Element that just generates some text. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class empty_element extends element { /** @var string message text. */ private $text; /** * Constructor * * @param string|null $msg The text */ public function __construct(?string $msg = null) { if (is_null($msg)) { $this->text = ' '; } else { $this->text = $msg; } } /** * Generate the html (simple case) * * @return string HTML */ public function html(): string { return $this->text; } } classes/local/ui/unique_value.php000064400000002511152266565660013145 0ustar00. /** * The gradebook simple view - UI factory * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; defined('MOODLE_INTERNAL') || die; /** * Simple interface for an item with a value. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ interface unique_value { /** * Get the value for this item. * @return null|string */ public function get_value(): ?string; } classes/local/ui/checkbox_attribute.php000064400000007237152266565660014326 0ustar00. /** * A checkbox ui element. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; use html_writer; defined('MOODLE_INTERNAL') || die; /** * A checkbox ui element. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class checkbox_attribute extends element { /** @var bool $ischecked Is it checked? */ private $ischecked; /** @var bool If this is a read-only input. */ private bool $isreadonly; /** @var int Is this element locked either 0 or a time. */ protected int $locked; /** * Constructor * * @param string $name The element name * @param string $label The label for the form element * @param bool $ischecked Is this thing on? * @param int $locked Is this element locked either 0 or a time. * @param bool $isreadonly If this is a read-only input. */ public function __construct(string $name, string $label, bool $ischecked = false, int $locked=0, bool $isreadonly = false) { $this->ischecked = $ischecked; $this->locked = $locked; $this->isreadonly = $isreadonly; parent::__construct($name, 1, $label); } /** * Nasty function allowing checkbox logic to escape the class. * @return bool */ public function is_checkbox(): bool { return true; } /** * Generate the HTML * * @return string */ public function html(): string { global $OUTPUT; $attributes = [ 'type' => 'checkbox', 'name' => $this->name, 'value' => 1, 'id' => $this->name ]; // UCSB fixed user should not be able to override locked grade. if ( $this->locked) { $attributes['disabled'] = 'DISABLED'; } $hidden = [ 'type' => 'hidden', 'name' => 'old' . $this->name ]; if ($this->ischecked) { $attributes['checked'] = 'CHECKED'; $hidden['value'] = 1; } $type = "override"; if (preg_match("/^exclude/", $this->name)) { $type = "exclude"; } if (!$this->isreadonly) { return ( html_writer::tag('label', get_string($type . 'for', 'gradereport_singleview', $this->label), ['for' => $this->name, 'class' => 'accesshide']) . html_writer::empty_tag('input', $attributes) . html_writer::empty_tag('input', $hidden) ); } else if ($this->ischecked) { return $OUTPUT->pix_icon('i/checked', get_string('selected', 'core_form'), 'moodle', ['class' => 'overrideexcludecheck']); } else { return ''; } } } classes/local/ui/element.php000064400000004776152266565660012113 0ustar00. /** * UI Element for an excluded grade_grade. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; defined('MOODLE_INTERNAL') || die; /** * UI Element for an excluded grade_grade. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class element { /** * The first bit of the name for this input. * @var string $name */ public $name; /** * The value for this input. * @var string $value */ public $value; /** * The form label for this input. * @var string $label */ public $label; /** * Constructor * * @param string $name The first bit of the name for this input * @param string $value The value for this input * @param string $label The label for this form field */ public function __construct(string $name, string $value, string $label) { $this->name = $name; $this->value = $value; $this->label = $label; } /** * Nasty function used for spreading checkbox logic all around * @return bool */ public function is_checkbox(): bool { return false; } /** * Nasty function used for spreading textbox logic all around * @return bool */ public function is_textbox(): bool { return false; } /** * Nasty function used for spreading dropdown logic all around * @return bool */ public function is_dropdown(): bool { return false; } /** * Return the HTML * @return string */ abstract public function html(): string; } classes/local/ui/finalgrade.php000064400000015701152266565660012544 0ustar00. /** * UI element representing the finalgrade column. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; defined('MOODLE_INTERNAL') || die; use context_course; use stdClass; /** * UI element representing the finalgrade column. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class finalgrade extends grade_attribute_format implements unique_value, be_disabled, be_readonly { /** * Name of this input * @var string $name */ public $name = 'finalgrade'; /** * Get the value for this input. * * @return null|string The value based on the grade_grade. */ public function get_value(): ?string { $this->label = $this->grade->grade_item->itemname; $val = $this->grade->finalgrade; if ($this->grade->grade_item->scaleid) { return $val ? (int)$val : -1; } else { return $val ? format_float($val, $this->grade->grade_item->get_decimals()) : ''; } } /** * Get the label for this input. * * @return string The label for this form input. */ public function get_label(): string { if (!isset($this->grade->label)) { $this->grade->label = ''; } return $this->grade->label; } /** * Is this input field disabled. * * @return bool Set disabled on this input or not. */ public function is_disabled(): bool { $locked = 0; $gradeitemlocked = 0; $overridden = 0; // Disable editing if grade item or grade score is locked // if any of these items are set, then we will disable editing // at some point, we might want to show the reason for the lock // this code could be simplified, but its more readable for steve's little mind. if (!empty($this->grade->locked)) { $locked = 1; } if (!empty($this->grade->grade_item->locked)) { $gradeitemlocked = 1; } if ($this->grade->grade_item->is_overridable_item() and !$this->grade->is_overridden()) { $overridden = 1; } return ($locked || $gradeitemlocked || $overridden); } /** * Return true if this is read-only. * * @return bool */ public function is_readonly(): bool { global $USER; return empty($USER->editing); } /** * Create the element for this column. * * @return element */ public function determine_format(): element { global $CFG; if (($this->grade->is_hidden() || $this->grade->grade_item->is_hidden()) && !has_capability('moodle/grade:viewhidden', context_course::instance($this->grade->grade_item->courseid))) { return new empty_element(); } if ($this->grade->grade_item->load_scale()) { $scale = $this->grade->grade_item->load_scale(); $options = [-1 => get_string('nograde')]; foreach ($scale->scale_items as $i => $name) { $options[$i + 1] = $name; } return new dropdown_attribute( $this->get_name(), $options, $this->get_label(), $this->get_value(), $this->is_disabled(), $this->is_readonly() ); } else { $textattribute = new text_attribute( $this->get_name(), $this->get_value(), $this->get_label(), $this->is_disabled(), $this->is_readonly() ); // Set min/max attributes, if applicable. $textattribute->set_type('number'); $gradeitem = $this->grade->grade_item; $decimals = $gradeitem->get_decimals(); // Min attribute. $minvalue = null; if (isset($gradeitem->grademin)) { $minvalue = format_float($gradeitem->grademin, $decimals); } $textattribute->set_min($minvalue); // Max attribute. $maxvalue = null; if (isset($gradeitem->grademax) && empty($CFG->unlimitedgrades)) { $maxvalue = format_float($gradeitem->grademax, $decimals); } $textattribute->set_max($maxvalue); return $textattribute; } } /** * Save the altered value for this field. * * @param string $value The new value. * @return string Any error string */ public function set($value) { $userid = $this->grade->userid; $gradeitem = $this->grade->grade_item; $feedback = false; $feedbackformat = false; if ($gradeitem->gradetype == GRADE_TYPE_SCALE) { $value = (int)unformat_float($value); if ($value == -1) { $finalgrade = null; } else { $finalgrade = $value; } } else { $finalgrade = unformat_float($value); } $errorstr = ''; if ($finalgrade) { $bounded = $gradeitem->bounded_grade($finalgrade); if ($bounded > $finalgrade) { $errorstr = 'lessthanmin'; } else if ($bounded < $finalgrade) { $errorstr = 'morethanmax'; } } if ($errorstr) { $user = get_complete_user_data('id', $userid); $gradestr = new stdClass; if (has_capability('moodle/site:viewfullnames', \context_course::instance($gradeitem->courseid))) { $gradestr->username = fullname($user, true); } else { $gradestr->username = fullname($user); } $gradestr->itemname = $this->grade->grade_item->get_name(); $errorstr = get_string($errorstr, 'grades', $gradestr); return $errorstr; } // Only update grades if there are no errors. $gradeitem->update_final_grade($userid, $finalgrade, 'singleview', $feedback, FORMAT_MOODLE, null, null, true); return ''; } } classes/local/ui/range.php000064400000003571152266565660011546 0ustar00. /** * UI element that generates a min/max range (text only). * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; use grade_item; defined('MOODLE_INTERNAL') || die; /** * UI element that generates a grade_item min/max range (text only). * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class range extends attribute_format { /** @var grade_item To store the grade item. */ private grade_item $item; /** * Constructor * @param grade_item $item The grade item */ public function __construct(grade_item $item) { $this->item = $item; } /** * Build this UI element. * * @return element */ public function determine_format(): element { $decimals = $this->item->get_decimals(); $min = format_float($this->item->grademin, $decimals); $max = format_float($this->item->grademax, $decimals); return new empty_element("$min - $max"); } } classes/local/ui/bulk_insert.php000064400000007555152266565660013001 0ustar00. /** * Checkbox element used for bulk inserting values in the gradebook. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; defined('MOODLE_INTERNAL') || die; /** * Checkbox element used for bulk inserting values in the gradebook. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class bulk_insert extends element { /** @var string $applyname To store the "apply" suffix. */ protected $applyname; /** @var string $selectname To store the "type" suffix. */ protected $selectname; /** @var string $insertname To store the "value" suffix. */ protected $insertname; /** * Constructor * * @param mixed $item The grade item or user. */ public function __construct($item) { $this->name = 'bulk_' . $item->id; $this->applyname = $this->name_for('apply'); $this->selectname = $this->name_for('type'); $this->insertname = $this->name_for('value'); } /** * Is this checkbox checked? * * @param array|object $data The form data * @return bool */ public function is_applied($data): bool { return isset($data->{$this->applyname}); } /** * Get the type of this input (user or grade) * * @param array|object $data The form data * @return string */ public function get_type($data): string { return $data->{$this->selectname}; } /** * Get the value from either the user or grade. * * @param array|object $data The form data * @return string */ public function get_insert_value($data): string { return $data->{$this->insertname}; } /** * Generate the html for this form element. * * @return string HTML */ public function html(): string { global $OUTPUT; $text = new text_attribute($this->insertname, "0", 'bulk'); $context = (object) [ 'label' => get_string('bulklegend', 'gradereport_singleview'), 'applylabel' => get_string('bulkperform', 'gradereport_singleview'), 'applyname' => $this->applyname, 'menuname' => $this->selectname, 'menulabel' => get_string('bulkappliesto', 'gradereport_singleview'), 'menuoptions' => [ ['value' => 'all', 'name' => get_string('all_grades', 'gradereport_singleview')], ['value' => 'blanks', 'name' => get_string('blanks', 'gradereport_singleview'), 'selected' => true], ], 'valuename' => $this->insertname, 'valuefield' => $text->html() ]; return $OUTPUT->render_from_template('gradereport_singleview/bulk_insert', $context); } /** * This form element has 3 elements with different suffixes. * Generate the name with the suffix. * * @param string $extend The suffix. * @return string */ private function name_for($extend) { return "{$this->name}_$extend"; } } classes/local/ui/be_readonly.php000064400000002271152266565660012731 0ustar00. /** * be_readonly interface. * * @package gradereport_singleview * @copyright 2022 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; /** * Simple interface implemented to add behaviour that an element can be checked to see * if it should be read-only. */ interface be_readonly { /** * Return true if this is read-only. * * @return bool */ public function is_readonly(): bool; } classes/local/ui/attribute_format.php000064400000003211152266565660014014 0ustar00. /** * Class that builds an element tree that can be converted to a string * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; defined('MOODLE_INTERNAL') || die; /** * Class that builds an element tree that can be converted to a string * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class attribute_format { /** * Used to convert this class to an "element" which can be converted to a string. * @return element */ abstract public function determine_format(): element; /** * Convert this to an element and then to a string * @return string */ public function __toString(): string { return $this->determine_format()->html(); } } classes/local/ui/override.php000064400000007265152266565660012275 0ustar00. /** * An override grade checkbox element * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; use context_course; defined('MOODLE_INTERNAL') || die; /** * An override grade checkbox element * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class override extends grade_attribute_format implements be_checked, be_disabled, be_readonly { /** * The name for this input * @var string $name */ public $name = 'override'; /** * Is this input checked * * @return bool */ public function is_checked(): bool { return $this->grade->is_overridden(); } /** * Is this input disabled * * @return bool */ public function is_disabled(): bool { $lockedgrade = $lockedgradeitem = 0; if (!empty($this->grade->locked)) { $lockedgrade = 1; } if (!empty($this->grade->grade_item->locked)) { $lockedgradeitem = 1; } return ($lockedgrade || $lockedgradeitem); } /** * Return true if this is read-only. * * @return bool */ public function is_readonly(): bool { global $USER; return empty($USER->editing); } /** * Get the label for this form element. * * @return string */ public function get_label(): string { if (!isset($this->grade->label)) { $this->grade->label = ''; } return $this->grade->label; } /** * Generate the element for this form input. * * @return element */ public function determine_format(): element { // If the grade is hidden and the user does not have permission to view hidden grades, // then we don't show the override checkbox. $allowhiddenoverride = (!$this->grade->is_hidden() && !$this->grade->grade_item->is_hidden()) || has_capability('moodle/grade:viewhidden', context_course::instance($this->grade->grade_item->courseid)); if (!$allowhiddenoverride || !$this->grade->grade_item->is_overridable_item()) { return new empty_element(); } return new checkbox_attribute( $this->get_name(), $this->get_label(), $this->is_checked(), $this->is_disabled(), $this->is_readonly() ); } /** * Save the modified value of this form element. * * @param string $value The new value to set * @return mixed string|false Any error message */ public function set($value) { if (empty($this->grade->id)) { return false; } $state = !($value == 0); $this->grade->set_overridden($state); $this->grade->grade_item->force_regrading(); return false; } } classes/local/ui/text_attribute.php000064400000011254152266565660013516 0ustar00. /** * UI element for a text input field. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; defined('MOODLE_INTERNAL') || die; /** * UI element for a text input field. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class text_attribute extends element { /** * Is this input disabled? * @var bool $isdisabled */ private $isdisabled; /** @var bool If this is a read-only input. */ private bool $isreadonly; /** * @var string|null The input type to pass to the template. * This defaults to text but can be overridden to number for grade inputs. */ private $type = null; /** * @var string|null The value to set for the input's `min` attribute. * This is set if a minimum grade is provided for the grade input field. */ private $min = null; /** * @var string|null The value to set for the input's `max` attribute. * This is set if a maximum grade is provided for the grade input field. */ private $max = null; /** * Constructor * * @param string $name The input name (the first bit) * @param string $value The input initial value. * @param string $label The label for this input field. * @param bool $isdisabled Is this input disabled. * @param bool $isreadonly If this is a read-only input. */ public function __construct(string $name, string $value, string $label, bool $isdisabled = false, bool $isreadonly = false) { $this->isdisabled = $isdisabled; $this->isreadonly = $isreadonly; parent::__construct($name, $value, $label); } /** * Nasty function allowing custom textbox behaviour outside the class. * @return bool Is this a textbox. */ public function is_textbox(): bool { return true; } /** * Render the html for this field. * @return string The HTML. */ public function html(): string { global $OUTPUT; $context = (object) [ 'id' => $this->name, 'name' => $this->name, 'value' => $this->value, 'disabled' => $this->isdisabled, 'readonly' => $this->isreadonly, ]; $context->label = ''; if (preg_match("/^feedback/", $this->name)) { $context->label = get_string('feedbackfor', 'gradereport_singleview', $this->label); } else if (preg_match("/^finalgrade/", $this->name)) { $context->label = get_string('gradefor', 'gradereport_singleview', $this->label); } // Set this input field with type="number" if the decimal separator for current language is set to a period. // Other decimal separators may not be recognised by browsers yet which may cause issues when entering grades. $decsep = get_string('decsep', 'core_langconfig'); $context->isnumeric = $this->type === 'number' && $decsep === '.'; if ($context->isnumeric) { $context->type = $this->type; $context->min = $this->min; $context->max = $this->max; } return $OUTPUT->render_from_template('gradereport_singleview/text_attribute', $context); } /** * Input type setter. * * @param string|null $type * @return void */ public function set_type(?string $type): void { $this->type = $type; } /** * Min attribute setter. * * @param string|null $min * @return void */ public function set_min(?string $min): void { $this->min = $min; } /** * Max attribute setter. * * @param string|null $max * @return void */ public function set_max(?string $max): void { $this->max = $max; } } classes/local/ui/be_checked.php000064400000002435152266565660012504 0ustar00. /** * Is this thing checked? * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; defined('MOODLE_INTERNAL') || die; /** * Is this thing checked? * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ interface be_checked { /** * Return true if this is checked. * @return bool */ public function is_checked(): bool; } classes/local/ui/feedback.php000064400000010263152266565660012172 0ustar00. /** * Class used to render a feedback input box. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\local\ui; use context_course; defined('MOODLE_INTERNAL') || die; /** * Class used to render a feedback input box. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class feedback extends grade_attribute_format implements unique_value, be_disabled, be_readonly { /** * Name of this input * @var string $name */ public $name = 'feedback'; /** * Get the value for this input. * * @return string The value */ public function get_value(): ?string { return $this->grade->feedback ? $this->grade->feedback : ''; } /** * Get the string to use in the label for this input. * * @return string The label text */ public function get_label(): string { if (!isset($this->grade->label)) { $this->grade->label = ''; } return $this->grade->label; } /** * Determine if this input should be disabled based on the other settings. * * @return boolean Should this input be disabled when the page loads. */ public function is_disabled(): bool { $locked = 0; $gradeitemlocked = 0; $overridden = 0; /* Disable editing if grade item or grade score is locked * if any of these items are set, then we will disable editing * at some point, we might want to show the reason for the lock * this code could be simplified, but its more readable for steve's little mind */ if (!empty($this->grade->locked)) { $locked = 1; } if (!empty($this->grade->grade_item->locked)) { $gradeitemlocked = 1; } if ($this->grade->grade_item->is_overridable_item() and !$this->grade->is_overridden()) { $overridden = 1; } return ($locked || $gradeitemlocked || $overridden); } /** * Return true if this is read-only. * * @return bool */ public function is_readonly(): bool { global $USER; return empty($USER->editing); } /** * Create a text_attribute for this ui element. * * @return element */ public function determine_format(): element { if (($this->grade->is_hidden() || $this->grade->grade_item->is_hidden()) && !has_capability('moodle/grade:viewhidden', context_course::instance($this->grade->grade_item->courseid))) { return new empty_element(); } return new text_attribute( $this->get_name(), $this->get_value(), $this->get_label(), $this->is_disabled(), $this->is_readonly() ); } /** * Update the value for this input. * * @param string $value The new feedback value. * @return null|string Any error message */ public function set($value) { $finalgrade = false; $trimmed = trim($value); if (empty($trimmed)) { $feedback = null; } else { $feedback = $value; } $this->grade->grade_item->update_final_grade( $this->grade->userid, $finalgrade, 'singleview', $feedback, FORMAT_MOODLE ); } } classes/output/action_bar.php000064400000007114152266565660012401 0ustar00. declare(strict_types=1); namespace gradereport_singleview\output; use moodle_url; use renderer_base; use gradereport_singleview\report\singleview; /** * Renderable class for the action bar elements in the single view report page. * * @package gradereport_singleview * @copyright 2022 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class action_bar extends \core_grades\output\action_bar { /** @var singleview $report The single view report class. */ protected singleview $report; /** @var string $itemtype The single view item type. */ protected string $itemtype; /** * The class constructor. * * @param \context $context The context object. * @param singleview $report The single view report class. * @param string $itemtype The single view item type. */ public function __construct(\context $context, singleview $report, string $itemtype) { parent::__construct($context); $this->report = $report; $this->itemtype = $itemtype; } /** * Returns the template for the action bar. * * @return string */ public function get_template(): string { return 'gradereport_singleview/action_bar'; } /** * Export the data for the mustache template. * * @param \renderer_base $output renderer to be used to render the action bar elements. * @return array */ public function export_for_template(renderer_base $output) { global $USER; $courseid = $this->context->instanceid; // Get the data used to output the general navigation selector. $generalnavselector = new \core_grades\output\general_action_bar( $this->context, new moodle_url('/grade/report/singleview/index.php', ['id' => $courseid]), 'report', 'singleview' ); $data = $generalnavselector->export_for_template($output); // The data required to output the page toggle element. $data['pagetoggler'] = [ 'displaylabel' => true, 'userselectactive' => $this->itemtype === 'user', 'gradeselectactive' => $this->itemtype === 'grade', 'gradezerolink' => (new moodle_url('/grade/report/singleview/index.php', ['id' => $courseid, 'item' => 'grade_select']))->out(false), 'userzerolink' => (new moodle_url('/grade/report/singleview/index.php', ['id' => $courseid, 'item' => 'user_select']))->out(false) ]; $data['groupselector'] = $this->report->group_selector; $data['itemselector'] = $this->report->itemselector; $data['pbarurl'] = $this->report->pbarurl->out(false); if (!empty($USER->editing) && isset($this->report->screen->item)) { $data['bulkactions'] = $this->report->bulk_actions_menu($output); } return $data; } } classes/event/grade_report_viewed.php000064400000003402152266565660014075 0ustar00. /** * Single view report viewed event. * * @package gradereport_singleview * @copyright 2014 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace gradereport_singleview\event; defined('MOODLE_INTERNAL') || die(); /** * User report viewed event class. * * @package gradereport_singleview * @since Moodle 2.8 * @copyright 2014 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class grade_report_viewed extends \core\event\grade_report_viewed { /** * Returns localised general event name. * * @return string */ public static function get_name() { return get_string('eventgradereportviewed', 'gradereport_singleview'); } /** * Custom validation. * * Throw \coding_exception notice in case of any problems. */ protected function validate_data() { parent::validate_data(); if (!isset($this->relateduserid)) { throw new \coding_exception('The \'relateduserid\' value must be set.'); } } } classes/report/singleview.php000064400000015225152266565660012431 0ustar00. namespace gradereport_singleview\report; use context_course; use grade_report; use moodle_url; use renderer_base; use stdClass; defined('MOODLE_INTERNAL') || die; require_once($CFG->dirroot . '/grade/report/lib.php'); /** * This class is the main class that must be implemented by a grade report plugin. * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class singleview extends grade_report { /** @var string|null $itemselector The raw HTML of the item selector based on the selected single view item type. */ public ?string $itemselector = null; /** @var \gradereport_singleview\local\screen\screen screen type. */ public $screen; /** * Return the list of valid screens, used to validate the input. * * @return array List of screens. */ public static function valid_screens(): array { // This is a list of all the known classes representing a screen in this plugin. return ['user', 'select', 'grade', 'user_select', 'grade_select']; } /** * Process data from a form submission. Delegated to the current screen. * * @param array $data The data from the form * @return array|object List of warnings */ public function process_data($data) { if (has_capability('moodle/grade:edit', $this->context)) { return $this->screen->process($data); } } /** * Unused - abstract function declared in the parent class. * * @param string $target * @param string $action */ public function process_action($target, $action) { } /** * Constructor for this report. Creates the appropriate screen class based on itemtype. * * @param int $courseid The course id. * @param object $gpr grade plugin return tracking object * @param context_course $context * @param string $itemtype Should be user, select or grade * @param int|null $itemid The id of the user or grade item * @param string|null $unused Used to be group id but that was removed and this is now unused. */ public function __construct( int $courseid, object $gpr, context_course $context, string $itemtype, ?int $itemid, ?string $unused = null ) { parent::__construct($courseid, $gpr, $context); $base = '/grade/report/singleview/index.php'; $idparams = ['id' => $courseid]; $this->baseurl = new moodle_url($base, $idparams); $this->pbarurl = new moodle_url($base, $idparams + [ 'item' => $itemtype, 'itemid' => $itemid ]); // The setup_group method is used to validate group mode and permissions and define the currentgroup value. $this->setup_groups(); if (($itemtype !== 'grade') && ($itemtype !== 'user')) { $itemid = null; } $this->setup_item_selector($itemtype, $itemid); $screenclass = "\\gradereport_singleview\\local\\screen\\{$itemtype}"; $this->screen = new $screenclass($courseid, $itemid, $this->currentgroup); // Load custom or predifined js. $this->screen->js(); } /** * Build the html for the screen. * @return string HTML to display */ public function output(): string { global $OUTPUT; return $OUTPUT->container($this->screen->html()); } protected function setup_groups() { parent::setup_groups(); if ($this->groupmode) { $this->group_selector = static::groups_course_menu($this->course); } } /** * Ideally we should move this function to the base class and call it from the setup_groups in the base class, * so all reports would automatically use it. * * @param stdClass $course * @return string */ protected static function groups_course_menu(stdClass $course) { global $PAGE; return $PAGE->get_renderer('core', 'course')->render(new \core_course\output\actionbar\group_selector($PAGE->context)); } /** * Function used to set the appropriate item selector (raw HTML) based on the selected single view item type. * * @param string $itemtype The single view item type. * @param int|null $itemid The item ID. */ protected function setup_item_selector(string $itemtype, ?int $itemid) { global $PAGE; $renderer = $PAGE->get_renderer('gradereport_singleview'); if ($itemtype === 'user' || $itemtype === 'user_select' ) { $this->itemselector = $renderer->users_selector($this->course, $itemid, $this->currentgroup); } else if ($itemtype === 'grade' || $itemtype === 'grade_select' ) { $this->itemselector = $renderer->grade_items_selector($this->course, $itemid); } } /** * Adds bulk actions menu. * * @param renderer_base $output * @return string HTML to display */ public function bulk_actions_menu(renderer_base $output): string { $options = [ 'overrideallgrades' => get_string('overrideallgrades', 'gradereport_singleview'), 'overridenonegrades' => get_string('overridenonegrades', 'gradereport_singleview'), 'excludeallgrades' => get_string('excludeallgrades', 'gradereport_singleview'), 'excludenonegrades' => get_string('excludenonegrades', 'gradereport_singleview'), 'bulklegend' => get_string('bulklegend', 'gradereport_singleview') ]; $menu = new \action_menu(); $menu->set_menu_trigger(get_string('actions'), 'text-dark'); foreach ($options as $type => $option) { $action = new \action_menu_link_secondary(new \moodle_url('#'), null, $option, ['data-action' => $type, 'data-role' => 'bulkaction']); $menu->add($action); } $menu->attributes['class'] .= ' float-start my-auto'; return $output->render($menu); } } templates/button.mustache000064400000001641152266565660011635 0ustar00{{! 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 . }} {{! @template gradereport_singleview/button Button. Example context (json): { "type": "submit", "value": "Save" } }} templates/perpage.mustache000064400000003526152266565660011751 0ustar00{{! 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 . }} {{! @template gradereport_grader/perpage The students per page dropdown element. Context variables required for this template: * baseurl - The base URL for the perpage action. * options - Choices Example context (json): { "baseurl": "http://example.com/grade/report/grader/index.php?id=2&report=grader", "options": [ {"name": "20", "value": "20"}, {"name": "100", "value": "100", "selected": true}, {"name": "All", "value": "0"} ] } }}
{{#pagingbar}}
{{{pagingbar}}}
{{/pagingbar}} {{#js}} document.getElementById('{{uniqid}}').addEventListener('change', function(e) { var url = new URL('{{{baseurl}}}'); url.searchParams.set('perpage', e.target.value); window.location.href = url; }); {{/js}} templates/text_attribute.mustache000064400000003027152266565660013371 0ustar00{{! 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 . }} {{! @template gradereport_singleview/text_attribute Text attribute. Example context (json): { "name": "Awesome-report", "label": "Text label", "value": "Text information", "disabled": false, "type": "number", "isnumeric": true, "min": "0", "max": "100" } }} {{#readonly}} {{value}} {{/readonly}} {{^readonly}} {{#label}}{{/label}} {{/readonly}} templates/bulkinsert.mustache000064400000004610152266565660012503 0ustar00{{! 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 . }} {{! @template gradereport_singleview/bulkinsert Template for the bulk insert grades modal. Example context (json): { "name": "bulksinsertmodal", "id": "bulksinsertmodal" } }}
{{#str}}unsavedataalert, gradereport_singleview{{/str}} {{#str}}unsaveddatawarning, gradereport_singleview{{/str}}
{{#str}}bulkchoice, gradereport_singleview{{/str}}

{{#str}}bulkinsertvalue, gradereport_singleview{{/str}}

templates/bulk_insert.mustache000064400000003522152266565660012643 0ustar00{{! 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 . }} {{! @template gradereport_singleview/bulk_insert Bulk insert attribute. Example context (json): { "applyname": "Apply-name", "applylabel": "Perform bulk insert", "label": "Bulk insert", "menuname": "Menu-name", "menulabel": "For", "menuoptions": "JSON object", "value": "all", "selected": "", "name": "All grades", "valuename": "Value-name", "valuelabel": "Insert value", "valuefield": "PARSED HTML" } }}
{{label}} {{{valuefield}}}
templates/zero_state.mustache000064400000002743152266565660012505 0ustar00{{! 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 . }} {{! @template gradereport_singleview/zero_state The zero state of the user report that contains the image and trigger for the search widget. Example context (json): { "imglink": "http://foo.bar/gradereport/?userid=25", "userzerolink": "http://foo.bar/gradereport/?userid=25", "gradezerolink": "http://foo.bar/gradereport/?userid=25" } }}

{{#str}}pluginname, gradereport_singleview{{/str}}

{{#str}}viewsingleuserorgradeitem, gradereport_singleview{{/str}}

{{>gradereport_singleview/page_toggler}}
templates/action_bar.mustache000064400000010562152266565660012425 0ustar00{{! 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 . }} {{! @template gradereport_singleview/action_bar Context variables required for this template: * generalnavselector - The data object containing the required properties to render the general navigation selector. * groupselector - (optional) HTML that outputs the group selector * itemselector - (optional) HTML that outputs the user or grade item selector * pagetoggler - (optional) HTML that outputs the user/grade item view toggler * bulkactions - (optional) HTML that outputs the bulk actione menu Example context (json): { "generalnavselector": { "name": "Gradebook tertiary navigation selector", "value": "opt2", "baseid": "select-menu56789", "selectedoption": "Gradebook setup", "options": [ { "selected": false, "isgroup": { "name": "View", "id": "select-menu-group1", "options": [ { "name": "Grader report", "value": "opt1", "id": "select-menu-option1", "selected": false } ] } }, { "selected": false, "isgroup": { "name": "Setup", "id": "select-menu-group2", "options": [ { "name": "Gradebook setup", "value": "opt2", "id": "select-menu-option2", "selected": true } ] } } ] }, "groupselector": "
", "itemselector": "
", "pagetoggler": "
", "bulkactions": "
" } }}
{{#generalnavselector}} {{/generalnavselector}} {{#groupselector}} {{/groupselector}} {{#itemselector}} {{/itemselector}} {{#pagetoggler}}
{{#bulkactions}} {{#js}} require(['gradereport_singleview/bulkactions'], function(bulkactions) { bulkactions.init(); }); {{/js}} {{/bulkactions}}
{{/pagetoggler}}
templates/gradesearch_body.mustache000064400000002744152266565660013614 0ustar00{{! 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 . }} {{! @template gradereport_singleview/gradesearch_body The body of the grade item widget. Example context (json): {} }} {{< core/search_input_auto }} {{$label}}{{#str}} searchgrades, gradereport_singleview {{/str}}{{/label}} {{$placeholder}}{{#str}} searchgrades, gradereport_singleview {{/str}}{{/placeholder}} {{$additionalattributes}} role="combobox" aria-expanded="true" aria-controls="listbox-{{uniqid}}" aria-autocomplete="list" data-input-element="input-{{uniqid}}" {{/additionalattributes}} {{/ core/search_input_auto }}
templates/zero_state_grade.mustache000064400000002423152266565660013642 0ustar00{{! 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 . }} {{! @template gradereport_singleview/zero_state_grade The zero state of the user report that contains the image and trigger for the search widget. Example context (json): { "imglink": "http://foo.bar/gradereport/?userid=25" } }}

{{#str}}viewsinglegradeitem, gradereport_singleview{{/str}}

{{#str}}singleviewdescription, gradereport_singleview{{/str}}

templates/grade_item_selector.mustache000064400000004217152266565660014324 0ustar00{{! 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 . }} {{! @template gradereport_singleview/grade_item_selector The grade item selector trigger element. Context variables required for this template: * itemid - The value of the grade item selector element (id of the preselected grade item) * instance - The instance ID of the combo box. * courseid - The course ID. * selectedoption - (optional) Object containing information about the selected option. * text - The text of the selected option. Example context (json): { "itemid": "21", "instance": "25", "courseid": "2", "selectedoption": { "text": "Grade item 1" } } }}
{{#selectedoption}}
{{text}}
{{/selectedoption}} {{^selectedoption}} {{/selectedoption}}
templates/dropdown_attribute.mustache000064400000002570152266565660014243 0ustar00{{! 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 . }} {{! @template gradereport_singleview/dropdown_attribute Dropdown attribute. Example context (json): { "name": "Awesome-test", "disabled": "true", "options": "true", "value": "1", "selected": "true" } }} {{#readonly}} {{text}} {{/readonly}} {{^readonly}} {{/readonly}} templates/zero_state_user.mustache000064400000002410152266565660013532 0ustar00{{! 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 . }} {{! @template gradereport_singleview/zero_state_user The zero state of the user report that contains the image and trigger for the search widget. Example context (json): { "imglink": "http://foo.bar/gradereport/?userid=25" } }}

{{#str}}viewsingleuser, gradereport_singleview{{/str}}

{{#str}}singleviewdescription, gradereport_singleview{{/str}}

templates/page_toggler.mustache000064400000004214152266565660012760 0ustar00{{! 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 . }} {{! @template gradereport_singleview/page_toggler The small selector at the top right of the page to switch between zero states. Example context (json): { "displaylabel": true, "userzerolink": "http://foo.bar/gradereport/?userid=25", "userselectactive": "false", "gradepagepage": "http://foo.bar/gradereport/?userid=25", "gradeselectactive": "true" } }}
{{#displaylabel}}

{{#str}}viewby, gradereport_singleview{{/str}}

{{/displaylabel}}
templates/report_navigation.mustache000064400000005260152266565660014055 0ustar00{{! 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 . }} {{! @template gradereport_singleview/report_navigation The previous/next user/item navigation for the singleview report. Context variables required for this template: * previoususer - (optional) The object containing information about the previous user. * name - The name of the previous user/item. * url - The URL to the previous user/item report. * nextuser - (optional) The object containing information about the next user. * name - The name of the next user/item. * url - The URL to the next user/item report. Example context (json): { "previoususer": { "name": "John Smith", "url": "https://example.com/grade/report/singleview/index.php?id=2&itemid=3" }, "previoususer": { "name": "Jane Doe", "url": "https://example.com/grade/report/singleview/index.php?id=2&itemid=5" } } }}
{{#perpageselect}}
{{{perpageselect}}}
{{/perpageselect}}
{{#previoususer}} {{/previoususer}}
{{#nextuser}} {{/nextuser}}
templates/report.mustache000064400000003000152266565660011624 0ustar00{{! 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 . }} {{! @template gradereport_singleview/report Report table and sticky footer. Context variables required for this template: * sesskey - string - The session key. * table - string - HTML content of the grade items or users table. * stickyfooter - string - HTML content of the sticky footer. Example context (json): { "sesskey": "fakesesskey", "table": "
NameUser fullnameGradeActions
", "stickyfooter": "" } }}
{{{table}}} {{{stickyfooter}}}
index.php000064400000026063152266565660006416 0ustar00. /** * Displays the Single view * * @package gradereport_singleview * @copyright 2014 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define('NO_OUTPUT_BUFFERING', true); require_once('../../../config.php'); require_once($CFG->dirroot.'/lib/gradelib.php'); require_once($CFG->dirroot.'/grade/lib.php'); require_once($CFG->dirroot.'/grade/report/lib.php'); // This report may require a lot of memory and time on large courses. raise_memory_limit(MEMORY_HUGE); set_time_limit(120); $courseid = required_param('id', PARAM_INT); $groupid = optional_param('group', null, PARAM_INT); $reset = optional_param('reset', 0, PARAM_BOOL); // Making this work with profile reports. $userid = optional_param('userid', null, PARAM_INT); $itemid = optional_param('itemid', null, PARAM_INT); $itemtype = optional_param('item', null, PARAM_TEXT); $page = optional_param('page', 0, PARAM_INT); $perpage = optional_param('perpage', null, PARAM_INT); $edit = optional_param('edit', -1, PARAM_BOOL); // Sticky editing mode. $courseparams = ['id' => $courseid]; $PAGE->set_pagelayout('report'); $PAGE->set_other_editing_capability('moodle/grade:edit'); if (!$course = $DB->get_record('course', $courseparams)) { throw new \moodle_exception('invalidcourseid'); } require_login($course); $context = context_course::instance($course->id); // This is the normal requirements. require_capability('gradereport/singleview:view', $context); require_capability('moodle/grade:viewall', $context); require_capability('moodle/grade:edit', $context); $gpr = new grade_plugin_return([ 'type' => 'report', 'plugin' => 'singleview', 'courseid' => $courseid ]); // Last selected report session tracking. if (!isset($USER->grade_last_report)) { $USER->grade_last_report = []; } $USER->grade_last_report[$course->id] = 'singleview'; // If the item type is not explicitly defined or not valid, try to use the last viewed one (obtain in from the session) // or fallback to the user select (zero) state. if (!$itemtype || !in_array($itemtype, \gradereport_singleview\report\singleview::valid_screens())) { $itemtype = isset($SESSION->gradereport_singleview["itemtype-{$context->id}"]) ? $SESSION->gradereport_singleview["itemtype-{$context->id}"] : 'user_select'; } $currentgroup = $gpr->groupid; // To make some other functions work better later. if (!$currentgroup) { $currentgroup = null; } $lastvieweduseritemid = $SESSION->gradereport_singleview["useritem-{$context->id}"] ?? null; $lastviewedgradeitemid = $SESSION->gradereport_singleview["gradeitem-{$context->id}"] ?? null; switch ($itemtype) { case 'user_select': // If there is a stored user item (last viewed) in a session variable, bypass the user select zero state // and display this user item. Also, make sure that the stored last viewed user is part of the current // list of gradable users in this course. if ($lastvieweduseritemid && array_key_exists($lastvieweduseritemid, grade_report::get_gradable_users($courseid, $currentgroup))) { $itemtype = 'user'; $itemid = $lastvieweduseritemid; } else { $itemid = null; } break; case 'user': if (is_null($itemid)) { $itemid = $userid ?? $lastvieweduseritemid; } // If the item id (user id) cannot be defined or the user id is not part of the list of gradable users, // display the user select zero state. if (is_null($itemid) || !array_key_exists($itemid, grade_report::get_gradable_users($courseid, $currentgroup)) || $reset) { $itemtype = 'user_select'; unset($SESSION->gradereport_singleview["useritem-{$context->id}"]); } break; case 'grade_select': // If there is a stored grade item (last viewed) in a session variable, bypass the grade item select zero state // and display this grade item. if ($lastviewedgradeitemid) { $itemtype = 'grade'; $itemid = $lastviewedgradeitemid; } else { $itemid = null; } break; case 'grade': // If there is a stored grade item (last viewed) in a session variable, use it. if (is_null($itemid) && $lastviewedgradeitemid) { $itemid = $lastviewedgradeitemid; } $gtree = new grade_tree($courseid, false, false, null, !$CFG->enableoutcomes); $gradeableitems = $gtree->get_items(); // The item id (grade item id) cannot be defined, display the grade select zero state. if (is_null($itemid) || !array_key_exists($itemid, $gtree->get_items())) { $itemtype = 'grade_select'; unset($SESSION->gradereport_singleview["gradeitem-{$context->id}"]); } break; } $pageparams = [ 'id' => $courseid, 'userid' => $userid, 'itemid' => $itemid, 'item' => $itemtype, 'page' => $page, 'perpage' => $perpage, ]; if (!is_null($groupid)) { $pageparams['group'] = $groupid; } $PAGE->set_url(new moodle_url('/grade/report/singleview/index.php', $pageparams)); // Make sure we have proper final grades. $taskindicator = new \core\output\task_indicator( \core_course\task\regrade_final_grades::create($courseid), get_string('recalculatinggrades', 'grades'), get_string('recalculatinggradesadhoc', 'grades'), $PAGE->url, ); if ($taskindicator->has_task_record()) { // We need to bail out early as the report requires recalculations to be complete, so just display a basic header // with navigation, and the indicator. $actionbar = new \core_grades\output\general_action_bar( $context, new moodle_url('/grade/report/singleview/index.php', ['id' => $courseid]), 'report', 'singleview' ); print_grade_page_head($course->id, 'report', 'singleview', actionbar: $actionbar); echo $OUTPUT->render($taskindicator); echo $OUTPUT->footer(); exit; } $report = new gradereport_singleview\report\singleview($courseid, $gpr, $context, $itemtype, $itemid); // Build editing on/off button for themes that need it. $button = ''; if ($PAGE->user_allowed_editing() && !$PAGE->theme->haseditswitch) { if ($edit != - 1) { $USER->editing = $edit; } // Page params for the turn editing on button. $options = $gpr->get_options(); $button = $OUTPUT->edit_button(new moodle_url($PAGE->url, $options), 'get'); } $reportname = $report->screen->heading(); $baseurl = new moodle_url('/grade/report/singleview/index.php', ['id' => $courseid, 'item' => $itemtype]); if ($itemtype == 'user' || $itemtype == 'user_select') { $PAGE->requires->js_call_amd('gradereport_singleview/user', 'init', [$baseurl->out(false)]); $actionbar = new \gradereport_singleview\output\action_bar($context, $report, 'user'); } else if ($itemtype == 'grade' || $itemtype == 'grade_select') { $PAGE->requires->js_call_amd('gradereport_singleview/grade', 'init', [$baseurl->out(false)]); $actionbar = new \gradereport_singleview\output\action_bar($context, $report, 'grade'); } else { $actionbar = new \core_grades\output\general_action_bar($context, new moodle_url('/grade/report/singleview/index.php', ['id' => $courseid]), 'report', 'singleview'); } if ($course->groupmode && $itemtype !== 'select') { $PAGE->requires->js_call_amd('core_course/actionbar/group', 'init', [$baseurl->out(false)]); } if ($itemtype == 'user') { print_grade_page_head($course->id, 'report', 'singleview', $reportname, false, $button, true, null, null, $report->screen->item, $actionbar); } else { print_grade_page_head($course->id, 'report', 'singleview', $reportname, false, $button, true, null, null, null, $actionbar); } if ($data = data_submitted()) { // Must have a sesskey for all actions. require_sesskey(); $result = $report->process_data($data); // If result is not null (because somedata was processed), warnings and success message should be displayed. if (!is_null($result)) { if (!empty($result->warnings)) { foreach ($result->warnings as $warning) { \core\notification::add($warning); } } // And notify the user of the success result. \core\notification::add( get_string('savegradessuccess', 'gradereport_singleview', count((array) $result->changecount)), \core\notification::SUCCESS ); } } // Make sure we have proper final grades. grade_regrade_final_grades_if_required($course); if ($taskindicator->has_task_record()) { echo $OUTPUT->render($taskindicator); echo $OUTPUT->footer(); exit; } // Save the screen state in a session variable as last viewed state. $SESSION->gradereport_singleview["itemtype-{$context->id}"] = $itemtype; if ($itemid) { $SESSION->gradereport_singleview["{$itemtype}item-{$context->id}"] = $itemid; } $stickyfooter = ''; if (($itemtype !== 'select') && ($itemtype !== 'grade_select') &&($itemtype !== 'user_select')) { $item = (isset($userid)) ? $userid : $itemid; $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol); $showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol); $showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $context); $gui = new graded_users_iterator($course, null, $currentgroup); $gui->require_active_enrolment($showonlyactiveenrol); $gui->init(); $userreportrenderer = $PAGE->get_renderer('gradereport_singleview'); // Add previous/next user navigation. $footercontent = $userreportrenderer->report_navigation($gpr, $courseid, $context, $report, $groupid, $itemtype, $itemid); $buttonhtml = implode(' ', $report->screen->buttons($report->screen->is_readonly())); $footercontent .= $report->screen->bulk_insert() . $buttonhtml; $stickyfooter = new core\output\sticky_footer($footercontent); $stickyfooter = $OUTPUT->render($stickyfooter); $gui->close(); } echo $OUTPUT->render_from_template('gradereport_singleview/report', [ 'table' => $report->output(), 'stickyfooter' => $stickyfooter, 'sesskey' => sesskey() ]); $event = \gradereport_singleview\event\grade_report_viewed::create( [ 'context' => $context, 'courseid' => $courseid, 'relateduserid' => $USER->id, ] ); $event->trigger(); echo $OUTPUT->footer(); amd/src/group.js000064400000005057152266565660007620 0ustar00// 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 . /** * Allow the user to search for groups within the singleview report. * * @module gradereport_singleview/group * @copyright 2023 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @deprecated since Moodle 4.5 - please use core_course/actionbar/group instead. * @todo Final deprecation in Moodle 6.0. See MDL-82116. */ import GroupSearch from 'core_group/comboboxsearch/group'; import Url from 'core/url'; export default class Group extends GroupSearch { courseID; item = null; /** * Construct the class. * * @param {string} item The page type we are currently on. */ constructor(item) { window.console.warn( 'The gradereport_singleview/group module has been deprecated since Moodle 4.5.' + ' Please use core_course/actionbar/group instead.', ); super(); this.item = item; // Define our standard lookups. this.selectors = {...this.selectors, courseid: '[data-region="courseid"]', }; const component = document.querySelector(this.componentSelector()); this.courseID = component.querySelector(this.selectors.courseid).dataset.courseid; } /** * Allow the class to be invoked via PHP. * * @param {string} item The page type we are currently on. * @returns {Group} */ static init(item) { return new Group(item); } /** * Build up the link that is dedicated to a particular result. * * @param {Number} groupID The ID of the group selected. * @returns {string|*} */ selectOneLink(groupID) { return Url.relativeUrl('/grade/report/singleview/index.php', { id: this.courseID, groupsearchvalue: this.getSearchTerm(), group: groupID, item: this.item }, false); } } amd/src/user.js000064400000006025152266565660007436 0ustar00// 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 . /** * Allow the user to search for learners within the singleview report. * * @module gradereport_singleview/user * @copyright 2023 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import UserSearch from 'core_user/comboboxsearch/user'; import {renderForPromise, replaceNodeContents} from 'core/templates'; import * as Repository from 'core_grades/searchwidget/repository'; export default class User extends UserSearch { /** * Construct the class. * * @param {string} baseUrl The base URL for the page. */ constructor(baseUrl) { super(); this.baseUrl = baseUrl; } static init(baseUrl) { return new User(baseUrl); } /** * Build the content then replace the node. */ async renderDropdown() { const {html, js} = await renderForPromise('core_user/comboboxsearch/resultset', { instance: this.instance, users: this.getMatchedResults().slice(0, 5), hasresults: this.getMatchedResults().length > 0, searchterm: this.getSearchTerm(), }); replaceNodeContents(this.getHTMLElements().searchDropdown, html, js); // Remove aria-activedescendant when the available options change. this.searchInput.removeAttribute('aria-activedescendant'); } /** * Stub out default required function unused here. * @returns {null} */ selectAllResultsLink() { return null; } /** * Build up the view all link that is dedicated to a particular result. * * @param {Number} userID The ID of the user selected. * @returns {string|*} */ selectOneLink(userID) { const url = new URL(this.baseUrl); url.searchParams.set('searchvalue', this.getSearchTerm()); url.searchParams.set('item', 'user'); url.searchParams.set('userid', userID); return url.toString(); } /** * Get the data we will be searching against in this component. * * @returns {Promise<*>} */ fetchDataset() { // Small typing checks as sometimes groups don't exist therefore the element returns a empty string. const gts = typeof (this.groupID) === "string" && this.groupID === '' ? 0 : this.groupID; return Repository.userFetch(this.courseID, gts).then((r) => r.users); } } amd/src/bulkactions.js000064400000020363152266565660010777 0ustar00// 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 . /** * Javascript module for bulk actions. * * @module gradereport_singleview/bulkactions * @copyright 2022 Ilya Tregubov * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import Pending from 'core/pending'; import CustomEvents from "core/custom_interaction_events"; import ModalSaveCancel from 'core/modal_save_cancel'; import Templates from 'core/templates'; import ModalEvents from 'core/modal_events'; import * as Str from 'core/str'; import Notification from 'core/notification'; import selectors from 'gradereport_singleview/selectors'; /** * Initialize module. */ export const init = () => { const pendingPromise = new Pending(); registerListenerEvents(); pendingPromise.resolve(); }; /** * Register bulk actions related event listeners. * * @method registerListenerEvents */ const registerListenerEvents = () => { const events = [ 'click', CustomEvents.events.activate, CustomEvents.events.keyboardActivate ]; CustomEvents.define(document, events); // Register events. events.forEach((event) => { document.addEventListener(event, async(e) => { const trigger = e.target.closest(selectors.actions.bulkaction); if (!trigger) { return; } if ((trigger.dataset.action === 'overrideallgrades') || (trigger.dataset.action === 'overridenonegrades')) { const override = document.querySelectorAll(selectors.elements.override); if (trigger.dataset.action === 'overridenonegrades') { // Alert for removing all grade overrides on page. Str.get_strings([ {key: 'removeoverride', component: 'gradereport_singleview'}, {key: 'overridenoneconfirm', component: 'gradereport_singleview'}, {key: 'removeoverridesave', component: 'gradereport_singleview'}, {key: 'cancel', component: 'moodle'}, ]) .then((strings) => { return Notification.confirm( strings[0], strings[1], strings[2], strings[3], () => { // Uncheck each override checkbox - this will make grade and feedback input fields disabled. override.forEach((el) => { if (el.checked) { el.click(); } }); }); }) .catch(Notification.exception); } else { // Check each override checkbox - this will make grade and feedback input fields enabled. override.forEach((el) => { if (!el.checked) { el.click(); } }); } } else if ((trigger.dataset.action === 'excludeallgrades') || (trigger.dataset.action === 'excludenonegrades')) { const exclude = document.querySelectorAll(selectors.elements.exclude); const checked = (trigger.dataset.action === 'excludeallgrades'); // Uncheck or check each exclude checkbox. exclude.forEach((el) => { el.checked = checked; }); } else if (trigger.dataset.action === 'bulklegend') { // Modal for bulk insert grades. Str.get_strings([ {key: 'bulklegend', component: 'gradereport_singleview'}, {key: 'save', component: 'moodle'}, ]) .then((strings) => { return ModalSaveCancel.create({ body: Templates.render('gradereport_singleview/bulkinsert', { id: 'bulkinsertmodal', name: 'bulkinsertmodal' }), title: strings[0], buttons: { save: strings[1], }, removeOnClose: true, show: true, }); }) .then((modal) => { modal.getFooter().find(selectors.elements.modalsave).attr('disabled', true); // We need to acknowledge that we understand risks of loosing data. // Only when acknowledge checkbox is checked we allow selecting insert options. modal.getRoot().on('change', selectors.elements.warningcheckbox, (e) => { e.preventDefault(); if (e.target.checked) { modal.getRoot().find(selectors.elements.modalformdata).removeClass('dimmed_text'); modal.getRoot().find(selectors.elements.modalradio).removeAttr('disabled'); modal.getRoot().find(selectors.elements.modalinput).removeAttr('disabled'); const formRadioData = modal.getRoot().find(selectors.elements.modalradiochecked).val(); // We allow saving grades only when all needed data present on form. if (formRadioData) { modal.getFooter().find(selectors.elements.modalsave).removeAttr('disabled'); } } else { modal.getRoot().find(selectors.elements.modalformdata).addClass('dimmed_text'); modal.getRoot().find(selectors.elements.modalradio).attr('disabled', true); modal.getRoot().find(selectors.elements.modalinput).attr('disabled', true); modal.getFooter().find(selectors.elements.modalsave).attr('disabled', true); } }); // We allow saving grades only when all needed data present on form. modal.getRoot().on('change', selectors.elements.modalradio, (e) => { e.preventDefault(); modal.getFooter().find(selectors.elements.modalsave).removeAttr('disabled'); }); modal.getRoot().on(ModalEvents.save, () => { // When save button is clicked in modal form we insert data from modal // into preexisted hidden bulk insert form and Save button for table form. document.querySelector(selectors.elements.enablebulkinsert).checked = true; const formRadioData = modal.getRoot().find(selectors.elements.modalradiochecked).val(); const $select = document.querySelector(selectors.elements.formradio); $select.value = formRadioData; const formData = modal.getRoot().find(selectors.elements.modalgrade).val(); document.querySelector(selectors.elements.formgrade).value = formData; document.querySelector(selectors.elements.formsave).click(); }); return modal; }) .catch(Notification.exception); } }); }); }; amd/src/grade.js000064400000004002152266565660007533 0ustar00// 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 . /** * Allow the user to search for grades within the singleview report. * * @module gradereport_singleview/grade * @copyright 2023 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import GradeItemSearch from 'core_grades/comboboxsearch/grade'; // Define our standard lookups. const selectors = { component: '.grade-search', courseid: '[data-region="courseid"]', }; const component = document.querySelector(selectors.component); export default class GradeItems extends GradeItemSearch { courseID = component.querySelector(selectors.courseid).dataset.courseid; /** * Construct the class. * * @param {string} baseUrl The base URL for the page. */ constructor(baseUrl) { super(); this.baseUrl = baseUrl; } static init(baseUrl) { return new GradeItems(baseUrl); } /** * Build up the link that is dedicated to a particular result. * * @param {Number} gradeID The ID of the grade item selected. * @returns {string|*} */ selectOneLink(gradeID) { const url = new URL(this.baseUrl); url.searchParams.set('gradesearchvalue', this.getSearchTerm()); url.searchParams.set('item', 'grade'); url.searchParams.set('itemid', gradeID); return url.toString(); } } amd/src/selectors.js000064400000003627152266565660010470 0ustar00// 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 . /** * Define all of the selectors we will be using on the grading interface. * * @module gradereport_singleview/selectors * @copyright 2022 Ilya Tregubov * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * A small helper function to build queryable data selectors. * @method getDataSelector * @param {String} name * @param {String} value * @return {string} */ const getDataSelector = (name, value) => { return `[data-${name}="${value}"]`; }; export default { actions: { bulkaction: getDataSelector('role', 'bulkaction'), }, elements: { override: 'input[type=checkbox][name^=override]', exclude: 'input[type=checkbox][name^=exclude]', modalsave: '[data-action="save"]', warningcheckbox: 'input[type="checkbox"]', modalformdata: '.formdata', modalradio: 'input[type="radio"]', modalinput: 'input[type="text"]', modalradiochecked: 'input[type="radio"]:checked', enablebulkinsert: 'input[type="checkbox"][name^=bulk]', formradio: 'select[name^=bulk]', modalgrade: '.form-control', formgrade: 'input[type="text"][name^=bulk]', formsave: 'input[type="submit"]', }, }; amd/build/bulkactions.min.js.map000064400000027147152266565660012654 0ustar00{"version":3,"file":"bulkactions.min.js","sources":["../src/bulkactions.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript module for bulk actions.\n *\n * @module gradereport_singleview/bulkactions\n * @copyright 2022 Ilya Tregubov \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Pending from 'core/pending';\nimport CustomEvents from \"core/custom_interaction_events\";\nimport ModalSaveCancel from 'core/modal_save_cancel';\nimport Templates from 'core/templates';\nimport ModalEvents from 'core/modal_events';\nimport * as Str from 'core/str';\nimport Notification from 'core/notification';\nimport selectors from 'gradereport_singleview/selectors';\n\n/**\n * Initialize module.\n */\nexport const init = () => {\n const pendingPromise = new Pending();\n registerListenerEvents();\n pendingPromise.resolve();\n};\n\n/**\n * Register bulk actions related event listeners.\n *\n * @method registerListenerEvents\n */\nconst registerListenerEvents = () => {\n const events = [\n 'click',\n CustomEvents.events.activate,\n CustomEvents.events.keyboardActivate\n ];\n CustomEvents.define(document, events);\n\n // Register events.\n events.forEach((event) => {\n document.addEventListener(event, async(e) => {\n const trigger = e.target.closest(selectors.actions.bulkaction);\n\n if (!trigger) {\n return;\n }\n if ((trigger.dataset.action === 'overrideallgrades') || (trigger.dataset.action === 'overridenonegrades')) {\n const override = document.querySelectorAll(selectors.elements.override);\n\n if (trigger.dataset.action === 'overridenonegrades') {\n // Alert for removing all grade overrides on page.\n Str.get_strings([\n {key: 'removeoverride', component: 'gradereport_singleview'},\n {key: 'overridenoneconfirm', component: 'gradereport_singleview'},\n {key: 'removeoverridesave', component: 'gradereport_singleview'},\n {key: 'cancel', component: 'moodle'},\n ])\n .then((strings) => {\n return Notification.confirm(\n strings[0],\n strings[1],\n strings[2],\n strings[3],\n () => {\n // Uncheck each override checkbox - this will make grade and feedback input fields disabled.\n override.forEach((el) => {\n if (el.checked) {\n el.click();\n }\n });\n });\n })\n .catch(Notification.exception);\n\n } else {\n // Check each override checkbox - this will make grade and feedback input fields enabled.\n override.forEach((el) => {\n if (!el.checked) {\n el.click();\n }\n });\n }\n } else if ((trigger.dataset.action === 'excludeallgrades') || (trigger.dataset.action === 'excludenonegrades')) {\n const exclude = document.querySelectorAll(selectors.elements.exclude);\n const checked = (trigger.dataset.action === 'excludeallgrades');\n // Uncheck or check each exclude checkbox.\n exclude.forEach((el) => {\n el.checked = checked;\n });\n } else if (trigger.dataset.action === 'bulklegend') {\n // Modal for bulk insert grades.\n Str.get_strings([\n {key: 'bulklegend', component: 'gradereport_singleview'},\n {key: 'save', component: 'moodle'},\n ])\n .then((strings) => {\n return ModalSaveCancel.create({\n body: Templates.render('gradereport_singleview/bulkinsert', {\n id: 'bulkinsertmodal',\n name: 'bulkinsertmodal'\n }),\n title: strings[0],\n buttons: {\n save: strings[1],\n },\n removeOnClose: true,\n show: true,\n });\n })\n .then((modal) => {\n modal.getFooter().find(selectors.elements.modalsave).attr('disabled', true);\n\n // We need to acknowledge that we understand risks of loosing data.\n // Only when acknowledge checkbox is checked we allow selecting insert options.\n modal.getRoot().on('change', selectors.elements.warningcheckbox,\n (e) => {\n e.preventDefault();\n if (e.target.checked) {\n modal.getRoot().find(selectors.elements.modalformdata).removeClass('dimmed_text');\n modal.getRoot().find(selectors.elements.modalradio).removeAttr('disabled');\n modal.getRoot().find(selectors.elements.modalinput).removeAttr('disabled');\n\n const formRadioData = modal.getRoot().find(selectors.elements.modalradiochecked).val();\n // We allow saving grades only when all needed data present on form.\n if (formRadioData) {\n modal.getFooter().find(selectors.elements.modalsave).removeAttr('disabled');\n }\n } else {\n modal.getRoot().find(selectors.elements.modalformdata).addClass('dimmed_text');\n modal.getRoot().find(selectors.elements.modalradio).attr('disabled', true);\n modal.getRoot().find(selectors.elements.modalinput).attr('disabled', true);\n modal.getFooter().find(selectors.elements.modalsave).attr('disabled', true);\n }\n });\n\n // We allow saving grades only when all needed data present on form.\n modal.getRoot().on('change', selectors.elements.modalradio, (e) => {\n e.preventDefault();\n modal.getFooter().find(selectors.elements.modalsave).removeAttr('disabled');\n });\n\n modal.getRoot().on(ModalEvents.save, () => {\n // When save button is clicked in modal form we insert data from modal\n // into preexisted hidden bulk insert form and Save button for table form.\n document.querySelector(selectors.elements.enablebulkinsert).checked = true;\n const formRadioData = modal.getRoot().find(selectors.elements.modalradiochecked).val();\n const $select = document.querySelector(selectors.elements.formradio);\n $select.value = formRadioData;\n\n const formData = modal.getRoot().find(selectors.elements.modalgrade).val();\n document.querySelector(selectors.elements.formgrade).value = formData;\n document.querySelector(selectors.elements.formsave).click();\n });\n\n return modal;\n })\n .catch(Notification.exception);\n }\n });\n });\n};\n"],"names":["pendingPromise","Pending","registerListenerEvents","resolve","events","CustomEvents","activate","keyboardActivate","define","document","forEach","event","addEventListener","async","trigger","e","target","closest","selectors","actions","bulkaction","dataset","action","override","querySelectorAll","elements","Str","get_strings","key","component","then","strings","Notification","confirm","el","checked","click","catch","exception","exclude","ModalSaveCancel","create","body","Templates","render","id","name","title","buttons","save","removeOnClose","show","modal","getFooter","find","modalsave","attr","getRoot","on","warningcheckbox","preventDefault","modalformdata","removeClass","modalradio","removeAttr","modalinput","modalradiochecked","val","addClass","ModalEvents","querySelector","enablebulkinsert","formRadioData","formradio","value","formData","modalgrade","formgrade","formsave"],"mappings":";;;;;;;gnCAmCoB,WACVA,eAAiB,IAAIC,iBAC3BC,yBACAF,eAAeG,iBAQbD,uBAAyB,WACrBE,OAAS,CACX,QACAC,mCAAaD,OAAOE,SACpBD,mCAAaD,OAAOG,qDAEXC,OAAOC,SAAUL,QAG9BA,OAAOM,SAASC,QACZF,SAASG,iBAAiBD,OAAOE,MAAAA,UACvBC,QAAUC,EAAEC,OAAOC,QAAQC,mBAAUC,QAAQC,eAE9CN,WAG2B,sBAA3BA,QAAQO,QAAQC,QAA+D,uBAA3BR,QAAQO,QAAQC,OAAkC,OACjGC,SAAWd,SAASe,iBAAiBN,mBAAUO,SAASF,UAE/B,uBAA3BT,QAAQO,QAAQC,OAEhBI,IAAIC,YAAY,CACZ,CAACC,IAAK,iBAAkBC,UAAW,0BACnC,CAACD,IAAK,sBAAuBC,UAAW,0BACxC,CAACD,IAAK,qBAAsBC,UAAW,0BACvC,CAACD,IAAK,SAAUC,UAAW,YAE9BC,MAAMC,SACIC,sBAAaC,QAChBF,QAAQ,GACRA,QAAQ,GACRA,QAAQ,GACRA,QAAQ,IACR,KAEIR,SAASb,SAASwB,KACVA,GAAGC,SACHD,GAAGE,gBAKtBC,MAAML,sBAAaM,WAIpBf,SAASb,SAASwB,KACTA,GAAGC,SACJD,GAAGE,gBAIZ,GAAgC,qBAA3BtB,QAAQO,QAAQC,QAA8D,sBAA3BR,QAAQO,QAAQC,OAAiC,OACtGiB,QAAU9B,SAASe,iBAAiBN,mBAAUO,SAASc,SACvDJ,QAAsC,qBAA3BrB,QAAQO,QAAQC,OAEjCiB,QAAQ7B,SAASwB,KACbA,GAAGC,QAAUA,eAEiB,eAA3BrB,QAAQO,QAAQC,QAEvBI,IAAIC,YAAY,CACZ,CAACC,IAAK,aAAcC,UAAW,0BAC/B,CAACD,IAAK,OAAQC,UAAW,YAE5BC,MAAMC,SACIS,2BAAgBC,OAAO,CAC1BC,KAAMC,mBAAUC,OAAO,oCAAqC,CACxDC,GAAI,kBACJC,KAAM,oBAEVC,MAAOhB,QAAQ,GACfiB,QAAS,CACLC,KAAMlB,QAAQ,IAElBmB,eAAe,EACfC,MAAM,MAGbrB,MAAMsB,QACHA,MAAMC,YAAYC,KAAKpC,mBAAUO,SAAS8B,WAAWC,KAAK,YAAY,GAItEJ,MAAMK,UAAUC,GAAG,SAAUxC,mBAAUO,SAASkC,iBAC3C5C,OACGA,EAAE6C,iBACE7C,EAAEC,OAAOmB,QAAS,CAClBiB,MAAMK,UAAUH,KAAKpC,mBAAUO,SAASoC,eAAeC,YAAY,eACnEV,MAAMK,UAAUH,KAAKpC,mBAAUO,SAASsC,YAAYC,WAAW,YAC/DZ,MAAMK,UAAUH,KAAKpC,mBAAUO,SAASwC,YAAYD,WAAW,YAEzCZ,MAAMK,UAAUH,KAAKpC,mBAAUO,SAASyC,mBAAmBC,OAG7Ef,MAAMC,YAAYC,KAAKpC,mBAAUO,SAAS8B,WAAWS,WAAW,iBAGpEZ,MAAMK,UAAUH,KAAKpC,mBAAUO,SAASoC,eAAeO,SAAS,eAChEhB,MAAMK,UAAUH,KAAKpC,mBAAUO,SAASsC,YAAYP,KAAK,YAAY,GACrEJ,MAAMK,UAAUH,KAAKpC,mBAAUO,SAASwC,YAAYT,KAAK,YAAY,GACrEJ,MAAMC,YAAYC,KAAKpC,mBAAUO,SAAS8B,WAAWC,KAAK,YAAY,MAKlFJ,MAAMK,UAAUC,GAAG,SAAUxC,mBAAUO,SAASsC,YAAahD,IACrDA,EAAE6C,iBACFR,MAAMC,YAAYC,KAAKpC,mBAAUO,SAAS8B,WAAWS,WAAW,eAGxEZ,MAAMK,UAAUC,GAAGW,sBAAYpB,MAAM,KAGjCxC,SAAS6D,cAAcpD,mBAAUO,SAAS8C,kBAAkBpC,SAAU,QAChEqC,cAAgBpB,MAAMK,UAAUH,KAAKpC,mBAAUO,SAASyC,mBAAmBC,MACjE1D,SAAS6D,cAAcpD,mBAAUO,SAASgD,WAClDC,MAAQF,oBAEVG,SAAWvB,MAAMK,UAAUH,KAAKpC,mBAAUO,SAASmD,YAAYT,MACrE1D,SAAS6D,cAAcpD,mBAAUO,SAASoD,WAAWH,MAAQC,SAC7DlE,SAAS6D,cAAcpD,mBAAUO,SAASqD,UAAU1C,WAGjDgB,SAEVf,MAAML,sBAAaM"}amd/build/group.min.js.map000064400000006505152266565660011465 0ustar00{"version":3,"file":"group.min.js","sources":["../src/group.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Allow the user to search for groups within the singleview report.\n *\n * @module gradereport_singleview/group\n * @copyright 2023 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @deprecated since Moodle 4.5 - please use core_course/actionbar/group instead.\n * @todo Final deprecation in Moodle 6.0. See MDL-82116.\n */\nimport GroupSearch from 'core_group/comboboxsearch/group';\nimport Url from 'core/url';\n\nexport default class Group extends GroupSearch {\n\n courseID;\n\n item = null;\n\n /**\n * Construct the class.\n *\n * @param {string} item The page type we are currently on.\n */\n constructor(item) {\n window.console.warn(\n 'The gradereport_singleview/group module has been deprecated since Moodle 4.5.' +\n ' Please use core_course/actionbar/group instead.',\n );\n\n super();\n this.item = item;\n\n // Define our standard lookups.\n this.selectors = {...this.selectors,\n courseid: '[data-region=\"courseid\"]',\n };\n const component = document.querySelector(this.componentSelector());\n this.courseID = component.querySelector(this.selectors.courseid).dataset.courseid;\n }\n\n /**\n * Allow the class to be invoked via PHP.\n *\n * @param {string} item The page type we are currently on.\n * @returns {Group}\n */\n static init(item) {\n return new Group(item);\n }\n\n /**\n * Build up the link that is dedicated to a particular result.\n *\n * @param {Number} groupID The ID of the group selected.\n * @returns {string|*}\n */\n selectOneLink(groupID) {\n return Url.relativeUrl('/grade/report/singleview/index.php', {\n id: this.courseID,\n groupsearchvalue: this.getSearchTerm(),\n group: groupID,\n item: this.item\n }, false);\n }\n}\n"],"names":["Group","GroupSearch","constructor","item","window","console","warn","selectors","this","courseid","component","document","querySelector","componentSelector","courseID","dataset","selectOneLink","groupID","Url","relativeUrl","id","groupsearchvalue","getSearchTerm","group"],"mappings":"mhBA2BqBA,cAAcC,eAW/BC,YAAYC,MACRC,OAAOC,QAAQC,KACX,6MATD,WAcEH,KAAOA,UAGPI,UAAY,IAAIC,KAAKD,UACtBE,SAAU,kCAERC,UAAYC,SAASC,cAAcJ,KAAKK,0BACzCC,SAAWJ,UAAUE,cAAcJ,KAAKD,UAAUE,UAAUM,QAAQN,qBASjEN,aACD,IAAIH,MAAMG,MASrBa,cAAcC,gBACHC,aAAIC,YAAY,qCAAsC,CACzDC,GAAIZ,KAAKM,SACTO,iBAAkBb,KAAKc,gBACvBC,MAAON,QACPd,KAAMK,KAAKL,OACZ"}amd/build/selectors.min.js.map000064400000005065152266565660012334 0ustar00{"version":3,"file":"selectors.min.js","sources":["../src/selectors.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Define all of the selectors we will be using on the grading interface.\n *\n * @module gradereport_singleview/selectors\n * @copyright 2022 Ilya Tregubov \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * A small helper function to build queryable data selectors.\n * @method getDataSelector\n * @param {String} name\n * @param {String} value\n * @return {string}\n */\nconst getDataSelector = (name, value) => {\n return `[data-${name}=\"${value}\"]`;\n};\n\nexport default {\n actions: {\n bulkaction: getDataSelector('role', 'bulkaction'),\n },\n elements: {\n override: 'input[type=checkbox][name^=override]',\n exclude: 'input[type=checkbox][name^=exclude]',\n modalsave: '[data-action=\"save\"]',\n warningcheckbox: 'input[type=\"checkbox\"]',\n modalformdata: '.formdata',\n modalradio: 'input[type=\"radio\"]',\n modalinput: 'input[type=\"text\"]',\n modalradiochecked: 'input[type=\"radio\"]:checked',\n enablebulkinsert: 'input[type=\"checkbox\"][name^=bulk]',\n formradio: 'select[name^=bulk]',\n modalgrade: '.form-control',\n formgrade: 'input[type=\"text\"][name^=bulk]',\n formsave: 'input[type=\"submit\"]',\n },\n};\n"],"names":["name","value","actions","bulkaction","elements","override","exclude","modalsave","warningcheckbox","modalformdata","modalradio","modalinput","modalradiochecked","enablebulkinsert","formradio","modalgrade","formgrade","formsave"],"mappings":"8JA8ByBA,KAAMC,eAIhB,CACXC,QAAS,CACLC,YANiBH,KAMW,OANLC,MAMa,6BALxBD,kBAASC,cAOzBG,SAAU,CACNC,SAAU,uCACVC,QAAS,sCACTC,UAAW,uBACXC,gBAAiB,yBACjBC,cAAe,YACfC,WAAY,sBACZC,WAAY,qBACZC,kBAAmB,8BACnBC,iBAAkB,qCAClBC,UAAW,qBACXC,WAAY,gBACZC,UAAW,iCACXC,SAAU"}amd/build/bulkactions.min.js000064400000013107152266565660012067 0ustar00define("gradereport_singleview/bulkactions",["exports","core/pending","core/custom_interaction_events","core/modal_save_cancel","core/templates","core/modal_events","core/str","core/notification","gradereport_singleview/selectors"],(function(_exports,_pending,_custom_interaction_events,_modal_save_cancel,_templates,_modal_events,Str,_notification,_selectors){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** * Javascript module for bulk actions. * * @module gradereport_singleview/bulkactions * @copyright 2022 Ilya Tregubov * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_pending=_interopRequireDefault(_pending),_custom_interaction_events=_interopRequireDefault(_custom_interaction_events),_modal_save_cancel=_interopRequireDefault(_modal_save_cancel),_templates=_interopRequireDefault(_templates),_modal_events=_interopRequireDefault(_modal_events),Str=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Str),_notification=_interopRequireDefault(_notification),_selectors=_interopRequireDefault(_selectors);_exports.init=()=>{const pendingPromise=new _pending.default;registerListenerEvents(),pendingPromise.resolve()};const registerListenerEvents=()=>{const events=["click",_custom_interaction_events.default.events.activate,_custom_interaction_events.default.events.keyboardActivate];_custom_interaction_events.default.define(document,events),events.forEach((event=>{document.addEventListener(event,(async e=>{const trigger=e.target.closest(_selectors.default.actions.bulkaction);if(trigger)if("overrideallgrades"===trigger.dataset.action||"overridenonegrades"===trigger.dataset.action){const override=document.querySelectorAll(_selectors.default.elements.override);"overridenonegrades"===trigger.dataset.action?Str.get_strings([{key:"removeoverride",component:"gradereport_singleview"},{key:"overridenoneconfirm",component:"gradereport_singleview"},{key:"removeoverridesave",component:"gradereport_singleview"},{key:"cancel",component:"moodle"}]).then((strings=>_notification.default.confirm(strings[0],strings[1],strings[2],strings[3],(()=>{override.forEach((el=>{el.checked&&el.click()}))})))).catch(_notification.default.exception):override.forEach((el=>{el.checked||el.click()}))}else if("excludeallgrades"===trigger.dataset.action||"excludenonegrades"===trigger.dataset.action){const exclude=document.querySelectorAll(_selectors.default.elements.exclude),checked="excludeallgrades"===trigger.dataset.action;exclude.forEach((el=>{el.checked=checked}))}else"bulklegend"===trigger.dataset.action&&Str.get_strings([{key:"bulklegend",component:"gradereport_singleview"},{key:"save",component:"moodle"}]).then((strings=>_modal_save_cancel.default.create({body:_templates.default.render("gradereport_singleview/bulkinsert",{id:"bulkinsertmodal",name:"bulkinsertmodal"}),title:strings[0],buttons:{save:strings[1]},removeOnClose:!0,show:!0}))).then((modal=>(modal.getFooter().find(_selectors.default.elements.modalsave).attr("disabled",!0),modal.getRoot().on("change",_selectors.default.elements.warningcheckbox,(e=>{if(e.preventDefault(),e.target.checked){modal.getRoot().find(_selectors.default.elements.modalformdata).removeClass("dimmed_text"),modal.getRoot().find(_selectors.default.elements.modalradio).removeAttr("disabled"),modal.getRoot().find(_selectors.default.elements.modalinput).removeAttr("disabled");modal.getRoot().find(_selectors.default.elements.modalradiochecked).val()&&modal.getFooter().find(_selectors.default.elements.modalsave).removeAttr("disabled")}else modal.getRoot().find(_selectors.default.elements.modalformdata).addClass("dimmed_text"),modal.getRoot().find(_selectors.default.elements.modalradio).attr("disabled",!0),modal.getRoot().find(_selectors.default.elements.modalinput).attr("disabled",!0),modal.getFooter().find(_selectors.default.elements.modalsave).attr("disabled",!0)})),modal.getRoot().on("change",_selectors.default.elements.modalradio,(e=>{e.preventDefault(),modal.getFooter().find(_selectors.default.elements.modalsave).removeAttr("disabled")})),modal.getRoot().on(_modal_events.default.save,(()=>{document.querySelector(_selectors.default.elements.enablebulkinsert).checked=!0;const formRadioData=modal.getRoot().find(_selectors.default.elements.modalradiochecked).val();document.querySelector(_selectors.default.elements.formradio).value=formRadioData;const formData=modal.getRoot().find(_selectors.default.elements.modalgrade).val();document.querySelector(_selectors.default.elements.formgrade).value=formData,document.querySelector(_selectors.default.elements.formsave).click()})),modal))).catch(_notification.default.exception)}))}))}})); //# sourceMappingURL=bulkactions.min.js.mapamd/build/grade.min.js000064400000002050152266565660010626 0ustar00define("gradereport_singleview/grade",["exports","core_grades/comboboxsearch/grade"],(function(_exports,_grade){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_grade=(obj=_grade)&&obj.__esModule?obj:{default:obj};const selectors_component=".grade-search",selectors_courseid='[data-region="courseid"]',component=document.querySelector(selectors_component);class GradeItems extends _grade.default{constructor(baseUrl){super(),function(obj,key,value){key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value}(this,"courseID",component.querySelector(selectors_courseid).dataset.courseid),this.baseUrl=baseUrl}static init(baseUrl){return new GradeItems(baseUrl)}selectOneLink(gradeID){const url=new URL(this.baseUrl);return url.searchParams.set("gradesearchvalue",this.getSearchTerm()),url.searchParams.set("item","grade"),url.searchParams.set("itemid",gradeID),url.toString()}}return _exports.default=GradeItems,_exports.default})); //# sourceMappingURL=grade.min.js.mapamd/build/selectors.min.js000064400000001561152266565660011555 0ustar00define("gradereport_singleview/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;var name,value,_default={actions:{bulkaction:(name="role",value="bulkaction","[data-".concat(name,'="').concat(value,'"]'))},elements:{override:"input[type=checkbox][name^=override]",exclude:"input[type=checkbox][name^=exclude]",modalsave:'[data-action="save"]',warningcheckbox:'input[type="checkbox"]',modalformdata:".formdata",modalradio:'input[type="radio"]',modalinput:'input[type="text"]',modalradiochecked:'input[type="radio"]:checked',enablebulkinsert:'input[type="checkbox"][name^=bulk]',formradio:"select[name^=bulk]",modalgrade:".form-control",formgrade:'input[type="text"][name^=bulk]',formsave:'input[type="submit"]'}};return _exports.default=_default,_exports.default})); //# sourceMappingURL=selectors.min.js.mapamd/build/user.min.js.map000064400000010257152266565660011306 0ustar00{"version":3,"file":"user.min.js","sources":["../src/user.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Allow the user to search for learners within the singleview report.\n *\n * @module gradereport_singleview/user\n * @copyright 2023 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport UserSearch from 'core_user/comboboxsearch/user';\nimport {renderForPromise, replaceNodeContents} from 'core/templates';\nimport * as Repository from 'core_grades/searchwidget/repository';\n\nexport default class User extends UserSearch {\n\n /**\n * Construct the class.\n *\n * @param {string} baseUrl The base URL for the page.\n */\n constructor(baseUrl) {\n super();\n this.baseUrl = baseUrl;\n }\n\n static init(baseUrl) {\n return new User(baseUrl);\n }\n\n /**\n * Build the content then replace the node.\n */\n async renderDropdown() {\n const {html, js} = await renderForPromise('core_user/comboboxsearch/resultset', {\n instance: this.instance,\n users: this.getMatchedResults().slice(0, 5),\n hasresults: this.getMatchedResults().length > 0,\n searchterm: this.getSearchTerm(),\n });\n replaceNodeContents(this.getHTMLElements().searchDropdown, html, js);\n // Remove aria-activedescendant when the available options change.\n this.searchInput.removeAttribute('aria-activedescendant');\n }\n\n /**\n * Stub out default required function unused here.\n * @returns {null}\n */\n selectAllResultsLink() {\n return null;\n }\n\n /**\n * Build up the view all link that is dedicated to a particular result.\n *\n * @param {Number} userID The ID of the user selected.\n * @returns {string|*}\n */\n selectOneLink(userID) {\n const url = new URL(this.baseUrl);\n url.searchParams.set('searchvalue', this.getSearchTerm());\n url.searchParams.set('item', 'user');\n url.searchParams.set('userid', userID);\n return url.toString();\n }\n\n /**\n * Get the data we will be searching against in this component.\n *\n * @returns {Promise<*>}\n */\n fetchDataset() {\n // Small typing checks as sometimes groups don't exist therefore the element returns a empty string.\n const gts = typeof (this.groupID) === \"string\" && this.groupID === '' ? 0 : this.groupID;\n return Repository.userFetch(this.courseID, gts).then((r) => r.users);\n }\n}\n"],"names":["User","UserSearch","constructor","baseUrl","html","js","instance","this","users","getMatchedResults","slice","hasresults","length","searchterm","getSearchTerm","getHTMLElements","searchDropdown","searchInput","removeAttribute","selectAllResultsLink","selectOneLink","userID","url","URL","searchParams","set","toString","fetchDataset","gts","groupID","Repository","userFetch","courseID","then","r"],"mappings":";;;;;;;skCA0BqBA,aAAaC,cAO9BC,YAAYC,sBAEHA,QAAUA,oBAGPA,gBACD,IAAIH,KAAKG,sCAOVC,KAACA,KAADC,GAAOA,UAAY,+BAAiB,qCAAsC,CAC5EC,SAAUC,KAAKD,SACfE,MAAOD,KAAKE,oBAAoBC,MAAM,EAAG,GACzCC,WAAYJ,KAAKE,oBAAoBG,OAAS,EAC9CC,WAAYN,KAAKO,qDAEDP,KAAKQ,kBAAkBC,eAAgBZ,KAAMC,SAE5DY,YAAYC,gBAAgB,yBAOrCC,8BACW,KASXC,cAAcC,cACJC,IAAM,IAAIC,IAAIhB,KAAKJ,gBACzBmB,IAAIE,aAAaC,IAAI,cAAelB,KAAKO,iBACzCQ,IAAIE,aAAaC,IAAI,OAAQ,QAC7BH,IAAIE,aAAaC,IAAI,SAAUJ,QACxBC,IAAII,WAQfC,qBAEUC,IAAgC,iBAAlBrB,KAAKsB,SAA0C,KAAjBtB,KAAKsB,QAAiB,EAAItB,KAAKsB,eAC1EC,WAAWC,UAAUxB,KAAKyB,SAAUJ,KAAKK,MAAMC,GAAMA,EAAE1B"}amd/build/user.min.js000064400000004766152266565660010542 0ustar00define("gradereport_singleview/user",["exports","core_user/comboboxsearch/user","core/templates","core_grades/searchwidget/repository"],(function(_exports,_user,_templates,Repository){var obj; /** * Allow the user to search for learners within the singleview report. * * @module gradereport_singleview/user * @copyright 2023 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_user=(obj=_user)&&obj.__esModule?obj:{default:obj},Repository=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Repository);class User extends _user.default{constructor(baseUrl){super(),this.baseUrl=baseUrl}static init(baseUrl){return new User(baseUrl)}async renderDropdown(){const{html:html,js:js}=await(0,_templates.renderForPromise)("core_user/comboboxsearch/resultset",{instance:this.instance,users:this.getMatchedResults().slice(0,5),hasresults:this.getMatchedResults().length>0,searchterm:this.getSearchTerm()});(0,_templates.replaceNodeContents)(this.getHTMLElements().searchDropdown,html,js),this.searchInput.removeAttribute("aria-activedescendant")}selectAllResultsLink(){return null}selectOneLink(userID){const url=new URL(this.baseUrl);return url.searchParams.set("searchvalue",this.getSearchTerm()),url.searchParams.set("item","user"),url.searchParams.set("userid",userID),url.toString()}fetchDataset(){const gts="string"==typeof this.groupID&&""===this.groupID?0:this.groupID;return Repository.userFetch(this.courseID,gts).then((r=>r.users))}}return _exports.default=User,_exports.default})); //# sourceMappingURL=user.min.js.mapamd/build/group.min.js000064400000002511152266565660010702 0ustar00define("gradereport_singleview/group",["exports","core_group/comboboxsearch/group","core/url"],(function(_exports,_group,_url){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_group=_interopRequireDefault(_group),_url=_interopRequireDefault(_url);class Group extends _group.default{constructor(item){window.console.warn("The gradereport_singleview/group module has been deprecated since Moodle 4.5. Please use core_course/actionbar/group instead."),super(),_defineProperty(this,"courseID",void 0),_defineProperty(this,"item",null),this.item=item,this.selectors={...this.selectors,courseid:'[data-region="courseid"]'};const component=document.querySelector(this.componentSelector());this.courseID=component.querySelector(this.selectors.courseid).dataset.courseid}static init(item){return new Group(item)}selectOneLink(groupID){return _url.default.relativeUrl("/grade/report/singleview/index.php",{id:this.courseID,groupsearchvalue:this.getSearchTerm(),group:groupID,item:this.item},!1)}}return _exports.default=Group,_exports.default})); //# sourceMappingURL=group.min.js.mapamd/build/grade.min.js.map000064400000005277152266565660011420 0ustar00{"version":3,"file":"grade.min.js","sources":["../src/grade.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Allow the user to search for grades within the singleview report.\n *\n * @module gradereport_singleview/grade\n * @copyright 2023 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport GradeItemSearch from 'core_grades/comboboxsearch/grade';\n\n// Define our standard lookups.\nconst selectors = {\n component: '.grade-search',\n courseid: '[data-region=\"courseid\"]',\n};\nconst component = document.querySelector(selectors.component);\n\nexport default class GradeItems extends GradeItemSearch {\n\n courseID = component.querySelector(selectors.courseid).dataset.courseid;\n\n /**\n * Construct the class.\n *\n * @param {string} baseUrl The base URL for the page.\n */\n constructor(baseUrl) {\n super();\n this.baseUrl = baseUrl;\n }\n\n static init(baseUrl) {\n return new GradeItems(baseUrl);\n }\n\n /**\n * Build up the link that is dedicated to a particular result.\n *\n * @param {Number} gradeID The ID of the grade item selected.\n * @returns {string|*}\n */\n selectOneLink(gradeID) {\n const url = new URL(this.baseUrl);\n url.searchParams.set('gradesearchvalue', this.getSearchTerm());\n url.searchParams.set('item', 'grade');\n url.searchParams.set('itemid', gradeID);\n return url.toString();\n }\n}\n"],"names":["selectors","component","document","querySelector","GradeItems","GradeItemSearch","constructor","baseUrl","dataset","courseid","selectOneLink","gradeID","url","URL","this","searchParams","set","getSearchTerm","toString"],"mappings":"oQAyBMA,oBACS,gBADTA,mBAEQ,2BAERC,UAAYC,SAASC,cAAcH,2BAEpBI,mBAAmBC,eASpCC,YAAYC,2KAPDN,UAAUE,cAAcH,oBAAoBQ,QAAQC,eAStDF,QAAUA,oBAGPA,gBACD,IAAIH,WAAWG,SAS1BG,cAAcC,eACJC,IAAM,IAAIC,IAAIC,KAAKP,gBACzBK,IAAIG,aAAaC,IAAI,mBAAoBF,KAAKG,iBAC9CL,IAAIG,aAAaC,IAAI,OAAQ,SAC7BJ,IAAIG,aAAaC,IAAI,SAAUL,SACxBC,IAAIM"}UPGRADING.md000064400000000405152266565660006430 0ustar00# gradereport_singleview Upgrade notes ## 4.5 ### Deprecated - The `gradereport_singleview/group` ESM has been deprecated. Please use `core_course/actionbar/group` instead. For more information see [MDL-80745](https://tracker.moodle.org/browse/MDL-80745) tests/screen_test.php000064400000012040152266565660010755 0ustar00. namespace gradereport_singleview; use gradereport_singleview_screen_testable; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once(__DIR__ . '/fixtures/screen.php'); require_once($CFG->libdir . '/gradelib.php'); /** * Tests for screen class. * * @package gradereport_singleview * @category test * @copyright 2014 onwards Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class screen_test extends \advanced_testcase { /** * Test load_users method. */ public function test_load_users(): void { global $DB; $this->setAdminUser(); $this->resetAfterTest(true); $roleteacher = $DB->get_record('role', ['shortname' => 'teacher'], '*', MUST_EXIST); // Create a course, users and groups. $course = $this->getDataGenerator()->create_course(); $coursecontext = \context_course::instance($course->id); $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]); $teacher = $this->getDataGenerator()->create_user(); $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $roleteacher->id); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); $this->getDataGenerator()->create_group_member(['groupid' => $group->id, 'userid' => $teacher->id]); $this->getDataGenerator()->create_group_member(['groupid' => $group->id, 'userid' => $user1->id]); $this->getDataGenerator()->create_group_member(['groupid' => $group->id, 'userid' => $user2->id]); // Perform a regrade before creating the report. grade_regrade_final_grades($course->id); $screentest = new gradereport_singleview_screen_testable($course->id, 0, $group->id); $groupusers = $screentest->test_load_users(); $this->assertDebuggingCalled('The function load_users() is deprecated. ' . 'Please use grade_report::get_gradable_users() instead.'); $this->assertCount(2, $groupusers); // Now, let's suspend the enrolment of a user. Should return only one user. $this->getDataGenerator()->enrol_user($user2->id, $course->id, $roleteacher->id, 'manual', 0, 0, ENROL_USER_SUSPENDED); $users = $screentest->test_load_users(); $this->assertDebuggingCalled('The function load_users() is deprecated. ' . 'Please use grade_report::get_gradable_users() instead.'); $this->assertCount(1, $users); // Change the viewsuspendedusers capabilities and set the user preference to display suspended users. assign_capability('moodle/course:viewsuspendedusers', CAP_ALLOW, $roleteacher->id, $coursecontext, true); set_user_preference('grade_report_showonlyactiveenrol', false, $teacher); accesslib_clear_all_caches_for_unit_testing(); $this->setUser($teacher); $screentest = new gradereport_singleview_screen_testable($course->id, 0, $group->id); $users = $screentest->test_load_users(); $this->assertDebuggingCalled('The function load_users() is deprecated. ' . 'Please use grade_report::get_gradable_users() instead.'); $this->assertCount(2, $users); // Change the capability again, now the user can't see the suspended enrolments. assign_capability('moodle/course:viewsuspendedusers', CAP_PROHIBIT, $roleteacher->id, $coursecontext, true); set_user_preference('grade_report_showonlyactiveenrol', false, $teacher); accesslib_clear_all_caches_for_unit_testing(); $users = $screentest->test_load_users(); $this->assertDebuggingCalled('The function load_users() is deprecated. ' . 'Please use grade_report::get_gradable_users() instead.'); $this->assertCount(1, $users); // Now, activate the user enrolment again. We shall get 2 users now. $this->getDataGenerator()->enrol_user($user2->id, $course->id, $roleteacher->id, 'manual', 0, 0, ENROL_USER_ACTIVE); $users = $screentest->test_load_users(); $this->assertDebuggingCalled('The function load_users() is deprecated. ' . 'Please use grade_report::get_gradable_users() instead.'); $this->assertCount(2, $users); } } tests/fixtures/screen.php000064400000003150152266565660011571 0ustar00. /** * Fixtures for single view report screen class testing. * * @package gradereport_singleview * @copyright 2014 onwards Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); class gradereport_singleview_screen_testable extends \gradereport_singleview\local\screen\screen { /** * Wrapper to make protected method accessible during testing. * * @return array returns array of users. */ public function test_load_users(): array { return $this->load_users(); } /** * Return the HTML for the page. */ public function init($selfitemisempty = false) { } /** * Get the type of items on this screen, not valid so return false. */ public function item_type(): string { } /** * Return the HTML for the page. */ public function html(): string { } } tests/behat/pagination.feature000064400000020061152266565660012521 0ustar00@gradereport @gradereport_singleview @javascript Feature: Singleview report pagination In order to consume the content of the report better As a teacher I need the report to be paginated Background: Given the following "courses" exist: | fullname | shortname | | Course 1 | C1 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | course | idnumber | name | intro | grade | | assign | C1 | a1 | Test assignment one | Submit something! | 300 | Scenario: Default is used when teachers have no preference yet on singleview report Given "41" "users" exist with the following data: | username | student[count] | | firstname | Student | | lastname | [count] | | email | student[count]@example.com | And "41" "course enrolments" exist with the following data: | user | student[count] | | course | C1 | | role |student | When I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1" And I click on grade item menu "Test assignment one" of type "gradeitem" on "grader" page And I choose "Single view for this item" in the open action menu Then the field "perpage" matches value "20" # There is also 1 header row. And I should see "21" node occurrences of type "tr" in the "singleview-grades" "table" And I should see "3" in the ".stickyfooter .pagination" "css_element" And I should not see "4" in the ".stickyfooter .pagination" "css_element" Scenario: Teachers can have their preference for the number of students on singleview report Given the following "courses" exist: | fullname | shortname | | Course 2 | C2 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C2 | editingteacher | And the following "activities" exist: | activity | course | idnumber | name | intro | grade | | assign | C2 | a2 | Test assignment two | Submit something! | 300 | When I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1" And I click on grade item menu "Test assignment one" of type "gradeitem" on "grader" page And I choose "Single view for this item" in the open action menu And I set the field "perpage" to "100" And I am on the "Course 2" "grades > Grader report > View" page And I click on grade item menu "Test assignment two" of type "gradeitem" on "grader" page And I choose "Single view for this item" in the open action menu Then the field "perpage" matches value "100" Scenario: Teachers can change the number of students shown on singleview report Given "101" "users" exist with the following data: | username | student[count] | | firstname | Student | | lastname | [count] | | email | student[count]@example.com | And "101" "course enrolments" exist with the following data: | user | student[count] | | course | C1 | | role |student | When I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1" And I click on grade item menu "Test assignment one" of type "gradeitem" on "grader" page And I choose "Single view for this item" in the open action menu And I set the field "perpage" to "100" # There is also 1 header row. Then I should see "101" node occurrences of type "tr" in the "singleview-grades" "table" And I should see "2" in the ".stickyfooter .pagination" "css_element" And I should not see "3" in the ".stickyfooter .pagination" "css_element" Scenario: Teachers can view all students on singleview report Given "150" "users" exist with the following data: | username | student[count] | | firstname | Student | | lastname | [count] | | email | student[count]@example.com | And "150" "course enrolments" exist with the following data: | user | student[count] | | course | C1 | | role |student | When I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1" And I click on grade item menu "Test assignment one" of type "gradeitem" on "grader" page And I choose "Single view for this item" in the open action menu And I set the field "perpage" to "All" # There is also 1 header row. Then I should see "151" node occurrences of type "tr" in the "singleview-grades" "table" And ".stickyfooter .pagination" "css_element" should not exist Scenario: Max perpage is 1000 and All is not available Given "1100" "users" exist with the following data: | username | student[count] | | firstname | Student | | lastname | [count] | | email | student[count]@example.com | And "1100" "course enrolments" exist with the following data: | user | student[count] | | course | C1 | | role | student | When I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1" And I click on grade item menu "Test assignment one" of type "gradeitem" on "grader" page And I choose "Single view for this item" in the open action menu Then the "perpage" select box should contain "1000" And the "perpage" select box should not contain "All" Scenario: Page loads correctly with Max perpage limit Given "1100" "users" exist with the following data: | username | student[count] | | firstname | Student | | lastname | [count] | | email | student[count]@example.com | And "1100" "course enrolments" exist with the following data: | user | student[count] | | course | C1 | | role | student | When I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1" And I click on grade item menu "Test assignment one" of type "gradeitem" on "grader" page And I choose "Single view for this item" in the open action menu And I set the field "perpage" to "1000" Then I should see "1001" node occurrences of type "tr" in the "singleview-grades" "table" And I click on "2" "link" in the ".stickyfooter .pagination" "css_element" And I should see "101" node occurrences of type "tr" in the "singleview-grades" "table" And I should see "1" in the ".stickyfooter .pagination" "css_element" And I should see "2" in the ".stickyfooter .pagination" "css_element" And I should not see "3" in the ".stickyfooter .pagination" "css_element" @javascript Scenario: The pagination bar is only displayed when there is more than one page on singleview report Given "21" "users" exist with the following data: | username | student[count] | | firstname | Student | | lastname | [count] | | email | student[count]@example.com | And "21" "course enrolments" exist with the following data: | user | student[count] | | course | C1 | | role |student | When I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1" And I click on grade item menu "Test assignment one" of type "gradeitem" on "grader" page And I choose "Single view for this item" in the open action menu # By default, we have 20 students per page. And ".stickyfooter .pagination" "css_element" should exist And I set the field "perpage" to "100" Then ".stickyfooter .pagination" "css_element" should not exist tests/behat/bulk_insert_grades.feature000064400000020022152266565660014233 0ustar00@core @core_grades @gradereport_singleview @javascript Feature: We can bulk insert grades for students in a course As a teacher In order to quickly grade items I can bulk insert values for all or empty grades. Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | idnumber | alternatename | | teacher1 | Teacher | 1 | teacher1@example.com | t1 | fred | | student1 | Student | 1 | student1@example.com | s1 | james | | student2 | Student | 2 | student1@example.com | s2 | holly | | student3 | Student | 3 | student1@example.com | s3 | anna | | student4 | Student | 4 | student1@example.com | s4 | zac | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | | student4 | C1 | student | And the following "activities" exist: | activity | course | idnumber | name | intro | | assign | C1 | a1 | Test assignment one | Submit something! | | assign | C1 | a2 | Test assignment two | Submit something! | | assign | C1 | a3 | Test assignment three | Submit something! | | assign | C1 | a4 | Test assignment four | Submit nothing! | And I am on the "Course 1" "Course" page logged in as "teacher1" And I turn editing mode on Scenario: I can not save bulk insert until I fill required form elements Given I navigate to "View > Grader report" in the course gradebook And I click on grade item menu "Test assignment one" of type "gradeitem" on "grader" page And I choose "Single view for this item" in the open action menu And I click on "Actions" "link" When I click on "Bulk insert" "link" And the "Empty grades" "radio" should be disabled And the "All grades" "radio" should be disabled And the "[name=bulkinsertmodal]" "css_element" should be disabled And the "[data-action=save]" "css_element" should be disabled And I click on "I understand that my unsaved changes will be lost." "checkbox" And the "Empty grades" "radio" should be enabled And the "All grades" "radio" should be enabled And the "[name=bulkinsertmodal]" "css_element" should be enabled And the "[data-action=save]" "css_element" should be disabled And I click on "Empty grades" "radio" And the "Empty grades" "radio" should be enabled And the "All grades" "radio" should be enabled And the "[name=bulkinsertmodal]" "css_element" should be enabled Then the "[data-action=save]" "css_element" should be enabled Scenario: I can bulk insert grades and check their override flags for grade view. Given I am on the "Test assignment one" "assign activity" page And I go to "Student 1" "Test assignment one" activity advanced grading page And I set the following fields to these values: | Grade out of 100 | 50 | And I press "Save changes" And I am on "Course 1" course homepage with editing mode on And I navigate to "View > Grader report" in the course gradebook And I click on grade item menu "Test assignment one" of type "gradeitem" on "grader" page And I choose "Single view for this item" in the open action menu And the field "Grade for Student 1" matches value "50.00" And the field "Override for Student 1" matches value "0" And I click on "Actions" "link" And I click on "Bulk insert" "link" And I click on "I understand that my unsaved changes will be lost." "checkbox" And I click on "Empty grades" "radio" And I set the field "Insert new grade" to "1.0" And I click on "Save" "button" in the ".modal-dialog" "css_element" And the field "Grade for Student 1" matches value "50.00" And the field "Override for Student 1" matches value "0" And the field "Grade for Student 2" matches value "1.00" And the field "Override for Student 2" matches value "1" And the field "Grade for Student 3" matches value "1.00" And the field "Override for Student 3" matches value "1" And the field "Grade for Student 4" matches value "1.00" And the field "Override for Student 4" matches value "1" And I click on "Actions" "link" When I click on "Bulk insert" "link" And I click on "I understand that my unsaved changes will be lost." "checkbox" And I click on "All grades" "radio" And I set the field "Insert new grade" to "2.0" And I click on "Save" "button" in the ".modal-dialog" "css_element" And the field "Grade for Student 1" matches value "2.00" And the field "Override for Student 1" matches value "1" And the field "Grade for Student 2" matches value "2.00" And the field "Override for Student 2" matches value "1" And the field "Grade for Student 3" matches value "2.00" And the field "Override for Student 3" matches value "1" And the field "Grade for Student 4" matches value "2.00" Then the field "Override for Student 4" matches value "1" Scenario: I can bulk insert grades and check their override flags for user view. Given I am on the "Test assignment two" "assign activity" page And I go to "Student 1" "Test assignment two" activity advanced grading page And I set the following fields to these values: | Grade out of 100 | 50 | And I press "Save changes" And I am on "Course 1" course homepage with editing mode on And I navigate to "View > Grader report" in the course gradebook And I click on user menu "Student 1" And I choose "Single view for this user" in the open action menu And the field "Grade for Test assignment two" matches value "50.00" And the field "Override for Test assignment two" matches value "0" And I click on "Actions" "link" When I click on "Bulk insert" "link" And I click on "I understand that my unsaved changes will be lost." "checkbox" And I click on "Empty grades" "radio" And I set the field "Insert new grade" to "1.0" And I click on "Save" "button" in the ".modal-dialog" "css_element" And the field "Grade for Test assignment two" matches value "50.00" And the field "Override for Test assignment two" matches value "0" And the field "Grade for Test assignment one" matches value "1.00" And the field "Override for Test assignment one" matches value "1" And the field "Grade for Test assignment three" matches value "1.00" And the field "Override for Test assignment three" matches value "1" And the field "Grade for Test assignment four" matches value "1.00" Then the field "Override for Test assignment four" matches value "1" Scenario: I can not update grades if the value is out of bounds. Given I navigate to "View > Grader report" in the course gradebook And I click on grade item menu "Test assignment one" of type "gradeitem" on "grader" page And I choose "Single view for this item" in the open action menu And I click on "Actions" "link" When I click on "Bulk insert" "link" And I click on "I understand that my unsaved changes will be lost." "checkbox" And I click on "Empty grades" "radio" And I set the field "Insert new grade" to "-1" And I click on "Save" "button" in the ".modal-dialog" "css_element" And I should see "The grade entered for Test assignment one for Student 1 is less than the minimum allowed" And I should see "The grade entered for Test assignment one for Student 2 is less than the minimum allowed" And I should see "The grade entered for Test assignment one for Student 3 is less than the minimum allowed" And I should see "The grade entered for Test assignment one for Student 4 is less than the minimum allowed" Then I should see "Grades were set for 0 items" tests/behat/gradeitemsearch.feature000064400000007547152266565660013535 0ustar00@core @core_grades @gradereport_singleview @javascript Feature: Given we have opted to search for a grade item, Lets find and search them. Background: Given the following "courses" exist: | fullname | shortname | category | groupmode | | Course 1 | C1 | 0 | 1 | And the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | t1 | | student1 | Student | 1 | student1@example.com | s1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And the following "activities" exist: | activity | course | idnumber | name | | assign | C1 | a1 | Test assignment one | | assign | C1 | a2 | Test assignment two | And I am on the "Course 1" "grades > Single view > View" page logged in as "teacher1" And I change window size to "large" Scenario: A teacher can search for and find a grade item to view Given I click on "Grade items" "link" in the ".page-toggler" "css_element" And I click on ".gradesearchwidget" "css_element" When I confirm "Test assignment one" exists in the "Search items" search combo box And I confirm "Test assignment two" exists in the "Search items" search combo box Then I set the field "Search items" to "two" And I wait until "Test assignment one" "option_role" does not exist And I confirm "Test assignment one" does not exist in the "Search items" search combo box And I confirm "Test assignment two" exists in the "Search items" search combo box And I click on "Test assignment two" in the "Search items" search combo box # The search input remains in the field on reload this is in keeping with other search implementations. And I click on ".gradesearchwidget" "css_element" And the field "Search items" matches value "two" Then I set the field "Search items" to "Turtle" And I should see "No results for \"Turtle\"" @accessibility Scenario: A teacher can set focus and search using the input with a keyboard # Basic tests for the page. Given I click on "Grade items" "link" in the ".page-toggler" "css_element" And I click on ".gradesearchwidget" "css_element" And the page should meet accessibility standards And the page should meet "wcag131, wcag141, wcag412" accessibility standards And the page should meet accessibility standards with "wcag131, wcag141, wcag412" extra tests # Move onto general keyboard navigation testing. And I click on "Search items" "field" And I wait until "Test assignment one" "option_role" exists And I press the down key And the focused element is "Search items" "field" And ".active" "css_element" should exist in the "Test assignment one" "option_role" And I press the up key And the focused element is "Search items" "field" And ".active" "css_element" should exist in the "Course total" "option_role" And I press the down key And the focused element is "Search items" "field" And ".active" "css_element" should exist in the "Test assignment one" "option_role" Then I set the field "Search items" to "Goodmeme" And I wait until "Test assignment one" "option_role" does not exist And I press the down key And the focused element is "Search items" "field" # Lets check the tabbing order. And I set the field "Search items" to "one" And I wait until "Test assignment one" "option_role" exists And I press the tab key And the focused element is "Clear search input" "button" in the ".grade-search" "css_element" And I press the enter key And I wait until the page is ready And ".gradesearchwidget" "css_element" should exist tests/behat/usersearch.feature000064400000031446152266565660012545 0ustar00@core @core_grades @gradereport_singleview @javascript Feature: Within the singleview report, a teacher can search for users. Background: Given the following "courses" exist: | fullname | shortname | category | groupmode | | Course 1 | C1 | 0 | 1 | | Course 2 | C2 | 0 | 0 | And the following "users" exist: | username | firstname | lastname | email | idnumber | phone1 | phone2 | department | institution | city | country | | teacher1 | Teacher | 1 | teacher1@example.com | t1 | 1234567892 | 1234567893 | ABC1 | ABCD | Perth | AU | | student1 | Student | 1 | student1@example.com | s1 | 3213078612 | 8974325612 | ABC1 | ABCD | Hanoi | VN | | student2 | Dummy | User | student2@example.com | s2 | 4365899871 | 7654789012 | ABC2 | ABCD | Tokyo | JP | | student3 | User | Example | student3@example.com | s3 | 3243249087 | 0875421745 | ABC2 | ABCD | Olney | GB | | student4 | User | Test | student4@example.com | s4 | 0987532523 | 2149871323 | ABC3 | ABCD | Tokyo | JP | | student5 | Turtle | Manatee | student5@example.com | s5 | 1239087780 | 9873623589 | ABC3 | ABCD | Perth | AU | # Note: Add groups etc so we can test that the search ignores those filters as well if we go down the filter dataset path. And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | teacher1 | C2 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | | student4 | C1 | student | | student5 | C1 | student | And the following "activities" exist: | activity | course | idnumber | name | | assign | C1 | a1 | Test assignment one | And the following config values are set as admin: | showuseridentity | idnumber,email,city,country,phone1,phone2,department,institution | And I change window size to "large" And I am on the "Course 1" "grades > Single view > View" page logged in as "teacher1" And I click on "Users" "link" in the ".page-toggler" "css_element" Scenario: A teacher can view and trigger the user search # Check the placeholder text (no users are initially shown). Given I should see "Search users" And I should see "Search for a user to view all their grades" When I set the field "Search users" to "Turtle" And I confirm "Turtle Manatee" exists in the "Search users" search combo box And I confirm "User Example" does not exist in the "Search users" search combo box And I click on "Turtle Manatee" "list_item" # Business case: This will trigger a page reload and can not dynamically update the table. And I wait until the page is ready And "Turtle Manatee" "heading" should exist And "Teacher 1" "heading" should not exist And "Student 1" "heading" should not exist And "User Example" "heading" should not exist And "User Test" "heading" should not exist And "Dummy User" "heading" should not exist And I set the field "Search users" to "Turt" And I wait until "Turtle Manatee" "option_role" exists And I click on "Clear search input" "button" in the ".user-search" "css_element" And "Turtle Manatee" "option_role" should not be visible Scenario: A teacher can search the single view report to find specified users # Case: Standard search. Given I click on "Dummy" in the "Search users" search combo box And "Dummy User" "heading" should exist And "Teacher 1" "heading" should not exist And "Student 1" "heading" should not exist And "User Example" "heading" should not exist And "User Test" "heading" should not exist And "Turtle Manatee" "heading" should not exist # Case: No users found. When I set the field "Search users" to "Plagiarism" And I should see "No results for \"Plagiarism\"" # Table remains unchanged as the user had no results to select from the dropdown. And "Dummy User" "heading" should exist And "Teacher 1" "heading" should not exist And "Student 1" "heading" should not exist And "User Example" "heading" should not exist And "User Test" "heading" should not exist And "Turtle Manatee" "heading" should not exist # Case: Multiple users found and select only one result. Then I set the field "Search users" to "User" And I wait until "Dummy User" "option_role" exists And I confirm "Dummy User" exists in the "Search users" search combo box And I confirm "User Example" exists in the "Search users" search combo box And I confirm "User Test" exists in the "Search users" search combo box And I confirm "Turtle Manatee" does not exist in the "Search users" search combo box # Check if the matched field names (by lines) includes some identifiable info to help differentiate similar users. And I confirm "User (student2@example.com)" exists in the "Search users" search combo box And I confirm "User (student3@example.com)" exists in the "Search users" search combo box And I confirm "User (student4@example.com)" exists in the "Search users" search combo box And I click on "Dummy User" "list_item" And I wait until the page is ready And "Dummy User" "heading" should exist And "Teacher 1" "heading" should not exist And "Student 1" "heading" should not exist And "User Example" "heading" should not exist And "User Test" "heading" should not exist And "Turtle Manatee" "heading" should not exist # Case: No users enrolled. And I am on the "Course 2" "grades > Single view > View" page And I click on "Users" "link" in the ".page-toggler" "css_element" And I set the field "Search users" to "a" And I wait until "No results for \"a\"" "text" exists Scenario: A teacher can quickly tell that a search is active on the current table Given I click on "Turtle" in the "Search users" search combo box And I wait until the page is ready # The search input remains in the field on reload this is in keeping with other search implementations. When the field "Search users" matches value "Turtle Manatee" # The users get preloaded for accessibility reasons. And "Turtle Manatee" "option_role" should exist # Test if we can then further retain the turtle result set and further filter from there. Then I set the field "Search users" to "Turtle plagiarism" And I wait until "Turtle Manatee" "option_role" does not exist And I should see "No results for \"Turtle plagiarism\"" Scenario: A teacher can search for values besides the users' name Given I set the field "Search users" to "student5@example.com" And I wait until "Turtle Manatee" "list_item" exists And I set the field "Search users" to "@example.com" And I wait until "Dummy User" "list_item" exists # Note: All learners match this email & showing emails is current default. And I confirm "Dummy User" exists in the "Search users" search combo box And I confirm "User Example" exists in the "Search users" search combo box And I confirm "User Test" exists in the "Search users" search combo box And I confirm "Student 1" exists in the "Search users" search combo box And I confirm "Turtle Manatee" exists in the "Search users" search combo box # Search on the country field. When I set the field "Search users" to "JP" And I wait until "Dummy User" "list_item" exists And I wait until "Turtle Manatee" "list_item" does not exist And I confirm "Dummy User" exists in the "Search users" search combo box And I confirm "User Test" exists in the "Search users" search combo box # Search on the city field. And I set the field "Search users" to "Hanoi" And I wait until "User Test" "list_item" does not exist Then I confirm "Student 1" exists in the "Search users" search combo box # Search on the institution field. And I set the field "Search users" to "ABCD" And I wait until "Dummy User" "list_item" exists And I confirm "User Example" exists in the "Search users" search combo box And I confirm "User Test" exists in the "Search users" search combo box And I confirm "Student 1" exists in the "Search users" search combo box And I confirm "Turtle Manatee" exists in the "Search users" search combo box # Search on the department field. And I set the field "Search users" to "ABC3" And I wait until "User Example" "list_item" does not exist And I confirm "User Test" exists in the "Search users" search combo box And I confirm "Turtle Manatee" exists in the "Search users" search combo box # Search on the phone1 field. And I set the field "Search users" to "4365899871" And I wait until "User Test" "list_item" does not exist And I confirm "Dummy User" exists in the "Search users" search combo box # Search on the phone2 field. And I set the field "Search users" to "2149871323" And I wait until "Dummy User" "list_item" does not exist And I confirm "User Test" exists in the "Search users" search combo box # Search on the institution field then press enter to show the record set. And I set the field "Search users" to "ABC" And "Turtle Manatee" "list_item" should exist And I confirm "Dummy User" exists in the "Search users" search combo box And I confirm "User Example" exists in the "Search users" search combo box And I confirm "User Test" exists in the "Search users" search combo box And I confirm "Student 1" exists in the "Search users" search combo box And I press the down key And I press the enter key And I wait until the page is ready And "Student 1" "heading" should exist And "User Example" "heading" should not exist And "User Test" "heading" should not exist And "Dummy User" "heading" should not exist And "Turtle Manatee" "heading" should not exist And "Teacher 1" "heading" should not exist @accessibility Scenario: A teacher can set focus and search using the input are with a keyboard Given I set the field "Search users" to "ABC" # Basic tests for the page. And the page should meet accessibility standards And the page should meet "wcag131, wcag141, wcag412" accessibility standards And the page should meet accessibility standards with "wcag131, wcag141, wcag412" extra tests # Move onto general keyboard navigation testing. When I wait until "Turtle Manatee" "option_role" exists And I press the down key And ".active" "css_element" should exist in the "Student 1" "option_role" And I press the up key And ".active" "css_element" should exist in the "Dummy User" "option_role" And I press the down key And ".active" "css_element" should exist in the "Student 1" "option_role" And I press the escape key And the focused element is "Search users" "field" Then I set the field "Search users" to "Goodmeme" And I press the down key And the focused element is "Search users" "field" And I navigate to "View > Single view" in the course gradebook And I click on "Users" "link" in the ".page-toggler" "css_element" And I set the field "Search users" to "ABC" And I wait until "Turtle Manatee" "option_role" exists And I press the down key And ".active" "css_element" should exist in the "Student 1" "option_role" # Lets check the tabbing order. And I set the field "Search users" to "ABC" And I wait until "Turtle Manatee" "option_role" exists And I press the tab key And the focused element is "Clear search input" "button" in the ".user-search" "css_element" And I press the tab key And the focused element is not "Search users" "field" # Ensure we can interact with the clear search with the keyboard. # Sometimes with behat we get unattached nodes causing spurious failures. And I set the field "Search users" to "ABC" And I wait "1" seconds And I press the tab key And the focused element is "Clear search input" "button" in the ".user-search" "css_element" And I press the enter key And I confirm "Turtle Manatee" does not exist in the "Search users" search combo box Scenario: A teacher can clear the user search. # A teacher searches for and selects a specific user. Given I click on "Turtle" in the "Search users" search combo box And "Turtle Manatee" "heading" should exist When I click on "Clear" "link" # Page reloads with search field cleared and no student details displayed (empty state). And I wait until the page is ready Then the field "Search users" matches value "" And "Turtle Manatee" "heading" should not exist tests/behat/singleview.feature000064400000037456152266565660012564 0ustar00@core @core_grades @gradereport_singleview @javascript Feature: We can use Single view As a teacher In order to view and edit grades For users and activities for a course. Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | idnumber | middlename | alternatename | firstnamephonetic | lastnamephonetic | | teacher1 | Teacher | 1 | teacher1@example.com | t1 | | fred | | | | teacher2 | No edit | 1 | teacher2@example.com | t2 | | nick | | | | teacher3 | Teacher | 3 | teacher3@example.com | t3 | | jack | | | | student1 | Grainne | Beauchamp | student1@example.com | s1 | Ann | Jill | Gronya | Beecham | | student2 | Niamh | Cholmondely | student2@example.com | s2 | Jane | Nina | Nee | Chumlee | | student3 | Siobhan | Desforges | student3@example.com | s3 | Sarah | Sev | Shevon | De-forjay | | student4 | Student | 4 | student4@example.com | s4 | | zac | | | And the following "scales" exist: | name | scale | | Test Scale | Disappointing, Good, Very good, Excellent | And the following "grade items" exist: | itemname | course | gradetype | scale | | new grade item 1 | C1 | Scale | Test Scale | And the following "scales" exist: | name | scale | | Test Scale | Disappointing, Good, Very good, Excellent | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | teacher2 | C1 | teacher | | teacher3 | C1 | teacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | | student4 | C1 | student | And the following "grade categories" exist: | fullname | course | | Sub category 1 | C1| | Sub category 2 | C1| And the following "activities" exist: | activity | course | idnumber | name | intro | grade | | assign | C1 | a1 | Test assignment one | Submit something! | 300 | | assign | C1 | a2 | Test assignment two | Submit something! | 100 | | assign | C1 | a3 | Test assignment three | Submit something! | 150 | | assign | C1 | a4 | Test assignment four | Submit nothing! | 150 | And the following "grade items" exist: | itemname | course | gradetype | | Test grade item | C1 | Scale | And the following "permission overrides" exist: | capability | permission | role | contextlevel | reference | | moodle/grade:edit | Allow | teacher | Course | C1 | | gradereport/singleview:view | Allow | teacher | Course | C1 | And the following config values are set as admin: | fullnamedisplay | firstnamephonetic,lastnamephonetic | | alternativefullnameformat | middlename, alternatename, firstname, lastname | And I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1" Scenario: I can update grades, add feedback and exclude grades. Given I navigate to "View > Single view" in the course gradebook And I click on "Users" "link" in the ".page-toggler" "css_element" And I click on "Student" in the "Search users" search combo box And I turn editing mode on And I set the field "Override for Test assignment one" to "1" When I set the following fields to these values: | Grade for Test assignment one | 10.00 | | Feedback for Test assignment one | test data | And I set the field "Exclude for Test assignment four" to "1" And I press "Save" Then I should see "Grades were set for 2 items" And the field "Exclude for Test assignment four" matches value "1" And the field "Grade for Test assignment one" matches value "10.00" And I set the following fields to these values: | Test grade item | 45 | And I press "Save" Then I should see "Grades were set for 1 items" And the field "Grade for Test grade item" matches value "45.00" And the field "Grade for Course total" matches value "55.00" And I open the action menu in "Test assignment three" "table_row" And I choose "Show all grades" in the open action menu And I click on "Override for Ann, Jill, Grainne, Beauchamp" "checkbox" And I set the following fields to these values: | Grade for Ann, Jill, Grainne, Beauchamp | 12.05 | | Feedback for Ann, Jill, Grainne, Beauchamp | test data2 | And I set the field "Exclude for Jane, Nina, Niamh, Cholmondely" to "1" And I press "Save" Then I should see "Grades were set for 2 items" And the field "Grade for Ann, Jill, Grainne, Beauchamp" matches value "12.05" And the field "Exclude for Jane, Nina, Niamh, Cholmondely" matches value "1" And I click on "new grade item 1" in the "Search items" search combo box And I set the field "Grade for Ann, Jill, Grainne, Beauchamp" to "Very good" And I press "Save" Then I should see "Grades were set for 1 items" And the following should exist in the "generaltable" table: | User full name | Grade | | Ann, Jill, Grainne, Beauchamp | Very good | And I am on the "Course 1" "grades > Single view > View" page logged in as "teacher2" And I click on "Users" "link" in the ".page-toggler" "css_element" And I click on "Student" in the "Search users" search combo box And I turn editing mode on And the "Exclude for Test assignment one" "checkbox" should be disabled And the "Override for Test assignment one" "checkbox" should be enabled Scenario: Single view links work on grade report. Given I click on grade item menu "Test assignment one" of type "gradeitem" on "grader" page And I choose "Single view for this item" in the open action menu And I should see "Test assignment one" When I navigate to "View > Grader report" in the course gradebook And I click on user menu "Grainne Beauchamp" And I choose "Single view for this user" in the open action menu Then I should see "Gronya,Beecham" Scenario: I can bulk update grades. Given I click on user menu "Grainne Beauchamp" And I choose "Single view for this user" in the open action menu And I should see "Gronya,Beecham" When I turn editing mode on And I click on "Actions" "link" And I click on "Bulk insert" "link" And I click on "I understand that my unsaved changes will be lost." "checkbox" And I click on "All grades" "radio" And I set the field "Insert new grade" to "1.0" And I click on "Save" "button" in the ".modal-dialog" "css_element" Then I should see "Grades were set for 6 items" Scenario: I can bulk update grades with custom decimal separator Given the following "language customisations" exist: | component | stringid | value | | core_langconfig | decsep | # | And I click on user menu "Grainne Beauchamp" And I choose "Single view for this user" in the open action menu And I should see "Gronya,Beecham" When I turn editing mode on And I click on "Actions" "link" And I click on "Bulk insert" "link" And I click on "I understand that my unsaved changes will be lost." "checkbox" And I click on "All grades" "radio" And I set the field "Insert new grade" to "1#25" And I click on "Save" "button" in the ".modal-dialog" "css_element" Then I should see "Grades were set for 6 items" # Custom scale, cast to int And the field "Grade for new grade item 1" matches value "Disappointing" # Value grade, float with custom decsep. And the field "Grade for Test assignment one" matches value "1#25" # Numerical scale, cast to int, showing as float with custom decsep. And the field "Grade for Test grade item" matches value "1#00" Scenario: Navigation works in the Single view. Given I click on user menu "Grainne Beauchamp" And I choose "Single view for this user" in the open action menu Then I should see "Gronya,Beecham" And I click on "Nee,Chumlee" "link" in the ".stickyfooter" "css_element" Then I should see "Nee,Chumlee" And I click on "Gronya,Beecham" "link" in the ".stickyfooter" "css_element" Then I should see "Gronya,Beecham" And I open the action menu in "Test assignment four" "table_row" And I choose "Show all grades" in the open action menu Then I should see "Test assignment four" And I click on "Test assignment three" in the "Search items" search combo box Then I should see "Test assignment three" And I click on "Test assignment four" in the "Search items" search combo box Then I should see "Test assignment four" Scenario: Activities are clickable only when it has a valid activity page. Given I click on user menu "Grainne Beauchamp" And I choose "Single view for this user" in the open action menu And "new grade item 1" "link" should not exist in the "//tbody//tr[position()=1]//td[position()=2]" "xpath_element" Then "Category total" "link" should not exist in the "//tbody//tr[position()=2]//td[position()=2]" "xpath_element" And "Course total" "link" should not exist in the "//tbody//tr[position()=last()]//td[position()=2]" "xpath_element" Scenario: Teacher sees his last viewed singleview report type when navigating back to the gradebook singleview report. Given I navigate to "View > Single view" in the course gradebook And I should see "Search for a user to view all their grades" in the "region-main" "region" And I click on "Grade items" "link" And I should see "Select a grade item above" in the "region-main" "region" When I am on the "Course 1" "grades > Single view > View" page Then I should see "Select a grade item above" in the "region-main" "region" And I am on the "Course 1" "grades > Single view > View" page logged in as "teacher3" And I should see "Search for a user to view all their grades" in the "region-main" "region" Scenario: Teacher sees his last viewed user report when navigating back to the gradebook singleview report. Given I navigate to "View > Single view" in the course gradebook And I click on "Gronya,Beecham" in the "Search users" search combo box And I should see "Gronya,Beecham" in the "region-main" "region" When I am on the "Course 1" "grades > Single view > View" page Then I should not see "Search for a user to view all their grades" in the "region-main" "region" And I should see "Gronya,Beecham" in the "region-main" "region" And I am on the "Course 1" "grades > Single view > View" page logged in as "teacher3" And I should see "Search for a user to view all their grades" in the "region-main" "region" Scenario: Teacher sees his last viewed grade item report when navigating back to the gradebook singleview report. Given I navigate to "View > Single view" in the course gradebook And I click on "Grade items" "link" And I click on "Test assignment one" in the "Search items" search combo box And I should see "Test assignment one" in the "region-main" "region" When I am on the "Course 1" "grades > Single view > View" page Then I should not see "Select a grade item above" in the "region-main" "region" And I should see "Test assignment one" in the "region-main" "region" And I am on the "Course 1" "grades > Single view > View" page logged in as "teacher3" And I should see "Search for a user to view all their grades" in the "region-main" "region" Scenario: Teacher sees his last viewed user report if the user is a part of the the current group. Given the following "groups" exist: | name | course | idnumber | participation | | Group 1 | C1 | G1 | 1 | And the following "group members" exist: | user | group | | student2 | G1 | And I am on the "Course 1" "course editing" page And I expand all fieldsets And I set the field "Group mode" to "Visible groups" And I press "Save and display" And I navigate to "View > Single view" in the course gradebook And I click on "Nee,Chumlee" in the "Search users" search combo box And I navigate to "View > Grader report" in the course gradebook And I click on "Group 1" in the "Search groups" search combo box When I navigate to "View > Single view" in the course gradebook Then I should see "Nee,Chumlee" in the "region-main" "region" And I should not see "Search for a user to view all their grades" in the "region-main" "region" Scenario: Teacher does not see his last viewed user report if the user is not a part of the the current group. Given the following "groups" exist: | name | course | idnumber | participation | | Group 1 | C1 | G1 | 1 | And the following "group members" exist: | user | group | | student2 | G1 | And I am on the "Course 1" "course editing" page And I expand all fieldsets And I set the field "Group mode" to "Visible groups" And I press "Save and display" And I navigate to "View > Single view" in the course gradebook And I click on "Gronya,Beecham" in the "Search users" search combo box And I navigate to "View > Grader report" in the course gradebook And I click on "Group 1" in the "Search groups" search combo box When I navigate to "View > Single view" in the course gradebook Then I should see "Search for a user to view all their grades" in the "region-main" "region" And I should not see "Gronya,Beecham" in the "region-main" "region" # edge case coverage: to ensure session variable not causing error upon second visit And I reload the page And I should see "Search for a user to view all their grades" in the "region-main" "region" Scenario: Teacher does not see his last viewed user report if that user is no longer enrolled in the course. Given I navigate to "View > Single view" in the course gradebook And I click on "Gronya,Beecham" in the "Search users" search combo box And I navigate to course participants And I click on "Unenrol" "icon" in the "Gronya,Beecham" "table_row" And I click on "Unenrol" "button" in the "Unenrol" "dialogue" When I am on the "Course 1" "grades > Single view > View" page Then I should see "Search for a user to view all their grades" in the "region-main" "region" And I should not see "Gronya,Beecham" in the "region-main" "region" # edge case coverage: to ensure session variable not causing error upon second visit And I reload the page And I should see "Search for a user to view all their grades" in the "region-main" "region" Scenario: Teacher does not see his last viewed grade item report if the item no longer exists in the course. Given I navigate to "View > Single view" in the course gradebook And I click on "Grade items" "link" And I click on "Test assignment four" in the "Search items" search combo box And I am on "Course 1" course homepage with editing mode on And I delete "Test assignment four" activity And I run all adhoc tasks When I navigate to "View > Single view" in the course gradebook Then I should see "Select a grade item above" in the "region-main" "region" And I should not see "Test grade item" in the "region-main" "region" # edge case coverage: to ensure session variable not causing error upon second visit And I reload the page And I should see "Select a grade item above" in the "region-main" "region" tests/behat/hidden_grades.feature000064400000066276152266565660013172 0ustar00@gradereport @gradereport_singleview Feature: We don't show hidden grades for users without the 'moodle/grade:viewhidden' capability on singleview report In order to show singleview report in secure way As a teacher without the 'moodle/grade:viewhidden' capability I should not see hidden grades in the singleview report Background: Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student1 | 1 | student1@example.com | | student2 | Student2 | 2 | student2@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | And the following "activities" exist: | activity | course | section | name | intro | assignsubmission_onlinetext_enabled | submissiondrafts | | assign | C1 | 1 | Test assignment name 1 | Submit your online text | 1 | 0 | | assign | C1 | 1 | Test assignment name 2 | submit your online text | 1 | 0 | | assign | C1 | 1 | Test assignment name 3 | submit your online text | 1 | 0 | # Hidden manual grade item. And the following "grade items" exist: | itemname | grademin | grademax | course | hidden | | Manual grade | 20 | 40 | C1 | 1 | And the following "grade grades" exist: | gradeitem | user | grade | | Test assignment name 1 | student1 | 80 | | Test assignment name 1 | student2 | 70 | | Test assignment name 2 | student1 | 90 | | Test assignment name 2 | student2 | 60 | | Test assignment name 3 | student1 | 10 | | Test assignment name 3 | student2 | 50 | | Manual grade | student1 | 30 | | Manual grade | student2 | 40 | And I log in as "admin" And I am on "Course 1" course homepage with editing mode on # Hide assignment 2 activity. And I open "Test assignment name 2" actions menu And I choose "Hide" in the open action menu And I navigate to "View > Grader report" in the course gradebook # Hide grade. And I click on grade menu "Test assignment name 1" for user "student1" And I choose "Hide" in the open action menu # Hide assignment 3 grade item. And I set the following settings for grade item "Test assignment name 3" of type "gradeitem" on "grader" page: | Hidden | 1 | @javascript Scenario: View singleview report containing hidden activities or grade items or grades with editing on and required capabilities Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I navigate to "View > Single view" in the course gradebook And I click on "Users" "link" in the ".page-toggler" "css_element" When I click on "Student1" in the "Search users" search combo box And the field "Grade for Test assignment name 1" matches value "80" And the field "Grade for Test assignment name 2" matches value "90" And the field "Grade for Test assignment name 3" matches value "10" And the field "Grade for Manual grade" matches value "30" And the field "Grade for Course total" matches value "210" And "Hidden" "icon" should exist in the "Test assignment name 1" "table_row" And "Hidden" "icon" should exist in the "Test assignment name 2" "table_row" And "Hidden" "icon" should exist in the "Test assignment name 3" "table_row" And "Hidden" "icon" should exist in the "Manual grade" "table_row" And "Hidden" "icon" should not exist in the "Course total" "table_row" And the field "Feedback for Test assignment name 1" matches value "" And the field "Feedback for Test assignment name 2" matches value "" And the field "Feedback for Test assignment name 3" matches value "" And the field "Feedback for Manual grade" matches value "" And the field "Feedback for Course total" matches value "" And "Override for Test assignment name 1" "checkbox" should exist in the "Test assignment name 1" "table_row" And "Override for Test assignment name 2" "checkbox" should exist in the "Test assignment name 2" "table_row" And "Override for Test assignment name 3" "checkbox" should exist in the "Test assignment name 3" "table_row" And "Override for Manual grade" "checkbox" should not exist in the "Manual grade" "table_row" And "Override for Course total" "checkbox" should exist in the "Course total" "table_row" And "Exclude for Test assignment name 1" "checkbox" should exist in the "Test assignment name 1" "table_row" And "Exclude for Test assignment name 2" "checkbox" should exist in the "Test assignment name 2" "table_row" And "Exclude for Test assignment name 3" "checkbox" should exist in the "Test assignment name 3" "table_row" And "Exclude for Manual grade" "checkbox" should exist in the "Manual grade" "table_row" And "Exclude for Course total" "checkbox" should exist in the "Course total" "table_row" And I click on "Student2" in the "Search users" search combo box And the field "Grade for Test assignment name 1" matches value "70" And the field "Grade for Test assignment name 2" matches value "60" And the field "Grade for Test assignment name 3" matches value "50" And the field "Grade for Manual grade" matches value "40" And the field "Grade for Course total" matches value "220" And "Hidden" "icon" should not exist in the "Test assignment name 1" "table_row" And "Hidden" "icon" should exist in the "Test assignment name 2" "table_row" And "Hidden" "icon" should exist in the "Test assignment name 3" "table_row" And "Hidden" "icon" should exist in the "Manual grade" "table_row" And "Hidden" "icon" should not exist in the "Course total" "table_row" And the field "Feedback for Test assignment name 1" matches value "" And the field "Feedback for Test assignment name 2" matches value "" And the field "Feedback for Test assignment name 3" matches value "" And the field "Feedback for Manual grade" matches value "" And the field "Feedback for Course total" matches value "" And "Override for Test assignment name 1" "checkbox" should exist in the "Test assignment name 1" "table_row" And "Override for Test assignment name 2" "checkbox" should exist in the "Test assignment name 2" "table_row" And "Override for Test assignment name 3" "checkbox" should exist in the "Test assignment name 3" "table_row" And "Override for Manual grade" "checkbox" should not exist in the "Manual grade" "table_row" And "Override for Course total" "checkbox" should exist in the "Course total" "table_row" And "Exclude for Test assignment name 1" "checkbox" should exist in the "Test assignment name 1" "table_row" And "Exclude for Test assignment name 2" "checkbox" should exist in the "Test assignment name 2" "table_row" And "Exclude for Test assignment name 3" "checkbox" should exist in the "Test assignment name 3" "table_row" And "Exclude for Manual grade" "checkbox" should exist in the "Manual grade" "table_row" And "Exclude for Course total" "checkbox" should exist in the "Course total" "table_row" And I click on "Grade items" "link" in the ".page-toggler" "css_element" And I click on "Test assignment name 1" in the "Search items" search combo box And the field "Grade for Student1 1" matches value "80" And the field "Grade for Student2 2" matches value "70" And "Hidden" "icon" should exist in the "Student1 1" "table_row" And "Hidden" "icon" should not exist in the "Student2 2" "table_row" And the field "Feedback for Student1 1" matches value "" And the field "Feedback for Student2 2" matches value "" And "Override for Student1 1" "checkbox" should exist in the "Student1 1" "table_row" And "Override for Student2 2" "checkbox" should exist in the "Student2 2" "table_row" And "Exclude for Student1 1" "checkbox" should exist in the "Student1 1" "table_row" And "Exclude for Student2 2" "checkbox" should exist in the "Student2 2" "table_row" And I click on "Test assignment name 2" in the "Search items" search combo box And the field "Grade for Student1 1" matches value "90" And the field "Grade for Student2 2" matches value "60" And "Hidden" "icon" should exist in the "Student1 1" "table_row" And "Hidden" "icon" should exist in the "Student2 2" "table_row" And the field "Feedback for Student1 1" matches value "" And the field "Feedback for Student2 2" matches value "" And "Override for Student1 1" "checkbox" should exist in the "Student1 1" "table_row" And "Override for Student2 2" "checkbox" should exist in the "Student2 2" "table_row" And "Exclude for Student1 1" "checkbox" should exist in the "Student1 1" "table_row" And "Exclude for Student2 2" "checkbox" should exist in the "Student2 2" "table_row" And I click on "Test assignment name 3" in the "Search items" search combo box And the field "Grade for Student1 1" matches value "10" And the field "Grade for Student2 2" matches value "50" And "Hidden" "icon" should exist in the "Student1 1" "table_row" And "Hidden" "icon" should exist in the "Student2 2" "table_row" And the field "Feedback for Student1 1" matches value "" And the field "Feedback for Student2 2" matches value "" And "Exclude for Student1 1" "checkbox" should exist in the "Student1 1" "table_row" And "Exclude for Student2 2" "checkbox" should exist in the "Student2 2" "table_row" And "Override for Student1 1" "checkbox" should exist in the "Student1 1" "table_row" And "Override for Student2 2" "checkbox" should exist in the "Student2 2" "table_row" And I click on "Manual grade" in the "Search items" search combo box And the field "Grade for Student1 1" matches value "30" And the field "Grade for Student2 2" matches value "40" And "Hidden" "icon" should exist in the "Student1 1" "table_row" And "Hidden" "icon" should exist in the "Student2 2" "table_row" And the field "Feedback for Student1 1" matches value "" And the field "Feedback for Student2 2" matches value "" And "Override for Student1 1" "checkbox" should not exist in the "Student1 1" "table_row" And "Override for Student2 2" "checkbox" should not exist in the "Student2 2" "table_row" And "Exclude for Student1 1" "checkbox" should exist in the "Student1 1" "table_row" And "Exclude for Student2 2" "checkbox" should exist in the "Student2 2" "table_row" And I click on "Course total" in the "Search items" search combo box And the field "Grade for Student1 1" matches value "210" And the field "Grade for Student2 2" matches value "220" And "Hidden" "icon" should not exist in the "Student1 1" "table_row" And "Hidden" "icon" should not exist in the "Student2 2" "table_row" And the field "Feedback for Student1 1" matches value "" And the field "Feedback for Student2 2" matches value "" And "Override for Student1 1" "checkbox" should exist in the "Student1 1" "table_row" And "Override for Student2 2" "checkbox" should exist in the "Student2 2" "table_row" And "Exclude for Student1 1" "checkbox" should exist in the "Student1 1" "table_row" Then "Exclude for Student2 2" "checkbox" should exist in the "Student2 2" "table_row" @javascript Scenario: View singleview report containing hidden activities or grade items or grades with editing off and required capabilities Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode off And I navigate to "View > Single view" in the course gradebook And I click on "Grade items" "link" in the ".page-toggler" "css_element" And I click on "Course total" in the "Search items" search combo box And the following should exist in the "generaltable" table: | User full name | Grade | | Student1 1 | 210 | | Student2 2 | 220 | And "Hidden" "icon" should not exist in the "Student1 1" "table_row" And "Hidden" "icon" should not exist in the "Student2 2" "table_row" When I click on "Manual grade" in the "Search items" search combo box And the following should exist in the "generaltable" table: | User full name | Grade | | Student1 1 | 30 | | Student2 2 | 40 | And "Hidden" "icon" should exist in the "Student1 1" "table_row" And "Hidden" "icon" should exist in the "Student2 2" "table_row" And I click on "Test assignment name 3" in the "Search items" search combo box And the following should exist in the "generaltable" table: | User full name | Grade | | Student1 1 | 10 | | Student2 2 | 50 | And "Hidden" "icon" should exist in the "Student1 1" "table_row" And "Hidden" "icon" should exist in the "Student2 2" "table_row" And I click on "Test assignment name 2" in the "Search items" search combo box And the following should exist in the "generaltable" table: | User full name | Grade | | Student1 1 | 90 | | Student2 2 | 60 | And "Hidden" "icon" should exist in the "Student1 1" "table_row" And "Hidden" "icon" should exist in the "Student2 2" "table_row" And I click on "Test assignment name 1" in the "Search items" search combo box And the following should exist in the "generaltable" table: | User full name | Grade | | Student1 1 | 80 | | Student2 2 | 70 | And "Hidden" "icon" should exist in the "Student1 1" "table_row" And "Hidden" "icon" should not exist in the "Student2 2" "table_row" And I click on "Users" "link" in the ".page-toggler" "css_element" And I click on "Student1" in the "Search users" search combo box And the following should exist in the "generaltable" table: | Grade item | Grade | | Test assignment name 1 | 80 | | Test assignment name 2 | 90 | | Test assignment name 3 | 10 | | Manual grade | 30 | | Course total | 210 | And "Hidden" "icon" should exist in the "Test assignment name 1" "table_row" And "Hidden" "icon" should exist in the "Test assignment name 2" "table_row" And "Hidden" "icon" should exist in the "Test assignment name 3" "table_row" And "Hidden" "icon" should exist in the "Manual grade" "table_row" And "Hidden" "icon" should not exist in the "Course total" "table_row" And I click on "Student2" in the "Search users" search combo box And the following should exist in the "generaltable" table: | Grade item | Grade | | Test assignment name 1 | 70 | | Test assignment name 2 | 60 | | Test assignment name 3 | 50 | | Manual grade | 40 | | Course total | 220 | And "Hidden" "icon" should not exist in the "Test assignment name 1" "table_row" And "Hidden" "icon" should exist in the "Test assignment name 2" "table_row" And "Hidden" "icon" should exist in the "Test assignment name 3" "table_row" And "Hidden" "icon" should exist in the "Manual grade" "table_row" Then "Hidden" "icon" should not exist in the "Course total" "table_row" @javascript Scenario: View singleview report containing hidden activities or grade items or grades with editing off without required capabilities Given I log in as "teacher1" And the following "role capability" exists: | role | editingteacher | | moodle/grade:viewhidden | prohibit | And I am on "Course 1" course homepage with editing mode off And I navigate to "View > Single view" in the course gradebook When I click on "Users" "link" in the ".page-toggler" "css_element" And I click on "Student2" in the "Search users" search combo box And the following should exist in the "generaltable" table: # Total is weird!!!!!!!!!!!!!!. | Grade item | Grade | | Test assignment name 1 | 70 | | Test assignment name 2 | | | Test assignment name 3 | | | Manual grade | | | Course total | 220 | And "Hidden" "icon" should not exist in the "Test assignment name 1" "table_row" And "Hidden" "icon" should not exist in the "Test assignment name 2" "table_row" And "Hidden" "icon" should not exist in the "Test assignment name 3" "table_row" And "Hidden" "icon" should not exist in the "Manual grade" "table_row" And "Hidden" "icon" should not exist in the "Course total" "table_row" And I click on "Student1" in the "Search users" search combo box And the following should exist in the "generaltable" table: | Grade item | Grade | | Test assignment name 1 | | | Test assignment name 2 | | | Test assignment name 3 | | | Manual grade | | | Course total | 210 | And "Hidden" "icon" should not exist in the "Test assignment name 1" "table_row" And "Hidden" "icon" should not exist in the "Test assignment name 2" "table_row" And "Hidden" "icon" should not exist in the "Test assignment name 3" "table_row" And "Hidden" "icon" should not exist in the "Manual grade" "table_row" And "Hidden" "icon" should not exist in the "Course total" "table_row" And I click on "Grade items" "link" in the ".page-toggler" "css_element" And I click on "Test assignment name 1" in the "Search items" search combo box And the following should exist in the "generaltable" table: | User full name | Grade | | Student1 1 | | | Student2 2 | 70 | And "Hidden" "icon" should not exist in the "Student1 1" "table_row" And "Hidden" "icon" should not exist in the "Student2 2" "table_row" And I click on "Test assignment name 2" in the "Search items" search combo box And the following should exist in the "generaltable" table: | User full name | Grade | | Student1 1 | | | Student2 2 | | And "Hidden" "icon" should not exist in the "Student1 1" "table_row" And "Hidden" "icon" should not exist in the "Student2 2" "table_row" And I click on "Test assignment name 3" in the "Search items" search combo box And the following should exist in the "generaltable" table: | User full name | Grade | | Student1 1 | | | Student2 2 | | And "Hidden" "icon" should not exist in the "Student1 1" "table_row" And "Hidden" "icon" should not exist in the "Student2 2" "table_row" And I click on "Manual grade" in the "Search items" search combo box And the following should exist in the "generaltable" table: | User full name | Grade | | Student1 1 | | | Student2 2 | | And "Hidden" "icon" should not exist in the "Student1 1" "table_row" And "Hidden" "icon" should not exist in the "Student2 2" "table_row" And I click on "Course total" in the "Search items" search combo box And the following should exist in the "generaltable" table: | User full name | Grade | | Student1 1 | 210 | | Student2 2 | 220 | And "Hidden" "icon" should not exist in the "Student1 1" "table_row" Then "Hidden" "icon" should not exist in the "Student2 2" "table_row" @javascript Scenario: View singleview report containing hidden activities or grade items or grades with editing on without required capabilities Given I log in as "teacher1" And the following "role capability" exists: | role | editingteacher | | moodle/grade:viewhidden | prohibit | And I am on "Course 1" course homepage with editing mode on And I navigate to "View > Single view" in the course gradebook And I click on "Grade items" "link" in the ".page-toggler" "css_element" And I click on "Course total" in the "Search items" search combo box And the field "Grade for Student1 1" matches value "210" And the field "Grade for Student2 2" matches value "220" And "Hidden" "icon" should not exist in the "Student1 1" "table_row" And "Hidden" "icon" should not exist in the "Student2 2" "table_row" And the field "Feedback for Student1 1" matches value "" And the field "Feedback for Student2 2" matches value "" And "Override for Student1 1" "checkbox" should exist in the "Student1 1" "table_row" And "Override for Student2 2" "checkbox" should exist in the "Student2 2" "table_row" And "Exclude for Student1 1" "checkbox" should exist in the "Student1 1" "table_row" And "Exclude for Student2 2" "checkbox" should exist in the "Student2 2" "table_row" When I click on "Manual grade" in the "Search items" search combo box And the following should exist in the "generaltable" table: | User full name | Grade | | Student1 1 | | | Student2 2 | | And "Hidden" "icon" should not exist in the "Student1 1" "table_row" And "Hidden" "icon" should not exist in the "Student2 2" "table_row" And I click on "Test assignment name 3" in the "Search items" search combo box And the following should exist in the "generaltable" table: | User full name | Grade | | Student1 1 | | | Student2 2 | | And "Hidden" "icon" should not exist in the "Student1 1" "table_row" And "Hidden" "icon" should not exist in the "Student2 2" "table_row" And I click on "Test assignment name 2" in the "Search items" search combo box And the following should exist in the "generaltable" table: | User full name | Grade | | Student1 1 | | | Student2 2 | | And "Hidden" "icon" should not exist in the "Student1 1" "table_row" And "Hidden" "icon" should not exist in the "Student2 2" "table_row" And I click on "Test assignment name 1" in the "Search items" search combo box And the following should exist in the "generaltable" table: | User full name | Grade | | Student1 1 | | And "Hidden" "icon" should not exist in the "Student1 1" "table_row" And the field "Grade for Student2 2" matches value "70" And "Hidden" "icon" should not exist in the "Student2 2" "table_row" And the field "Feedback for Student2 2" matches value "" And "Override for Student2 2" "checkbox" should exist in the "Student2 2" "table_row" And "Exclude for Student2 2" "checkbox" should exist in the "Student2 2" "table_row" And I click on "Users" "link" in the ".page-toggler" "css_element" And I click on "Student1" in the "Search users" search combo box And the following should exist in the "generaltable" table: | Grade item | Grade | Feedback | | Test assignment name 1 | | | | Test assignment name 2 | | | | Test assignment name 3 | | | | Manual grade | | | And "Hidden" "icon" should not exist in the "Test assignment name 1" "table_row" And "Hidden" "icon" should not exist in the "Test assignment name 2" "table_row" And "Hidden" "icon" should not exist in the "Test assignment name 3" "table_row" And "Hidden" "icon" should not exist in the "Manual grade" "table_row" And "Hidden" "icon" should not exist in the "Course total" "table_row" And "Override for Test assignment name 1" "checkbox" should not exist in the "Test assignment name 1" "table_row" And "Exclude for Test assignment name 1" "checkbox" should not exist in the "Test assignment name 1" "table_row" And "Override for Test assignment name 2" "checkbox" should not exist in the "Test assignment name 2" "table_row" And "Exclude for Test assignment name 2" "checkbox" should not exist in the "Test assignment name 2" "table_row" And "Override for Test assignment name 3" "checkbox" should not exist in the "Test assignment name 3" "table_row" And "Exclude for Test assignment name 3" "checkbox" should not exist in the "Test assignment name 3" "table_row" And "Override for Manual grade" "checkbox" should not exist in the "Manual grade" "table_row" And "Exclude for Manual grade" "checkbox" should not exist in the "Manual grade" "table_row" And the field "Grade for Course total" matches value "210" And the field "Feedback for Course total" matches value "" And "Override for Course total" "checkbox" should exist in the "Course total" "table_row" And "Exclude for Course total" "checkbox" should exist in the "Course total" "table_row" And I click on "Student2" in the "Search users" search combo box And the following should exist in the "generaltable" table: | Grade item | Grade | Feedback | | Test assignment name 2 | | | | Test assignment name 3 | | | | Manual grade | | | And "Hidden" "icon" should not exist in the "Test assignment name 1" "table_row" And "Hidden" "icon" should not exist in the "Test assignment name 2" "table_row" And "Hidden" "icon" should not exist in the "Test assignment name 3" "table_row" And "Hidden" "icon" should not exist in the "Manual grade" "table_row" And "Hidden" "icon" should not exist in the "Course total" "table_row" And the field "Grade for Test assignment name 1" matches value "70" And the field "Feedback for Test assignment name 1" matches value "" And "Override for Test assignment name 1" "checkbox" should exist in the "Test assignment name 1" "table_row" And "Exclude for Test assignment name 1" "checkbox" should exist in the "Test assignment name 1" "table_row" And "Override for Test assignment name 2" "checkbox" should not exist in the "Test assignment name 2" "table_row" And "Exclude for Test assignment name 2" "checkbox" should not exist in the "Test assignment name 2" "table_row" And "Override for Test assignment name 3" "checkbox" should not exist in the "Test assignment name 3" "table_row" And "Exclude for Test assignment name 3" "checkbox" should not exist in the "Test assignment name 3" "table_row" And "Override for Manual grade" "checkbox" should not exist in the "Manual grade" "table_row" And "Exclude for Manual grade" "checkbox" should not exist in the "Manual grade" "table_row" And the field "Grade for Course total" matches value "220" And the field "Feedback for Course total" matches value "" And "Override for Course total" "checkbox" should exist in the "Course total" "table_row" Then "Exclude for Course total" "checkbox" should exist in the "Course total" "table_row" tests/behat/reportswitching.feature000064400000002707152266565660013632 0ustar00@core @core_grades @gradereport_singleview @javascript Feature: Given we land on the index page, select what type of report we wish to view. Background: Given the following "courses" exist: | fullname | shortname | category | groupmode | | Course 1 | C1 | 0 | 1 | And the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | t1 | | student1 | Student | 1 | student1@example.com | s1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And the following "activities" exist: | activity | course | idnumber | name | | assign | C1 | a1 | Test assignment one | Scenario: I switch between the two report types within singleview Given I am on the "Course 1" "grades > Single view > View" page logged in as "teacher1" And I click on "Grade items" "link" in the ".page-toggler" "css_element" And "Search users" "field" should not exist And I confirm "Test assignment one" exists in the "Search items" search combo box When I click on "Users" "link" in the ".page-toggler" "css_element" Then "Search users" "field" should exist And "Select a grade item" "combobox" should not exist js/singleview.js000064400000007645152266565660007731 0ustar00M.gradereport_singleview = {}; M.gradereport_singleview.init = function(Y) { if (this.initialised) { return; } this.initialised = true; var getColumnIndex = function(cell) { var rowNode = cell.ancestor('tr'); if (!rowNode || !cell) { return; } var cells = rowNode.all('td, th'); return cells.indexOf(cell); }, getNextCell = function(cell) { var n = cell || document.activeElement, next = n.next('td.cell, th.cell'); if (!next) { return null; } // Continue on until we find a navigable cell if (!next || !Y.one(next).one('input:not([type="hidden"]):not([disabled="DISABLED"]), select, a')) { return getNextCell(next); } return next; }, getPrevCell = function(cell) { var n = cell || document.activeElement, next = n.previous('td.cell, th.cell'); if (!next) { return null; } // Continue on until we find a navigable cell if (!Y.one(next).one('input:not([type="hidden"]):not([disabled="DISABLED"]), select, a')) { return getPrevCell(next); } return next; }, getAboveCell = function(cell) { var n = cell || document.activeElement, tr = n.ancestor('tr').previous('tr'), columnIndex = getColumnIndex(n), next = null; if (tr) { next = tr.all('td, th').item(columnIndex); } else { return null; } // Continue on until we find a navigable cell if (!Y.one(next).one('input:not([type="hidden"]):not([disabled="DISABLED"]), select, a')) { return getAboveCell(next); } return next; }, getBelowCell = function(cell) { var n = cell || document.activeElement, tr = n.ancestor('tr').next('tr'), columnIndex = getColumnIndex(n), next = null; if (tr) { next = tr.all('td, th').item(columnIndex); } else { return null; } // Continue on until we find a navigable cell if (!Y.one(next).one('input:not([type="hidden"]):not([disabled="DISABLED"]), select, a')) { return getBelowCell(next); } return next; }; // Add ctrl+arrow controls for navigation Y.one(Y.config.doc.body).delegate('key', function(e) { e.preventDefault(); e.stopPropagation(); var next = null; switch (e.keyCode) { case 37: next = getPrevCell(this.ancestor('td, th')); break; case 38: next = getAboveCell(this.ancestor('td, th')); break; case 39: next = getNextCell(this.ancestor('td, th')); break; case 40: next = getBelowCell(this.ancestor('td, th')); break; } if (next) { Y.one(next).one('input:not([type="hidden"]):not([disabled="DISABLED"]), select, a').focus(); } return; }, 'down:37,38,39,40+ctrl', 'table input, table select, table a'); // Override Toggle. Y.all('input[name^=override_]').each(function(input) { input.on('change', function() { var checked = input.getDOMNode().checked; var names = input.getAttribute('name').split("_"); var itemid = names[1]; var userid = names[2]; var interest = '_' + itemid + '_' + userid; Y.all('input[name$=' + interest + ']').filter('input[data-uielement=text]').each(function(text) { text.getDOMNode().disabled = !checked; }); // deal with scales that are not text... UCSB Y.all('select[name$=' + interest + ']').each(function(select) { select.getDOMNode().disabled = !checked; }); }); }); };