RepoHooks   F
last analyzed

Complexity

Total Complexity 124

Size/Duplication

Total Lines 1108
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 30

Importance

Changes 0
Metric Value
wmc 124
lcom 2
cbo 30
dl 0
loc 1108
rs 1.168
c 0
b 0
f 0

37 Methods

Rating   Name   Duplication   Size   Complexity  
A onBeforePageDisplay() 0 6 2
A onBeforePageDisplayMobile() 0 16 3
B onSetupAfterCache() 0 35 6
A registerUnitTests() 0 3 1
A onNamespaceIsMovable() 0 5 2
A isNamespaceUsedByLocalEntities() 0 32 5
A onRevisionFromEditComplete() 0 35 4
A notifyEntityStoreWatcherOnUpdate() 0 21 2
A onArticleDeleteComplete() 0 26 3
A onArticleUndelete() 0 22 4
B onRecentChangeSave() 0 36 7
A onGetPreferences() 0 17 1
A onUserGetDefaultOptions() 0 5 1
A onPageHistoryLineEnding() 0 32 5
B onPageTabs() 0 53 10
A onSpecialPageReorderPages() 0 3 1
A onOutputPageBodyAttributes() 0 29 5
B onApiCheckCanExecute() 0 62 8
A onTitleGetRestrictionTypes() 0 8 2
A onAbuseFilterContentToString() 0 9 2
B onFormat() 0 29 6
A onOutputPageParserOutput() 0 33 5
A onContentModelCanBeUsedOn() 0 38 5
A onAPIQuerySiteInfoGeneralInfo() 0 25 3
A formatDispatchRow() 0 17 4
A onAPIQuerySiteInfoStatisticsInfo() 0 20 2
A onImportHandleRevisionXMLTag() 0 16 4
A onSidebarBeforeOutput() 0 18 2
A onResourceLoaderRegisterModules() 0 44 2
A onInfoAction() 0 29 3
A onApiMaxLagInfo() 0 26 4
A onParserOptionsRegister() 0 14 2
A onRejectParserCacheValue() 0 4 1
A onApiQueryModuleManager() 0 26 4
A onMediaWikiPHPUnitTestStartTest() 0 3 1
A onParserFirstCallInit() 0 6 1
A onRegistration() 0 11 1

How to fix   Complexity   

Complex Class

Complex classes like RepoHooks often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RepoHooks, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Wikibase\Repo;
4
5
use ApiBase;
6
use ApiEditPage;
7
use ApiModuleManager;
8
use ApiQuery;
9
use ApiQuerySiteinfo;
10
use CentralIdLookup;
11
use Content;
12
use ContentHandler;
13
use ExtensionRegistry;
14
use HistoryPager;
15
use IContextSource;
16
use LogEntry;
17
use MediaWiki\Linker\LinkTarget;
18
use MediaWiki\MediaWikiServices;
19
use MediaWiki\Revision\RevisionRecord;
20
use MediaWiki\Revision\SlotRecord;
21
use MediaWiki\User\UserIdentity;
22
use MWException;
23
use OutputPage;
24
use PageProps;
25
use Parser;
26
use ParserOptions;
27
use ParserOutput;
28
use RecentChange;
29
use ResourceLoader;
30
use Skin;
31
use SkinTemplate;
32
use StubUserLang;
33
use Title;
34
use User;
35
use Wikibase\Lib\Formatters\AutoCommentFormatter;
36
use Wikibase\Lib\LibHooks;
37
use Wikibase\Lib\ParserFunctions\CommaSeparatedList;
38
use Wikibase\Lib\Store\EntityRevision;
39
use Wikibase\Lib\Store\Sql\EntityChangeLookup;
40
use Wikibase\Repo\Api\MetaDataBridgeConfig;
41
use Wikibase\Repo\Content\EntityContent;
42
use Wikibase\Repo\Content\EntityHandler;
43
use Wikibase\Repo\Hooks\Helpers\OutputPageEntityViewChecker;
44
use Wikibase\Repo\Hooks\InfoActionHookHandler;
45
use Wikibase\Repo\Hooks\OutputPageEntityIdReader;
46
use Wikibase\Repo\Hooks\SidebarBeforeOutputHookHandler;
47
use Wikibase\Repo\Notifications\RepoEntityChange;
48
use Wikibase\Repo\ParserOutput\PlaceholderEmittingEntityTermsView;
49
use Wikibase\Repo\ParserOutput\TermboxFlag;
50
use Wikibase\Repo\ParserOutput\TermboxVersionParserCacheValueRejector;
51
use Wikibase\Repo\ParserOutput\TermboxView;
52
use Wikibase\Repo\Store\Sql\DispatchStats;
53
use Wikibase\Repo\Store\Sql\SqlSubscriptionLookup;
54
use Wikibase\View\ViewHooks;
55
use WikiPage;
56
57
/**
58
 * File defining the hook handlers for the Wikibase extension.
59
 *
60
 * @license GPL-2.0-or-later
61
 */
