34 use Wikimedia\Assert\Assert;
139 $ns =
$title->getNamespace();
142 throw new MWException(
"NS_MEDIA is a virtual namespace; use NS_FILE." );
143 } elseif ( $ns < 0 ) {
144 throw new MWException(
"Invalid or virtual namespace $ns given." );
176 public static function newFromID( $id, $from =
'fromdb' ) {
185 $row = $db->selectRow(
186 $pageQuery[
'tables'], $pageQuery[
'fields'], [
'page_id' => $id ], __METHOD__,
187 [], $pageQuery[
'joins']
206 public static function newFromRow( $row, $from =
'fromdb' ) {
208 $page->loadFromRow( $row, $from );
221 return self::READ_NORMAL;
223 return self::READ_LATEST;
236 return MediaWikiServices::getInstance()->getRevisionStore();
243 return MediaWikiServices::getInstance()->getRevisionRenderer();
250 return MediaWikiServices::getInstance()->getSlotRoleRegistry();
257 return MediaWikiServices::getInstance()->getParserCache();
264 return MediaWikiServices::getInstance()->getDBLoadBalancer();
303 $this->mDataLoaded =
false;
315 $this->mRedirectTarget =
null;
316 $this->mLastRevision =
null;
317 $this->mTouched =
'19700101000000';
318 $this->mLinksUpdated =
'19700101000000';
319 $this->mTimestamp =
'';
320 $this->mIsRedirect =
false;
321 $this->mLatest =
false;
334 $this->mPreparedEdit =
false;
358 'page_links_updated',
364 $fields[] =
'page_content_model';
368 $fields[] =
'page_lang';
387 'tables' => [
'page' ],
397 'page_links_updated',
405 $ret[
'fields'][] =
'page_content_model';
409 $ret[
'fields'][] =
'page_lang';
429 &$wikiPage, &$pageQuery[
'fields'], &$pageQuery[
'tables'], &$pageQuery[
'joins']
432 $row =
$dbr->selectRow(
433 $pageQuery[
'tables'],
434 $pageQuery[
'fields'],
441 Hooks::run(
'ArticlePageDataAfter', [ &$wikiPage, &$row ] );
457 'page_namespace' =>
$title->getNamespace(),
487 if ( is_int( $from ) && $from <= $this->mDataLoadedFrom ) {
492 if ( is_int( $from ) ) {
495 $db = $loadBalancer->getConnection( $index );
500 && $loadBalancer->getServerCount() > 1
501 && $loadBalancer->hasOrMadeRecentMasterChanges()
503 $from = self::READ_LATEST;
505 $db = $loadBalancer->getConnection( $index );
511 $from = self::READ_NORMAL;
533 if ( !is_int( $from ) ) {
535 $from = self::READ_NORMAL;
538 if ( is_int( $from ) && $from <= $this->mDataLoadedFrom ) {
557 $lc = MediaWikiServices::getInstance()->getLinkCache();
558 $lc->clearLink( $this->mTitle );
561 $lc->addGoodLinkObjFromRow( $this->mTitle,
$data );
563 $this->mTitle->loadFromRow(
$data );
566 $this->mTitle->loadRestrictions(
$data->page_restrictions );
568 $this->mId = intval(
$data->page_id );
571 $this->mIsRedirect = intval(
$data->page_is_redirect );
572 $this->mLatest = intval(
$data->page_latest );
575 if ( $this->mLastRevision && $this->mLastRevision->getId() !=
$this->mLatest ) {
576 $this->mLastRevision =
null;
577 $this->mTimestamp =
'';
580 $lc->addBadLinkObj( $this->mTitle );
582 $this->mTitle->loadFromRow(
false );
589 $this->mDataLoaded =
true;
597 if ( !$this->mDataLoaded ) {
607 if ( !$this->mDataLoaded ) {
610 return $this->mId > 0;
622 return $this->mTitle->isKnown();
631 if ( !$this->mDataLoaded ) {
650 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
652 return $cache->getWithSetCallback(
653 $cache->makeKey(
'page-content-model', $this->getLatest() ),
659 return $rev->getContentModel();
661 $title = $this->mTitle->getPrefixedDBkey();
662 wfWarn(
"Page $title exists but has no (visible) revisions!" );
663 return $this->mTitle->getContentModel();
670 return $this->mTitle->getContentModel();
678 if ( !$this->mDataLoaded ) {
681 return ( $this->mId && !$this->mIsRedirect );
689 if ( !$this->mDataLoaded ) {
700 if ( !$this->mDataLoaded ) {
711 if ( !$this->mDataLoaded ) {
723 $rev = $this->mTitle->getFirstRevision();
735 if ( $this->mLastRevision !==
null ) {
744 if ( $this->mDataLoadedFrom == self::READ_LOCKING ) {
754 } elseif ( $this->mDataLoadedFrom == self::READ_LATEST ) {
758 $flags = Revision::READ_LATEST;
775 $this->mLastRevision = $revision;
785 if ( $this->mLastRevision ) {
797 if ( $this->mLastRevision ) {
798 return $this->mLastRevision->getRevisionRecord();
818 if ( $this->mLastRevision ) {
819 return $this->mLastRevision->getContent( $audience,
$user );
829 if ( !$this->mTimestamp ) {
856 if ( $this->mLastRevision ) {
857 return $this->mLastRevision->getUser( $audience,
$user );
876 $userName = $revision->getUserText( $audience,
$user );
894 if ( $this->mLastRevision ) {
895 return $this->mLastRevision->getUserText( $audience,
$user );
913 if ( $this->mLastRevision ) {
914 return $this->mLastRevision->getComment( $audience,
$user );
927 if ( $this->mLastRevision ) {
928 return $this->mLastRevision->isMinor();
947 if ( !$this->mTitle->isContentPage() ) {
972 $hasLinks = (bool)
count( $editInfo->output->getLinks() );
977 [
'pl_from' => $this->
getId() ], __METHOD__ );
985 return $content->isCountable( $hasLinks );
996 if ( !$this->mTitle->isRedirect() ) {
1000 if ( $this->mRedirectTarget !==
null ) {
1006 $row =
$dbr->selectRow(
'redirect',
1007 [
'rd_namespace',
'rd_title',
'rd_fragment',
'rd_interwiki' ],
1008 [
'rd_from' => $this->
getId() ],
1013 if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) {
1017 if ( $row->rd_namespace ==
NS_MEDIA ) {
1020 $namespace = $row->rd_namespace;
1023 $namespace, $row->rd_title,
1024 $row->rd_fragment, $row->rd_interwiki
1052 function ()
use ( $retval, $latest ) {
1069 $dbw->startAtomic( __METHOD__ );
1072 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1073 $truncatedFragment = $contLang->truncateForDatabase( $rt->
getFragment(), 255 );
1077 'rd_from' => $this->
getId(),
1080 'rd_fragment' => $truncatedFragment,
1087 'rd_fragment' => $truncatedFragment,
1094 $dbw->endAtomic( __METHOD__ );
1118 if ( $rt->isExternal() ) {
1119 if ( $rt->isLocal() ) {
1123 $source = $this->mTitle->getFullURL(
'redirect=no' );
1124 return $rt->getFullURL( [
'rdfrom' =>
$source ] );
1132 if ( $rt->isSpecialPage() ) {
1136 if ( $rt->isValidRedirectTarget() ) {
1137 return $rt->getFullURL();
1157 $actorQuery = $actorMigration->getJoin(
'rev_user' );
1159 $tables = array_merge( [
'revision' ], $actorQuery[
'tables'], [
'user' ] );
1162 'user_id' => $actorQuery[
'fields'][
'rev_user'],
1163 'user_name' => $actorQuery[
'fields'][
'rev_user_text'],
1164 'actor_id' => $actorQuery[
'fields'][
'rev_actor'],
1165 'user_real_name' =>
'MIN(user_real_name)',
1166 'timestamp' =>
'MAX(rev_timestamp)',
1169 $conds = [
'rev_page' => $this->
getId() ];
1176 $conds[] =
'NOT(' . $actorMigration->getWhere(
$dbr,
'rev_user',
$user )[
'conds'] .
')';
1179 $conds[] =
"{$dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER )} = 0";
1182 'user' => [
'LEFT JOIN', $actorQuery[
'fields'][
'rev_user'] .
' = user_id' ],
1183 ] + $actorQuery[
'joins'];
1186 'GROUP BY' => [ $fields[
'user_id'], $fields[
'user_name'] ],
1187 'ORDER BY' =>
'timestamp DESC',
1204 && ( $oldId ===
null || $oldId === 0 || $oldId === $this->
getLatest() )
1224 ParserOptions $parserOptions, $oldid =
null, $forceParse =
false
1229 if ( $useParserCache && !$parserOptions->
isSafeToCache() ) {
1230 throw new InvalidArgumentException(
1231 'The supplied ParserOptions are not safe to cache. Fix the options or set $forceParse = true.'
1236 ': using parser cache: ' . ( $useParserCache ?
'yes' :
'no' ) .
"\n" );
1241 if ( $useParserCache ) {
1243 ->get( $this, $parserOptions );
1244 if ( $parserOutput !==
false ) {
1245 return $parserOutput;
1249 if ( $oldid ===
null || $oldid === 0 ) {
1256 return $pool->getParserOutput();
1272 function ()
use (
$user, $oldid ) {
1275 $user->clearNotification( $this->mTitle, $oldid );
1291 if ( !
Hooks::run(
'ArticlePurge', [ &$wikiPage ] ) ) {
1295 $this->mTitle->invalidateCache();
1307 $messageCache->updateMessageOverride( $this->mTitle, $this->
getContent() );
1330 $pageIdForInsert = $pageId ? [
'page_id' => $pageId ] : [];
1334 'page_namespace' => $this->mTitle->getNamespace(),
1335 'page_title' => $this->mTitle->getDBkey(),
1336 'page_restrictions' =>
'',
1337 'page_is_redirect' => 0,
1340 'page_touched' => $dbw->timestamp(),
1343 ] + $pageIdForInsert,
1348 if ( $dbw->affectedRows() > 0 ) {
1349 $newid = $pageId ? (int)$pageId : $dbw->insertId();
1350 $this->mId = $newid;
1351 $this->mTitle->resetArticleID( $newid );
1375 $lastRevIsRedirect =
null
1384 if ( (
int)$revision->getId() === 0 ) {
1385 throw new InvalidArgumentException(
1386 __METHOD__ .
': Revision has ID ' . var_export( $revision->getId(), 1 )
1390 $content = $revision->getContent();
1394 $conditions = [
'page_id' => $this->
getId() ];
1396 if ( !is_null( $lastRevision ) ) {
1398 $conditions[
'page_latest'] = $lastRevision;
1401 $revId = $revision->getId();
1402 Assert::parameter( $revId > 0,
'$revision->getId()',
'must be > 0' );
1405 'page_latest' => $revId,
1406 'page_touched' => $dbw->timestamp( $revision->getTimestamp() ),
1407 'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
1408 'page_is_redirect' => $rt !==
null ? 1 : 0,
1413 $row[
'page_content_model'] = $revision->getContentModel();
1416 $dbw->update(
'page',
1421 $result = $dbw->affectedRows() > 0;
1425 $this->mLatest = $revision->getId();
1426 $this->mIsRedirect = (bool)$rt;
1428 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1429 $linkCache->addGoodLinkObj(
1435 $revision->getContentModel()
1457 $isRedirect = !is_null( $redirectTitle );
1459 if ( !$isRedirect && $lastRevIsRedirect ===
false ) {
1463 if ( $isRedirect ) {
1467 $where = [
'rd_from' => $this->
getId() ];
1468 $dbw->delete(
'redirect', $where, __METHOD__ );
1475 return ( $dbw->affectedRows() != 0 );
1489 $row = $dbw->selectRow(
1490 [
'revision',
'page' ],
1491 [
'rev_id',
'rev_timestamp',
'page_is_redirect' ],
1493 'page_id' => $this->
getId(),
1494 'page_latest=rev_id' ],
1498 if (
wfTimestamp( TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) {
1501 $prev = $row->rev_id;
1502 $lastRevIsRedirect = (bool)$row->page_is_redirect;
1506 $lastRevIsRedirect =
null;
1529 $changedRoles = $aSlots->getRolesWithDifferentContent( $bSlots );
1531 return ( $changedRoles !== [ SlotRecord::MAIN ] && $changedRoles !== [] );
1548 if ( self::hasDifferencesOutsideMainSlot( $undo, $undoafter ) ) {
1586 $sectionId,
Content $sectionContent, $sectionTitle =
'', $edittime =
null
1589 if ( $edittime && $sectionId !==
'new' ) {
1597 && $lb->getServerCount() > 1
1598 && $lb->hasOrMadeRecentMasterChanges()
1604 $baseRevId =
$rev->getId();
1608 return $this->
replaceSectionAtRev( $sectionId, $sectionContent, $sectionTitle, $baseRevId );
1625 $sectionTitle =
'', $baseRevId =
null
1627 if ( strval( $sectionId ) ===
'' ) {
1629 $newContent = $sectionContent;
1632 throw new MWException(
"sections not supported for content model " .
1637 if ( is_null( $baseRevId ) || $sectionId ===
'new' ) {
1642 wfDebug( __METHOD__ .
" asked for bogus section (page: " .
1643 $this->
getId() .
"; section: $sectionId)\n" );
1647 $oldContent =
$rev->getContent();
1650 if ( !$oldContent ) {
1651 wfDebug( __METHOD__ .
": no page text\n" );
1655 $newContent = $oldContent->replaceSection( $sectionId, $sectionContent, $sectionTitle );
1696 MediaWikiServices::getInstance()->getContentLanguage(),
1697 MediaWikiServices::getInstance()->getDBLoadBalancerFactory()
1734 User $forUser =
null,
1739 if ( !$forRevision && !$forUpdate ) {
1742 $this->derivedDataUpdater =
null;
1745 if ( $this->derivedDataUpdater && !$this->derivedDataUpdater->isContentPrepared() ) {
1749 $this->derivedDataUpdater =
null;
1756 if ( $this->derivedDataUpdater
1757 && !$this->derivedDataUpdater->isReusableFor(
1761 $forEdit ? $this->getLatest() :
null
1764 $this->derivedDataUpdater =
null;
1767 if ( !$this->derivedDataUpdater ) {
1805 return $pageUpdater;
1872 User $user =
null, $serialFormat =
null, $tags = [], $undidRevId = 0
1892 $slotsUpdate->modifyContent( SlotRecord::MAIN,
$content );
1899 $updater->setOriginalRevisionId( $originalRevId );
1900 $updater->setUndidRevisionId( $undidRevId );
1907 if ( $needsPatrol && $this->
getTitle()->userCan(
'autopatrol',
$user ) ) {
1925 $this->mLatest = $revRec->getId();
1948 if ( $this->
getTitle()->isConversionTable() ) {
1951 $options->disableContentConversion();
1981 $serialFormat =
null,
1990 if ( !is_object( $revision ) ) {
1994 if ( $revid !==
null ) {
1995 wfDeprecated( __METHOD__ .
' with $revision = revision ID',
'1.25' );
1997 $revision = $store->getRevisionById( $revid, Revision::READ_LATEST );
2001 } elseif ( $revision instanceof
Revision ) {
2002 $revision = $revision->getRevisionRecord();
2005 $slots = RevisionSlotsUpdate::newFromContent( [ SlotRecord::MAIN =>
$content ] );
2008 if ( !
$updater->isUpdatePrepared() ) {
2015 'causeAction' =>
'prepare-edit',
2016 'causeAgent' =>
$user->getName(),
2022 return $updater->getPreparedEdit();
2054 'causeAction' =>
'edit-page',
2055 'causeAgent' =>
$user->getName(),
2082 if ( !$revision || !$revision->getId() ) {
2083 LoggerFactory::getInstance(
'wikipage' )->info(
2084 __METHOD__ .
'called with ' . ( $revision ?
'unsaved' :
'no' ) .
' revision'
2124 if ( !$revision || !$revision->getId() ) {
2125 LoggerFactory::getInstance(
'wikipage' )->info(
2126 __METHOD__ .
'called with ' . ( $revision ?
'unsaved' :
'no' ) .
' revision'
2152 &$cascade, $reason,
User $user, $tags =
null
2161 $this->mTitle->loadRestrictions(
null, Title::READ_LATEST );
2162 $restrictionTypes = $this->mTitle->getRestrictionTypes();
2163 $id = $this->
getId();
2174 $isProtected =
false;
2180 foreach ( $restrictionTypes
as $action ) {
2181 if ( !isset( $expiry[$action] ) || $expiry[$action] === $dbw->getInfinity() ) {
2182 $expiry[$action] =
'infinity';
2184 if ( !isset( $limit[$action] ) ) {
2185 $limit[$action] =
'';
2186 } elseif ( $limit[$action] !=
'' ) {
2191 $current = implode(
'', $this->mTitle->getRestrictions( $action ) );
2192 if ( $current !=
'' ) {
2193 $isProtected =
true;
2196 if ( $limit[$action] != $current ) {
2198 } elseif ( $limit[$action] !=
'' ) {
2202 if ( $this->mTitle->getRestrictionExpiry( $action ) != $expiry[$action] ) {
2208 if ( !$changed && $protect && $this->mTitle->areRestrictionsCascading() != $cascade ) {
2218 $revCommentMsg =
'unprotectedarticle-comment';
2219 $logAction =
'unprotect';
2220 } elseif ( $isProtected ) {
2221 $revCommentMsg =
'modifiedarticleprotection-comment';
2222 $logAction =
'modify';
2224 $revCommentMsg =
'protectedarticle-comment';
2225 $logAction =
'protect';
2228 $logRelationsValues = [];
2229 $logRelationsField =
null;
2230 $logParamsDetails = [];
2233 $nullRevision =
null;
2239 if ( !
Hooks::run(
'ArticleProtect', [ &$wikiPage, &
$user, $limit, $reason ] ) ) {
2244 $editrestriction = isset( $limit[
'edit'] )
2245 ? [ $limit[
'edit'] ]
2246 : $this->mTitle->getRestrictions(
'edit' );
2247 foreach ( array_keys( $editrestriction,
'sysop' )
as $key ) {
2248 $editrestriction[$key] =
'editprotected';
2250 foreach ( array_keys( $editrestriction,
'autoconfirmed' )
as $key ) {
2251 $editrestriction[$key] =
'editsemiprotected';
2255 foreach ( array_keys( $cascadingRestrictionLevels,
'sysop' )
as $key ) {
2256 $cascadingRestrictionLevels[$key] =
'editprotected';
2258 foreach ( array_keys( $cascadingRestrictionLevels,
'autoconfirmed' )
as $key ) {
2259 $cascadingRestrictionLevels[$key] =
'editsemiprotected';
2263 if ( !array_intersect( $editrestriction, $cascadingRestrictionLevels ) ) {
2278 if ( $nullRevision ===
null ) {
2279 return Status::newFatal(
'no-null-revision', $this->mTitle->getPrefixedText() );
2282 $logRelationsField =
'pr_id';
2285 foreach ( $limit
as $action => $restrictions ) {
2287 'page_restrictions',
2290 'pr_type' => $action
2294 if ( $restrictions !=
'' ) {
2295 $cascadeValue = ( $cascade && $action ==
'edit' ) ? 1 : 0;
2297 'page_restrictions',
2300 'pr_type' => $action,
2301 'pr_level' => $restrictions,
2302 'pr_cascade' => $cascadeValue,
2303 'pr_expiry' => $dbw->encodeExpiry( $expiry[$action] )
2307 $logRelationsValues[] = $dbw->insertId();
2308 $logParamsDetails[] = [
2310 'level' => $restrictions,
2311 'expiry' => $expiry[$action],
2312 'cascade' => (bool)$cascadeValue,
2320 [
'page_restrictions' =>
'' ],
2321 [
'page_id' => $id ],
2329 [ $this, $nullRevision, $latest,
$user ] );
2330 Hooks::run(
'ArticleProtectComplete', [ &$wikiPage, &
$user, $limit, $reason ] );
2335 if ( $limit[
'create'] !=
'' ) {
2337 $dbw->replace(
'protected_titles',
2338 [ [
'pt_namespace',
'pt_title' ] ],
2340 'pt_namespace' => $this->mTitle->getNamespace(),
2341 'pt_title' => $this->mTitle->getDBkey(),
2342 'pt_create_perm' => $limit[
'create'],
2343 'pt_timestamp' => $dbw->timestamp(),
2344 'pt_expiry' => $dbw->encodeExpiry( $expiry[
'create'] ),
2345 'pt_user' =>
$user->getId(),
2346 ] + $commentFields, __METHOD__
2348 $logParamsDetails[] = [
2350 'level' => $limit[
'create'],
2351 'expiry' => $expiry[
'create'],
2354 $dbw->delete(
'protected_titles',
2356 'pt_namespace' => $this->mTitle->getNamespace(),
2357 'pt_title' => $this->mTitle->getDBkey()
2363 $this->mTitle->flushRestrictions();
2366 if ( $logAction ==
'unprotect' ) {
2371 '4::description' => $protectDescriptionLog,
2372 '5:bool:cascade' => $cascade,
2373 'details' => $logParamsDetails,
2379 $logEntry->setTarget( $this->mTitle );
2380 $logEntry->setComment( $reason );
2381 $logEntry->setPerformer(
$user );
2382 $logEntry->setParameters(
$params );
2383 if ( !is_null( $nullRevision ) ) {
2384 $logEntry->setAssociatedRevId( $nullRevision->getId() );
2386 $logEntry->setTags( $tags );
2387 if ( $logRelationsField !==
null &&
count( $logRelationsValues ) ) {
2388 $logEntry->setRelations( [ $logRelationsField => $logRelationsValues ] );
2390 $logId = $logEntry->insert();
2391 $logEntry->publish( $logId );
2408 array $expiry, $cascade, $reason,
$user =
null
2415 $this->mTitle->getPrefixedText(),
2417 )->inContentLanguage()->text();
2419 $editComment .=
wfMessage(
'colon-separator' )->inContentLanguage()->text() . $reason;
2422 if ( $protectDescription ) {
2423 $editComment .=
wfMessage(
'word-separator' )->inContentLanguage()->text();
2424 $editComment .=
wfMessage(
'parentheses' )->params( $protectDescription )
2425 ->inContentLanguage()->text();
2428 $editComment .=
wfMessage(
'word-separator' )->inContentLanguage()->text();
2429 $editComment .=
wfMessage(
'brackets' )->params(
2430 wfMessage(
'protect-summary-cascade' )->inContentLanguage()->
text()
2431 )->inContentLanguage()->text();
2436 $nullRev->insertOn( $dbw );
2439 $oldLatest = $nullRev->getParentId();
2451 if ( $expiry !=
'infinity' ) {
2452 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
2455 $contLang->timeanddate( $expiry,
false,
false ),
2456 $contLang->date( $expiry,
false,
false ),
2457 $contLang->time( $expiry,
false,
false )
2458 )->inContentLanguage()->text();
2460 return wfMessage(
'protect-expiry-indefinite' )
2461 ->inContentLanguage()->text();
2473 $protectDescription =
'';
2475 foreach ( array_filter( $limit )
as $action => $restrictions ) {
2476 # $action is one of $wgRestrictionTypes = [ 'create', 'edit', 'move', 'upload' ].
2477 # All possible message keys are listed here for easier grepping:
2478 # * restriction-create
2479 # * restriction-edit
2480 # * restriction-move
2481 # * restriction-upload
2482 $actionText =
wfMessage(
'restriction-' . $action )->inContentLanguage()->text();
2483 # $restrictions is one of $wgRestrictionLevels = [ '', 'autoconfirmed', 'sysop' ],
2484 # with '' filtered out. All possible message keys are listed below:
2485 # * protect-level-autoconfirmed
2486 # * protect-level-sysop
2487 $restrictionsText =
wfMessage(
'protect-level-' . $restrictions )
2488 ->inContentLanguage()->text();
2492 if ( $protectDescription !==
'' ) {
2493 $protectDescription .=
wfMessage(
'word-separator' )->inContentLanguage()->text();
2495 $protectDescription .=
wfMessage(
'protect-summary-desc' )
2496 ->params( $actionText, $restrictionsText, $expiryText )
2497 ->inContentLanguage()->text();
2500 return $protectDescription;
2515 $protectDescriptionLog =
'';
2517 $dirMark = MediaWikiServices::getInstance()->getContentLanguage()->getDirMark();
2518 foreach ( array_filter( $limit )
as $action => $restrictions ) {
2520 $protectDescriptionLog .=
2522 "[$action=$restrictions] ($expiryText)";
2525 return trim( $protectDescriptionLog );
2538 if ( !is_array( $limit ) ) {
2539 throw new MWException( __METHOD__ .
' given non-array restriction set' );
2545 foreach ( array_filter( $limit )
as $action => $restrictions ) {
2546 $bits[] =
"$action=$restrictions";
2549 return implode(
':', $bits );
2569 $revCount += $safetyMargin;
2594 $reason, $suppress =
false, $u1 =
null, $u2 =
null, &$error =
'',
User $user =
null,
2598 [],
'delete', $immediate );
2627 $reason, $suppress =
false, $u1 =
null, $u2 =
null, &$error =
'',
User $deleter =
null,
2628 $tags = [], $logsubtype =
'delete', $immediate =
false
2643 [ &$wikiPage, &$deleter, &$reason, &$error, &
$status, $suppress ]
2647 $status->fatal(
'delete-hook-aborted' );
2653 $logsubtype, $immediate );
2665 $reason, $suppress,
User $deleter, $tags,
2666 $logsubtype, $immediate =
false, $webRequestId =
null
2673 $dbw->startAtomic( __METHOD__ );
2676 $id = $this->
getId();
2682 if ( $id == 0 || $this->
getLatest() != $lockedLatest ) {
2683 $dbw->endAtomic( __METHOD__ );
2685 $status->error(
'cannotdelete',
2699 }
catch ( Exception $ex ) {
2700 wfLogWarning( __METHOD__ .
': failed to load content during deletion! '
2701 . $ex->getMessage() );
2708 $explictTrxLogged =
false;
2711 if ( $done || !$immediate ) {
2714 $dbw->endAtomic( __METHOD__ );
2715 if ( $dbw->explicitTrxActive() ) {
2717 if ( !$explictTrxLogged ) {
2718 $explictTrxLogged =
true;
2719 LoggerFactory::getInstance(
'wfDebug' )->debug(
2720 'explicit transaction active in ' . __METHOD__ .
' while deleting {title}', [
2721 'title' => $this->
getTitle()->getText(),
2726 if ( $dbw->trxLevel() ) {
2729 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
2730 $lbFactory->waitForReplication();
2731 $dbw->startAtomic( __METHOD__ );
2736 $dbw->endAtomic( __METHOD__ );
2739 'wikiPageId' => $id,
2741 'reason' => $reason,
2742 'suppress' => $suppress,
2743 'userId' => $deleter->
getId(),
2744 'tags' => json_encode( $tags ),
2745 'logsubtype' => $logsubtype,
2751 $status->warning(
'delete-scheduled',
2761 $archivedRevisionCount = (int)$dbw->selectField(
2762 'archive',
'COUNT(*)',
2764 'ar_namespace' => $this->getTitle()->getNamespace(),
2765 'ar_title' => $this->
getTitle()->getDBkey(),
2773 $wikiPageBeforeDelete = clone $this;
2776 $dbw->delete(
'page', [
'page_id' => $id ], __METHOD__ );
2779 $logtype = $suppress ?
'suppress' :
'delete';
2782 $logEntry->setPerformer( $deleter );
2783 $logEntry->setTarget( $logTitle );
2784 $logEntry->setComment( $reason );
2785 $logEntry->setTags( $tags );
2786 $logid = $logEntry->insert();
2788 $dbw->onTransactionPreCommitOrIdle(
2789 function ()
use ( $logEntry, $logid ) {
2791 $logEntry->publish( $logid );
2796 $dbw->endAtomic( __METHOD__ );
2801 &$wikiPageBeforeDelete,
2807 $archivedRevisionCount
2812 $cache = MediaWikiServices::getInstance()->getMainObjectStash();
2813 $key =
$cache->makeKey(
'page-recent-delete', md5( $logTitle->getPrefixedText() ) );
2814 $cache->set( $key, 1, $cache::TTL_DAY );
2834 $namespace = $this->
getTitle()->getNamespace();
2835 $dbKey = $this->
getTitle()->getDBkey();
2860 $dbw->lockForUpdate(
2863 [
'revision',
'revision_comment_temp',
'revision_actor_temp' ]
2865 [
'rev_page' => $id ],
2878 $revQuery[
'fields'][] =
'rev_content_model';
2879 $revQuery[
'fields'][] =
'rev_content_format';
2885 $res = $dbw->select(
2888 [
'rev_page' => $id ],
2902 foreach (
$res as $row ) {
2908 $comment = $commentStore->getComment(
'rev_comment', $row );
2911 'ar_namespace' => $namespace,
2912 'ar_title' => $dbKey,
2913 'ar_timestamp' => $row->rev_timestamp,
2914 'ar_minor_edit' => $row->rev_minor_edit,
2915 'ar_rev_id' => $row->rev_id,
2916 'ar_parent_id' => $row->rev_parent_id,
2925 'ar_len' => $row->rev_len,
2926 'ar_page_id' => $id,
2927 'ar_deleted' => $suppress ? $bitfield : $row->rev_deleted,
2928 'ar_sha1' => $row->rev_sha1,
2929 ] + $commentStore->insert( $dbw,
'ar_comment', $comment )
2930 + $actorMigration->getInsertValues( $dbw,
'ar_user',
$user );
2933 $rowInsert[
'ar_text_id'] = $row->rev_text_id;
2936 $rowInsert[
'ar_content_model'] = $row->rev_content_model;
2937 $rowInsert[
'ar_content_format'] = $row->rev_content_format;
2941 $rowsInsert[] = $rowInsert;
2942 $revids[] = $row->rev_id;
2946 if ( (
int)$row->rev_user === 0 &&
IP::isValid( $row->rev_user_text ) ) {
2947 $ipRevIds[] = $row->rev_id;
2952 if (
count( $revids ) > 0 ) {
2954 $dbw->insert(
'archive', $rowsInsert, __METHOD__ );
2956 $dbw->delete(
'revision', [
'rev_id' => $revids ], __METHOD__ );
2957 $dbw->delete(
'revision_comment_temp', [
'revcomment_rev' => $revids ], __METHOD__ );
2959 $dbw->delete(
'revision_actor_temp', [
'revactor_rev' => $revids ], __METHOD__ );
2963 if (
count( $ipRevIds ) > 0 ) {
2964 $dbw->delete(
'ip_changes', [
'ipc_rev_id' => $ipRevIds ], __METHOD__ );
2982 'page_id' => $this->
getId(),
2985 'page_namespace' => $this->
getTitle()->getNamespace(),
2986 'page_title' => $this->
getTitle()->getDBkey()
3008 if ( $id !== $this->
getId() ) {
3009 throw new InvalidArgumentException(
'Mismatching page ID' );
3014 }
catch ( Exception $ex ) {
3022 [
'edits' => 1,
'articles' => -$countable,
'pages' => -1 ]
3027 $revision ? $revision->getRevisionRecord() :
$content
3029 foreach ( $updates
as $update ) {
3033 $causeAgent =
$user ?
$user->getName() :
'unknown';
3036 $this->mTitle,
'templatelinks',
'delete-page', $causeAgent );
3038 if ( $this->mTitle->getNamespace() ==
NS_FILE ) {
3040 $this->mTitle,
'imagelinks',
'delete-page', $causeAgent );
3089 $fromP, $summary, $token, $bot, &$resultDetails,
User $user, $tags =
null
3091 $resultDetails =
null;
3094 $editErrors = $this->mTitle->getUserPermissionsErrors(
'edit',
$user );
3095 $rollbackErrors = $this->mTitle->getUserPermissionsErrors(
'rollback',
$user );
3096 $errors = array_merge( $editErrors,
wfArrayDiff2( $rollbackErrors, $editErrors ) );
3098 if ( !
$user->matchEditToken( $token,
'rollback' ) ) {
3099 $errors[] = [
'sessionfailure' ];
3102 if (
$user->pingLimiter(
'rollback' ) ||
$user->pingLimiter() ) {
3103 $errors[] = [
'actionthrottledtext' ];
3107 if ( !empty( $errors ) ) {
3135 &$resultDetails,
User $guser, $tags =
null
3142 return [ [
'readonlytext' ] ];
3148 $current =
$updater->grabParentRevision();
3150 if ( is_null( $current ) ) {
3152 return [ [
'notanarticle' ] ];
3155 $currentEditorForPublic = $current->getUser( RevisionRecord::FOR_PUBLIC );
3156 $legacyCurrent =
new Revision( $current );
3157 $from = str_replace(
'_',
' ', $fromP );
3161 if ( $from !== ( $currentEditorForPublic ? $currentEditorForPublic->getName() :
'' ) ) {
3162 $resultDetails = [
'current' => $legacyCurrent ];
3163 return [ [
'alreadyrolled',
3164 htmlspecialchars( $this->mTitle->getPrefixedText() ),
3165 htmlspecialchars( $fromP ),
3166 htmlspecialchars( $currentEditorForPublic ? $currentEditorForPublic->getName() :
'' )
3175 $current->getUser( RevisionRecord::RAW )
3178 $s = $dbw->selectRow(
3179 [
'revision' ] + $actorWhere[
'tables'],
3180 [
'rev_id',
'rev_timestamp',
'rev_deleted' ],
3182 'rev_page' => $current->getPageId(),
3183 'NOT(' . $actorWhere[
'conds'] .
')',
3187 'USE INDEX' => [
'revision' =>
'page_timestamp' ],
3188 'ORDER BY' =>
'rev_timestamp DESC'
3190 $actorWhere[
'joins']
3192 if (
$s ===
false ) {
3194 return [ [
'cantrollback' ] ];
3195 } elseif (
$s->rev_deleted & RevisionRecord::DELETED_TEXT
3196 ||
$s->rev_deleted & RevisionRecord::DELETED_USER
3199 return [ [
'notvisiblerev' ] ];
3205 RevisionStore::READ_LATEST
3207 if ( empty( $summary ) ) {
3208 if ( !$currentEditorForPublic ) {
3209 $summary =
wfMessage(
'revertpage-nouser' );
3214 $legacyTarget =
new Revision( $target );
3215 $targetEditorForPublic = $target->getUser( RevisionRecord::FOR_PUBLIC );
3218 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
3220 $targetEditorForPublic ? $targetEditorForPublic->getName() :
null,
3221 $currentEditorForPublic ? $currentEditorForPublic->getName() :
null,
3223 $contLang->timeanddate(
wfTimestamp( TS_MW,
$s->rev_timestamp ) ),
3225 $contLang->timeanddate( $current->getTimestamp() )
3227 if ( $summary instanceof Message ) {
3228 $summary = $summary->params(
$args )->inContentLanguage()->text();
3234 $summary = trim( $summary );
3239 if ( $guser->
isAllowed(
'minoredit' ) ) {
3243 if ( $bot && ( $guser->
isAllowedAny(
'markbotedits',
'bot' ) ) ) {
3248 $currentContent = $current->getContent( SlotRecord::MAIN );
3249 $targetContent = $target->getContent( SlotRecord::MAIN );
3250 $changingContentModel = $targetContent->getModel() !== $currentContent->getModel();
3253 $tags[] =
'mw-rollback';
3259 foreach ( $target->getSlots()->getSlots()
as $slot ) {
3265 foreach ( $current->getSlotRoles()
as $role ) {
3266 if ( !$target->hasSlot( $role ) ) {
3271 $updater->setOriginalRevisionId( $target->getId() );
3291 if ( $bot && $guser->
isAllowed(
'markbotedits' ) ) {
3301 if (
count( $set ) ) {
3305 $current->getUser( RevisionRecord::RAW ),
3308 $dbw->update(
'recentchanges', $set,
3310 'rc_cur_id' => $current->getPageId(),
3311 'rc_timestamp > ' . $dbw->addQuotes(
$s->rev_timestamp ),
3312 $actorWhere[
'conds'],
3318 if ( !
$updater->wasSuccessful() ) {
3319 return $updater->getStatus()->getErrorsArray();
3324 $resultDetails = [
'current' => $legacyCurrent ];
3325 return [ [
'alreadyrolled',
3326 htmlspecialchars( $this->mTitle->getPrefixedText() ),
3327 htmlspecialchars( $fromP ),
3328 htmlspecialchars( $targetEditorForPublic ? $targetEditorForPublic->getName() :
'' )
3332 if ( $changingContentModel ) {
3336 $log->setPerformer( $guser );
3337 $log->setTarget( $this->mTitle );
3338 $log->setComment( $summary );
3339 $log->setParameters( [
3340 '4::oldmodel' => $currentContent->getModel(),
3341 '5::newmodel' => $targetContent->getModel(),
3344 $logId = $log->insert( $dbw );
3345 $log->publish( $logId );
3348 $revId =
$rev->getId();
3350 Hooks::run(
'ArticleRollbackComplete', [ $this, $guser, $legacyTarget, $legacyCurrent ] );
3353 'summary' => $summary,
3354 'current' => $legacyCurrent,
3355 'target' => $legacyTarget,
3379 $other =
$title->getOtherPage();
3381 $other->purgeSquid();
3385 $title->deleteTitleProtection();
3387 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle(
$title );
3414 $other =
$title->getOtherPage();
3416 $other->purgeSquid();
3421 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle(
$title );
3443 $user->setNewtalk(
false );
3465 $slotsChanged =
null
3469 if ( $slotsChanged ===
null || in_array( SlotRecord::MAIN, $slotsChanged ) ) {
3483 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle(
$title );
3491 $revid = $revision ? $revision->getId() :
null;
3515 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
3536 $id = $this->
getId();
3542 $res =
$dbr->select(
'categorylinks',
3543 [
'cl_to AS page_title, ' .
NS_CATEGORY .
' AS page_namespace' ],
3546 [
'cl_from' => $id ],
3560 $id = $this->
getId();
3567 $res =
$dbr->select( [
'categorylinks',
'page_props',
'page' ],
3569 [
'cl_from' => $id,
'pp_page=page_id',
'pp_propname' =>
'hiddencat',
3570 'page_namespace' =>
NS_CATEGORY,
'page_title=cl_to' ],
3573 if (
$res !==
false ) {
3574 foreach (
$res as $row ) {
3604 $id = $id ?: $this->
getId();
3607 $addFields = [
'cat_pages = cat_pages + 1' ];
3608 $removeFields = [
'cat_pages = cat_pages - 1' ];
3609 if (
$type !==
'page' ) {
3610 $addFields[] =
"cat_{$type}s = cat_{$type}s + 1";
3611 $removeFields[] =
"cat_{$type}s = cat_{$type}s - 1";
3616 if (
count( $added ) ) {
3617 $existingAdded = $dbw->selectFieldValues(
3620 [
'cat_title' => $added ],
3627 if (
count( $existingAdded ) ) {
3631 [
'cat_title' => $existingAdded ],
3636 $missingAdded = array_diff( $added, $existingAdded );
3637 if (
count( $missingAdded ) ) {
3639 foreach ( $missingAdded
as $cat ) {
3641 'cat_title' => $cat,
3643 'cat_subcats' => (
$type ===
'subcat' ) ? 1 : 0,
3644 'cat_files' => (
$type ===
'file' ) ? 1 : 0,
3657 if (
count( $deleted ) ) {
3661 [
'cat_title' => $deleted ],
3666 foreach ( $added
as $catName ) {
3668 Hooks::run(
'CategoryAfterPageAdded', [ $cat, $this ] );
3671 foreach ( $deleted
as $catName ) {
3673 Hooks::run(
'CategoryAfterPageRemoved', [ $cat, $this, $id ] );
3676 $cat->refreshCountsIfEmpty();
3692 if ( !
Hooks::run(
'OpportunisticLinksUpdate',
3693 [ $this, $this->mTitle, $parserOutput ]
3701 'isOpportunistic' =>
true,
3705 if ( $this->mTitle->areRestrictionsCascading() ) {
3710 } elseif ( !$config->get(
'MiserMode' ) && $parserOutput->
hasDynamicContent() ) {
3721 $key =
$cache->makeKey(
'dynamic-linksupdate',
'last', $this->
getId() );
3723 if (
$cache->add( $key, time(), $ttl ) ) {
3743 wfDeprecated( __METHOD__ .
' without a RevisionRecord',
'1.32' );
3747 }
catch ( Exception $ex ) {
3752 wfDebug( __METHOD__ .
' failed to load current revision of page ' . $this->
getId() );
3759 wfDeprecated( __METHOD__ .
' with a Content object instead of a RevisionRecord',
'1.32' );
3761 $slotContent = [ SlotRecord::MAIN =>
$rev ];
3763 $slotContent = array_map(
function (
SlotRecord $slot ) {
3765 },
$rev->getSlots()->getSlots() );
3774 foreach ( $slotContent
as $role =>
$content ) {
3777 $updates =
$handler->getDeletionUpdates(
3781 $allUpdates = array_merge( $allUpdates, $updates );
3784 $legacyUpdates =
$content->getDeletionUpdates( $this );
3787 $legacyUpdates = array_filter( $legacyUpdates,
function ( $update ) {
3791 $allUpdates = array_merge( $allUpdates, $legacyUpdates );
3835 return $this->
getTitle()->getCanonicalURL();
3844 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3846 return $linkCache->getMutableCacheKeys(
$cache, $this->
getTitle() );