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

ComponentLibrary::getComponentDataStore()   B

Complexity

Conditions 9
Paths 19

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 9

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 24
cts 24
cp 1
rs 8.0555
c 0
b 0
f 0
cc 9
nc 19
nop 0
crap 9
1
<?php
2
/**
3
 * Contains class holding and distributing information about all available components.
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
use \MediaWiki\MediaWikiServices;
30
use \MWException;
31
32
/**
33
 * Class ComponentLibrary
34
 *
35
 * Holds information about all registered components
36
 *
37
 * @since 1.0
38
 */
39
class ComponentLibrary {
40
	/**
41
	 * @var array
42
	 */
43
	const DEFAULT_ATTRIBUTES = [ 'class', 'id', 'style' ];
44
45
	/**
46
	 * File, that holds the component definition list.
47
	 *
48
	 * @var string
49
	 */
50
	const DEFINITIONS_FILE = __DIR__ . DIRECTORY_SEPARATOR . 'ComponentsDefinition.json';
51
52
	/**
53
	 * @var string
54
	 */
55
	const HANDLER_TYPE_PARSER_FUNCTION = 'function';
56
57
	/**
58
	 * @var string
59
	 */
60
	const HANDLER_TYPE_TAG_EXTENSION = 'tag';
61
62
	/**
63
	 * @var string
64
	 */
65
	const PARSER_HOOK_PREFIX = 'bootstrap_';
66
67
	/**
68
	 * This array holds all the data for all known components, whether they are registered or not.
69
	 *
70
	 * Array has form
71
	 * <pre>
72
	 *  "componentIdentifier" => [
73
	 *      "class" => <className>,
74
	 *      "name" => <componentName>
75
	 *      "handlerType" => <handlerType>,
76
	 *      "attributes" => [ "attr1", "attr2", ... ],
77
	 *      "aliases" => [ "alias" => "attribute", ... ]
78
	 *      "modules" => [
79
	 *          "default" => [ "module1", "module2", ... ],
80
	 *          "<skin>" => [ "module1", "module2", ... ],
81
	 *      ]
82
	 *  ]
83
	 * </pre>
84
	 *
85
	 * @var array $componentDataStore
86
	 */
87
	private $componentDataStore;
88
89
	/**
90
	 * The list of registered/allowed bootstrap components, name or alias
91
	 *
92
	 * @var string[] $registeredComponents
93
	 */
94
	private $registeredComponents;
95
96
	/**
97
	 * @param string $componentName
98
	 *
99 15
	 * @return string
100 15
	 */
101
	public static function compileParserHookStringFor( $componentName ) {
102
		return self::PARSER_HOOK_PREFIX . strtolower( $componentName );
103
	}
104
105
	/**
106
	 * ComponentLibrary constructor.
107
	 *
108
	 * Do not instantiate directly, but use {@see ApplicationFactory::getComponentLibrary} instead.
109
	 *
110
	 * @param bool|array $componentWhiteList (see {@see \BootstrapComponents\ComponentLibrary::$componentWhiteList})
111
	 *
112
	 * @throws \ConfigException cascading {@see \ConfigFactory::makeConfig} and
113
	 * @see ApplicationFactory::getComponentLibrary
114 87
	 *
115
	 */
116 87
	public function __construct( $componentWhiteList = null ) {
117 82
118
		if ( is_null( $componentWhiteList ) ) {
119 82
			$myConfig = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'BootstrapComponents' );
120 82
121 82
			$componentWhiteList = $myConfig->has( 'BootstrapComponentsWhitelist' )
122 82
				? $myConfig->get( 'BootstrapComponentsWhitelist' )
123 87
				: true;
124 87
		}
125 87
		$componentWhiteList = $this->mangle( $componentWhiteList );
126 87
		$this->registeredComponents = $this->registerComponents( $componentWhiteList );
127
	}
128
129
	/**
130
	 * Compiles an array for all bootstrap component parser functions to be uses in the BootstrapComponents.magic.php file.
131
	 *
132
	 * @return array
133 1
	 */
134 1
	public function compileMagicWordsArray() {
135 1
		$magicWords = [];
136 1
		foreach ( $this->getRegisteredComponents() as $componentName ) {
137 1
			if ( $this->isParserFunction( $componentName ) ) {
138 1
				$magicWords[self::compileParserHookStringFor( $componentName )]
139 1
					= [ 0, self::compileParserHookStringFor( $componentName ) ];
140 1
			}
141 1
		}
142
		return $magicWords;
143
	}
144
145
	/**
146
	 * Checks, if component $componentIdentifier is registered
147
	 *
148
	 * @param string $componentIdentifier
149
	 *
150
	 * @return bool
151 46
	 */
152 46
	public function isRegistered( $componentIdentifier ) {
153
		return in_array( $componentIdentifier, $this->registeredComponents, true );
154
	}
