Completed
Branch master (b92a94)
by
unknown
34:34
created

HTMLMultiSelectField::__construct()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 4
eloc 6
c 2
b 0
f 1
nc 4
nop 1
dl 0
loc 12
rs 9.2
1
<?php
2
3
/**
4
 * Multi-select field
5
 */
6
class HTMLMultiSelectField extends HTMLFormField implements HTMLNestedFilterable {
7
	/**
8
	 * @param array $params
9
	 *   In adition to the usual HTMLFormField parameters, this can take the following fields:
10
	 *   - dropdown: If given, the options will be displayed inside a dropdown with a text field that
11
	 *     can be used to filter them. This is desirable mostly for very long lists of options.
12
	 *     This only works for users with JavaScript support and falls back to the list of checkboxes.
13
	 *   - flatlist: If given, the options will be displayed on a single line (wrapping to following
14
	 *     lines if necessary), rather than each one on a line of its own. This is desirable mostly
15
	 *     for very short lists of concisely labelled options.
16
	 */
17
	public function __construct( $params ) {
18
		parent::__construct( $params );
19
20
		// For backwards compatibility, also handle the old way with 'cssclass' => 'mw-chosen'
21
		if ( isset( $params['dropdown'] ) || strpos( $this->mClass, 'mw-chosen' ) !== false ) {
22
			$this->mClass .= ' mw-htmlform-dropdown';
23
		}
24
25
		if ( isset( $params['flatlist'] ) ) {
26
			$this->mClass .= ' mw-htmlform-flatlist';
27
		}
28
	}
29
30
	function validate( $value, $alldata ) {
31
		$p = parent::validate( $value, $alldata );
32
33
		if ( $p !== true ) {
34
			return $p;
35
		}
36
37
		if ( !is_array( $value ) ) {
38
			return false;
39
		}
40
41
		# If all options are valid, array_intersect of the valid options
42
		# and the provided options will return the provided options.
43
		$validOptions = HTMLFormField::flattenOptions( $this->getOptions() );
44
45
		$validValues = array_intersect( $value, $validOptions );
46 View Code Duplication
		if ( count( $validValues ) == count( $value ) ) {
47
			return true;
48
		} else {
49
			return $this->msg( 'htmlform-select-badoption' )->parse();
50
		}
51
	}
52
53
	function getInputHTML( $value ) {
54
		if ( isset( $this->mParams['dropdown'] ) ) {
55
			$this->mParent->getOutput()->addModules( 'jquery.chosen' );
56
		}
57
58
		$value = HTMLFormField::forceToStringRecursive( $value );
59
		$html = $this->formatOptions( $this->getOptions(), $value );
60
61
		return $html;
62
	}
63
64
	function formatOptions( $options, $value ) {
65
		$html = '';
66
67
		$attribs = $this->getAttributes( [ 'disabled', 'tabindex' ] );
68
69
		foreach ( $options as $label => $info ) {
70
			if ( is_array( $info ) ) {
71
				$html .= Html::rawElement( 'h1', [], $label ) . "\n";
72
				$html .= $this->formatOptions( $info, $value );
73
			} else {
74
				$thisAttribs = [
75
					'id' => "{$this->mID}-$info",
76
					'value' => $info,
77
				];
78
				$checked = in_array( $info, $value, true );
79
80
				$checkbox = $this->getOneCheckbox( $checked, $attribs + $thisAttribs, $label );
81
82
				$html .= ' ' . Html::rawElement(
83
					'div',
84
					[ 'class' => 'mw-htmlform-flatlist-item' ],
85
					$checkbox
86
				);
87
			}
88
		}
89
90
		return $html;
91
	}
92
93
	protected function getOneCheckbox( $checked, $attribs, $label ) {
94
		if ( $this->mParent instanceof OOUIHTMLForm ) {
95
			throw new MWException( 'HTMLMultiSelectField#getOneCheckbox() is not supported' );
96
		} else {
97
			$elementFunc = [ 'Html', $this->mOptionsLabelsNotFromMessage ? 'rawElement' : 'element' ];
98
			$checkbox =
99
				Xml::check( "{$this->mName}[]", $checked, $attribs ) .
100
				'&#160;' .
101
				call_user_func( $elementFunc,
102
					'label',
103
					[ 'for' => $attribs['id'] ],
104
					$label
105
				);
106
			if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
107
				$checkbox = Html::openElement( 'div', [ 'class' => 'mw-ui-checkbox' ] ) .
108
					$checkbox .
109
					Html::closeElement( 'div' );
110
			}
111
			return $checkbox;
112
		}
113
	}
114
115
	/**
116
	 * Get the OOUI version of this field.
117
	 *
118
	 * @since 1.28
119
	 * @param string[] $value
120
	 * @return OOUI\CheckboxMultiselectInputWidget
121
	 */
122
	public function getInputOOUI( $value ) {
123
		$attr = $this->getTooltipAndAccessKey();
124
		$attr['id'] = $this->mID;
125
		$attr['name'] = "{$this->mName}[]";
126
127
		$attr['value'] = $value;
128
		$attr['options'] = $this->getOptionsOOUI();
129
130
		if ( $this->mOptionsLabelsNotFromMessage ) {
131
			foreach ( $attr['options'] as &$option ) {
0 ignored issues
show
Bug introduced by
The expression $attr['options'] of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
132
				$option['label'] = new OOUI\HtmlSnippet( $option['label'] );
133
			}
134
		}
135
136
		$attr += OOUI\Element::configFromHtmlAttributes(
137
			$this->getAttributes( [ 'disabled', 'tabindex' ] )
138
		);
139
140
		if ( $this->mClass !== '' ) {
141
			$attr['classes'] = [ $this->mClass ];
142
		}
143
144
		return new OOUI\CheckboxMultiselectInputWidget( $attr );
145
	}
146
147
	/**
148
	 * @param WebRequest $request
149
	 *
150
	 * @return string
151
	 */
152
	function loadDataFromRequest( $request ) {
153
		if ( $this->isSubmitAttempt( $request ) ) {
154
			// Checkboxes are just not added to the request arrays if they're not checked,
155
			// so it's perfectly possible for there not to be an entry at all
156
			return $request->getArray( $this->mName, [] );
157
		} else {
158
			// That's ok, the user has not yet submitted the form, so show the defaults
159
			return $this->getDefault();
160
		}
161
	}
162
163
	function getDefault() {
164
		if ( isset( $this->mDefault ) ) {
165
			return $this->mDefault;
166
		} else {
167
			return [];
168
		}
169
	}
170
171
	function filterDataForSubmit( $data ) {
172
		$data = HTMLFormField::forceToStringRecursive( $data );
173
		$options = HTMLFormField::flattenOptions( $this->getOptions() );
174
175
		$res = [];
176
		foreach ( $options as $opt ) {
177
			$res["$opt"] = in_array( $opt, $data, true );
178
		}
179
180
		return $res;
181
	}
182
183
	protected function needsLabel() {
184
		return false;
185
	}
186
}
187