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 );
204 $dbw->newDeleteQueryBuilder()
205 ->deleteFrom(
'protected_titles' )
207 ->caller( __METHOD__ )->execute();
208 $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'create_protection'] =
null;
222 $restrictions = $this->getRestrictions( $page, $action );
223 $semi = $this->options->get( MainConfigNames::SemiprotectedRestrictionLevels );
224 if ( !$restrictions || !$semi ) {
230 foreach ( array_keys( $semi,
'editsemiprotected' ) as $key ) {
231 $semi[$key] =
'autoconfirmed';
233 foreach ( array_keys( $restrictions,
'editsemiprotected' ) as $key ) {
234 $restrictions[$key] =
'autoconfirmed';
237 return !array_diff( $restrictions, $semi );
256 $applicableTypes = $this->listApplicableRestrictionTypes( $page );
258 if ( $action ===
'' ) {
259 foreach ( $applicableTypes as $type ) {
260 if ( $this->isProtected( $page, $type ) ) {
267 if ( !in_array( $action, $applicableTypes ) ) {
271 return (
bool)array_diff(
273 $this->getRestrictions( $page, $action ),
274 $this->options->get( MainConfigNames::RestrictionLevels )
289 return $this->getCascadeProtectionSourcesInternal( $page,
true );
305 $types = $this->listAllRestrictionTypes( $page->
exists() );
309 $types = array_values( array_diff( $types, [
'upload' ] ) );
312 if ( $this->hookContainer->isRegistered(
'TitleGetRestrictionTypes' ) ) {
313 $this->hookRunner->onTitleGetRestrictionTypes(
314 Title::newFromPageIdentity( $page ), $types );
331 return array_values( array_diff( $types, [
'create' ] ) );
335 return array_values( array_intersect( $types, [
'create' ] ) );
347 PageIdentity $page,
int $flags = IDBAccessObject::READ_NORMAL
357 if ( $this->areRestrictionsLoaded( $page ) && !$readLatest ) {
361 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
363 $cacheEntry[
'restrictions'] = [];
367 $page = $this->pageStore->getPageByReference( $page, $flags ) ?? $page;
369 $id = $page->
getId();
372 $loadRestrictionsFromDb =
static function ( IReadableDatabase $dbr ) use ( $fname, $id ) {
373 return iterator_to_array(
374 $dbr->newSelectQueryBuilder()
375 ->select( [
'pr_type',
'pr_expiry',
'pr_level',
'pr_cascade' ] )
376 ->from(
'page_restrictions' )
377 ->where( [
'pr_page' => $id ] )
378 ->caller( $fname )->fetchResultSet()
383 $dbr = $this->loadBalancer->getConnectionRef(
DB_PRIMARY );
384 $rows = $loadRestrictionsFromDb( $dbr );
386 $this->linkCache->addLinkObj( $page );
387 $latestRev = $this->linkCache->getGoodLinkFieldObj( $page,
'revision' );
394 $rows = $this->wanCache->getWithSetCallback(
396 $this->wanCache->makeKey(
'page-restrictions',
'v1', $id, $latestRev ),
397 $this->wanCache::TTL_DAY,
398 function ( $curValue, &$ttl, array &$setOpts ) use ( $loadRestrictionsFromDb ) {
399 $dbr = $this->loadBalancer->getConnectionRef(
DB_REPLICA );
400 $setOpts += Database::getCacheSetOptions( $dbr );
401 if ( $this->loadBalancer->hasOrMadeRecentPrimaryChanges() ) {
403 $ttl = WANObjectCache::TTL_UNCACHEABLE;
406 return $loadRestrictionsFromDb( $dbr );
412 $this->loadRestrictionsFromRows( $page, $rows );
414 $titleProtection = $this->getCreateProtectionInternal( $page );
416 if ( $titleProtection ) {
418 $expiry = $titleProtection[
'expiry'];
420 if ( !$expiry || $expiry > $now ) {
422 $cacheEntry[
'expiry'][
'create'] = $expiry ?:
null;
423 $cacheEntry[
'restrictions'][
'create'] =
424 explode(
',', trim( $titleProtection[
'permission'] ) );
427 $cacheEntry[
'create_protection'] =
null;
430 $cacheEntry[
'expiry'][
'create'] =
'infinity';
447 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
449 $restrictionTypes = $this->listApplicableRestrictionTypes( $page );
451 foreach ( $restrictionTypes as $type ) {
452 $cacheEntry[
'restrictions'][$type] = [];
453 $cacheEntry[
'expiry'][$type] =
'infinity';
456 $cacheEntry[
'cascade'] =
false;
466 foreach ( $rows as $row ) {
468 if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
472 $dbr = $this->loadBalancer->getConnectionRef(
DB_REPLICA );
473 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
478 if ( !$expiry || $expiry > $now ) {
479 $cacheEntry[
'expiry'][$row->pr_type] = $expiry ?:
null;
480 $cacheEntry[
'restrictions'][$row->pr_type]
481 = explode(
',', trim( $row->pr_level ) );
482 if ( $row->pr_cascade ) {
483 $cacheEntry[
'cascade'] =
true;
499 private function getCreateProtectionInternal( PageIdentity $page ): ?array {
501 if ( !$page->canExist() ) {
510 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
512 if ( !$cacheEntry || !array_key_exists(
'create_protection', $cacheEntry ) ) {
513 $dbr = $this->loadBalancer->getConnectionRef(
DB_REPLICA );
514 $commentQuery = $this->commentStore->getJoin(
'pt_reason' );
515 $row = $dbr->selectRow(
516 [
'protected_titles' ] + $commentQuery[
'tables'],
517 [
'pt_user',
'pt_expiry',
'pt_create_perm' ] + $commentQuery[
'fields'],
521 $commentQuery[
'joins']
525 $cacheEntry[
'create_protection'] = [
526 'user' => $row->pt_user,
527 'expiry' => $dbr->decodeExpiry( $row->pt_expiry ),
528 'permission' => $row->pt_create_perm,
529 'reason' => $this->commentStore->getComment(
'pt_reason', $row )->text,
532 $cacheEntry[
'create_protection'] =
null;
537 return $cacheEntry[
'create_protection'];
551 return $this->getCascadeProtectionSourcesInternal( $page,
false );
562 private function getCascadeProtectionSourcesInternal(
566 return $shortCircuit ? false : [ [], [] ];
569 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
571 if ( !$shortCircuit && isset( $cacheEntry[
'cascade_sources'] ) ) {
572 return $cacheEntry[
'cascade_sources'];
573 } elseif ( $shortCircuit && isset( $cacheEntry[
'has_cascading'] ) ) {
574 return $cacheEntry[
'has_cascading'];
577 $dbr = $this->loadBalancer->getConnectionRef(
DB_REPLICA );
578 $queryBuilder = $dbr->newSelectQueryBuilder();
579 $queryBuilder->select( [
'pr_expiry' ] )
580 ->from(
'page_restrictions' )
581 ->where( [
'pr_cascade' => 1 ] );
586 $queryBuilder->join(
'imagelinks',
null,
'il_from=pr_page' );
587 $queryBuilder->andWhere( [
'il_to' => $page->
getDBkey() ] );
589 $queryBuilder->join(
'templatelinks',
null,
'tl_from=pr_page' );
590 $queryBuilder->andWhere(
591 $this->linksMigration->getLinksConditions(
593 TitleValue::newFromPage( $page )
598 if ( !$shortCircuit ) {
599 $queryBuilder->fields( [
'pr_page',
'page_namespace',
'page_title',
'pr_type',
'pr_level' ] );
600 $queryBuilder->join(
'page',
null,
'page_id=pr_page' );
603 $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
606 $pageRestrictions = [];
609 foreach ( $res as $row ) {
610 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
611 if ( $expiry > $now ) {
612 if ( $shortCircuit ) {
613 $cacheEntry[
'has_cascading'] =
true;
617 $sources[$row->pr_page] =
new PageIdentityValue( $row->pr_page,
618 $row->page_namespace, $row->page_title, PageIdentity::LOCAL );
622 if ( !isset( $pageRestrictions[$row->pr_type] ) ) {
623 $pageRestrictions[$row->pr_type] = [];
626 if ( !in_array( $row->pr_level, $pageRestrictions[$row->pr_type] ) ) {
627 $pageRestrictions[$row->pr_type][] = $row->pr_level;
632 $cacheEntry[
'has_cascading'] = (bool)$sources;
634 if ( $shortCircuit ) {
638 $cacheEntry[
'cascade_sources'] = [ $sources, $pageRestrictions ];
639 return [ $sources, $pageRestrictions ];
650 return isset( $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'restrictions'] );
662 return isset( $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'cascade_sources'] );
674 if ( !$this->areRestrictionsLoaded( $page ) ) {
675 $this->loadRestrictions( $page );
677 return $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'cascade'] ??
false;
690 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.
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.