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" ); |
|
|
|
|
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
|
|
|
|
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.