Completed
Push — master ( eadb3e...25e675 )
by
unknown
10:12
created

AttributeManager::getValueForAttribute()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 12
cts 12
cp 1
rs 9.4555
c 0
b 0
f 0
cc 5
nc 4
nop 2
crap 5
1
<?php
2
/**
3
 * Contains the class for handling component attributes/parameters.
4
 *
5
 * @copyright (C) 2018, Tobias Oetterer, Paderborn University
6
 * @license       https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 (or later)
7
 *
8
 * This file is part of the MediaWiki extension BootstrapComponents.
9
 * The BootstrapComponents extension is free software: you can redistribute it
10
 * and/or modify it under the terms of the GNU General Public License as published
11
 * by the Free Software Foundation, either version 3 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * The BootstrapComponents extension is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
21
 *
22
 * @file
23
 * @ingroup       BootstrapComponents
24
 * @author        Tobias Oetterer
25
 */
26
27
namespace BootstrapComponents;
28
29
/**
30
 * Class TagManager
31
 *
32
 * Manages the execution of the <bootstrap> tag
33
 *
34
 * @since 1.0
35
 */
36
class AttributeManager {
37
38
	/**
39
	 * This introduces aliases for attributes.
40
	 *
41
	 * For instance, if someone adds "header" to its component, it is treated like "heading" if this is not present itself.
42
	 */
43
	const ALIASES = [
44
		'heading' => 'header',
45
		'footer'  => 'footing'
46
	];
47
48
	/**
49
	 * For attributes that take any value.
50
	 *
51
	 * @var int
52
	 */
53
	const ANY_VALUE = 1;
54
55
	/**
56
	 * For attributes that can be set to false by supplying one of certain values.
57
	 * Usually uses for flag-attributes like "active", "collapsible", etc.
58
	 *
59
	 * @see \BootstrapComponents\AttributeManager::$noValues
60
	 *
61
	 * @var int
62
	 */
63
	const NO_FALSE_VALUE = 0;
64
65
	/**
66
	 * Holds the register for allowed attributes per component
67
	 *
68
	 * @var array $allowedValuesForAttribute
69
	 */
70
	private $allowedValuesForAttribute;
71
72
	/**
73
	 * Holds all values indicating a "no". Can be used to ignore "enable"-fields.
74
	 *
75
	 * @var array $noValues
76
	 */
77
	private $noValues;
78
79
	/**
80
	 * The list of attributes that are considered valid
81
	 *
82
	 * @var string[] $validAttributes
83
	 */
84
	private $validAttributes;
85
86
	/**
87
	 * AttributeManager constructor.
88
	 *
89
	 * Do not instantiate directly, but use {@see ApplicationFactory::getAttributeManager}
90
	 * instead.
91
	 *
92
	 * @param string[] $validAttributes the list of attributes, this manager deems valid.
93
	 *
94
	 * @see ApplicationFactory::getAttributeManager
95
	 */
96 49
	public function __construct( $validAttributes ) {
97 49
		$this->noValues = [ false, 0, '0', 'no', 'false', 'off', 'disabled', 'ignored' ];
98 49
		$this->noValues[] = strtolower( wfMessage( 'confirmable-no' )->text() );
99 49
		list ( $this->validAttributes, $this->allowedValuesForAttribute ) = $this->registerValidAttributes( $validAttributes );
100 49
	}
101
102
	/**
103
	 * Returns the list of all available attributes
104
	 *
105
	 * @return string[]
106
	 */
107 1
	public function getAllKnownAttributes() {
108 1
		return array_keys( $this->getInitialAttributeRegister() );
109
	}
110
111
	/**
112
	 * Returns the allowed values for a given attribute or NULL if invalid attribute.
113
	 *
114
	 * Note that allowed values can be an array, {@see AttributeManager::NO_FALSE_VALUE},
115
	 * or {@see AttributeManager::ANY_VALUE}.
116
	 *
117
	 * @param string $attribute
118
	 *
119
	 * @return null|array|bool
120
	 */
121 44
	public function getAllowedValuesFor( $attribute ) {
122 44
		if ( !$this->isRegistered( $attribute ) ) {
123 1
			return null;
124
		}
125 43
		return $this->allowedValuesForAttribute[$attribute];
126
	}
127
128
	/**
129
	 * @return string[]
130
	 */
131 27
	public function getValidAttributes() {
132 27
		return $this->validAttributes;
133
	}
134
135
	/**
136
	 * Checks if given $attribute is registered with the manager.
137
	 *
138
	 * @param string $attribute
139
	 *
140
	 * @return bool
141
	 */
142 44
	public function isRegistered( $attribute ) {
143 44
		return isset( $this->allowedValuesForAttribute[$attribute] );
144
	}
145
146
	/**
147
	 * Registers $attribute with and its allowed values.
148
	 *
149
	 * Notes on attribute registering:
150
	 * * {@see AttributeManager::ANY_VALUE}: every non empty string is allowed
151
	 * * {@see AttributeManager::NO_FALSE_VALUE}: as along as the attribute is present and NOT set to a value contained in {@see AttributeManager::$noValues},
152
	 *      the attribute is considered valid. Note that flag attributes will be set to the empty string by the parser, e.g. having <tag active></tag> will have
153
	 *      active set to "". {@see AttributeManager::verifyValueForAttribute} returns those as true.
154
	 * * array: attribute must be present and contain a value in the array to be valid
155
	 *
156
	 * Note also, that values will be converted to lower case before checking, you therefore should only put lower case values in your
157
	 * allowed-values array.
158
	 *
159
	 * @param string     $attribute
160
	 * @param array|int  $allowedValues
161
	 *
162
	 * @return bool
163
	 */
164 4
	public function register( $attribute, $allowedValues ) {
165 4
		if ( !is_string( $attribute ) || !strlen( trim( $attribute ) ) ) {
166 1
			return false;
167
		}
168 4
		if ( !is_int( $allowedValues ) && ( !is_array( $allowedValues ) || !count( $allowedValues ) ) ) {
169 1
			return false;
170
		}
171 3
		$this->allowedValuesForAttribute[trim( $attribute )] = $allowedValues;
172 3
		return true;
173
	}
174
175
	/**
176
	 * Takes the attributes/options supplied by parser, removes the ones not registered for this component and
177
	 * verifies the rest. Note that the result array has an entry for every valid attribute, false if not supplied via parser.
178
	 *
179
	 * Valid here means: the attribute is registered with the manager and with the component
180
	 * Verified: The attributes value has been checked and deemed ok.
181
	 *
182
	 * Note that attributes not registered with the manager return with a false value.
183
	 *
184
	 * @param string[] $attributes
185
	 *
186
	 * @see AttributeManager::verifyValueForAttribute
187
	 *
188
	 * @return array
189
	 */
190 27
	public function verifyAttributes( $attributes ) {
191 27
		$verifiedAttributes = [];
192 27
		foreach ( $this->getValidAttributes() as $validAttribute ) {
193 26
			$value = $this->getValueForAttribute( $validAttribute, $attributes );
194 26
			if ( is_null( $value ) ) {
195 20
				$verifiedAttributes[$validAttribute] = false;
196 20
			} else {
197 25
				$verifiedAttributes[$validAttribute] = $this->verifyValueForAttribute( $validAttribute, $value );
198
			}
199 27
		}
200 27
		return $verifiedAttributes;
201
	}
202
203
	/**
204
	 * For each supplied valid attribute this registers the attribute together with its valid values.
205
	 *
206
	 * Note: Registers only known attributes.
207
	 *
208
	 * @param string[] $validAttributes
209
	 *
210
	 * @see AttributeManager::getInitialAttributeRegister
211
	 *
212
	 * @return array ($filteredValidAttributes, $attributesRegister)
213
	 */
214 49
	protected function registerValidAttributes( $validAttributes ) {
215 49
		$allAttributes = $this->getInitialAttributeRegister();
216 49
		$filteredValidAttributes = [];
217 49
		$attributesRegister = [];
218 49
		foreach ( $validAttributes as $validAttribute ) {
219 42
			$validAttribute = strtolower( trim( $validAttribute ) );
220 42
			if ( isset( $allAttributes[$validAttribute] ) ) {
221 41
				$filteredValidAttributes[] = $validAttribute;
222 41
				$attributesRegister[$validAttribute] = $allAttributes[$validAttribute];
223 41
			}
224 49
		}
225 49
		return [ $filteredValidAttributes, $attributesRegister ];
226
	}
227
228
	/**
229
	 * For a given attribute, this verifies, if value is allowed. If verification succeeds, the value will be returned, false otherwise.
230
	 * If an attribute is registered as NO_FALSE_VALUE and value is the empty string, it gets converted to true.
231
	 *
232
	 * Note: an ANY_VALUE attribute can still be the empty string.
233
	 * Note: that every value for an unregistered attribute fails verification automatically.
234
	 *
235
	 * @param string $attribute
236
	 * @param string $value
237
	 *
238
	 * @return bool|string
239
	 */
240 25
	protected function verifyValueForAttribute( $attribute, $value ) {
241 25
		$allowedValues = $this->getAllowedValuesFor( $attribute );
242 25
		if ( $allowedValues === self::NO_FALSE_VALUE ) {
243 10
			return $this->verifyValueForNoValueAttribute( $value );
244 20
		} elseif ( $allowedValues === self::ANY_VALUE ) {
245
			// here, the component deals with empty strings its own way and we return blindly what we got
246 17
			return $value;
247 13
		} elseif ( is_array( $allowedValues ) && in_array( strtolower( $value ), $allowedValues, true ) ) {
248 12
			return $value;
249
		}
250 10
		return false;
251
	}
252
253
	/**
254
	 * @return array
255
	 */
256 49
	private function getInitialAttributeRegister() {
257
		return [
258 49
			'active'      => self::NO_FALSE_VALUE,
259 49
			'class'       => self::ANY_VALUE,
260 49
			'color'       => [ 'default', 'primary', 'success', 'info', 'warning', 'danger' ],
261 49
			'collapsible' => self::NO_FALSE_VALUE,
262 49
			'disabled'    => self::NO_FALSE_VALUE,
263 49
			'dismissible' => self::NO_FALSE_VALUE,
264 49
			'footer'      => self::ANY_VALUE,
265 49
			'heading'     => self::ANY_VALUE,
266 49
			'id'          => self::ANY_VALUE,
267 49
			'link'        => self::ANY_VALUE,
268 49
			'placement'   => [ 'top', 'bottom', 'left', 'right' ],
269 49
			'size'        => [ 'xs', 'sm', 'md', 'lg' ],
270 49
			'style'       => self::ANY_VALUE,
271 49
			'text'        => self::ANY_VALUE,
272 49
			'trigger'     => [ 'default', 'focus', 'hover' ],
273 49
		];
274
275
	}
276
277
	/**
278
	 * Extracts the value for attribute from passed attributes. If attribute itself
279
	 * is not set, it also looks for aliases.
280
	 *
281
	 * @param $attribute
282
	 * @param $passedAttributes
283
	 *
284
	 * @see AttributeManager::ALIASES
285
	 *
286
	 * @return null|string
287
	 */
288 26
	private function getValueForAttribute( $attribute, $passedAttributes ) {
289 26
		if ( isset ( $passedAttributes[$attribute] ) ) {
290 24
			return $passedAttributes[$attribute];
291
		}
292 21
		$definedAliases = self::ALIASES;
293 21
		if ( isset( $definedAliases[$attribute] ) ) {
294 6
			$aliases = (array)$definedAliases[$attribute];
295 6
			foreach ( $aliases as $alias ) {
296 6
				if ( isset( $passedAttributes[$alias] ) ) {
297 2
					return $passedAttributes[$alias];
298
				}
299 5
			}
300 5
		}
301 20
		return null;
302
	}
303
304
	/**
305
	 * @param string $value
306
	 *
307
	 * @return bool|string
308
	 */
309 10
	private function verifyValueForNoValueAttribute( $value ) {
310 10
		if ( in_array( strtolower( $value ), $this->noValues, true ) ) {
311 7
			return false;
312
		}
313 6
		return empty( $value ) ? true : $value;
314
	}
315
}