Completed
Push — master ( 7aacb2...2b39b6 )
by
unknown
06:55
created

AttributeManager   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 256
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 31
lcom 1
cbo 0
dl 0
loc 256
ccs 73
cts 73
cp 1
rs 9.92
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getAllKnownAttributes() 0 3 1
A isValid() 0 3 1
A isSuppliedInRequest() 0 14 6
A validateAttributeAndValue() 0 5 1
B calculateValidAttributeNames() 0 22 6
A getAllowedValuesFor() 0 6 2
A isInRegister() 0 3 1
A resolveAttributeName() 0 5 2
A validateValueForAttribute() 0 12 5
A getAttributeRegister() 0 24 1
A verifyValueForNoValueAttribute() 0 9 4
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 AttributeManager
31
 *
32
 * @since 1.0
33
 */
34
class AttributeManager {
35
36
	/**
37
	 * For attributes that take any value.
38
	 *
39
	 * @var int
40
	 */
41
	const ANY_VALUE = 1;
42
43
	/**
44
	 * For attributes that can be set to false by supplying one of certain values.
45
	 * Usually uses for flag-attributes like "active", "collapsible", etc.
46
	 *
47
	 * @see \BootstrapComponents\AttributeManager::$noValues
48
	 *
49
	 * @var int
50
	 */
51
	const NO_FALSE_VALUE = 0;
52
53
	/**
54
	 * Holds all values indicating a "no". Can be used to ignore "enable"-fields.
55
	 *
56
	 * @var array $noValues
57
	 */
58
	private $noValues;
59
60
	/**
61
	 * The list of attributes that are considered valid; holds alias relation 'name|alias' => 'real name'
62
	 *
63
	 * @var string[] $validAttributeNameMapping
64
	 */
65
	private $validAttributeNameMapping;
66
67
	#@todo need a method: attributeIsSupplied(). for components must be able to check, if "header" was supplied and don't know about aliases
68
69
	/**
70
	 * AttributeManager constructor.
71
	 *
72
	 * Do not instantiate directly, but use {@see ApplicationFactory::getNewAttributeManager}
73
	 * instead.
74
	 *
75
	 * @param string[] $componentAttributes the list of attributes, this manager deems valid.
76
	 * @param string[] $aliases             the list of aliases and their corresponding attribute
77
	 *
78
	 * @see ApplicationFactory::getNewAttributeManager
79
	 */
80
	public function __construct( $componentAttributes, $aliases ) {
81
		$this->noValues = [ false, 0, '0', 'no', 'false', 'off', 'disabled', 'ignored' ];
82
		$this->noValues[] = strtolower( wfMessage( 'confirmable-no' )->text() );
83
		$this->validAttributeNameMapping = $this->calculateValidAttributeNames( $componentAttributes, $aliases );
84
	}
85
86
	/**
87
	 * Returns the list of all defined attributes, including those that are invalid for the current component.
88
	 *
89
	 * @return string[]
90
	 */
91
	public function getAllKnownAttributes() {
92
		return array_keys( $this->getAttributeRegister() );
93
	}
94 49
95 49
	/**
96 49
	 * Checks if given $attribute is registered with the manager. Note that an alias is deemed valid.
97 49
	 *
98 49
	 * @param string $attribute
99
	 *
100
	 * @return bool
101
	 */
102
	public function isValid( $attribute ) {
103
		return isset( $this->validAttributeNameMapping[$attribute] );
104
	}
105 1
106 1
	/**
107
	 * Checks, if the attribute $neededAttribute or its alias is found in $suppliedAttributes.
108
	 *
109
	 * @param string   $neededAttribute
110
	 * @param string[] $suppliedAttributes
111
	 *
112
	 * @return bool
113
	 */
114
	public function isSuppliedInRequest( $neededAttribute, $suppliedAttributes ) {
115
		if ( !is_array( $suppliedAttributes ) || empty( $suppliedAttributes ) ) {
116
			return false;
117
		}
118
		if ( in_array( $neededAttribute, $suppliedAttributes ) ) {
119 44
			return true;
120 44
		}
121 1
		foreach ( $suppliedAttributes as $suppliedAttribute ) {
122
			if ( $this->resolveAttributeName( $suppliedAttribute ) == $neededAttribute ) {
123 43
				return true;
124
			}
125
		}
126
		return false;
127
	}
128
129 27
	/**
130 27
	 * Extracts the _verified_ value for attribute from passed attributes. When attribute is an alias, it returns the real attribute name
131
	 * together with the aliases value.
132
	 *
133
	 * Note:
134
	 *  * To make certain, that $attribute is valid, use {@see \BootstrapComponents\AttributeManager::isValid} beforehand.
135
	 *  * If there is no data for $attribute in $passedValue, or the value does not pass verification, this returns null as $passedValue.
136
	 *
137
	 * @param string $attribute   the attribute|alias, data will be extracted for
138
	 * @param string $passedValue must be already parsed
139
	 *
140 44
	 * @return array ( (string) $attributeName, (null|string) $passedValue );
141 44
	 */
142
	public function validateAttributeAndValue( $attribute, $passedValue ) {
143
		$attributeRealName = $this->resolveAttributeName( $attribute );
144
		$verifiedValue = $this->validateValueForAttribute( $attributeRealName, $passedValue );
145
		return [ $attributeRealName, $verifiedValue ];
146
	}
147
148
	/**
149
	 * Registers all valid attribute names and all aliases.
150
	 *
151
	 * @param string[] $validAttributes
152
	 * @param string[] $aliases
153
	 *
154
	 * @return array $filteredValidAttributeNameMapping
155
	 * @see AttributeManager::getAttributeRegister
156
	 *
157
	 */
158
	protected function calculateValidAttributeNames( $validAttributes, $aliases ) {
159
		$filteredValidAttributeNameMapping = [];
160
		foreach ( $validAttributes as $validAttribute ) {
161
			$validAttribute = strtolower( trim( $validAttribute ) );
162 4
			if ( $this->isInRegister( $validAttribute ) ) {
163 4
				$filteredValidAttributeNameMapping[$validAttribute] = $validAttribute;
164 1
			}
165
		}
166 4
		foreach ( $aliases as $alias => $attributeName ) {
167 1
			$attributeName = strtolower( trim( $attributeName ) );
168
			if ( !isset( $filteredValidAttributeNameMapping[$attributeName] ) ) {
169 3
				throw new \LogicException(
170 3
					'Alias \'' . $alias . '\' points to an invalid attribute \''
171
					. $attributeName . '\'. Cannot initialize AttributeManager!'
172
				);
173
			}
174
			if ( !isset( $filteredValidAttributeNameMapping[$alias] ) ) {
175
				$filteredValidAttributeNameMapping[$alias] = $attributeName;
176
			}
177
		}
178
		return $filteredValidAttributeNameMapping;
179
	}
180
181
	/**
182
	 * Returns the allowed values for a given attribute or NULL if invalid attribute. Note that an alias is deemed "valid".
183
	 *
184
	 * Note that allowed values can be an array, {@see AttributeManager::NO_FALSE_VALUE},
185
	 * or {@see AttributeManager::ANY_VALUE}.
186
	 *
187
	 * @param string $attribute
188 27
	 *
189 27
	 * @return null|array|bool
190 27
	 */
191 26
	protected function getAllowedValuesFor( $attribute ) {
192 26
		if ( !$this->isValid( $attribute ) ) {
193 20
			return null;
194 20
		}
195 25
		return $this->getAttributeRegister()[$this->validAttributeNameMapping[$attribute]];
196
	}
197 27
198 27
	/**
199
	 * @param string $attribute
200
	 *
201
	 * @return bool
202
	 */
203
	protected function isInRegister( $attribute ) {
204
		return isset( $this->getAttributeRegister()[$attribute] );
205
	}
206
207
	/**
208
	 * Resolves aliases to their mapped attribute. If $attributeName is unknown, it is returned.
209
	 *
210
	 * @param string $attributeName
211
	 *
212 49
	 * @return string
213 49
	 */
214 49
	protected function resolveAttributeName( $attributeName ) {
215 49
		return isset( $this->validAttributeNameMapping[$attributeName] )
216 49
			? $this->validAttributeNameMapping[$attributeName]
217 42
			: $attributeName;
218 42
	}
219 41
220 41
	/**
221 41
	 * For a given attribute, this verifies, if value is allowed. If verification succeeds, the value will be returned, null otherwise.
222 49
	 * If an attribute is registered as NO_FALSE_VALUE and value is the empty string, it gets converted to true.
223 49
	 *
224
	 * Note: an ANY_VALUE attribute can still be the empty string.
225
	 * Note: that every value for an unregistered attribute fails verification automatically.
226
	 *
227
	 * @param string $attribute
228
	 * @param string $value
229
	 *
230
	 * @return null|bool|string
231
	 */
232
	protected function validateValueForAttribute( $attribute, $value ) {
233
		$allowedValues = $this->getAllowedValuesFor( $attribute );
234
		if ( $allowedValues === self::NO_FALSE_VALUE ) {
235
			return $this->verifyValueForNoValueAttribute( $value );
236
		} elseif ( $allowedValues === self::ANY_VALUE ) {
237
			// here, the component deals with empty strings its own way and we return blindly what we got
238 25
			return $value;
239 25
		} elseif ( is_array( $allowedValues ) && in_array( strtolower( $value ), $allowedValues, true ) ) {
240 25
			return $value;
241 10
		}
242 20
		return null;
243
	}
244 17
245 13
	/**
246 12
	 * @return array
247
	 */
248 10
	private function getAttributeRegister() {
249
		return [
250
			'active'      => self::NO_FALSE_VALUE,
251
			'background'  => [ 'primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark', 'white', ],
252
			'class'       => self::ANY_VALUE,
253
			'color'       => [ 'default', 'primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark', 'white', ],
254 49
			'collapsible' => self::NO_FALSE_VALUE,
255
			'disabled'    => self::NO_FALSE_VALUE,
256 49
			'dismissible' => self::NO_FALSE_VALUE,
257 49
			'fade'        => self::NO_FALSE_VALUE,
258 49
			'footer'      => self::ANY_VALUE,
259 49
			'header'      => self::ANY_VALUE,
260 49
			'id'          => self::ANY_VALUE,
261 49
			'link'        => self::ANY_VALUE,
262 49
			'outline'     => self::NO_FALSE_VALUE,
263 49
			'pill'        => self::NO_FALSE_VALUE,
264 49
			'placement'   => [ 'top', 'bottom', 'left', 'right' ],
265 49
			'size'        => [ 'xs', 'sm', 'md', 'lg' ],
266 49
			'style'       => self::ANY_VALUE,
267 49
			'text'        => self::ANY_VALUE,
268 49
			'trigger'     => [ 'default', 'focus', 'hover' ],
269 49
		];
270 49
271 49
	}
272
273
	/**
274
	 * If $value is a no-value, this returns false. if then $value is empty(), this returns true, $value otherwise.
275
	 *
276
	 * @param null|string $value
277
	 *
278
	 * @return bool|string
279
	 */
280
	private function verifyValueForNoValueAttribute( $value ) {
281
		if ( is_string( $value ) ) {
282
			$value = trim( strtolower( $value ) );
283
		}
284
		if ( in_array( $value, $this->noValues, true ) ) {
285
			return false;
286 26
		}
287 26
		return empty( $value ) ? true : $value;
288 24
	}
289
}
290