HTMLSelectAndOtherField.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. <?php
  2. /**
  3. * Double field with a dropdown list constructed from a system message in the format
  4. * * Optgroup header
  5. * ** <option value>
  6. * * New Optgroup header
  7. * Plus a text field underneath for an additional reason. The 'value' of the field is
  8. * "<select>: <extra reason>", or "<extra reason>" if nothing has been selected in the
  9. * select dropdown.
  10. * @todo FIXME: If made 'required', only the text field should be compulsory.
  11. */
  12. class HTMLSelectAndOtherField extends HTMLSelectField {
  13. /** @var string[] */
  14. private $mFlatOptions;
  15. public function __construct( $params ) {
  16. if ( array_key_exists( 'other', $params ) ) {
  17. // Do nothing
  18. } elseif ( array_key_exists( 'other-message', $params ) ) {
  19. $params['other'] = $this->getMessage( $params['other-message'] )->plain();
  20. } else {
  21. $params['other'] = $this->msg( 'htmlform-selectorother-other' )->plain();
  22. }
  23. parent::__construct( $params );
  24. if ( $this->getOptions() === null ) {
  25. // Sulk
  26. throw new MWException( 'HTMLSelectAndOtherField called without any options' );
  27. }
  28. if ( !in_array( 'other', $this->mOptions, true ) ) {
  29. // Have 'other' always as first element
  30. $this->mOptions = [ $params['other'] => 'other' ] + $this->mOptions;
  31. }
  32. $this->mFlatOptions = self::flattenOptions( $this->getOptions() );
  33. }
  34. public function getInputHTML( $value ) {
  35. $select = parent::getInputHTML( $value[1] );
  36. $textAttribs = [
  37. 'id' => $this->mID . '-other',
  38. 'size' => $this->getSize(),
  39. 'class' => [ 'mw-htmlform-select-and-other-field' ],
  40. 'data-id-select' => $this->mID,
  41. ];
  42. if ( $this->mClass !== '' ) {
  43. $textAttribs['class'][] = $this->mClass;
  44. }
  45. if ( isset( $this->mParams['maxlength-unit'] ) ) {
  46. $textAttribs['data-mw-maxlength-unit'] = $this->mParams['maxlength-unit'];
  47. }
  48. $allowedParams = [
  49. 'required',
  50. 'autofocus',
  51. 'multiple',
  52. 'disabled',
  53. 'tabindex',
  54. 'maxlength', // gets dynamic with javascript, see mediawiki.htmlform.js
  55. 'maxlength-unit', // 'bytes' or 'codepoints', see mediawiki.htmlform.js
  56. ];
  57. $textAttribs += $this->getAttributes( $allowedParams );
  58. $textbox = Html::input( $this->mName . '-other', $value[2], 'text', $textAttribs );
  59. return "$select<br />\n$textbox";
  60. }
  61. protected function getOOUIModules() {
  62. return [ 'mediawiki.widgets.SelectWithInputWidget' ];
  63. }
  64. public function getInputOOUI( $value ) {
  65. $this->mParent->getOutput()->addModuleStyles( 'mediawiki.widgets.SelectWithInputWidget.styles' );
  66. # TextInput
  67. $textAttribs = [
  68. 'name' => $this->mName . '-other',
  69. 'value' => $value[2],
  70. ];
  71. $allowedParams = [
  72. 'required',
  73. 'autofocus',
  74. 'multiple',
  75. 'disabled',
  76. 'tabindex',
  77. 'maxlength',
  78. ];
  79. $textAttribs += OOUI\Element::configFromHtmlAttributes(
  80. $this->getAttributes( $allowedParams )
  81. );
  82. if ( $this->mClass !== '' ) {
  83. $textAttribs['classes'] = [ $this->mClass ];
  84. }
  85. # DropdownInput
  86. $dropdownInputAttribs = [
  87. 'name' => $this->mName,
  88. 'id' => $this->mID . '-select',
  89. 'options' => $this->getOptionsOOUI(),
  90. 'value' => $value[1],
  91. ];
  92. $allowedParams = [
  93. 'tabindex',
  94. 'disabled',
  95. ];
  96. $dropdownInputAttribs += OOUI\Element::configFromHtmlAttributes(
  97. $this->getAttributes( $allowedParams )
  98. );
  99. if ( $this->mClass !== '' ) {
  100. $dropdownInputAttribs['classes'] = [ $this->mClass ];
  101. }
  102. $disabled = false;
  103. if ( isset( $this->mParams[ 'disabled' ] ) && $this->mParams[ 'disabled' ] ) {
  104. $disabled = true;
  105. }
  106. return $this->getInputWidget( [
  107. 'id' => $this->mID,
  108. 'disabled' => $disabled,
  109. 'textinput' => $textAttribs,
  110. 'dropdowninput' => $dropdownInputAttribs,
  111. 'or' => false,
  112. 'required' => $this->mParams[ 'required' ] ?? false,
  113. 'classes' => [ 'mw-htmlform-select-and-other-field' ],
  114. 'data' => [
  115. 'maxlengthUnit' => $this->mParams['maxlength-unit'] ?? 'bytes'
  116. ],
  117. ] );
  118. }
  119. public function getInputWidget( $params ) {
  120. return new MediaWiki\Widget\SelectWithInputWidget( $params );
  121. }
  122. /**
  123. * @inheritDoc
  124. */
  125. public function getDefault() {
  126. $default = parent::getDefault();
  127. // Default values of empty form
  128. $final = '';
  129. $list = 'other';
  130. $text = '';
  131. if ( $default !== null ) {
  132. $final = $default;
  133. // Assume the default is a text value, with the 'other' option selected.
  134. // Then check if that assumption is correct, and update $list and $text if not.
  135. $text = $final;
  136. foreach ( $this->mFlatOptions as $option ) {
  137. $match = $option . $this->msg( 'colon-separator' )->inContentLanguage()->text();
  138. if ( strpos( $final, $match ) === 0 ) {
  139. $list = $option;
  140. $text = substr( $final, strlen( $match ) );
  141. break;
  142. }
  143. }
  144. }
  145. return [ $final, $list, $text ];
  146. }
  147. /**
  148. * @param WebRequest $request
  149. *
  150. * @return array ["<overall message>","<select value>","<text field value>"]
  151. */
  152. public function loadDataFromRequest( $request ) {
  153. if ( $request->getCheck( $this->mName ) ) {
  154. $list = $request->getText( $this->mName );
  155. $text = $request->getText( $this->mName . '-other' );
  156. // Should be built the same as in mediawiki.htmlform.js
  157. if ( $list == 'other' ) {
  158. $final = $text;
  159. } elseif ( !in_array( $list, $this->mFlatOptions, true ) ) {
  160. # User has spoofed the select form to give an option which wasn't
  161. # in the original offer. Sulk...
  162. $final = $text;
  163. } elseif ( $text == '' ) {
  164. $final = $list;
  165. } else {
  166. $final = $list . $this->msg( 'colon-separator' )->inContentLanguage()->text() . $text;
  167. }
  168. return [ $final, $list, $text ];
  169. }
  170. return $this->getDefault();
  171. }
  172. public function getSize() {
  173. return $this->mParams['size'] ?? 45;
  174. }
  175. public function validate( $value, $alldata ) {
  176. # HTMLSelectField forces $value to be one of the options in the select
  177. # field, which is not useful here. But we do want the validation further up
  178. # the chain
  179. $p = parent::validate( $value[1], $alldata );
  180. if ( $p !== true ) {
  181. return $p;
  182. }
  183. if ( isset( $this->mParams['required'] )
  184. && $this->mParams['required'] !== false
  185. && $value[0] === ''
  186. ) {
  187. return $this->msg( 'htmlform-required' );
  188. }
  189. return true;
  190. }
  191. }