1<?php
2/**
3 * Class for generating HTML <select> elements.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23/**
24 * Class for generating HTML <select> or <datalist> elements.
25 */
26class XmlSelect {
27	protected $options = [];
28	protected $default = false;
29	protected $tagName = 'select';
30	protected $attributes = [];
31
32	public function __construct( $name = false, $id = false, $default = false ) {
33		if ( $name ) {
34			$this->setAttribute( 'name', $name );
35		}
36
37		if ( $id ) {
38			$this->setAttribute( 'id', $id );
39		}
40
41		if ( $default !== false ) {
42			$this->default = $default;
43		}
44	}
45
46	/**
47	 * @param string|array $default
48	 */
49	public function setDefault( $default ) {
50		$this->default = $default;
51	}
52
53	/**
54	 * @param string|array $tagName
55	 */
56	public function setTagName( $tagName ) {
57		$this->tagName = $tagName;
58	}
59
60	/**
61	 * @param string $name
62	 * @param string $value
63	 */
64	public function setAttribute( $name, $value ) {
65		$this->attributes[$name] = $value;
66	}
67
68	/**
69	 * @param string $name
70	 * @return string|null
71	 */
72	public function getAttribute( $name ) {
73		return $this->attributes[$name] ?? null;
74	}
75
76	/**
77	 * @param string $label
78	 * @param string|false $value If not given, assumed equal to $label
79	 */
80	public function addOption( $label, $value = false ) {
81		$value = $value !== false ? $value : $label;
82		$this->options[] = [ $label => $value ];
83	}
84
85	/**
86	 * This accepts an array of form
87	 * label => value
88	 * label => ( label => value, label => value )
89	 *
90	 * @param array $options
91	 */
92	public function addOptions( $options ) {
93		$this->options[] = $options;
94	}
95
96	/**
97	 * This accepts an array of form:
98	 * label => value
99	 * label => ( label => value, label => value )
100	 *
101	 * @param array $options
102	 * @param string|array|false $default
103	 * @return string
104	 */
105	public static function formatOptions( $options, $default = false ) {
106		$data = '';
107
108		foreach ( $options as $label => $value ) {
109			if ( is_array( $value ) ) {
110				$contents = self::formatOptions( $value, $default );
111				$data .= Html::rawElement( 'optgroup', [ 'label' => $label ], $contents ) . "\n";
112			} else {
113				// If $default is an array, then the <select> probably has the multiple attribute,
114				// so we should check if each $value is in $default, rather than checking if
115				// $value is equal to $default.
116				$selected = is_array( $default ) ? in_array( $value, $default ) : $value === $default;
117				$data .= Xml::option( $label, $value, $selected ) . "\n";
118			}
119		}
120
121		return $data;
122	}
123
124	/**
125	 * @return string
126	 */
127	public function getHTML() {
128		$contents = '';
129
130		foreach ( $this->options as $options ) {
131			$contents .= self::formatOptions( $options, $this->default );
132		}
133
134		return Html::rawElement( $this->tagName, $this->attributes, rtrim( $contents ) );
135	}
136
137	/**
138	 * Parse labels and values out of a comma- and colon-separated list of options, such as is used for
139	 * expiry and duration lists. Documentation of the format is on translatewiki.net.
140	 * @link https://translatewiki.net/wiki/Template:Doc-mediawiki-options-list
141	 * @param string $msg The message to parse.
142	 * @return string[] The options array, where keys are option labels (i.e. translations)
143	 * and values are option values (i.e. untranslated).
144	 */
145	public static function parseOptionsMessage( string $msg ): array {
146		$options = [];
147		foreach ( explode( ',', $msg ) as $option ) {
148			// Normalize options that only have one part.
149			if ( strpos( $option, ':' ) === false ) {
150				$option = "$option:$option";
151			}
152			// Extract the two parts.
153			list( $label, $value ) = explode( ':', $option );
154			$options[ trim( $label ) ] = trim( $value );
155		}
156		return $options;
157	}
158}
159