Completed
Pull Request — master (#2)
by Tobias
05:56
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 47
	public function __construct( $info ) {
108
109 47
		$this->assertExtensionBootstrapPresent();
110
111 47
		if ( !empty( $info['version'] ) ) {
112 1
			$this->prepareEnvironment( $info['version'] );
113 1
		}
114
115 47
		$configFactory = MediaWikiServices::getInstance()->getConfigFactory();
116 47
		$this->registerMyConfiguration( $configFactory );
117 47
		$this->myConfig = $configFactory->makeConfig( 'BootstrapComponents' );
118
119 47
		list( $this->componentLibrary, $this->nestingController ) = $this->initializeApplications( $this->myConfig );
120 47
	}
121
122
	/**
123
	 * @param array $hooksToRegister
124
	 *
125
	 * @return array
126
	 */
127 40
	public function buildHookCallbackListFor( $hooksToRegister ) {
128 40
		$hookCallbackList = [];
129 40
		$completeHookDefinitionList = $this->getCompleteHookDefinitionList(
130 40
			$this->myConfig, $this->componentLibrary, $this->nestingController
131 40
		);
132 40
		foreach ( $hooksToRegister as $requestedHook ) {
133 39
			if ( isset( $completeHookDefinitionList[$requestedHook] ) ) {
134 38
				$hookCallbackList[$requestedHook] = $completeHookDefinitionList[$requestedHook];
135 38
			}
136 40
		}
137 40
		return $hookCallbackList;
138
	}
139
140
	/**
141
	 * @throws \MWException cascading {@see \Hooks::clear}
142
	 */
143 20
	public function clear() {
144 20
		foreach ( self::AVAILABLE_HOOKS as $name ) {
145 20
			Hooks::clear( $name );
146 20
		}
147 20
	}
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 = [ '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
			$requestedHookList[] = 'ParserBeforeTidy';
169 4
		}
170 6
		return $requestedHookList;
171
	}
172
173
	/**
174
	 * @param \Config           $myConfig
175
	 * @param ComponentLibrary  $componentLibrary
176
	 * @param NestingController $nestingController
177
	 *
178
	 * @return \Closure[]
179
	 */
180 41
	public function getCompleteHookDefinitionList( $myConfig, $componentLibrary, $nestingController ) {
181
		return [
182
			/**
183
			 * Hook: GalleryGetModes
184
			 *
185
			 * Allows extensions to add classes that can render different modes of a gallery.
186
			 *
187
			 * @see https://www.mediawiki.org/wiki/Manual:Hooks/GalleryGetModes
188
			 */
189
			'GalleryGetModes'          => function( &$modeArray ) {
190 2
				$modeArray['carousel'] = 'BootstrapComponents\\CarouselGallery';
191 2
				return true;
192 41
			},
193
194
			/**
195
			 * Hook: ImageBeforeProduceHTML
196
			 *
197
			 * Called before producing the HTML created by a wiki image insertion
198
			 *
199
			 * @see https://www.mediawiki.org/wiki/Manual:Hooks/ImageBeforeProduceHTML
200
			 */
201 41
			'ImageBeforeProduceHTML'   => $this->createImageBeforeProduceHTMLCallback( $nestingController, $myConfig ),
202
203
			/**
204
			 * Hook: InternalParseBeforeLinks
205
			 *
206
			 * Used to process the expanded wiki code after <nowiki>, HTML-comments, and templates have been treated.
207
			 *
208
			 * @see https://www.mediawiki.org/wiki/Manual:Hooks/InternalParseBeforeLinks
209
			 */
210 41
			'InternalParseBeforeLinks' => $this->createInternalParseBeforeLinksCallback(),
211
212
			/**
213
			 * Hook: ParserBeforeTidy
214
			 *
215
			 * Used to process the nearly-rendered html code for the page (but before any html tidying occurs).
216
			 *
217
			 * @see https://www.mediawiki.org/wiki/Manual:Hooks/ParserBeforeTidy
218
			 */
219
			'ParserBeforeTidy'         => function( \Parser &$parser, &$text, $parserOutputHelper = null ) {
220
				// injects right before the tidy marker report (e.g. <!-- Tidy found no errors -->), at the very end of the wiki text content
221 21
				if ( is_null( $parserOutputHelper ) ) {
222 20
					$parserOutputHelper = ApplicationFactory::getInstance()->getParserOutputHelper( $parser );
223 20
				}
224 21
				$text .= $parserOutputHelper->getContentForLaterInjection();
225 21
				return true;
226 41
			},
227
228
			/**
229
			 * Hook: ParserFirstCallInit
230
			 *
231
			 * Called when the parser initializes for the first time.
232
			 *
233
			 * @see https://www.mediawiki.org/wiki/Manual:Hooks/ParserFirstCallInit
234
			 */
235 41
			'ParserFirstCallInit'      => $this->createParserFirstCallInitCallback( $componentLibrary, $nestingController ),
236
237
			'ScribuntoExternalLibraries' => function( $engine, &$extraLibraries ) {
238 2
				if ( $engine == 'lua' ) {
239 2
					$extraLibraries['mw.bootstrap'] = 'BootstrapComponents\\LuaLibrary';
240 2
				}
241 2
				return true;
242 41
			},
243
244
			/**
245
			 * Hook: SetupAfterCache
246
			 *
247
			 * Called in Setup.php, after cache objects are set
248
			 *
249
			 * @see https://www.mediawiki.org/wiki/Manual:Hooks/SetupAfterCache
250
			 */
251
			'SetupAfterCache'          => function() {
252 1
				BootstrapManager::getInstance()->addAllBootstrapModules();
253 1
				return true;
254 41
			},
255 41
		];
256
	}
