73 $this->options = $options;
74 $this->wanCache = $wanCache;
75 $this->loadBalancer = $loadBalancer;
76 $this->linkCache = $linkCache;
77 $this->linksMigration = $linksMigration;
78 $this->commentStore = $commentStore;
79 $this->hookContainer = $hookContainer;
80 $this->hookRunner =
new HookRunner( $hookContainer );
81 $this->pageStore = $pageStore;
103 if ( !in_array( $action, $restrictionTypes ) ) {
108 return $restrictions[$action] ?? [];
121 if ( !$this->areRestrictionsLoaded( $page ) ) {
122 $this->loadRestrictions( $page );
124 return $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'restrictions'] ?? [];
138 if ( !$this->areRestrictionsLoaded( $page ) ) {
139 $this->loadRestrictions( $page );
141 return $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'expiry'][$action] ??
null;
161 $protection = $this->getCreateProtectionInternal( $page );
164 if ( $protection[
'permission'] ==
'sysop' ) {
165 $protection[
'permission'] =
'editprotected';
167 if ( $protection[
'permission'] ==
'autoconfirmed' ) {
168 $protection[
'permission'] =
'editsemiprotected';
183 $dbw = $this->loadBalancer->getConnection(
DB_PRIMARY );
184 $dbw->newDeleteQueryBuilder()
185 ->deleteFrom(
'protected_titles' )
187 ->caller( __METHOD__ )->execute();
188 $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'create_protection'] =
null;
202 $restrictions = $this->getRestrictions( $page, $action );
203 $semi = $this->options->get( MainConfigNames::SemiprotectedRestrictionLevels );
204 if ( !$restrictions || !$semi ) {
210 foreach ( array_keys( $semi,
'editsemiprotected' ) as $key ) {
211 $semi[$key] =
'autoconfirmed';
213 foreach ( array_keys( $restrictions,
'editsemiprotected' ) as $key ) {
214 $restrictions[$key] =
'autoconfirmed';
217 return !array_diff( $restrictions, $semi );
236 $applicableTypes = $this->listApplicableRestrictionTypes( $page );
238 if ( $action ===
'' ) {
239 foreach ( $applicableTypes as $type ) {
240 if ( $this->isProtected( $page, $type ) ) {
247 if ( !in_array( $action, $applicableTypes ) ) {
251 return (
bool)array_diff(
253 $this->getRestrictions( $page, $action ),
254 $this->options->get( MainConfigNames::RestrictionLevels )
269 return $this->getCascadeProtectionSourcesInternal( $page,
true );
285 $types = $this->listAllRestrictionTypes( $page->
exists() );
289 $types = array_values( array_diff( $types, [
'upload' ] ) );
292 if ( $this->hookContainer->isRegistered(
'TitleGetRestrictionTypes' ) ) {
293 $this->hookRunner->onTitleGetRestrictionTypes(
294 Title::newFromPageIdentity( $page ), $types );
311 return array_values( array_diff( $types, [
'create' ] ) );
315 return array_values( array_intersect( $types, [
'create' ] ) );
327 PageIdentity $page,
int $flags = IDBAccessObject::READ_NORMAL
335 $readLatest = DBAccessObjectUtils::hasFlags( $flags, IDBAccessObject::READ_LATEST );
337 if ( $this->areRestrictionsLoaded( $page ) && !$readLatest ) {
341 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
343 $cacheEntry[
'restrictions'] = [];
347 $page = $this->pageStore->getPageByReference( $page, $flags ) ?? $page;
349 $id = $page->
getId();
352 $loadRestrictionsFromDb =
static function ( IReadableDatabase $dbr ) use ( $fname, $id ) {
353 return iterator_to_array(
354 $dbr->newSelectQueryBuilder()
355 ->select( [
'pr_type',
'pr_expiry',
'pr_level',
'pr_cascade' ] )
356 ->from(
'page_restrictions' )
357 ->where( [
'pr_page' => $id ] )
358 ->caller( $fname )->fetchResultSet()
363 $dbr = $this->loadBalancer->getConnection(
DB_PRIMARY );
364 $rows = $loadRestrictionsFromDb( $dbr );
366 $this->pageStore->getPageForLink( TitleValue::newFromPage( $page ) )->getId();
367 $latestRev = $this->linkCache->getGoodLinkFieldObj( $page,
'revision' );
374 $rows = $this->wanCache->getWithSetCallback(
376 $this->wanCache->makeKey(
'page-restrictions',
'v1', $id, $latestRev ),
377 $this->wanCache::TTL_DAY,
378 function ( $curValue, &$ttl, array &$setOpts ) use ( $loadRestrictionsFromDb ) {
379 $dbr = $this->loadBalancer->getConnection(
DB_REPLICA );
380 $setOpts += Database::getCacheSetOptions( $dbr );
381 if ( $this->loadBalancer->hasOrMadeRecentPrimaryChanges() ) {
383 $ttl = WANObjectCache::TTL_UNCACHEABLE;
386 return $loadRestrictionsFromDb( $dbr );
392 $this->loadRestrictionsFromRows( $page, $rows );
394 $titleProtection = $this->getCreateProtectionInternal( $page );
396 if ( $titleProtection ) {
398 $expiry = $titleProtection[
'expiry'];
400 if ( !$expiry || $expiry > $now ) {
402 $cacheEntry[
'expiry'][
'create'] = $expiry ?:
null;
403 $cacheEntry[
'restrictions'][
'create'] =
404 explode(
',', trim( $titleProtection[
'permission'] ) );
407 $cacheEntry[
'create_protection'] =
null;
410 $cacheEntry[
'expiry'][
'create'] =
'infinity';
427 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
429 $restrictionTypes = $this->listApplicableRestrictionTypes( $page );
431 foreach ( $restrictionTypes as $type ) {
432 $cacheEntry[
'restrictions'][$type] = [];
433 $cacheEntry[
'expiry'][$type] =
'infinity';
436 $cacheEntry[
'cascade'] =
false;
446 foreach ( $rows as $row ) {
448 if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
452 $dbr = $this->loadBalancer->getConnection(
DB_REPLICA );
453 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
458 if ( !$expiry || $expiry > $now ) {
459 $cacheEntry[
'expiry'][$row->pr_type] = $expiry ?:
null;
460 $cacheEntry[
'restrictions'][$row->pr_type]
461 = explode(
',', trim( $row->pr_level ) );
462 if ( $row->pr_cascade ) {
463 $cacheEntry[
'cascade'] =
true;
479 private function getCreateProtectionInternal( PageIdentity $page ): ?array {
481 if ( !$page->canExist() ) {
490 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
492 if ( !$cacheEntry || !array_key_exists(
'create_protection', $cacheEntry ) ) {
493 $dbr = $this->loadBalancer->getConnection(
DB_REPLICA );
494 $commentQuery = $this->commentStore->getJoin(
'pt_reason' );
495 $row = $dbr->newSelectQueryBuilder()
496 ->select( [
'pt_user',
'pt_expiry',
'pt_create_perm' ] )
497 ->from(
'protected_titles' )
499 ->queryInfo( $commentQuery )
500 ->caller( __METHOD__ )
504 $cacheEntry[
'create_protection'] = [
505 'user' => $row->pt_user,
506 'expiry' => $dbr->decodeExpiry( $row->pt_expiry ),
507 'permission' => $row->pt_create_perm,
508 'reason' => $this->commentStore->getComment(
'pt_reason', $row )->text,
511 $cacheEntry[
'create_protection'] =
null;
516 return $cacheEntry[
'create_protection'];
530 return $this->getCascadeProtectionSourcesInternal( $page,
false );
541 private function getCascadeProtectionSourcesInternal(
545 return $shortCircuit ? false : [ [], [] ];
548 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
550 if ( !$shortCircuit && isset( $cacheEntry[
'cascade_sources'] ) ) {
551 return $cacheEntry[
'cascade_sources'];
552 } elseif ( $shortCircuit && isset( $cacheEntry[
'has_cascading'] ) ) {
553 return $cacheEntry[
'has_cascading'];
556 $dbr = $this->loadBalancer->getConnection(
DB_REPLICA );
557 $queryBuilder = $dbr->newSelectQueryBuilder();
558 $queryBuilder->select( [
'pr_expiry' ] )
559 ->from(
'page_restrictions' )
560 ->where( [
'pr_cascade' => 1 ] );
565 $queryBuilder->join(
'imagelinks',
null,
'il_from=pr_page' );
566 $queryBuilder->andWhere( [
'il_to' => $page->
getDBkey() ] );
568 $queryBuilder->join(
'templatelinks',
null,
'tl_from=pr_page' );
569 $queryBuilder->andWhere(
570 $this->linksMigration->getLinksConditions(
572 TitleValue::newFromPage( $page )
577 if ( !$shortCircuit ) {
578 $queryBuilder->fields( [
'pr_page',
'page_namespace',
'page_title',
'pr_type',
'pr_level' ] );
579 $queryBuilder->join(
'page',
null,
'page_id=pr_page' );
582 $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
585 $pageRestrictions = [];
588 foreach ( $res as $row ) {
589 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
590 if ( $expiry > $now ) {
591 if ( $shortCircuit ) {
592 $cacheEntry[
'has_cascading'] =
true;
596 $sources[$row->pr_page] =
new PageIdentityValue( $row->pr_page,
597 $row->page_namespace, $row->page_title, PageIdentity::LOCAL );
601 if ( !isset( $pageRestrictions[$row->pr_type] ) ) {
602 $pageRestrictions[$row->pr_type] = [];
605 if ( !in_array( $row->pr_level, $pageRestrictions[$row->pr_type] ) ) {
606 $pageRestrictions[$row->pr_type][] = $row->pr_level;
611 $cacheEntry[
'has_cascading'] = (bool)$sources;
613 if ( $shortCircuit ) {
617 $cacheEntry[
'cascade_sources'] = [ $sources, $pageRestrictions ];
618 return [ $sources, $pageRestrictions ];
629 return isset( $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'restrictions'] );
641 return isset( $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'cascade_sources'] );
653 if ( !$this->areRestrictionsLoaded( $page ) ) {
654 $this->loadRestrictions( $page );
656 return $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'cascade'] ??
false;
669 unset( $this->cache[CacheKeyHelper::getKeyForPage( $page )] );
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
if(!defined('MW_SETUP_CALLBACK'))
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.
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.