Completed
Push — master ( c8702c...a22429 )
by Tobias
09:12
created

Setup   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 347
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 94.83%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 29
c 1
b 1
f 0
lcom 1
cbo 4
dl 0
loc 347
ccs 110
cts 116
cp 0.9483
rs 10

15 Methods

Rating   Name   Duplication   Size   Complexity  
A onExtensionLoad() 0 6 1
A __construct() 0 14 2
A buildHookCallbackListFor() 0 12 3
A clear() 0 5 2
B compileRequestedHooksListFor() 0 15 5
B getCompleteHookDefinitionList() 0 80 3
A initializeApplications() 0 8 1
A isRegistered() 0 3 1
A register() 0 6 2
A registerMyConfiguration() 0 3 1
A run() 0 10 1
A assertExtensionBootstrapPresent() 0 7 2
A createImageBeforeProduceHTMLCallback() 0 16 3
A createInternalParseBeforeLinksCallback() 0 12 1
A prepareEnvironment() 0 3 1
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 \BootstrapComponents\Hooks\ParserFirstCallInit;
30
use \Bootstrap\BootstrapManager;
31
use \Hooks;
32
use \MagicWord;
33
use \MediaWiki\MediaWikiServices;
34
use \MWException;
35
use \Parser;
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 $componentLibrary
65
	 */
66
	private $componentLibrary;
67
68
	/**
69
	 * @var \Config $myConfig
70
	 */
71
	private $myConfig;
72
73
	/**
74
	 * @var NestingController $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 46
	public function __construct( $info ) {
108
109 46
		$this->assertExtensionBootstrapPresent();
110
111 46
		if ( !empty( $info['version'] ) ) {
112 1
			$this->prepareEnvironment( $info['version'] );
113 1
		}
114
115 46
		$configFactory = MediaWikiServices::getInstance()->getConfigFactory();
116 46
		$this->registerMyConfiguration( $configFactory );
117 46
		$this->myConfig = $configFactory->makeConfig( 'BootstrapComponents' );
118
119 46
		list( $this->componentLibrary, $this->nestingController ) = $this->initializeApplications( $this->myConfig );
120 46
	}
121
122
	/**
123
	 * @param array $hooksToRegister
124
	 *
125
	 * @return array
126
	 */
127 39
	public function buildHookCallbackListFor( $hooksToRegister ) {
128 39
		$hookCallbackList = [];
129 39
		$completeHookDefinitionList = $this->getCompleteHookDefinitionList(
130 39
			$this->myConfig, $this->componentLibrary, $this->nestingController
131 39
		);
132 39
		foreach ( $hooksToRegister as $requestedHook ) {
133 38
			if ( isset( $completeHookDefinitionList[$requestedHook] ) ) {
134 37
				$hookCallbackList[$requestedHook] = $completeHookDefinitionList[$requestedHook];
135 37
			}
136 39
		}
137 39
		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 40
	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 40
			},
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 40
			'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 40
			'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 21
				if ( is_null( $parserOutputHelper ) ) {
221 20
					$parserOutputHelper = ApplicationFactory::getInstance()->getParserOutputHelper( $parser );
222 20
				}
223 21
				$text .= $parserOutputHelper->getContentForLaterInjection();
224 21
				return true;
225 40
			},
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
			'ParserFirstCallInit'      => function( Parser $parser ) use ( $componentLibrary, $nestingController ) {
235
				$hook = new ParserFirstCallInit( $parser, $componentLibrary, $nestingController );
236
				return $hook->process();
237 40
			},
238
239
			'ScribuntoExternalLibraries' => function( $engine, &$extraLibraries ) {
240 2
				if ( $engine == 'lua' ) {
241 2
					$extraLibraries['mw.bootstrap'] = 'BootstrapComponents\\LuaLibrary';
242 2
				}
243 2
				return true;
244 40
			},
245
246
			/**
247
			 * Hook: SetupAfterCache
248
			 *
249
			 * Called in Setup.php, after cache objects are set
250
			 *
251
			 * @see https://www.mediawiki.org/wiki/Manual:Hooks/SetupAfterCache
252
			 */
253
			'SetupAfterCache'          => function() {
254 1
				BootstrapManager::getInstance()->addAllBootstrapModules();
255 1
				return true;
256 40
			},
257 40
		];
258
	}
259
260
	/**
261
	 * @param \Config $myConfig
262
	 *
263
	 * @throws \MWException cascading {@see \BootstrapComponents\ApplicationFactory} calls
264
	 * @throws \ConfigException cascading {@see \Config::get}
265
	 *
266
	 * @return array
267
	 */
