diff --git a/classes/form/context/render_context.php b/classes/form/context/render_context.php index 7ca8a84d..a5bbf22a 100644 --- a/classes/form/context/render_context.php +++ b/classes/form/context/render_context.php @@ -146,7 +146,7 @@ abstract public function hide_if(string $dependant, string $dependency, string $ * @return string name of the element qualified by this context's prefix */ public function mangle_name(string $name): string { - if (utils::str_starts_with($name, $this->prefix)) { + if (str_starts_with($name, $this->prefix)) { // Already mangled, perhaps by an array_render_context. return $name; } diff --git a/classes/question_ui_renderer.php b/classes/question_ui_renderer.php index bddb7191..201c817f 100644 --- a/classes/question_ui_renderer.php +++ b/classes/question_ui_renderer.php @@ -249,7 +249,7 @@ private function mangle_ids_and_names(): void { ") as $attr ) { $original = $attr->value; - if ($attr->name === "usemap" && utils::str_starts_with($original, "#")) { + if ($attr->name === "usemap" && str_starts_with($original, "#")) { // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/useMap. $attr->value = "#" . $this->attempt->get_qt_field_name(substr($original, 1)); } else { diff --git a/classes/utils.php b/classes/utils.php index 8d4b61b2..39c7d85f 100644 --- a/classes/utils.php +++ b/classes/utils.php @@ -42,18 +42,6 @@ public static function &ensure_exists(array &$array, string $key): array { return $array[$key]; } - /** - * Determines whether a string starts with another. - * - * @param string $haystack - * @param string $needle - * @return bool - */ - public static function str_starts_with(string $haystack, string $needle): bool { - // From https://stackoverflow.com/a/10473026. - return substr_compare($haystack, $needle, 0, strlen($needle)) === 0; - } - /** * Given an array and a key such as `abc[def]`, returns `$array["abc"]["def"]`. * @@ -127,4 +115,15 @@ public static function reindex_integer_arrays(array &$array): void { $array = array_values($array); } } + + /** + * Given an array as returned by {@see \question_attempt_step::get_qt_data()}, filters out vars starting with `_`. + * + * @param array $qtvars + * @return array + * @see question_attempt_step for the meaning of different step var prefixes + */ + public static function filter_for_response(array $qtvars): array { + return array_filter($qtvars, fn($key) => !str_starts_with($key, '_'), ARRAY_FILTER_USE_KEY); + } } diff --git a/question.php b/question.php index 57a1da08..6201c6ea 100644 --- a/question.php +++ b/question.php @@ -24,8 +24,8 @@ use qtype_questionpy\api\api; use qtype_questionpy\api\attempt_ui; -use qtype_questionpy\constants; use qtype_questionpy\api\scoring_code; +use qtype_questionpy\constants; use qtype_questionpy\question_ui_metadata_extractor; use qtype_questionpy\utils; @@ -160,11 +160,7 @@ public function apply_attempt_state(question_attempt_step $step) { $this->scoringstate = $this->get_behaviour()->get_qa()->get_last_qt_var(constants::QT_VAR_SCORING_STATE); $lastqtdata = $this->get_behaviour()->get_qa()->get_last_qt_data(null); - $lastresponse = $lastqtdata === null ? null : array_filter( - $lastqtdata, - fn($key) => !utils::str_starts_with($key, "_"), - ARRAY_FILTER_USE_KEY - ); + $lastresponse = $lastqtdata === null ? null : utils::filter_for_response($lastqtdata); /* TODO: This method is also called from question_attempt->regrade and question_attempt->start_question_based_on, where we shouldn't need to get the UI. */ @@ -267,7 +263,7 @@ public function is_complete_response(array $response): bool { } /** - * Use by many of the behaviours to determine whether the student's + * Used by many of the behaviours to determine whether the student's * response has changed. This is normally used to determine that a new set * of responses can safely be discarded. * @@ -277,8 +273,10 @@ public function is_complete_response(array $response): bool { * @return bool whether the two sets of responses are the same - that is * whether the new set of responses can safely be discarded. */ - public function is_same_response(array $prevresponse, array $newresponse) { - return false; + public function is_same_response(array $prevresponse, array $newresponse): bool { + // The response has already been filtered against get_expected_data, we just need to filter out attempt state + // and scoring state before comparing. + return utils::filter_for_response($prevresponse) == utils::filter_for_response($newresponse); } /**