These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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 ) { |
||
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 ) { |
||
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, |
||
218 | UserIdentity $user |
||
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(), |
||
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, |
||
287 | User $user, |
||
288 | $reason, |
||
289 | $id, |
||
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 ) { |
||
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 ) { |
||
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 ) { |
||
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 ) { |
||
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 ) { |
||
596 | if ( $module instanceof ApiEditPage ) { |
||
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
|
|||
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 ) ) { |
||
713 | $title = $wgTitle; |
||
714 | } |
||
715 | |||
716 | if ( !( $title instanceof Title ) ) { |
||
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 ) { |
||
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 ) { |
||
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() ), |
||
904 | 'median' => self::formatDispatchRow( $stats->getMedian() ), |
||
905 | 'stalest' => self::formatDispatchRow( $stats->getStalest() ), |
||
906 | 'average' => self::formatDispatchRow( $stats->getAverage() ), |
||
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 ) { |
||
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 ) { |
||
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 ) { |
||
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 |
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.