34use Wikimedia\Assert\Assert;
143 $ns =
$title->getNamespace();
146 throw new MWException(
"NS_MEDIA is a virtual namespace; use NS_FILE." );
147 } elseif ( $ns < 0 ) {
148 throw new MWException(
"Invalid or virtual namespace $ns given." );
152 if ( !Hooks::run(
'WikiPageFactory', [
$title, &$page ] ) ) {
180 public static function newFromID( $id, $from =
'fromdb' ) {
186 $from = self::convertSelectType( $from );
188 $pageQuery = self::getQueryInfo();
189 $row = $db->selectRow(
190 $pageQuery[
'tables'], $pageQuery[
'fields'], [
'page_id' => $id ], __METHOD__,
191 [], $pageQuery[
'joins']
196 return self::newFromRow( $row, $from );
210 public static function newFromRow( $row, $from =
'fromdb' ) {
211 $page = self::factory( Title::newFromRow( $row ) );
212 $page->loadFromRow( $row, $from );
225 return self::READ_NORMAL;
227 return self::READ_LATEST;
229 return self::READ_LOCKING;
240 return MediaWikiServices::getInstance()->getRevisionStore();
247 return MediaWikiServices::getInstance()->getRevisionRenderer();
254 return MediaWikiServices::getInstance()->getSlotRoleRegistry();
261 return MediaWikiServices::getInstance()->getParserCache();
268 return MediaWikiServices::getInstance()->getDBLoadBalancer();
307 $this->mDataLoaded =
false;
308 $this->mDataLoadedFrom = self::READ_NONE;
319 $this->mRedirectTarget =
null;
320 $this->mLastRevision =
null;
321 $this->mTouched =
'19700101000000';
322 $this->mLinksUpdated =
'19700101000000';
323 $this->mTimestamp =
'';
324 $this->mIsRedirect =
false;
325 $this->mLatest =
false;
338 $this->mPreparedEdit =
false;
362 'page_links_updated',
368 $fields[] =
'page_content_model';
372 $fields[] =
'page_lang';
391 'tables' => [
'page' ],
401 'page_links_updated',
409 $ret[
'fields'][] =
'page_content_model';
413 $ret[
'fields'][] =
'page_lang';
427 $pageQuery = self::getQueryInfo();
432 Hooks::run(
'ArticlePageDataBefore', [
433 &$wikiPage, &$pageQuery[
'fields'], &$pageQuery[
'tables'], &$pageQuery[
'joins']
436 $row =
$dbr->selectRow(
437 $pageQuery[
'tables'],
438 $pageQuery[
'fields'],
445 Hooks::run(
'ArticlePageDataAfter', [ &$wikiPage, &$row ] );
461 'page_namespace' =>
$title->getNamespace(),
462 'page_title' =>
$title->getDBkey() ], $options );
474 return $this->
pageData(
$dbr, [
'page_id' => $id ], $options );
490 $from = self::convertSelectType( $from );
491 if ( is_int( $from ) && $from <= $this->mDataLoadedFrom ) {
496 if ( is_int( $from ) ) {
497 list( $index, $opts ) = DBAccessObjectUtils::getDBOptions( $from );
499 $db = $loadBalancer->getConnection( $index );
504 && $loadBalancer->getServerCount() > 1
505 && $loadBalancer->hasOrMadeRecentMasterChanges()
507 $from = self::READ_LATEST;
508 list( $index, $opts ) = DBAccessObjectUtils::getDBOptions( $from );
509 $db = $loadBalancer->getConnection( $index );
515 $from = self::READ_NORMAL;
535 $from = self::convertSelectType( $from );
537 if ( !is_int( $from ) ) {
539 $from = self::READ_NORMAL;
542 if ( is_int( $from ) && $from <= $this->mDataLoadedFrom ) {
561 $lc = MediaWikiServices::getInstance()->getLinkCache();
562 $lc->clearLink( $this->mTitle );
565 $lc->addGoodLinkObjFromRow( $this->mTitle, $data );
567 $this->mTitle->loadFromRow( $data );
570 $this->mTitle->loadRestrictions( $data->page_restrictions );
572 $this->mId = intval( $data->page_id );
573 $this->mTouched =
wfTimestamp( TS_MW, $data->page_touched );
575 $this->mIsRedirect = intval( $data->page_is_redirect );
576 $this->mLatest = intval( $data->page_latest );
579 if ( $this->mLastRevision && $this->mLastRevision->getId() != $this->mLatest ) {
580 $this->mLastRevision =
null;
581 $this->mTimestamp =
'';
584 $lc->addBadLinkObj( $this->mTitle );
586 $this->mTitle->loadFromRow(
false );
593 $this->mDataLoaded =
true;
594 $this->mDataLoadedFrom = self::convertSelectType( $from );
601 if ( !$this->mDataLoaded ) {
611 if ( !$this->mDataLoaded ) {
614 return $this->mId > 0;
626 return $this->mTitle->isKnown();
635 if ( !$this->mDataLoaded ) {
639 return (
bool)$this->mIsRedirect;
654 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
656 return $cache->getWithSetCallback(
657 $cache->makeKey(
'page-content-model', $this->getLatest() ),
663 return $rev->getContentModel();
665 $title = $this->mTitle->getPrefixedDBkey();
666 wfWarn(
"Page $title exists but has no (visible) revisions!" );
667 return $this->mTitle->getContentModel();
674 return $this->mTitle->getContentModel();
682 if ( !$this->mDataLoaded ) {
685 return ( $this->mId && !$this->mIsRedirect );
693 if ( !$this->mDataLoaded ) {
696 return $this->mTouched;
704 if ( !$this->mDataLoaded ) {
707 return $this->mLinksUpdated;
715 if ( !$this->mDataLoaded ) {
718 return (
int)$this->mLatest;
727 $rev = $this->mTitle->getFirstRevision();
729 $rev = $this->mTitle->getFirstRevision( Title::READ_LATEST );
739 if ( $this->mLastRevision !==
null ) {
748 if ( $this->mDataLoadedFrom == self::READ_LOCKING ) {
757 $revision = Revision::newFromPageId( $this->
getId(), $latest, $flags );
758 } elseif ( $this->mDataLoadedFrom == self::READ_LATEST ) {
762 $flags = Revision::READ_LATEST;
763 $revision = Revision::newFromPageId( $this->
getId(), $latest, $flags );
766 $revision = Revision::newKnownCurrent(
$dbr, $this->
getTitle(), $latest );
779 $this->mLastRevision = $revision;
789 if ( $this->mLastRevision ) {
790 return $this->mLastRevision;
801 if ( $this->mLastRevision ) {
802 return $this->mLastRevision->getRevisionRecord();
820 public function getContent( $audience = RevisionRecord::FOR_PUBLIC,
User $user =
null ) {
822 if ( $this->mLastRevision ) {
823 return $this->mLastRevision->getContent( $audience, $user );
833 if ( !$this->mTimestamp ) {
858 public function getUser( $audience = RevisionRecord::FOR_PUBLIC,
User $user =
null ) {
860 if ( $this->mLastRevision ) {
861 return $this->mLastRevision->getUser( $audience, $user );
877 public function getCreator( $audience = RevisionRecord::FOR_PUBLIC,
User $user =
null ) {
880 $userName = $revision->getUserText( $audience, $user );
881 return User::newFromName( $userName,
false );
896 public function getUserText( $audience = RevisionRecord::FOR_PUBLIC,
User $user =
null ) {
898 if ( $this->mLastRevision ) {
899 return $this->mLastRevision->getUserText( $audience, $user );
915 public function getComment( $audience = RevisionRecord::FOR_PUBLIC,
User $user =
null ) {
917 if ( $this->mLastRevision ) {
918 return $this->mLastRevision->getComment( $audience, $user );
931 if ( $this->mLastRevision ) {
932 return $this->mLastRevision->isMinor();
951 if ( !$this->mTitle->isContentPage() ) {
976 $hasLinks = (bool)count( $editInfo->output->getLinks() );
981 [
'pl_from' => $this->
getId() ], __METHOD__ );
989 return $content->isCountable( $hasLinks );
1000 if ( !$this->mTitle->isRedirect() ) {
1004 if ( $this->mRedirectTarget !==
null ) {
1005 return $this->mRedirectTarget;
1010 $row =
$dbr->selectRow(
'redirect',
1011 [
'rd_namespace',
'rd_title',
'rd_fragment',
'rd_interwiki' ],
1012 [
'rd_from' => $this->
getId() ],
1017 if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) {
1021 if ( $row->rd_namespace ==
NS_MEDIA ) {
1024 $namespace = $row->rd_namespace;
1026 $this->mRedirectTarget = Title::makeTitle(
1027 $namespace, $row->rd_title,
1028 $row->rd_fragment, $row->rd_interwiki
1030 return $this->mRedirectTarget;
1035 return $this->mRedirectTarget;
1055 DeferredUpdates::addCallableUpdate(
1056 function () use ( $retval, $latest ) {
1059 DeferredUpdates::POSTSEND,
1074 $dbw->startAtomic( __METHOD__ );
1077 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1078 $truncatedFragment = $contLang->truncateForDatabase( $rt->
getFragment(), 255 );
1082 'rd_from' => $this->
getId(),
1085 'rd_fragment' => $truncatedFragment,
1092 'rd_fragment' => $truncatedFragment,
1102 $dbw->endAtomic( __METHOD__ );
1128 if ( $rt->isExternal() ) {
1129 if ( $rt->isLocal() ) {
1133 $source = $this->mTitle->getFullURL(
'redirect=no' );
1134 return $rt->getFullURL( [
'rdfrom' =>
$source ] );
1142 if ( $rt->isSpecialPage() ) {
1146 if ( $rt->isValidRedirectTarget() ) {
1147 return $rt->getFullURL();
1166 $actorMigration = ActorMigration::newMigration();
1167 $actorQuery = $actorMigration->getJoin(
'rev_user' );
1169 $tables = array_merge( [
'revision' ], $actorQuery[
'tables'], [
'user' ] );
1172 'user_id' => $actorQuery[
'fields'][
'rev_user'],
1173 'user_name' => $actorQuery[
'fields'][
'rev_user_text'],
1174 'actor_id' => $actorQuery[
'fields'][
'rev_actor'],
1175 'user_real_name' =>
'MIN(user_real_name)',
1176 'timestamp' =>
'MAX(rev_timestamp)',
1179 $conds = [
'rev_page' => $this->
getId() ];
1184 ? User::newFromId( $this->
getUser() )
1185 : User::newFromName( $this->
getUserText(),
false );
1186 $conds[] =
'NOT(' . $actorMigration->getWhere(
$dbr,
'rev_user', $user )[
'conds'] .
')';
1189 $conds[] =
"{$dbr->bitAnd( 'rev_deleted', RevisionRecord::DELETED_USER )} = 0";
1192 'user' => [
'LEFT JOIN', $actorQuery[
'fields'][
'rev_user'] .
' = user_id' ],
1193 ] + $actorQuery[
'joins'];
1196 'GROUP BY' => [ $fields[
'user_id'], $fields[
'user_name'] ],
1197 'ORDER BY' =>
'timestamp DESC',
1200 $res =
$dbr->select( $tables, $fields, $conds, __METHOD__, $options, $jconds );
1214 && ( $oldId ===
null || $oldId === 0 || $oldId === $this->
getLatest() )
1234 ParserOptions $parserOptions, $oldid =
null, $forceParse =
false
1239 if ( $useParserCache && !$parserOptions->
isSafeToCache() ) {
1240 throw new InvalidArgumentException(
1241 'The supplied ParserOptions are not safe to cache. Fix the options or set $forceParse = true.'
1246 ': using parser cache: ' . ( $useParserCache ?
'yes' :
'no' ) .
"\n" );
1251 if ( $useParserCache ) {
1253 ->get( $this, $parserOptions );
1254 if ( $parserOutput !==
false ) {
1255 return $parserOutput;
1259 if ( $oldid ===
null || $oldid === 0 ) {
1266 return $pool->getParserOutput();
1281 DeferredUpdates::addCallableUpdate(
1282 function () use ( $user, $oldid ) {
1283 Hooks::run(
'PageViewUpdates', [ $this, $user ] );
1287 DeferredUpdates::PRESEND
1301 if ( !Hooks::run(
'ArticlePurge', [ &$wikiPage ] ) ) {
1305 $this->mTitle->invalidateCache();
1310 DeferredUpdates::addUpdate(
1312 DeferredUpdates::PRESEND
1316 $messageCache = MessageCache::singleton();
1317 $messageCache->updateMessageOverride( $this->mTitle, $this->
getContent() );
1340 $pageIdForInsert = $pageId ? [
'page_id' => $pageId ] : [];
1344 'page_namespace' => $this->mTitle->getNamespace(),
1345 'page_title' => $this->mTitle->getDBkey(),
1346 'page_restrictions' =>
'',
1347 'page_is_redirect' => 0,
1350 'page_touched' => $dbw->timestamp(),
1353 ] + $pageIdForInsert,
1358 if ( $dbw->affectedRows() > 0 ) {
1359 $newid = $pageId ? (int)$pageId : $dbw->insertId();
1360 $this->mId = $newid;
1361 $this->mTitle->resetArticleID( $newid );
1385 $lastRevIsRedirect =
null
1394 if ( (
int)$revision->getId() === 0 ) {
1395 throw new InvalidArgumentException(
1396 __METHOD__ .
': Revision has ID ' . var_export( $revision->getId(), 1 )
1400 $content = $revision->getContent();
1404 $conditions = [
'page_id' => $this->
getId() ];
1406 if ( !is_null( $lastRevision ) ) {
1408 $conditions[
'page_latest'] = $lastRevision;
1411 $revId = $revision->getId();
1412 Assert::parameter( $revId > 0,
'$revision->getId()',
'must be > 0' );
1415 'page_latest' => $revId,
1416 'page_touched' => $dbw->timestamp( $revision->getTimestamp() ),
1417 'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
1418 'page_is_redirect' => $rt !==
null ? 1 : 0,
1423 $row[
'page_content_model'] = $revision->getContentModel();
1426 $dbw->update(
'page',
1431 $result = $dbw->affectedRows() > 0;
1435 $this->mLatest = $revision->getId();
1436 $this->mIsRedirect = (bool)$rt;
1438 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1439 $linkCache->addGoodLinkObj(
1445 $revision->getContentModel()
1467 $isRedirect = !is_null( $redirectTitle );
1469 if ( !$isRedirect && $lastRevIsRedirect ===
false ) {
1473 if ( $isRedirect ) {
1477 $where = [
'rd_from' => $this->
getId() ];
1478 $dbw->delete(
'redirect', $where, __METHOD__ );
1483 RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->
getTitle() );
1500 $row = $dbw->selectRow(
1501 [
'revision',
'page' ],
1502 [
'rev_id',
'rev_timestamp',
'page_is_redirect' ],
1504 'page_id' => $this->
getId(),
1505 'page_latest=rev_id' ],
1509 if (
wfTimestamp( TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) {
1512 $prev = $row->rev_id;
1513 $lastRevIsRedirect = (bool)$row->page_is_redirect;
1517 $lastRevIsRedirect =
null;
1520 $ret = $this->
updateRevisionOn( $dbw, $revision, $prev, $lastRevIsRedirect );
1540 $changedRoles = $aSlots->getRolesWithDifferentContent( $bSlots );
1542 return ( $changedRoles !== [ SlotRecord::MAIN ] && $changedRoles !== [] );
1559 if ( self::hasDifferencesOutsideMainSlot( $undo, $undoafter ) ) {
1565 return $handler->getUndoContent( $this->
getRevision(), $undo, $undoafter );
1597 $sectionId,
Content $sectionContent, $sectionTitle =
'', $edittime =
null
1600 if ( $edittime && $sectionId !==
'new' ) {
1603 $rev = Revision::loadFromTimestamp(
$dbr, $this->mTitle, $edittime );
1608 && $lb->getServerCount() > 1
1609 && $lb->hasOrMadeRecentMasterChanges()
1611 $dbw = $lb->getConnectionRef(
DB_MASTER );
1612 $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
1615 $baseRevId = $rev->getId();
1619 return $this->
replaceSectionAtRev( $sectionId, $sectionContent, $sectionTitle, $baseRevId );
1636 $sectionTitle =
'', $baseRevId =
null
1638 if ( strval( $sectionId ) ===
'' ) {
1640 $newContent = $sectionContent;
1643 throw new MWException(
"sections not supported for content model " .
1648 if ( is_null( $baseRevId ) || $sectionId ===
'new' ) {
1651 $rev = Revision::newFromId( $baseRevId );
1653 wfDebug( __METHOD__ .
" asked for bogus section (page: " .
1654 $this->
getId() .
"; section: $sectionId)\n" );
1658 $oldContent = $rev->getContent();
1661 if ( !$oldContent ) {
1662 wfDebug( __METHOD__ .
": no page text\n" );
1666 $newContent = $oldContent->replaceSection( $sectionId, $sectionContent, $sectionTitle );
1705 JobQueueGroup::singleton(),
1706 MessageCache::singleton(),
1707 MediaWikiServices::getInstance()->getContentLanguage(),
1708 MediaWikiServices::getInstance()->getDBLoadBalancerFactory()
1746 User $forUser =
null,
1751 if ( !$forRevision && !$forUpdate ) {
1754 $this->derivedDataUpdater =
null;
1757 if ( $this->derivedDataUpdater && !$this->derivedDataUpdater->isContentPrepared() ) {
1761 $this->derivedDataUpdater =
null;
1768 if ( $this->derivedDataUpdater
1769 && !$this->derivedDataUpdater->isReusableFor(
1773 $forEdit ? $this->getLatest() : null
1776 $this->derivedDataUpdater =
null;
1779 if ( !$this->derivedDataUpdater ) {
1783 return $this->derivedDataUpdater;
1817 return $pageUpdater;
1884 User $user =
null, $serialFormat =
null, $tags = [], $undidRevId = 0
1889 $summary = CommentStoreComment::newUnsavedComment( trim( $summary ) );
1899 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
1900 if ( ( $flags &
EDIT_MINOR ) && !$permissionManager->userHasRight( $user,
'minoredit' ) ) {
1901 $flags = ( $flags & ~EDIT_MINOR );
1905 $slotsUpdate->modifyContent( SlotRecord::MAIN,
$content );
1911 $updater->setContent( SlotRecord::MAIN,
$content );
1912 $updater->setOriginalRevisionId( $originalRevId );
1913 $updater->setUndidRevisionId( $undidRevId );
1921 if ( $needsPatrol && $permissionManager->userCan(
1922 'autopatrol', $user, $this->getTitle()
1924 $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
1927 $updater->addTags( $tags );
1929 $revRec = $updater->saveRevision(
1941 $this->mLatest = $revRec->getId();
1944 return $updater->getStatus();
1962 $options = ParserOptions::newCanonical(
$context );
1964 if ( $this->
getTitle()->isConversionTable() ) {
1967 $options->disableContentConversion();
1995 $serialFormat =
null,
2004 if ( $revision !==
null ) {
2005 if ( $revision instanceof
Revision ) {
2006 $revision = $revision->getRevisionRecord();
2008 throw new InvalidArgumentException(
2009 __METHOD__ .
': invalid $revision argument type ' . gettype( $revision ) );
2013 $slots = RevisionSlotsUpdate::newFromContent( [ SlotRecord::MAIN =>
$content ] );
2016 if ( !$updater->isUpdatePrepared() ) {
2017 $updater->prepareContent( $user, $slots, $useCache );
2020 $updater->prepareUpdate(
2023 'causeAction' =>
'prepare-edit',
2024 'causeAgent' => $user->getName(),
2030 return $updater->getPreparedEdit();
2062 'causeAction' =>
'edit-page',
2063 'causeAgent' => $user->
getName(),
2070 $updater->prepareUpdate( $revision, $options );
2072 $updater->doUpdates();
2090 if ( !$revision || !$revision->getId() ) {
2091 LoggerFactory::getInstance(
'wikipage' )->info(
2092 __METHOD__ .
'called with ' . ( $revision ?
'unsaved' :
'no' ) .
' revision'
2096 $user = User::newFromIdentity( $revision->getUser( RevisionRecord::RAW ) );
2099 $updater->prepareUpdate( $revision, $options );
2100 $updater->doParserCacheUpdate();
2133 $options[
'recursive'] = $options[
'recursive'] ??
true;
2135 if ( !$revision || !$revision->getId() ) {
2136 LoggerFactory::getInstance(
'wikipage' )->info(
2137 __METHOD__ .
'called with ' . ( $revision ?
'unsaved' :
'no' ) .
' revision'
2141 $user = User::newFromIdentity( $revision->getUser( RevisionRecord::RAW ) );
2144 $updater->prepareUpdate( $revision, $options );
2145 $updater->doSecondaryDataUpdates( $options );
2163 &$cascade, $reason,
User $user, $tags =
null
2172 $this->mTitle->loadRestrictions(
null, Title::READ_LATEST );
2173 $restrictionTypes = $this->mTitle->getRestrictionTypes();
2174 $id = $this->
getId();
2181 Title::purgeExpiredRestrictions();
2185 $isProtected =
false;
2191 foreach ( $restrictionTypes as $action ) {
2192 if ( !isset( $expiry[$action] ) || $expiry[$action] === $dbw->getInfinity() ) {
2193 $expiry[$action] =
'infinity';
2195 if ( !isset( $limit[$action] ) ) {
2196 $limit[$action] =
'';
2197 } elseif ( $limit[$action] !=
'' ) {
2202 $current = implode(
'', $this->mTitle->getRestrictions( $action ) );
2203 if ( $current !=
'' ) {
2204 $isProtected =
true;
2207 if ( $limit[$action] != $current ) {
2209 } elseif ( $limit[$action] !=
'' ) {
2213 if ( $this->mTitle->getRestrictionExpiry( $action ) != $expiry[$action] ) {
2219 if ( !$changed && $protect && $this->mTitle->areRestrictionsCascading() != $cascade ) {
2225 return Status::newGood();
2229 $revCommentMsg =
'unprotectedarticle-comment';
2230 $logAction =
'unprotect';
2231 } elseif ( $isProtected ) {
2232 $revCommentMsg =
'modifiedarticleprotection-comment';
2233 $logAction =
'modify';
2235 $revCommentMsg =
'protectedarticle-comment';
2236 $logAction =
'protect';
2239 $logRelationsValues = [];
2240 $logRelationsField =
null;
2241 $logParamsDetails = [];
2244 $nullRevision =
null;
2250 if ( !Hooks::run(
'ArticleProtect', [ &$wikiPage, &$user, $limit, $reason ] ) ) {
2251 return Status::newGood();
2255 $editrestriction = isset( $limit[
'edit'] )
2256 ? [ $limit[
'edit'] ]
2257 : $this->mTitle->getRestrictions(
'edit' );
2258 foreach ( array_keys( $editrestriction,
'sysop' ) as $key ) {
2259 $editrestriction[$key] =
'editprotected';
2261 foreach ( array_keys( $editrestriction,
'autoconfirmed' ) as $key ) {
2262 $editrestriction[$key] =
'editsemiprotected';
2266 foreach ( array_keys( $cascadingRestrictionLevels,
'sysop' ) as $key ) {
2267 $cascadingRestrictionLevels[$key] =
'editprotected';
2269 foreach ( array_keys( $cascadingRestrictionLevels,
'autoconfirmed' ) as $key ) {
2270 $cascadingRestrictionLevels[$key] =
'editsemiprotected';
2274 if ( !array_intersect( $editrestriction, $cascadingRestrictionLevels ) ) {
2289 if ( $nullRevision ===
null ) {
2290 return Status::newFatal(
'no-null-revision', $this->mTitle->getPrefixedText() );
2293 $logRelationsField =
'pr_id';
2296 foreach ( $limit as $action => $restrictions ) {
2298 'page_restrictions',
2301 'pr_type' => $action
2305 if ( $restrictions !=
'' ) {
2306 $cascadeValue = ( $cascade && $action ==
'edit' ) ? 1 : 0;
2308 'page_restrictions',
2311 'pr_type' => $action,
2312 'pr_level' => $restrictions,
2313 'pr_cascade' => $cascadeValue,
2314 'pr_expiry' => $dbw->encodeExpiry( $expiry[$action] )
2318 $logRelationsValues[] = $dbw->insertId();
2319 $logParamsDetails[] = [
2321 'level' => $restrictions,
2322 'expiry' => $expiry[$action],
2323 'cascade' => (bool)$cascadeValue,
2331 [
'page_restrictions' =>
'' ],
2332 [
'page_id' => $id ],
2339 Hooks::run(
'NewRevisionFromEditComplete',
2340 [ $this, $nullRevision, $latest, $user ] );
2341 Hooks::run(
'ArticleProtectComplete', [ &$wikiPage, &$user, $limit, $reason ] );
2346 if ( $limit[
'create'] !=
'' ) {
2347 $commentFields = CommentStore::getStore()->insert( $dbw,
'pt_reason', $reason );
2348 $dbw->replace(
'protected_titles',
2349 [ [
'pt_namespace',
'pt_title' ] ],
2351 'pt_namespace' => $this->mTitle->getNamespace(),
2352 'pt_title' => $this->mTitle->getDBkey(),
2353 'pt_create_perm' => $limit[
'create'],
2354 'pt_timestamp' => $dbw->timestamp(),
2355 'pt_expiry' => $dbw->encodeExpiry( $expiry[
'create'] ),
2356 'pt_user' => $user->
getId(),
2357 ] + $commentFields, __METHOD__
2359 $logParamsDetails[] = [
2361 'level' => $limit[
'create'],
2362 'expiry' => $expiry[
'create'],
2365 $dbw->delete(
'protected_titles',
2367 'pt_namespace' => $this->mTitle->getNamespace(),
2368 'pt_title' => $this->mTitle->getDBkey()
2374 $this->mTitle->flushRestrictions();
2377 if ( $logAction ==
'unprotect' ) {
2382 '4::description' => $protectDescriptionLog,
2383 '5:bool:cascade' => $cascade,
2384 'details' => $logParamsDetails,
2390 $logEntry->setTarget( $this->mTitle );
2391 $logEntry->setComment( $reason );
2392 $logEntry->setPerformer( $user );
2393 $logEntry->setParameters( $params );
2394 if ( !is_null( $nullRevision ) ) {
2395 $logEntry->setAssociatedRevId( $nullRevision->getId() );
2397 $logEntry->addTags( $tags );
2398 if ( $logRelationsField !==
null && count( $logRelationsValues ) ) {
2399 $logEntry->setRelations( [ $logRelationsField => $logRelationsValues ] );
2401 $logId = $logEntry->insert();
2402 $logEntry->publish( $logId );
2404 return Status::newGood( $logId );
2419 array $expiry, $cascade, $reason, $user =
null
2426 $this->mTitle->getPrefixedText(),
2427 $user ? $user->getName() :
''
2428 )->inContentLanguage()->text();
2430 $editComment .=
wfMessage(
'colon-separator' )->inContentLanguage()->text() . $reason;
2433 if ( $protectDescription ) {
2434 $editComment .=
wfMessage(
'word-separator' )->inContentLanguage()->text();
2435 $editComment .=
wfMessage(
'parentheses' )->params( $protectDescription )
2436 ->inContentLanguage()->text();
2439 $editComment .=
wfMessage(
'word-separator' )->inContentLanguage()->text();
2440 $editComment .=
wfMessage(
'brackets' )->params(
2441 wfMessage(
'protect-summary-cascade' )->inContentLanguage()->text()
2442 )->inContentLanguage()->text();
2445 $nullRev = Revision::newNullRevision( $dbw, $this->
getId(), $editComment,
true, $user );
2447 $nullRev->insertOn( $dbw );
2450 $oldLatest = $nullRev->getParentId();
2462 if ( $expiry !=
'infinity' ) {
2463 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
2466 $contLang->timeanddate( $expiry,
false,
false ),
2467 $contLang->date( $expiry,
false,
false ),
2468 $contLang->time( $expiry,
false,
false )
2469 )->inContentLanguage()->text();
2471 return wfMessage(
'protect-expiry-indefinite' )
2472 ->inContentLanguage()->text();
2484 $protectDescription =
'';
2486 foreach ( array_filter( $limit ) as $action => $restrictions ) {
2487 # $action is one of $wgRestrictionTypes = [ 'create', 'edit', 'move', 'upload' ].
2488 # All possible message keys are listed here for easier grepping:
2489 # * restriction-create
2490 # * restriction-edit
2491 # * restriction-move
2492 # * restriction-upload
2493 $actionText =
wfMessage(
'restriction-' . $action )->inContentLanguage()->text();
2494 # $restrictions is one of $wgRestrictionLevels = [ '', 'autoconfirmed', 'sysop' ],
2495 # with '' filtered out. All possible message keys are listed below:
2496 # * protect-level-autoconfirmed
2497 # * protect-level-sysop
2498 $restrictionsText =
wfMessage(
'protect-level-' . $restrictions )
2499 ->inContentLanguage()->text();
2503 if ( $protectDescription !==
'' ) {
2504 $protectDescription .=
wfMessage(
'word-separator' )->inContentLanguage()->text();
2506 $protectDescription .=
wfMessage(
'protect-summary-desc' )
2507 ->params( $actionText, $restrictionsText, $expiryText )
2508 ->inContentLanguage()->text();
2511 return $protectDescription;
2526 $protectDescriptionLog =
'';
2528 $dirMark = MediaWikiServices::getInstance()->getContentLanguage()->getDirMark();
2529 foreach ( array_filter( $limit ) as $action => $restrictions ) {
2531 $protectDescriptionLog .=
2533 "[$action=$restrictions] ($expiryText)";
2536 return trim( $protectDescriptionLog );
2549 if ( !is_array( $limit ) ) {
2550 throw new MWException( __METHOD__ .
' given non-array restriction set' );
2556 foreach ( array_filter( $limit ) as $action => $restrictions ) {
2557 $bits[] =
"$action=$restrictions";
2560 return implode(
':', $bits );
2580 $revCount += $safetyMargin;
2605 $reason, $suppress =
false, $u1 =
null, $u2 =
null, &$error =
'',
User $user =
null,
2609 [],
'delete', $immediate );
2612 return $status->isOK();
2638 $reason, $suppress =
false, $u1 =
null, $u2 =
null, &$error =
'',
User $deleter =
null,
2639 $tags = [], $logsubtype =
'delete', $immediate =
false
2645 $status = Status::newGood();
2653 if ( !Hooks::run(
'ArticleDelete',
2654 [ &$wikiPage, &$deleter, &$reason, &$error, &$status, $suppress ]
2656 if ( $status->isOK() ) {
2658 $status->fatal(
'delete-hook-aborted' );
2664 $logsubtype, $immediate );
2676 $reason, $suppress,
User $deleter, $tags,
2677 $logsubtype, $immediate =
false, $webRequestId =
null
2681 $status = Status::newGood();
2684 $dbw->startAtomic( __METHOD__ );
2687 $id = $this->
getId();
2693 if ( $id == 0 || $this->
getLatest() != $lockedLatest ) {
2694 $dbw->endAtomic( __METHOD__ );
2696 $status->error(
'cannotdelete',
2710 }
catch ( Exception $ex ) {
2711 wfLogWarning( __METHOD__ .
': failed to load content during deletion! '
2712 . $ex->getMessage() );
2719 $explictTrxLogged =
false;
2722 if ( $done || !$immediate ) {
2725 $dbw->endAtomic( __METHOD__ );
2726 if ( $dbw->explicitTrxActive() ) {
2728 if ( !$explictTrxLogged ) {
2729 $explictTrxLogged =
true;
2730 LoggerFactory::getInstance(
'wfDebug' )->debug(
2731 'explicit transaction active in ' . __METHOD__ .
' while deleting {title}', [
2732 'title' => $this->
getTitle()->getText(),
2737 if ( $dbw->trxLevel() ) {
2740 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
2741 $lbFactory->waitForReplication();
2742 $dbw->startAtomic( __METHOD__ );
2747 $dbw->endAtomic( __METHOD__ );
2750 'namespace' => $this->
getTitle()->getNamespace(),
2751 'title' => $this->
getTitle()->getDBkey(),
2752 'wikiPageId' => $id,
2753 'requestId' => $webRequestId ?? WebRequest::getRequestId(),
2754 'reason' => $reason,
2755 'suppress' => $suppress,
2756 'userId' => $deleter->
getId(),
2757 'tags' => json_encode( $tags ),
2758 'logsubtype' => $logsubtype,
2762 JobQueueGroup::singleton()->push(
$job );
2764 $status->warning(
'delete-scheduled',
2774 $archivedRevisionCount = (int)$dbw->selectField(
2775 'archive',
'COUNT(*)',
2777 'ar_namespace' => $this->getTitle()->getNamespace(),
2778 'ar_title' => $this->
getTitle()->getDBkey(),
2786 $wikiPageBeforeDelete = clone $this;
2789 $dbw->delete(
'page', [
'page_id' => $id ], __METHOD__ );
2792 $logtype = $suppress ?
'suppress' :
'delete';
2795 $logEntry->setPerformer( $deleter );
2796 $logEntry->setTarget( $logTitle );
2797 $logEntry->setComment( $reason );
2798 $logEntry->addTags( $tags );
2799 $logid = $logEntry->insert();
2801 $dbw->onTransactionPreCommitOrIdle(
2802 function () use ( $logEntry, $logid ) {
2804 $logEntry->publish( $logid );
2809 $dbw->endAtomic( __METHOD__ );
2813 Hooks::run(
'ArticleDeleteComplete', [
2814 &$wikiPageBeforeDelete,
2820 $archivedRevisionCount
2822 $status->value = $logid;
2825 $dbCache = ObjectCache::getInstance(
'db-replicated' );
2826 $key = $dbCache->makeKey(
'page-recent-delete', md5( $logTitle->getPrefixedText() ) );
2827 $dbCache->set( $key, 1, $dbCache::TTL_DAY );
2847 $namespace = $this->
getTitle()->getNamespace();
2848 $dbKey = $this->
getTitle()->getDBkey();
2850 $commentStore = CommentStore::getStore();
2851 $actorMigration = ActorMigration::newMigration();
2858 $bitfield = RevisionRecord::SUPPRESSED_ALL;
2873 $dbw->lockForUpdate(
2876 [
'revision',
'revision_comment_temp',
'revision_actor_temp' ]
2878 [
'rev_page' => $id ],
2891 $revQuery[
'fields'][] =
'rev_content_model';
2892 $revQuery[
'fields'][] =
'rev_content_format';
2898 $res = $dbw->select(
2901 [
'rev_page' => $id ],
2915 foreach (
$res as $row ) {
2921 $comment = $commentStore->getComment(
'rev_comment', $row );
2922 $user = User::newFromAnyId( $row->rev_user, $row->rev_user_text, $row->rev_actor );
2924 'ar_namespace' => $namespace,
2925 'ar_title' => $dbKey,
2926 'ar_timestamp' => $row->rev_timestamp,
2927 'ar_minor_edit' => $row->rev_minor_edit,
2928 'ar_rev_id' => $row->rev_id,
2929 'ar_parent_id' => $row->rev_parent_id,
2938 'ar_len' => $row->rev_len,
2939 'ar_page_id' => $id,
2940 'ar_deleted' => $suppress ? $bitfield : $row->rev_deleted,
2941 'ar_sha1' => $row->rev_sha1,
2942 ] + $commentStore->insert( $dbw,
'ar_comment', $comment )
2943 + $actorMigration->getInsertValues( $dbw,
'ar_user', $user );
2946 $rowInsert[
'ar_text_id'] = $row->rev_text_id;
2949 $rowInsert[
'ar_content_model'] = $row->rev_content_model;
2950 $rowInsert[
'ar_content_format'] = $row->rev_content_format;
2954 $rowsInsert[] = $rowInsert;
2955 $revids[] = $row->rev_id;
2959 if ( (
int)$row->rev_user === 0 && IP::isValid( $row->rev_user_text ) ) {
2960 $ipRevIds[] = $row->rev_id;
2965 if ( count( $revids ) > 0 ) {
2967 $dbw->insert(
'archive', $rowsInsert, __METHOD__ );
2969 $dbw->delete(
'revision', [
'rev_id' => $revids ], __METHOD__ );
2970 $dbw->delete(
'revision_comment_temp', [
'revcomment_rev' => $revids ], __METHOD__ );
2971 $dbw->delete(
'revision_actor_temp', [
'revactor_rev' => $revids ], __METHOD__ );
2974 if ( count( $ipRevIds ) > 0 ) {
2975 $dbw->delete(
'ip_changes', [
'ipc_rev_id' => $ipRevIds ], __METHOD__ );
2993 'page_id' => $this->
getId(),
2996 'page_namespace' => $this->
getTitle()->getNamespace(),
2997 'page_title' => $this->
getTitle()->getDBkey()
3019 if ( $id !== $this->
getId() ) {
3020 throw new InvalidArgumentException(
'Mismatching page ID' );
3025 }
catch ( Exception $ex ) {
3032 DeferredUpdates::addUpdate( SiteStatsUpdate::factory(
3033 [
'edits' => 1,
'articles' => -$countable,
'pages' => -1 ]
3038 $revision ? $revision->getRevisionRecord() :
$content
3040 foreach ( $updates as $update ) {
3041 DeferredUpdates::addUpdate( $update );
3044 $causeAgent = $user ? $user->getName() :
'unknown';
3046 LinksUpdate::queueRecursiveJobsForTable(
3047 $this->mTitle,
'templatelinks',
'delete-page', $causeAgent );
3049 if ( $this->mTitle->getNamespace() ==
NS_FILE ) {
3050 LinksUpdate::queueRecursiveJobsForTable(
3051 $this->mTitle,
'imagelinks',
'delete-page', $causeAgent );
3055 self::onArticleDelete( $this->mTitle );
3056 ResourceLoaderWikiModule::invalidateModuleCache(
3060 WikiMap::getCurrentWikiDbDomain()->
getId()
3067 DeferredUpdates::addUpdate(
new SearchUpdate( $id, $this->mTitle ) );
3100 $fromP, $summary, $token, $bot, &$resultDetails,
User $user, $tags =
null
3102 $resultDetails =
null;
3105 $editErrors = $this->mTitle->getUserPermissionsErrors(
'edit', $user );
3106 $rollbackErrors = $this->mTitle->getUserPermissionsErrors(
'rollback', $user );
3107 $errors = array_merge( $editErrors,
wfArrayDiff2( $rollbackErrors, $editErrors ) );
3110 $errors[] = [
'sessionfailure' ];
3114 $errors[] = [
'actionthrottledtext' ];
3118 if ( !empty( $errors ) ) {
3122 return $this->
commitRollback( $fromP, $summary, $bot, $resultDetails, $user, $tags );
3146 &$resultDetails,
User $guser, $tags =
null
3153 return [ [
'readonlytext' ] ];
3159 $current = $updater->grabParentRevision();
3161 if ( is_null( $current ) ) {
3163 return [ [
'notanarticle' ] ];
3166 $currentEditorForPublic = $current->getUser( RevisionRecord::FOR_PUBLIC );
3167 $legacyCurrent =
new Revision( $current );
3168 $from = str_replace(
'_',
' ', $fromP );
3172 if ( $from !== ( $currentEditorForPublic ? $currentEditorForPublic->getName() :
'' ) ) {
3173 $resultDetails = [
'current' => $legacyCurrent ];
3174 return [ [
'alreadyrolled',
3175 htmlspecialchars( $this->mTitle->getPrefixedText() ),
3176 htmlspecialchars( $fromP ),
3177 htmlspecialchars( $currentEditorForPublic ? $currentEditorForPublic->getName() :
'' )
3183 $actorWhere = ActorMigration::newMigration()->getWhere(
3186 $current->getUser( RevisionRecord::RAW )
3189 $s = $dbw->selectRow(
3190 [
'revision' ] + $actorWhere[
'tables'],
3191 [
'rev_id',
'rev_timestamp',
'rev_deleted' ],
3193 'rev_page' => $current->getPageId(),
3194 'NOT(' . $actorWhere[
'conds'] .
')',
3198 'USE INDEX' => [
'revision' =>
'page_timestamp' ],
3199 'ORDER BY' =>
'rev_timestamp DESC'
3201 $actorWhere[
'joins']
3203 if (
$s ===
false ) {
3205 return [ [
'cantrollback' ] ];
3206 } elseif (
$s->rev_deleted & RevisionRecord::DELETED_TEXT
3207 ||
$s->rev_deleted & RevisionRecord::DELETED_USER
3210 return [ [
'notvisiblerev' ] ];
3216 RevisionStore::READ_LATEST
3218 if ( empty( $summary ) ) {
3219 if ( !$currentEditorForPublic ) {
3220 $summary =
wfMessage(
'revertpage-nouser' );
3222 $summary =
wfMessage(
'revertpage-anon' );
3227 $legacyTarget =
new Revision( $target );
3228 $targetEditorForPublic = $target->getUser( RevisionRecord::FOR_PUBLIC );
3231 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
3233 $targetEditorForPublic ? $targetEditorForPublic->getName() :
null,
3234 $currentEditorForPublic ? $currentEditorForPublic->getName() :
null,
3236 $contLang->timeanddate(
wfTimestamp( TS_MW,
$s->rev_timestamp ) ),
3238 $contLang->timeanddate( $current->getTimestamp() )
3240 if ( $summary instanceof
Message ) {
3241 $summary = $summary->params(
$args )->inContentLanguage()->text();
3247 $summary = trim( $summary );
3252 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
3253 if ( $permissionManager->userHasRight( $guser,
'minoredit' ) ) {
3257 if ( $bot && ( $permissionManager->userHasAnyRight( $guser,
'markbotedits',
'bot' ) ) ) {
3262 $currentContent = $current->getContent( SlotRecord::MAIN );
3263 $targetContent = $target->getContent( SlotRecord::MAIN );
3264 $changingContentModel = $targetContent->getModel() !== $currentContent->getModel();
3267 $tags[] =
'mw-rollback';
3273 foreach ( $target->getSlots()->getSlots() as $slot ) {
3274 $updater->inheritSlot( $slot );
3279 foreach ( $current->getSlotRoles() as $role ) {
3280 if ( !$target->hasSlot( $role ) ) {
3281 $updater->removeSlot( $role );
3285 $updater->setOriginalRevisionId( $target->getId() );
3287 $updater->addTags( $tags );
3294 'autopatrol', $guser, $this->getTitle()
3296 $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
3300 $rev = $updater->saveRevision(
3301 CommentStoreComment::newUnsavedComment( $summary ),
3308 if ( $bot && $permissionManager->userHasRight( $guser,
'markbotedits' ) ) {
3315 $set[
'rc_patrolled'] = RecentChange::PRC_AUTOPATROLLED;
3318 if ( count( $set ) ) {
3319 $actorWhere = ActorMigration::newMigration()->getWhere(
3322 $current->getUser( RevisionRecord::RAW ),
3325 $dbw->update(
'recentchanges', $set,
3327 'rc_cur_id' => $current->getPageId(),
3328 'rc_timestamp > ' . $dbw->addQuotes(
$s->rev_timestamp ),
3329 $actorWhere[
'conds'],
3335 if ( !$updater->wasSuccessful() ) {
3336 return $updater->getStatus()->getErrorsArray();
3340 if ( $updater->isUnchanged() ) {
3341 $resultDetails = [
'current' => $legacyCurrent ];
3342 return [ [
'alreadyrolled',
3343 htmlspecialchars( $this->mTitle->getPrefixedText() ),
3344 htmlspecialchars( $fromP ),
3345 htmlspecialchars( $currentEditorForPublic ? $currentEditorForPublic->getName() :
'' )
3349 if ( $changingContentModel ) {
3353 $log->setPerformer( $guser );
3354 $log->setTarget( $this->mTitle );
3355 $log->setComment( $summary );
3356 $log->setParameters( [
3357 '4::oldmodel' => $currentContent->getModel(),
3358 '5::newmodel' => $targetContent->getModel(),
3361 $logId = $log->insert( $dbw );
3362 $log->publish( $logId );
3365 $revId = $rev->getId();
3367 Hooks::run(
'ArticleRollbackComplete', [ $this, $guser, $legacyTarget, $legacyCurrent ] );
3370 'summary' => $summary,
3371 'current' => $legacyCurrent,
3372 'target' => $legacyTarget,
3396 $other =
$title->getOtherPage();
3398 $other->purgeSquid();
3402 $title->deleteTitleProtection();
3404 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle(
$title );
3410 [
'causeAction' =>
'page-create' ]
3412 JobQueueGroup::singleton()->lazyPush(
$job );
3419 Category::newFromTitle(
$title )->getID();
3434 $other =
$title->getOtherPage();
3436 $other->purgeSquid();
3441 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle(
$title );
3449 MessageCache::singleton()->updateMessageOverride(
$title,
null );
3457 [
'causeAction' =>
'page-delete' ]
3459 JobQueueGroup::singleton()->lazyPush(
$job );
3464 $user = User::newFromName(
$title->getText(),
false );
3466 $user->setNewtalk(
false );
3471 RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect(
$title );
3474 self::purgeInterwikiCheckKey(
$title );
3488 $slotsChanged =
null
3492 if ( $slotsChanged ===
null || in_array( SlotRecord::MAIN, $slotsChanged ) ) {
3499 [
'causeAction' =>
'page-edit' ]
3506 [
'causeAction' =>
'page-edit' ]
3508 JobQueueGroup::singleton()->lazyPush( $jobs );
3510 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle(
$title );
3518 $revid = $revision ? $revision->getId() :
null;
3519 DeferredUpdates::addCallableUpdate(
function () use (
$title, $revid ) {
3524 self::purgeInterwikiCheckKey(
$title );
3541 DeferredUpdates::addCallableUpdate(
function () use (
$title ) {
3542 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
3549 WikiMap::getCurrentWikiDbDomain()->getId(),
3563 $id = $this->
getId();
3569 $res =
$dbr->select(
'categorylinks',
3570 [
'cl_to AS page_title, ' .
NS_CATEGORY .
' AS page_namespace' ],
3573 [
'cl_from' => $id ],
3587 $id = $this->
getId();
3594 $res =
$dbr->select( [
'categorylinks',
'page_props',
'page' ],
3596 [
'cl_from' => $id,
'pp_page=page_id',
'pp_propname' =>
'hiddencat',
3597 'page_namespace' =>
NS_CATEGORY,
'page_title=cl_to' ],
3600 if (
$res !==
false ) {
3601 foreach (
$res as $row ) {
3602 $result[] = Title::makeTitle(
NS_CATEGORY, $row->cl_to );
3631 $id = $id ?: $this->
getId();
3632 $type = MediaWikiServices::getInstance()->getNamespaceInfo()->
3633 getCategoryLinkType( $this->
getTitle()->getNamespace() );
3635 $addFields = [
'cat_pages = cat_pages + 1' ];
3636 $removeFields = [
'cat_pages = cat_pages - 1' ];
3637 if (
$type !==
'page' ) {
3638 $addFields[] =
"cat_{$type}s = cat_{$type}s + 1";
3639 $removeFields[] =
"cat_{$type}s = cat_{$type}s - 1";
3644 if ( count( $added ) ) {
3645 $existingAdded = $dbw->selectFieldValues(
3648 [
'cat_title' => $added ],
3655 if ( count( $existingAdded ) ) {
3659 [
'cat_title' => $existingAdded ],
3664 $missingAdded = array_diff( $added, $existingAdded );
3665 if ( count( $missingAdded ) ) {
3667 foreach ( $missingAdded as $cat ) {
3669 'cat_title' => $cat,
3671 'cat_subcats' => (
$type ===
'subcat' ) ? 1 : 0,
3672 'cat_files' => (
$type ===
'file' ) ? 1 : 0,
3685 if ( count( $deleted ) ) {
3689 [
'cat_title' => $deleted ],
3694 foreach ( $added as $catName ) {
3695 $cat = Category::newFromName( $catName );
3696 Hooks::run(
'CategoryAfterPageAdded', [ $cat, $this ] );
3699 foreach ( $deleted as $catName ) {
3700 $cat = Category::newFromName( $catName );
3701 Hooks::run(
'CategoryAfterPageRemoved', [ $cat, $this, $id ] );
3703 DeferredUpdates::addCallableUpdate(
function () use ( $cat ) {
3704 $cat->refreshCountsIfEmpty();
3720 if ( !Hooks::run(
'OpportunisticLinksUpdate',
3721 [ $this, $this->mTitle, $parserOutput ]
3726 $config = RequestContext::getMain()->getConfig();
3729 'isOpportunistic' =>
true,
3733 if ( $this->mTitle->areRestrictionsCascading() ) {
3735 JobQueueGroup::singleton()->lazyPush(
3738 } elseif ( !$config->get(
'MiserMode' ) && $parserOutput->
hasDynamicContent() ) {
3748 $cache = ObjectCache::getLocalClusterInstance();
3749 $key =
$cache->makeKey(
'dynamic-linksupdate',
'last', $this->
getId() );
3751 if (
$cache->add( $key, time(), $ttl ) ) {
3752 JobQueueGroup::singleton()->lazyPush(
3771 wfDeprecated( __METHOD__ .
' without a RevisionRecord',
'1.32' );
3775 }
catch ( Exception $ex ) {
3780 wfDebug( __METHOD__ .
' failed to load current revision of page ' . $this->
getId() );
3786 } elseif ( $rev instanceof
Content ) {
3787 wfDeprecated( __METHOD__ .
' with a Content object instead of a RevisionRecord',
'1.32' );
3789 $slotContent = [ SlotRecord::MAIN => $rev ];
3791 $slotContent = array_map(
function (
SlotRecord $slot ) {
3792 return $slot->
getContent( RevisionRecord::RAW );
3793 }, $rev->getSlots()->getSlots() );
3802 foreach ( $slotContent as $role =>
$content ) {
3803 $handler =
$content->getContentHandler();
3805 $updates = $handler->getDeletionUpdates(
3809 $allUpdates = array_merge( $allUpdates, $updates );
3812 $legacyUpdates =
$content->getDeletionUpdates( $this );
3815 $legacyUpdates = array_filter( $legacyUpdates,
function ( $update ) {
3819 $allUpdates = array_merge( $allUpdates, $legacyUpdates );
3822 Hooks::run(
'PageDeletionDataUpdates', [ $this->
getTitle(), $rev, &$allUpdates ] );
3825 Hooks::run(
'WikiPageDeletionUpdates', [ $this,
$content, &$allUpdates ] );
3863 return $this->
getTitle()->getCanonicalURL();
3872 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3874 return $linkCache->getMutableCacheKeys(
$cache, $this->
getTitle() );
bool $wgPageLanguageUseDB
Enable page language feature Allows setting page language in database.
$wgDeleteRevisionsBatchSize
Page deletions with > this number of revisions will use the job queue.
$wgCascadingRestrictionLevels
Restriction levels that can be used with cascading protection.
int $wgMultiContentRevisionSchemaMigrationStage
RevisionStore table schema migration stage (content, slots, content_models & slot_roles tables).
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history,...
$wgUseAutomaticEditSummaries
If user doesn't specify any edit summary when making a an edit, MediaWiki will try to automatically c...
$wgSitename
Name of the site.
$wgPageCreationLog
Maintain a log of page creations at Special:Log/create?
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
$wgArticleCountMethod
Method used to determine if a page in a content namespace should be counted as a valid article.
$wgAjaxEditStash
Have clients send edits to be prepared when filling in edit summaries.
$wgEnableScaryTranscluding
Enable interwiki transcluding.
$wgRCWatchCategoryMembership
Treat category membership changes as a RecentChange.
$wgContentHandlerUseDB
Set to false to disable use of the database fields introduced by the ContentHandler facility.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfRandom()
Get a random decimal value in the domain of [0, 1), in a way not likely to give duplicate values for ...
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
wfIncrStats( $key, $count=1)
Increment a statistics counter.
wfReadOnly()
Check whether the wiki is in read-only mode.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfArrayDiff2( $a, $b)
Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
wfReadOnlyReason()
Check if the site is in read-only mode and return the message if so.
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
wfMsgReplaceArgs( $message, $args)
Replace message parameter keys on the given formatted output.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
static get(Title $title)
Create a new BacklinkCache or reuse any existing one.
getCacheExpiry()
Returns the number of seconds after which this object should expire.
Handles purging the appropriate CDN objects given a list of URLs or Title instances.
static newForBacklinks(Title $title, $table, $params=[])
static clearFileCache(Title $title)
Clear the file caches for a page for all actions.
static invalidateCache(Title $title, $revid=null)
Clear the info cache for a given Title.
Update object handling the cleanup of links tables after a page was deleted.
Class for creating new log entries and inserting them into the database.
A handle for managing updates for derived page data on edit, import, purge, etc.
setRcWatchCategoryMembership( $rcWatchCategoryMembership)
setLogger(LoggerInterface $logger)
setArticleCountMethod( $articleCountMethod)
Controller-like object for creating and updating pages by creating new revisions.
The Message class provides methods which fulfil two basic services:
Set options of the Parser.
getStubThreshold()
Thumb size preferred by the user.
isSafeToCache()
Test whether these options are safe to cache.
hasDynamicContent()
Check whether the cache TTL was lowered due to dynamic content.
static newPrioritized(Title $title, array $params)
static newDynamic(Title $title, array $params)
getContentHandler()
Returns the content handler appropriate for this revision's content model.
Database independant search index updater.
static newFromResult( $res)
Represents a title within MediaWiki.
getNamespace()
Get the namespace index, i.e.
getFragment()
Get the Title fragment (i.e.
getDBkey()
Get the main part with underscores.
getInterwiki()
Get the interwiki prefix.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
getName()
Get the user name, or the IP of an anonymous user.
getId()
Get the user's ID.
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Multi-datacenter aware caching interface.
Special handling for category pages.
Special handling for file pages.
Class representing a MediaWiki article and history.
static newFromID( $id, $from='fromdb')
Constructor from a page id.
doUpdateRestrictions(array $limit, array $expiry, &$cascade, $reason, User $user, $tags=null)
Update the article's restriction field, and leave a log entry.
getContributors()
Get a list of users who have edited this article, not including the user who made the most recent rev...
doPurge()
Perform the actions of a page purging.
followRedirect()
Get the Title object or URL this page redirects to.
insertOn( $dbw, $pageId=null)
Insert a new empty page record for this article.
doDeleteArticleBatched( $reason, $suppress, User $deleter, $tags, $logsubtype, $immediate=false, $webRequestId=null)
Back-end article deletion.
updateCategoryCounts(array $added, array $deleted, $id=0)
Update all the appropriate counts in the category table, given that we've added the categories $added...
static purgeInterwikiCheckKey(Title $title)
#-
wasLoadedFrom( $from)
Checks whether the page data was loaded using the given database access mode (or better).
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
pageDataFromTitle( $dbr, $title, $options=[])
Fetch a page record matching the Title object's namespace and title using a sanitized title string.
checkFlags( $flags)
Check flags and add EDIT_NEW or EDIT_UPDATE to them as needed.
isLocal()
Whether this content displayed on this page comes from the local database.
getRevision()
Get the latest revision.
getUndoContent(Revision $undo, Revision $undoafter)
Get the content that needs to be saved in order to undo all revisions between $undo and $undoafter.
getContent( $audience=RevisionRecord::FOR_PUBLIC, User $user=null)
Get the content of the current revision.
static onArticleEdit(Title $title, Revision $revision=null, $slotsChanged=null)
Purge caches on page update etc.
getLinksTimestamp()
Get the page_links_updated field.
getMinorEdit()
Returns true if last revision was marked as "minor edit".
clearCacheFields()
Clear the object cache fields.
clearPreparedEdit()
Clear the mPreparedEdit cache field, as may be needed by mutable content types.
getLatest()
Get the page_latest field.
doViewUpdates(User $user, $oldid=0)
Do standard deferred updates after page view (existing or missing page)
updateIfNewerOn( $dbw, $revision)
If the given revision is newer than the currently set page_latest, update the page record.
__clone()
Makes sure that the mTitle object is cloned to the newly cloned WikiPage.
loadFromRow( $data, $from)
Load the object from a database row.
archiveRevisions( $dbw, $id, $suppress)
Archives revisions as part of page deletion.
supportsSections()
Returns true if this page's content model supports sections.
getRedirectTarget()
If this page is a redirect, get its target.
DerivedPageDataUpdater null $derivedDataUpdater
setTimestamp( $ts)
Set the page timestamp (use only to avoid DB queries)
protectDescriptionLog(array $limit, array $expiry)
Builds the description to serve as comment for the log entry.
makeParserOptions( $context)
Get parser options suitable for rendering the primary article wikitext.
pageData( $dbr, $conditions, $options=[])
Fetch a page record with the given conditions.
getSourceURL()
Get the source URL for the content on this page, typically the canonical URL, but may be a remote lin...
getOldestRevision()
Get the Revision object of the oldest revision.
isBatchedDelete( $safetyMargin=0)
Determines if deletion of this page would be batched (executed over time by the job queue) or not (co...
replaceSectionAtRev( $sectionId, Content $sectionContent, $sectionTitle='', $baseRevId=null)
setLastEdit(Revision $revision)
Set the latest revision.
updateRevisionOn( $dbw, $revision, $lastRevision=null, $lastRevIsRedirect=null)
Update the page record to point to a newly saved revision.
shouldCheckParserCache(ParserOptions $parserOptions, $oldId)
Should the parser cache be used?
loadLastEdit()
Loads everything except the text This isn't necessary for all uses, so it's only done if needed.
getDerivedDataUpdater(User $forUser=null, RevisionRecord $forRevision=null, RevisionSlotsUpdate $forUpdate=null, $forEdit=false)
Returns a DerivedPageDataUpdater for use with the given target revision or new content.
getContentModel()
Returns the page's content model id (see the CONTENT_MODEL_XXX constants).
pageDataFromId( $dbr, $id, $options=[])
Fetch a page record matching the requested ID.
doEditUpdates(Revision $revision, User $user, array $options=[])
Do standard deferred updates after page edit.
insertRedirectEntry(Title $rt, $oldLatest=null)
Insert or update the redirect table entry for this page to indicate it redirects to $rt.
getCategories()
Returns a list of categories this page is a member of.
doDeleteUpdates( $id, Content $content=null, Revision $revision=null, User $user=null)
Do some database updates after deletion.
string $mTimestamp
Timestamp of the current revision or empty string if not loaded.
getHiddenCategories()
Returns a list of hidden categories this page is a member of.
doEditContent(Content $content, $summary, $flags=0, $originalRevId=false, User $user=null, $serialFormat=null, $tags=[], $undidRevId=0)
Change an existing article or create a new article.
getDeletionUpdates( $rev=null)
Returns a list of updates to be performed when this page is deleted.
static newFromRow( $row, $from='fromdb')
Constructor from a database row.
getAutoDeleteReason(&$hasHistory)
Auto-generates a deletion reason.
lockAndGetLatest()
Lock the page row for this title+id and return page_latest (or 0)
int false $mLatest
False means "not loaded".
static flattenRestrictions( $limit)
Take an array of page restrictions and flatten it to a string suitable for insertion into the page_re...
getParserOutput(ParserOptions $parserOptions, $oldid=null, $forceParse=false)
Get a ParserOutput for the given ParserOptions and revision ID.
getUserText( $audience=RevisionRecord::FOR_PUBLIC, User $user=null)
updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect=null)
Add row to the redirect table if this is a redirect, remove otherwise.
prepareContentForEdit(Content $content, $revision=null, User $user=null, $serialFormat=null, $useCache=true)
Prepare content which is about to be saved.
hasViewableContent()
Check if this page is something we're going to be showing some sort of sensible content for.
triggerOpportunisticLinksUpdate(ParserOutput $parserOutput)
Opportunistically enqueue link update jobs given fresh parser output if useful.
getUser( $audience=RevisionRecord::FOR_PUBLIC, User $user=null)
insertRedirect()
Insert an entry for this page into the redirect table if the content is a redirect.
static hasDifferencesOutsideMainSlot(Revision $a, Revision $b)
Helper method for checking whether two revisions have differences that go beyond the main slot.
getComment( $audience=RevisionRecord::FOR_PUBLIC, User $user=null)
updateParserCache(array $options=[])
Update the parser cache.
getCreator( $audience=RevisionRecord::FOR_PUBLIC, User $user=null)
Get the User object of the user who created the page.
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new page object.
int $mDataLoadedFrom
One of the READ_* constants.
static onArticleDelete(Title $title)
Clears caches when article is deleted.
newPageUpdater(User $user, RevisionSlotsUpdate $forUpdate=null)
Returns a PageUpdater for creating new revisions on this page (or creating the page).
replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle='', $edittime=null)
static selectFields()
Return the list of revision fields that should be selected to create a new page.
insertProtectNullRevision( $revCommentMsg, array $limit, array $expiry, $cascade, $reason, $user=null)
Insert a new null revision for this page.
getTitle()
Get the title object of the article.
isRedirect()
Tests if the article content represents a redirect.
static onArticleCreate(Title $title)
The onArticle*() functions are supposed to be a kind of hooks which should be called whenever any of ...
doDeleteArticleReal( $reason, $suppress=false, $u1=null, $u2=null, &$error='', User $deleter=null, $tags=[], $logsubtype='delete', $immediate=false)
Back-end article deletion Deletes the article with database consistency, writes logs,...
commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser, $tags=null)
Backend implementation of doRollback(), please refer there for parameter and return value documentati...
getRedirectURL( $rt)
Get the Title object or URL to use for a redirect.
doSecondaryDataUpdates(array $options=[])
Do secondary data updates (such as updating link tables).
loadPageData( $from='fromdb')
Load the object from a given source by title.
checkTouched()
Loads page_touched and returns a value indicating if it should be used.
isCountable( $editInfo=false)
Determine whether a page would be suitable for being counted as an article in the site_stats table ba...
doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user, $tags=null)
Roll back the most recent consecutive set of edits to a page from the same user; fails if there are n...
getContentHandler()
Returns the ContentHandler instance to be used to deal with the content of this WikiPage.
getRevisionRecord()
Get the latest revision.
getWikiDisplayName()
The display name for the site this content come from.
static convertSelectType( $type)
Convert 'fromdb', 'fromdbmaster' and 'forupdate' to READ_* constants.
getMutableCacheKeys(WANObjectCache $cache)
protectDescription(array $limit, array $expiry)
Builds the description to serve as comment for the edit.
getTouched()
Get the page_touched field.
__construct(Title $title)
Constructor and clear the article.
doDeleteArticle( $reason, $suppress=false, $u1=null, $u2=null, &$error='', User $user=null, $immediate=false)
Same as doDeleteArticleReal(), but returns a simple boolean.
PreparedEdit false $mPreparedEdit
Map of cache fields (text, parser output, ect) for a proposed/new edit.
const SCHEMA_COMPAT_WRITE_OLD
Base interface for content objects.
Interface for database access objects.
const READ_LOCKING
Constants for object loading bitfield flags (higher => higher QoS)
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
if(count( $args)< 1) $job