75 $this->options = $options;
76 $this->wanCache = $wanCache;
77 $this->loadBalancerFactory = $loadBalancerFactory;
78 $this->linkCache = $linkCache;
79 $this->linksMigration = $linksMigration;
80 $this->commentStore = $commentStore;
81 $this->hookContainer = $hookContainer;
82 $this->hookRunner =
new HookRunner( $hookContainer );
83 $this->pageStore = $pageStore;
105 if ( !in_array( $action, $restrictionTypes ) ) {
110 return $restrictions[$action] ?? [];
123 if ( !$this->areRestrictionsLoaded( $page ) ) {
124 $this->loadRestrictions( $page );
126 return $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'restrictions'] ?? [];
140 if ( !$this->areRestrictionsLoaded( $page ) ) {
141 $this->loadRestrictions( $page );
143 return $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'expiry'][$action] ??
null;
159 $protection = $this->getCreateProtectionInternal( $page );
162 if ( $protection[
'permission'] ==
'sysop' ) {
163 $protection[
'permission'] =
'editprotected';
165 if ( $protection[
'permission'] ==
'autoconfirmed' ) {
166 $protection[
'permission'] =
'editsemiprotected';
181 $dbw = $this->loadBalancerFactory->getPrimaryDatabase();
182 $dbw->newDeleteQueryBuilder()
183 ->deleteFrom(
'protected_titles' )
185 ->caller( __METHOD__ )->execute();
186 $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'create_protection'] =
null;
200 $restrictions = $this->getRestrictions( $page, $action );
201 $semi = $this->options->get( MainConfigNames::SemiprotectedRestrictionLevels );
202 if ( !$restrictions || !$semi ) {
208 foreach ( array_keys( $semi,
'editsemiprotected' ) as $key ) {
209 $semi[$key] =
'autoconfirmed';
211 foreach ( array_keys( $restrictions,
'editsemiprotected' ) as $key ) {
212 $restrictions[$key] =
'autoconfirmed';
215 return !array_diff( $restrictions, $semi );
234 $applicableTypes = $this->listApplicableRestrictionTypes( $page );
236 if ( $action ===
'' ) {
237 foreach ( $applicableTypes as $type ) {
238 if ( $this->isProtected( $page, $type ) ) {
245 if ( !in_array( $action, $applicableTypes ) ) {
249 return (
bool)array_diff(
251 $this->getRestrictions( $page, $action ),
252 $this->options->get( MainConfigNames::RestrictionLevels )
267 return $this->shouldUseVirtualDomains()
268 ? $this->getCascadeProtectionSourcesInternal( $page )[0] !== []
269 : $this->getCascadeProtectionSourcesInternalJoined( $page )[0] !== [];
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->loadBalancerFactory->getPrimaryDatabase();
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 ) use ( $loadRestrictionsFromDb ) {
379 $dbr = $this->loadBalancerFactory->getReplicaDatabase();
380 if ( $this->loadBalancerFactory->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->loadBalancerFactory->getReplicaDatabase();
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->loadBalancerFactory->getReplicaDatabase();
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->shouldUseVirtualDomains()
534 ? $this->getCascadeProtectionSourcesInternal( $page )
535 : $this->getCascadeProtectionSourcesInternalJoined( $page );
549 private function getCascadeProtectionSourcesInternal(
552 if ( !$page->canExist() ) {
553 return [ [], [], [], [] ];
556 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
558 if ( isset( $cacheEntry[
'cascade_sources'] ) ) {
559 return $cacheEntry[
'cascade_sources'];
562 $dbr = $this->loadBalancerFactory->getReplicaDatabase();
565 $cascadeRestrictions = $dbr->newSelectQueryBuilder()
574 ->from(
'page_restrictions' )
575 ->join(
'page',
null,
'page_id=pr_page' )
576 ->where( [
'pr_cascade' => 1 ] )
577 ->caller( __METHOD__ )
580 if ( $cascadeRestrictions->numRows() === 0 ) {
581 return [ [], [], [], [] ];
584 $restrictionsByPage = [];
585 foreach ( $cascadeRestrictions as $row ) {
586 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
587 if ( $expiry > $now ) {
588 if ( !isset( $restrictionsByPage[$row->pr_page] ) ) {
589 $restrictionsByPage[$row->pr_page] = [
590 'title' => PageIdentityValue::localIdentity(
592 (
int)$row->page_namespace,
596 $row->pr_type => $row->pr_level
600 $restrictionsByPage[$row->pr_page][
'restrictions'][$row->pr_type] = $row->pr_level;
605 if ( $restrictionsByPage === [] ) {
606 return [ [], [], [], [] ];
609 $title = TitleValue::newFromPage( $page );
611 $templateLinksDb = $this->loadBalancerFactory->getReplicaDatabase( TemplateLinksTable::VIRTUAL_DOMAIN );
612 $templateLinks = $templateLinksDb->newSelectQueryBuilder()
613 ->select(
'tl_from' )
614 ->from(
'templatelinks' )
615 ->where( [
'tl_from' => array_keys( $restrictionsByPage ) ] )
616 ->andWhere( $this->linksMigration->getLinksConditions(
'templatelinks', $title ) )
617 ->caller( __METHOD__ )
622 $pageRestrictions = [];
624 foreach ( $templateLinks as $link ) {
625 $pageData = $restrictionsByPage[$link->tl_from];
626 $tlSources[$link->tl_from] = $pageData[
'title'];
627 foreach ( $pageData[
'restrictions'] as $type => $level ) {
628 if ( !isset( $pageRestrictions[$type] ) ) {
629 $pageRestrictions[$type] = [];
632 if ( !in_array( $level, $pageRestrictions[$type] ) ) {
633 $pageRestrictions[$type][] = $level;
639 $imageLinksDb = $this->loadBalancerFactory->getReplicaDatabase( ImageLinksTable::VIRTUAL_DOMAIN );
640 $imageLinks = $imageLinksDb->newSelectQueryBuilder()
641 ->select(
'il_from' )
642 ->from(
'imagelinks' )
643 ->where( [
'il_from' => array_keys( $restrictionsByPage ) ] )
644 ->andWhere( $this->linksMigration->getLinksConditions(
'imagelinks', $title ) )
645 ->caller( __METHOD__ )
648 foreach ( $imageLinks as $link ) {
649 $pageData = $restrictionsByPage[$link->il_from];
650 $ilSources[$link->il_from] = $pageData[
'title'];
651 foreach ( $pageData[
'restrictions'] as $type => $level ) {
652 if ( !isset( $pageRestrictions[$type] ) ) {
653 $pageRestrictions[$type] = [];
656 if ( !in_array( $level, $pageRestrictions[$type] ) ) {
657 $pageRestrictions[$type][] = $level;
663 $sources = array_replace( $tlSources, $ilSources );
665 $cacheEntry[
'cascade_sources'] = [ $sources, $pageRestrictions, $tlSources, $ilSources ];
667 return $cacheEntry[
'cascade_sources'];
681 private function getCascadeProtectionSourcesInternalJoined( PageIdentity $page ): array {
682 if ( !$page->canExist() ) {
683 return [ [], [], [], [] ];
686 $cacheEntry = &$this->cache[CacheKeyHelper::getKeyForPage( $page )];
688 if ( isset( $cacheEntry[
'cascade_sources'] ) ) {
689 return $cacheEntry[
'cascade_sources'];
692 $title = TitleValue::newFromPage( $page );
694 $dbr = $this->loadBalancerFactory->getReplicaDatabase();
695 $baseQuery = $dbr->newSelectQueryBuilder()
704 ->from(
'page_restrictions' )
705 ->join(
'page',
null,
'page_id=pr_page' )
706 ->where( [
'pr_cascade' => 1 ] );
708 $templateQuery = clone $baseQuery;
709 $templateQuery->join(
'templatelinks',
null,
'tl_from=pr_page' )
710 ->fields( [
'type' => $dbr->addQuotes(
'tl' ) ] )
711 ->andWhere( $this->linksMigration->getLinksConditions(
'templatelinks', $title ) );
714 $imageQuery = clone $baseQuery;
715 $imageQuery->join(
'imagelinks',
null,
'il_from=pr_page' )
716 ->fields( [
'type' => $dbr->addQuotes(
'il' ) ] )
717 ->andWhere( $this->linksMigration->getLinksConditions(
'imagelinks', $title ) );
719 $unionQuery = $dbr->newUnionQueryBuilder()
721 ->add( $templateQuery )
723 $res = $unionQuery->caller( __METHOD__ )->fetchResultSet();
725 $res = $templateQuery->caller( __METHOD__ )->fetchResultSet();
730 $pageRestrictions = [];
733 foreach ( $res as $row ) {
734 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
735 if ( $expiry > $now ) {
736 if ( $row->type ===
'il' ) {
737 $ilSources[$row->pr_page] = PageIdentityValue::localIdentity(
739 (
int)$row->page_namespace,
742 } elseif ( $row->type ===
'tl' ) {
743 $tlSources[$row->pr_page] = PageIdentityValue::localIdentity(
745 (
int)$row->page_namespace,
753 if ( !isset( $pageRestrictions[$row->pr_type] ) ) {
754 $pageRestrictions[$row->pr_type] = [];
757 if ( !in_array( $row->pr_level, $pageRestrictions[$row->pr_type] ) ) {
758 $pageRestrictions[$row->pr_type][] = $row->pr_level;
763 $sources = array_replace( $tlSources, $ilSources );
765 $cacheEntry[
'cascade_sources'] = [ $sources, $pageRestrictions, $tlSources, $ilSources ];
767 return $cacheEntry[
'cascade_sources'];
784 private function shouldUseVirtualDomains(): bool {
785 $virtualDomains = $this->options->get( MainConfigNames::VirtualDomainsMapping );
786 return isset( $virtualDomains[LinksTable::VIRTUAL_DOMAIN] );
797 return isset( $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'restrictions'] );
809 return isset( $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'cascade_sources'] );
821 if ( !$this->areRestrictionsLoaded( $page ) ) {
822 $this->loadRestrictions( $page );
824 return $this->cache[CacheKeyHelper::getKeyForPage( $page )][
'cascade'] ??
false;
837 unset( $this->cache[CacheKeyHelper::getKeyForPage( $page )] );