@@ -134,7 +134,7 @@ public function render(): render_result {
134
134
mt_srand ($ nextseed );
135
135
}
136
136
137
- $ warnings = $ this ->check_for_unknown_options ($ availableoptions );
137
+ $ warnings = $ this ->check_for_and_preserve_unknown_options ($ availableoptions );
138
138
$ this ->result = new render_result ($ this ->xml ->saveHTML (), $ warnings );
139
139
return $ this ->result ;
140
140
}
@@ -601,11 +601,11 @@ function (array $match) use ($question) {
601
601
* - While we should discourage it, it is possible for inputs to be inside `qpy:if-role` or `qpy:feedback`
602
602
* elements. {@see question_ui_metadata_extractor} doesn't resolve those.
603
603
*
604
- * @return array
605
- * @see check_for_unknown_options
604
+ * @return array<string, available_opts_info>
605
+ * @see check_for_and_preserve_unknown_options
606
606
*/
607
607
private function extract_available_options (): array {
608
- $ optionsbyname = [];
608
+ $ infobyname = [];
609
609
610
610
/** @var DOMElement $select */
611
611
foreach ($ this ->xpath ->query ('//xhtml:select[not(@qpy:warn-on-unknown-option = "no")] ' ) as $ select ) {
@@ -614,69 +614,72 @@ private function extract_available_options(): array {
614
614
continue ;
615
615
}
616
616
617
- $ values = [];
617
+ $ optvalues = [];
618
618
/** @var DOMElement $option */
619
619
foreach ($ this ->xpath ->query ('./xhtml:option | ./xhtml:optgroup/xhtml:option ' , $ select ) as $ option ) {
620
- $ values [] = $ option ->hasAttribute ('value ' ) ? $ option ->getAttribute ('value ' ) : $ option ->textContent ;
620
+ $ optvalues [] = $ option ->hasAttribute ('value ' ) ? $ option ->getAttribute ('value ' ) : $ option ->textContent ;
621
621
}
622
622
623
- $ optionsbyname [$ name ] = array_unique ($ values );
623
+ $ warn = $ select ->getAttributeNS (constants::NAMESPACE_QPY , 'warn-on-unknown-option ' ) !== 'no ' ;
624
+
625
+ $ infobyname [$ name ] = new available_opts_info ('select ' , array_unique ($ optvalues ), $ warn );
624
626
}
625
627
626
- $ ignorednames = [];
627
628
/** @var DOMElement $input */
628
629
foreach ($ this ->xpath ->query ('//xhtml:input[(@type="checkbox" or @type="radio")] ' ) as $ input ) {
629
630
$ name = $ input ->getAttribute ('name ' );
630
631
if (!$ name ) {
631
632
continue ;
632
633
}
633
- if (in_array ($ name , $ ignorednames )) {
634
- continue ;
635
- }
636
- if ($ input ->getAttributeNS (constants::NAMESPACE_QPY , 'warn-on-unknown-option ' ) === 'no ' ) {
637
- $ ignorednames [] = $ name ;
638
- continue ;
639
- }
640
634
641
- if (!array_key_exists ($ name , $ optionsbyname )) {
642
- $ optionsbyname [$ name ] = [];
635
+ $ info = $ infobyname [$ name ] ??= new available_opts_info ($ input ->getAttribute ('type ' ), [], true );
636
+
637
+ if ($ input ->getAttributeNS (constants::NAMESPACE_QPY , 'warn-on-unknown-option ' ) === 'no ' ) {
638
+ $ info ->warnonunknownoption = false ;
643
639
}
644
640
645
641
$ value = $ input ->hasAttribute ('value ' ) ? $ input ->getAttribute ('value ' ) : 'on ' ;
646
- if (!in_array ($ value , $ optionsbyname [ $ name ] )) {
647
- $ optionsbyname [ $ name ] [] = $ value ;
642
+ if (!in_array ($ value , $ info -> availableoptions )) {
643
+ $ info -> availableoptions [] = $ value ;
648
644
}
649
645
}
650
646
651
- foreach ($ ignorednames as $ ignoredname ) {
652
- unset($ optionsbyname [$ ignoredname ]);
653
- }
654
- foreach ($ optionsbyname as &$ values ) {
655
- sort ($ values );
647
+ foreach ($ infobyname as $ info ) {
648
+ sort ($ info ->availableoptions );
656
649
}
657
650
658
- return $ optionsbyname ;
651
+ return $ infobyname ;
659
652
}
660
653
661
654
/**
662
- * Checks if the last response contains values which are invalid.
655
+ * Checks the last response for invalid values and adds hidden inputs to preserve those invalid values .
663
656
*
664
- * @param array $availableoptionsbyname
665
- * @return array
657
+ * @param available_opts_info[] $availableoptsinfobyname
658
+ * @return invalid_option_warning[]
659
+ * @throws coding_exception
666
660
* @see extract_available_options
667
661
*/
668
- private function check_for_unknown_options (array $ availableoptionsbyname ): array {
662
+ private function check_for_and_preserve_unknown_options (array $ availableoptsinfobyname ): array {
669
663
$ response = utils::get_last_response ($ this ->attempt );
670
664
671
665
$ warnings = [];
672
- foreach ($ availableoptionsbyname as $ name => $ availableoptions ) {
666
+ foreach ($ availableoptsinfobyname as $ name => $ info ) {
667
+ if (!$ info ->warnonunknownoption ) {
668
+ continue ;
669
+ }
673
670
if (!array_key_exists ($ name , $ response )) {
674
671
continue ;
675
672
}
676
673
677
674
$ lastvalue = $ response [$ name ];
678
- if (!in_array ($ lastvalue , $ availableoptions )) {
679
- $ warnings [] = new invalid_option_warning ($ name , $ lastvalue , $ availableoptions );
675
+ if (in_array ($ lastvalue , $ info ->availableoptions )) {
676
+ continue ;
677
+ }
678
+
679
+ $ warnings [] = new invalid_option_warning ($ name , $ lastvalue , $ info ->availableoptions );
680
+ if ($ info ->type !== 'select ' ) {
681
+ // Selects are handled in dom_utils::set_select_value.
682
+ dom_utils::add_hidden_input ($ this ->xml ->documentElement , $ this ->attempt ->get_qt_field_name ($ name ), $ lastvalue );
680
683
}
681
684
}
682
685
return $ warnings ;
0 commit comments