46 private $loadBalancer;
52 private $linksMigration;
55 private $commentStore;
58 private $hookContainer;
100 $this->options = $options;
101 $this->wanCache = $wanCache;
102 $this->loadBalancer = $loadBalancer;
103 $this->linkCache = $linkCache;
104 $this->linksMigration = $linksMigration;
105 $this->commentStore = $commentStore;
106 $this->hookContainer = $hookContainer;
107 $this->hookRunner =
new HookRunner( $hookContainer );
108 $this->pageStore = $pageStore;
130 if ( !in_array( $action, $restrictionTypes ) ) {
135 return $restrictions[$action] ?? [];
148 if ( !$this->areRestrictionsLoaded( $page ) ) {
149 $this->loadRestrictions( $page );
151 return $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'restrictions'] ?? [];
165 if ( !$this->areRestrictionsLoaded( $page ) ) {
166 $this->loadRestrictions( $page );
168 return $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'expiry'][$action] ??
null;
188 $protection = $this->getCreateProtectionInternal( $page );
191 if ( $protection[
'permission'] ==
'sysop' ) {
192 $protection[
'permission'] =
'editprotected';
194 if ( $protection[
'permission'] ==
'autoconfirmed' ) {
195 $protection[
'permission'] =
'editsemiprotected';
210 $dbw = $this->loadBalancer->getConnection(
DB_PRIMARY );
211 $dbw->newDeleteQueryBuilder()
212 ->deleteFrom(
'protected_titles' )
214 ->caller( __METHOD__ )->execute();
215 $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'create_protection'] =
null;
229 $restrictions = $this->getRestrictions( $page, $action );
230 $semi = $this->options->get( MainConfigNames::SemiprotectedRestrictionLevels );
231 if ( !$restrictions || !$semi ) {
237 foreach ( array_keys( $semi,
'editsemiprotected' ) as $key ) {
238 $semi[$key] =
'autoconfirmed';
240 foreach ( array_keys( $restrictions,
'editsemiprotected' ) as $key ) {
241 $restrictions[$key] =
'autoconfirmed';
244 return !array_diff( $restrictions, $semi );
263 $applicableTypes = $this->listApplicableRestrictionTypes( $page );
265 if ( $action ===
'' ) {
266 foreach ( $applicableTypes as $type ) {
267 if ( $this->isProtected( $page, $type ) ) {
274 if ( !in_array( $action, $applicableTypes ) ) {
278 return (
bool)array_diff(
280 $this->getRestrictions( $page, $action ),
281 $this->options->get( MainConfigNames::RestrictionLevels )
296 return $this->getCascadeProtectionSourcesInternal( $page,
true );
312 $types = $this->listAllRestrictionTypes( $page->
exists() );
316 $types = array_values( array_diff( $types, [
'upload' ] ) );
319 if ( $this->hookContainer->isRegistered(
'TitleGetRestrictionTypes' ) ) {
320 $this->hookRunner->onTitleGetRestrictionTypes(
321 Title::newFromPageIdentity( $page ), $types );
338 return array_values( array_diff( $types, [
'create' ] ) );
342 return array_values( array_intersect( $types, [
'create' ] ) );
354 PageIdentity $page,
int $flags = IDBAccessObject::READ_NORMAL
362 $readLatest = DBAccessObjectUtils::hasFlags( $flags, IDBAccessObject::READ_LATEST );
364 if ( $this->areRestrictionsLoaded( $page ) && !$readLatest ) {
368 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
370 $cacheEntry[
'restrictions'] = [];
374 $page = $this->pageStore->getPageByReference( $page, $flags ) ?? $page;
376 $id = $page->
getId();
379 $loadRestrictionsFromDb =
static function ( IReadableDatabase $dbr ) use ( $fname, $id ) {
380 return iterator_to_array(
381 $dbr->newSelectQueryBuilder()
382 ->select( [
'pr_type',
'pr_expiry',
'pr_level',
'pr_cascade' ] )
383 ->from(
'page_restrictions' )
384 ->where( [
'pr_page' => $id ] )
385 ->caller( $fname )->fetchResultSet()
390 $dbr = $this->loadBalancer->getConnection(
DB_PRIMARY );
391 $rows = $loadRestrictionsFromDb( $dbr );
393 $this->pageStore->getPageForLink( TitleValue::newFromPage( $page ) )->getId();
394 $latestRev = $this->linkCache->getGoodLinkFieldObj( $page,
'revision' );
401 $rows = $this->wanCache->getWithSetCallback(
403 $this->wanCache->makeKey(
'page-restrictions',
'v1', $id, $latestRev ),
404 $this->wanCache::TTL_DAY,
405 function ( $curValue, &$ttl, array &$setOpts ) use ( $loadRestrictionsFromDb ) {
406 $dbr = $this->loadBalancer->getConnection(
DB_REPLICA );
407 $setOpts += Database::getCacheSetOptions( $dbr );
408 if ( $this->loadBalancer->hasOrMadeRecentPrimaryChanges() ) {
410 $ttl = WANObjectCache::TTL_UNCACHEABLE;
413 return $loadRestrictionsFromDb( $dbr );
419 $this->loadRestrictionsFromRows( $page, $rows );
421 $titleProtection = $this->getCreateProtectionInternal( $page );
423 if ( $titleProtection ) {
425 $expiry = $titleProtection[
'expiry'];
427 if ( !$expiry || $expiry > $now ) {
429 $cacheEntry[
'expiry'][
'create'] = $expiry ?:
null;
430 $cacheEntry[
'restrictions'][
'create'] =
431 explode(
',', trim( $titleProtection[
'permission'] ) );
434 $cacheEntry[
'create_protection'] =
null;
437 $cacheEntry[
'expiry'][
'create'] =
'infinity';
454 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
456 $restrictionTypes = $this->listApplicableRestrictionTypes( $page );
458 foreach ( $restrictionTypes as $type ) {
459 $cacheEntry[
'restrictions'][$type] = [];
460 $cacheEntry[
'expiry'][$type] =
'infinity';
463 $cacheEntry[
'cascade'] =
false;
473 foreach ( $rows as $row ) {
475 if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
479 $dbr = $this->loadBalancer->getConnection(
DB_REPLICA );
480 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
485 if ( !$expiry || $expiry > $now ) {
486 $cacheEntry[
'expiry'][$row->pr_type] = $expiry ?:
null;
487 $cacheEntry[
'restrictions'][$row->pr_type]
488 = explode(
',', trim( $row->pr_level ) );
489 if ( $row->pr_cascade ) {
490 $cacheEntry[
'cascade'] =
true;
506 private function getCreateProtectionInternal( PageIdentity $page ): ?array {
508 if ( !$page->canExist() ) {
517 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
519 if ( !$cacheEntry || !array_key_exists(
'create_protection', $cacheEntry ) ) {
520 $dbr = $this->loadBalancer->getConnection(
DB_REPLICA );
521 $commentQuery = $this->commentStore->getJoin(
'pt_reason' );
522 $row = $dbr->newSelectQueryBuilder()
523 ->select( [
'pt_user',
'pt_expiry',
'pt_create_perm' ] )
524 ->from(
'protected_titles' )
526 ->queryInfo( $commentQuery )
527 ->caller( __METHOD__ )
531 $cacheEntry[
'create_protection'] = [
532 'user' => $row->pt_user,
533 'expiry' => $dbr->decodeExpiry( $row->pt_expiry ),
534 'permission' => $row->pt_create_perm,
535 'reason' => $this->commentStore->getComment(
'pt_reason', $row )->text,
538 $cacheEntry[
'create_protection'] =
null;
543 return $cacheEntry[
'create_protection'];
557 return $this->getCascadeProtectionSourcesInternal( $page,
false );
568 private function getCascadeProtectionSourcesInternal(
572 return $shortCircuit ? false : [ [], [] ];
575 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
577 if ( !$shortCircuit && isset( $cacheEntry[
'cascade_sources'] ) ) {
578 return $cacheEntry[
'cascade_sources'];
579 } elseif ( $shortCircuit && isset( $cacheEntry[
'has_cascading'] ) ) {
580 return $cacheEntry[
'has_cascading'];
583 $dbr = $this->loadBalancer->getConnection(
DB_REPLICA );
584 $queryBuilder = $dbr->newSelectQueryBuilder();
585 $queryBuilder->select( [
'pr_expiry' ] )
586 ->from(
'page_restrictions' )
587 ->where( [
'pr_cascade' => 1 ] );
592 $queryBuilder->join(
'imagelinks',
null,
'il_from=pr_page' );
593 $queryBuilder->andWhere( [
'il_to' => $page->
getDBkey() ] );
595 $queryBuilder->join(
'templatelinks',
null,
'tl_from=pr_page' );
596 $queryBuilder->andWhere(
597 $this->linksMigration->getLinksConditions(
599 TitleValue::newFromPage( $page )
604 if ( !$shortCircuit ) {
605 $queryBuilder->fields( [
'pr_page',
'page_namespace',
'page_title',
'pr_type',
'pr_level' ] );
606 $queryBuilder->join(
'page',
null,
'page_id=pr_page' );
609 $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
612 $pageRestrictions = [];
615 foreach ( $res as $row ) {
616 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
617 if ( $expiry > $now ) {
618 if ( $shortCircuit ) {
619 $cacheEntry[
'has_cascading'] =
true;
623 $sources[$row->pr_page] =
new PageIdentityValue( $row->pr_page,
624 $row->page_namespace, $row->page_title, PageIdentity::LOCAL );
628 if ( !isset( $pageRestrictions[$row->pr_type] ) ) {
629 $pageRestrictions[$row->pr_type] = [];
632 if ( !in_array( $row->pr_level, $pageRestrictions[$row->pr_type] ) ) {
633 $pageRestrictions[$row->pr_type][] = $row->pr_level;
638 $cacheEntry[
'has_cascading'] = (bool)$sources;
640 if ( $shortCircuit ) {
644 $cacheEntry[
'cascade_sources'] = [ $sources, $pageRestrictions ];
645 return [ $sources, $pageRestrictions ];
656 return isset( $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'restrictions'] );
668 return isset( $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'cascade_sources'] );
680 if ( !$this->areRestrictionsLoaded( $page ) ) {
681 $this->loadRestrictions( $page );
683 return $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'cascade'] ??
false;
696 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.
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.