29 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
47 use Wikimedia\Assert\Assert;
48 use Wikimedia\Assert\PreconditionException;
49 use Wikimedia\IPUtils;
50 use Wikimedia\NonSerializable\NonSerializableTrait;
62 use NonSerializableTrait;
63 use ProtectedHookAccessorTrait;
147 $pageIdentity->
assertWiki( PageIdentity::LOCAL );
151 if ( !
$title->canExist() ) {
156 "WikiPage constructed on a Title that cannot exist as a page: $title",
184 return MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $pageIdentity );
198 public static function newFromID( $id, $from =
'fromdb' ) {
199 return MediaWikiServices::getInstance()->getWikiPageFactory()->newFromID( $id, $from );
214 public static function newFromRow( $row, $from =
'fromdb' ) {
215 return MediaWikiServices::getInstance()->getWikiPageFactory()->newFromRow( $row, $from );
227 return self::READ_NORMAL;
229 return self::READ_LATEST;
231 return self::READ_LOCKING;
242 return MediaWikiServices::getInstance()->getRevisionStore();
249 return MediaWikiServices::getInstance()->getRevisionRenderer();
256 return MediaWikiServices::getInstance()->getSlotRoleRegistry();
263 return MediaWikiServices::getInstance()->getContentHandlerFactory();
270 return MediaWikiServices::getInstance()->getParserCache();
277 return MediaWikiServices::getInstance()->getDBLoadBalancer();
317 $this->mDataLoaded =
false;
329 $this->mRedirectTarget =
null;
330 $this->mLastRevision =
null;
331 $this->mTouched =
'19700101000000';
332 $this->mLinksUpdated =
'19700101000000';
333 $this->mTimestamp =
'';
334 $this->mIsRedirect =
false;
335 $this->mLatest =
false;
348 $this->mPreparedEdit =
false;
364 'tables' => [
'page' ],
374 'page_links_updated',
377 'page_content_model',
383 $ret[
'fields'][] =
'page_lang';
399 $this->getHookRunner()->onArticlePageDataBefore(
400 $this, $pageQuery[
'fields'], $pageQuery[
'tables'], $pageQuery[
'joins'] );
402 $row =
$dbr->selectRow(
403 $pageQuery[
'tables'],
404 $pageQuery[
'fields'],
411 $this->getHookRunner()->onArticlePageDataAfter( $this, $row );
426 if ( !
$title->canExist() ) {
431 'page_namespace' =>
$title->getNamespace(),
432 'page_title' =>
$title->getDBkey() ], $options );
444 return $this->
pageData(
$dbr, [
'page_id' => $id ], $options );
460 if ( !$this->mTitle->canExist() ) {
467 "Accessing WikiPage that cannot exist as a page: {$this->mTitle}. ",
473 if ( is_int( $from ) && $from <= $this->mDataLoadedFrom ) {
478 if ( is_int( $from ) ) {
481 $db = $loadBalancer->getConnection( $index );
486 && $loadBalancer->getServerCount() > 1
487 && $loadBalancer->hasOrMadeRecentMasterChanges()
489 $from = self::READ_LATEST;
491 $db = $loadBalancer->getConnection( $index );
497 $from = self::READ_NORMAL;
519 if ( !is_int( $from ) ) {
521 $from = self::READ_NORMAL;
524 if ( $from <= $this->mDataLoadedFrom ) {
543 $lc = MediaWikiServices::getInstance()->getLinkCache();
544 $lc->clearLink( $this->mTitle );
547 $lc->addGoodLinkObjFromRow( $this->mTitle, $data );
549 $this->mTitle->loadFromRow( $data );
552 $this->mTitle->loadRestrictions( $data->page_restrictions );
554 $this->mId = intval( $data->page_id );
555 $this->mTouched = MWTimestamp::convert( TS_MW, $data->page_touched );
556 $this->mLinksUpdated = $data->page_links_updated ===
null
558 : MWTimestamp::convert( TS_MW, $data->page_links_updated );
559 $this->mIsRedirect = intval( $data->page_is_redirect );
560 $this->mLatest = intval( $data->page_latest );
563 if ( $this->mLastRevision && $this->mLastRevision->getId() != $this->mLatest ) {
564 $this->mLastRevision =
null;
565 $this->mTimestamp =
'';
568 $lc->addBadLinkObj( $this->mTitle );
570 $this->mTitle->loadFromRow(
false );
577 $this->mDataLoaded =
true;
593 Assert::precondition(
594 $this->mTitle->canExist(),
595 'This WikiPage instance does not represent a proper page!'
604 public function getId( $wikiId = self::LOCAL ): int {
608 if ( !$this->mDataLoaded ) {
618 if ( !$this->mDataLoaded ) {
621 return $this->mId > 0;
633 return $this->mTitle->isKnown();
642 if ( !$this->mDataLoaded ) {
661 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
663 return $cache->getWithSetCallback(
664 $cache->makeKey(
'page-content-model', $this->getLatest() ),
670 $slot = $rev->getSlot(
674 return $slot->getModel();
676 LoggerFactory::getInstance(
'wikipage' )->warning(
677 'Page exists but has no (visible) revisions!',
679 'page-title' => $this->mTitle->getPrefixedDBkey(),
680 'page-id' => $this->getId(),
683 return $this->mTitle->getContentModel();
686 [
'pcTTL' => $cache::TTL_PROC_LONG ]
691 return $this->mTitle->getContentModel();
699 if ( !$this->mDataLoaded ) {
702 return ( $this->mId && !$this->mIsRedirect );
710 if ( !$this->mDataLoaded ) {
721 if ( !$this->mDataLoaded ) {
732 if ( !$this->mDataLoaded ) {
747 return $rev ?
new Revision( $rev ) :
null;
755 if ( $this->mLastRevision !==
null ) {
759 if ( !$this->mTitle->canExist() ) {
766 "Accessing WikiPage that cannot exist as a page: {$this->mTitle}. ",
776 if ( $this->mDataLoadedFrom == self::READ_LOCKING ) {
785 ->getRevisionByPageId( $this->
getId(), $latest, RevisionStore::READ_LOCKING );
786 } elseif ( $this->mDataLoadedFrom == self::READ_LATEST ) {
791 ->getRevisionByPageId( $this->
getId(), $latest, RevisionStore::READ_LATEST );
806 $this->mLastRevision = $revRecord;
818 if ( $this->mLastRevision ) {
819 return new Revision( $this->mLastRevision );
831 if ( $this->mLastRevision ) {
852 if ( $this->mLastRevision ) {
853 return $this->mLastRevision->getContent( SlotRecord::MAIN, $audience, $performer );
863 if ( !$this->mTimestamp ) {
867 return MWTimestamp::convert( TS_MW, $this->mTimestamp );
876 $this->mTimestamp = MWTimestamp::convert( TS_MW, $ts );
889 public function getUser( $audience = RevisionRecord::FOR_PUBLIC,
Authority $performer =
null ) {
891 if ( $this->mLastRevision ) {
892 $revUser = $this->mLastRevision->getUser( $audience, $performer );
893 return $revUser ? $revUser->getId() : 0;
913 return $revRecord->getUser( $audience, $performer );
931 if ( $this->mLastRevision ) {
932 $revUser = $this->mLastRevision->getUser( $audience, $performer );
933 return $revUser ? $revUser->getName() :
'';
952 if ( $this->mLastRevision ) {
953 $revComment = $this->mLastRevision->getComment( $audience, $performer );
954 return $revComment ? $revComment->text :
'';
967 if ( $this->mLastRevision ) {
968 return $this->mLastRevision->isMinor();
987 if ( !$this->mTitle->isContentPage() ) {
1012 $hasLinks = (bool)count( $editInfo->output->getLinks() );
1017 [
'pl_from' => $this->
getId() ], __METHOD__ );
1025 return $content->isCountable( $hasLinks );
1036 if ( !$this->mTitle->isRedirect() ) {
1040 if ( $this->mRedirectTarget !==
null ) {
1046 $row =
$dbr->selectRow(
'redirect',
1047 [
'rd_namespace',
'rd_title',
'rd_fragment',
'rd_interwiki' ],
1048 [
'rd_from' => $this->
getId() ],
1053 if ( $row && $row->rd_fragment !==
null && $row->rd_interwiki !==
null ) {
1057 if ( $row->rd_namespace ==
NS_MEDIA ) {
1060 $namespace = $row->rd_namespace;
1063 $namespace, $row->rd_title,
1064 $row->rd_fragment, $row->rd_interwiki
1092 function () use ( $retval, $latest ) {
1095 DeferredUpdates::POSTSEND,
1110 $dbw->startAtomic( __METHOD__ );
1113 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1114 $truncatedFragment = $contLang->truncateForDatabase( $rt->
getFragment(), 255 );
1118 'rd_from' => $this->
getId(),
1121 'rd_fragment' => $truncatedFragment,
1128 'rd_fragment' => $truncatedFragment,
1138 $dbw->endAtomic( __METHOD__ );
1164 if ( $rt->isExternal() ) {
1165 if ( $rt->isLocal() ) {
1169 $source = $this->mTitle->getFullURL(
'redirect=no' );
1170 return $rt->getFullURL( [
'rdfrom' =>
$source ] );
1178 if ( $rt->isSpecialPage() ) {
1182 if ( $rt->isValidRedirectTarget() ) {
1183 return $rt->getFullURL();
1203 $actorQuery = $actorMigration->getJoin(
'rev_user' );
1205 $tables = array_merge( [
'revision' ], $actorQuery[
'tables'], [
'user' ] );
1207 $revactor_actor = $actorQuery[
'fields'][
'rev_actor'];
1209 'user_id' => $actorQuery[
'fields'][
'rev_user'],
1210 'user_name' => $actorQuery[
'fields'][
'rev_user_text'],
1211 'actor_id' =>
"MIN($revactor_actor)",
1212 'user_real_name' =>
'MIN(user_real_name)',
1213 'timestamp' =>
'MAX(rev_timestamp)',
1216 $conds = [
'rev_page' => $this->
getId() ];
1223 $conds[] =
'NOT(' . $actorMigration->getWhere(
$dbr,
'rev_user', $user )[
'conds'] .
')';
1226 $conds[] =
"{$dbr->bitAnd( 'rev_deleted', RevisionRecord::DELETED_USER )} = 0";
1229 'user' => [
'LEFT JOIN', $actorQuery[
'fields'][
'rev_user'] .
' = user_id' ],
1230 ] + $actorQuery[
'joins'];
1233 'GROUP BY' => [ $fields[
'user_id'], $fields[
'user_name'] ],
1234 'ORDER BY' =>
'timestamp DESC',
1237 $res =
$dbr->select( $tables, $fields, $conds, __METHOD__, $options, $jconds );
1252 && ( $oldId ===
null || $oldId === 0 || $oldId === $this->
getLatest() )
1272 ParserOptions $parserOptions, $oldid =
null, $noCache =
false
1284 $options = $noCache ? ParserOutputAccess::OPT_NO_CACHE : 0;
1286 $status = MediaWikiServices::getInstance()->getParserOutputAccess()->getParserOutput(
1287 $this, $parserOptions, $revision, $options
1289 return $status->isOK() ? $status->getValue() :
false;
1305 function () use ( $user, $oldid ) {
1306 $this->getHookRunner()->onPageViewUpdates( $this, $user );
1310 DeferredUpdates::PRESEND
1321 if ( !$this->getHookRunner()->onArticlePurge( $this ) ) {
1325 $this->mTitle->invalidateCache();
1328 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1329 $hcu->purgeTitleUrls( $this->mTitle, $hcu::PURGE_PRESEND );
1331 if ( $this->mTitle->getNamespace() ===
NS_MEDIAWIKI ) {
1332 MediaWikiServices::getInstance()->getMessageCache()
1333 ->updateMessageOverride( $this->mTitle, $this->
getContent() );
1358 $pageIdForInsert = $pageId ? [
'page_id' => $pageId ] : [];
1362 'page_namespace' => $this->mTitle->getNamespace(),
1363 'page_title' => $this->mTitle->getDBkey(),
1364 'page_restrictions' =>
'',
1365 'page_is_redirect' => 0,
1368 'page_touched' => $dbw->timestamp(),
1371 ] + $pageIdForInsert,
1376 if ( $dbw->affectedRows() > 0 ) {
1377 $newid = $pageId ? (int)$pageId : $dbw->insertId();
1378 $this->mId = $newid;
1379 $this->mTitle->resetArticleID( $newid );
1403 $lastRevIsRedirect =
null
1410 if ( (
int)$revision->getId() === 0 ) {
1411 throw new InvalidArgumentException(
1412 __METHOD__ .
': Revision has ID ' . var_export( $revision->getId(), 1 )
1416 if ( $revision instanceof
Revision ) {
1417 wfDeprecated( __METHOD__ .
' with a Revision object',
'1.35' );
1418 $revision = $revision->getRevisionRecord();
1421 $content = $revision->getContent( SlotRecord::MAIN );
1425 $conditions = [
'page_id' => $this->
getId() ];
1427 if ( $lastRevision !==
null ) {
1429 $conditions[
'page_latest'] = $lastRevision;
1432 $revId = $revision->getId();
1433 Assert::parameter( $revId > 0,
'$revision->getId()',
'must be > 0' );
1435 $model = $revision->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )->getModel();
1438 'page_latest' => $revId,
1439 'page_touched' => $dbw->timestamp( $revision->getTimestamp() ),
1440 'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
1441 'page_is_redirect' => $rt !==
null ? 1 : 0,
1443 'page_content_model' => $model,
1446 $dbw->update(
'page',
1451 $result = $dbw->affectedRows() > 0;
1455 $this->mLatest = $revision->getId();
1456 $this->mIsRedirect = (bool)$rt;
1458 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1459 $linkCache->addGoodLinkObj(
1487 $isRedirect = $redirectTitle !==
null;
1489 if ( !$isRedirect && $lastRevIsRedirect ===
false ) {
1493 if ( $isRedirect ) {
1497 $where = [
'rd_from' => $this->
getId() ];
1498 $dbw->delete(
'redirect', $where, __METHOD__ );
1503 MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()
1504 ->invalidateImageRedirect( $this->
getTitle() );
1523 $revisionRecord = $revision->getRevisionRecord();
1525 $row = $dbw->selectRow(
1526 [
'revision',
'page' ],
1527 [
'rev_id',
'rev_timestamp',
'page_is_redirect' ],
1529 'page_id' => $this->
getId(),
1530 'page_latest=rev_id'
1536 $rowTimestamp = MWTimestamp::convert( TS_MW, $row->rev_timestamp );
1537 if ( $rowTimestamp >= $revisionRecord->getTimestamp() ) {
1540 $prev = $row->rev_id;
1541 $lastRevIsRedirect = (bool)$row->page_is_redirect;
1545 $lastRevIsRedirect =
null;
1572 wfDeprecated( __METHOD__ .
' with Revision objects',
'1.35' );
1573 $a = $a->getRevisionRecord();
1576 wfDeprecated( __METHOD__ .
' with Revision objects',
'1.35' );
1577 $b = $b->getRevisionRecord();
1579 $aSlots = $a->getSlots();
1580 $bSlots = $b->getSlots();
1581 $changedRoles = $aSlots->getRolesWithDifferentContent( $bSlots );
1583 return ( $changedRoles !== [ SlotRecord::MAIN ] && $changedRoles !== [] );
1603 if ( self::hasDifferencesOutsideMainSlot(
1615 $revision = $revRecord ?
new Revision( $revRecord ) :
null;
1616 return $handler->getUndoContent( $revision, $undo, $undoafter );
1648 $sectionId,
Content $sectionContent, $sectionTitle =
'', $edittime =
null
1651 if ( $edittime && $sectionId !==
'new' ) {
1653 $rev = $this->
getRevisionStore()->getRevisionByTimestamp( $this->mTitle, $edittime );
1658 && $lb->getServerCount() > 1
1659 && $lb->hasOrMadeRecentMasterChanges()
1662 $this->mTitle, $edittime, RevisionStore::READ_LATEST );
1665 $baseRevId = $rev->getId();
1669 return $this->
replaceSectionAtRev( $sectionId, $sectionContent, $sectionTitle, $baseRevId );
1686 $sectionTitle =
'', $baseRevId =
null
1688 if ( strval( $sectionId ) ===
'' ) {
1690 $newContent = $sectionContent;
1693 throw new MWException(
"sections not supported for content model " .
1698 if ( $baseRevId ===
null || $sectionId ===
'new' ) {
1702 if ( !$revRecord ) {
1703 wfDebug( __METHOD__ .
" asked for bogus section (page: " .
1704 $this->
getId() .
"; section: $sectionId)" );
1708 $oldContent = $revRecord->getContent( SlotRecord::MAIN );
1711 if ( !$oldContent ) {
1712 wfDebug( __METHOD__ .
": no page text" );
1716 $newContent = $oldContent->replaceSection( $sectionId, $sectionContent, $sectionTitle );
1749 $services = MediaWikiServices::getInstance();
1751 $services->getMainObjectStash(),
1752 $services->getDBLoadBalancer(),
1754 EditResultCache::CONSTRUCTOR_OPTIONS,
1755 $services->getMainConfig()
1766 $services->getMessageCache(),
1767 $services->getContentLanguage(),
1768 $services->getDBLoadBalancerFactory(),
1769 $this->getContentHandlerFactory(),
1770 $this->getHookContainer(),
1809 User $forUser =
null,
1814 if ( !$forRevision && !$forUpdate ) {
1817 $this->derivedDataUpdater =
null;
1820 if ( $this->derivedDataUpdater && !$this->derivedDataUpdater->isContentPrepared() ) {
1824 $this->derivedDataUpdater =
null;
1831 if ( $this->derivedDataUpdater
1832 && !$this->derivedDataUpdater->isReusableFor(
1836 $forEdit ? $this->getLatest() :
null
1839 $this->derivedDataUpdater =
null;
1842 if ( !$this->derivedDataUpdater ) {
1867 $config = MediaWikiServices::getInstance()->getMainConfig();
1877 $this->getHookContainer(),
1879 PageUpdater::CONSTRUCTOR_OPTIONS,
1885 $pageUpdater->setUsePageCreationLog( $config->get(
'PageCreationLog' ) );
1886 $pageUpdater->setAjaxEditStash( $config->get(
'AjaxEditStash' ) );
1887 $pageUpdater->setUseAutomaticEditSummaries(
1888 $config->get(
'UseAutomaticEditSummaries' )
1891 return $pageUpdater;
1963 Authority $performer =
null, $serialFormat =
null, $tags = [], $undidRevId = 0
1967 if ( !$performer ) {
1968 $performer = $wgUser;
1972 $content, $performer, $summary, $flags, $originalRevId, $tags, $undidRevId
2041 $originalRevId =
false,
2059 $slotsUpdate->modifyContent( SlotRecord::MAIN,
$content );
2064 $user = MediaWikiServices::getInstance()->getUserFactory()->newFromAuthority( $performer );
2066 $updater->setContent( SlotRecord::MAIN,
$content );
2069 $originalRevision = $originalRevId ? $revisionStore->getRevisionById( $originalRevId ) :
null;
2070 if ( $originalRevision && $undidRevId !== 0 ) {
2072 $oldestRevertedRev = $revisionStore->getNextRevision( $originalRevision );
2073 if ( $oldestRevertedRev ) {
2074 $updater->markAsRevert(
2075 EditResult::REVERT_UNDO,
2076 $oldestRevertedRev->getId(),
2081 $updater->markAsRevert( EditResult::REVERT_UNDO, $undidRevId );
2083 } elseif ( $undidRevId !== 0 ) {
2086 $updater->markAsRevert( EditResult::REVERT_UNDO, $undidRevId );
2090 $undidRevision = $revisionStore->getRevisionById( $undidRevId );
2091 if ( $undidRevision ) {
2092 $originalRevision = $revisionStore->getPreviousRevision( $undidRevision );
2098 if ( $originalRevision &&
2099 $originalRevision->getContent( SlotRecord::MAIN )->equals(
$content )
2101 $updater->setOriginalRevisionId( $originalRevision->getId() );
2110 if ( $needsPatrol && $performer->
authorizeWrite(
'autopatrol', $this->getTitle() ) ) {
2114 $updater->addTags( $tags );
2116 $revRec = $updater->saveRevision(
2128 $this->mLatest = $revRec->getId();
2131 return $updater->getStatus();
2151 if ( $this->
getTitle()->isConversionTable() ) {
2154 $options->disableContentConversion();
2183 $serialFormat =
null,
2192 if ( $revision !==
null ) {
2193 if ( $revision instanceof
Revision ) {
2194 wfDeprecated( __METHOD__ .
' with a Revision object',
'1.35' );
2195 $revision = $revision->getRevisionRecord();
2197 throw new InvalidArgumentException(
2198 __METHOD__ .
': invalid $revision argument type ' . gettype( $revision ) );
2202 $slots = RevisionSlotsUpdate::newFromContent( [ SlotRecord::MAIN =>
$content ] );
2205 if ( !$updater->isUpdatePrepared() ) {
2206 $updater->prepareContent( $user, $slots, $useCache );
2209 $updater->prepareUpdate(
2212 'causeAction' =>
'prepare-edit',
2213 'causeAgent' => $user->getName(),
2219 return $updater->getPreparedEdit();
2252 if ( $revisionRecord instanceof
Revision ) {
2253 wfDeprecated( __METHOD__ .
' with a Revision object',
'1.35' );
2254 $revisionRecord = $revisionRecord->getRevisionRecord();
2256 if ( isset( $options[
'oldrevision'] ) && $options[
'oldrevision'] instanceof
Revision ) {
2258 __METHOD__ .
' with the `oldrevision` option being a ' .
2262 $options[
'oldrevision'] = $options[
'oldrevision']->getRevisionRecord();
2266 'causeAction' =>
'edit-page',
2267 'causeAgent' => $user->
getName(),
2272 $updater->prepareUpdate( $revisionRecord, $options );
2274 $updater->doUpdates();
2292 if ( !$revision || !$revision->getId() ) {
2293 LoggerFactory::getInstance(
'wikipage' )->info(
2294 __METHOD__ .
'called with ' . ( $revision ?
'unsaved' :
'no' ) .
' revision'
2301 $updater->prepareUpdate( $revision, $options );
2302 $updater->doParserCacheUpdate();
2335 $options[
'recursive'] = $options[
'recursive'] ??
true;
2337 if ( !$revision || !$revision->getId() ) {
2338 LoggerFactory::getInstance(
'wikipage' )->info(
2339 __METHOD__ .
'called with ' . ( $revision ?
'unsaved' :
'no' ) .
' revision'
2346 $updater->prepareUpdate( $revision, $options );
2347 $updater->doSecondaryDataUpdates( $options );
2365 &$cascade, $reason,
User $user, $tags =
null
2376 $this->mTitle->loadRestrictions(
null, Title::READ_LATEST );
2377 $restrictionTypes = $this->mTitle->getRestrictionTypes();
2378 $id = $this->
getId();
2389 $isProtected =
false;
2395 foreach ( $restrictionTypes as $action ) {
2396 if ( !isset( $expiry[$action] ) || $expiry[$action] === $dbw->getInfinity() ) {
2397 $expiry[$action] =
'infinity';
2399 if ( !isset( $limit[$action] ) ) {
2400 $limit[$action] =
'';
2401 } elseif ( $limit[$action] !=
'' ) {
2406 $current = implode(
'', $this->mTitle->getRestrictions( $action ) );
2407 if ( $current !=
'' ) {
2408 $isProtected =
true;
2411 if ( $limit[$action] != $current ) {
2413 } elseif ( $limit[$action] !=
'' ) {
2417 if ( $this->mTitle->getRestrictionExpiry( $action ) != $expiry[$action] ) {
2423 if ( !$changed && $protect && $this->mTitle->areRestrictionsCascading() != $cascade ) {
2433 $revCommentMsg =
'unprotectedarticle-comment';
2434 $logAction =
'unprotect';
2435 } elseif ( $isProtected ) {
2436 $revCommentMsg =
'modifiedarticleprotection-comment';
2437 $logAction =
'modify';
2439 $revCommentMsg =
'protectedarticle-comment';
2440 $logAction =
'protect';
2443 $logRelationsValues = [];
2444 $logRelationsField =
null;
2445 $logParamsDetails = [];
2448 $nullRevision =
null;
2451 if ( !$this->getHookRunner()->onArticleProtect( $this, $user, $limit, $reason ) ) {
2456 $editrestriction = isset( $limit[
'edit'] )
2457 ? [ $limit[
'edit'] ]
2458 : $this->mTitle->getRestrictions(
'edit' );
2459 foreach ( array_keys( $editrestriction,
'sysop' ) as $key ) {
2460 $editrestriction[$key] =
'editprotected';
2462 foreach ( array_keys( $editrestriction,
'autoconfirmed' ) as $key ) {
2463 $editrestriction[$key] =
'editsemiprotected';
2467 foreach ( array_keys( $cascadingRestrictionLevels,
'sysop' ) as $key ) {
2468 $cascadingRestrictionLevels[$key] =
'editprotected';
2470 foreach ( array_keys( $cascadingRestrictionLevels,
'autoconfirmed' ) as $key ) {
2471 $cascadingRestrictionLevels[$key] =
'editsemiprotected';
2475 if ( !array_intersect( $editrestriction, $cascadingRestrictionLevels ) ) {
2490 if ( $nullRevisionRecord ===
null ) {
2491 return Status::newFatal(
'no-null-revision', $this->mTitle->getPrefixedText() );
2494 $logRelationsField =
'pr_id';
2501 $existingProtectionIds = $dbw->selectFieldValues(
2502 'page_restrictions',
2506 'pr_type' => array_map(
'strval', array_keys( $limit ) )
2511 if ( $existingProtectionIds ) {
2513 'page_restrictions',
2514 [
'pr_id' => $existingProtectionIds ],
2520 foreach ( $limit as $action => $restrictions ) {
2521 if ( $restrictions !=
'' ) {
2522 $cascadeValue = ( $cascade && $action ==
'edit' ) ? 1 : 0;
2524 'page_restrictions',
2527 'pr_type' => $action,
2528 'pr_level' => $restrictions,
2529 'pr_cascade' => $cascadeValue,
2530 'pr_expiry' => $dbw->encodeExpiry( $expiry[$action] )
2534 $logRelationsValues[] = $dbw->insertId();
2535 $logParamsDetails[] = [
2537 'level' => $restrictions,
2538 'expiry' => $expiry[$action],
2539 'cascade' => (bool)$cascadeValue,
2547 [
'page_restrictions' =>
'' ],
2548 [
'page_id' => $id ],
2552 $this->getHookRunner()->onRevisionFromEditComplete(
2553 $this, $nullRevisionRecord, $latest, $user, $tags );
2556 if ( $this->getHookContainer()->isRegistered(
'NewRevisionFromEditComplete' ) ) {
2558 $nullRevision =
new Revision( $nullRevisionRecord );
2559 $this->getHookRunner()->onNewRevisionFromEditComplete(
2560 $this, $nullRevision, $latest, $user, $tags );
2563 $this->getHookRunner()->onArticleProtectComplete( $this, $user, $limit, $reason );
2568 if ( $limit[
'create'] !=
'' ) {
2570 $dbw->replace(
'protected_titles',
2571 [ [
'pt_namespace',
'pt_title' ] ],
2573 'pt_namespace' => $this->mTitle->getNamespace(),
2574 'pt_title' => $this->mTitle->getDBkey(),
2575 'pt_create_perm' => $limit[
'create'],
2576 'pt_timestamp' => $dbw->timestamp(),
2577 'pt_expiry' => $dbw->encodeExpiry( $expiry[
'create'] ),
2578 'pt_user' => $user->
getId(),
2579 ] + $commentFields, __METHOD__
2581 $logParamsDetails[] = [
2583 'level' => $limit[
'create'],
2584 'expiry' => $expiry[
'create'],
2587 $dbw->delete(
'protected_titles',
2589 'pt_namespace' => $this->mTitle->getNamespace(),
2590 'pt_title' => $this->mTitle->getDBkey()
2596 $this->mTitle->flushRestrictions();
2599 if ( $logAction ==
'unprotect' ) {
2604 '4::description' => $protectDescriptionLog,
2605 '5:bool:cascade' => $cascade,
2606 'details' => $logParamsDetails,
2612 $logEntry->setTarget( $this->mTitle );
2613 $logEntry->setComment( $reason );
2614 $logEntry->setPerformer( $user );
2615 $logEntry->setParameters( $params );
2616 if ( $nullRevision !==
null ) {
2617 $logEntry->setAssociatedRevId( $nullRevision->getId() );
2619 $logEntry->addTags( $tags );
2620 if ( $logRelationsField !==
null && count( $logRelationsValues ) ) {
2621 $logEntry->setRelations( [ $logRelationsField => $logRelationsValues ] );
2623 $logId = $logEntry->insert();
2624 $logEntry->publish( $logId );
2643 string $revCommentMsg,
2655 $this->mTitle->getPrefixedText(),
2657 )->inContentLanguage()->text();
2659 $editComment .=
wfMessage(
'colon-separator' )->inContentLanguage()->text() . $reason;
2662 if ( $protectDescription ) {
2663 $editComment .=
wfMessage(
'word-separator' )->inContentLanguage()->text();
2664 $editComment .=
wfMessage(
'parentheses' )->params( $protectDescription )
2665 ->inContentLanguage()->text();
2668 $editComment .=
wfMessage(
'word-separator' )->inContentLanguage()->text();
2669 $editComment .=
wfMessage(
'brackets' )->params(
2670 wfMessage(
'protect-summary-cascade' )->inContentLanguage()->text()
2671 )->inContentLanguage()->text();
2676 $nullRevRecord =
$revStore->newNullRevision(
2684 if ( $nullRevRecord ) {
2685 $inserted =
$revStore->insertRevisionOn( $nullRevRecord, $dbw );
2688 $oldLatest = $inserted->getParentId();
2703 if ( $expiry !=
'infinity' ) {
2704 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
2707 $contLang->timeanddate( $expiry,
false,
false ),
2708 $contLang->date( $expiry,
false,
false ),
2709 $contLang->time( $expiry,
false,
false )
2710 )->inContentLanguage()->text();
2712 return wfMessage(
'protect-expiry-indefinite' )
2713 ->inContentLanguage()->text();
2725 $protectDescription =
'';
2727 foreach ( array_filter( $limit ) as $action => $restrictions ) {
2728 # $action is one of $wgRestrictionTypes = [ 'create', 'edit', 'move', 'upload' ].
2729 # All possible message keys are listed here for easier grepping:
2730 # * restriction-create
2731 # * restriction-edit
2732 # * restriction-move
2733 # * restriction-upload
2734 $actionText =
wfMessage(
'restriction-' . $action )->inContentLanguage()->text();
2735 # $restrictions is one of $wgRestrictionLevels = [ '', 'autoconfirmed', 'sysop' ],
2736 # with '' filtered out. All possible message keys are listed below:
2737 # * protect-level-autoconfirmed
2738 # * protect-level-sysop
2739 $restrictionsText =
wfMessage(
'protect-level-' . $restrictions )
2740 ->inContentLanguage()->text();
2744 if ( $protectDescription !==
'' ) {
2745 $protectDescription .=
wfMessage(
'word-separator' )->inContentLanguage()->text();
2747 $protectDescription .=
wfMessage(
'protect-summary-desc' )
2748 ->params( $actionText, $restrictionsText, $expiryText )
2749 ->inContentLanguage()->text();
2752 return $protectDescription;
2767 $protectDescriptionLog =
'';
2769 $dirMark = MediaWikiServices::getInstance()->getContentLanguage()->getDirMark();
2770 foreach ( array_filter( $limit ) as $action => $restrictions ) {
2772 $protectDescriptionLog .=
2774 "[$action=$restrictions] ($expiryText)";
2777 return trim( $protectDescriptionLog );
2797 $revCount += $safetyMargin;
2828 $reason,
User $deleter, $suppress =
false, $u1 =
null, &$error =
'', $u2 =
null,
2829 $tags = [], $logsubtype =
'delete', $immediate =
false
2837 $this, $deleter, $reason, $error, $status, $suppress )
2839 if ( $status->isOK() ) {
2841 $status->fatal(
'delete-hook-aborted' );
2847 $logsubtype, $immediate );
2867 $reason, $suppress,
User $deleter, $tags,
2868 $logsubtype, $immediate =
false, $webRequestId =
null
2875 $dbw->startAtomic( __METHOD__ );
2878 $id = $this->
getId();
2884 if ( $id == 0 || $this->
getLatest() != $lockedLatest ) {
2885 $dbw->endAtomic( __METHOD__ );
2887 $status->error(
'cannotdelete',
2901 }
catch ( Exception $ex ) {
2902 wfLogWarning( __METHOD__ .
': failed to load content during deletion! '
2903 . $ex->getMessage() );
2910 $explictTrxLogged =
false;
2913 if ( $done || !$immediate ) {
2916 $dbw->endAtomic( __METHOD__ );
2917 if ( $dbw->explicitTrxActive() ) {
2919 if ( !$explictTrxLogged ) {
2920 $explictTrxLogged =
true;
2921 LoggerFactory::getInstance(
'wfDebug' )->debug(
2922 'explicit transaction active in ' . __METHOD__ .
' while deleting {title}', [
2923 'title' => $this->
getTitle()->getText(),
2928 if ( $dbw->trxLevel() ) {
2929 $dbw->commit( __METHOD__ );
2931 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
2932 $lbFactory->waitForReplication();
2933 $dbw->startAtomic( __METHOD__ );
2938 $dbw->endAtomic( __METHOD__ );
2941 'namespace' => $this->
getTitle()->getNamespace(),
2942 'title' => $this->
getTitle()->getDBkey(),
2943 'wikiPageId' => $id,
2945 'reason' => $reason,
2946 'suppress' => $suppress,
2947 'userId' => $deleter->
getId(),
2948 'tags' => json_encode( $tags ),
2949 'logsubtype' => $logsubtype,
2955 $status->warning(
'delete-scheduled',
2965 $archivedRevisionCount = (int)$dbw->selectField(
2966 'archive',
'COUNT(*)',
2968 'ar_namespace' => $this->getTitle()->getNamespace(),
2969 'ar_title' => $this->
getTitle()->getDBkey(),
2977 $wikiPageBeforeDelete = clone $this;
2980 $dbw->delete(
'page', [
'page_id' => $id ], __METHOD__ );
2983 $logtype = $suppress ?
'suppress' :
'delete';
2986 $logEntry->setPerformer( $deleter );
2987 $logEntry->setTarget( $logTitle );
2988 $logEntry->setComment( $reason );
2989 $logEntry->addTags( $tags );
2990 $logid = $logEntry->insert();
2992 $dbw->onTransactionPreCommitOrIdle(
2993 static function () use ( $logEntry, $logid ) {
2995 $logEntry->publish( $logid );
3000 $dbw->endAtomic( __METHOD__ );
3009 $this->getHookRunner()->onArticleDeleteComplete(
3010 $wikiPageBeforeDelete,
3016 $archivedRevisionCount
3018 $status->value = $logid;
3022 $key = $dbCache->makeKey(
'page-recent-delete', md5( $logTitle->getPrefixedText() ) );
3023 $dbCache->set( $key, 1, $dbCache::TTL_DAY );
3042 $namespace = $this->
getTitle()->getNamespace();
3043 $dbKey = $this->
getTitle()->getDBkey();
3053 $bitfield = RevisionRecord::SUPPRESSED_ALL;
3068 $dbw->lockForUpdate(
3071 [
'revision',
'revision_comment_temp',
'revision_actor_temp' ]
3073 [
'rev_page' => $id ],
3081 $res = $dbw->select(
3084 [
'rev_page' => $id ],
3098 foreach (
$res as $row ) {
3104 $comment = $commentStore->getComment(
'rev_comment', $row );
3107 'ar_namespace' => $namespace,
3108 'ar_title' => $dbKey,
3109 'ar_timestamp' => $row->rev_timestamp,
3110 'ar_minor_edit' => $row->rev_minor_edit,
3111 'ar_rev_id' => $row->rev_id,
3112 'ar_parent_id' => $row->rev_parent_id,
3113 'ar_len' => $row->rev_len,
3114 'ar_page_id' => $id,
3115 'ar_deleted' => $suppress ? $bitfield : $row->rev_deleted,
3116 'ar_sha1' => $row->rev_sha1,
3117 ] + $commentStore->insert( $dbw,
'ar_comment', $comment )
3118 + $actorMigration->getInsertValues( $dbw,
'ar_user', $user );
3120 $rowsInsert[] = $rowInsert;
3121 $revids[] = $row->rev_id;
3125 if ( (
int)$row->rev_user === 0 && IPUtils::isValid( $row->rev_user_text ) ) {
3126 $ipRevIds[] = $row->rev_id;
3131 if ( count( $revids ) > 0 ) {
3133 $dbw->insert(
'archive', $rowsInsert, __METHOD__ );
3135 $dbw->delete(
'revision', [
'rev_id' => $revids ], __METHOD__ );
3136 $dbw->delete(
'revision_comment_temp', [
'revcomment_rev' => $revids ], __METHOD__ );
3137 $dbw->delete(
'revision_actor_temp', [
'revactor_rev' => $revids ], __METHOD__ );
3140 if ( count( $ipRevIds ) > 0 ) {
3141 $dbw->delete(
'ip_changes', [
'ipc_rev_id' => $ipRevIds ], __METHOD__ );
3159 'page_id' => $this->
getId(),
3186 if ( $revRecord && $revRecord instanceof
Revision ) {
3187 wfDeprecated( __METHOD__ .
' with a Revision object',
'1.35' );
3188 $revRecord = $revRecord->getRevisionRecord();
3191 if ( $id !== $this->
getId() ) {
3192 throw new InvalidArgumentException(
'Mismatching page ID' );
3197 }
catch ( Exception $ex ) {
3205 [
'edits' => 1,
'articles' => -$countable,
'pages' => -1 ]
3210 foreach ( $updates as $update ) {
3214 $causeAgent = $user ? $user->getName() :
'unknown';
3217 $this->mTitle,
'templatelinks',
'delete-page', $causeAgent );
3219 if ( $this->mTitle->getNamespace() ===
NS_FILE ) {
3221 $this->mTitle,
'imagelinks',
'delete-page', $causeAgent );
3273 $fromP, $summary, $token, $bot, &$resultDetails,
Authority $performer, $tags =
null
3277 $resultDetails =
null;
3280 $permissionStatus = PermissionStatus::newEmpty();
3284 $user = MediaWikiServices::getInstance()->getUserFactory()->newFromAuthority( $performer );
3285 if ( !$user->matchEditToken( $token,
'rollback' ) ) {
3286 $permissionStatus->fatal(
'sessionfailure' );
3289 if ( $user->pingLimiter(
'rollback' ) || $user->pingLimiter() ) {
3290 $permissionStatus->fatal(
'actionthrottledtext' );
3294 if ( !$permissionStatus->isGood() ) {
3295 return $permissionStatus->toLegacyErrorArray();
3298 return $this->
commitRollback( $fromP, $summary, $bot, $resultDetails, $performer, $tags );
3324 &$resultDetails,
Authority $performer, $tags =
null
3331 return [ [
'readonlytext' ] ];
3336 $user = MediaWikiServices::getInstance()->getUserFactory()->newFromAuthority( $performer );
3338 $current = $updater->grabParentRevision();
3340 if ( $current ===
null ) {
3342 return [ [
'notanarticle' ] ];
3345 $currentEditorForPublic = $current->getUser( RevisionRecord::FOR_PUBLIC );
3346 $legacyCurrentCallback =
static function () use ( $current ) {
3350 $from = str_replace(
'_',
' ', $fromP );
3354 if ( $from !== ( $currentEditorForPublic ? $currentEditorForPublic->getName() :
'' ) ) {
3357 'current' => $legacyCurrentCallback,
3358 'current-revision-record' => $current,
3360 [
'current' =>
'1.35' ],
3363 return [ [
'alreadyrolled',
3364 htmlspecialchars( $this->mTitle->getPrefixedText() ),
3365 htmlspecialchars( $fromP ),
3366 htmlspecialchars( $currentEditorForPublic ? $currentEditorForPublic->getName() :
'' )
3375 $current->getUser( RevisionRecord::RAW )
3378 $s = $dbw->selectRow(
3379 [
'revision' ] + $actorWhere[
'tables'],
3380 [
'rev_id',
'rev_timestamp',
'rev_deleted' ],
3382 'rev_page' => $current->getPageId(),
3383 'NOT(' . $actorWhere[
'conds'] .
')',
3387 'USE INDEX' => [
'revision' =>
'page_timestamp' ],
3388 'ORDER BY' => [
'rev_timestamp DESC',
'rev_id DESC' ]
3390 $actorWhere[
'joins']
3392 if (
$s ===
false ) {
3394 return [ [
'cantrollback' ] ];
3395 } elseif (
$s->rev_deleted & RevisionRecord::DELETED_TEXT
3396 ||
$s->rev_deleted & RevisionRecord::DELETED_USER
3399 return [ [
'notvisiblerev' ] ];
3405 RevisionStore::READ_LATEST
3407 if ( empty( $summary ) ) {
3408 if ( !$currentEditorForPublic ) {
3409 $summary =
wfMessage(
'revertpage-nouser' );
3411 $summary =
wfMessage(
'revertpage-anon' );
3416 $targetEditorForPublic = $target->getUser( RevisionRecord::FOR_PUBLIC );
3419 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
3421 $targetEditorForPublic ? $targetEditorForPublic->getName() :
null,
3422 $currentEditorForPublic ? $currentEditorForPublic->getName() :
null,
3424 $contLang->timeanddate( MWTimestamp::convert( TS_MW,
$s->rev_timestamp ) ),
3426 $contLang->timeanddate( $current->getTimestamp() )
3428 if ( $summary instanceof
Message ) {
3429 $summary = $summary->params(
$args )->inContentLanguage()->text();
3435 $summary = trim( $summary );
3440 if ( $performer->
isAllowed(
'minoredit' ) ) {
3444 if ( $bot && ( $performer->
isAllowedAny(
'markbotedits',
'bot' ) ) ) {
3449 $currentContent = $current->getContent( SlotRecord::MAIN );
3450 $targetContent = $target->getContent( SlotRecord::MAIN );
3451 $changingContentModel = $targetContent->getModel() !== $currentContent->getModel();
3456 foreach ( $target->getSlots()->getSlots() as $slot ) {
3457 $updater->inheritSlot( $slot );
3462 foreach ( $current->getSlotRoles() as $role ) {
3463 if ( !$target->hasSlot( $role ) ) {
3464 $updater->removeSlot( $role );
3468 $updater->setOriginalRevisionId( $target->getId() );
3471 RevisionStore::READ_LATEST
3473 if ( $oldestRevertedRevision !==
null ) {
3474 $updater->markAsRevert(
3475 EditResult::REVERT_ROLLBACK,
3476 $oldestRevertedRevision->getId(),
3490 $rev = $updater->saveRevision(
3498 if ( $bot && $performer->
isAllowed(
'markbotedits' ) ) {
3508 if ( count( $set ) ) {
3512 $current->getUser( RevisionRecord::RAW ),
3515 $dbw->update(
'recentchanges', $set,
3517 'rc_cur_id' => $current->getPageId(),
3518 'rc_timestamp > ' . $dbw->addQuotes(
$s->rev_timestamp ),
3519 $actorWhere[
'conds'],
3525 if ( !$updater->wasSuccessful() ) {
3526 return $updater->getStatus()->getErrorsArray();
3530 if ( $updater->isUnchanged() ) {
3533 'current' => $legacyCurrentCallback,
3534 'current-revision-record' => $current,
3536 [
'current' =>
'1.35' ],
3539 return [ [
'alreadyrolled',
3540 htmlspecialchars( $this->mTitle->getPrefixedText() ),
3541 htmlspecialchars( $fromP ),
3542 htmlspecialchars( $currentEditorForPublic ? $currentEditorForPublic->getName() :
'' )
3546 if ( $changingContentModel ) {
3551 $log->setTarget( $this->mTitle );
3552 $log->setComment( $summary );
3553 $log->setParameters( [
3554 '4::oldmodel' => $currentContent->getModel(),
3555 '5::newmodel' => $targetContent->getModel(),
3558 $logId = $log->insert( $dbw );
3559 $log->publish( $logId );
3562 $revId = $rev->getId();
3565 if ( $this->getHookContainer()->isRegistered(
'ArticleRollbackComplete' ) ) {
3567 $legacyCurrent =
new Revision( $current );
3568 $legacyTarget =
new Revision( $target );
3569 $this->getHookRunner()->onArticleRollbackComplete( $this, $user,
3570 $legacyTarget, $legacyCurrent );
3573 $this->getHookRunner()->onRollbackComplete( $this, $user, $target, $current );
3575 $legacyTargetCallback =
static function () use ( $target ) {
3580 $tags = array_merge(
3582 $updater->getEditResult()->getRevertTags()
3587 'summary' => $summary,
3588 'current' => $legacyCurrentCallback,
3589 'current-revision-record' => $current,
3590 'target' => $legacyTargetCallback,
3591 'target-revision-record' => $target,
3595 [
'current' =>
'1.35',
'target' =>
'1.35' ],
3618 $other =
$title->getOtherPage();
3620 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
3621 $hcu->purgeTitleUrls( [
$title, $other ], $hcu::PURGE_INTENT_TXROUND_REFLECTED );
3624 $title->deleteTitleProtection();
3626 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle(
$title );
3632 [
'causeAction' =>
'page-create' ]
3654 $other =
$title->getOtherPage();
3656 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
3657 $hcu->purgeTitleUrls( [
$title, $other ], $hcu::PURGE_INTENT_TXROUND_REFLECTED );
3661 $services = MediaWikiServices::getInstance();
3662 $services->getLinkCache()->invalidateTitle(
$title );
3668 $services->getMessageCache()->updateMessageOverride(
$title,
null );
3676 [
'causeAction' =>
'page-delete' ]
3685 MediaWikiServices::getInstance()
3686 ->getTalkPageNotificationManager()
3687 ->removeUserHasNewMessages( $user );
3692 $services->getRepoGroup()->getLocalRepo()->invalidateImageRedirect(
$title );
3710 $slotsChanged =
null
3712 if ( $revRecord && $revRecord instanceof
Revision ) {
3713 wfDeprecated( __METHOD__ .
' with a Revision object',
'1.35' );
3714 $revRecord = $revRecord->getRevisionRecord();
3720 if ( $slotsChanged ===
null || in_array( SlotRecord::MAIN, $slotsChanged ) ) {
3727 [
'causeAction' =>
'page-edit' ]
3734 [
'causeAction' =>
'page-edit' ]
3738 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle(
$title );
3740 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
3741 $hcu->purgeTitleUrls(
$title, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
3744 $revid = $revRecord ? $revRecord->getId() :
null;
3768 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
3789 $id = $this->
getId();
3795 $res =
$dbr->select(
'categorylinks',
3796 [
'page_title' =>
'cl_to',
'page_namespace' =>
NS_CATEGORY ],
3797 [
'cl_from' => $id ],
3812 $id = $this->
getId();
3819 $res =
$dbr->select( [
'categorylinks',
'page_props',
'page' ],
3821 [
'cl_from' => $id,
'pp_page=page_id',
'pp_propname' =>
'hiddencat',
3822 'page_namespace' =>
NS_CATEGORY,
'page_title=cl_to' ],
3825 if (
$res !==
false ) {
3826 foreach (
$res as $row ) {
3856 $id = $id ?: $this->
getId();
3857 $type = MediaWikiServices::getInstance()->getNamespaceInfo()->
3860 $addFields = [
'cat_pages = cat_pages + 1' ];
3861 $removeFields = [
'cat_pages = cat_pages - 1' ];
3862 if (
$type !==
'page' ) {
3863 $addFields[] =
"cat_{$type}s = cat_{$type}s + 1";
3864 $removeFields[] =
"cat_{$type}s = cat_{$type}s - 1";
3869 if ( count( $added ) ) {
3870 $existingAdded = $dbw->selectFieldValues(
3873 [
'cat_title' => $added ],
3880 if ( count( $existingAdded ) ) {
3884 [
'cat_title' => $existingAdded ],
3889 $missingAdded = array_diff( $added, $existingAdded );
3890 if ( count( $missingAdded ) ) {
3892 foreach ( $missingAdded as $cat ) {
3894 'cat_title' => $cat,
3896 'cat_subcats' => (
$type ===
'subcat' ) ? 1 : 0,
3897 'cat_files' => (
$type ===
'file' ) ? 1 : 0,
3910 if ( count( $deleted ) ) {
3914 [
'cat_title' => $deleted ],
3919 foreach ( $added as $catName ) {
3921 $this->getHookRunner()->onCategoryAfterPageAdded( $cat, $this );
3924 foreach ( $deleted as $catName ) {
3926 $this->getHookRunner()->onCategoryAfterPageRemoved( $cat, $this, $id );
3929 $cat->refreshCountsIfEmpty();
3945 if ( !$this->getHookRunner()->onOpportunisticLinksUpdate( $this,
3946 $this->mTitle, $parserOutput )
3954 'isOpportunistic' =>
true,
3958 if ( $this->mTitle->areRestrictionsCascading() ) {
3963 } elseif ( !$config->get(
'MiserMode' ) && $parserOutput->
hasDynamicContent() ) {
3974 $key =
$cache->makeKey(
'dynamic-linksupdate',
'last', $this->
getId() );
3976 if (
$cache->add( $key, time(), $ttl ) ) {
3996 wfDeprecated( __METHOD__ .
' without a RevisionRecord',
'1.32' );
4000 }
catch ( Exception $ex ) {
4005 wfDebug( __METHOD__ .
' failed to load current revision of page ' . $this->
getId() );
4011 } elseif ( $rev instanceof
Content ) {
4012 wfDeprecated( __METHOD__ .
' with a Content object instead of a RevisionRecord',
'1.32' );
4014 $slotContent = [ SlotRecord::MAIN => $rev ];
4016 $slotContent = array_map(
static function (
SlotRecord $slot ) {
4018 }, $rev->getSlots()->getSlots() );
4028 foreach ( $slotContent as $role =>
$content ) {
4029 $handler =
$content->getContentHandler();
4031 $updates = $handler->getDeletionUpdates(
4035 $allUpdates = array_merge( $allUpdates, $updates );
4038 $legacyUpdates =
$content->getDeletionUpdates( $this );
4041 $legacyUpdates = array_filter( $legacyUpdates,
static function ( $update ) {
4045 $allUpdates = array_merge( $allUpdates, $legacyUpdates );
4048 $this->getHookRunner()->onPageDeletionDataUpdates(
4049 $this->
getTitle(), $rev, $allUpdates );
4052 $this->getHookRunner()->onWikiPageDeletionUpdates( $this,
$content, $allUpdates );
4090 return $this->
getTitle()->getCanonicalURL();
4099 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
4101 return $linkCache->getMutableCacheKeys(
$cache, $this->
getTitle() );
4123 return $this->
getTitle()->getNamespace();
4131 return $this->
getTitle()->getDBkey();
4139 return $this->
getTitle()->getWikiId();
4148 return $this->mTitle->canExist();
4156 return $this->mTitle->__toString();
4169 if ( $other->
getWikiId() !== $this->getWikiId()
4170 || $other->
getId() !== $this->getId() ) {
4174 if ( $this->
getId() === 0 ) {
4176 || $other->
getDBkey() !== $this->getDBkey() ) {