Completed
Push — master ( 35bd4c...c8e02c )
by
unknown
10:31 queued 10s
created

Setup   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 346
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 96.46%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 28
lcom 1
cbo 5
dl 0
loc 346
ccs 109
cts 113
cp 0.9646
rs 10
c 1
b 1
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
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
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 78 2
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\OutputPageParserOutput;
30
use \BootstrapComponents\Hooks\ParserFirstCallInit;
31
use \Bootstrap\BootstrapManager;
32
use \Hooks;
33
use \MagicWord;
34
use \MediaWiki\MediaWikiServices;
35
use \MWException;
36
use \Parser;
37
38
/**
39
 * Class Setup
40
 *
41
 * Registers all hooks and components for Extension BootstrapComponents.
42
 *
43
 * Information on how to add an additional hook
44
 *  1. add it to {@see Setup::AVAILABLE_HOOKS}.
45
 *  2. add an appropriate entry in the array inside {@see Setup::getCompleteHookDefinitionList}
46
 *     with the hook as array key and the callback as value.
47
 *  3. have {@see Setup::compileRequestedHooksListFor} add the hook to its result array. Based on
48
 *     a certain condition, if necessary.
49
 *  4. add appropriate tests to {@see \BootstrapComponents\Tests\Unit\SetupTest}.
50
 *
51
 * @since 1.0
52
 */
