ÿØÿà 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-Ї Ç home/smkb8269/public_html/lms.smk-almanshuriyah.sch.id/mod/quiz/attempt.php000064400000013531152267105520022627 0ustar00. /** * This script displays a particular page of a quiz attempt that is in progress. * * @package mod_quiz * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ use mod_quiz\output\navigation_panel_attempt; use mod_quiz\output\renderer; use mod_quiz\quiz_attempt; require_once(__DIR__ . '/../../config.php'); require_once($CFG->dirroot . '/mod/quiz/locallib.php'); // Look for old-style URLs, such as may be in the logs, and redirect them to startattemtp.php. if ($id = optional_param('id', 0, PARAM_INT)) { redirect($CFG->wwwroot . '/mod/quiz/startattempt.php?cmid=' . $id . '&sesskey=' . sesskey()); } else if ($qid = optional_param('q', 0, PARAM_INT)) { if (!$cm = get_coursemodule_from_instance('quiz', $qid)) { throw new \moodle_exception('invalidquizid', 'quiz'); } redirect(new moodle_url('/mod/quiz/startattempt.php', ['cmid' => $cm->id, 'sesskey' => sesskey()])); } // Get submitted parameters. $attemptid = required_param('attempt', PARAM_INT); $page = optional_param('page', 0, PARAM_INT); $cmid = optional_param('cmid', null, PARAM_INT); $attemptobj = quiz_create_attempt_handling_errors($attemptid, $cmid); $page = $attemptobj->force_page_number_into_range($page); $PAGE->set_url($attemptobj->attempt_url(null, $page)); // During quiz attempts, the browser back/forwards buttons should force a reload. $PAGE->set_cacheable(false); $PAGE->set_secondary_active_tab("modulepage"); // Check login. require_login($attemptobj->get_course(), false, $attemptobj->get_cm()); // Check that this attempt belongs to this user. if ($attemptobj->get_userid() != $USER->id) { if ($attemptobj->has_capability('mod/quiz:viewreports')) { redirect($attemptobj->review_url(null, $page)); } else { throw new moodle_exception('notyourattempt', 'quiz', $attemptobj->view_url()); } } // Check capabilities and block settings. if (!$attemptobj->is_preview_user()) { $attemptobj->require_capability('mod/quiz:attempt'); if (empty($attemptobj->get_quiz()->showblocks)) { $PAGE->blocks->show_only_fake_blocks(); } } else { navigation_node::override_active_url($attemptobj->start_attempt_url()); } // If the attempt is already closed, send them to the review page. if ($attemptobj->is_finished()) { redirect($attemptobj->review_url(null, $page)); } else if ($attemptobj->get_state() == quiz_attempt::OVERDUE) { redirect($attemptobj->summary_url()); } // Check the access rules. $accessmanager = $attemptobj->get_access_manager(time()); $accessmanager->setup_attempt_page($PAGE); /** @var renderer $output */ $output = $PAGE->get_renderer('mod_quiz'); $messages = $accessmanager->prevent_access(); if (!$attemptobj->is_preview_user() && $messages) { throw new \moodle_exception('attempterror', 'quiz', $attemptobj->view_url(), $output->access_messages($messages)); } if ($accessmanager->is_preflight_check_required($attemptobj->get_attemptid())) { redirect($attemptobj->start_attempt_url(null, $page)); } // Set up auto-save if required. $autosaveperiod = get_config('quiz', 'autosaveperiod'); if ($autosaveperiod) { $PAGE->requires->string_for_js('strftimedatetimeshortaccurate', 'langconfig'); $PAGE->requires->string_for_js('lastautosave', 'quiz'); $PAGE->requires->yui_module('moodle-mod_quiz-autosave', 'M.mod_quiz.autosave.init', [$autosaveperiod]); } // Log this page view. $attemptobj->fire_attempt_viewed_event(); // Get the list of questions needed by this page. $slots = $attemptobj->get_slots($page); // Check. if (empty($slots)) { throw new moodle_exception('noquestionsfound', 'quiz', $attemptobj->view_url()); } // Update attempt page, redirecting the user if $page is not valid. if (!$attemptobj->set_currentpage($page)) { redirect($attemptobj->start_attempt_url(null, $attemptobj->get_currentpage())); } if ($attemptobj->is_own_preview()) { $attemptobj->update_questions_to_new_version_if_changed(); } // Initialise the JavaScript. $headtags = $attemptobj->get_html_head_contributions($page); $PAGE->requires->js_init_call('M.mod_quiz.init_attempt_form', null, false, quiz_get_js_module()); \core\session\manager::keepalive(); // Try to prevent sessions expiring during quiz attempts. // Arrange for the navigation to be displayed in the first region on the page. $navbc = $attemptobj->get_navigation_panel($output, navigation_panel_attempt::class, $page); $regions = $PAGE->blocks->get_regions(); $PAGE->blocks->add_fake_block($navbc, reset($regions)); $headtags = $attemptobj->get_html_head_contributions($page); $PAGE->set_title($attemptobj->attempt_page_title($page)); $PAGE->add_body_class('limitedwidth'); $PAGE->set_heading($attemptobj->get_course()->fullname); if ($PAGE->pagelayout === 'secure') { // Show the activity header (but only the name) in the secure layout on quiz pages. $PAGE->activityheader->set_attrs([ 'description' => '', ]); } else { $PAGE->activityheader->disable(); } if ($attemptobj->is_last_page($page)) { $nextpage = -1; } else { $nextpage = $page + 1; } echo $output->attempt_page($attemptobj, $page, $accessmanager, $messages, $slots, $id, $nextpage); home/smkb8269/public_html/lms.smk-almanshuriyah.sch.id/mod/h5pactivity/classes/output/attempt.php000064400000017676152267170630027127 0ustar00. /** * Contains class mod_h5pactivity\output\reportlink * * @package mod_h5pactivity * @copyright 2020 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace mod_h5pactivity\output; defined('MOODLE_INTERNAL') || die(); use mod_h5pactivity\local\attempt as activity_attempt; use renderable; use templatable; use renderer_base; use moodle_url; use user_picture; use stdClass; /** * Class to help display report link in mod_h5pactivity. * * @copyright 2020 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class attempt implements renderable, templatable { /** @var activity_attempt attempt */ public $attempt; /** @var stdClass user record */ public $user; /** @var int courseid necesary to present user picture */ public $courseid; /** * Constructor. * * @param activity_attempt $attempt the attempt object * @param stdClass $user a user record (default null). * @param int $courseid optional course id (default null). */ public function __construct(activity_attempt $attempt, ?stdClass $user = null, ?int $courseid = null) { $this->attempt = $attempt; $this->user = $user; $this->courseid = $courseid; } /** * Export this data so it can be used as the context for a mustache template. * * @param renderer_base $output * @return stdClass */ public function export_for_template(renderer_base $output) { $attempt = $this->attempt; $data = (object)[ 'id' => $attempt->get_id(), 'h5pactivityid' => $attempt->get_h5pactivityid(), 'userid' => $attempt->get_userid(), 'timecreated' => $attempt->get_timecreated(), 'timemodified' => $attempt->get_timemodified(), 'attempt' => $attempt->get_attempt(), 'rawscore' => $attempt->get_rawscore(), 'maxscore' => $attempt->get_maxscore(), 'duration' => '-', 'durationcompact' => '-', 'completion' => $attempt->get_completion(), 'completionicon' => $this->completion_icon($output, $attempt->get_completion()), 'completiontext' => $this->completion_icon($output, $attempt->get_completion(), true), 'success' => $attempt->get_success(), 'successicon' => $this->success_icon($output, $attempt->get_success()), 'successtext' => $this->success_icon($output, $attempt->get_success(), true), 'scaled' => $attempt->get_scaled(), 'reporturl' => new moodle_url('/mod/h5pactivity/report.php', [ 'a' => $attempt->get_h5pactivityid(), 'attemptid' => $attempt->get_id() ]), ]; if ($attempt->get_duration() !== null) { $data->durationvalue = $attempt->get_duration(); $duration = $this->extract_duration($data->durationvalue); $data->duration = $this->format_duration($duration); $data->durationcompact = $this->format_duration_short($duration); } if (!empty($data->maxscore)) { $data->score = get_string('score_out_of', 'mod_h5pactivity', $data); } if ($this->user) { $data->user = $this->user; $userpicture = new user_picture($this->user); $userpicture->courseid = $this->courseid; $data->user->picture = $output->render($userpicture); $data->user->fullname = fullname($this->user); } return $data; } /** * Return a completion icon HTML. * * @param renderer_base $output the renderer base object * @param int|null $completion the current completion value * @param bool $showtext if the icon must have a text or only icon * @return string icon HTML */ private function completion_icon(renderer_base $output, ?int $completion = null, bool $showtext = false): string { if ($completion === null) { return ''; } if ($completion) { $alt = get_string('attempt_completion_yes', 'mod_h5pactivity'); $icon = 'i/completion-auto-y'; } else { $alt = get_string('attempt_completion_no', 'mod_h5pactivity'); $icon = 'i/completion-auto-n'; } $text = ''; if ($showtext) { $text = $alt; $alt = ''; } return $output->pix_icon($icon, $alt).$text; } /** * Return a success icon * @param renderer_base $output the renderer base object * @param int|null $success the current success value * @param bool $showtext if the icon must have a text or only icon * @return string icon HTML */ private function success_icon(renderer_base $output, ?int $success = null, bool $showtext = false): string { if ($success === null) { $alt = get_string('attempt_success_unknown', 'mod_h5pactivity'); if ($showtext) { return $alt; } $icon = 'i/empty'; } else if ($success) { $alt = get_string('attempt_success_pass', 'mod_h5pactivity'); $icon = 'i/checkedcircle'; } else { $alt = get_string('attempt_success_fail', 'mod_h5pactivity'); $icon = 'i/uncheckedcircle'; } $text = ''; if ($showtext) { $text = $alt; $alt = ''; } return $output->pix_icon($icon, $alt).$text; } /** * Return the duration in long format (localized) * * @param stdClass $duration object with (h)hours, (m)minutes and (s)seconds * @return string the long format duration */ private function format_duration(stdClass $duration): string { $result = []; if ($duration->h) { $result[] = get_string('numhours', 'moodle', $duration->h); } if ($duration->m) { $result[] = get_string('numminutes', 'moodle', $duration->m); } if ($duration->s) { $result[] = get_string('numseconds', 'moodle', $duration->s); } return implode(' ', $result); } /** * Return the duration en short format (for example: 145' 43'') * * Note: this method is used to make duration responsive. * * @param stdClass $duration object with (h)hours, (m)minutes and (s)seconds * @return string the short format duration */ private function format_duration_short(stdClass $duration): string { $result = []; if ($duration->h || $duration->m) { $result[] = ($duration->h * 60 + $duration->m)."'"; } if ($duration->s) { $result[] = $duration->s."''"; } return implode(' ', $result); } /** * Extract hours and minutes from second duration. * * Note: this function is used to generate the param for format_duration * and format_duration_short * * @param int $seconds number of second * @return stdClass with (h)hours, (m)minutes and (s)seconds */ private function extract_duration(int $seconds): stdClass { $h = floor($seconds / 3600); $m = floor(($seconds - $h * 3600) / 60); $s = $seconds - ($h * 3600 + $m * 60); return (object)['h' => $h, 'm' => $m, 's' => $s]; } } home/smkb8269/public_html/lms.smk-almanshuriyah.sch.id/mod/h5pactivity/classes/local/attempt.php000064400000037621152267171020026643 0ustar00. /** * H5P activity attempt object * * @package mod_h5pactivity * @since Moodle 3.9 * @copyright 2020 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace mod_h5pactivity\local; use core_xapi\handler; use stdClass; use core_xapi\local\statement; /** * Class attempt for H5P activity * * @package mod_h5pactivity * @since Moodle 3.9 * @copyright 2020 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class attempt { /** @var stdClass the h5pactivity_attempts record. */ private $record; /** @var boolean if the DB statement has been updated. */ private $scoreupdated = false; /** * Create a new attempt object. * * @param stdClass $record the h5pactivity_attempts record */ public function __construct(stdClass $record) { $this->record = $record; } /** * Create a new user attempt in a specific H5P activity. * * @param stdClass $user a user record * @param stdClass $cm a course_module record * @return attempt|null a new attempt object or null if fail */ public static function new_attempt(stdClass $user, stdClass $cm): ?attempt { global $DB; $record = new stdClass(); $record->h5pactivityid = $cm->instance; $record->userid = $user->id; $record->timecreated = time(); $record->timemodified = $record->timecreated; $record->rawscore = 0; $record->maxscore = 0; $record->duration = 0; $record->completion = null; $record->success = null; // Get last attempt number. $conditions = ['h5pactivityid' => $cm->instance, 'userid' => $user->id]; $countattempts = $DB->count_records('h5pactivity_attempts', $conditions); $record->attempt = $countattempts + 1; $record->id = $DB->insert_record('h5pactivity_attempts', $record); if (!$record->id) { return null; } // Remove any xAPI State associated to this attempt. $context = \context_module::instance($cm->id); $xapihandler = handler::create('mod_h5pactivity'); $xapihandler->wipe_states($context->id, $user->id); return new attempt($record); } /** * Get the last user attempt in a specific H5P activity. * * If no previous attempt exists, it generates a new one. * * @param stdClass $user a user record * @param stdClass $cm a course_module record * @return attempt|null a new attempt object or null if some problem accured */ public static function last_attempt(stdClass $user, stdClass $cm): ?attempt { global $DB; $conditions = ['h5pactivityid' => $cm->instance, 'userid' => $user->id]; $records = $DB->get_records('h5pactivity_attempts', $conditions, 'attempt DESC', '*', 0, 1); if (empty($records)) { return self::new_attempt($user, $cm); } return new attempt(array_shift($records)); } /** * Wipe all attempt data for specific course_module and an optional user. * * @param stdClass $cm a course_module record * @param stdClass $user a user record */ public static function delete_all_attempts(stdClass $cm, ?stdClass $user = null): void { global $DB; $where = 'a.h5pactivityid = :h5pactivityid'; $conditions = ['h5pactivityid' => $cm->instance]; if (!empty($user)) { $where .= ' AND a.userid = :userid'; $conditions['userid'] = $user->id; } $DB->delete_records_select('h5pactivity_attempts_results', "attemptid IN ( SELECT a.id FROM {h5pactivity_attempts} a WHERE $where)", $conditions); $DB->delete_records('h5pactivity_attempts', $conditions); } /** * Delete a specific attempt. * * @param attempt $attempt the attempt object to delete */ public static function delete_attempt(attempt $attempt): void { global $DB; $attempt->delete_results(); $DB->delete_records('h5pactivity_attempts', ['id' => $attempt->get_id()]); } /** * Save a new result statement into the attempt. * * It also updates the rawscore and maxscore if necessary. * * @param statement $statement the xAPI statement object * @param string $subcontent = '' optional subcontent identifier * @return bool if it can save the statement into db */ public function save_statement(statement $statement, string $subcontent = ''): bool { global $DB; // Check statement data. $xapiobject = $statement->get_object(); if (empty($xapiobject)) { return false; } $xapiresult = $statement->get_result(); $xapidefinition = $xapiobject->get_definition(); if (empty($xapidefinition) || empty($xapiresult)) { return false; } $xapicontext = $statement->get_context(); if ($xapicontext) { $context = $xapicontext->get_data(); } else { $context = new stdClass(); } $definition = $xapidefinition->get_data(); $result = $xapiresult->get_data(); $duration = $xapiresult->get_duration(); // Insert attempt_results record. $record = new stdClass(); $record->attemptid = $this->record->id; $record->subcontent = $subcontent; $record->timecreated = time(); $record->interactiontype = $definition->interactionType ?? 'other'; $record->description = $this->get_description_from_definition($definition); $record->correctpattern = $this->get_correctpattern_from_definition($definition); $record->response = $result->response ?? ''; $record->additionals = $this->get_additionals($definition, $context); $record->rawscore = 0; $record->maxscore = 0; if (isset($result->score)) { $record->rawscore = $result->score->raw ?? 0; $record->maxscore = $result->score->max ?? 0; } $record->duration = $duration; if (isset($result->completion)) { $record->completion = ($result->completion) ? 1 : 0; } if (isset($result->success)) { $record->success = ($result->success) ? 1 : 0; } if (!$DB->insert_record('h5pactivity_attempts_results', $record)) { return false; } // If no subcontent provided, results are propagated to the attempt itself. if (empty($subcontent)) { $this->set_duration($record->duration); $this->set_completion($record->completion ?? null); $this->set_success($record->success ?? null); // If Maxscore is not empty means that the rawscore is valid (even if it's 0) // and scaled score can be calculated. if ($record->maxscore) { $this->set_score($record->rawscore, $record->maxscore); } } // Refresh current attempt. return $this->save(); } /** * Update the current attempt record into DB. * * @return bool true if update is succesful */ public function save(): bool { global $DB; $this->record->timemodified = time(); // Calculate scaled score. if ($this->scoreupdated) { if (empty($this->record->maxscore)) { $this->record->scaled = 0; } else { $this->record->scaled = $this->record->rawscore / $this->record->maxscore; } } return $DB->update_record('h5pactivity_attempts', $this->record); } /** * Set the attempt score. * * @param int|null $rawscore the attempt rawscore * @param int|null $maxscore the attempt maxscore */ public function set_score(?int $rawscore, ?int $maxscore): void { $this->record->rawscore = $rawscore; $this->record->maxscore = $maxscore; $this->scoreupdated = true; } /** * Set the attempt duration. * * @param int|null $duration the attempt duration */ public function set_duration(?int $duration): void { $this->record->duration = $duration; } /** * Set the attempt completion. * * @param int|null $completion the attempt completion */ public function set_completion(?int $completion): void { $this->record->completion = $completion; } /** * Set the attempt success. * * @param int|null $success the attempt success */ public function set_success(?int $success): void { $this->record->success = $success; } /** * Delete the current attempt results from the DB. */ public function delete_results(): void { global $DB; $conditions = ['attemptid' => $this->record->id]; $DB->delete_records('h5pactivity_attempts_results', $conditions); } /** * Return de number of results stored in this attempt. * * @return int the number of results stored in this attempt. */ public function count_results(): int { global $DB; $conditions = ['attemptid' => $this->record->id]; return $DB->count_records('h5pactivity_attempts_results', $conditions); } /** * Return all results stored in this attempt. * * @return stdClass[] results records. */ public function get_results(): array { global $DB; $conditions = ['attemptid' => $this->record->id]; return $DB->get_records('h5pactivity_attempts_results', $conditions, 'id ASC'); } /** * Get additional data for some interaction types. * * @param stdClass $definition the statement object definition data * @param stdClass $context the statement optional context * @return string JSON encoded additional information */ private function get_additionals(stdClass $definition, stdClass $context): string { $additionals = []; $interactiontype = $definition->interactionType ?? 'other'; switch ($interactiontype) { case 'choice': case 'sequencing': $additionals['choices'] = $definition->choices ?? []; break; case 'matching': $additionals['source'] = $definition->source ?? []; $additionals['target'] = $definition->target ?? []; break; case 'likert': $additionals['scale'] = $definition->scale ?? []; break; case 'performance': $additionals['steps'] = $definition->steps ?? []; break; } $additionals['extensions'] = $definition->extensions ?? new stdClass(); // Add context extensions. $additionals['contextExtensions'] = $context->extensions ?? new stdClass(); if (empty($additionals)) { return ''; } return json_encode($additionals); } /** * Extract the result description from statement object definition. * * In principle, H5P package can send a multilang description but the reality * is that most activities only send the "en_US" description if any and the * activity does not have any control over it. * * @param stdClass $definition the statement object definition * @return string The available description if any */ private function get_description_from_definition(stdClass $definition): string { if (!isset($definition->description)) { return ''; } $translations = (array) $definition->description; if (empty($translations)) { return ''; } // By default, H5P packages only send "en-US" descriptions. return $translations['en-US'] ?? array_shift($translations); } /** * Extract the correct pattern from statement object definition. * * The correct pattern depends on the type of content and the plugin * has no control over it so we just store it in case that the statement * data have it. * * @param stdClass $definition the statement object definition * @return string The correct pattern if any */ private function get_correctpattern_from_definition(stdClass $definition): string { if (!isset($definition->correctResponsesPattern)) { return ''; } // Only arrays are allowed. if (is_array($definition->correctResponsesPattern)) { return json_encode($definition->correctResponsesPattern); } return ''; } /** * Return the attempt number. * * @return int the attempt number */ public function get_attempt(): int { return $this->record->attempt; } /** * Return the attempt ID. * * @return int the attempt id */ public function get_id(): int { return $this->record->id; } /** * Return the attempt user ID. * * @return int the attempt userid */ public function get_userid(): int { return $this->record->userid; } /** * Return the attempt H5P timecreated. * * @return int the attempt timecreated */ public function get_timecreated(): int { return $this->record->timecreated; } /** * Return the attempt H5P timemodified. * * @return int the attempt timemodified */ public function get_timemodified(): int { return $this->record->timemodified; } /** * Return the attempt H5P activity ID. * * @return int the attempt userid */ public function get_h5pactivityid(): int { return $this->record->h5pactivityid; } /** * Return the attempt maxscore. * * @return int the maxscore value */ public function get_maxscore(): int { return $this->record->maxscore; } /** * Return the attempt rawscore. * * @return int the rawscore value */ public function get_rawscore(): int { return $this->record->rawscore; } /** * Return the attempt duration. * * @return int|null the duration value */ public function get_duration(): ?int { return $this->record->duration; } /** * Return the attempt completion. * * @return int|null the completion value */ public function get_completion(): ?int { return $this->record->completion; } /** * Return the attempt success. * * @return int|null the success value */ public function get_success(): ?int { return $this->record->success; } /** * Return the attempt scaled. * * @return int|null the scaled value */ public function get_scaled(): ?int { return is_null($this->record->scaled) ? $this->record->scaled : (int)$this->record->scaled; } /** * Return if the attempt has been modified. * * Note: adding a result only add track information unless the statement does * not specify subcontent. In this case this will update also the statement. * * @return bool if the attempt score have been modified */ public function get_scoreupdated(): bool { return $this->scoreupdated; } }