155
156
	/**
157
	 * Returns the defined/allowed attribute aliases for component/alias $componentIdentifier.
158
	 *
159
	 * @param string $componentIdentifier
160
	 *
161 21
	 * @return array
162 21
	 * @throws MWException provided component is not known
163 1
	 */
164
	public function getAliasesFor( $componentIdentifier ) {
165 20
		return $this->accessComponentDataStore( $componentIdentifier, 'aliases' );
166
	}
167
168
	/**
169
	 * Returns the defined/allowed attributes for component/alias $componentIdentifier.
170
	 *
171
	 * @param string $componentIdentifier
172
	 *
173
	 * @return array
174
	 * @throws MWException provided component is not known
175
	 */
176 32
	public function getAttributesFor( $componentIdentifier ) {
177 32
		return $this->accessComponentDataStore( $componentIdentifier, 'attributes' );
178 2
	}
179
180 30
	/**
181
	 * Returns class name for a registered component/alias.
182
	 *
183
	 * @param string $componentIdentifier
184
	 *
185
	 * @return string
186
	 */
187
	public function getClassFor( $componentIdentifier ) {
188
		return $this->accessComponentDataStore( $componentIdentifier, 'class' );
189
	}
190
191
	/**
192 38
	 * Returns handler type for a registered component/alias. 'UNKNOWN' if unknown component.
193 38
	 *
194 1
	 * @param string $componentIdentifier
195
	 *
196 37
	 * @return string
197
	 * @see \BootstrapComponents\ComponentLibrary::HANDLER_TYPE_PARSER_FUNCTION,
198
	 *      \BootstrapComponents\ComponentLibrary::HANDLER_TYPE_TAG_EXTENSION
199
	 *
200
	 */
201
	public function getHandlerTypeFor( $componentIdentifier ) {
202
		try {
203
			return $this->accessComponentDataStore( $componentIdentifier, 'handlerType' );
204 4
		} catch ( MWException $e ) {
0 ignored issues
show
Bug introduced by
The class MWException does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
205 4
			return 'UNKNOWN';
206
		}
207
	}
208
209
	/**
210
	 * Returns an array of all the known components' names, _excluding_ aliases,
211
	 *
212
	 * @return array
213
	 */
214
	public function getKnownComponents() {
215
		return array_keys( $this->getComponentDataStore() );
216
	}
217 30
218 30
	/**
219 30
	 * Returns all the needed modules for a component/alias. False, if there are none.
220 30
	 * If skin is set, returns all modules especially registered for that skin as well
221 30
	 *
222 24
	 * @param string $componentIdentifier
223
	 * @param string $skin
224 11
	 *
225 11
	 * @return array
226 11
	 */
227 11
	public function getModulesFor( $componentIdentifier, $skin = null ) {
228
		$allModules = $this->accessComponentDataStore( $componentIdentifier, 'modules' );
229
230
		$modules = isset( $allModules['default'] )
231
			? (array) $allModules['default']
232
			: [];
233
		if ( $skin === null || !isset( $allModules[$skin] ) ) {
234
			return $modules;
235
		}
236
		return array_merge(
237
			$modules,
238 32
			(array) $allModules[$skin]
239 32
		);
240 1
	}
241
242 31
	/**
243
	 * Returns the component name for a given class.
244
	 *
245
	 * @param string $componentClass
246
	 *
247
	 * @throws MWException if supplied class is not registered
248
	 *
249
	 * @return string
250 8
	 */
251 8
	public function getNameFor( $componentClass ) {
252
		$component = null;
253
254
		// if $componentClass is not in values in $this->registeredComponentClasses, this has to fail
255
		foreach ( $this->getComponentDataStore() as $componentIdentifier => $componentData ) {
256
			if ( isset( $componentData['class'] ) && ( $componentData['class'] == $componentClass ) && isset( $componentData['name'] ) ) {
257
				$component = $componentData['name'];
258
				break;
259
			}
260
		}
261 22
		if ( is_null( $component ) ) {
262 22
			throw new MWException( 'Trying to get a component name for unregistered class "' . (string) $componentClass . '"!' );
263
		}
264
		return $this->accessComponentDataStore( $component, 'name' );
265
	}
266
267
	/**
268
	 * Returns an array of all the registered component's names. Including aliases.
269
	 *
270
	 * @return string[]
271
	 */
272 6
	public function getRegisteredComponents() {
273 6
		return $this->registeredComponents;
274
	}
275
276
	/**
277
	 * True, if referenced component is registered as parser function.
278
	 *
279
	 * @param string $componentName
280
	 *
281
	 * @return bool
282
	 */
283 87
	public function isParserFunction( $componentName ) {
284 87
		return $this->getHandlerTypeFor( $componentName ) == self::HANDLER_TYPE_PARSER_FUNCTION;
285 84
	}
