Completed
Branch master (ecb46d)
by Tobias
01:39
created

Setup::getCompleteHookDefinitionList()   B

Complexity

Conditions 3
Paths 1

Size

Total Lines 77
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 77
ccs 22
cts 22
cp 1
rs 8.9342
c 0
b 0
f 0
cc 3
eloc 20
nc 1
nop 3
crap 3

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Contains the class doing preparing the environment and registering the needed/wanted hooks.
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 \Bootstrap\BootstrapManager;
30
use \Hooks;
31
use \MagicWord;
32
use \MediaWiki\MediaWikiServices;
33
use \MWException;
34
use \Parser;
35
use \ReflectionClass;
36
37
/**
38
 * Class Setup
39
 *
40
 * Registers all hooks and components for Extension BootstrapComponents.
41
 *
42
 * Information on how to add an additional hook
43
 *  1. add it to {@see Setup::AVAILABLE_HOOKS}.
44
 *  2. add an appropriate entry in the array inside {@see Setup::getCompleteHookDefinitionList}
45
 *     with the hook as array key and the callback as value.
46
 *  3. have {@see Setup::compileRequestedHooksListFor} add the hook to its result array. Based on
47
 *     a certain condition, if necessary.
48
 *  4. add appropriate tests to {@see \BootstrapComponents\Tests\Unit\SetupTest}.
49
 *
50
 * @since 1.0
51
 */