268 46
	public function initializeApplications( $myConfig ) {
269 46
		$applicationFactory = ApplicationFactory::getInstance();
270 46
		$componentLibrary = $applicationFactory->getComponentLibrary(
271 46
			$myConfig->get( 'BootstrapComponentsWhitelist' )
272 46
		);
273 46
		$nestingController = $applicationFactory->getNestingController();
274 46
		return [ $componentLibrary, $nestingController ];
275
	}
276
277
	/**
278
	 * @param string $hook
279
	 *
280
	 * @return boolean
281
	 */
282 5
	public function isRegistered( $hook ) {
283 5
		return Hooks::isRegistered( $hook );
284
	}
285
286
	/**
287
	 * @param array $hookList
288
	 *
289
	 * @return int
290
	 */
291 27
	public function register( $hookList ) {
292 27
		foreach ( $hookList as $hook => $callback ) {
293 27
			Hooks::register( $hook, $callback );
294 27
		}
295 27
		return count( $hookList );
296
	}
297
298
	/**
299
	 * @param \ConfigFactory $configFactory
300
	 * Registers my own configuration, so that it is present during onLoad. See phabricator issue T184837
301
	 *
302
	 * @see https://phabricator.wikimedia.org/T184837
303
	 */
304 46
	public function registerMyConfiguration( $configFactory ) {
305 46
		$configFactory->register( 'BootstrapComponents', 'GlobalVarConfig::newInstance' );
306 46
	}
307
308
	/**
309
	 * Executes the setup process.
310
	 *
311
	 * @throws \ConfigException
312
	 *
313
	 * @return int
314
	 */
315 2
	public function run() {
316 2
		$requestedHooks = $this->compileRequestedHooksListFor(
317 2
			$this->myConfig
318 2
		);
319 2
		$hookCallbackList = $this->buildHookCallbackListFor(
320
			$requestedHooks
321 2
		);
322
323 2
		return $this->register( $hookCallbackList );
324
	}
325
326
	/**
327
	 * @throws \MWException
328
	 */
329 46
	private function assertExtensionBootstrapPresent() {
330 46
		if ( !defined( 'BS_VERSION' ) ) {
331
			echo 'The BootstrapComponents extension requires Extension Bootstrap to be installed. '
332
				. 'Please check <a href="https://github.com/oetterer/BootstrapComponents/">the online help</a>' . PHP_EOL;
333
			throw new MWException( 'BootstrapComponents needs extension Bootstrap present.' );
334
		}
335 46
	}
336
337
	/**
338
	 * Callback for Hook: ImageBeforeProduceHTML
339
	 *
340
	 * Called before producing the HTML created by a wiki image insertion
341
	 *
342
	 * @param NestingController $nestingController
343
	 * @param \Config           $myConfig
344
	 *
345
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/ImageBeforeProduceHTML
346
	 *
347
	 * @return \Closure
348
	 */
349 40
	private function createImageBeforeProduceHTMLCallback( $nestingController, $myConfig ) {
350
351
		return function( &$dummy, &$title, &$file, &$frameParams, &$handlerParams, &$time, &$res
352
		) use ( $nestingController, $myConfig ) {
353
354 11
			$imageModal = new ImageModal( $dummy, $title, $file, $nestingController );
355
356 11
			if ( $myConfig->has( 'BootstrapComponentsDisableSourceLinkOnImageModal' )
357 11
				&& $myConfig->get( 'BootstrapComponentsDisableSourceLinkOnImageModal' )
358 11
			) {
359
				$imageModal->disableSourceLink();
360
			}
361
362 11
			return $imageModal->parse( $frameParams, $handlerParams, $time, $res );
363 40
		};
364
	}
365
366
	/**
367
	 * Callback for Hook: InternalParseBeforeLinks
368
	 *
369
	 * Used to process the expanded wiki code after <nowiki>, HTML-comments, and templates have been treated.
370
	 *
371
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/InternalParseBeforeLinks
372
	 *
373
	 * @return \Closure
374
	 */
375
	private function createInternalParseBeforeLinksCallback() {
376 40
		return function( Parser &$parser, &$text ) {
377 21
			$mw = MagicWord::get( 'BSC_NO_IMAGE_MODAL' );
378
			// we do not use our ParserOutputHelper class here, for we would need to reset it in integration tests.
379
			// resetting our factory build classes is unfortunately a little skittish
380 21
			$parser->getOutput()->setExtensionData(
381 21
				'bsc_no_image_modal',
382 21
				$mw->matchAndRemove( $text )
383 21
			);
384 21
			return true;
385 40
		};
386
	}
387
388
	/**
389
	 * Version number retrieved from extension info array.
390
	 *
391
	 * @param string $version
392
	 *
393
	 * @return bool
394
	 */
395 1
	private function prepareEnvironment( $version ) {
396 1
		return @define( 'BOOTSTRAP_COMPONENTS_VERSION', (string) $version );
397
	}
398
}
399