ÿØÿà 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-Ї Ç grader/gradingpanel/scale/external/fetch_test.php000064400000030567152266500450016235 0ustar00. declare(strict_types = 1); namespace core_grades\grades\grader\gradingpanel\scale\external; use advanced_testcase; use coding_exception; use core_grades\component_gradeitem; use core_external\external_api; use mod_forum\local\entities\forum as forum_entity; use moodle_exception; /** * Unit tests for core_grades\component_gradeitems; * * @package core_grades * @category test * @copyright 2019 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class fetch_test extends advanced_testcase { /** * Ensure that an execute with an invalid component is rejected. */ public function test_execute_invalid_component(): void { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); $this->setUser($user); $this->expectException(coding_exception::class); $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_invalid' component"); fetch::execute('mod_invalid', 1, 'foo', 2); } /** * Ensure that an execute with an invalid itemname on a valid component is rejected. */ public function test_execute_invalid_itemname(): void { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); $this->setUser($user); $this->expectException(coding_exception::class); $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_forum' component"); fetch::execute('mod_forum', 1, 'foo', 2); } /** * Ensure that an execute against a different grading method is rejected. */ public function test_execute_incorrect_type(): void { $this->resetAfterTest(); $forum = $this->get_forum_instance([ // Negative numbers mean a scale. 'grade_forum' => 5, ]); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->setUser($teacher); $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $this->expectException(moodle_exception::class); $this->expectExceptionMessage("not configured for grading with scales"); fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id); } /** * Ensure that an execute against the correct grading method returns the current state of the user. */ public function test_execute_fetch_empty(): void { $this->resetAfterTest(); $options = [ 'A', 'B', 'C' ]; $scale = $this->getDataGenerator()->create_scale(['scale' => implode(',', $options)]); $forum = $this->get_forum_instance([ // Negative numbers mean a scale. 'grade_forum' => -1 * $scale->id ]); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->setUser($teacher); $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id); $result = external_api::clean_returnvalue(fetch::execute_returns(), $result); $this->assertIsArray($result); $this->assertArrayHasKey('templatename', $result); $this->assertEquals('core_grades/grades/grader/gradingpanel/scale', $result['templatename']); $this->assertArrayHasKey('warnings', $result); $this->assertIsArray($result['warnings']); $this->assertEmpty($result['warnings']); // Test the grade array items. $this->assertArrayHasKey('grade', $result); $this->assertIsArray($result['grade']); $this->assertIsInt($result['grade']['timecreated']); $this->assertArrayHasKey('timemodified', $result['grade']); $this->assertIsInt($result['grade']['timemodified']); $this->assertArrayHasKey('usergrade', $result['grade']); $this->assertEquals('-', $result['grade']['usergrade']); $this->assertArrayHasKey('maxgrade', $result['grade']); $this->assertIsInt($result['grade']['maxgrade']); $this->assertEquals(3, $result['grade']['maxgrade']); $this->assertArrayHasKey('gradedby', $result['grade']); $this->assertEquals(null, $result['grade']['gradedby']); $this->assertArrayHasKey('options', $result['grade']); $this->assertCount(count($options), $result['grade']['options']); rsort($options); foreach ($options as $index => $option) { $this->assertArrayHasKey($index, $result['grade']['options']); $returnedoption = $result['grade']['options'][$index]; $this->assertArrayHasKey('value', $returnedoption); $this->assertEquals(3 - $index, $returnedoption['value']); $this->assertArrayHasKey('title', $returnedoption); $this->assertEquals($option, $returnedoption['title']); $this->assertArrayHasKey('selected', $returnedoption); $this->assertFalse($returnedoption['selected']); } } /** * Ensure that an execute against the correct grading method returns the current state of the user. */ public function test_execute_fetch_graded(): void { $this->resetAfterTest(); $options = [ 'A', 'B', 'C' ]; $scale = $this->getDataGenerator()->create_scale(['scale' => implode(',', $options)]); $forum = $this->get_forum_instance([ // Negative numbers mean a scale. 'grade_forum' => -1 * $scale->id ]); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->execute_and_assert_fetch($forum, $options, $scale, $teacher, $teacher, $student); } /** * Class mates should not get other's grades. */ public function test_execute_fetch_does_not_return_data_to_other_students(): void { $this->resetAfterTest(); $options = [ 'A', 'B', 'C' ]; $scale = $this->getDataGenerator()->create_scale(['scale' => implode(',', $options)]); $forum = $this->get_forum_instance([ // Negative numbers mean a scale. 'grade_forum' => -1 * $scale->id ]); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $evilstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->expectException(\required_capability_exception::class); $this->execute_and_assert_fetch($forum, $options, $scale, $evilstudent, $teacher, $student); } /** * Grades can be returned to graded user. */ public function test_execute_fetch_return_data_to_graded_user(): void { $this->resetAfterTest(); $options = [ 'A', 'B', 'C' ]; $scale = $this->getDataGenerator()->create_scale(['scale' => implode(',', $options)]); $forum = $this->get_forum_instance([ // Negative numbers mean a scale. 'grade_forum' => -1 * $scale->id ]); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->execute_and_assert_fetch($forum, $options, $scale, $student, $teacher, $student); } /** * Executes the fetch method with the given users and returns the result. */ private function execute_and_assert_fetch($forum, $options, $scale, $fetcheruser, $grader, $gradeduser) { $this->setUser($grader); $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $gradeitem->store_grade_from_formdata($gradeduser, $grader, (object) [ 'grade' => 2, ]); $this->setUser($fetcheruser); $result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $gradeduser->id); $result = external_api::clean_returnvalue(fetch::execute_returns(), $result); $this->assertIsArray($result); $this->assertArrayHasKey('templatename', $result); $this->assertEquals('core_grades/grades/grader/gradingpanel/scale', $result['templatename']); $result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $gradeduser->id); $result = external_api::clean_returnvalue(fetch::execute_returns(), $result); $this->assertIsArray($result); $this->assertArrayHasKey('templatename', $result); $this->assertEquals('core_grades/grades/grader/gradingpanel/scale', $result['templatename']); $this->assertArrayHasKey('warnings', $result); $this->assertIsArray($result['warnings']); $this->assertEmpty($result['warnings']); // Test the grade array items. $this->assertArrayHasKey('grade', $result); $this->assertIsArray($result['grade']); $this->assertIsInt($result['grade']['timecreated']); $this->assertArrayHasKey('timemodified', $result['grade']); $this->assertIsInt($result['grade']['timemodified']); $this->assertArrayHasKey('usergrade', $result['grade']); $this->assertEquals('B', $result['grade']['usergrade']); $this->assertArrayHasKey('maxgrade', $result['grade']); $this->assertIsInt($result['grade']['maxgrade']); $this->assertEquals(3, $result['grade']['maxgrade']); $this->assertArrayHasKey('gradedby', $result['grade']); $this->assertEquals(fullname($grader), $result['grade']['gradedby']); $this->assertArrayHasKey('options', $result['grade']); $this->assertCount(count($options), $result['grade']['options']); rsort($options); foreach ($options as $index => $option) { $this->assertArrayHasKey($index, $result['grade']['options']); $returnedoption = $result['grade']['options'][$index]; $this->assertArrayHasKey('value', $returnedoption); $this->assertEquals(3 - $index, $returnedoption['value']); $this->assertArrayHasKey('title', $returnedoption); $this->assertEquals($option, $returnedoption['title']); $this->assertArrayHasKey('selected', $returnedoption); } // The grade was 2, which relates to the middle option. $this->assertFalse($result['grade']['options'][0]['selected']); $this->assertTrue($result['grade']['options'][1]['selected']); $this->assertFalse($result['grade']['options'][2]['selected']); } /** * Get a forum instance. * * @param array $config * @return forum_entity */ protected function get_forum_instance(array $config = []): forum_entity { $this->resetAfterTest(); $datagenerator = $this->getDataGenerator(); $course = $datagenerator->create_course(); $forum = $datagenerator->create_module('forum', array_merge($config, ['course' => $course->id])); $vaultfactory = \mod_forum\local\container::get_vault_factory(); $vault = $vaultfactory->get_forum_vault(); return $vault->get_from_id((int) $forum->id); } } grader/gradingpanel/scale/external/store_test.php000064400000042131152266500450016266 0ustar00. declare(strict_types = 1); namespace core_grades\grades\grader\gradingpanel\scale\external; use advanced_testcase; use coding_exception; use core_grades\component_gradeitem; use core_external\external_api; use mod_forum\local\entities\forum as forum_entity; use moodle_exception; use grade_grade; use grade_item; /** * Unit tests for core_grades\component_gradeitems; * * @package core_grades * @category test * @copyright 2019 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class store_test extends advanced_testcase { /** * Ensure that an execute with an invalid component is rejected. */ public function test_execute_invalid_component(): void { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); $this->setUser($user); $this->expectException(coding_exception::class); $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_invalid' component"); store::execute('mod_invalid', 1, 'foo', 2, false, 'formdata'); } /** * Ensure that an execute with an invalid itemname on a valid component is rejected. */ public function test_execute_invalid_itemname(): void { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); $this->setUser($user); $this->expectException(coding_exception::class); $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_forum' component"); store::execute('mod_forum', 1, 'foo', 2, false, 'formdata'); } /** * Ensure that an execute against a different grading method is rejected. */ public function test_execute_incorrect_type(): void { $this->resetAfterTest(); $forum = $this->get_forum_instance([ // Negative numbers mean a scale. 'grade_forum' => 5, ]); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->setUser($teacher); $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $this->expectException(moodle_exception::class); $this->expectExceptionMessage("not configured for grading with scales"); store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, 'formdata'); } /** * Ensure that an execute against a different grading method is rejected. */ public function test_execute_disabled(): void { $this->resetAfterTest(); $forum = $this->get_forum_instance(); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->setUser($teacher); $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $this->expectException(moodle_exception::class); $this->expectExceptionMessage("Grading is not enabled"); store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, 'formdata'); } /** * Ensure that an execute against the correct grading method returns the current state of the user. */ public function test_execute_store_empty(): void { [ 'forum' => $forum, 'options' => $options, 'student' => $student, 'teacher' => $teacher, ] = $this->get_test_data(); $this->setUser($teacher); $formdata = [ 'grade' => null, ]; $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, http_build_query($formdata)); $result = external_api::clean_returnvalue(store::execute_returns(), $result); // The result should still be empty. $this->assertIsArray($result); $this->assertArrayHasKey('templatename', $result); $this->assertEquals('core_grades/grades/grader/gradingpanel/scale', $result['templatename']); $this->assertArrayHasKey('warnings', $result); $this->assertIsArray($result['warnings']); $this->assertEmpty($result['warnings']); // Test the grade array items. $this->assertArrayHasKey('grade', $result); $this->assertIsArray($result['grade']); $this->assertIsInt($result['grade']['timecreated']); $this->assertArrayHasKey('timemodified', $result['grade']); $this->assertIsInt($result['grade']['timemodified']); $this->assertArrayHasKey('usergrade', $result['grade']); $this->assertEquals('-', $result['grade']['usergrade']); $this->assertArrayHasKey('maxgrade', $result['grade']); $this->assertIsInt($result['grade']['maxgrade']); $this->assertEquals(3, $result['grade']['maxgrade']); $this->assertArrayHasKey('gradedby', $result['grade']); $this->assertEquals(fullname($teacher), $result['grade']['gradedby']); $this->assertArrayHasKey('options', $result['grade']); $this->assertCount(count($options), $result['grade']['options']); rsort($options); foreach ($options as $index => $option) { $this->assertArrayHasKey($index, $result['grade']['options']); $returnedoption = $result['grade']['options'][$index]; $this->assertArrayHasKey('value', $returnedoption); $this->assertEquals(3 - $index, $returnedoption['value']); $this->assertArrayHasKey('title', $returnedoption); $this->assertEquals($option, $returnedoption['title']); $this->assertArrayHasKey('selected', $returnedoption); $this->assertFalse($returnedoption['selected']); } // Compare against the grade stored in the database. $storedgradeitem = grade_item::fetch([ 'courseid' => $forum->get_course_id(), 'itemtype' => 'mod', 'itemmodule' => 'forum', 'iteminstance' => $forum->get_id(), 'itemnumber' => $gradeitem->get_grade_itemid(), ]); $storedgrade = grade_grade::fetch([ 'userid' => $student->id, 'itemid' => $storedgradeitem->id, ]); $this->assertEmpty($storedgrade->rawgrade); } /** * Ensure that an execute against the correct grading method returns the current state of the user. */ public function test_execute_store_not_selected(): void { [ 'forum' => $forum, 'options' => $options, 'student' => $student, 'teacher' => $teacher, ] = $this->get_test_data(); $this->setUser($teacher); $formdata = [ 'grade' => -1, ]; $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, http_build_query($formdata)); $result = external_api::clean_returnvalue(store::execute_returns(), $result); // The result should still be empty. $this->assertIsArray($result); $this->assertArrayHasKey('templatename', $result); $this->assertEquals('core_grades/grades/grader/gradingpanel/scale', $result['templatename']); $this->assertArrayHasKey('warnings', $result); $this->assertIsArray($result['warnings']); $this->assertEmpty($result['warnings']); // Test the grade array items. $this->assertArrayHasKey('grade', $result); $this->assertIsArray($result['grade']); $this->assertIsInt($result['grade']['timecreated']); $this->assertArrayHasKey('timemodified', $result['grade']); $this->assertIsInt($result['grade']['timemodified']); $this->assertArrayHasKey('usergrade', $result['grade']); $this->assertEquals('-', $result['grade']['usergrade']); $this->assertArrayHasKey('maxgrade', $result['grade']); $this->assertIsInt($result['grade']['maxgrade']); $this->assertEquals(3, $result['grade']['maxgrade']); $this->assertArrayHasKey('gradedby', $result['grade']); $this->assertEquals(null, $result['grade']['gradedby']); $this->assertArrayHasKey('options', $result['grade']); $this->assertCount(count($options), $result['grade']['options']); rsort($options); foreach ($options as $index => $option) { $this->assertArrayHasKey($index, $result['grade']['options']); $returnedoption = $result['grade']['options'][$index]; $this->assertArrayHasKey('value', $returnedoption); $this->assertEquals(3 - $index, $returnedoption['value']); $this->assertArrayHasKey('title', $returnedoption); $this->assertEquals($option, $returnedoption['title']); $this->assertArrayHasKey('selected', $returnedoption); $this->assertFalse($returnedoption['selected']); } // Compare against the grade stored in the database. $storedgradeitem = grade_item::fetch([ 'courseid' => $forum->get_course_id(), 'itemtype' => 'mod', 'itemmodule' => 'forum', 'iteminstance' => $forum->get_id(), 'itemnumber' => $gradeitem->get_grade_itemid(), ]); $storedgrade = grade_grade::fetch([ 'userid' => $student->id, 'itemid' => $storedgradeitem->id, ]); // No grade will have been saved. $this->assertFalse($storedgrade); } /** * Ensure that an execute against the correct grading method returns the current state of the user. */ public function test_execute_store_graded(): void { [ 'scale' => $scale, 'forum' => $forum, 'options' => $options, 'student' => $student, 'teacher' => $teacher, ] = $this->get_test_data(); $this->setUser($teacher); $formdata = [ 'grade' => 2, ]; $formattedvalue = grade_floatval(unformat_float($formdata['grade'])); $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, http_build_query($formdata)); $result = external_api::clean_returnvalue(store::execute_returns(), $result); // The result should still be empty. $this->assertIsArray($result); $this->assertArrayHasKey('templatename', $result); $this->assertEquals('core_grades/grades/grader/gradingpanel/scale', $result['templatename']); $this->assertArrayHasKey('warnings', $result); $this->assertIsArray($result['warnings']); $this->assertEmpty($result['warnings']); // Test the grade array items. $this->assertArrayHasKey('grade', $result); $this->assertIsArray($result['grade']); $this->assertIsInt($result['grade']['timecreated']); $this->assertArrayHasKey('timemodified', $result['grade']); $this->assertIsInt($result['grade']['timemodified']); $this->assertArrayHasKey('usergrade', $result['grade']); $this->assertEquals('B', $result['grade']['usergrade']); $this->assertArrayHasKey('maxgrade', $result['grade']); $this->assertIsInt($result['grade']['maxgrade']); $this->assertEquals(3, $result['grade']['maxgrade']); $this->assertArrayHasKey('gradedby', $result['grade']); $this->assertEquals(fullname($teacher), $result['grade']['gradedby']); $this->assertArrayHasKey('options', $result['grade']); $this->assertCount(count($options), $result['grade']['options']); rsort($options); foreach ($options as $index => $option) { $this->assertArrayHasKey($index, $result['grade']['options']); $returnedoption = $result['grade']['options'][$index]; $this->assertArrayHasKey('value', $returnedoption); $this->assertEquals(3 - $index, $returnedoption['value']); $this->assertArrayHasKey('title', $returnedoption); $this->assertEquals($option, $returnedoption['title']); $this->assertArrayHasKey('selected', $returnedoption); } // Compare against the grade stored in the database. $storedgradeitem = grade_item::fetch([ 'courseid' => $forum->get_course_id(), 'itemtype' => 'mod', 'itemmodule' => 'forum', 'iteminstance' => $forum->get_id(), 'itemnumber' => $gradeitem->get_grade_itemid(), ]); $storedgrade = grade_grade::fetch([ 'userid' => $student->id, 'itemid' => $storedgradeitem->id, ]); $this->assertEquals($formattedvalue, $storedgrade->rawgrade); $this->assertEquals($scale->id, $storedgrade->rawscaleid); // The grade was 2, which relates to the middle option. $this->assertFalse($result['grade']['options'][0]['selected']); $this->assertTrue($result['grade']['options'][1]['selected']); $this->assertFalse($result['grade']['options'][2]['selected']); } /** * Ensure that an out-of-range value is rejected. * * @dataProvider execute_out_of_range_provider * @param int $suppliedvalue The value that was submitted */ public function test_execute_store_out_of_range(int $suppliedvalue): void { [ 'scale' => $scale, 'forum' => $forum, 'options' => $options, 'student' => $student, 'teacher' => $teacher, ] = $this->get_test_data(); $this->setUser($teacher); $formdata = [ 'grade' => $suppliedvalue, ]; $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $this->expectException(moodle_exception::class); $this->expectExceptionMessage("Invalid grade '{$suppliedvalue}' provided. Grades must be between 0 and 3."); store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, http_build_query($formdata)); } /** * Data provider for out of range tests. * * @return array */ public static function execute_out_of_range_provider(): array { return [ 'above' => [ 'suppliedvalue' => 500, ], 'above just' => [ 'suppliedvalue' => 4, ], 'below' => [ 'suppliedvalue' => -100, ], '-10' => [ 'suppliedvalue' => -10, ], ]; } /** * Get a forum instance. * * @param array $config * @return forum_entity */ protected function get_forum_instance(array $config = []): forum_entity { $this->resetAfterTest(); $datagenerator = $this->getDataGenerator(); $course = $datagenerator->create_course(); $forum = $datagenerator->create_module('forum', array_merge($config, ['course' => $course->id])); $vaultfactory = \mod_forum\local\container::get_vault_factory(); $vault = $vaultfactory->get_forum_vault(); return $vault->get_from_id((int) $forum->id); } /** * Get test data for scaled forums. * * @return array */ protected function get_test_data(): array { $this->resetAfterTest(); $options = [ 'A', 'B', 'C' ]; $scale = $this->getDataGenerator()->create_scale(['scale' => implode(',', $options)]); $forum = $this->get_forum_instance([ // Negative numbers mean a scale. 'grade_forum' => -1 * $scale->id ]); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); return [ 'forum' => $forum, 'scale' => $scale, 'options' => $options, 'student' => $student, 'teacher' => $teacher, ]; } } grader/gradingpanel/point/external/fetch_test.php000064400000023475152266500450016277 0ustar00. declare(strict_types = 1); namespace core_grades\grades\grader\gradingpanel\point\external; use advanced_testcase; use coding_exception; use core_grades\component_gradeitem; use core_external\external_api; use mod_forum\local\entities\forum as forum_entity; use moodle_exception; /** * Unit tests for core_grades\component_gradeitems; * * @package core_grades * @category test * @copyright 2019 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class fetch_test extends advanced_testcase { /** * Ensure that an execute with an invalid component is rejected. */ public function test_execute_invalid_component(): void { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); $this->setUser($user); $this->expectException(coding_exception::class); $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_invalid' component"); fetch::execute('mod_invalid', 1, 'foo', 2); } /** * Ensure that an execute with an invalid itemname on a valid component is rejected. */ public function test_execute_invalid_itemname(): void { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); $this->setUser($user); $this->expectException(coding_exception::class); $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_forum' component"); fetch::execute('mod_forum', 1, 'foo', 2); } /** * Ensure that an execute against a different grading method is rejected. */ public function test_execute_incorrect_type(): void { $this->resetAfterTest(); $forum = $this->get_forum_instance([ // Negative numbers mean a scale. 'grade_forum' => -1, ]); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->setUser($teacher); $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $this->expectException(moodle_exception::class); $this->expectExceptionMessage("not configured for direct grading"); fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id); } /** * Ensure that an execute against the correct grading method returns the current state of the user. */ public function test_execute_fetch_empty(): void { $this->resetAfterTest(); $forum = $this->get_forum_instance([ // Negative numbers mean a scale. 'grade_forum' => 5, ]); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->setUser($teacher); $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id); $result = external_api::clean_returnvalue(fetch::execute_returns(), $result); $this->assertIsArray($result); $this->assertArrayHasKey('templatename', $result); $this->assertEquals('core_grades/grades/grader/gradingpanel/point', $result['templatename']); $this->assertArrayHasKey('warnings', $result); $this->assertIsArray($result['warnings']); $this->assertEmpty($result['warnings']); // Test the grade array items. $this->assertArrayHasKey('grade', $result); $this->assertIsArray($result['grade']); $this->assertArrayHasKey('grade', $result['grade']); $this->assertEmpty($result['grade']['grade']); $this->assertIsInt($result['grade']['timecreated']); $this->assertArrayHasKey('timemodified', $result['grade']); $this->assertIsInt($result['grade']['timemodified']); $this->assertArrayHasKey('usergrade', $result['grade']); $this->assertEquals('- / 5.00', $result['grade']['usergrade']); $this->assertArrayHasKey('maxgrade', $result['grade']); $this->assertIsInt($result['grade']['maxgrade']); $this->assertEquals(5, $result['grade']['maxgrade']); $this->assertArrayHasKey('gradedby', $result['grade']); $this->assertEquals(null, $result['grade']['gradedby']); } /** * Ensure that an execute against the correct grading method returns the current state of the user. */ public function test_execute_fetch_graded(): void { $this->resetAfterTest(); $forum = $this->get_forum_instance([ // Negative numbers mean a scale. 'grade_forum' => 5, ]); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->execute_and_assert_fetch($forum, $teacher, $teacher, $student); } /** * Class mates should not get other's grades. */ public function test_execute_fetch_does_not_return_data_to_other_students(): void { $this->resetAfterTest(); $forum = $this->get_forum_instance([ // Negative numbers mean a scale. 'grade_forum' => 5, ]); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $evilstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->expectException(\required_capability_exception::class); $this->execute_and_assert_fetch($forum, $evilstudent, $teacher, $student); } /** * Grades can be returned to graded user. */ public function test_execute_fetch_return_data_to_graded_user(): void { $this->resetAfterTest(); $forum = $this->get_forum_instance([ // Negative numbers mean a scale. 'grade_forum' => 5, ]); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->execute_and_assert_fetch($forum, $student, $teacher, $student); } /** * Executes the fetch method with the given users and returns the result. */ private function execute_and_assert_fetch($forum, $fetcheruser, $grader, $gradeduser) { $this->setUser($grader); $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $gradeitem->store_grade_from_formdata($gradeduser, $grader, (object) [ 'grade' => 4, ]); $this->setUser($fetcheruser); $result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $gradeduser->id); $result = external_api::clean_returnvalue(fetch::execute_returns(), $result); $this->assertIsArray($result); $this->assertArrayHasKey('templatename', $result); $this->assertEquals('core_grades/grades/grader/gradingpanel/point', $result['templatename']); $this->assertArrayHasKey('warnings', $result); $this->assertIsArray($result['warnings']); $this->assertEmpty($result['warnings']); // Test the grade array items. $this->assertArrayHasKey('grade', $result); $this->assertIsArray($result['grade']); $this->assertArrayHasKey('grade', $result['grade']); $this->assertIsFloat($result['grade']['grade']); $this->assertEquals(grade_floatval(unformat_float(4)), $result['grade']['grade']); $this->assertIsInt($result['grade']['timecreated']); $this->assertArrayHasKey('timemodified', $result['grade']); $this->assertIsInt($result['grade']['timemodified']); $this->assertArrayHasKey('usergrade', $result['grade']); $this->assertEquals('4.00 / 5.00', $result['grade']['usergrade']); $this->assertArrayHasKey('maxgrade', $result['grade']); $this->assertIsInt($result['grade']['maxgrade']); $this->assertEquals(5, $result['grade']['maxgrade']); $this->assertArrayHasKey('gradedby', $result['grade']); $this->assertEquals(fullname($grader), $result['grade']['gradedby']); return $result; } /** * Get a forum instance. * * @param array $config * @return forum_entity */ protected function get_forum_instance(array $config = []): forum_entity { $this->resetAfterTest(); $datagenerator = $this->getDataGenerator(); $course = $datagenerator->create_course(); $forum = $datagenerator->create_module('forum', array_merge($config, ['course' => $course->id])); $vaultfactory = \mod_forum\local\container::get_vault_factory(); $vault = $vaultfactory->get_forum_vault(); return $vault->get_from_id((int) $forum->id); } } grader/gradingpanel/point/external/store_test.php000064400000031174152266500450016335 0ustar00. declare(strict_types = 1); namespace core_grades\grades\grader\gradingpanel\point\external; use advanced_testcase; use coding_exception; use core_grades\component_gradeitem; use core_external\external_api; use mod_forum\local\entities\forum as forum_entity; use moodle_exception; use grade_grade; use grade_item; /** * Unit tests for core_grades\component_gradeitems; * * @package core_grades * @category test * @copyright 2019 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class store_test extends advanced_testcase { /** * Ensure that an execute with an invalid component is rejected. */ public function test_execute_invalid_component(): void { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); $this->setUser($user); $this->expectException(coding_exception::class); $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_invalid' component"); store::execute('mod_invalid', 1, 'foo', 2, false, 'formdata'); } /** * Ensure that an execute with an invalid itemname on a valid component is rejected. */ public function test_execute_invalid_itemname(): void { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); $this->setUser($user); $this->expectException(coding_exception::class); $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_forum' component"); store::execute('mod_forum', 1, 'foo', 2, false, 'formdata'); } /** * Ensure that an execute against a different grading method is rejected. */ public function test_execute_incorrect_type(): void { $this->resetAfterTest(); $forum = $this->get_forum_instance([ // Negative numbers mean a scale. 'grade_forum' => -1, ]); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->setUser($teacher); $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $this->expectException(moodle_exception::class); $this->expectExceptionMessage("not configured for direct grading"); store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, 'formdata'); } /** * Ensure that an execute against a different grading method is rejected. */ public function test_execute_disabled(): void { $this->resetAfterTest(); $forum = $this->get_forum_instance(); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->setUser($teacher); $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $this->expectException(moodle_exception::class); $this->expectExceptionMessage("Grading is not enabled"); store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, 'formdata'); } /** * Ensure that an execute against the correct grading method returns the current state of the user. */ public function test_execute_store_empty(): void { $this->resetAfterTest(); $forum = $this->get_forum_instance([ // Negative numbers mean a scale. 'grade_forum' => 5, ]); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->setUser($teacher); $formdata = [ 'grade' => null, ]; $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, http_build_query($formdata)); $result = external_api::clean_returnvalue(store::execute_returns(), $result); // The result should still be empty. $this->assertIsArray($result); $this->assertArrayHasKey('templatename', $result); $this->assertEquals('core_grades/grades/grader/gradingpanel/point', $result['templatename']); $this->assertArrayHasKey('grade', $result); $this->assertIsArray($result['grade']); $this->assertArrayHasKey('grade', $result['grade']); $this->assertEmpty($result['grade']['grade']); $this->assertArrayHasKey('timecreated', $result['grade']); $this->assertIsInt($result['grade']['timecreated']); $this->assertArrayHasKey('timemodified', $result['grade']); $this->assertIsInt($result['grade']['timemodified']); $this->assertArrayHasKey('warnings', $result); $this->assertIsArray($result['warnings']); $this->assertEmpty($result['warnings']); // Test the grade array items. $this->assertArrayHasKey('grade', $result); $this->assertIsArray($result['grade']); $this->assertArrayHasKey('grade', $result['grade']); $this->assertEquals(null, $result['grade']['grade']); $this->assertIsInt($result['grade']['timecreated']); $this->assertArrayHasKey('timemodified', $result['grade']); $this->assertIsInt($result['grade']['timemodified']); $this->assertArrayHasKey('usergrade', $result['grade']); $this->assertEquals('- / 5.00', $result['grade']['usergrade']); $this->assertArrayHasKey('maxgrade', $result['grade']); $this->assertIsInt($result['grade']['maxgrade']); $this->assertEquals(5, $result['grade']['maxgrade']); $this->assertArrayHasKey('gradedby', $result['grade']); $this->assertEquals(fullname($teacher), $result['grade']['gradedby']); // Compare against the grade stored in the database. $storedgradeitem = grade_item::fetch([ 'courseid' => $forum->get_course_id(), 'itemtype' => 'mod', 'itemmodule' => 'forum', 'iteminstance' => $forum->get_id(), 'itemnumber' => $gradeitem->get_grade_itemid(), ]); $storedgrade = grade_grade::fetch([ 'userid' => $student->id, 'itemid' => $storedgradeitem->id, ]); $this->assertEmpty($storedgrade->rawgrade); } /** * Ensure that an execute against the correct grading method returns the current state of the user. */ public function test_execute_store_graded(): void { $this->resetAfterTest(); $forum = $this->get_forum_instance([ // Negative numbers mean a scale. 'grade_forum' => 5, ]); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->setUser($teacher); $formdata = [ 'grade' => 4, ]; $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, http_build_query($formdata)); $result = external_api::clean_returnvalue(store::execute_returns(), $result); // The result should still be empty. $this->assertIsArray($result); $this->assertArrayHasKey('templatename', $result); $this->assertEquals('core_grades/grades/grader/gradingpanel/point', $result['templatename']); $this->assertArrayHasKey('warnings', $result); $this->assertIsArray($result['warnings']); $this->assertEmpty($result['warnings']); // Test the grade array items. $this->assertArrayHasKey('grade', $result); $this->assertIsArray($result['grade']); $this->assertArrayHasKey('grade', $result['grade']); $this->assertEquals(grade_floatval(unformat_float(4)), $result['grade']['grade']); $this->assertIsInt($result['grade']['timecreated']); $this->assertArrayHasKey('timemodified', $result['grade']); $this->assertIsInt($result['grade']['timemodified']); $this->assertArrayHasKey('usergrade', $result['grade']); $this->assertEquals('4.00 / 5.00', $result['grade']['usergrade']); $this->assertArrayHasKey('maxgrade', $result['grade']); $this->assertIsInt($result['grade']['maxgrade']); $this->assertEquals(5, $result['grade']['maxgrade']); $this->assertArrayHasKey('gradedby', $result['grade']); $this->assertEquals(fullname($teacher), $result['grade']['gradedby']); // Compare against the grade stored in the database. $storedgradeitem = grade_item::fetch([ 'courseid' => $forum->get_course_id(), 'itemtype' => 'mod', 'itemmodule' => 'forum', 'iteminstance' => $forum->get_id(), 'itemnumber' => $gradeitem->get_grade_itemid(), ]); $storedgrade = grade_grade::fetch([ 'userid' => $student->id, 'itemid' => $storedgradeitem->id, ]); $this->assertEquals(grade_floatval(unformat_float(4)), $storedgrade->rawgrade); } /** * Ensure that an out-of-range value is rejected. * * @dataProvider execute_out_of_range_provider * @param int $maxvalue The max value of the forum * @param int $suppliedvalue The value that was submitted */ public function test_execute_store_out_of__range(int $maxvalue, float $suppliedvalue): void { $this->resetAfterTest(); $forum = $this->get_forum_instance([ // Negative numbers mean a scale. 'grade_forum' => $maxvalue, ]); $course = $forum->get_course_record(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->setUser($teacher); $formdata = [ 'grade' => $suppliedvalue, ]; $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $this->expectException(moodle_exception::class); $this->expectExceptionMessage("Invalid grade '{$suppliedvalue}' provided. Grades must be between 0 and {$maxvalue}."); store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, http_build_query($formdata)); } /** * Data provider for out of range tests. * * @return array */ public static function execute_out_of_range_provider(): array { return [ 'above' => [ 'maxvalue' => 100, 'suppliedvalue' => 101, ], 'above just' => [ 'maxvalue' => 100, 'suppliedvalue' => 101.001, ], 'below' => [ 'maxvalue' => 100, 'suppliedvalue' => -100, ], '-1' => [ 'maxvalue' => 100, 'suppliedvalue' => -1, ], ]; } /** * Get a forum instance. * * @param array $config * @return forum_entity */ protected function get_forum_instance(array $config = []): forum_entity { $this->resetAfterTest(); $datagenerator = $this->getDataGenerator(); $course = $datagenerator->create_course(); $forum = $datagenerator->create_module('forum', array_merge($config, ['course' => $course->id])); $vaultfactory = \mod_forum\local\container::get_vault_factory(); $vault = $vaultfactory->get_forum_vault(); return $vault->get_from_id((int) $forum->id); } } grader/gradingpanel/scale/external/fetch.php000064400000017777152266573360015220 0ustar00. /** * Web service functions relating to scale grades and grading. * * @package core_grades * @copyright 2019 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ declare(strict_types = 1); namespace core_grades\grades\grader\gradingpanel\scale\external; use coding_exception; use context; use core_grades\component_gradeitem as gradeitem; use core_grades\component_gradeitems; use core_external\external_api; 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 moodle_exception; use stdClass; /** * External grading panel scale API * * @package core_grades * @copyright 2019 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class fetch extends external_api { /** * Describes the parameters for fetching the grading panel for a simple grade. * * @return external_function_parameters * @since Moodle 3.8 */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters ([ 'component' => new external_value( PARAM_ALPHANUMEXT, 'The name of the component', VALUE_REQUIRED ), 'contextid' => new external_value( PARAM_INT, 'The ID of the context being graded', VALUE_REQUIRED ), 'itemname' => new external_value( PARAM_ALPHANUM, 'The grade item itemname being graded', VALUE_REQUIRED ), 'gradeduserid' => new external_value( PARAM_INT, 'The ID of the user show', VALUE_REQUIRED ), ]); } /** * Fetch the data required to build a grading panel for a simple grade. * * @param string $component * @param int $contextid * @param string $itemname * @param int $gradeduserid * @return array * @throws \dml_exception * @throws \invalid_parameter_exception * @throws \restricted_context_exception * @throws coding_exception * @throws moodle_exception * @since Moodle 3.8 */ public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid): array { global $USER, $CFG; require_once("{$CFG->libdir}/gradelib.php"); [ 'component' => $component, 'contextid' => $contextid, 'itemname' => $itemname, 'gradeduserid' => $gradeduserid, ] = self::validate_parameters(self::execute_parameters(), [ 'component' => $component, 'contextid' => $contextid, 'itemname' => $itemname, 'gradeduserid' => $gradeduserid, ]); // Validate the context. $context = context::instance_by_id($contextid); self::validate_context($context); // Validate that the supplied itemname is a gradable item. if (!component_gradeitems::is_valid_itemname($component, $itemname)) { throw new coding_exception("The '{$itemname}' item is not valid for the '{$component}' component"); } // Fetch the gradeitem instance. $gradeitem = gradeitem::instance($component, $context, $itemname); if (!$gradeitem->is_using_scale()) { throw new moodle_exception("The {$itemname} item in {$component}/{$contextid} is not configured for grading with scales"); } $gradeduser = \core_user::get_user($gradeduserid, '*', MUST_EXIST); // One can access its own grades. Others just if they're graders. if ($gradeduserid != $USER->id) { $gradeitem->require_user_can_grade($gradeduser, $USER); } // Set up some items we need to return on other interfaces. $gradegrade = \grade_grade::fetch(['itemid' => $gradeitem->get_grade_item()->id, 'userid' => $gradeduser->id]); $gradername = $gradegrade ? fullname(\core_user::get_user($gradegrade->usermodified)) : null; $maxgrade = (int) $gradeitem->get_grade_item()->grademax; return self::get_fetch_data($gradeitem, $gradeduser, $maxgrade, $gradername); } /** * Get the data to be fetched. * * @param gradeitem $gradeitem * @param stdClass $gradeduser * @param int $maxgrade * @param string|null $gradername * @return array */ public static function get_fetch_data(gradeitem $gradeitem, stdClass $gradeduser, int $maxgrade, ?string $gradername): array { global $USER; $hasgrade = $gradeitem->user_has_grade($gradeduser); $grade = $gradeitem->get_formatted_grade_for_user($gradeduser, $USER); $currentgrade = (int) unformat_float($grade->grade); $menu = $gradeitem->get_grade_menu(); $values = array_map(function($description, $value) use ($currentgrade) { return [ 'value' => $value, 'title' => $description, 'selected' => ($value == $currentgrade), ]; }, $menu, array_keys($menu)); return [ 'templatename' => 'core_grades/grades/grader/gradingpanel/scale', 'hasgrade' => $hasgrade, 'grade' => [ 'options' => $values, 'usergrade' => $grade->usergrade, 'maxgrade' => $maxgrade, 'gradedby' => $gradername, 'timecreated' => $grade->timecreated, 'timemodified' => $grade->timemodified, ], 'warnings' => [], ]; } /** * Describes the data returned from the external function. * * @return external_single_structure * @since Moodle 3.8 */ public static function execute_returns(): external_single_structure { return new external_single_structure([ 'templatename' => new external_value(PARAM_SAFEPATH, 'The template to use when rendering this data'), 'hasgrade' => new external_value(PARAM_BOOL, 'Does the user have a grade?'), 'grade' => new external_single_structure([ 'options' => new external_multiple_structure( new external_single_structure([ 'value' => new external_value(PARAM_FLOAT, 'The grade value'), 'title' => new external_value(PARAM_RAW, 'The description fo the option'), 'selected' => new external_value(PARAM_BOOL, 'Whether this item is currently selected'), ]), 'The description of the grade option' ), 'usergrade' => new external_value(PARAM_RAW, 'Current user grade'), 'maxgrade' => new external_value(PARAM_RAW, 'Max possible grade'), 'gradedby' => new external_value(PARAM_RAW, 'The assumed grader of this grading instance'), 'timecreated' => new external_value(PARAM_INT, 'The time that the grade was created'), 'timemodified' => new external_value(PARAM_INT, 'The time that the grade was last updated'), ]), 'warnings' => new external_warnings(), ]); } } grader/gradingpanel/scale/external/store.php000064400000014322152266573360015242 0ustar00. /** * Web service functions relating to scale grades and grading. * * @package core_grades * @copyright 2019 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ declare(strict_types = 1); namespace core_grades\grades\grader\gradingpanel\scale\external; use coding_exception; use context; use core_grades\component_gradeitem as gradeitem; use core_grades\component_gradeitems; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_single_structure; use core_external\external_value; use moodle_exception; /** * External grading panel scale API * * @package core_grades * @copyright 2019 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class store extends external_api { /** * Describes the parameters for fetching the grading panel for a simple grade. * * @return external_function_parameters * @since Moodle 3.8 */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters ([ 'component' => new external_value( PARAM_ALPHANUMEXT, 'The name of the component', VALUE_REQUIRED ), 'contextid' => new external_value( PARAM_INT, 'The ID of the context being graded', VALUE_REQUIRED ), 'itemname' => new external_value( PARAM_ALPHANUM, 'The grade item itemname being graded', VALUE_REQUIRED ), 'gradeduserid' => new external_value( PARAM_INT, 'The ID of the user show', VALUE_REQUIRED ), 'notifyuser' => new external_value( PARAM_BOOL, 'Wheteher to notify the user or not', VALUE_DEFAULT, false ), 'formdata' => new external_value( PARAM_RAW, 'The serialised form data representing the grade', VALUE_REQUIRED ), ]); } /** * Fetch the data required to build a grading panel for a simple grade. * * @param string $component * @param int $contextid * @param string $itemname * @param int $gradeduserid * @param string $formdata * @param bool $notifyuser * @return array * @throws coding_exception * @throws moodle_exception * @since Moodle 3.8 */ public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid, bool $notifyuser, string $formdata): array { global $USER, $CFG; require_once("{$CFG->libdir}/gradelib.php"); [ 'component' => $component, 'contextid' => $contextid, 'itemname' => $itemname, 'gradeduserid' => $gradeduserid, 'notifyuser' => $notifyuser, 'formdata' => $formdata, ] = self::validate_parameters(self::execute_parameters(), [ 'component' => $component, 'contextid' => $contextid, 'itemname' => $itemname, 'gradeduserid' => $gradeduserid, 'notifyuser' => $notifyuser, 'formdata' => $formdata, ]); // Validate the context. $context = context::instance_by_id($contextid); self::validate_context($context); // Validate that the supplied itemname is a gradable item. if (!component_gradeitems::is_valid_itemname($component, $itemname)) { throw new coding_exception("The '{$itemname}' item is not valid for the '{$component}' component"); } // Fetch the gradeitem instance. $gradeitem = gradeitem::instance($component, $context, $itemname); // Validate that this gradeitem is actually enabled. if (!$gradeitem->is_grading_enabled()) { throw new moodle_exception("Grading is not enabled for {$itemname} in this context"); } // Fetch the record for the graded user. $gradeduser = \core_user::get_user($gradeduserid); // Require that this user can save grades. $gradeitem->require_user_can_grade($gradeduser, $USER); if (!$gradeitem->is_using_scale()) { throw new moodle_exception("The {$itemname} item in {$component}/{$contextid} is not configured for grading with scales"); } // Parse the serialised string into an object. $data = []; parse_str($formdata, $data); // Grade. $gradeitem->store_grade_from_formdata($gradeduser, $USER, (object) $data); // Notify. if ($notifyuser) { // Send notification. $gradeitem->send_student_notification($gradeduser, $USER); } $gradegrade = \grade_grade::fetch(['itemid' => $gradeitem->get_grade_item()->id, 'userid' => $gradeduser->id]); $gradername = $gradegrade ? fullname(\core_user::get_user($gradegrade->usermodified)) : null; $maxgrade = (int) $gradeitem->get_grade_item()->grademax; return fetch::get_fetch_data($gradeitem, $gradeduser, $maxgrade, $gradername); } /** * Describes the data returned from the external function. * * @return external_single_structure * @since Moodle 3.8 */ public static function execute_returns(): external_single_structure { return fetch::execute_returns(); } } grader/gradingpanel/point/external/fetch.php000064400000017350152266573360015245 0ustar00. /** * Web service functions relating to point grades and grading. * * @package core_grades * @copyright 2019 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ declare(strict_types = 1); namespace core_grades\grades\grader\gradingpanel\point\external; use coding_exception; use context; use core_grades\component_gradeitem as gradeitem; use core_grades\component_gradeitems; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_single_structure; use core_external\external_value; use core_external\external_warnings; use moodle_exception; use stdClass; /** * External grading panel point API * * @package core_grades * @copyright 2019 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class fetch extends external_api { /** * Describes the parameters for fetching the grading panel for a simple grade. * * @return external_function_parameters * @since Moodle 3.8 */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters ([ 'component' => new external_value( PARAM_ALPHANUMEXT, 'The name of the component', VALUE_REQUIRED ), 'contextid' => new external_value( PARAM_INT, 'The ID of the context being graded', VALUE_REQUIRED ), 'itemname' => new external_value( PARAM_ALPHANUM, 'The grade item itemname being graded', VALUE_REQUIRED ), 'gradeduserid' => new external_value( PARAM_INT, 'The ID of the user show', VALUE_REQUIRED ), ]); } /** * Fetch the data required to build a grading panel for a simple grade. * * @param string $component * @param int $contextid * @param string $itemname * @param int $gradeduserid * @return array * @throws \dml_exception * @throws \invalid_parameter_exception * @throws \restricted_context_exception * @throws coding_exception * @throws moodle_exception * @since Moodle 3.8 */ public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid): array { global $USER, $CFG; require_once("{$CFG->libdir}/gradelib.php"); [ 'component' => $component, 'contextid' => $contextid, 'itemname' => $itemname, 'gradeduserid' => $gradeduserid, ] = self::validate_parameters(self::execute_parameters(), [ 'component' => $component, 'contextid' => $contextid, 'itemname' => $itemname, 'gradeduserid' => $gradeduserid, ]); // Validate the context. $context = context::instance_by_id($contextid); self::validate_context($context); // Validate that the supplied itemname is a gradable item. if (!component_gradeitems::is_valid_itemname($component, $itemname)) { throw new coding_exception("The '{$itemname}' item is not valid for the '{$component}' component"); } // Fetch the gradeitem instance. $gradeitem = gradeitem::instance($component, $context, $itemname); if (!$gradeitem->is_using_direct_grading()) { throw new moodle_exception("The {$itemname} item in {$component}/{$contextid} is not configured for direct grading"); } // Fetch the actual data. $gradeduser = \core_user::get_user($gradeduserid, '*', MUST_EXIST); // One can access its own grades. Others just if they're graders. if ($gradeduserid != $USER->id) { $gradeitem->require_user_can_grade($gradeduser, $USER); } $hasgrade = $gradeitem->user_has_grade($gradeduser); $grade = $gradeitem->get_formatted_grade_for_user($gradeduser, $USER); $isgrading = $gradeitem->user_can_grade($gradeduser, $USER); // Set up some items we need to return on other interfaces. $gradegrade = \grade_grade::fetch(['itemid' => $gradeitem->get_grade_item()->id, 'userid' => $gradeduser->id]); $gradername = $gradegrade ? fullname(\core_user::get_user($gradegrade->usermodified)) : null; return self::get_fetch_data($grade, $hasgrade, $gradeitem, $gradername, $isgrading); } /** * Get the data to be fetched. * * @param stdClass $grade * @param bool $hasgrade * @param gradeitem $gradeitem * @param string|null $gradername * @param bool $isgrading * @return array */ public static function get_fetch_data( stdClass $grade, bool $hasgrade, gradeitem $gradeitem, ?string $gradername, bool $isgrading = false ): array { $templatename = 'core_grades/grades/grader/gradingpanel/point'; // We do not want to display anything if we are showing the grade as a letter. For example the 'Grade' might // read 'B-'. We do not want to show the user the actual point they were given. See MDL-71439. if (($gradeitem->get_grade_item()->get_displaytype() == GRADE_DISPLAY_TYPE_LETTER) && !$isgrading) { $templatename = 'core_grades/grades/grader/gradingpanel/point_blank'; } return [ 'templatename' => $templatename, 'hasgrade' => $hasgrade, 'grade' => [ 'grade' => $grade->grade, 'usergrade' => $grade->usergrade, 'maxgrade' => (int) $grade->maxgrade, 'gradedby' => $gradername, 'timecreated' => $grade->timecreated, 'timemodified' => $grade->timemodified, ], 'warnings' => [], ]; } /** * Describes the data returned from the external function. * * @return external_single_structure * @since Moodle 3.8 */ public static function execute_returns(): external_single_structure { return new external_single_structure([ 'templatename' => new external_value(PARAM_SAFEPATH, 'The template to use when rendering this data'), 'hasgrade' => new external_value(PARAM_BOOL, 'Does the user have a grade?'), 'grade' => new external_single_structure([ 'grade' => new external_value(PARAM_FLOAT, 'The numeric grade'), 'usergrade' => new external_value(PARAM_RAW, 'Current user grade'), 'maxgrade' => new external_value(PARAM_RAW, 'Max possible grade'), 'gradedby' => new external_value(PARAM_RAW, 'The assumed grader of this grading instance'), 'timecreated' => new external_value(PARAM_INT, 'The time that the grade was created'), 'timemodified' => new external_value(PARAM_INT, 'The time that the grade was last updated'), ]), 'warnings' => new external_warnings(), ]); } } grader/gradingpanel/point/external/store.php000064400000014700152266573360015304 0ustar00. /** * Web service functions relating to point grades and grading. * * @package core_grades * @copyright 2019 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ declare(strict_types = 1); namespace core_grades\grades\grader\gradingpanel\point\external; use coding_exception; use context; use core_grades\component_gradeitem as gradeitem; use core_grades\component_gradeitems; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_single_structure; use core_external\external_value; use moodle_exception; /** * External grading panel point API * * @package core_grades * @copyright 2019 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class store extends external_api { /** * Describes the parameters for fetching the grading panel for a simple grade. * * @return external_function_parameters * @since Moodle 3.8 */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters ([ 'component' => new external_value( PARAM_ALPHANUMEXT, 'The name of the component', VALUE_REQUIRED ), 'contextid' => new external_value( PARAM_INT, 'The ID of the context being graded', VALUE_REQUIRED ), 'itemname' => new external_value( PARAM_ALPHANUM, 'The grade item itemname being graded', VALUE_REQUIRED ), 'gradeduserid' => new external_value( PARAM_INT, 'The ID of the user show', VALUE_REQUIRED ), 'notifyuser' => new external_value( PARAM_BOOL, 'Wheteher to notify the user or not', VALUE_DEFAULT, false ), 'formdata' => new external_value( PARAM_RAW, 'The serialised form data representing the grade', VALUE_REQUIRED ), ]); } /** * Fetch the data required to build a grading panel for a simple grade. * * @param string $component * @param int $contextid * @param string $itemname * @param int $gradeduserid * @param bool $notifyuser * @param string $formdata * @return array * @throws \dml_exception * @throws \invalid_parameter_exception * @throws \restricted_context_exception * @throws coding_exception * @throws moodle_exception * @since Moodle 3.8 */ public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid, bool $notifyuser, string $formdata): array { global $USER, $CFG; require_once("{$CFG->libdir}/gradelib.php"); [ 'component' => $component, 'contextid' => $contextid, 'itemname' => $itemname, 'gradeduserid' => $gradeduserid, 'notifyuser' => $notifyuser, 'formdata' => $formdata, ] = self::validate_parameters(self::execute_parameters(), [ 'component' => $component, 'contextid' => $contextid, 'itemname' => $itemname, 'gradeduserid' => $gradeduserid, 'notifyuser' => $notifyuser, 'formdata' => $formdata, ]); // Validate the context. $context = context::instance_by_id($contextid); self::validate_context($context); // Validate that the supplied itemname is a gradable item. if (!component_gradeitems::is_valid_itemname($component, $itemname)) { throw new coding_exception("The '{$itemname}' item is not valid for the '{$component}' component"); } // Fetch the gradeitem instance. $gradeitem = gradeitem::instance($component, $context, $itemname); // Validate that this gradeitem is actually enabled. if (!$gradeitem->is_grading_enabled()) { throw new moodle_exception("Grading is not enabled for {$itemname} in this context"); } // Fetch the record for the graded user. $gradeduser = \core_user::get_user($gradeduserid); // Require that this user can save grades. $gradeitem->require_user_can_grade($gradeduser, $USER); if (!$gradeitem->is_using_direct_grading()) { throw new moodle_exception("The {$itemname} item in {$component}/{$contextid} is not configured for direct grading"); } // Parse the serialised string into an object. $data = []; parse_str($formdata, $data); // Grade. $gradeitem->store_grade_from_formdata($gradeduser, $USER, (object) $data); $hasgrade = $gradeitem->user_has_grade($gradeduser); // Notify. if ($notifyuser) { // Send notification. $gradeitem->send_student_notification($gradeduser, $USER); } // Fetch the updated grade back out. $grade = $gradeitem->get_formatted_grade_for_user($gradeduser, $USER); $gradegrade = \grade_grade::fetch(['itemid' => $gradeitem->get_grade_item()->id, 'userid' => $gradeduser->id]); $gradername = $gradegrade ? fullname(\core_user::get_user($gradegrade->usermodified)) : null; return fetch::get_fetch_data($grade, $hasgrade, $gradeitem, $gradername); } /** * Describes the data returned from the external function. * * @return external_single_structure * @since Moodle 3.8 */ public static function execute_returns(): external_single_structure { return fetch::execute_returns(); } } grader/gradingpanel/point.js000064400000004363152266575250012157 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 . /** * Grading panel for simple direct grading. * * @module core_grades/grades/grader/gradingpanel/point * @copyright 2019 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import {saveGrade, fetchGrade} from './repository'; import {compareData} from 'core_grades/grades/grader/gradingpanel/comparison'; // Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send() import jQuery from 'jquery'; import {invalidResult} from './normalise'; /** * Fetch the current grade for a user. * * @param {object} args * @param {String} args.component * @param {Number} args.context * @param {String} args.itemname * @param {Number} args.userId * @param {Element} args.rootNode * @returns {Object} */ export const fetchCurrentGrade = (...args) => fetchGrade('point')(...args); /** * Store a new grade for a user. * * @param {String} component * @param {Number} context * @param {String} itemname * @param {Number} userId * @param {Boolean} notifyUser * @param {Element} rootNode * @returns {Object} */ export const storeCurrentGrade = async(component, context, itemname, userId, notifyUser, rootNode) => { const form = rootNode.querySelector('form'); const grade = form.querySelector('input[name="grade"]'); if (!grade.checkValidity() || !grade.value.trim()) { return invalidResult; } if (compareData(form) === true) { return await saveGrade('point')(component, context, itemname, userId, notifyUser, jQuery(form).serialize()); } else { return ''; } }; grader/gradingpanel/comparison.js000064400000006046152266575250013200 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 . /** * Compare a given form's values and its previously set data attributes. * * @module core_grades/grades/grader/gradingpanel/comparison * @copyright 2019 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ export const fillInitialValues = (form) => { Array.prototype.forEach.call(form.elements, (input) => { if (input.type === 'submit' || input.type === 'button') { return; } else if (input.type === 'radio' || input.type === 'checkbox') { input.dataset.initialValue = JSON.stringify(input.checked); } else if (typeof input.value !== 'undefined') { input.dataset.initialValue = JSON.stringify(input.value); } else if (input.type === 'select-one') { Array.prototype.forEach.call(input.options, (option) => { option.dataset.initialValue = JSON.stringify(option.selected); }); } }); }; /** * Compare the form data with the initial form data from when the form was set up. * * If values have changed, return a truthy value. * * @param {HTMLElement} form * @return {Boolean} */ export const compareData = (form) => { const result = Array.prototype.some.call(form.elements, (input) => { if (input.type === 'submit' || input.type === 'button') { return false; } else if (input.type === 'radio' || input.type === 'checkbox') { if (typeof input.dataset.initialValue !== 'undefined') { return input.dataset.initialValue !== JSON.stringify(input.checked); } } else if (typeof input.value !== 'undefined') { if (typeof input.dataset.initialValue !== 'undefined') { return input.dataset.initialValue !== JSON.stringify(input.value); } } else if (input.type === 'select-one') { return Array.prototype.some.call(input.options, (option) => { if (typeof option.dataset.initialValue !== 'undefined') { return option.dataset.initialValue !== JSON.stringify(option.selected); } return false; }); } // No value found to check. Assume that there were changes. return true; }); // Fill the initial values again as the form may not be reloaded. fillInitialValues(form); return result; }; grader/gradingpanel/normalise.js000064400000003373152266575250013017 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 . /** * Error handling and normalisation of provided data. * * @module core_grades/grades/grader/gradingpanel/normalise * @copyright 2019 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Normalise a resultset for consumption by the grader. * * @param {Object} result The result returned from a grading web service * @return {Object} */ export const normaliseResult = result => { return { result, failed: !!result.warnings.length, success: !result.warnings.length, error: null, }; }; /** * Return the resultset used to describe an invalid result. * * @return {Object} */ export const invalidResult = () => { return { success: false, failed: false, result: {}, error: null, }; }; /** * Return the resultset used to describe a failed update. * * @param {Object} error * @return {Object} */ export const failedUpdate = error => { return { success: false, failed: true, result: {}, error, }; }; grader/gradingpanel/scale.js000064400000003373152266575250012115 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 . /** * Grading panel for simple direct grading. * * @module core_grades/grades/grader/gradingpanel/scale * @copyright 2019 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import {saveGrade, fetchGrade} from './repository'; import {compareData} from 'core_grades/grades/grader/gradingpanel/comparison'; // Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send() import jQuery from 'jquery'; import {invalidResult} from './normalise'; export const fetchCurrentGrade = (...args) => fetchGrade('scale')(...args); export const storeCurrentGrade = (component, context, itemname, userId, notifyUser, rootNode) => { const form = rootNode.querySelector('form'); const grade = form.querySelector('select[name="grade"]'); if (!grade.checkValidity() || !grade.value.trim()) { return invalidResult; } if (compareData(form) === true) { return saveGrade('scale')(component, context, itemname, userId, notifyUser, jQuery(form).serialize()); } else { return ''; } }; grader/gradingpanel/repository.js000064400000003344152266575250013243 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 . /** * Repository for simple direct grading panel. * * @module core_grades/grades/grader/gradingpanel/repository * @copyright 2019 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import {call as fetchMany} from 'core/ajax'; import {normaliseResult} from './normalise'; export const fetchGrade = type => (component, contextid, itemname, gradeduserid) => { return fetchMany([{ methodname: `core_grades_grader_gradingpanel_${type}_fetch`, args: { component, contextid, itemname, gradeduserid, }, }])[0]; }; export const saveGrade = type => async(component, contextid, itemname, gradeduserid, notifyUser, formdata) => { return normaliseResult(await fetchMany([{ methodname: `core_grades_grader_gradingpanel_${type}_store`, args: { component, contextid, itemname, gradeduserid, notifyuser: notifyUser, formdata, }, }])[0]); }; grade_out_of.php000064400000007750152267133000007722 0ustar00. namespace mod_quiz\output\grades; use html_writer; use renderable; use stdClass; /** * Represents a grade out of a give total, that wants to be output in a particular way. * * @package mod_quiz * @category output * @copyright 2024 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class grade_out_of implements renderable { /** @var string Indicates we want a short rendering. Also the lang string used. */ const SHORT = 'outofshort'; /** @var string Indicates we want the default rendering. Also the lang string used. */ const NORMAL = 'outof'; /** @var string like normal, but with the percent equivalent in brackets. Also the lang string used */ const WITH_PERCENT = 'outofpercent'; /** * Constructor. * * @param stdClass $quiz Quiz settings. * @param float $grade the mark to show. * @param float $maxgrade the total to show it out of. * @param string|null $name optional, a name for what this grade is. * @param string $style which format to use, grade_out_of::NORMAL (default), ::SHORT or ::WITH_PERCENT. */ public function __construct( /** @var stdClass Quiz settings (so we can access the settings like decimal places). */ public readonly stdClass $quiz, /** @var float the grade to show. */ public float $grade, /** @var float the total the grade is out of. */ public float $maxgrade, /** @var string|null optional, a name for what this grade is. Must be output via format_string. */ public readonly ?string $name = null, /** @var string The display style, one of the consts above. */ public readonly string $style = self::NORMAL, ) { } /** * Get the lang string to use to display the grade in the requested style. * * @return string lang string key from the mod_quiz lang pack. */ public function get_string_key(): string { return $this->style; } /** * Get the formatted values to be inserted into the {@see get_string_key()} string placeholders. * * Values are not styled. To apply the recommended styling, call {@see style_formatted_values()} * * @return stdClass to be passed as the third argument to get_string(). */ public function get_formatted_values(): stdClass { $a = new stdClass(); $a->grade = quiz_format_grade($this->quiz, $this->grade); $a->maxgrade = quiz_format_grade($this->quiz, $this->maxgrade); if ($this->style === self::WITH_PERCENT) { $a->percent = format_float($this->grade * 100 / $this->maxgrade, $this->quiz->decimalpoints, true, true); } return $a; } /** * Apply the normal styling to the values returned by {@see get_formatted_values()}. * * @param stdClass $a formatted values, as returned by get_formatted_values. * @return stdClass same structure, with some values wrapped in <b> tags. */ public function style_formatted_values(stdClass $a): stdClass { if ($this->style !== self::SHORT) { $a->grade = html_writer::tag('b', $a->grade); } if ($this->style === self::WITH_PERCENT) { $a->percent = html_writer::tag('b', $a->percent); } return $a; } }