257
258
	/**
259
	 * @param \Config $myConfig
260
	 *
261
	 * @throws \MWException cascading {@see \BootstrapComponents\ApplicationFactory} calls
262
	 * @throws \ConfigException cascading {@see \Config::get}
263
	 *
264
	 * @return array
265
	 */
266 47
	public function initializeApplications( $myConfig ) {
267 47
		$applicationFactory = ApplicationFactory::getInstance();
268 47
		$componentLibrary = $applicationFactory->getComponentLibrary(
269 47
			$myConfig->get( 'BootstrapComponentsWhitelist' )
270 47
		);
271 47
		$nestingController = $applicationFactory->getNestingController();
272 47
		return [ $componentLibrary, $nestingController ];
273
	}
274
275
	/**
276
	 * @param string $hook
277
	 *
278
	 * @return boolean
279
	 */
280 5
	public function isRegistered( $hook ) {
281 5
		return Hooks::isRegistered( $hook );
282
	}
283
284
	/**
285
	 * @param array $hookList
286
	 *
287
	 * @return int
288
	 */
289 26
	public function register( $hookList ) {
290 26
		foreach ( $hookList as $hook => $callback ) {
291 26
			Hooks::register( $hook, $callback );
292 26
		}
293 26
		return count( $hookList );
294
	}
295
296
	/**
297
	 * @param \ConfigFactory $configFactory
298
	 * Registers my own configuration, so that it is present during onLoad. See phabricator issue T184837
299
	 *
300
	 * @see https://phabricator.wikimedia.org/T184837
301
	 */
302 47
	public function registerMyConfiguration( $configFactory ) {
303 47
		$configFactory->register( 'BootstrapComponents', 'GlobalVarConfig::newInstance' );
304 47
	}
305
306
	/**
307
	 * Executes the setup process.
308
	 *
309
	 * @throws \ConfigException
310
	 *
311
	 * @return int
312
	 */
313 2
	public function run() {
314 2
		$requestedHooks = $this->compileRequestedHooksListFor(
315 2
			$this->myConfig
316 2
		);
317 2
		$hookCallbackList = $this->buildHookCallbackListFor(
318
			$requestedHooks
319 2
		);
320
321 2
		return $this->register( $hookCallbackList );
322
	}
323
324
	/**
325
	 * @throws \MWException
326
	 */
327 47
	private function assertExtensionBootstrapPresent() {
328 47
		if ( !defined( 'BS_VERSION' ) ) {
329
			echo 'The BootstrapComponents extension requires Extension Bootstrap to be installed. '
330
				. 'Please check <a href="https://github.com/oetterer/BootstrapComponents/">the online help</a>' . PHP_EOL;
331
			throw new MWException( 'BootstrapComponents needs extension Bootstrap present.' );
332
		}
333 47
	}
334
335
	/**
336
	 * Callback for Hook: ImageBeforeProduceHTML
337
	 *
338
	 * Called before producing the HTML created by a wiki image insertion
339
	 *
340
	 * @param NestingController $nestingController
341
	 * @param \Config           $myConfig
342
	 *
343
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/ImageBeforeProduceHTML
344
	 *
345
	 * @return \Closure
346
	 */
347 41
	private function createImageBeforeProduceHTMLCallback( $nestingController, $myConfig ) {
348
349
		return function( &$dummy, &$title, &$file, &$frameParams, &$handlerParams, &$time, &$res
350
		) use ( $nestingController, $myConfig ) {
351
352 11
			$imageModal = new ImageModal( $dummy, $title, $file, $nestingController );
353
354 11
			if ( $myConfig->has( 'BootstrapComponentsDisableSourceLinkOnImageModal' )
355 11
				&& $myConfig->get( 'BootstrapComponentsDisableSourceLinkOnImageModal' )
356 11
			) {
357
				$imageModal->disableSourceLink();
358
			}
359
360 11
			return $imageModal->parse( $frameParams, $handlerParams, $time, $res );
361 41
		};
362
	}
363
364
	/**
365
	 * Callback for Hook: InternalParseBeforeLinks
366
	 *
367
	 * Used to process the expanded wiki code after <nowiki>, HTML-comments, and templates have been treated.
368
	 *
369
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/InternalParseBeforeLinks
370
	 *
371
	 * @return \Closure
372
	 */
373 41
	private function createInternalParseBeforeLinksCallback() {
374
		return function( Parser &$parser, &$text ) {
375 21
			$mw = MagicWord::get( 'BSC_NO_IMAGE_MODAL' );
376 21
			if ( $mw->matchAndRemove( $text ) ) {
377
				// we do not use our ParserOutputHelper class here, for we would need to reset it in integration tests.
378
				// resetting our factory build classes is unfortunately a little skittish
379 2
				$parser->getOutput()->setExtensionData( 'bsc_no_image_modal', true );
380 2
			}
381 21
			return true;
382 41
		};
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 41
	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 41
		};
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 15
		return function() use ( $componentName, $componentLibrary, $nestingController, $parserOutputHelper ) {
439
440 15
			$componentClass = $componentLibrary->getClassFor( $componentName );
441 15
			$objectReflection = new ReflectionClass( $componentClass );
442 15
			$object = $objectReflection->newInstanceArgs( [ $componentLibrary, $parserOutputHelper, $nestingController ] );
443
444 15
			$parserRequest = ApplicationFactory::getInstance()->getNewParserRequest(
445 15
				func_get_args(),
446 15
				$componentLibrary->isParserFunction( $componentName ),
447
				$componentName
448 15
			);
449
			/** @var AbstractComponent $object */
450 15
			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
}