34 use 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." );
180 public static function newFromID( $id, $from =
'fromdb' ) {
189 $row = $db->selectRow(
190 $pageQuery[
'tables'], $pageQuery[
'fields'], [
'page_id' => $id ], __METHOD__,
191 [], $pageQuery[
'joins']
210 public static function newFromRow( $row, $from =
'fromdb' ) {
212 $page->loadFromRow( $row, $from );
225 return self::READ_NORMAL;
227 return self::READ_LATEST;
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;
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';
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 );
491 if ( is_int( $from ) && $from <= $this->mDataLoadedFrom ) {
496 if ( is_int( $from ) ) {
499 $db = $loadBalancer->getConnection( $index );
504 && $loadBalancer->getServerCount() > 1
505 && $loadBalancer->hasOrMadeRecentMasterChanges()
507 $from = self::READ_LATEST;
509 $db = $loadBalancer->getConnection( $index );
515 $from = self::READ_NORMAL;
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;
601 if ( !$this->mDataLoaded ) {
611 if ( !$this->mDataLoaded ) {
614 return $this->mId > 0;
626 return $this->mTitle->isKnown();
635 if ( !$this->mDataLoaded ) {
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 ) {
704 if ( !$this->mDataLoaded ) {
715 if ( !$this->mDataLoaded ) {
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 ) {
758 } elseif ( $this->mDataLoadedFrom == self::READ_LATEST ) {
762 $flags = Revision::READ_LATEST;
779 $this->mLastRevision = $revision;
789 if ( $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 );
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 ) {
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;
1027 $namespace, $row->rd_title,
1028 $row->rd_fragment, $row->rd_interwiki
1056 function () use ( $retval, $latest ) {
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();
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() ];
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();
1282 function () use ( $user, $oldid ) {
1283 Hooks::run(
'PageViewUpdates', [ $this, $user ] );
1301 if ( !
Hooks::run(
'ArticlePurge', [ &$wikiPage ] ) ) {
1305 $this->mTitle->invalidateCache();
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__ );
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' ) {
1608 && $lb->getServerCount() > 1
1609 && $lb->hasOrMadeRecentMasterChanges()
1611 $dbw = $lb->getConnectionRef(
DB_MASTER );
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' ) {
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 );
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 ) {
1817 return $pageUpdater;
1884 User $user =
null, $serialFormat =
null, $tags = [], $undidRevId = 0
1899 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
1900 if ( ( $flags &
EDIT_MINOR ) && !$permissionManager->userHasRight( $user,
'minoredit' ) ) {
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()
1927 $updater->addTags( $tags );
1929 $revRec = $updater->saveRevision(
1941 $this->mLatest = $revRec->getId();
1944 return $updater->getStatus();
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'
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'
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();
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 ) {
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 ] ) ) {
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 ],
2340 [ $this, $nullRevision, $latest, $user ] );
2341 Hooks::run(
'ArticleProtectComplete', [ &$wikiPage, &$user, $limit, $reason ] );
2346 if ( $limit[
'create'] !=
'' ) {
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 );
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();
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 );
2638 $reason, $suppress =
false, $u1 =
null, $u2 =
null, &$error =
'',
User $deleter =
null,
2639 $tags = [], $logsubtype =
'delete', $immediate =
false
2654 [ &$wikiPage, &$deleter, &$reason, &$error, &
$status, $suppress ]
2658 $status->fatal(
'delete-hook-aborted' );
2664 $logsubtype, $immediate );
2676 $reason, $suppress,
User $deleter, $tags,
2677 $logsubtype, $immediate =
false, $webRequestId =
null
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,
2754 'reason' => $reason,
2755 'suppress' => $suppress,
2756 'userId' => $deleter->
getId(),
2757 'tags' => json_encode( $tags ),
2758 'logsubtype' => $logsubtype,
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__ );
2814 &$wikiPageBeforeDelete,
2820 $archivedRevisionCount
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();
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 );
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 ) {
3033 [
'edits' => 1,
'articles' => -$countable,
'pages' => -1 ]
3038 $revision ? $revision->getRevisionRecord() :
$content
3040 foreach ( $updates as $update ) {
3044 $causeAgent = $user ? $user->getName() :
'unknown';
3047 $this->mTitle,
'templatelinks',
'delete-page', $causeAgent );
3049 if ( $this->mTitle->getNamespace() ==
NS_FILE ) {
3051 $this->mTitle,
'imagelinks',
'delete-page', $causeAgent );
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() :
'' )
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()
3300 $rev = $updater->saveRevision(
3308 if ( $bot && $permissionManager->userHasRight( $guser,
'markbotedits' ) ) {
3318 if ( count( $set ) ) {
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' ]
3434 $other =
$title->getOtherPage();
3436 $other->purgeSquid();
3441 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle(
$title );
3457 [
'causeAction' =>
'page-delete' ]
3466 $user->setNewtalk(
false );
3488 $slotsChanged =
null
3492 if ( $slotsChanged ===
null || in_array( SlotRecord::MAIN, $slotsChanged ) ) {
3499 [
'causeAction' =>
'page-edit' ]
3506 [
'causeAction' =>
'page-edit' ]
3510 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle(
$title );
3518 $revid = $revision ? $revision->getId() :
null;
3542 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
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 ) {
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 ) {
3696 Hooks::run(
'CategoryAfterPageAdded', [ $cat, $this ] );
3699 foreach ( $deleted as $catName ) {
3701 Hooks::run(
'CategoryAfterPageRemoved', [ $cat, $this, $id ] );
3704 $cat->refreshCountsIfEmpty();
3720 if ( !
Hooks::run(
'OpportunisticLinksUpdate',
3721 [ $this, $this->mTitle, $parserOutput ]
3729 'isOpportunistic' =>
true,
3733 if ( $this->mTitle->areRestrictionsCascading() ) {
3738 } elseif ( !$config->get(
'MiserMode' ) && $parserOutput->
hasDynamicContent() ) {
3749 $key =
$cache->makeKey(
'dynamic-linksupdate',
'last', $this->
getId() );
3751 if (
$cache->add( $key, time(), $ttl ) ) {
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 );
3863 return $this->
getTitle()->getCanonicalURL();
3872 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3874 return $linkCache->getMutableCacheKeys(
$cache, $this->
getTitle() );