48 private $loadBalancer;
54 private $linksMigration;
57 private $commentStore;
60 private $hookContainer;
102 $this->options = $options;
103 $this->wanCache = $wanCache;
104 $this->loadBalancer = $loadBalancer;
105 $this->linkCache = $linkCache;
106 $this->linksMigration = $linksMigration;
107 $this->commentStore = $commentStore;
108 $this->hookContainer = $hookContainer;
109 $this->hookRunner =
new HookRunner( $hookContainer );
110 $this->pageStore = $pageStore;
128 return $restrictions[$action] ?? [];
141 if ( !$this->areRestrictionsLoaded( $page ) ) {
142 $this->loadRestrictions( $page );
159 if ( !$this->areRestrictionsLoaded( $page ) ) {
160 $this->loadRestrictions( $page );
162 return $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'expiry'][$action] ??
null;
182 $protection = $this->getCreateProtectionInternal( $page );
185 if ( $protection[
'permission'] ==
'sysop' ) {
186 $protection[
'permission'] =
'editprotected';
188 if ( $protection[
'permission'] ==
'autoconfirmed' ) {
189 $protection[
'permission'] =
'editsemiprotected';
204 $dbw = $this->loadBalancer->getConnectionRef(
DB_PRIMARY );
210 $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'create_protection'] =
null;
224 $restrictions = $this->getRestrictions( $page, $action );
225 $semi = $this->options->get( MainConfigNames::SemiprotectedRestrictionLevels );
226 if ( !$restrictions || !$semi ) {
232 foreach ( array_keys( $semi,
'editsemiprotected' ) as $key ) {
233 $semi[$key] =
'autoconfirmed';
235 foreach ( array_keys( $restrictions,
'editsemiprotected' ) as $key ) {
236 $restrictions[$key] =
'autoconfirmed';
239 return !array_diff( $restrictions, $semi );
258 $applicableTypes = $this->listApplicableRestrictionTypes( $page );
260 if ( $action ===
'' ) {
261 foreach ( $applicableTypes as
$type ) {
262 if ( $this->isProtected( $page,
$type ) ) {
269 if ( !in_array( $action, $applicableTypes ) ) {
273 return (
bool)array_diff(
275 $this->getRestrictions( $page, $action ),
276 $this->options->get( MainConfigNames::RestrictionLevels )
291 return $this->getCascadeProtectionSourcesInternal( $page,
true );
307 $types = $this->listAllRestrictionTypes( $page->
exists() );
311 $types = array_values( array_diff( $types, [
'upload' ] ) );
314 if ( $this->hookContainer->isRegistered(
'TitleGetRestrictionTypes' ) ) {
315 $this->hookRunner->onTitleGetRestrictionTypes(
334 return array_values( array_diff( $types, [
'create' ] ) );
338 return array_values( array_intersect( $types, [
'create' ] ) );
350 PageIdentity $page,
int $flags = IDBAccessObject::READ_NORMAL
360 if ( $this->areRestrictionsLoaded( $page ) && !$readLatest ) {
364 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
366 $cacheEntry[
'restrictions'] = [];
370 $page = $this->pageStore->getPageByReference( $page, $flags ) ?? $page;
372 $id = $page->
getId();
375 $loadRestrictionsFromDb =
static function ( IDatabase
$dbr ) use ( $fname, $id ) {
376 return iterator_to_array(
377 $dbr->newSelectQueryBuilder()
378 ->select( [
'pr_type',
'pr_expiry',
'pr_level',
'pr_cascade' ] )
379 ->from(
'page_restrictions' )
380 ->where( [
'pr_page' => $id ] )
381 ->caller( $fname )->fetchResultSet()
387 $rows = $loadRestrictionsFromDb(
$dbr );
389 $this->linkCache->addLinkObj( $page );
390 $latestRev = $this->linkCache->getGoodLinkFieldObj( $page,
'revision' );
397 $rows = $this->wanCache->getWithSetCallback(
399 $this->wanCache->makeKey(
'page-restrictions',
'v1', $id, $latestRev ),
400 $this->wanCache::TTL_DAY,
401 function ( $curValue, &$ttl, array &$setOpts ) use ( $loadRestrictionsFromDb ) {
403 $setOpts += Database::getCacheSetOptions(
$dbr );
404 if ( $this->loadBalancer->hasOrMadeRecentPrimaryChanges() ) {
406 $ttl = WANObjectCache::TTL_UNCACHEABLE;
409 return $loadRestrictionsFromDb(
$dbr );
415 $this->loadRestrictionsFromRows( $page, $rows );
417 $titleProtection = $this->getCreateProtectionInternal( $page );
419 if ( $titleProtection ) {
421 $expiry = $titleProtection[
'expiry'];
423 if ( !$expiry || $expiry > $now ) {
425 $cacheEntry[
'expiry'][
'create'] = $expiry ?:
null;
426 $cacheEntry[
'restrictions'][
'create'] =
427 explode(
',', trim( $titleProtection[
'permission'] ) );
430 $cacheEntry[
'create_protection'] =
null;
433 $cacheEntry[
'expiry'][
'create'] =
'infinity';
450 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
452 $restrictionTypes = $this->listApplicableRestrictionTypes( $page );
454 foreach ( $restrictionTypes as
$type ) {
455 $cacheEntry[
'restrictions'][
$type] = [];
456 $cacheEntry[
'expiry'][
$type] =
'infinity';
459 $cacheEntry[
'cascade'] =
false;
469 foreach ( $rows as $row ) {
471 if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
476 $expiry =
$dbr->decodeExpiry( $row->pr_expiry );
481 if ( !$expiry || $expiry > $now ) {
482 $cacheEntry[
'expiry'][$row->pr_type] = $expiry ?:
null;
483 $cacheEntry[
'restrictions'][$row->pr_type]
484 = explode(
',', trim( $row->pr_level ) );
485 if ( $row->pr_cascade ) {
486 $cacheEntry[
'cascade'] =
true;
502 private function getCreateProtectionInternal( PageIdentity $page ): ?array {
504 if ( !$page->canExist() ) {
513 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
515 if ( !$cacheEntry || !array_key_exists(
'create_protection', $cacheEntry ) ) {
517 $commentQuery = $this->commentStore->getJoin(
'pt_reason' );
518 $row =
$dbr->selectRow(
519 [
'protected_titles' ] + $commentQuery[
'tables'],
520 [
'pt_user',
'pt_expiry',
'pt_create_perm' ] + $commentQuery[
'fields'],
524 $commentQuery[
'joins']
528 $cacheEntry[
'create_protection'] = [
529 'user' => $row->pt_user,
530 'expiry' =>
$dbr->decodeExpiry( $row->pt_expiry ),
531 'permission' => $row->pt_create_perm,
532 'reason' => $this->commentStore->getComment(
'pt_reason', $row )->text,
535 $cacheEntry[
'create_protection'] =
null;
540 return $cacheEntry[
'create_protection'];
556 return $this->getCascadeProtectionSourcesInternal( $page,
false );
567 private function getCascadeProtectionSourcesInternal(
570 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
572 if ( !$shortCircuit && isset( $cacheEntry[
'cascade_sources'] ) ) {
573 return $cacheEntry[
'cascade_sources'];
574 } elseif ( $shortCircuit && isset( $cacheEntry[
'has_cascading'] ) ) {
575 return $cacheEntry[
'has_cascading'];
579 $queryBuilder =
$dbr->newSelectQueryBuilder();
580 $queryBuilder->select( [
'pr_expiry' ] )
581 ->from(
'page_restrictions' )
582 ->where( [
'pr_cascade' => 1 ] );
587 $queryBuilder->join(
'imagelinks',
null,
'il_from=pr_page' );
588 $queryBuilder->andWhere( [
'il_to' => $page->
getDBkey() ] );
590 $queryBuilder->join(
'templatelinks',
null,
'tl_from=pr_page' );
591 $queryBuilder->andWhere(
592 $this->linksMigration->getLinksConditions(
599 if ( !$shortCircuit ) {
600 $queryBuilder->fields( [
'pr_page',
'page_namespace',
'page_title',
'pr_type',
'pr_level' ] );
601 $queryBuilder->join(
'page',
null,
'page_id=pr_page' );
604 $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
607 $pageRestrictions = [];
610 foreach (
$res as $row ) {
611 $expiry =
$dbr->decodeExpiry( $row->pr_expiry );
612 if ( $expiry > $now ) {
613 if ( $shortCircuit ) {
614 $cacheEntry[
'has_cascading'] =
true;
618 $sources[$row->pr_page] =
new PageIdentityValue( $row->pr_page,
619 $row->page_namespace, $row->page_title, PageIdentity::LOCAL );
623 if ( !isset( $pageRestrictions[$row->pr_type] ) ) {
624 $pageRestrictions[$row->pr_type] = [];
627 if ( !in_array( $row->pr_level, $pageRestrictions[$row->pr_type] ) ) {
628 $pageRestrictions[$row->pr_type][] = $row->pr_level;
633 $cacheEntry[
'has_cascading'] = (bool)$sources;
635 if ( $shortCircuit ) {
639 $cacheEntry[
'cascade_sources'] = [ $sources, $pageRestrictions ];
640 return [ $sources, $pageRestrictions ];
651 return isset( $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'restrictions'] );
663 return isset( $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'cascade_sources'] );
675 if ( !$this->areRestrictionsLoaded( $page ) ) {
676 $this->loadRestrictions( $page );
678 return $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'cascade'] ??
false;
691 unset( $this->cache[CacheKeyHelper::getKeyForPage( $page )] );
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
Helper class for DAO classes.
static hasFlags( $bitfield, $flags)
Cache for article titles (prefixed DB keys) and ids linked from one source.
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()
Immutable value object representing a page identity.
Represents a page (or page fragment) title within MediaWiki.
static newFromPage(PageReference $page)
Create a TitleValue from a local PageReference.
Represents a title within MediaWiki.
static castFromPageIdentity(?PageIdentity $pageIdentity)
Return a Title for a given PageIdentity.
Multi-datacenter aware caching interface.
Interface for database access objects.
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.