1<?php
2
3/**
4 * HTML form generation and submission handling, OOUI style.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * http://www.gnu.org/copyleft/gpl.html
20 *
21 * @file
22 */
23
24/**
25 * Compact stacked vertical format for forms, implemented using OOUI widgets.
26 *
27 * @stable to extend
28 */
29class OOUIHTMLForm extends HTMLForm {
30	private $oouiErrors;
31	private $oouiWarnings;
32
33	/**
34	 * @stable to call
35	 * @inheritDoc
36	 */
37	public function __construct( $descriptor, $context = null, $messagePrefix = '' ) {
38		parent::__construct( $descriptor, $context, $messagePrefix );
39		$this->getOutput()->enableOOUI();
40		$this->getOutput()->addModuleStyles( 'mediawiki.htmlform.ooui.styles' );
41	}
42
43	/**
44	 * Symbolic display format name.
45	 * @var string
46	 */
47	protected $displayFormat = 'ooui';
48
49	public static function loadInputFromParameters( $fieldname, $descriptor,
50		HTMLForm $parent = null
51	) {
52		$field = parent::loadInputFromParameters( $fieldname, $descriptor, $parent );
53		$field->setShowEmptyLabel( false );
54		return $field;
55	}
56
57	public function getButtons() {
58		$buttons = '';
59
60		if ( $this->mShowSubmit ) {
61			$attribs = [ 'infusable' => true ];
62
63			if ( isset( $this->mSubmitID ) ) {
64				$attribs['id'] = $this->mSubmitID;
65			}
66
67			if ( isset( $this->mSubmitName ) ) {
68				$attribs['name'] = $this->mSubmitName;
69			}
70
71			if ( isset( $this->mSubmitTooltip ) ) {
72				$attribs += [
73					'title' => Linker::titleAttrib( $this->mSubmitTooltip ),
74					'accessKey' => Linker::accesskey( $this->mSubmitTooltip ),
75				];
76			}
77
78			$attribs['classes'] = [ 'mw-htmlform-submit' ];
79			$attribs['type'] = 'submit';
80			$attribs['label'] = $this->getSubmitText();
81			$attribs['value'] = $this->getSubmitText();
82			$attribs['flags'] = $this->mSubmitFlags;
83
84			$buttons .= new OOUI\ButtonInputWidget( $attribs );
85		}
86
87		if ( $this->mShowReset ) {
88			$buttons .= new OOUI\ButtonInputWidget( [
89				'type' => 'reset',
90				'label' => $this->msg( 'htmlform-reset' )->text(),
91			] );
92		}
93
94		if ( $this->mShowCancel ) {
95			$target = $this->getCancelTargetURL();
96			$buttons .= new OOUI\ButtonWidget( [
97				'label' => $this->msg( 'cancel' )->text(),
98				'href' => $target,
99			] );
100		}
101
102		foreach ( $this->mButtons as $button ) {
103			$attrs = [];
104
105			if ( $button['attribs'] ) {
106				$attrs += $button['attribs'];
107			}
108
109			if ( isset( $button['id'] ) ) {
110				$attrs['id'] = $button['id'];
111			}
112
113			if ( isset( $button['label-message'] ) ) {
114				$label = new OOUI\HtmlSnippet( $this->getMessage( $button['label-message'] )->parse() );
115			} elseif ( isset( $button['label'] ) ) {
116				$label = $button['label'];
117			} elseif ( isset( $button['label-raw'] ) ) {
118				$label = new OOUI\HtmlSnippet( $button['label-raw'] );
119			} else {
120				$label = $button['value'];
121			}
122
123			$attrs['classes'] = isset( $attrs['class'] ) ? (array)$attrs['class'] : [];
124
125			$buttons .= new OOUI\ButtonInputWidget( [
126				'type' => 'submit',
127				'name' => $button['name'],
128				'value' => $button['value'],
129				'label' => $label,
130				'flags' => $button['flags'],
131				'framed' => $button['framed'],
132			] + $attrs );
133		}
134
135		if ( !$buttons ) {
136			return '';
137		}
138
139		return Html::rawElement( 'div',
140			[ 'class' => 'mw-htmlform-submit-buttons' ], "\n$buttons" ) . "\n";
141	}
142
143	/**
144	 * @inheritDoc
145	 * @return OOUI\PanelLayout
146	 */
147	protected function wrapFieldSetSection( $legend, $section, $attributes, $isRoot ) {
148		// to get a user visible effect, wrap the fieldset into a framed panel layout
149		$layout = new OOUI\PanelLayout( [
150			'expanded' => false,
151			'padded' => true,
152			'framed' => true,
153		] );
154
155		$layout->appendContent(
156			new OOUI\FieldsetLayout( [
157				'label' => $legend,
158				'items' => [
159					new OOUI\Widget( [
160						'content' => new OOUI\HtmlSnippet( $section )
161					] ),
162				],
163			] + $attributes )
164		);
165		return $layout;
166	}
167
168	/**
169	 * Put a form section together from the individual fields' HTML, merging it and wrapping.
170	 * @param OOUI\FieldLayout[] $fieldsHtml
171	 * @param string $sectionName
172	 * @param bool $anyFieldHasLabel Unused
173	 * @return string HTML
174	 */
175	protected function formatSection( array $fieldsHtml, $sectionName, $anyFieldHasLabel ) {
176		if ( !$fieldsHtml ) {
177			// Do not generate any wrappers for empty sections. Sections may be empty if they only have
178			// subsections, but no fields. A legend will still be added in wrapFieldSetSection().
179			return '';
180		}
181
182		$html = implode( '', $fieldsHtml );
183
184		if ( $sectionName ) {
185			$html = Html::rawElement(
186				'div',
187				[ 'id' => Sanitizer::escapeIdForAttribute( $sectionName ) ],
188				$html
189			);
190		}
191		return $html;
192	}
193
194	/**
195	 * @param string|array|Status $elements
196	 * @param string $elementsType
197	 * @return string
198	 */
199	public function getErrorsOrWarnings( $elements, $elementsType ) {
200		if ( $elements === '' ) {
201			return '';
202		}
203
204		if ( !in_array( $elementsType, [ 'error', 'warning' ], true ) ) {
205			throw new DomainException( $elementsType . ' is not a valid type.' );
206		}
207		$errors = [];
208		if ( $elements instanceof Status ) {
209			if ( !$elements->isGood() ) {
210				$errors = $elements->getErrorsByType( $elementsType );
211				foreach ( $errors as &$error ) {
212					// Input:  [ 'message' => 'foo', 'params' => [ 'a', 'b', 'c' ] ]
213					// Output: [ 'foo', 'a', 'b', 'c' ]
214					$error = $this->getMessage(
215						array_merge( [ $error['message'] ], $error['params'] ) )->parse();
216				}
217			}
218		} elseif ( $elementsType === 'error' ) {
219			if ( is_array( $elements ) ) {
220				foreach ( $elements as $error ) {
221					$errors[] = $this->getMessage( $error )->parse();
222				}
223			} elseif ( $elements && $elements !== true ) {
224				$errors[] = (string)$elements;
225			}
226		}
227
228		foreach ( $errors as &$error ) {
229			$error = new OOUI\HtmlSnippet( $error );
230		}
231
232		// Used in formatFormHeader()
233		if ( $elementsType === 'error' ) {
234			$this->oouiErrors = $errors;
235		} else {
236			$this->oouiWarnings = $errors;
237		}
238		return '';
239	}
240
241	public function getHeaderText( $section = null ) {
242		if ( $section === null ) {
243			// We handle $this->mHeader elsewhere, in getBody()
244			return '';
245		} else {
246			return parent::getHeaderText( $section );
247		}
248	}
249
250	protected function formatFormHeader() {
251		if ( !( $this->mHeader || $this->oouiErrors || $this->oouiWarnings ) ) {
252			return '';
253		}
254		$classes = [ 'mw-htmlform-ooui-header' ];
255		if ( $this->oouiErrors ) {
256			$classes[] = 'mw-htmlform-ooui-header-errors';
257		}
258		if ( $this->oouiWarnings ) {
259			$classes[] = 'mw-htmlform-ooui-header-warnings';
260		}
261		// if there's no header, don't create an (empty) LabelWidget, simply use a placeholder
262		if ( $this->mHeader ) {
263			$element = new OOUI\LabelWidget( [ 'label' => new OOUI\HtmlSnippet( $this->mHeader ) ] );
264		} else {
265			$element = new OOUI\Widget( [] );
266		}
267		return new OOUI\FieldLayout(
268			$element,
269			[
270				'align' => 'top',
271				'errors' => $this->oouiErrors,
272				'notices' => $this->oouiWarnings,
273				'classes' => $classes,
274			]
275		);
276	}
277
278	public function getBody() {
279		$html = parent::getBody();
280		$html = $this->formatFormHeader() . $html;
281		return $html;
282	}
283
284	public function wrapForm( $html ) {
285		if ( is_string( $this->mWrapperLegend ) ) {
286			$phpClass = $this->mCollapsible ? CollapsibleFieldsetLayout::class : OOUI\FieldsetLayout::class;
287			$content = new $phpClass( [
288				'label' => $this->mWrapperLegend,
289				'collapsed' => $this->mCollapsed,
290				'items' => [
291					new OOUI\Widget( [
292						'content' => new OOUI\HtmlSnippet( $html )
293					] ),
294				],
295			] + OOUI\Element::configFromHtmlAttributes( $this->mWrapperAttributes ) );
296		} else {
297			$content = new OOUI\HtmlSnippet( $html );
298		}
299
300		$classes = [ 'mw-htmlform', 'mw-htmlform-ooui' ];
301		$form = new OOUI\FormLayout( $this->getFormAttributes() + [
302			'classes' => $classes,
303			'content' => $content,
304		] );
305
306		// Include a wrapper for style, if requested.
307		$form = new OOUI\PanelLayout( [
308			'classes' => [ 'mw-htmlform-ooui-wrapper' ],
309			'expanded' => false,
310			'padded' => $this->mWrapperLegend !== false,
311			'framed' => $this->mWrapperLegend !== false,
312			'content' => $form,
313		] );
314
315		return $form;
316	}
317}
318