72 $this->options = $options;
73 $this->wanCache = $wanCache;
74 $this->loadBalancer = $loadBalancer;
75 $this->linkCache = $linkCache;
76 $this->linksMigration = $linksMigration;
77 $this->commentStore = $commentStore;
78 $this->hookContainer = $hookContainer;
79 $this->hookRunner =
new HookRunner( $hookContainer );
80 $this->pageStore = $pageStore;
102 if ( !in_array( $action, $restrictionTypes ) ) {
107 return $restrictions[$action] ?? [];
120 if ( !$this->areRestrictionsLoaded( $page ) ) {
121 $this->loadRestrictions( $page );
123 return $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'restrictions'] ?? [];
137 if ( !$this->areRestrictionsLoaded( $page ) ) {
138 $this->loadRestrictions( $page );
140 return $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'expiry'][$action] ??
null;
160 $protection = $this->getCreateProtectionInternal( $page );
163 if ( $protection[
'permission'] ==
'sysop' ) {
164 $protection[
'permission'] =
'editprotected';
166 if ( $protection[
'permission'] ==
'autoconfirmed' ) {
167 $protection[
'permission'] =
'editsemiprotected';
182 $dbw = $this->loadBalancer->getConnection(
DB_PRIMARY );
183 $dbw->newDeleteQueryBuilder()
184 ->deleteFrom(
'protected_titles' )
186 ->caller( __METHOD__ )->execute();
187 $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'create_protection'] =
null;
201 $restrictions = $this->getRestrictions( $page, $action );
202 $semi = $this->options->get( MainConfigNames::SemiprotectedRestrictionLevels );
203 if ( !$restrictions || !$semi ) {
209 foreach ( array_keys( $semi,
'editsemiprotected' ) as $key ) {
210 $semi[$key] =
'autoconfirmed';
212 foreach ( array_keys( $restrictions,
'editsemiprotected' ) as $key ) {
213 $restrictions[$key] =
'autoconfirmed';
216 return !array_diff( $restrictions, $semi );
235 $applicableTypes = $this->listApplicableRestrictionTypes( $page );
237 if ( $action ===
'' ) {
238 foreach ( $applicableTypes as $type ) {
239 if ( $this->isProtected( $page, $type ) ) {
246 if ( !in_array( $action, $applicableTypes ) ) {
250 return (
bool)array_diff(
252 $this->getRestrictions( $page, $action ),
253 $this->options->get( MainConfigNames::RestrictionLevels )
268 return $this->getCascadeProtectionSourcesInternal( $page )[0] !== [];
284 $types = $this->listAllRestrictionTypes( $page->
exists() );
288 $types = array_values( array_diff( $types, [
'upload' ] ) );
291 if ( $this->hookContainer->isRegistered(
'TitleGetRestrictionTypes' ) ) {
292 $this->hookRunner->onTitleGetRestrictionTypes(
293 Title::newFromPageIdentity( $page ), $types );
310 return array_values( array_diff( $types, [
'create' ] ) );
314 return array_values( array_intersect( $types, [
'create' ] ) );
326 PageIdentity $page,
int $flags = IDBAccessObject::READ_NORMAL
334 $readLatest = DBAccessObjectUtils::hasFlags( $flags, IDBAccessObject::READ_LATEST );
336 if ( $this->areRestrictionsLoaded( $page ) && !$readLatest ) {
340 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
342 $cacheEntry[
'restrictions'] = [];
346 $page = $this->pageStore->getPageByReference( $page, $flags ) ?? $page;
348 $id = $page->
getId();
351 $loadRestrictionsFromDb =
static function ( IReadableDatabase $dbr ) use ( $fname, $id ) {
352 return iterator_to_array(
353 $dbr->newSelectQueryBuilder()
354 ->select( [
'pr_type',
'pr_expiry',
'pr_level',
'pr_cascade' ] )
355 ->from(
'page_restrictions' )
356 ->where( [
'pr_page' => $id ] )
357 ->caller( $fname )->fetchResultSet()
362 $dbr = $this->loadBalancer->getConnection(
DB_PRIMARY );
363 $rows = $loadRestrictionsFromDb( $dbr );
365 $this->pageStore->getPageForLink( TitleValue::newFromPage( $page ) )->getId();
366 $latestRev = $this->linkCache->getGoodLinkFieldObj( $page,
'revision' );
373 $rows = $this->wanCache->getWithSetCallback(
375 $this->wanCache->makeKey(
'page-restrictions',
'v1', $id, $latestRev ),
376 $this->wanCache::TTL_DAY,
377 function ( $curValue, &$ttl, array &$setOpts ) use ( $loadRestrictionsFromDb ) {
378 $dbr = $this->loadBalancer->getConnection(
DB_REPLICA );
379 $setOpts += Database::getCacheSetOptions( $dbr );
380 if ( $this->loadBalancer->hasOrMadeRecentPrimaryChanges() ) {
382 $ttl = WANObjectCache::TTL_UNCACHEABLE;
385 return $loadRestrictionsFromDb( $dbr );
391 $this->loadRestrictionsFromRows( $page, $rows );
393 $titleProtection = $this->getCreateProtectionInternal( $page );
395 if ( $titleProtection ) {
397 $expiry = $titleProtection[
'expiry'];
399 if ( !$expiry || $expiry > $now ) {
401 $cacheEntry[
'expiry'][
'create'] = $expiry ?:
null;
402 $cacheEntry[
'restrictions'][
'create'] =
403 explode(
',', trim( $titleProtection[
'permission'] ) );
406 $cacheEntry[
'create_protection'] =
null;
409 $cacheEntry[
'expiry'][
'create'] =
'infinity';
426 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
428 $restrictionTypes = $this->listApplicableRestrictionTypes( $page );
430 foreach ( $restrictionTypes as $type ) {
431 $cacheEntry[
'restrictions'][$type] = [];
432 $cacheEntry[
'expiry'][$type] =
'infinity';
435 $cacheEntry[
'cascade'] =
false;
445 foreach ( $rows as $row ) {
447 if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
451 $dbr = $this->loadBalancer->getConnection(
DB_REPLICA );
452 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
457 if ( !$expiry || $expiry > $now ) {
458 $cacheEntry[
'expiry'][$row->pr_type] = $expiry ?:
null;
459 $cacheEntry[
'restrictions'][$row->pr_type]
460 = explode(
',', trim( $row->pr_level ) );
461 if ( $row->pr_cascade ) {
462 $cacheEntry[
'cascade'] =
true;
478 private function getCreateProtectionInternal( PageIdentity $page ): ?array {
480 if ( !$page->canExist() ) {
489 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
491 if ( !$cacheEntry || !array_key_exists(
'create_protection', $cacheEntry ) ) {
492 $dbr = $this->loadBalancer->getConnection(
DB_REPLICA );
493 $commentQuery = $this->commentStore->getJoin(
'pt_reason' );
494 $row = $dbr->newSelectQueryBuilder()
495 ->select( [
'pt_user',
'pt_expiry',
'pt_create_perm' ] )
496 ->from(
'protected_titles' )
498 ->queryInfo( $commentQuery )
499 ->caller( __METHOD__ )
503 $cacheEntry[
'create_protection'] = [
504 'user' => $row->pt_user,
505 'expiry' => $dbr->decodeExpiry( $row->pt_expiry ),
506 'permission' => $row->pt_create_perm,
507 'reason' => $this->commentStore->getComment(
'pt_reason', $row )->text,
510 $cacheEntry[
'create_protection'] =
null;
515 return $cacheEntry[
'create_protection'];
533 return $this->getCascadeProtectionSourcesInternal( $page );
542 private function getCascadeProtectionSourcesInternal(
545 if ( !$page->canExist() ) {
546 return [ [], [], [], [] ];
549 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
551 if ( isset( $cacheEntry[
'cascade_sources'] ) ) {
552 return $cacheEntry[
'cascade_sources'];
555 $dbr = $this->loadBalancer->getConnection(
DB_REPLICA );
557 $baseQuery = $dbr->newSelectQueryBuilder()
566 ->from(
'page_restrictions' )
567 ->join(
'page',
null,
'page_id=pr_page' )
568 ->where( [
'pr_cascade' => 1 ] );
570 $templateQuery = clone $baseQuery;
571 $templateQuery->join(
'templatelinks',
null,
'tl_from=pr_page' )
573 'type' => $dbr->addQuotes(
'tl' ),
576 $this->linksMigration->getLinksConditions(
'templatelinks', TitleValue::newFromPage( $page ) )
580 $imageQuery = clone $baseQuery;
581 $imageQuery->join(
'imagelinks',
null,
'il_from=pr_page' )
583 'type' => $dbr->addQuotes(
'il' ),
585 ->andWhere( [
'il_to' => $page->
getDBkey() ] );
587 $unionQuery = $dbr->newUnionQueryBuilder()
589 ->add( $templateQuery )
592 $res = $unionQuery->caller( __METHOD__ )->fetchResultSet();
594 $res = $templateQuery->caller( __METHOD__ )->fetchResultSet();
599 $pageRestrictions = [];
601 foreach ( $res as $row ) {
602 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
603 if ( $expiry > $now ) {
604 if ( $row->type ===
'il' ) {
605 $ilSources[$row->pr_page] =
new PageIdentityValue(
607 $row->page_namespace,
611 } elseif ( $row->type ===
'tl' ) {
612 $tlSources[$row->pr_page] =
new PageIdentityValue(
614 $row->page_namespace,
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 $sources = array_replace( $tlSources, $ilSources );
635 $cacheEntry[
'cascade_sources'] = [ $sources, $pageRestrictions, $tlSources, $ilSources ];
637 return $cacheEntry[
'cascade_sources'];
648 return isset( $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'restrictions'] );
660 return isset( $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'cascade_sources'] );
672 if ( !$this->areRestrictionsLoaded( $page ) ) {
673 $this->loadRestrictions( $page );
675 return $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'cascade'] ??
false;
688 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.