286
287 3
	/**
288 3
	 * True, if referenced component is registered as tag extension.
289 3
	 *
290 3
	 * @param string $componentName
291 3
	 *
292
	 * @return bool
293
	 */
294
	public function isTagExtension( $componentName ) {
295
		return $this->getHandlerTypeFor( $componentName ) == self::HANDLER_TYPE_TAG_EXTENSION;
296
	}
297
298
	/**
299
	 * @param string $componentIdentifier
300
	 * @param string $field
301
	 *
302 87
	 * @throws MWException on non existing $componentIdentifier or $field
303 87
	 * @return mixed
304 87
	 */
305 87
	protected function accessComponentDataStore( $componentIdentifier, $field ) {
306 87
		if ( !isset( $this->getComponentDataStore()[$componentIdentifier][$field] ) ) {
307 87
			throw new MWException(
308
				'Trying to access undefined field \'' . $field . '\' of component \'' . $componentIdentifier . '\'. Aborting'
309 87
			);
310 87
		}
311 87
		return $this->getComponentDataStore()[$componentIdentifier][$field];
312 87
	}
313 87
314
	/**
315
	 * Sees to it, that the whitelist (if it is an array) contains only lowercase strings.
316
	 *
317
	 * @param bool|array $componentWhiteList
318
	 *
319
	 * @return bool|array
320
	 */
321
	private function mangle( $componentWhiteList ) {
322
		if ( !is_array( $componentWhiteList ) ) {
323 87
			return $componentWhiteList;
324 87
		}
325 87
		$newWhiteList = [];
326 87
		foreach ( $componentWhiteList as $element ) {
327 87
			$newWhiteList[] = strtolower( trim( $element ) );
328
		}
329 87
		return $newWhiteList;
330 87
	}
331
332 87
	/**
333
	 * This adds the default attributes to the attribute list.
334 4
	 *
335
	 * @param array $componentAttributes
336
	 *
337 86
	 * @return array
338 86
	 */
339 87
	private function normalizeAttributes( $componentAttributes ) {
340
		$componentAttributes = (array) $componentAttributes;
341 87
		$componentAttributes = array_unique(
342
			array_merge(
343
				$componentAttributes,
344
				self::DEFAULT_ATTRIBUTES
345
			)
346
		);
347
		return $componentAttributes;
348
	}
349 87
350
	/**
351
	 * Generates the array for registered components containing all whitelisted component identifiers
352 87
	 *
353 87
	 * @param bool|array $componentWhiteList
354
	 *
355 87
	 * @return string[] list of registered component identifiers
356 87
	 */
357 87
	private function registerComponents( $componentWhiteList ) {
358
		$registeredComponents = [];
359 87
		foreach ( $this->getKnownComponents() as $componentIdentifier ) {
360 87
361
			if ( !$componentWhiteList || (is_array( $componentWhiteList ) && !in_array( $componentIdentifier, $componentWhiteList )) ) {
362 87
				// if $componentWhiteList is false, or and array and does not contain the $componentIdentifier, we will not register it
363 87
				continue;
364 87
			}
365 87
			$registeredComponents[] = $componentIdentifier;
366
		}
367 87
		return $registeredComponents;
368 87
	}
369 87
370
	/**
371 87
	 * Raw library data used in registration process.
372 87
	 *
373
	 * @return array
374 87
	 */
375 87
	private function getComponentDataStore() {
376 87
		if ( !empty( $this->componentDataStore ) ) {
377
			return $this->componentDataStore;
378 87
		}
379 87
		$rawData = json_decode( file_get_contents( self::DEFINITIONS_FILE ), JSON_OBJECT_AS_ARRAY );
380
381 87
		$componentAliases = [];
382 87
		$componentDataStore = [];
383 87
		foreach ( $rawData as $componentName => $componentData ) {
384 87
385 87
			if ( !is_array( $componentData ) ) {
386 87
				$componentAliases[$componentName] = trim( (string) $componentData );
387 87
				continue;
388
			}
389 87
390 87
			$componentData['name'] = $componentName;
391 87
			$componentData['attributes'] = $this->normalizeAttributes(
392
				(isset( $componentData['attributes'] ) ? $componentData['attributes'] : [])
393 87
			);
394 87
			$componentData['aliases'] = isset( $componentData['aliases'] ) ? $componentData['aliases'] : [];
395
			$componentData['modules'] = isset( $componentData['modules'] ) ? $componentData['modules'] : [];
396 87
			$componentDataStore[$componentName] = $componentData;
397 87
		}
398
399 87
		foreach ( $componentAliases as $alias => $componentName ) {
400 87
			if ( isset( $componentDataStore[$componentName] ) ) {
401 87
				$componentDataStore[$alias] = $componentDataStore[$componentName];
402
			}
403 87
		}
404 87
405
		return $this->componentDataStore = $componentDataStore;
406 87
	}
407
}
408