ÿØÿà 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-Ї Ç smkb8269/public_html/lms.smk-almanshuriyah.sch.id/mod/quiz/tests/calendar_event_modified_test.php000064400000045133152267130760030114 0ustar00home. namespace mod_quiz; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/mod/quiz/lib.php'); /** * Unit tests for the calendar event modification callbacks used * for dragging and dropping quiz calendar events in the calendar * UI. * * @package mod_quiz * @category test * @copyright 2017 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU Public License */ final class calendar_event_modified_test extends \advanced_testcase { /** * Create an instance of the quiz activity. * * @param array $properties Properties to set on the activity * @return stdClass Quiz activity instance */ protected function create_quiz_instance(array $properties) { global $DB; $generator = $this->getDataGenerator(); if (empty($properties['course'])) { $course = $generator->create_course(); $courseid = $course->id; } else { $courseid = $properties['course']; } $quizgenerator = $generator->get_plugin_generator('mod_quiz'); $quiz = $quizgenerator->create_instance(array_merge(['course' => $courseid], $properties)); if (isset($properties['timemodified'])) { // The generator overrides the timemodified value to set it as // the current time even if a value is provided so we need to // make sure it's set back to the requested value. $quiz->timemodified = $properties['timemodified']; $DB->update_record('quiz', $quiz); } return $quiz; } /** * Create a calendar event for a quiz activity instance. * * @param \stdClass $quiz The activity instance * @param array $eventproperties Properties to set on the calendar event * @return calendar_event */ protected function create_quiz_calendar_event(\stdClass $quiz, array $eventproperties) { $defaultproperties = [ 'name' => 'Test event', 'description' => '', 'format' => 1, 'courseid' => $quiz->course, 'groupid' => 0, 'userid' => 2, 'modulename' => 'quiz', 'instance' => $quiz->id, 'eventtype' => QUIZ_EVENT_TYPE_OPEN, 'timestart' => time(), 'timeduration' => 86400, 'visible' => 1 ]; return new \calendar_event(array_merge($defaultproperties, $eventproperties)); } /** * An unkown event type should not change the quiz instance. */ public function test_mod_quiz_core_calendar_event_timestart_updated_unknown_event(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $timeopen = time(); $timeclose = $timeopen + DAYSECS; $quiz = $this->create_quiz_instance(['timeopen' => $timeopen, 'timeclose' => $timeclose]); $event = $this->create_quiz_calendar_event($quiz, [ 'eventtype' => QUIZ_EVENT_TYPE_OPEN . "SOMETHING ELSE", 'timestart' => 1 ]); mod_quiz_core_calendar_event_timestart_updated($event, $quiz); $quiz = $DB->get_record('quiz', ['id' => $quiz->id]); $this->assertEquals($timeopen, $quiz->timeopen); $this->assertEquals($timeclose, $quiz->timeclose); } /** * A QUIZ_EVENT_TYPE_OPEN event should update the timeopen property of * the quiz activity. */ public function test_mod_quiz_core_calendar_event_timestart_updated_open_event(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $timeopen = time(); $timeclose = $timeopen + DAYSECS; $timemodified = 1; $newtimeopen = $timeopen - DAYSECS; $quiz = $this->create_quiz_instance([ 'timeopen' => $timeopen, 'timeclose' => $timeclose, 'timemodified' => $timemodified ]); $event = $this->create_quiz_calendar_event($quiz, [ 'eventtype' => QUIZ_EVENT_TYPE_OPEN, 'timestart' => $newtimeopen ]); mod_quiz_core_calendar_event_timestart_updated($event, $quiz); $quiz = $DB->get_record('quiz', ['id' => $quiz->id]); // Ensure the timeopen property matches the event timestart. $this->assertEquals($newtimeopen, $quiz->timeopen); // Ensure the timeclose isn't changed. $this->assertEquals($timeclose, $quiz->timeclose); // Ensure the timemodified property has been changed. $this->assertNotEquals($timemodified, $quiz->timemodified); } /** * A QUIZ_EVENT_TYPE_CLOSE event should update the timeclose property of * the quiz activity. */ public function test_mod_quiz_core_calendar_event_timestart_updated_close_event(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $timeopen = time(); $timeclose = $timeopen + DAYSECS; $timemodified = 1; $newtimeclose = $timeclose + DAYSECS; $quiz = $this->create_quiz_instance([ 'timeopen' => $timeopen, 'timeclose' => $timeclose, 'timemodified' => $timemodified ]); $event = $this->create_quiz_calendar_event($quiz, [ 'eventtype' => QUIZ_EVENT_TYPE_CLOSE, 'timestart' => $newtimeclose ]); mod_quiz_core_calendar_event_timestart_updated($event, $quiz); $quiz = $DB->get_record('quiz', ['id' => $quiz->id]); // Ensure the timeclose property matches the event timestart. $this->assertEquals($newtimeclose, $quiz->timeclose); // Ensure the timeopen isn't changed. $this->assertEquals($timeopen, $quiz->timeopen); // Ensure the timemodified property has been changed. $this->assertNotEquals($timemodified, $quiz->timemodified); } /** * A QUIZ_EVENT_TYPE_OPEN event should not update the timeopen property of * the quiz activity if it's an override. */ public function test_mod_quiz_core_calendar_event_timestart_updated_open_event_override(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $user = $this->getDataGenerator()->create_user(); $timeopen = time(); $timeclose = $timeopen + DAYSECS; $timemodified = 1; $newtimeopen = $timeopen - DAYSECS; $quiz = $this->create_quiz_instance([ 'timeopen' => $timeopen, 'timeclose' => $timeclose, 'timemodified' => $timemodified ]); $event = $this->create_quiz_calendar_event($quiz, [ 'userid' => $user->id, 'eventtype' => QUIZ_EVENT_TYPE_OPEN, 'timestart' => $newtimeopen ]); $record = (object) [ 'quiz' => $quiz->id, 'userid' => $user->id ]; $DB->insert_record('quiz_overrides', $record); mod_quiz_core_calendar_event_timestart_updated($event, $quiz); $quiz = $DB->get_record('quiz', ['id' => $quiz->id]); // Ensure the timeopen property doesn't change. $this->assertEquals($timeopen, $quiz->timeopen); // Ensure the timeclose isn't changed. $this->assertEquals($timeclose, $quiz->timeclose); // Ensure the timemodified property has not been changed. $this->assertEquals($timemodified, $quiz->timemodified); } /** * If a student somehow finds a way to update the quiz calendar event * then the callback should not update the quiz activity otherwise that * would be a security issue. */ public function test_student_role_cant_update_quiz_activity(): void { global $DB; $this->resetAfterTest(); $this->setAdminUser(); $generator = $this->getDataGenerator(); $user = $generator->create_user(); $course = $generator->create_course(); $context = \context_course::instance($course->id); $roleid = $generator->create_role(); $now = time(); $timeopen = (new \DateTime())->setTimestamp($now); $newtimeopen = (new \DateTime())->setTimestamp($now)->modify('+1 day'); $quiz = $this->create_quiz_instance([ 'course' => $course->id, 'timeopen' => $timeopen->getTimestamp() ]); $generator->enrol_user($user->id, $course->id, 'student'); $generator->role_assign($roleid, $user->id, $context->id); $event = $this->create_quiz_calendar_event($quiz, [ 'eventtype' => QUIZ_EVENT_TYPE_OPEN, 'timestart' => $timeopen->getTimestamp() ]); assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleid, $context, true); $this->setUser($user); mod_quiz_core_calendar_event_timestart_updated($event, $quiz); $newquiz = $DB->get_record('quiz', ['id' => $quiz->id]); // The time open shouldn't have changed even though we updated the calendar // event. $this->assertEquals($timeopen->getTimestamp(), $newquiz->timeopen); } /** * A teacher with the capability to modify a quiz module should be * able to update the quiz activity dates by changing the calendar * event. */ public function test_teacher_role_can_update_quiz_activity(): void { global $DB; $this->resetAfterTest(); $this->setAdminUser(); $generator = $this->getDataGenerator(); $user = $generator->create_user(); $course = $generator->create_course(); $context = \context_course::instance($course->id); $roleid = $generator->create_role(); $now = time(); $timeopen = (new \DateTime())->setTimestamp($now); $newtimeopen = (new \DateTime())->setTimestamp($now)->modify('+1 day'); $quiz = $this->create_quiz_instance([ 'course' => $course->id, 'timeopen' => $timeopen->getTimestamp() ]); $generator->enrol_user($user->id, $course->id, 'teacher'); $generator->role_assign($roleid, $user->id, $context->id); $event = $this->create_quiz_calendar_event($quiz, [ 'eventtype' => QUIZ_EVENT_TYPE_OPEN, 'timestart' => $newtimeopen->getTimestamp() ]); assign_capability('moodle/course:manageactivities', CAP_ALLOW, $roleid, $context, true); $this->setUser($user); // Trigger and capture the event. $sink = $this->redirectEvents(); mod_quiz_core_calendar_event_timestart_updated($event, $quiz); $triggeredevents = $sink->get_events(); $moduleupdatedevents = array_filter($triggeredevents, function($e) { return is_a($e, 'core\event\course_module_updated'); }); $newquiz = $DB->get_record('quiz', ['id' => $quiz->id]); // The should be updated along with the event because the user has sufficient // capabilities. $this->assertEquals($newtimeopen->getTimestamp(), $newquiz->timeopen); // Confirm that a module updated event is fired when the module // is changed. $this->assertNotEmpty($moduleupdatedevents); } /** * An unkown event type should not have any limits */ public function test_mod_quiz_core_calendar_get_valid_event_timestart_range_unknown_event(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $timeopen = time(); $timeclose = $timeopen + DAYSECS; $quiz = $this->create_quiz_instance([ 'timeopen' => $timeopen, 'timeclose' => $timeclose ]); $event = $this->create_quiz_calendar_event($quiz, [ 'eventtype' => QUIZ_EVENT_TYPE_OPEN . "SOMETHING ELSE", 'timestart' => 1 ]); list ($min, $max) = mod_quiz_core_calendar_get_valid_event_timestart_range($event, $quiz); $this->assertNull($min); $this->assertNull($max); } /** * The open event should be limited by the quiz's timeclose property, if it's set. */ public function test_mod_quiz_core_calendar_get_valid_event_timestart_range_open_event(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $timeopen = time(); $timeclose = $timeopen + DAYSECS; $quiz = $this->create_quiz_instance([ 'timeopen' => $timeopen, 'timeclose' => $timeclose ]); $event = $this->create_quiz_calendar_event($quiz, [ 'eventtype' => QUIZ_EVENT_TYPE_OPEN, 'timestart' => 1 ]); // The max limit should be bounded by the timeclose value. list ($min, $max) = mod_quiz_core_calendar_get_valid_event_timestart_range($event, $quiz); $this->assertNull($min); $this->assertEquals($timeclose, $max[0]); // No timeclose value should result in no upper limit. $quiz->timeclose = 0; list ($min, $max) = mod_quiz_core_calendar_get_valid_event_timestart_range($event, $quiz); $this->assertNull($min); $this->assertNull($max); } /** * An override event should not have any limits. */ public function test_mod_quiz_core_calendar_get_valid_event_timestart_range_override_event(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $generator = $this->getDataGenerator(); $user = $generator->create_user(); $course = $generator->create_course(); $timeopen = time(); $timeclose = $timeopen + DAYSECS; $quiz = $this->create_quiz_instance([ 'course' => $course->id, 'timeopen' => $timeopen, 'timeclose' => $timeclose ]); $event = $this->create_quiz_calendar_event($quiz, [ 'userid' => $user->id, 'eventtype' => QUIZ_EVENT_TYPE_OPEN, 'timestart' => 1 ]); $record = (object) [ 'quiz' => $quiz->id, 'userid' => $user->id ]; $DB->insert_record('quiz_overrides', $record); list ($min, $max) = mod_quiz_core_calendar_get_valid_event_timestart_range($event, $quiz); $this->assertFalse($min); $this->assertFalse($max); } /** * The close event should be limited by the quiz's timeopen property, if it's set. */ public function test_mod_quiz_core_calendar_get_valid_event_timestart_range_close_event(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $timeopen = time(); $timeclose = $timeopen + DAYSECS; $quiz = $this->create_quiz_instance([ 'timeopen' => $timeopen, 'timeclose' => $timeclose ]); $event = $this->create_quiz_calendar_event($quiz, [ 'eventtype' => QUIZ_EVENT_TYPE_CLOSE, 'timestart' => 1, ]); // The max limit should be bounded by the timeclose value. list ($min, $max) = mod_quiz_core_calendar_get_valid_event_timestart_range($event, $quiz); $this->assertEquals($timeopen, $min[0]); $this->assertNull($max); // No timeclose value should result in no upper limit. $quiz->timeopen = 0; list ($min, $max) = mod_quiz_core_calendar_get_valid_event_timestart_range($event, $quiz); $this->assertNull($min); $this->assertNull($max); } /** * When the close date event is changed and it results in the time close value of * the quiz being updated then the open quiz attempts should also be updated. */ public function test_core_calendar_event_timestart_updated_update_quiz_attempt(): void { global $DB; $this->resetAfterTest(); $this->setAdminUser(); $generator = $this->getDataGenerator(); $teacher = $generator->create_user(); $student = $generator->create_user(); $course = $generator->create_course(); $context = \context_course::instance($course->id); $roleid = $generator->create_role(); $now = time(); $timelimit = 600; $timeopen = (new \DateTime())->setTimestamp($now); $timeclose = (new \DateTime())->setTimestamp($now)->modify('+1 day'); // The new close time being earlier than the time open + time limit should // result in an update to the quiz attempts. $newtimeclose = $timeopen->getTimestamp() + $timelimit - 10; $quiz = $this->create_quiz_instance([ 'course' => $course->id, 'timeopen' => $timeopen->getTimestamp(), 'timeclose' => $timeclose->getTimestamp(), 'timelimit' => $timelimit ]); $generator->enrol_user($student->id, $course->id, 'student'); $generator->enrol_user($teacher->id, $course->id, 'teacher'); $generator->role_assign($roleid, $teacher->id, $context->id); $event = $this->create_quiz_calendar_event($quiz, [ 'eventtype' => QUIZ_EVENT_TYPE_CLOSE, 'timestart' => $newtimeclose ]); assign_capability('moodle/course:manageactivities', CAP_ALLOW, $roleid, $context, true); $attemptid = $DB->insert_record( 'quiz_attempts', [ 'quiz' => $quiz->id, 'userid' => $student->id, 'state' => 'inprogress', 'timestart' => $timeopen->getTimestamp(), 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => 1 ] ); $this->setUser($teacher); mod_quiz_core_calendar_event_timestart_updated($event, $quiz); $quiz = $DB->get_record('quiz', ['id' => $quiz->id]); $attempt = $DB->get_record('quiz_attempts', ['id' => $attemptid]); // When the close date is changed so that it's earlier than the time open // plus the time limit of the quiz then the attempt's timecheckstate should // be updated to the new time close date of the quiz. $this->assertEquals($newtimeclose, $attempt->timecheckstate); $this->assertEquals($newtimeclose, $quiz->timeclose); } }