52
class Setup {
53
54
	/**
55
	 * @var array
56
	 */
57
	const AVAILABLE_HOOKS = [
58
		'GalleryGetModes', 'ImageBeforeProduceHTML', 'InternalParseBeforeLinks',
59
		'ParserBeforeTidy', 'ParserFirstCallInit', 'ScribuntoExternalLibraries',
60
		'SetupAfterCache'
61
	];
62
63
	/**
64
	 * @var ComponentLibrary
65
	 */
66
	private $componentLibrary;
67
68
	/**
69
	 * @var \Config
70
	 */
71
	private $myConfig;
72
73
	/**
74
	 * @var NestingController
75
	 */
76
	private $nestingController;
77
78
	/**
79
	 * Callback function when extension is loaded via extension.json or composer.
80
	 *
81
	 * Note: With this we omit hook registration in extension.json and define our own here
82
	 * to better allow for unit testing.
83
	 *
84
	 * @param array $info
85
	 *
86
	 * @throws \ConfigException cascading {@see Setup::run}
87
	 * @throws \MWException cascading {@see Setup::__construct()} and {@see Setup::run}
88
	 *
89
	 * @return bool
90
	 */
91 1
	public static function onExtensionLoad( $info ) {
92 1
		$setup = new self( $info );
93
94 1
		$setup->run();
95 1
		return true;
96
	}
97
98
	/**
99
	 * Setup constructor.
100
	 *
101
	 * @param $info
102
	 *
103
	 * @throws \ConfigException cascading {@see \BootstrapComponents\Setup::getHooksToRegister}
104
	 * @throws \MWException cascading {@see \BootstrapComponents\Setup::getHooksToRegister}
105
	 *
106
	 */
107 48
	public function __construct( $info ) {
108
109 48
		$this->assertExtensionBootstrapPresent();
110
111 48
		if ( !empty( $info['version'] ) ) {
112 1
			$this->prepareEnvironment( $info['version'] );
113 1
		}
114
115 48
		$configFactory = MediaWikiServices::getInstance()->getConfigFactory();
116 48
		$this->registerMyConfiguration( $configFactory );
117 48
		$this->myConfig = $configFactory->makeConfig( 'BootstrapComponents' );
118
119 48
		list( $this->componentLibrary, $this->nestingController ) = $this->initializeApplications( $this->myConfig );
120 48
	}
121
122
	/**
123
	 * @param array $hooksToRegister
124
	 *
125
	 * @return array
126
	 */
127 41
	public function buildHookCallbackListFor( $hooksToRegister ) {
128 41
		$hookCallbackList = [];
129 41
		$completeHookDefinitionList = $this->getCompleteHookDefinitionList(
130 41
			$this->myConfig, $this->componentLibrary, $this->nestingController
131 41
		);
132 41
		foreach ( $hooksToRegister as $requestedHook ) {
133 40
			if ( isset( $completeHookDefinitionList[$requestedHook] ) ) {
134 39
				$hookCallbackList[$requestedHook] = $completeHookDefinitionList[$requestedHook];
135 39
			}
136 41
		}
137 41
		return $hookCallbackList;
138
	}
139
140
	/**
141
	 * @throws \MWException cascading {@see \Hooks::clear}
142
	 */
143 21
	public function clear() {
144 21
		foreach ( self::AVAILABLE_HOOKS as $name ) {
145 21
			Hooks::clear( $name );
146 21
		}
147 21
	}
148
149
	/**
150
	 * @param \Config $myConfig
151
	 *
152
	 * @throws \ConfigException cascading {@see \Config::get}
153
	 *
154
	 * @return string[]
155
	 */
156 6
	public function compileRequestedHooksListFor( $myConfig ) {
157 6
		$requestedHookList = [ 'ParserBeforeTidy', 'ParserFirstCallInit', 'SetupAfterCache', 'ScribuntoExternalLibraries' ];
158 6
		if ( $myConfig->has( 'BootstrapComponentsEnableCarouselGalleryMode' )
159 6
			&& $myConfig->get( 'BootstrapComponentsEnableCarouselGalleryMode' )
160 6
		) {
161 4
			$requestedHookList[] = 'GalleryGetModes';
162 4
		}
163 6
		if ( $myConfig->has( 'BootstrapComponentsModalReplaceImageTag' )
164 6
			&& $myConfig->get( 'BootstrapComponentsModalReplaceImageTag' )
165 6
		) {
166 4
			$requestedHookList[] = 'ImageBeforeProduceHTML';
167 4
			$requestedHookList[] = 'InternalParseBeforeLinks';
168 4
		}
169 6
		return $requestedHookList;
170
	}
171
172
	/**
173
	 * @param \Config           $myConfig
174
	 * @param ComponentLibrary  $componentLibrary
175
	 * @param NestingController $nestingController
176
	 *
177
	 * @return \Closure[]
178
	 */
179 42
	public function getCompleteHookDefinitionList( $myConfig, $componentLibrary, $nestingController ) {
180
		return [
181
			/**
182
			 * Hook: GalleryGetModes
183
			 *
184
			 * Allows extensions to add classes that can render different modes of a gallery.
185
			 *
186
			 * @see https://www.mediawiki.org/wiki/Manual:Hooks/GalleryGetModes
187
			 */
188
			'GalleryGetModes'          => function( &$modeArray ) {
189 2
				$modeArray['carousel'] = 'BootstrapComponents\\CarouselGallery';
190 2
				return true;
191 42
			},
192
193
			/**
194
			 * Hook: ImageBeforeProduceHTML
195
			 *
196
			 * Called before producing the HTML created by a wiki image insertion
197
			 *
198
			 * @see https://www.mediawiki.org/wiki/Manual:Hooks/ImageBeforeProduceHTML
199
			 */
200 42
			'ImageBeforeProduceHTML'   => $this->createImageBeforeProduceHTMLCallback( $nestingController, $myConfig ),
201
202
			/**
203
			 * Hook: InternalParseBeforeLinks
204
			 *
205
			 * Used to process the expanded wiki code after <nowiki>, HTML-comments, and templates have been treated.
206
			 *
207
			 * @see https://www.mediawiki.org/wiki/Manual:Hooks/InternalParseBeforeLinks
208
			 */
209 42
			'InternalParseBeforeLinks' => $this->createInternalParseBeforeLinksCallback(),
210
211
			/**
212
			 * Hook: ParserBeforeTidy
213
			 *
214
			 * Used to process the nearly-rendered html code for the page (but before any html tidying occurs).
215
			 *
216
			 * @see https://www.mediawiki.org/wiki/Manual:Hooks/ParserBeforeTidy
217
			 */
218
			'ParserBeforeTidy'         => function( \Parser &$parser, &$text, $parserOutputHelper = null ) {
219
				// injects right before the tidy marker report (e.g. <!-- Tidy found no errors -->), at the very end of the wiki text content
220 22
				if ( is_null( $parserOutputHelper ) ) {
221 21
					$parserOutputHelper = ApplicationFactory::getInstance()->getParserOutputHelper( $parser );
222 21
				}
223 22
				$text .= $parserOutputHelper->getContentForLaterInjection();
224 22
				return true;
225 42
			},
226
227
			/**
228
			 * Hook: ParserFirstCallInit
229
			 *
230
			 * Called when the parser initializes for the first time.
231
			 *
232
			 * @see https://www.mediawiki.org/wiki/Manual:Hooks/ParserFirstCallInit
233
			 */
234 42
			'ParserFirstCallInit'      => $this->createParserFirstCallInitCallback( $componentLibrary, $nestingController ),
235
236
			'ScribuntoExternalLibraries' => function( $engine, &$extraLibraries ) {
237 2
				if ( $engine == 'lua' ) {
238 2
					$extraLibraries['mw.bootstrap'] = 'BootstrapComponents\\LuaLibrary';
239 2
				}
240 2
				return true;
241 42
			},
242
243
			/**
244
			 * Hook: SetupAfterCache
245
			 *
246
			 * Called in Setup.php, after cache objects are set
247
			 *
248
			 * @see https://www.mediawiki.org/wiki/Manual:Hooks/SetupAfterCache
249
			 */
250
			'SetupAfterCache'          => function() {
251 1
				BootstrapManager::getInstance()->addAllBootstrapModules();
252 1
				return true;
253 42
			},
254 42
		];
255
	}
256
257
	/**
258
	 * @param \Config $myConfig
259
	 *
260
	 * @throws \MWException cascading {@see \BootstrapComponents\ApplicationFactory} calls
261
	 * @throws \ConfigException cascading {@see \Config::get}
262
	 *
263
	 * @return array
264
	 */
265 48
	public function initializeApplications( $myConfig ) {
266 48
		$applicationFactory = ApplicationFactory::getInstance();
267 48
		$componentLibrary = $applicationFactory->getComponentLibrary(
268 48
			$myConfig->get( 'BootstrapComponentsWhitelist' )
269 48
		);
270 48
		$nestingController = $applicationFactory->getNestingController();
271 48
		return [ $componentLibrary, $nestingController ];
272
	}
273
274
	/**
275
	 * @param string $hook
276
	 *
277
	 * @return boolean
278
	 */
279 5
	public function isRegistered( $hook ) {
280 5
		return Hooks::isRegistered( $hook );
281
	}
282
283
	/**
284
	 * @param array $hookList
285
	 *
286
	 * @return int
287
	 */
288 27
	public function register( $hookList ) {
289 27
		foreach ( $hookList as $hook => $callback ) {
290 27
			Hooks::register( $hook, $callback );
291 27
		}
292 27
		return count( $hookList );
293
	}
294
295
	/**
296
	 * @param \ConfigFactory $configFactory
297
	 * Registers my own configuration, so that it is present during onLoad. See phabricator issue T184837
298
	 *
299
	 * @see https://phabricator.wikimedia.org/T184837
300
	 */
301 48
	public function registerMyConfiguration( $configFactory ) {
302 48
		$configFactory->register( 'BootstrapComponents', 'GlobalVarConfig::newInstance' );
303 48
	}
304
305
	/**
306
	 * Executes the setup process.
307
	 *
308
	 * @throws \ConfigException
309
	 *
310
	 * @return int
311
	 */
312 2
	public function run() {
313 2
		$requestedHooks = $this->compileRequestedHooksListFor(
314 2
			$this->myConfig
315 2
		);
316 2
		$hookCallbackList = $this->buildHookCallbackListFor(
317
			$requestedHooks
318 2
		);
319
320 2
		return $this->register( $hookCallbackList );
321
	}
322
323
	/**
324
	 * @throws \MWException
325
	 */
326 48
	private function assertExtensionBootstrapPresent() {
327 48
		if ( !defined( 'BS_VERSION' ) ) {
328
			echo 'The BootstrapComponents extension requires Extension Bootstrap to be installed. '
329
				. 'Please check <a href="https://github.com/oetterer/BootstrapComponents/">the online help</a>' . PHP_EOL;
330
			throw new MWException( 'BootstrapComponents needs extension Bootstrap present.' );
331
		}
332 48
	}
333
334
	/**
335
	 * Callback for Hook: ImageBeforeProduceHTML
336
	 *
337
	 * Called before producing the HTML created by a wiki image insertion
338
	 *
339
	 * @param NestingController $nestingController
340
	 * @param \Config           $myConfig
341
	 *
342
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/ImageBeforeProduceHTML
343
	 *
344
	 * @return \Closure
345
	 */
346 42
	private function createImageBeforeProduceHTMLCallback( $nestingController, $myConfig ) {
347
348
		return function( &$dummy, &$title, &$file, &$frameParams, &$handlerParams, &$time, &$res
349
		) use ( $nestingController, $myConfig ) {
350
351 11
			$imageModal = new ImageModal( $dummy, $title, $file, $nestingController );
352
353 11
			if ( $myConfig->has( 'BootstrapComponentsDisableSourceLinkOnImageModal' )
354 11
				&& $myConfig->get( 'BootstrapComponentsDisableSourceLinkOnImageModal' )
355 11
			) {
356
				$imageModal->disableSourceLink();
357
			}
358
359 11
			return $imageModal->parse( $frameParams, $handlerParams, $time, $res );
360 42
		};
361
	}
362
363
	/**
364
	 * Callback for Hook: InternalParseBeforeLinks
365
	 *
366
	 * Used to process the expanded wiki code after <nowiki>, HTML-comments, and templates have been treated.
367
	 *
368
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/InternalParseBeforeLinks
369
	 *
370
	 * @return \Closure
371
	 */
372 42
	private function createInternalParseBeforeLinksCallback() {
373
		return function( Parser &$parser, &$text ) {
374 22
			$mw = MagicWord::get( 'BSC_NO_IMAGE_MODAL' );
375
			// we do not use our ParserOutputHelper class here, for we would need to reset it in integration tests.
376
			// resetting our factory build classes is unfortunately a little skittish
377 22
			$parser->getOutput()->setExtensionData(
378 22
				'bsc_no_image_modal',
379 22
				$mw->matchAndRemove( $text )
380 22
			);
381 22
			return true;
382 42
		};
383
	}
384
385
	/**
386
	 * Callback for Hook: ParserFirstCallInit
387
	 *
388
	 * Called when the parser initializes for the first time.
389
	 *
390
	 * @param ComponentLibrary  $componentLibrary
391
	 * @param NestingController $nestingController
392
	 *
393
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/ParserFirstCallInit
394
	 *
395
	 * @return \Closure
396
	 */
397 42
	private function createParserFirstCallInitCallback( $componentLibrary, $nestingController ) {
398
399
		return function( Parser $parser ) use ( $componentLibrary, $nestingController ) {
400
401 2
			$parserOutputHelper = ApplicationFactory::getInstance()->getParserOutputHelper( $parser );
402
403 2
			foreach ( $componentLibrary->getRegisteredComponents() as $componentName ) {
404
405 2
				$parserHookString = $componentLibrary::compileParserHookStringFor( $componentName );
406 2
				$callback = $this->createParserHookCallbackFor(
407 2
					$componentName, $componentLibrary, $nestingController, $parserOutputHelper
408 2
				);
409
410 2
				if ( $componentLibrary->isParserFunction( $componentName ) ) {
411 2
					$parser->setFunctionHook( $parserHookString, $callback );
412 2
				} elseif ( $componentLibrary->isTagExtension( $componentName ) ) {
413 2
					$parser->setHook( $parserHookString, $callback );
414 2
				} else {
415
					wfDebugLog(
416
						'BootstrapComponents',
417
						'Unknown handler type (' . $componentLibrary->getHandlerTypeFor( $componentName )
418
						. ') detected for component ' . $parserHookString
419
					);
420
				}
421 2
			}
422 2
			return true;
423 42
		};
424
	}
425
426
	/**
427
	 * Creates the callback to be registered with {@see \Parser::setFunctionHook} or {@see \Parser::setHook}.
428
	 *
429
	 * @param string             $componentName
430
	 * @param ComponentLibrary   $componentLibrary
431
	 * @param NestingController  $nestingController
432
	 * @param ParserOutputHelper $parserOutputHelper
433
	 *
434
	 * @return \Closure
435
	 */
436
	private function createParserHookCallbackFor( $componentName, $componentLibrary, $nestingController, $parserOutputHelper ) {
437
438 16
		return function() use ( $componentName, $componentLibrary, $nestingController, $parserOutputHelper ) {
439
440 16
			$componentClass = $componentLibrary->getClassFor( $componentName );
441 16
			$objectReflection = new ReflectionClass( $componentClass );
442 16
			$object = $objectReflection->newInstanceArgs( [ $componentLibrary, $parserOutputHelper, $nestingController ] );
443
444 16
			$parserRequest = ApplicationFactory::getInstance()->getNewParserRequest(
445 16
				func_get_args(),
446 16
				$componentLibrary->isParserFunction( $componentName ),
447
				$componentName
448 16
			);
449
			/** @var AbstractComponent $object */
450 16
			return $object->parseComponent( $parserRequest );
451 2
		};
452
	}
453
454
	/**
455
	 * Version number retrieved from extension info array.
456
	 *
457
	 * @param string $version
458
	 *
459
	 * @return bool
460
	 */
461 1
	private function prepareEnvironment( $version ) {
462 1
		return @define( 'BOOTSTRAP_COMPONENTS_VERSION', (string) $version );
463
	}
464
}
465