MediaWiki master
RestrictionStore.php
Go to the documentation of this file.
1<?php
2
4
21use stdClass;
28
33
35 public const CONSTRUCTOR_OPTIONS = [
41 ];
42
43 private ServiceOptions $options;
44 private WANObjectCache $wanCache;
45 private LBFactory $loadBalancerFactory;
46 private LinkCache $linkCache;
47 private LinksMigration $linksMigration;
48 private CommentStore $commentStore;
49 private HookContainer $hookContainer;
50 private HookRunner $hookRunner;
51 private PageStore $pageStore;
52
63 private $cache = [];
64
65 public function __construct(
66 ServiceOptions $options,
67 WANObjectCache $wanCache,
68 LBFactory $loadBalancerFactory,
69 LinkCache $linkCache,
70 LinksMigration $linksMigration,
71 CommentStore $commentStore,
72 HookContainer $hookContainer,
73 PageStore $pageStore
74 ) {
75 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
76 $this->options = $options;
77 $this->wanCache = $wanCache;
78 $this->loadBalancerFactory = $loadBalancerFactory;
79 $this->linkCache = $linkCache;
80 $this->linksMigration = $linksMigration;
81 $this->commentStore = $commentStore;
82 $this->hookContainer = $hookContainer;
83 $this->hookRunner = new HookRunner( $hookContainer );
84 $this->pageStore = $pageStore;
85 }
86
98 public function getRestrictions( PageIdentity $page, string $action ): array {
99 $page->assertWiki( PageIdentity::LOCAL );
100
101 // Optimization: Avoid repeatedly fetching page restrictions (from cache or DB)
102 // for repeated PermissionManager::userCan calls, if this action cannot be restricted
103 // in the first place. This is primarily to improve batch rendering on RecentChanges,
104 // where as of writing this will save 0.5s on a 8.0s response. (T341319)
105 $restrictionTypes = $this->listApplicableRestrictionTypes( $page );
106 if ( !in_array( $action, $restrictionTypes ) ) {
107 return [];
108 }
109
110 $restrictions = $this->getAllRestrictions( $page );
111 return $restrictions[$action] ?? [];
112 }
113
121 public function getAllRestrictions( PageIdentity $page ): array {
122 $page->assertWiki( PageIdentity::LOCAL );
123
124 if ( !$this->areRestrictionsLoaded( $page ) ) {
125 $this->loadRestrictions( $page );
126 }
127 return $this->cache[CacheKeyHelper::getKeyForPage( $page )]['restrictions'] ?? [];
128 }
129
138 public function getRestrictionExpiry( PageIdentity $page, string $action ): ?string {
139 $page->assertWiki( PageIdentity::LOCAL );
140
141 if ( !$this->areRestrictionsLoaded( $page ) ) {
142 $this->loadRestrictions( $page );
143 }
144 return $this->cache[CacheKeyHelper::getKeyForPage( $page )]['expiry'][$action] ?? null;
145 }
146
157 public function getCreateProtection( PageIdentity $page ): ?array {
158 $page->assertWiki( PageIdentity::LOCAL );
159
160 $protection = $this->getCreateProtectionInternal( $page );
161 // TODO: the remapping below probably need to be migrated into other method one day
162 if ( $protection ) {
163 if ( $protection['permission'] == 'sysop' ) {
164 $protection['permission'] = 'editprotected'; // B/C
165 }
166 if ( $protection['permission'] == 'autoconfirmed' ) {
167 $protection['permission'] = 'editsemiprotected'; // B/C
168 }
169 }
170 return $protection;
171 }
172
179 public function deleteCreateProtection( PageIdentity $page ): void {
180 $page->assertWiki( PageIdentity::LOCAL );
181
182 $dbw = $this->loadBalancerFactory->getPrimaryDatabase();
183 $dbw->newDeleteQueryBuilder()
184 ->deleteFrom( 'protected_titles' )
185 ->where( [ 'pt_namespace' => $page->getNamespace(), 'pt_title' => $page->getDBkey() ] )
186 ->caller( __METHOD__ )->execute();
187 $this->cache[CacheKeyHelper::getKeyForPage( $page )]['create_protection'] = null;
188 }
189
198 public function isSemiProtected( PageIdentity $page, string $action = 'edit' ): bool {
199 $page->assertWiki( PageIdentity::LOCAL );
200
201 $restrictions = $this->getRestrictions( $page, $action );
202 $semi = $this->options->get( MainConfigNames::SemiprotectedRestrictionLevels );
203 if ( !$restrictions || !$semi ) {
204 // Not protected, or all protection is full protection
205 return false;
206 }
207
208 // Remap autoconfirmed to editsemiprotected for BC
209 foreach ( array_keys( $semi, 'editsemiprotected' ) as $key ) {
210 $semi[$key] = 'autoconfirmed';
211 }
212 foreach ( array_keys( $restrictions, 'editsemiprotected' ) as $key ) {
213 $restrictions[$key] = 'autoconfirmed';
214 }
215
216 return !array_diff( $restrictions, $semi );
217 }
218
226 public function isProtected( PageIdentity $page, string $action = '' ): bool {
227 $page->assertWiki( PageIdentity::LOCAL );
228
229 // Special pages have inherent protection (TODO: remove after switch to ProperPageIdentity)
230 if ( $page->getNamespace() === NS_SPECIAL ) {
231 return true;
232 }
233
234 // Check regular protection levels
235 $applicableTypes = $this->listApplicableRestrictionTypes( $page );
236
237 if ( $action === '' ) {
238 foreach ( $applicableTypes as $type ) {
239 if ( $this->isProtected( $page, $type ) ) {
240 return true;
241 }
242 }
243 return false;
244 }
245
246 if ( !in_array( $action, $applicableTypes ) ) {
247 return false;
248 }
249
250 return (bool)array_diff(
251 array_intersect(
252 $this->getRestrictions( $page, $action ),
253 $this->options->get( MainConfigNames::RestrictionLevels )
254 ),
255 [ '' ]
256 );
257 }
258
265 public function isCascadeProtected( PageIdentity $page ): bool {
266 $page->assertWiki( PageIdentity::LOCAL );
267
268 return $this->shouldUseVirtualDomains()
269 ? $this->getCascadeProtectionSourcesInternal( $page )[0] !== []
270 : $this->getCascadeProtectionSourcesInternalJoined( $page )[0] !== [];
271 }
272
279 public function listApplicableRestrictionTypes( PageIdentity $page ): array {
280 $page->assertWiki( PageIdentity::LOCAL );
281
282 if ( !$page->canExist() ) {
283 return [];
284 }
285
286 $types = $this->listAllRestrictionTypes( $page->exists() );
287
288 if ( $page->getNamespace() !== NS_FILE ) {
289 // Remove the upload restriction for non-file titles
290 $types = array_values( array_diff( $types, [ 'upload' ] ) );
291 }
292
293 if ( $this->hookContainer->isRegistered( 'TitleGetRestrictionTypes' ) ) {
294 $this->hookRunner->onTitleGetRestrictionTypes(
295 Title::newFromPageIdentity( $page ), $types );
296 }
297
298 return $types;
299 }
300
308 public function listAllRestrictionTypes( bool $exists = true ): array {
309 $types = $this->options->get( MainConfigNames::RestrictionTypes );
310 if ( $exists ) {
311 // Remove the create restriction for existing titles
312 return array_values( array_diff( $types, [ 'create' ] ) );
313 }
314
315 // Only the create restrictions apply to non-existing titles
316 return array_values( array_intersect( $types, [ 'create' ] ) );
317 }
318
327 public function loadRestrictions(
328 PageIdentity $page, int $flags = IDBAccessObject::READ_NORMAL
329 ): void {
330 $page->assertWiki( PageIdentity::LOCAL );
331
332 if ( !$page->canExist() ) {
333 return;
334 }
335
336 $readLatest = DBAccessObjectUtils::hasFlags( $flags, IDBAccessObject::READ_LATEST );
337
338 if ( $this->areRestrictionsLoaded( $page ) && !$readLatest ) {
339 return;
340 }
341
342 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
343
344 $cacheEntry['restrictions'] = [];
345
346 // XXX Work around https://phabricator.wikimedia.org/T287575
347 if ( $readLatest ) {
348 $page = $this->pageStore->getPageByReference( $page, $flags ) ?? $page;
349 }
350 $id = $page->getId();
351 if ( $id ) {
352 $fname = __METHOD__;
353 $loadRestrictionsFromDb = static function ( IReadableDatabase $dbr ) use ( $fname, $id ) {
354 return iterator_to_array(
355 $dbr->newSelectQueryBuilder()
356 ->select( [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ] )
357 ->from( 'page_restrictions' )
358 ->where( [ 'pr_page' => $id ] )
359 ->caller( $fname )->fetchResultSet()
360 );
361 };
362
363 if ( $readLatest ) {
364 $dbr = $this->loadBalancerFactory->getPrimaryDatabase();
365 $rows = $loadRestrictionsFromDb( $dbr );
366 } else {
367 $this->pageStore->getPageForLink( TitleValue::newFromPage( $page ) )->getId();
368 $latestRev = $this->linkCache->getGoodLinkFieldObj( $page, 'revision' );
369 if ( !$latestRev ) {
370 // This method can get called in the middle of page creation
371 // (WikiPage::doUserEditContent) where a page might have an
372 // id but no revisions, while checking the "autopatrol" permission.
373 $rows = [];
374 } else {
375 $rows = $this->wanCache->getWithSetCallback(
376 // Page protections always leave a new dummy revision
377 $this->wanCache->makeKey( 'page-restrictions', 'v1', $id, $latestRev ),
378 $this->wanCache::TTL_DAY,
379 function ( $curValue, &$ttl, array &$setOpts ) use ( $loadRestrictionsFromDb ) {
380 $dbr = $this->loadBalancerFactory->getReplicaDatabase();
381 $setOpts += Database::getCacheSetOptions( $dbr );
382 if ( $this->loadBalancerFactory->hasOrMadeRecentPrimaryChanges() ) {
383 // TODO: cleanup Title cache and caller assumption mess in general
384 $ttl = WANObjectCache::TTL_UNCACHEABLE;
385 }
386
387 return $loadRestrictionsFromDb( $dbr );
388 }
389 );
390 }
391 }
392
393 $this->loadRestrictionsFromRows( $page, $rows );
394 } else {
395 $titleProtection = $this->getCreateProtectionInternal( $page );
396
397 if ( $titleProtection ) {
398 $now = wfTimestampNow();
399 $expiry = $titleProtection['expiry'];
400
401 if ( !$expiry || $expiry > $now ) {
402 // Apply the restrictions
403 $cacheEntry['expiry']['create'] = $expiry ?: null;
404 $cacheEntry['restrictions']['create'] =
405 explode( ',', trim( $titleProtection['permission'] ) );
406 } else {
407 // Get rid of the old restrictions
408 $cacheEntry['create_protection'] = null;
409 }
410 } else {
411 $cacheEntry['expiry']['create'] = 'infinity';
412 }
413 }
414 }
415
424 PageIdentity $page, array $rows
425 ): void {
426 $page->assertWiki( PageIdentity::LOCAL );
427
428 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
429
430 $restrictionTypes = $this->listApplicableRestrictionTypes( $page );
431
432 foreach ( $restrictionTypes as $type ) {
433 $cacheEntry['restrictions'][$type] = [];
434 $cacheEntry['expiry'][$type] = 'infinity';
435 }
436
437 $cacheEntry['cascade'] = false;
438
439 if ( !$rows ) {
440 return;
441 }
442
443 // New restriction format -- load second to make them override old-style restrictions.
444 $now = wfTimestampNow();
445
446 // Cycle through all the restrictions.
447 foreach ( $rows as $row ) {
448 // Don't take care of restrictions types that aren't allowed
449 if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
450 continue;
451 }
452
453 $dbr = $this->loadBalancerFactory->getReplicaDatabase();
454 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
455
456 // Only apply the restrictions if they haven't expired!
457 // XXX Why would !$expiry ever be true? It should always be either 'infinity' or a
458 // string consisting of 14 digits. Likewise for the ?: below.
459 if ( !$expiry || $expiry > $now ) {
460 $cacheEntry['expiry'][$row->pr_type] = $expiry ?: null;
461 $cacheEntry['restrictions'][$row->pr_type]
462 = explode( ',', trim( $row->pr_level ) );
463 if ( $row->pr_cascade ) {
464 $cacheEntry['cascade'] = true;
465 }
466 }
467 }
468 }
469
480 private function getCreateProtectionInternal( PageIdentity $page ): ?array {
481 // Can't protect pages in special namespaces
482 if ( !$page->canExist() ) {
483 return null;
484 }
485
486 // Can't apply this type of protection to pages that exist.
487 if ( $page->exists() ) {
488 return null;
489 }
490
491 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
492
493 if ( !$cacheEntry || !array_key_exists( 'create_protection', $cacheEntry ) ) {
494 $dbr = $this->loadBalancerFactory->getReplicaDatabase();
495 $commentQuery = $this->commentStore->getJoin( 'pt_reason' );
496 $row = $dbr->newSelectQueryBuilder()
497 ->select( [ 'pt_user', 'pt_expiry', 'pt_create_perm' ] )
498 ->from( 'protected_titles' )
499 ->where( [ 'pt_namespace' => $page->getNamespace(), 'pt_title' => $page->getDBkey() ] )
500 ->queryInfo( $commentQuery )
501 ->caller( __METHOD__ )
502 ->fetchRow();
503
504 if ( $row ) {
505 $cacheEntry['create_protection'] = [
506 'user' => $row->pt_user,
507 'expiry' => $dbr->decodeExpiry( $row->pt_expiry ),
508 'permission' => $row->pt_create_perm,
509 'reason' => $this->commentStore->getComment( 'pt_reason', $row )->text,
510 ];
511 } else {
512 $cacheEntry['create_protection'] = null;
513 }
514
515 }
516
517 return $cacheEntry['create_protection'];
518 }
519
532 public function getCascadeProtectionSources( PageIdentity $page ): array {
533 $page->assertWiki( PageIdentity::LOCAL );
534
535 return $this->shouldUseVirtualDomains()
536 ? $this->getCascadeProtectionSourcesInternal( $page )
537 : $this->getCascadeProtectionSourcesInternalJoined( $page );
538 }
539
551 private function getCascadeProtectionSourcesInternal(
552 PageIdentity $page
553 ): array {
554 if ( !$page->canExist() ) {
555 return [ [], [], [], [] ];
556 }
557
558 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
559
560 if ( isset( $cacheEntry['cascade_sources'] ) ) {
561 return $cacheEntry['cascade_sources'];
562 }
563
564 $dbr = $this->loadBalancerFactory->getReplicaDatabase();
565 $now = wfTimestampNow();
566
567 $cascadeRestrictions = $dbr->newSelectQueryBuilder()
568 ->select( [
569 'pr_page',
570 'pr_expiry',
571 'page_namespace',
572 'page_title',
573 'pr_type',
574 'pr_level'
575 ] )
576 ->from( 'page_restrictions' )
577 ->join( 'page', null, 'page_id=pr_page' )
578 ->where( [ 'pr_cascade' => 1 ] )
579 ->caller( __METHOD__ )
580 ->fetchResultSet();
581
582 if ( $cascadeRestrictions->numRows() === 0 ) {
583 return [ [], [], [], [] ];
584 }
585
586 $restrictionsByPage = [];
587 foreach ( $cascadeRestrictions as $row ) {
588 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
589 if ( $expiry > $now ) {
590 if ( !isset( $restrictionsByPage[$row->pr_page] ) ) {
591 $restrictionsByPage[$row->pr_page] = [
592 'title' => PageIdentityValue::localIdentity(
593 (int)$row->pr_page,
594 (int)$row->page_namespace,
595 $row->page_title
596 ),
597 'restrictions' => [
598 $row->pr_type => $row->pr_level
599 ]
600 ];
601 } else {
602 $restrictionsByPage[$row->pr_page]['restrictions'][$row->pr_type] = $row->pr_level;
603 }
604 }
605 }
606
607 if ( $restrictionsByPage === [] ) {
608 return [ [], [], [], [] ];
609 }
610
611 $title = TitleValue::newFromPage( $page );
612
613 $templateLinksDb = $this->loadBalancerFactory->getReplicaDatabase( TemplateLinksTable::VIRTUAL_DOMAIN );
614 $templateLinks = $templateLinksDb->newSelectQueryBuilder()
615 ->select( 'tl_from' )
616 ->from( 'templatelinks' )
617 ->where( [ 'tl_from' => array_keys( $restrictionsByPage ) ] )
618 ->andWhere( $this->linksMigration->getLinksConditions( 'templatelinks', $title ) )
619 ->caller( __METHOD__ )
620 ->fetchResultSet();
621
622 $tlSources = [];
623 $ilSources = [];
624 $pageRestrictions = [];
625
626 foreach ( $templateLinks as $link ) {
627 $pageData = $restrictionsByPage[$link->tl_from];
628 $tlSources[$link->tl_from] = $pageData['title'];
629 foreach ( $pageData['restrictions'] as $type => $level ) {
630 if ( !isset( $pageRestrictions[$type] ) ) {
631 $pageRestrictions[$type] = [];
632 }
633
634 if ( !in_array( $level, $pageRestrictions[$type] ) ) {
635 $pageRestrictions[$type][] = $level;
636 }
637 }
638 }
639
640 if ( $page->getNamespace() === NS_FILE ) {
641 $imageLinksDb = $this->loadBalancerFactory->getReplicaDatabase( ImageLinksTable::VIRTUAL_DOMAIN );
642 $imageLinks = $imageLinksDb->newSelectQueryBuilder()
643 ->select( 'il_from' )
644 ->from( 'imagelinks' )
645 ->where( [ 'il_from' => array_keys( $restrictionsByPage ) ] )
646 ->andWhere( $this->linksMigration->getLinksConditions( 'imagelinks', $title ) )
647 ->caller( __METHOD__ )
648 ->fetchResultSet();
649
650 foreach ( $imageLinks as $link ) {
651 $pageData = $restrictionsByPage[$link->il_from];
652 $ilSources[$link->il_from] = $pageData['title'];
653 foreach ( $pageData['restrictions'] as $type => $level ) {
654 if ( !isset( $pageRestrictions[$type] ) ) {
655 $pageRestrictions[$type] = [];
656 }
657
658 if ( !in_array( $level, $pageRestrictions[$type] ) ) {
659 $pageRestrictions[$type][] = $level;
660 }
661 }
662 }
663 }
664
665 $sources = array_replace( $tlSources, $ilSources );
666
667 $cacheEntry['cascade_sources'] = [ $sources, $pageRestrictions, $tlSources, $ilSources ];
668
669 return $cacheEntry['cascade_sources'];
670 }
671
683 private function getCascadeProtectionSourcesInternalJoined( PageIdentity $page ): array {
684 if ( !$page->canExist() ) {
685 return [ [], [], [], [] ];
686 }
687
688 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
689
690 if ( isset( $cacheEntry['cascade_sources'] ) ) {
691 return $cacheEntry['cascade_sources'];
692 }
693
694 $title = TitleValue::newFromPage( $page );
695
696 $dbr = $this->loadBalancerFactory->getReplicaDatabase();
697 $baseQuery = $dbr->newSelectQueryBuilder()
698 ->select( [
699 'pr_expiry',
700 'pr_page',
701 'page_namespace',
702 'page_title',
703 'pr_type',
704 'pr_level'
705 ] )
706 ->from( 'page_restrictions' )
707 ->join( 'page', null, 'page_id=pr_page' )
708 ->where( [ 'pr_cascade' => 1 ] );
709
710 $templateQuery = clone $baseQuery;
711 $templateQuery->join( 'templatelinks', null, 'tl_from=pr_page' )
712 ->fields( [ 'type' => $dbr->addQuotes( 'tl' ) ] )
713 ->andWhere( $this->linksMigration->getLinksConditions( 'templatelinks', $title ) );
714
715 if ( $page->getNamespace() === NS_FILE ) {
716 $imageQuery = clone $baseQuery;
717 $imageQuery->join( 'imagelinks', null, 'il_from=pr_page' )
718 ->fields( [ 'type' => $dbr->addQuotes( 'il' ) ] )
719 ->andWhere( $this->linksMigration->getLinksConditions( 'imagelinks', $title ) );
720
721 $unionQuery = $dbr->newUnionQueryBuilder()
722 ->add( $imageQuery )
723 ->add( $templateQuery )
724 ->all();
725 $res = $unionQuery->caller( __METHOD__ )->fetchResultSet();
726 } else {
727 $res = $templateQuery->caller( __METHOD__ )->fetchResultSet();
728 }
729
730 $tlSources = [];
731 $ilSources = [];
732 $pageRestrictions = [];
733 $now = wfTimestampNow();
734
735 foreach ( $res as $row ) {
736 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
737 if ( $expiry > $now ) {
738 if ( $row->type === 'il' ) {
739 $ilSources[$row->pr_page] = PageIdentityValue::localIdentity(
740 (int)$row->pr_page,
741 (int)$row->page_namespace,
742 $row->page_title
743 );
744 } elseif ( $row->type === 'tl' ) {
745 $tlSources[$row->pr_page] = PageIdentityValue::localIdentity(
746 (int)$row->pr_page,
747 (int)$row->page_namespace,
748 $row->page_title
749 );
750 }
751
752 // Add groups needed for each restriction type if its not already there
753 // Make sure this restriction type still exists
754
755 if ( !isset( $pageRestrictions[$row->pr_type] ) ) {
756 $pageRestrictions[$row->pr_type] = [];
757 }
758
759 if ( !in_array( $row->pr_level, $pageRestrictions[$row->pr_type] ) ) {
760 $pageRestrictions[$row->pr_type][] = $row->pr_level;
761 }
762 }
763 }
764
765 $sources = array_replace( $tlSources, $ilSources );
766
767 $cacheEntry['cascade_sources'] = [ $sources, $pageRestrictions, $tlSources, $ilSources ];
768
769 return $cacheEntry['cascade_sources'];
770 }
771
786 private function shouldUseVirtualDomains(): bool {
787 $virtualDomains = $this->options->get( MainConfigNames::VirtualDomainsMapping );
788 return isset( $virtualDomains[LinksTable::VIRTUAL_DOMAIN] );
789 }
790
796 public function areRestrictionsLoaded( PageIdentity $page ): bool {
797 $page->assertWiki( PageIdentity::LOCAL );
798
799 return isset( $this->cache[CacheKeyHelper::getKeyForPage( $page )]['restrictions'] );
800 }
801
808 public function areCascadeProtectionSourcesLoaded( PageIdentity $page ): bool {
809 $page->assertWiki( PageIdentity::LOCAL );
810
811 return isset( $this->cache[CacheKeyHelper::getKeyForPage( $page )]['cascade_sources'] );
812 }
813
820 public function areRestrictionsCascading( PageIdentity $page ): bool {
821 $page->assertWiki( PageIdentity::LOCAL );
822
823 if ( !$this->areRestrictionsLoaded( $page ) ) {
824 $this->loadRestrictions( $page );
825 }
826 return $this->cache[CacheKeyHelper::getKeyForPage( $page )]['cascade'] ?? false;
827 }
828
836 public function flushRestrictions( PageIdentity $page ): void {
837 $page->assertWiki( PageIdentity::LOCAL );
838
839 unset( $this->cache[CacheKeyHelper::getKeyForPage( $page )] );
840 }
841
842}
const NS_FILE
Definition Defines.php:57
const NS_SPECIAL
Definition Defines.php:40
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:71
Handle database storage of comments such as edit summaries and log reasons.
A class for passing options to services.
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
The base class for classes which update a single link table.
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Service for compat reading of links tables.
A class containing constants representing the names of configuration variables.
const NamespaceProtection
Name constant for the NamespaceProtection setting, for use with Config::get()
const RestrictionTypes
Name constant for the RestrictionTypes setting, for use with Config::get()
const SemiprotectedRestrictionLevels
Name constant for the SemiprotectedRestrictionLevels setting, for use with Config::get()
const RestrictionLevels
Name constant for the RestrictionLevels setting, for use with Config::get()
const VirtualDomainsMapping
Name constant for the VirtualDomainsMapping setting, for use with Config::get()
Helper class for mapping page value objects to a string key.
Page existence and metadata cache.
Definition LinkCache.php:54
Immutable value object representing a page identity.
loadRestrictionsFromRows(PageIdentity $page, array $rows)
Compiles list of active page restrictions for this existing page.
getAllRestrictions(PageIdentity $page)
Returns the restricted actions and their restrictions for the specified page.
listAllRestrictionTypes(bool $exists=true)
Get a filtered list of all restriction types supported by this wiki.
getRestrictions(PageIdentity $page, string $action)
Returns list of restrictions for specified page.
deleteCreateProtection(PageIdentity $page)
Remove any title creation protection due to page existing.
getCascadeProtectionSources(PageIdentity $page)
Cascading protection: Get the source of any cascading restrictions on this page.
getRestrictionExpiry(PageIdentity $page, string $action)
Get the expiry time for the restriction against a given action.
isCascadeProtected(PageIdentity $page)
Cascading protection: Return true if cascading restrictions apply to this page, false if not.
isSemiProtected(PageIdentity $page, string $action='edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
listApplicableRestrictionTypes(PageIdentity $page)
Returns restriction types for the current page.
__construct(ServiceOptions $options, WANObjectCache $wanCache, LBFactory $loadBalancerFactory, LinkCache $linkCache, LinksMigration $linksMigration, CommentStore $commentStore, HookContainer $hookContainer, PageStore $pageStore)
isProtected(PageIdentity $page, string $action='')
Does the title correspond to a protected article?
flushRestrictions(PageIdentity $page)
Flush the protection cache in this object and force reload from the database.
areRestrictionsCascading(PageIdentity $page)
Checks if restrictions are cascading for the current page.
loadRestrictions(PageIdentity $page, int $flags=IDBAccessObject::READ_NORMAL)
Load restrictions from page.page_restrictions and the page_restrictions table.
getCreateProtection(PageIdentity $page)
Is this title subject to protection against creation?
areCascadeProtectionSourcesLoaded(PageIdentity $page)
Determines whether cascading protection sources have already been loaded from the database.
Represents the target of a wiki link.
Represents a title within MediaWiki.
Definition Title.php:69
Multi-datacenter aware caching interface.
Interface for objects (potentially) representing an editable wiki page.
getId( $wikiId=self::LOCAL)
Returns the page ID.
canExist()
Checks whether this PageIdentity represents a "proper" page, meaning that it could exist as an editab...
exists()
Checks if the page currently exists.
getNamespace()
Returns the page's namespace number.
getDBkey()
Get the page title in DB key form.
Interface for database access objects.
A database connection without write operations.