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 );
158 if ( !$this->areRestrictionsLoaded( $page ) ) {
159 $this->loadRestrictions( $page );
161 return $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'expiry'][$action] ??
null;
181 $protection = $this->getCreateProtectionInternal( $page );
184 if ( $protection[
'permission'] ==
'sysop' ) {
185 $protection[
'permission'] =
'editprotected';
187 if ( $protection[
'permission'] ==
'autoconfirmed' ) {
188 $protection[
'permission'] =
'editsemiprotected';
203 $dbw = $this->loadBalancer->getConnectionRef(
DB_PRIMARY );
209 $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'create_protection'] =
null;
223 $restrictions = $this->getRestrictions( $page, $action );
224 $semi = $this->options->get( MainConfigNames::SemiprotectedRestrictionLevels );
225 if ( !$restrictions || !$semi ) {
231 foreach ( array_keys( $semi,
'editsemiprotected' ) as $key ) {
232 $semi[$key] =
'autoconfirmed';
234 foreach ( array_keys( $restrictions,
'editsemiprotected' ) as $key ) {
235 $restrictions[$key] =
'autoconfirmed';
238 return !array_diff( $restrictions, $semi );
257 $applicableTypes = $this->listApplicableRestrictionTypes( $page );
259 if ( $action ===
'' ) {
260 foreach ( $applicableTypes as
$type ) {
261 if ( $this->isProtected( $page,
$type ) ) {
268 if ( !in_array( $action, $applicableTypes ) ) {
272 return (
bool)array_diff(
274 $this->getRestrictions( $page, $action ),
275 $this->options->get( MainConfigNames::RestrictionLevels )
290 return $this->getCascadeProtectionSourcesInternal( $page,
true );
306 $types = $this->listAllRestrictionTypes( $page->
exists() );
310 $types = array_values( array_diff( $types, [
'upload' ] ) );
313 if ( $this->hookContainer->isRegistered(
'TitleGetRestrictionTypes' ) ) {
314 $this->hookRunner->onTitleGetRestrictionTypes(
315 Title::newFromPageIdentity( $page ), $types );
332 return array_values( array_diff( $types, [
'create' ] ) );
336 return array_values( array_intersect( $types, [
'create' ] ) );
348 PageIdentity $page,
int $flags = IDBAccessObject::READ_NORMAL
358 if ( $this->areRestrictionsLoaded( $page ) && !$readLatest ) {
362 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
364 $cacheEntry[
'restrictions'] = [];
368 $page = $this->pageStore->getPageByReference( $page, $flags ) ?? $page;
370 $id = $page->
getId();
373 $loadRestrictionsFromDb =
static function ( IDatabase
$dbr ) use ( $fname, $id ) {
374 return iterator_to_array(
375 $dbr->newSelectQueryBuilder()
376 ->select( [
'pr_type',
'pr_expiry',
'pr_level',
'pr_cascade' ] )
377 ->from(
'page_restrictions' )
378 ->where( [
'pr_page' => $id ] )
379 ->caller( $fname )->fetchResultSet()
385 $rows = $loadRestrictionsFromDb(
$dbr );
387 $this->linkCache->addLinkObj( $page );
388 $latestRev = $this->linkCache->getGoodLinkFieldObj( $page,
'revision' );
395 $rows = $this->wanCache->getWithSetCallback(
397 $this->wanCache->makeKey(
'page-restrictions',
'v1', $id, $latestRev ),
398 $this->wanCache::TTL_DAY,
399 function ( $curValue, &$ttl, array &$setOpts ) use ( $loadRestrictionsFromDb ) {
401 $setOpts += Database::getCacheSetOptions(
$dbr );
402 if ( $this->loadBalancer->hasOrMadeRecentPrimaryChanges() ) {
404 $ttl = WANObjectCache::TTL_UNCACHEABLE;
407 return $loadRestrictionsFromDb(
$dbr );
413 $this->loadRestrictionsFromRows( $page, $rows );
415 $titleProtection = $this->getCreateProtectionInternal( $page );
417 if ( $titleProtection ) {
419 $expiry = $titleProtection[
'expiry'];
421 if ( !$expiry || $expiry > $now ) {
423 $cacheEntry[
'expiry'][
'create'] = $expiry ?:
null;
424 $cacheEntry[
'restrictions'][
'create'] =
425 explode(
',', trim( $titleProtection[
'permission'] ) );
428 $cacheEntry[
'create_protection'] =
null;
431 $cacheEntry[
'expiry'][
'create'] =
'infinity';
448 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
450 $restrictionTypes = $this->listApplicableRestrictionTypes( $page );
452 foreach ( $restrictionTypes as
$type ) {
453 $cacheEntry[
'restrictions'][
$type] = [];
454 $cacheEntry[
'expiry'][
$type] =
'infinity';
457 $cacheEntry[
'cascade'] =
false;
467 foreach ( $rows as $row ) {
469 if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
474 $expiry =
$dbr->decodeExpiry( $row->pr_expiry );
479 if ( !$expiry || $expiry > $now ) {
480 $cacheEntry[
'expiry'][$row->pr_type] = $expiry ?:
null;
481 $cacheEntry[
'restrictions'][$row->pr_type]
482 = explode(
',', trim( $row->pr_level ) );
483 if ( $row->pr_cascade ) {
484 $cacheEntry[
'cascade'] =
true;
500 private function getCreateProtectionInternal( PageIdentity $page ): ?array {
502 if ( !$page->canExist() ) {
511 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
513 if ( !$cacheEntry || !array_key_exists(
'create_protection', $cacheEntry ) ) {
515 $commentQuery = $this->commentStore->getJoin(
'pt_reason' );
516 $row =
$dbr->selectRow(
517 [
'protected_titles' ] + $commentQuery[
'tables'],
518 [
'pt_user',
'pt_expiry',
'pt_create_perm' ] + $commentQuery[
'fields'],
522 $commentQuery[
'joins']
526 $cacheEntry[
'create_protection'] = [
527 'user' => $row->pt_user,
528 'expiry' =>
$dbr->decodeExpiry( $row->pt_expiry ),
529 'permission' => $row->pt_create_perm,
530 'reason' => $this->commentStore->getComment(
'pt_reason', $row )->text,
533 $cacheEntry[
'create_protection'] =
null;
538 return $cacheEntry[
'create_protection'];
552 return $this->getCascadeProtectionSourcesInternal( $page,
false );
563 private function getCascadeProtectionSourcesInternal(
567 return $shortCircuit ? false : [ [], [] ];
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'))
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.
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.