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