53
class Setup {
54
55
	/**
56
	 * @var array
57
	 */
58
	const AVAILABLE_HOOKS = [
59
		'GalleryGetModes', 'ImageBeforeProduceHTML', 'InternalParseBeforeLinks',
60
		'OutputPageParserOutput', 'ParserFirstCallInit', 'ScribuntoExternalLibraries',
61
		'SetupAfterCache',
62
	];
63
	// dev note: for modals, please see \BootstrapComponents\ModalBuilder for a list of tested hooks
64
65
	/**
66
	 * @var ComponentLibrary $componentLibrary
67
	 */
68
	private $componentLibrary;
69
70
	/**
71
	 * @var \Config $myConfig
72
	 */
73
	private $myConfig;
74
75
	/**
76
	 * @var NestingController $nestingController
77
	 */
78
	private $nestingController;
79
80
	/**
81
	 * Callback function when extension is loaded via extension.json or composer.
82
	 *
83
	 * Note: With this we omit hook registration in extension.json and define our own here
84
	 * to better allow for unit testing.
85
	 *
86
	 * @param array $info
87
	 *
88
	 * @throws \ConfigException cascading {@see Setup::run}
89
	 * @throws \MWException cascading {@see Setup::__construct()} and {@see Setup::run}
90
	 *
91
	 * @return bool
92
	 */
93 1
	public static function onExtensionLoad( $info ) {
94 1
		$setup = new self( $info );
95
96 1
		$setup->run();
97 1
		return true;
98
	}
99
100
	/**
101
	 * Setup constructor.
102
	 *
103
	 * @param $info
104
	 *
105
	 * @throws \ConfigException cascading {@see \BootstrapComponents\Setup::getHooksToRegister}
106
	 * @throws \MWException cascading {@see \BootstrapComponents\Setup::getHooksToRegister}
107
	 *
108
	 */
109 47
	public function __construct( $info ) {
110
111 47
		$this->assertExtensionBootstrapPresent();
112
113 47
		if ( !empty( $info['version'] ) ) {
114 1
			$this->prepareEnvironment( $info['version'] );
115 1
		}
116
117 47
		$configFactory = MediaWikiServices::getInstance()->getConfigFactory();
118 47
		$this->registerMyConfiguration( $configFactory );
119 47
		$this->myConfig = $configFactory->makeConfig( 'BootstrapComponents' );
120
121 47
		list( $this->componentLibrary, $this->nestingController ) = $this->initializeApplications( $this->myConfig );
122 47
	}
123
124
	/**
125
	 * @param array $hooksToRegister
126
	 *
127
	 * @return array
128
	 */
129 40
	public function buildHookCallbackListFor( $hooksToRegister ) {
130 40
		$hookCallbackList = [];
131 40
		$completeHookDefinitionList = $this->getCompleteHookDefinitionList(
132 40
			$this->myConfig, $this->componentLibrary, $this->nestingController
133 40
		);
134 40
		foreach ( $hooksToRegister as $requestedHook ) {
135 39
			if ( isset( $completeHookDefinitionList[$requestedHook] ) ) {
136 38
				$hookCallbackList[$requestedHook] = $completeHookDefinitionList[$requestedHook];
137 38
			}
138 40
		}
139 40
		return $hookCallbackList;
140
	}
141
142
	/**
143
	 * @throws \MWException cascading {@see \Hooks::clear}
144
	 */
145 21
	public function clear() {
146 21
		foreach ( self::AVAILABLE_HOOKS as $name ) {
147 21
			Hooks::clear( $name );
148 21
		}
149 21
	}
150
151
	/**
152
	 * @param \Config $myConfig
153
	 *
154
	 * @throws \ConfigException cascading {@see \Config::get}
155
	 *
156
	 * @return string[]
157
	 */
158 6
	public function compileRequestedHooksListFor( $myConfig ) {
159 6
		$requestedHookList = [ 'OutputPageParserOutput', 'ParserFirstCallInit', 'SetupAfterCache', 'ScribuntoExternalLibraries' ];
160 6
		if ( $myConfig->has( 'BootstrapComponentsEnableCarouselGalleryMode' )
161 6
			&& $myConfig->get( 'BootstrapComponentsEnableCarouselGalleryMode' )
162 6
		) {
163 4
			$requestedHookList[] = 'GalleryGetModes';
164 4
		}
165 6
		if ( $myConfig->has( 'BootstrapComponentsModalReplaceImageTag' )
166 6
			&& $myConfig->get( 'BootstrapComponentsModalReplaceImageTag' )
167 6
		) {
168 4
			$requestedHookList[] = 'ImageBeforeProduceHTML';
169 4
			$requestedHookList[] = 'InternalParseBeforeLinks';
170 4
		}
171 6
		return $requestedHookList;
172
	}
173
174
	/**
175
	 * @param \Config           $myConfig
176
	 * @param ComponentLibrary  $componentLibrary
177
	 * @param NestingController $nestingController
178
	 *
179
	 * @return \Closure[]
180
	 */
181 41
	public function getCompleteHookDefinitionList( $myConfig, $componentLibrary, $nestingController ) {
182
		return [
183
			/**
184
			 * Hook: GalleryGetModes
185
			 *
186
			 * Allows extensions to add classes that can render different modes of a gallery.
187
			 *
188
			 * @see https://www.mediawiki.org/wiki/Manual:Hooks/GalleryGetModes
189
			 */
190
			'GalleryGetModes'          => function( &$modeArray ) {
191 2
				$modeArray['carousel'] = 'BootstrapComponents\\CarouselGallery';
192 2
				return true;
193 41
			},
194
195
			/**
196
			 * Hook: ImageBeforeProduceHTML
197
			 *
198
			 * Called before producing the HTML created by a wiki image insertion
199
			 *
200
			 * @see https://www.mediawiki.org/wiki/Manual:Hooks/ImageBeforeProduceHTML
201
			 */
202 41
			'ImageBeforeProduceHTML'   => $this->createImageBeforeProduceHTMLCallback( $nestingController, $myConfig ),
203
204
			/**
205
			 * Hook: InternalParseBeforeLinks
206
			 *
207
			 * Used to process the expanded wiki code after <nowiki>, HTML-comments, and templates have been treated.
208
			 *
209
			 * @see https://www.mediawiki.org/wiki/Manual:Hooks/InternalParseBeforeLinks
210
			 */
211 41
			'InternalParseBeforeLinks' => $this->createInternalParseBeforeLinksCallback(),
212
213
			/**
214
			 * Hook: OutputPageParserOutput
215
			 *
216
			 * Called after parse, before the HTML is added to the output.
217
			 *
218
			 * @see https://www.mediawiki.org/wiki/Manual:Hooks/OutputPageParserOutput
219
			 */
220
			'OutputPageParserOutput'   => function( \OutputPage &$outputPage, \ParserOutput $parserOutput, ParserOutputHelper &$parserOutputHelper = null ) {
221
				// check, if we need to omit execution on actions edit, submit, or history
222
				// $action = $outputPage->parserOptions()->getUser()->getRequest()->getVal( "action" );
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
223 1
				$hook = new OutputPageParserOutput( $outputPage, $parserOutput, $parserOutputHelper );
224 1
				return $hook->process();
225 41
			},
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 1
				$hook = new ParserFirstCallInit( $parser, $componentLibrary, $nestingController );
236 1
				return $hook->process();
237 41
			},
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 41
			},
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 41
			},
257 41
		];
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 47
	public function initializeApplications( $myConfig ) {
269 47
		$applicationFactory = ApplicationFactory::getInstance();
270 47
		$componentLibrary = $applicationFactory->getComponentLibrary(
271 47
			$myConfig->get( 'BootstrapComponentsWhitelist' )
272 47
		);
273 47
		$nestingController = $applicationFactory->getNestingController();
274 47
		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 47
	public function registerMyConfiguration( $configFactory ) {
305 47
		$configFactory->register( 'BootstrapComponents', 'GlobalVarConfig::newInstance' );
306 47
	}
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 47
	private function assertExtensionBootstrapPresent() {
330 47
		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 47
	}
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 41
	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 41
		};
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 41
		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 41
		};
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