62
final class RepoHooks {
63
64
	/**
65
	 * Handler for the BeforePageDisplay hook, simply injects wikibase.ui.entitysearch module
66
	 * replacing the native search box with the entity selector widget.
67
	 *
68
	 * @param OutputPage $out
69
	 * @param Skin $skin
70
	 */
71
	public static function onBeforePageDisplay( OutputPage $out, Skin $skin ) {
0 ignored issues
show
Unused Code introduced by
The parameter $skin is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
72
		$settings = WikibaseRepo::getDefaultInstance()->getSettings();
73
		if ( $settings->getSetting( 'enableEntitySearchUI' ) === true ) {
74
			$out->addModules( 'wikibase.ui.entitysearch' );
75
		}
76
	}
77
78
	/**
79
	 * Handler for the BeforePageDisplayMobile hook that adds the wikibase mobile styles.
80
	 *
81
	 * @param OutputPage $out
82
	 * @param Skin $skin
83
	 */
84
	public static function onBeforePageDisplayMobile( OutputPage $out, Skin $skin ) {
0 ignored issues
show
Unused Code introduced by
The parameter $skin is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
85
		$title = $out->getTitle();
86
		$repo = WikibaseRepo::getDefaultInstance();
87
		$entityNamespaceLookup = $repo->getEntityNamespaceLookup();
88
		$isEntityTitle = $entityNamespaceLookup->isNamespaceWithEntities( $title->getNamespace() );
89
		$useNewTermbox = $repo->getSettings()->getSetting( 'termboxEnabled' );
90
91
		if ( $isEntityTitle ) {
92
			$out->addModules( 'wikibase.mobile' );
93
94
			if ( $useNewTermbox ) {
95
				$out->addModules( 'wikibase.termbox' );
96
				$out->addModuleStyles( [ 'wikibase.termbox.styles' ] );
97
			}
98
		}
99
	}
100
101
	/**
102
	 * Handler for the SetupAfterCache hook, completing the content and namespace setup.
103
	 * This updates the $wgContentHandlers and $wgNamespaceContentModels registries
104
	 * according to information provided by entity type definitions and the entityNamespaces
105
	 * setting.
106
	 *
107
	 * @throws MWException
108
	 */
109
	public static function onSetupAfterCache() {
110
		global $wgContentHandlers,
111
			$wgNamespaceContentModels;
112
113
		$wikibaseRepo = WikibaseRepo::getDefaultInstance();
114
		$namespaces = $wikibaseRepo->getLocalEntityNamespaces();
115
		$namespaceLookup = $wikibaseRepo->getEntityNamespaceLookup();
116
117
		// Register entity namespaces.
118
		// Note that $wgExtraNamespaces and $wgNamespaceAliases have already been processed at this
119
		// point and should no longer be touched.
120
		$contentModelIds = $wikibaseRepo->getContentModelMappings();
121
122
		foreach ( $namespaces as $entityType => $namespace ) {
123
			// TODO: once there is a mechanism for registering the default content model for
124
			// slots other than the main slot, do that!
125
			// XXX: we should probably not just ignore $entityTypes that don't match $contentModelIds.
126
			if ( !isset( $wgNamespaceContentModels[$namespace] )
127
				&& isset( $contentModelIds[$entityType] )
128
				&& $namespaceLookup->getEntitySlotRole( $namespace ) === 'main'
129
			) {
130
				$wgNamespaceContentModels[$namespace] = $contentModelIds[$entityType];
131
			}
132
		}
133
134
		// Register callbacks for instantiating ContentHandlers for EntityContent.
135
		foreach ( $contentModelIds as $entityType => $model ) {
136
			$wgContentHandlers[$model] = function () use ( $wikibaseRepo, $entityType ) {
137
				$entityContentFactory = $wikibaseRepo->getEntityContentFactory();
138
				return $entityContentFactory->getContentHandlerForType( $entityType );
139
			};
140
		}
141
142
		return true;
143
	}
144
145
	/**
146
	 * Hook to add PHPUnit test cases.
147
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/UnitTestsList
148
	 *
149
	 * @param string[] &$paths
150
	 */
151
	public static function registerUnitTests( array &$paths ) {
152
		$paths[] = __DIR__ . '/../tests/phpunit/';
153
	}
154
155
	/**
156
	 * Handler for the NamespaceIsMovable hook.
157
	 *
158
	 * Implemented to prevent moving pages that are in an entity namespace.
159
	 *
160
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/NamespaceIsMovable
161
	 *
162
	 * @param int $ns Namespace ID
163
	 * @param bool &$movable
164
	 */
165
	public static function onNamespaceIsMovable( $ns, &$movable ) {
166
		if ( self::isNamespaceUsedByLocalEntities( $ns ) ) {
167
			$movable = false;
168
		}
169
	}
170
171
	private static function isNamespaceUsedByLocalEntities( $namespace ) {
172
		$wikibaseRepo = WikibaseRepo::getDefaultInstance();
173
		$namespaceLookup = $wikibaseRepo->getEntityNamespaceLookup();
174
175
		// TODO: this logic seems badly misplaced, probably WikibaseRepo should be asked and be
176
		// providing different and more appropriate EntityNamespaceLookup instance
177
		// However looking at the current use of EntityNamespaceLookup, it seems to be used
178
		// for different kinds of things, which calls for more systematic audit and changes.
179
		if ( !$namespaceLookup->isEntityNamespace( $namespace ) ) {
180
			return false;
181
		}
182
183
		$entityType = $namespaceLookup->getEntityType( $namespace );
184
185
		if ( $entityType === null ) {
186
			return false;
187
		}
188
189
		$entitySource = $wikibaseRepo->getEntitySourceDefinitions()->getSourceForEntityType(
190
			$entityType
191
		);
192
		if ( $entitySource === null ) {
193
			return false;
194
		}
195
196
		$localEntitySourceName = $wikibaseRepo->getSettings()->getSetting( 'localEntitySourceName' );
197
		if ( $entitySource->getSourceName() === $localEntitySourceName ) {
198
			return true;
199
		}
200
201
		return false;
202
	}
203
204
	/**
205
	 * Called when a revision was inserted due to an edit.
206
	 *
207
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/RevisionFromEditComplete
208
	 *
209
	 * @param WikiPage $wikiPage
210
	 * @param RevisionRecord $revisionRecord
211
	 * @param int $baseID
212
	 * @param UserIdentity $user
213
	 */
214
	public static function onRevisionFromEditComplete(
215
		WikiPage $wikiPage,
216
		RevisionRecord $revisionRecord,
217
		$baseID,
0 ignored issues
show
Unused Code introduced by
The parameter $baseID is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
218
		UserIdentity $user
0 ignored issues
show
Unused Code introduced by
The parameter $user is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
219
	) {
220
		$entityContentFactory = WikibaseRepo::getDefaultInstance()->getEntityContentFactory();
221
222
		if ( $entityContentFactory->isEntityContentModel( $wikiPage->getContent()->getModel() ) ) {
223
			self::notifyEntityStoreWatcherOnUpdate(
224
				$revisionRecord->getContent( SlotRecord::MAIN ),
225
				$revisionRecord
226
			);
227
228
			$notifier = WikibaseRepo::getDefaultInstance()->getChangeNotifier();
229
			$parentId = $revisionRecord->getParentId();
230
231
			if ( !$parentId ) {
232
				$notifier->notifyOnPageCreated( $revisionRecord );
233
			} else {
234
				$parent = MediaWikiServices::getInstance()
235
					->getRevisionLookup()
236
					->getRevisionById( $parentId );
237
238
				if ( !$parent ) {
239
					wfLogWarning(
240
						__METHOD__ . ': Cannot notify on page modification: '
241
						. 'failed to load parent revision with ID ' . $parentId
242
					);
243
				} else {
244
					$notifier->notifyOnPageModified( $revisionRecord, $parent );
245
				}
246
			}
247
		}
248
	}
249
250
	private static function notifyEntityStoreWatcherOnUpdate(
251
		EntityContent $content,
252
		RevisionRecord $revision
253
	) {
254
		$watcher = WikibaseRepo::getDefaultInstance()->getEntityStoreWatcher();
255
256
		// Notify storage/lookup services that the entity was updated. Needed to track page-level changes.
257
		// May be redundant in some cases. Take care not to cause infinite regress.
258
		if ( $content->isRedirect() ) {
259
			$watcher->redirectUpdated(
260
				$content->getEntityRedirect(),
0 ignored issues
show
Bug introduced by
It seems like $content->getEntityRedirect() can be null; however, redirectUpdated() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
261
				$revision->getId()
262
			);
263
		} else {
264
			$watcher->entityUpdated( new EntityRevision(
265
				$content->getEntity(),
266
				$revision->getId(),
267
				$revision->getTimestamp()
268
			) );
269
		}
270
	}
271
272
	/**
273
	 * Occurs after the delete article request has been processed.
274
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/ArticleDeleteComplete
275
	 *
276
	 * @param WikiPage $wikiPage
277
	 * @param User $user
278
	 * @param string $reason
279
	 * @param int $id id of the article that was deleted
280
	 * @param Content|null $content
281
	 * @param LogEntry $logEntry
282
	 *
283
	 * @throws MWException
284
	 */
285
	public static function onArticleDeleteComplete(
286
		WikiPage $wikiPage,
0 ignored issues
show
Unused Code introduced by
The parameter $wikiPage is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
287
		User $user,
288
		$reason,
0 ignored issues
show
Unused Code introduced by
The parameter $reason is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
289
		$id,
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
290
		?Content $content,
291
		LogEntry $logEntry
292
	) {
293
		$wikibaseRepo = WikibaseRepo::getDefaultInstance();
294
		$entityContentFactory = $wikibaseRepo->getEntityContentFactory();
295
296
		// Bail out if we are not looking at an entity
297
		if ( !$content || !$entityContentFactory->isEntityContentModel( $content->getModel() ) ) {
298
			return;
299
		}
300
301
		/** @var EntityContent $content */
302
		'@phan-var EntityContent $content';
303
304
		// Notify storage/lookup services that the entity was deleted. Needed to track page-level deletion.
305
		// May be redundant in some cases. Take care not to cause infinite regress.
306
		$wikibaseRepo->getEntityStoreWatcher()->entityDeleted( $content->getEntityId() );
307
308
		$notifier = $wikibaseRepo->getChangeNotifier();
309
		$notifier->notifyOnPageDeleted( $content, $user, $logEntry->getTimestamp() );
310
	}
311
312
	/**
313
	 * Handle changes for undeletions
314
	 *
315
	 * @param Title $title
316
	 * @param bool $created
317
	 * @param string $comment
318
	 */
319
	public static function onArticleUndelete( Title $title, $created, $comment ) {
0 ignored issues
show
Unused Code introduced by
The parameter $created is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $comment is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
320
		$wikibaseRepo = WikibaseRepo::getDefaultInstance();
321
		$entityContentFactory = $wikibaseRepo->getEntityContentFactory();
322
323
		// Bail out if we are not looking at an entity
324
		if ( !$entityContentFactory->isEntityContentModel( $title->getContentModel() ) ) {
325
			return;
326
		}
327
328
		$revisionId = $title->getLatestRevID();
329
		$revisionRecord = MediaWikiServices::getInstance()
330
			->getRevisionLookup()
331
			->getRevisionById( $revisionId );
332
		$content = $revisionRecord ? $revisionRecord->getContent( SlotRecord::MAIN ) : null;
333
334
		if ( !( $content instanceof EntityContent ) ) {
335
			return;
336
		}
337
338
		$notifier = $wikibaseRepo->getChangeNotifier();
339
		$notifier->notifyOnPageUndeleted( $revisionRecord );
340
	}
341
342
	/**
343
	 * Nasty hack to inject information from RC into the change notification saved earlier
344
	 * by the onRevisionFromEditComplete hook handler.
345
	 *
346
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/RecentChange_save
347
	 *
348
	 * @todo find a better way to do this!
349
	 *
350
	 * @param RecentChange $recentChange
351
	 */
352
	public static function onRecentChangeSave( RecentChange $recentChange ) {
353
		$logType = $recentChange->getAttribute( 'rc_log_type' );
354
		$logAction = $recentChange->getAttribute( 'rc_log_action' );
355
		$revId = $recentChange->getAttribute( 'rc_this_oldid' );
356
357
		if ( $revId <= 0 ) {
358
			// If we don't have a revision ID, we have no chance to find the right change to update.
359
			// NOTE: As of February 2015, RC entries for undeletion have rc_this_oldid = 0.
360
			return;
361
		}
362
363
		if ( $logType === null || ( $logType === 'delete' && $logAction === 'restore' ) ) {
364
			$changeLookup = WikibaseRepo::getDefaultInstance()->getStore()->getEntityChangeLookup();
365
366
			/** @var RepoEntityChange $change */
367
			$change = $changeLookup->loadByRevisionId( $revId, EntityChangeLookup::FROM_MASTER );
368
			'@phan-var RepoEntityChange $change';
369
370
			if ( $change ) {
371
				$changeStore = WikibaseRepo::getDefaultInstance()->getStore()->getChangeStore();
372
373
				$centralIdLookup = CentralIdLookup::factoryNonLocal();
374
				if ( $centralIdLookup === null ) {
375
					$centralUserId = 0;
376
				} else {
377
					$repoUser = $recentChange->getPerformer();
378
					$centralUserId = $centralIdLookup->centralIdFromLocalUser(
379
						$repoUser
380
					);
381
				}
382
383
				$change->setMetadataFromRC( $recentChange, $centralUserId );
384
				$changeStore->saveChange( $change );
385
			}
386
		}
387
	}
388
389
	/**
390
	 * Allows to add user preferences.
391
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/GetPreferences
392
	 *
393
	 * NOTE: Might make sense to put the inner functionality into a well structured Preferences file once this
394
	 *       becomes more.
395
	 *
396
	 * @param User $user
397
	 * @param array[] &$preferences
398
	 */
399
	public static function onGetPreferences( User $user, array &$preferences ) {
0 ignored issues
show
Unused Code introduced by
The parameter $user is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
400
		$preferences['wb-acknowledgedcopyrightversion'] = [
401
			'type' => 'api'
402
		];
403
404
		$preferences['wb-dismissleavingsitenotice'] = [
405
			'type' => 'api'
406
		];
407
408
		$preferences['wikibase-entitytermsview-showEntitytermslistview'] = [
409
			'type' => 'toggle',
410
			'label-message' => 'wikibase-setting-entitytermsview-showEntitytermslistview',
411
			'help-message' => 'wikibase-setting-entitytermsview-showEntitytermslistview-help',
412
			'section' => 'rendering/advancedrendering',
413
			'default' => '1',
414
		];
415
	}
416
417
	/**
418
	 * Called after fetching the core default user options.
419
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/UserGetDefaultOptions
420
	 *
421
	 * @param array &$defaultOptions
422
	 */
423
	public static function onUserGetDefaultOptions( array &$defaultOptions ) {
424
		// pre-select default language in the list of fallback languages
425
		$defaultLang = $defaultOptions['language'];
426
		$defaultOptions[ 'wb-languages-' . $defaultLang ] = 1;
427
	}
428
429
	/**
430
	 * Modify line endings on history page.
431
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/PageHistoryLineEnding
432
	 *
433
	 * @param HistoryPager $history
434
	 * @param object $row
435
	 * @param string &$html
436
	 * @param array $classes
437
	 */
438
	public static function onPageHistoryLineEnding( HistoryPager $history, $row, &$html, array $classes ) {
0 ignored issues
show
Unused Code introduced by
The parameter $classes is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
439
		// Note: This assumes that HistoryPager::getTitle returns a Title.
440
		$entityContentFactory = WikibaseRepo::getDefaultInstance()->getEntityContentFactory();
441
442
		$wikiPage = $history->getWikiPage();
443
		$services = MediaWikiServices::getInstance();
444
445
		$revisionRecord = $services->getRevisionFactory()->newRevisionFromRow( $row );
446
		$linkTarget = $revisionRecord->getPageAsLinkTarget();
447
448
		if ( $entityContentFactory->isEntityContentModel( $history->getTitle()->getContentModel() )
449
			&& $wikiPage->getLatest() !== $revisionRecord->getId()
450
			&& $services->getPermissionManager()->quickUserCan(
451
				'edit',
452
				$history->getUser(),
453
				$linkTarget
454
			)
455
			&& !$revisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
456
		) {
457
			$link = $services->getLinkRenderer()->makeKnownLink(
458
				$linkTarget,
459
				$history->msg( 'wikibase-restoreold' )->text(),
460
				[],
461
				[
462
					'action' => 'edit',
463
					'restore' => $revisionRecord->getId()
464
				]
465
			);
466
467
			$html .= ' ' . $history->msg( 'parentheses' )->rawParams( $link )->escaped();
468
		}
469
	}
470
471
	/**
472
	 * Alter the structured navigation links in SkinTemplates.
473
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/SkinTemplateNavigation
474
	 *
475
	 * @param SkinTemplate $skinTemplate
476
	 * @param array[] &$links
477
	 */
478
	public static function onPageTabs( SkinTemplate $skinTemplate, array &$links ) {
479
		$entityContentFactory = WikibaseRepo::getDefaultInstance()->getEntityContentFactory();
480
481
		$title = $skinTemplate->getRelevantTitle();
482
483
		if ( $entityContentFactory->isEntityContentModel( $title->getContentModel() ) ) {
484
			unset( $links['views']['edit'] );
485
			unset( $links['views']['viewsource'] );
486
487
			if ( MediaWikiServices::getInstance()->getPermissionManager()
488
					->quickUserCan( 'edit', $skinTemplate->getUser(), $title )
489
			) {
490
				$out = $skinTemplate->getOutput();
491
				$request = $skinTemplate->getRequest();
492
				$old = !$out->isRevisionCurrent()
493
					&& !$request->getCheck( 'diff' );
494
495
				$restore = $request->getCheck( 'restore' );
496
497
				if ( $old || $restore ) {
498
					// insert restore tab into views array, at the second position
499
500
					$revid = $restore
501
						? $request->getText( 'restore' )
502
						: $out->getRevisionId();
503
504
					$rev = MediaWikiServices::getInstance()
505
						->getRevisionLookup()
506
						->getRevisionById( $revid );
507
					if ( !$rev || $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
508
						return;
509
					}
510
511
					$head = array_slice( $links['views'], 0, 1 );
512
					$tail = array_slice( $links['views'], 1 );
513
					$neck = [
514
						'restore' => [
515
							'class' => $restore ? 'selected' : false,
516
							'text' => $skinTemplate->getLanguage()->ucfirst(
517
								wfMessage( 'wikibase-restoreold' )->text()
518
							),
519
							'href' => $title->getLocalURL( [
520
								'action' => 'edit',
521
								'restore' => $revid
522
							] ),
523
						]
524
					];
525
526
					$links['views'] = array_merge( $head, $neck, $tail );
527
				}
528
			}
529
		}
530
	}
531
532
	/**
533
	 * Reorder the groups for the special pages
534
	 *
535
	 * @param array &$groups
536
	 * @param bool $moveOther
537
	 */
538
	public static function onSpecialPageReorderPages( &$groups, $moveOther ) {
0 ignored issues
show
Unused Code introduced by
The parameter $moveOther is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
539
		$groups = array_merge( [ 'wikibaserepo' => null ], $groups );
540
	}
541
542
	/**
543
	 * Used to append a css class to the body, so the page can be identified as Wikibase item page.
544
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/OutputPageBodyAttributes
545
	 *
546
	 * @param OutputPage $out
547
	 * @param Skin $skin
548
	 * @param array &$bodyAttrs
549
	 */
550
	public static function onOutputPageBodyAttributes( OutputPage $out, Skin $skin, array &$bodyAttrs ) {
551
		$wikibaseRepo = WikibaseRepo::getDefaultInstance();
552
		$outputPageEntityIdReader = new OutputPageEntityIdReader(
553
			new OutputPageEntityViewChecker( $wikibaseRepo->getEntityContentFactory() ),
554
			$wikibaseRepo->getEntityIdParser()
555
		);
556
557
		$entityId = $outputPageEntityIdReader->getEntityIdFromOutputPage( $out );
558
559
		if ( $entityId === null ) {
560
			return;
561
		}
562
563
		// TODO: preg_replace kind of ridiculous here, should probably change the ENTITY_TYPE constants instead
564
		$entityType = preg_replace( '/^wikibase-/i', '', $entityId->getEntityType() );
565
566
		// add class to body so it's clear this is a wb item:
567
		$bodyAttrs['class'] .= ' wb-entitypage wb-' . $entityType . 'page';
568
		// add another class with the ID of the item:
569
		$bodyAttrs['class'] .= ' wb-' . $entityType . 'page-' . $entityId->getSerialization();
570
571
		if ( $skin->getRequest()->getCheck( 'diff' ) ) {
572
			$bodyAttrs['class'] .= ' wb-diffpage';
573
		}
574
575
		if ( $out->getTitle() && $out->getRevisionId() !== $out->getTitle()->getLatestRevID() ) {
576
			$bodyAttrs['class'] .= ' wb-oldrevpage';
577
		}
578
	}
579
580
	/**
581
	 * Handler for the ApiCheckCanExecute hook in ApiMain.
582
	 *
583
	 * This implementation causes the execution of ApiEditPage (action=edit) to fail
584
	 * for all namespaces reserved for Wikibase entities. This prevents direct text-level editing
585
	 * of structured data, and it also prevents other types of content being created in these
586
	 * namespaces.
587
	 *
588
	 * @param ApiBase $module The API module being called
589
	 * @param User    $user   The user calling the API
590
	 * @param array|string|null &$message Output-parameter holding for the message the call should fail with.
591
	 *                            This can be a message key or an array as expected by ApiBase::dieUsageMsg().
592
	 *
593
	 * @return bool true to continue execution, false to abort and with $message as an error message.
594
	 */
595
	public static function onApiCheckCanExecute( ApiBase $module, User $user, &$message ) {
0 ignored issues
show
Unused Code introduced by
The parameter $user is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
596
		if ( $module instanceof ApiEditPage ) {
0 ignored issues
show
Bug introduced by
The class ApiEditPage does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
597
			$params = $module->extractRequestParams();
598
			$pageObj = $module->getTitleOrPageId( $params );
599
			$namespace = $pageObj->getTitle()->getNamespace();
600
601
			// XXX FIXME: ApiEditPage doesn't expose the slot, but this 'magically' works if the edit is
602
			// to a MAIN slot and the entity is stored in a non-MAIN slot, because it falls back.
603
			// To be verified that this keeps working once T200570 is done in MediaWiki itself.
604
			$slots = $params['slots'] ?? [ SlotRecord::MAIN ];
605
606
			$wikibaseRepo = WikibaseRepo::getDefaultInstance();
607
608
			/**
609
			 * Don't make Wikibase check if a user can execute when the namespace in question does
610
			 * not refer to a namespace used locally for Wikibase entities.
611
			 */
612
			$localEntitySource = $wikibaseRepo->getLocalEntitySource();
613
			if ( !in_array( $namespace, $localEntitySource->getEntityNamespaceIds() ) ) {
614
					return true;
615
			}
616
617
			$entityContentFactory = $wikibaseRepo->getEntityContentFactory();
618
			$entityTypes = $wikibaseRepo->getEnabledEntityTypes();
619
620
			foreach ( $entityContentFactory->getEntityContentModels() as $contentModel ) {
621
				/** @var EntityHandler $handler */
622
				$handler = ContentHandler::getForModelID( $contentModel );
623
				'@phan-var EntityHandler $handler';
624
625
				if ( !in_array( $handler->getEntityType(), $entityTypes ) ) {
626
					// If the entity type isn't enabled then Wikibase shouldn't be checking anything.
627
					continue;
628
				}
629
630
				if (
631
					$handler->getEntityNamespace() === $namespace &&
632
					in_array( $handler->getEntitySlotRole(), $slots, true )
633
				) {
634
					// XXX: This is most probably redundant with setting
635
					// ContentHandler::supportsDirectApiEditing to false.
636
					// trying to use ApiEditPage on an entity namespace
637
					$params = $module->extractRequestParams();
638
639
					// allow undo
640
					if ( $params['undo'] > 0 ) {
641
						return true;
642
					}
643
644
					// fail
645
					$message = [
646
						'wikibase-no-direct-editing',
647
						$pageObj->getTitle()->getNsText()
648
					];
649
650
					return false;
651
				}
652
			}
653
		}
654
655
		return true;
656
	}
657
658
	/**
659
	 * Handler for the TitleGetRestrictionTypes hook.
660
	 *
661
	 * Implemented to prevent people from protecting pages from being
662
	 * created or moved in an entity namespace (which is pointless).
663
	 *
664
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/TitleGetRestrictionTypes
665
	 *
666
	 * @param Title $title
667
	 * @param string[] &$types The types of protection available
668
	 */
669
	public static function onTitleGetRestrictionTypes( Title $title, array &$types ) {
670
		$namespaceLookup = WikibaseRepo::getDefaultInstance()->getLocalEntityNamespaceLookup();
671
672
		if ( $namespaceLookup->isEntityNamespace( $title->getNamespace() ) ) {
673
			// Remove create and move protection for Wikibase namespaces
674
			$types = array_diff( $types, [ 'create', 'move' ] );
675
		}
676
	}
677
678
	/**
679
	 * Hook handler for AbuseFilter's AbuseFilter-contentToString hook, implemented
680
	 * to provide a custom text representation of Entities for filtering.
681
	 *
682
	 * @param Content $content
683
	 * @param string  &$text The resulting text
684
	 *
685
	 * @return bool
686
	 */
687
	public static function onAbuseFilterContentToString( Content $content, &$text ) {
688
		if ( $content instanceof EntityContent ) {
689
			$text = $content->getTextForFilters();
690
691
			return false;
692
		}
693
694
		return true;
695
	}
696
697
	/**
698
	 * Handler for the FormatAutocomments hook, implementing localized formatting
699
	 * for machine readable autocomments generated by SummaryFormatter.
700
	 *
701
	 * @param string &$comment reference to the autocomment text
702
	 * @param bool $pre true if there is content before the autocomment
703
	 * @param string $auto the autocomment unformatted
704
	 * @param bool $post true if there is content after the autocomment
705
	 * @param Title|null $title use for further information
706
	 * @param bool $local shall links be generated locally or globally
707
	 */
708
	public static function onFormat( &$comment, $pre, $auto, $post, $title, $local ) {
0 ignored issues
show
Unused Code introduced by
The parameter $local is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
709
		global $wgLang, $wgTitle;
710
711
		// If it is possible to avoid loading the whole page then the code will be lighter on the server.
712
		if ( !( $title instanceof Title ) ) {
0 ignored issues
show
Bug introduced by
The class Title does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
713
			$title = $wgTitle;
714
		}
715
716
		if ( !( $title instanceof Title ) ) {
0 ignored issues
show
Bug introduced by
The class Title does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
717
			return;
718
		}
719
720
		$namespaceLookup = WikibaseRepo::getDefaultInstance()->getEntityNamespaceLookup();
721
		$entityType = $namespaceLookup->getEntityType( $title->getNamespace() );
722
		if ( $entityType === null ) {
723
			return;
724
		}
725
726
		if ( $wgLang instanceof StubUserLang ) {
0 ignored issues
show
Bug introduced by
The class StubUserLang does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
727
			StubUserLang::unstub( $wgLang );
728
		}
729
730
		$formatter = new AutoCommentFormatter( $wgLang, [ 'wikibase-' . $entityType, 'wikibase-entity' ] );
731
		$formattedComment = $formatter->formatAutoComment( $auto );
732
733
		if ( is_string( $formattedComment ) ) {
734
			$comment = $formatter->wrapAutoComment( $pre, $formattedComment, $post );
735
		}
736
	}
737
738
	/**
739
	 * Called when pushing meta-info from the ParserOutput into OutputPage.
740
	 * Used to transfer 'wikibase-view-chunks' and entity data from ParserOutput to OutputPage.
741
	 *
742
	 * @param OutputPage $out
743
	 * @param ParserOutput $parserOutput
744
	 */
745
	public static function onOutputPageParserOutput( OutputPage $out, ParserOutput $parserOutput ) {
746
		// Set in EntityParserOutputGenerator.
747
		$placeholders = $parserOutput->getExtensionData( 'wikibase-view-chunks' );
748
		if ( $placeholders !== null ) {
749
			$out->setProperty( 'wikibase-view-chunks', $placeholders );
750
		}
751
752
		// Set in EntityParserOutputGenerator.
753
		$termsListItems = $parserOutput->getExtensionData( 'wikibase-terms-list-items' );
754
		if ( $termsListItems !== null ) {
755
			$out->setProperty( 'wikibase-terms-list-items', $termsListItems );
756
		}
757
758
		// Used in ViewEntityAction and EditEntityAction to override the page HTML title
759
		// with the label, if available, or else the id. Passed via parser output
760
		// and output page to save overhead of fetching content and accessing an entity
761
		// on page view.
762
		$meta = $parserOutput->getExtensionData( 'wikibase-meta-tags' );
763
		$out->setProperty( 'wikibase-meta-tags', $meta );
764
765
		$out->setProperty(
766
			TermboxView::TERMBOX_MARKUP,
767
			$parserOutput->getExtensionData( TermboxView::TERMBOX_MARKUP )
768
		);
769
770
		// Array with <link rel="alternate"> tags for the page HEAD.
771
		$alternateLinks = $parserOutput->getExtensionData( 'wikibase-alternate-links' );
772
		if ( $alternateLinks !== null ) {
773
			foreach ( $alternateLinks as $link ) {
774
				$out->addLink( $link );
775
			}
776
		}
777
	}
778
779
	/**
780
	 * Handler for the ContentModelCanBeUsedOn hook, used to prevent pages of inappropriate type
781
	 * to be placed in an entity namespace.
782
	 *
783
	 * @param string $contentModel
784
	 * @param LinkTarget $title Actually a Title object, but we only require getNamespace
785
	 * @param bool &$ok
786
	 *
787
	 * @return bool
788
	 */
789
	public static function onContentModelCanBeUsedOn( $contentModel, LinkTarget $title, &$ok ) {
790
		$wikibaseRepo = WikibaseRepo::getDefaultInstance();
791
792
		$namespaceLookup = $wikibaseRepo->getEntityNamespaceLookup();
793
		$contentModelIds = $wikibaseRepo->getContentModelMappings();
794
795
		// Find any entity type that is mapped to the title namespace
796
		$expectedEntityType = $namespaceLookup->getEntityType( $title->getNamespace() );
797
798
		// If we don't expect an entity type, then don't check anything else.
799
		if ( $expectedEntityType === null ) {
800
			return true;
801
		}
802
803
		// If the entity type is not from the local source, don't check anything else
804
		$entitySource = $wikibaseRepo->getEntitySourceDefinitions()->getSourceForEntityType( $expectedEntityType );
805
		if ( $entitySource->getSourceName() !== $wikibaseRepo->getLocalEntitySource()->getSourceName() ) {
806
			return true;
807
		}
808
809
		// XXX: If the slot is not the main slot, then assume someone isn't somehow trying
810
		// to add another content type there. We want to actually check per slot type here.
811
		// This should be fixed with https://gerrit.wikimedia.org/r/#/c/mediawiki/core/+/434544/
812
		$expectedSlot = $namespaceLookup->getEntitySlotRole( $expectedEntityType );
813
		if ( $expectedSlot !== 'main' ) {
814
			return true;
815
		}
816
817
		// If the namespace is an entity namespace, the content model
818
		// must be the model assigned to that namespace.
819
		$expectedModel = $contentModelIds[$expectedEntityType];
820
		if ( $expectedModel !== $contentModel ) {
821
			$ok = false;
822
			return false;
823
		}
824
825
		return true;
826
	}
827
828
	/**
829
	 * Exposes configuration values to the action=query&meta=siteinfo API, including lists of
830
	 * property and data value types, sparql endpoint, and several base URLs and URIs.
831
	 *
832
	 * @param ApiQuerySiteinfo $api
833
	 * @param array &$data
834
	 */
835
	public static function onAPIQuerySiteInfoGeneralInfo( ApiQuerySiteinfo $api, array &$data ) {
0 ignored issues
show
Unused Code introduced by
The parameter $api is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
836
		$wikibaseRepo = WikibaseRepo::getDefaultInstance();
837
		$dataTypes = $wikibaseRepo->getDataTypeFactory()->getTypes();
838
		$propertyTypes = [];
839
840
		foreach ( $dataTypes as $id => $type ) {
841
			$propertyTypes[$id] = [ 'valuetype' => $type->getDataValueType() ];
842
		}
843
844
		$data['wikibase-propertytypes'] = $propertyTypes;
845
846
		$conceptBaseUri = $wikibaseRepo->getSettings()->getSetting( 'conceptBaseUri' );
847
		$data['wikibase-conceptbaseuri'] = $conceptBaseUri;
848
849
		$geoShapeStorageBaseUrl = $wikibaseRepo->getSettings()->getSetting( 'geoShapeStorageBaseUrl' );
850
		$data['wikibase-geoshapestoragebaseurl'] = $geoShapeStorageBaseUrl;
851
852
		$tabularDataStorageBaseUrl = $wikibaseRepo->getSettings()->getSetting( 'tabularDataStorageBaseUrl' );
853
		$data['wikibase-tabulardatastoragebaseurl'] = $tabularDataStorageBaseUrl;
854
855
		$sparqlEndpoint = $wikibaseRepo->getSettings()->getSetting( 'sparqlEndpoint' );
856
		if ( is_string( $sparqlEndpoint ) ) {
857
			$data['wikibase-sparql'] = $sparqlEndpoint;
858
		}
859
	}
860
861
	/**
862
	 * Helper for onAPIQuerySiteInfoStatisticsInfo
863
	 *
864
	 * @param object $row
865
	 * @return array
866
	 */
867
	private static function formatDispatchRow( $row ) {
868
		$data = [
869
			'pending' => $row->chd_pending,
870
			'lag' => $row->chd_lag,
871
		];
872
		if ( isset( $row->chd_site ) ) {
873
			$data['site'] = $row->chd_site;
874
		}
875
		if ( isset( $row->chd_seen ) ) {
876
			$data['position'] = $row->chd_seen;
877
		}
878
		if ( isset( $row->chd_touched ) ) {
879
			$data['touched'] = wfTimestamp( TS_ISO_8601, $row->chd_touched );
880
		}
881
882
		return $data;
883
	}
884
885
	/**
886
	 * Adds DispatchStats info to the API
887
	 *
888
	 * @param array[] &$data
889
	 */
890
	public static function onAPIQuerySiteInfoStatisticsInfo( array &$data ) {
891
		$stats = new DispatchStats();
892
		$stats->load();
893
		if ( $stats->hasStats() ) {
894
			$data['dispatch'] = [
895
				'oldest' => [
896
					'id' => $stats->getMinChangeId(),
897
					'timestamp' => $stats->getMinChangeTimestamp(),
898
				],
899
				'newest' => [
900
					'id' => $stats->getMaxChangeId(),
901
					'timestamp' => $stats->getMaxChangeTimestamp(),
902
				],
903
				'freshest' => self::formatDispatchRow( $stats->getFreshest() ),
0 ignored issues
show
Bug introduced by
It seems like $stats->getFreshest() can be null; however, formatDispatchRow() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
904
				'median' => self::formatDispatchRow( $stats->getMedian() ),
0 ignored issues
show
Bug introduced by
It seems like $stats->getMedian() can be null; however, formatDispatchRow() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
905
				'stalest' => self::formatDispatchRow( $stats->getStalest() ),
0 ignored issues
show
Bug introduced by
It seems like $stats->getStalest() can be null; however, formatDispatchRow() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
906
				'average' => self::formatDispatchRow( $stats->getAverage() ),
0 ignored issues
show
Bug introduced by
It seems like $stats->getAverage() can be null; however, formatDispatchRow() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
907
			];
908
		}
909
	}
910
911
	/**
912
	 * Called by Import.php. Implemented to prevent the import of entities.
913
	 *
914
	 * @param object $importer unclear, see Bug T66657
915
	 * @param array $pageInfo
916
	 * @param array $revisionInfo
917
	 *
918
	 * @throws MWException
919
	 */
920
	public static function onImportHandleRevisionXMLTag( $importer, $pageInfo, $revisionInfo ) {
0 ignored issues
show
Unused Code introduced by
The parameter $importer is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $pageInfo is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
921
		if ( isset( $revisionInfo['model'] ) ) {
922
			$wikibaseRepo = WikibaseRepo::getDefaultInstance();
923
			$contentModels = $wikibaseRepo->getContentModelMappings();
924
			$allowImport = $wikibaseRepo->getSettings()->getSetting( 'allowEntityImport' );
925
926
			if ( !$allowImport && in_array( $revisionInfo['model'], $contentModels ) ) {
927
				// Skip entities.
928
				// XXX: This is rather rough.
929
				throw new MWException(
930
					'To avoid ID conflicts, the import of Wikibase entities is not supported.'
931
						. ' You can enable imports using the "allowEntityImport" setting.'
932
				);
933
			}
934
		}
935
	}
936
937
	/**
938
	 * Add Concept URI link to the toolbox section of the sidebar.
939
	 *
940
	 * @param Skin $skin
941
	 * @param string[] &$sidebar
942
	 * @return void
943
	 */
944
	public static function onSidebarBeforeOutput( Skin $skin, array &$sidebar ): void {
945
		$repo = WikibaseRepo::getDefaultInstance();
946
		$hookHandler = new SidebarBeforeOutputHookHandler(
947
			$repo->getSettings()->getSetting( 'conceptBaseUri' ),
948
			$repo->getEntityIdLookup(),
949
			$repo->getEntityLookup(),
950
			$repo->getEntityNamespaceLookup(),
951
			$repo->getLogger()
952
		);
953
954
		$conceptUriLink = $hookHandler->buildConceptUriLink( $skin );
955
956
		if ( $conceptUriLink === null ) {
957
			return;
958
		}
959
960
		$sidebar['TOOLBOX']['wb-concept-uri'] = $conceptUriLink;
961
	}
962
963
	/**
964
	 * Register ResourceLoader modules with dynamic dependencies.
965
	 *
966
	 * @param ResourceLoader $resourceLoader
967
	 */
968
	public static function onResourceLoaderRegisterModules( ResourceLoader $resourceLoader ) {
969
		$moduleTemplate = [
970
			'localBasePath' => __DIR__ . '/..',
971
			'remoteExtPath' => 'Wikibase/repo',
972
		];
973
974
		$modules = [
975
			'wikibase.WikibaseContentLanguages' => $moduleTemplate + [
976
				'scripts' => [
977
					'resources/wikibase.WikibaseContentLanguages.js',
978
				],
979
				'dependencies' => [
980
					'util.ContentLanguages',
981
					'util.inherit',
982
					'wikibase',
983
				],
984
				'targets' => [ 'desktop', 'mobile' ],
985
			],
986
			'wikibase.special.languageLabelDescriptionAliases' => $moduleTemplate + [
987
				'scripts' => [
988
					'resources/wikibase.special/wikibase.special.languageLabelDescriptionAliases.js',
989
				],
990
				'dependencies' => [
991
					'oojs-ui',
992
				],
993
				'messages' => [
994
					'wikibase-label-edit-placeholder',
995
					'wikibase-label-edit-placeholder-language-aware',
996
					'wikibase-description-edit-placeholder',
997
					'wikibase-description-edit-placeholder-language-aware',
998
					'wikibase-aliases-edit-placeholder',
999
					'wikibase-aliases-edit-placeholder-language-aware',
1000
				],
1001
			],
1002
		];
1003
1004
		$isUlsLoaded = ExtensionRegistry::getInstance()->isLoaded( 'UniversalLanguageSelector' );
1005
		if ( $isUlsLoaded ) {
1006
			$modules['wikibase.WikibaseContentLanguages']['dependencies'][] = 'ext.uls.languagenames';
1007
			$modules['wikibase.special.languageLabelDescriptionAliases']['dependencies'][] = 'ext.uls.mediawiki';
1008
		}
1009
1010
		$resourceLoader->register( $modules );
1011
	}
1012
1013
	/**
1014
	 * Adds the Wikis using the entity in action=info
1015
	 *
1016
	 * @param IContextSource $context
1017
	 * @param array[] &$pageInfo
1018
	 */
1019
	public static function onInfoAction( IContextSource $context, array &$pageInfo ) {
1020
		$wikibaseRepo = WikibaseRepo::getDefaultInstance();
1021
1022
		$namespaceChecker = $wikibaseRepo->getEntityNamespaceLookup();
1023
		$title = $context->getTitle();
1024
1025
		if ( !$title || !$namespaceChecker->isNamespaceWithEntities( $title->getNamespace() ) ) {
1026
			// shorten out
1027
			return;
1028
		}
1029
1030
		$mediaWikiServices = MediaWikiServices::getInstance();
1031
		$loadBalancer = $mediaWikiServices->getDBLoadBalancer();
1032
		$subscriptionLookup = new SqlSubscriptionLookup( $loadBalancer );
1033
		$entityIdLookup = $wikibaseRepo->getEntityIdLookup();
1034
1035
		$siteLookup = $mediaWikiServices->getSiteLookup();
1036
1037
		$infoActionHookHandler = new InfoActionHookHandler(
1038
			$namespaceChecker,
1039
			$subscriptionLookup,
1040
			$siteLookup,
1041
			$entityIdLookup,
1042
			$context,
1043
			PageProps::getInstance()
1044
		);
1045
1046
		$pageInfo = $infoActionHookHandler->handle( $context, $pageInfo );
1047
	}
1048
1049
	/**
1050
	 * Handler for the ApiMaxLagInfo to add dispatching lag stats
1051
	 *
1052
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/ApiMaxLagInfo
1053
	 *
1054
	 * @param array &$lagInfo
1055
	 */
1056
	public static function onApiMaxLagInfo( array &$lagInfo ) {
1057
1058
		$dispatchLagToMaxLagFactor = WikibaseRepo::getDefaultInstance()->getSettings()->getSetting(
1059
			'dispatchLagToMaxLagFactor'
1060
		);
1061
1062
		if ( $dispatchLagToMaxLagFactor <= 0 ) {
1063
			return;
1064
		}
1065
1066
		$stats = new DispatchStats();
1067
		$stats->load();
1068
		$median = $stats->getMedian();
1069
1070
		if ( $median ) {
1071
			$maxDispatchLag = $median->chd_lag / (float)$dispatchLagToMaxLagFactor;
1072
			if ( $maxDispatchLag > $lagInfo['lag'] ) {
1073
				$lagInfo = [
1074
					'host' => $median->chd_site,
1075
					'lag' => $maxDispatchLag,
1076
					'type' => 'wikibase-dispatching',
1077
					'dispatchLag' => $median->chd_lag,
1078
				];
1079
			}
1080
		}
1081
	}
1082
1083
	/**
1084
	 * Handler for the ParserOptionsRegister hook to add a "wb" option for cache-splitting
1085
	 *
1086
	 * This registers a lazy-loaded parser option with its value being the EntityHandler
1087
	 * parser version. Non-Wikibase parses will ignore this option, while Wikibase parses
1088
	 * will trigger its loading via ParserOutput::recordOption() and thereby include it
1089
	 * in the cache key to fragment the cache by EntityHandler::PARSER_VERSION.
1090
	 *
1091
	 * @param array &$defaults Options and their defaults
1092
	 * @param array &$inCacheKey Whether each option splits the parser cache
1093
	 * @param array &$lazyOptions Initializers for lazy-loaded options
1094
	 */
1095
	public static function onParserOptionsRegister( &$defaults, &$inCacheKey, &$lazyOptions ) {
1096
		$defaults['wb'] = null;
1097
		$inCacheKey['wb'] = true;
1098
		$lazyOptions['wb'] = function () {
1099
			return EntityHandler::PARSER_VERSION;
1100
		};
1101
		$defaults['termboxVersion'] = null;
1102
		$inCacheKey['termboxVersion'] = true;
1103
		$lazyOptions['termboxVersion'] = function () {
1104
			return TermboxFlag::getInstance()->shouldRenderTermbox() ?
1105
				TermboxView::TERMBOX_VERSION . TermboxView::CACHE_VERSION :
1106
				PlaceholderEmittingEntityTermsView::TERMBOX_VERSION . PlaceholderEmittingEntityTermsView::CACHE_VERSION;
1107
		};
1108
	}
1109
1110
	public static function onRejectParserCacheValue( ParserOutput $parserValue, WikiPage $wikiPage, ParserOptions $parserOpts ) {
0 ignored issues
show
Unused Code introduced by
The parameter $wikiPage is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1111
		$rejector = new TermboxVersionParserCacheValueRejector( TermboxFlag::getInstance() );
1112
		return $rejector->keepCachedValue( $parserValue, $parserOpts );
1113
	}
1114
1115
	public static function onApiQueryModuleManager( ApiModuleManager $moduleManager ) {
1116
		global $wgWBRepoSettings;
1117
1118
		if ( isset( $wgWBRepoSettings['dataBridgeEnabled'] ) && $wgWBRepoSettings['dataBridgeEnabled'] ) {
1119
			$moduleManager->addModule(
1120
				'wbdatabridgeconfig',
1121
				'meta',
1122
				[
1123
					'class' => MetaDataBridgeConfig::class,
1124
					'factory' => function( ApiQuery $apiQuery, $moduleName ) {
1125
						$repo = WikibaseRepo::getDefaultInstance();
1126
1127
						return new MetaDataBridgeConfig(
1128
							$repo->getSettings(),
1129
							$apiQuery,
1130
							$moduleName,
1131
							function ( string $pagename ): ?string {
1132
								$pageTitle = Title::newFromText( $pagename );
1133
								return $pageTitle ? $pageTitle->getFullURL() : null;
1134
							}
1135
						);
1136
					},
1137
				]
1138
			);
1139
		}
1140
	}
1141
1142
	public static function onMediaWikiPHPUnitTestStartTest( $test ) {
0 ignored issues
show
Unused Code introduced by
The parameter $test is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1143
		WikibaseRepo::resetClassStatics();
1144
	}
1145
1146
	/**
1147
	 * Register the parser functions.
1148
	 *
1149
	 * @param Parser $parser
1150
	 */
1151
	public static function onParserFirstCallInit( Parser $parser ) {
1152
		$parser->setFunctionHook(
1153
			CommaSeparatedList::NAME,
1154
			[ CommaSeparatedList::class, 'handle' ]
1155
		);
1156
	}
1157
1158
	public static function onRegistration() {
1159
		global $wgResourceModules;
1160
1161
		LibHooks::onRegistration();
1162
		ViewHooks::onRegistration();
1163
1164
		$wgResourceModules = array_merge(
1165
			$wgResourceModules,
1166
			require __DIR__ . '/../resources/Resources.php'
1167
		);
1168
	}
1169
}
1170