116 $ns =
$title->getNamespace();
119 throw new MWException(
"NS_MEDIA is a virtual namespace; use NS_FILE." );
120 } elseif ( $ns < 0 ) {
121 throw new MWException(
"Invalid or virtual namespace $ns given." );
161 $row = $db->selectRow(
162 'page', self::selectFields(), [
'page_id' => $id ], __METHOD__ );
195 return self::READ_NORMAL;
197 return self::READ_LATEST;
241 $this->mDataLoaded =
false;
253 $this->mRedirectTarget =
null;
254 $this->mLastRevision =
null;
255 $this->mTouched =
'19700101000000';
256 $this->mLinksUpdated =
'19700101000000';
257 $this->mTimestamp =
'';
258 $this->mIsRedirect =
false;
259 $this->mLatest =
false;
272 $this->mPreparedEdit =
false;
282 global $wgContentHandlerUseDB, $wgPageLanguageUseDB;
293 'page_links_updated',
298 if ( $wgContentHandlerUseDB ) {
299 $fields[] =
'page_content_model';
302 if ( $wgPageLanguageUseDB ) {
303 $fields[] =
'page_lang';
319 Hooks::run(
'ArticlePageDataBefore', [ &$this, &$fields ] );
321 $row =
$dbr->selectRow(
'page', $fields, $conditions, __METHOD__,
$options );
323 Hooks::run(
'ArticlePageDataAfter', [ &$wikiPage, &$row ] );
339 'page_namespace' =>
$title->getNamespace(),
369 if ( is_int(
$from ) && $from <= $this->mDataLoadedFrom ) {
374 if ( is_int(
$from ) ) {
380 &&
wfGetLB()->getServerCount() > 1
381 &&
wfGetLB()->hasOrMadeRecentMasterChanges()
383 $from = self::READ_LATEST;
390 $from = self::READ_NORMAL;
409 $lc->clearLink( $this->mTitle );
412 $lc->addGoodLinkObjFromRow( $this->mTitle, $data );
414 $this->mTitle->loadFromRow( $data );
417 $this->mTitle->loadRestrictions( $data->page_restrictions );
419 $this->mId = intval( $data->page_id );
422 $this->mIsRedirect = intval( $data->page_is_redirect );
423 $this->mLatest = intval( $data->page_latest );
426 if ( $this->mLastRevision && $this->mLastRevision->getId() !=
$this->mLatest ) {
427 $this->mLastRevision =
null;
428 $this->mTimestamp =
'';
431 $lc->addBadLinkObj( $this->mTitle );
433 $this->mTitle->loadFromRow(
false );
440 $this->mDataLoaded =
true;
448 if ( !$this->mDataLoaded ) {
458 if ( !$this->mDataLoaded ) {
461 return $this->mId > 0;
473 return $this->mTitle->isKnown();
482 if ( !$this->mDataLoaded ) {
503 return $cache->getWithSetCallback(
504 $cache->makeKey(
'page',
'content-model', $this->getLatest() ),
510 return $rev->getContentModel();
512 $title = $this->mTitle->getPrefixedDBkey();
513 wfWarn(
"Page $title exists but has no (visible) revisions!" );
514 return $this->mTitle->getContentModel();
521 return $this->mTitle->getContentModel();
529 if ( !$this->mDataLoaded ) {
532 return ( $this->mId && !$this->mIsRedirect );
540 if ( !$this->mDataLoaded ) {
551 if ( !$this->mDataLoaded ) {
562 if ( !$this->mDataLoaded ) {
580 while ( $continue ) {
581 $row = $db->selectRow(
582 [
'page',
'revision' ],
585 'page_namespace' => $this->mTitle->getNamespace(),
586 'page_title' => $this->mTitle->getDBkey(),
591 'ORDER BY' =>
'rev_timestamp ASC'
611 if ( $this->mLastRevision !==
null ) {
620 if ( $this->mDataLoadedFrom == self::READ_LOCKING ) {
630 } elseif ( $this->mDataLoadedFrom == self::READ_LATEST ) {
634 $flags = Revision::READ_LATEST;
651 $this->mLastRevision = $revision;
661 if ( $this->mLastRevision ) {
682 if ( $this->mLastRevision ) {
683 return $this->mLastRevision->getContent( $audience,
$user );
704 if ( $this->mLastRevision ) {
705 return $this->mLastRevision->getText( $audience,
$user );
715 if ( !$this->mTimestamp ) {
742 if ( $this->mLastRevision ) {
743 return $this->mLastRevision->getUser( $audience,
$user );
762 $userName = $revision->getUserText( $audience,
$user );
780 if ( $this->mLastRevision ) {
781 return $this->mLastRevision->getUserText( $audience,
$user );
798 if ( $this->mLastRevision ) {
799 return $this->mLastRevision->getComment( $audience,
$user );
812 if ( $this->mLastRevision ) {
813 return $this->mLastRevision->isMinor();
828 global $wgArticleCountMethod;
830 if ( !$this->mTitle->isContentPage() ) {
846 if ( $wgArticleCountMethod ===
'link' ) {
854 $hasLinks = (bool)count( $editInfo->output->getLinks() );
857 [
'pl_from' => $this->
getId() ], __METHOD__ );
861 return $content->isCountable( $hasLinks );
872 if ( !$this->mTitle->isRedirect() ) {
876 if ( $this->mRedirectTarget !==
null ) {
882 $row =
$dbr->selectRow(
'redirect',
883 [
'rd_namespace',
'rd_title',
'rd_fragment',
'rd_interwiki' ],
884 [
'rd_from' => $this->
getId() ],
889 if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) {
891 $row->rd_namespace, $row->rd_title,
892 $row->rd_fragment, $row->rd_interwiki
922 $that->insertRedirectEntry(
$retval, $latest );
938 $dbw->startAtomic( __METHOD__ );
941 $dbw->replace(
'redirect',
944 'rd_from' => $this->
getId(),
954 $dbw->endAtomic( __METHOD__ );
978 if ( $rt->isExternal() ) {
979 if ( $rt->isLocal() ) {
983 $source = $this->mTitle->getFullURL(
'redirect=no' );
984 return $rt->getFullURL( [
'rdfrom' =>
$source ] );
992 if ( $rt->isSpecialPage() ) {
996 if ( $rt->isValidRedirectTarget() ) {
997 return $rt->getFullURL();
1016 if (
$dbr->implicitGroupby() ) {
1017 $realNameField =
'user_real_name';
1019 $realNameField =
'MIN(user_real_name) AS user_real_name';
1022 $tables = [
'revision',
'user' ];
1025 'user_id' =>
'rev_user',
1026 'user_name' =>
'rev_user_text',
1028 'timestamp' =>
'MAX(rev_timestamp)',
1031 $conds = [
'rev_page' => $this->
getId() ];
1037 $conds[] =
"rev_user != $user";
1039 $conds[] =
"rev_user_text != {$dbr->addQuotes( $this->getUserText() )}";
1043 $conds[] =
"{$dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER )} = 0";
1046 'user' => [
'LEFT JOIN',
'rev_user = user_id' ],
1050 'GROUP BY' => [
'rev_user',
'rev_user_text' ],
1051 'ORDER BY' =>
'timestamp DESC',
1068 && ( $oldId ===
null || $oldId === 0 || $oldId === $this->
getLatest() )
1086 ParserOptions $parserOptions, $oldid =
null, $forceParse =
false
1091 ': using parser cache: ' . ( $useParserCache ?
'yes' :
'no' ) .
"\n" );
1096 if ( $useParserCache ) {
1103 if ( $oldid ===
null || $oldid === 0 ) {
1110 return $pool->getParserOutput();
1126 $user->clearNotification( $this->mTitle, $oldid );
1139 if ( !
Hooks::run(
'ArticlePurge', [ &$this ] ) ) {
1143 if ( (
$flags & self::PURGE_GLOBAL_PCACHE ) == self::PURGE_GLOBAL_PCACHE ) {
1145 $this->mTitle->invalidateCache();
1146 } elseif ( (
$flags & self::PURGE_CLUSTER_PCACHE ) == self::PURGE_CLUSTER_PCACHE ) {
1152 $cache->makeKey(
'page',
'last-dc-purge', $this->getId() ),
1177 if ( $text ===
null ) {
1199 return $cache->get(
$cache->makeKey(
'page',
'last-dc-purge', $this->getId() ) );
1217 $pageIdForInsert = $pageId ?: $dbw->nextSequenceValue(
'page_page_id_seq' );
1221 'page_id' => $pageIdForInsert,
1222 'page_namespace' => $this->mTitle->getNamespace(),
1223 'page_title' => $this->mTitle->getDBkey(),
1224 'page_restrictions' =>
'',
1225 'page_is_redirect' => 0,
1228 'page_touched' => $dbw->timestamp(),
1236 if ( $dbw->affectedRows() > 0 ) {
1237 $newid = $pageId ?: $dbw->insertId();
1238 $this->mId = $newid;
1239 $this->mTitle->resetArticleID( $newid );
1261 $lastRevIsRedirect =
null
1263 global $wgContentHandlerUseDB;
1266 if ( (
int)$revision->getId() === 0 ) {
1267 throw new InvalidArgumentException(
1268 __METHOD__ .
': Revision has ID ' . var_export( $revision->getId(), 1 )
1272 $content = $revision->getContent();
1276 $conditions = [
'page_id' => $this->
getId() ];
1278 if ( !is_null( $lastRevision ) ) {
1280 $conditions[
'page_latest'] = $lastRevision;
1284 'page_latest' => $revision->getId(),
1285 'page_touched' => $dbw->timestamp( $revision->getTimestamp() ),
1286 'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
1287 'page_is_redirect' => $rt !==
null ? 1 : 0,
1291 if ( $wgContentHandlerUseDB ) {
1292 $row[
'page_content_model'] = $revision->getContentModel();
1295 $dbw->update(
'page',
1300 $result = $dbw->affectedRows() > 0;
1304 $this->mLatest = $revision->getId();
1305 $this->mIsRedirect = (bool)$rt;
1313 $revision->getContentModel()
1335 $isRedirect = !is_null( $redirectTitle );
1337 if ( !$isRedirect && $lastRevIsRedirect ===
false ) {
1341 if ( $isRedirect ) {
1345 $where = [
'rd_from' => $this->
getId() ];
1346 $dbw->delete(
'redirect', $where, __METHOD__ );
1353 return ( $dbw->affectedRows() != 0 );
1368 $row = $dbw->selectRow(
1369 [
'revision',
'page' ],
1370 [
'rev_id',
'rev_timestamp',
'page_is_redirect' ],
1372 'page_id' => $this->
getId(),
1373 'page_latest=rev_id' ],
1377 if (
wfTimestamp(
TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) {
1380 $prev = $row->rev_id;
1381 $lastRevIsRedirect = (bool)$row->page_is_redirect;
1385 $lastRevIsRedirect =
null;
1441 if ( $edittime && $sectionId !==
'new' ) {
1448 &&
wfGetLB()->getServerCount() > 1
1449 &&
wfGetLB()->hasOrMadeRecentMasterChanges()
1455 $baseRevId =
$rev->getId();
1476 $sectionTitle =
'', $baseRevId =
null
1479 if ( strval( $sectionId ) ===
'' ) {
1484 throw new MWException(
"sections not supported for content model " .
1489 if ( is_null( $baseRevId ) || $sectionId ===
'new' ) {
1494 wfDebug( __METHOD__ .
" asked for bogus section (page: " .
1495 $this->
getId() .
"; section: $sectionId)\n" );
1499 $oldContent =
$rev->getContent();
1502 if ( !$oldContent ) {
1503 wfDebug( __METHOD__ .
": no page text\n" );
1507 $newContent = $oldContent->replaceSection( $sectionId,
$sectionContent, $sectionTitle );
1651 User $user =
null, $serialFormat =
null, $tags = []
1656 if ( $tags ===
null ) {
1661 if ( $this->mTitle->getText() ===
'' ) {
1662 throw new MWException(
'Something is trying to edit an article with an empty title' );
1665 if ( !
$content->getContentHandler()->canBeUsedOn( $this->mTitle ) ) {
1668 $this->mTitle->getPrefixedText()
1685 if ( !
Hooks::run(
'PageContentSave', $hook_args )
1688 if ( $hookStatus->isOK() ) {
1690 $hookStatus->fatal(
'edit-hook-aborted' );
1699 if ( $old_content && $old_content->getModel() !==
$content->getModel() ) {
1700 $tags[] =
'mw-contentmodelchange';
1718 $pstContent = $editInfo->pstContent;
1722 'serialized' => $editInfo->pst,
1723 'serialFormat' => $serialFormat,
1724 'baseRevId' => $baseRevId,
1725 'oldRevision' => $old_revision,
1726 'oldContent' => $old_content,
1730 'tags' => ( $tags !== null ) ? (
array)$tags : []
1742 $user->addAutopromoteOnceGroups(
'onEdit' );
1743 $user->addAutopromoteOnceGroups(
'onView' );
1771 $oldid = $meta[
'oldId'];
1773 $oldContent = $meta[
'oldContent'];
1778 $status->fatal(
'edit-gone-missing' );
1781 } elseif ( !$oldContent ) {
1783 throw new MWException(
"Could not find text for current revision {$oldid}." );
1788 'page' => $this->
getId(),
1789 'title' => $this->mTitle,
1791 'minor_edit' => $meta[
'minor'],
1792 'text' => $meta[
'serialized'],
1794 'parent_id' => $oldid,
1795 'user' =>
$user->getId(),
1796 'user_text' =>
$user->getName(),
1797 'timestamp' => $now,
1798 'content_model' =>
$content->getModel(),
1799 'content_format' => $meta[
'serialFormat'],
1802 $changed = !
$content->equals( $oldContent );
1808 $status->merge( $prepStatus );
1813 $dbw->startAtomic( __METHOD__ );
1818 if ( $latestNow != $oldid ) {
1819 $dbw->endAtomic( __METHOD__ );
1821 $status->fatal(
'edit-conflict' );
1832 $revisionId = $revision->insertOn( $dbw );
1834 if ( !$this->
updateRevisionOn( $dbw, $revision,
null, $meta[
'oldIsRedirect'] ) ) {
1835 throw new MWException(
"Failed to update page row to use new revision." );
1839 [ $this, $revision, $meta[
'baseRevId'],
$user ] );
1844 $patrolled = $wgUseRCPatrol && !count(
1845 $this->mTitle->getUserPermissionsErrors(
'autopatrol',
$user ) );
1850 $revision->isMinor(),
1857 $oldContent ? $oldContent->getSize() : 0,
1865 $user->incEditCount();
1867 $dbw->endAtomic( __METHOD__ );
1868 $this->mTimestamp = $now;
1873 $revision->setUserIdAndName(
1881 $status->value[
'revision'] = $revision;
1883 $status->warning(
'edit-no-change' );
1886 $this->mTitle->invalidateCache( $now );
1903 'changed' => $changed,
1904 'oldcountable' => $meta[
'oldCountable'],
1905 'oldrevision' => $meta[
'oldRevision']
1910 null,
null, &
$flags, $revision, &
$status, $meta[
'baseRevId'] ];
1936 global $wgUseRCPatrol, $wgUseNPPatrol;
1943 $status->merge( $prepStatus );
1949 $dbw->startAtomic( __METHOD__ );
1953 if ( $newid ===
false ) {
1954 $dbw->endAtomic( __METHOD__ );
1955 $status->fatal(
'edit-already-exists' );
1968 'title' => $this->mTitle,
1970 'minor_edit' => $meta[
'minor'],
1971 'text' => $meta[
'serialized'],
1973 'user' =>
$user->getId(),
1974 'user_text' =>
$user->getName(),
1975 'timestamp' => $now,
1976 'content_model' =>
$content->getModel(),
1977 'content_format' => $meta[
'serialFormat'],
1981 $revisionId = $revision->
insertOn( $dbw );
1984 throw new MWException(
"Failed to update page row to use new revision." );
1987 Hooks::run(
'NewRevisionFromEditComplete', [ $this, $revision,
false,
$user ] );
1992 $patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) &&
1993 !count( $this->mTitle->getUserPermissionsErrors(
'autopatrol',
$user ) );
1998 $revision->isMinor(),
2010 $user->incEditCount();
2012 $dbw->endAtomic( __METHOD__ );
2013 $this->mTimestamp = $now;
2016 $status->value[
'revision'] = $revision;
2063 if ( $this->
getTitle()->isConversionTable() ) {
2066 $options->disableContentConversion();
2105 $serialFormat =
null, $useCache =
true
2109 if ( is_object( $revision ) ) {
2110 $revid = $revision->getId();
2115 if ( $revid !==
null ) {
2126 if ( $serialFormat ===
null ) {
2127 $serialFormat =
$content->getContentHandler()->getDefaultFormat();
2130 if ( $this->mPreparedEdit
2131 && isset( $this->mPreparedEdit->newContent )
2132 && $this->mPreparedEdit->newContent->equals(
$content )
2133 && $this->mPreparedEdit->revid == $revid
2134 && $this->mPreparedEdit->format == $serialFormat
2142 $cachedEdit = $useCache && $wgAjaxEditStash
2147 Hooks::run(
'ArticlePrepareTextForEdit', [ $this, $popts ] );
2150 if ( $cachedEdit ) {
2151 $edit->timestamp = $cachedEdit->timestamp;
2156 $edit->revid = $revid;
2158 if ( $cachedEdit ) {
2159 $edit->pstContent = $cachedEdit->pstContent;
2162 ?
$content->preSaveTransform( $this->mTitle,
$user, $popts )
2166 $edit->format = $serialFormat;
2168 if ( $cachedEdit ) {
2169 $edit->output = $cachedEdit->output;
2176 $oldCallback = $edit->popts->getCurrentRevisionCallback();
2177 $edit->popts->setCurrentRevisionCallback(
2179 if (
$title->equals( $revision->getTitle() ) ) {
2188 $edit->popts->setSpeculativeRevIdCallback(
function () {
2197 $edit->output = $edit->pstContent
2198 ? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts )
2206 $edit->newText = $edit->newContent
2209 $edit->oldText = $edit->oldContent
2212 $edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialFormat ) :
'';
2214 if ( $edit->output ) {
2219 $this->mPreparedEdit = $edit;
2252 'restored' =>
false,
2253 'oldrevision' =>
null,
2254 'oldcountable' => null
2258 $logger = LoggerFactory::getInstance(
'SaveParse' );
2262 if ( !$this->mPreparedEdit ) {
2263 $logger->debug( __METHOD__ .
": No prepared edit...\n" );
2264 } elseif ( $this->mPreparedEdit->output->getFlag(
'vary-revision' ) ) {
2265 $logger->info( __METHOD__ .
": Prepared edit has vary-revision...\n" );
2266 } elseif ( $this->mPreparedEdit->output->getFlag(
'vary-revision-id' )
2267 && $this->mPreparedEdit->output->getSpeculativeRevIdUsed() !== $revision->
getId()
2269 $logger->info( __METHOD__ .
": Prepared edit has vary-revision-id with wrong ID...\n" );
2270 } elseif ( $this->mPreparedEdit->output->getFlag(
'vary-user' ) && !
$options[
'changed'] ) {
2271 $logger->info( __METHOD__ .
": Prepared edit has vary-user and is null...\n" );
2273 wfDebug( __METHOD__ .
": Using prepared edit...\n" );
2287 $editInfo->output, $this, $editInfo->popts,
2294 $updates =
$content->getSecondaryDataUpdates(
2295 $this->
getTitle(),
null, $recursive, $editInfo->output
2297 foreach ( $updates
as $update ) {
2299 $update->setRevision( $revision );
2300 $update->setTriggeringUser(
$user );
2304 if ( $wgRCWatchCategoryMembership
2315 'pageId' => $this->
getId(),
2324 if (
Hooks::run(
'ArticleEditUpdatesDeleteFromRecentchanges', [ &$this ] ) ) {
2326 if ( mt_rand( 0, 9 ) == 0 ) {
2331 if ( !$this->
exists() ) {
2335 $id = $this->
getId();
2336 $title = $this->mTitle->getPrefixedDBkey();
2337 $shortTitle = $this->mTitle->getDBkey();
2339 if (
$options[
'oldcountable'] ===
'no-change' ||
2345 } elseif (
$options[
'oldcountable'] !==
null ) {
2350 $edits =
$options[
'changed'] ? 1 : 0;
2351 $total =
$options[
'created'] ? 1 : 0;
2361 && $shortTitle !=
$user->getTitleKey()
2362 && !( $revision->
isMinor() &&
$user->isAllowed(
'nominornewtalk' ) )
2365 if ( !$recipient ) {
2366 wfDebug( __METHOD__ .
": invalid username\n" );
2370 if (
Hooks::run(
'ArticleEditUpdateNewTalk', [ &$this, $recipient ] ) ) {
2373 $recipient->setNewtalk(
true, $revision );
2374 } elseif ( $recipient->isLoggedIn() ) {
2375 $recipient->setNewtalk(
true, $revision );
2377 wfDebug( __METHOD__ .
": don't need to notify a nonexistent user\n" );
2386 if ( $msgtext ===
false || $msgtext ===
null ) {
2393 $wgContLang->updateConversionTable( $this->mTitle );
2423 &$cascade, $reason,
User $user, $tags =
null
2432 $restrictionTypes = $this->mTitle->getRestrictionTypes();
2433 $id = $this->
getId();
2444 $isProtected =
false;
2450 foreach ( $restrictionTypes
as $action ) {
2451 if ( !isset( $expiry[$action] ) || $expiry[$action] === $dbw->getInfinity() ) {
2452 $expiry[$action] =
'infinity';
2454 if ( !isset(
$limit[$action] ) ) {
2456 } elseif (
$limit[$action] !=
'' ) {
2461 $current = implode(
'', $this->mTitle->getRestrictions( $action ) );
2462 if ( $current !=
'' ) {
2463 $isProtected =
true;
2466 if (
$limit[$action] != $current ) {
2468 } elseif (
$limit[$action] !=
'' ) {
2472 if ( $this->mTitle->getRestrictionExpiry( $action ) != $expiry[$action] ) {
2478 if ( !$changed && $protect && $this->mTitle->areRestrictionsCascading() != $cascade ) {
2488 $revCommentMsg =
'unprotectedarticle';
2489 $logAction =
'unprotect';
2490 } elseif ( $isProtected ) {
2491 $revCommentMsg =
'modifiedarticleprotection';
2492 $logAction =
'modify';
2494 $revCommentMsg =
'protectedarticle';
2495 $logAction =
'protect';
2501 $logRelationsValues = [];
2502 $logRelationsField =
null;
2503 $logParamsDetails = [];
2506 $nullRevision =
null;
2514 $editrestriction = isset(
$limit[
'edit'] )
2516 : $this->mTitle->getRestrictions(
'edit' );
2517 foreach ( array_keys( $editrestriction,
'sysop' )
as $key ) {
2518 $editrestriction[$key] =
'editprotected';
2520 foreach ( array_keys( $editrestriction,
'autoconfirmed' )
as $key ) {
2521 $editrestriction[$key] =
'editsemiprotected';
2524 $cascadingRestrictionLevels = $wgCascadingRestrictionLevels;
2525 foreach ( array_keys( $cascadingRestrictionLevels,
'sysop' )
as $key ) {
2526 $cascadingRestrictionLevels[$key] =
'editprotected';
2528 foreach ( array_keys( $cascadingRestrictionLevels,
'autoconfirmed' )
as $key ) {
2529 $cascadingRestrictionLevels[$key] =
'editsemiprotected';
2533 if ( !array_intersect( $editrestriction, $cascadingRestrictionLevels ) ) {
2548 if ( $nullRevision ===
null ) {
2549 return Status::newFatal(
'no-null-revision', $this->mTitle->getPrefixedText() );
2552 $logRelationsField =
'pr_id';
2555 foreach (
$limit as $action => $restrictions ) {
2557 'page_restrictions',
2560 'pr_type' => $action
2564 if ( $restrictions !=
'' ) {
2565 $cascadeValue = ( $cascade && $action ==
'edit' ) ? 1 : 0;
2567 'page_restrictions',
2569 'pr_id' => $dbw->nextSequenceValue(
'page_restrictions_pr_id_seq' ),
2571 'pr_type' => $action,
2572 'pr_level' => $restrictions,
2573 'pr_cascade' => $cascadeValue,
2574 'pr_expiry' => $dbw->encodeExpiry( $expiry[$action] )
2578 $logRelationsValues[] = $dbw->insertId();
2579 $logParamsDetails[] = [
2581 'level' => $restrictions,
2582 'expiry' => $expiry[$action],
2583 'cascade' => (bool)$cascadeValue,
2591 [
'page_restrictions' =>
'' ],
2592 [
'page_id' => $id ],
2597 [ $this, $nullRevision, $latest,
$user ] );
2603 if (
$limit[
'create'] !=
'' ) {
2604 $dbw->replace(
'protected_titles',
2605 [ [
'pt_namespace',
'pt_title' ] ],
2607 'pt_namespace' => $this->mTitle->getNamespace(),
2608 'pt_title' => $this->mTitle->getDBkey(),
2609 'pt_create_perm' =>
$limit[
'create'],
2610 'pt_timestamp' => $dbw->timestamp(),
2611 'pt_expiry' => $dbw->encodeExpiry( $expiry[
'create'] ),
2612 'pt_user' =>
$user->getId(),
2613 'pt_reason' => $reason,
2616 $logParamsDetails[] = [
2618 'level' =>
$limit[
'create'],
2619 'expiry' => $expiry[
'create'],
2622 $dbw->delete(
'protected_titles',
2624 'pt_namespace' => $this->mTitle->getNamespace(),
2625 'pt_title' => $this->mTitle->getDBkey()
2631 $this->mTitle->flushRestrictions();
2634 if ( $logAction ==
'unprotect' ) {
2639 '4::description' => $protectDescriptionLog,
2640 '5:bool:cascade' => $cascade,
2641 'details' => $logParamsDetails,
2647 $logEntry->setTarget( $this->mTitle );
2648 $logEntry->setComment( $reason );
2649 $logEntry->setPerformer(
$user );
2650 $logEntry->setParameters(
$params );
2651 if ( !is_null( $nullRevision ) ) {
2652 $logEntry->setAssociatedRevId( $nullRevision->getId() );
2654 $logEntry->setTags( $tags );
2655 if ( $logRelationsField !==
null && count( $logRelationsValues ) ) {
2656 $logEntry->setRelations( [ $logRelationsField => $logRelationsValues ] );
2658 $logId = $logEntry->insert();
2659 $logEntry->publish( $logId );
2676 array $expiry, $cascade, $reason,
$user =
null
2685 $this->mTitle->getPrefixedText()
2686 )->inContentLanguage()->text()
2689 $editComment .=
wfMessage(
'colon-separator' )->inContentLanguage()->text() . $reason;
2692 if ( $protectDescription ) {
2693 $editComment .=
wfMessage(
'word-separator' )->inContentLanguage()->text();
2694 $editComment .=
wfMessage(
'parentheses' )->params( $protectDescription )
2695 ->inContentLanguage()->text();
2698 $editComment .=
wfMessage(
'word-separator' )->inContentLanguage()->text();
2699 $editComment .=
wfMessage(
'brackets' )->params(
2700 wfMessage(
'protect-summary-cascade' )->inContentLanguage()->
text()
2701 )->inContentLanguage()->text();
2706 $nullRev->insertOn( $dbw );
2709 $oldLatest = $nullRev->getParentId();
2723 if ( $expiry !=
'infinity' ) {
2726 $wgContLang->timeanddate( $expiry,
false,
false ),
2729 )->inContentLanguage()->text();
2731 return wfMessage(
'protect-expiry-indefinite' )
2732 ->inContentLanguage()->text();
2744 $protectDescription =
'';
2746 foreach ( array_filter(
$limit )
as $action => $restrictions ) {
2747 # $action is one of $wgRestrictionTypes = [ 'create', 'edit', 'move', 'upload' ].
2748 # All possible message keys are listed here for easier grepping:
2749 # * restriction-create
2750 # * restriction-edit
2751 # * restriction-move
2752 # * restriction-upload
2753 $actionText =
wfMessage(
'restriction-' . $action )->inContentLanguage()->text();
2754 # $restrictions is one of $wgRestrictionLevels = [ '', 'autoconfirmed', 'sysop' ],
2755 # with '' filtered out. All possible message keys are listed below:
2756 # * protect-level-autoconfirmed
2757 # * protect-level-sysop
2758 $restrictionsText =
wfMessage(
'protect-level-' . $restrictions )
2759 ->inContentLanguage()->text();
2763 if ( $protectDescription !==
'' ) {
2764 $protectDescription .=
wfMessage(
'word-separator' )->inContentLanguage()->text();
2766 $protectDescription .=
wfMessage(
'protect-summary-desc' )
2767 ->params( $actionText, $restrictionsText, $expiryText )
2768 ->inContentLanguage()->text();
2771 return $protectDescription;
2788 $protectDescriptionLog =
'';
2790 foreach ( array_filter(
$limit )
as $action => $restrictions ) {
2792 $protectDescriptionLog .=
$wgContLang->getDirMark() .
2793 "[$action=$restrictions] ($expiryText)";
2796 return trim( $protectDescriptionLog );
2809 if ( !is_array(
$limit ) ) {
2810 throw new MWException( __METHOD__ .
' given non-array restriction set' );
2816 foreach ( array_filter(
$limit )
as $action => $restrictions ) {
2817 $bits[] =
"$action=$restrictions";
2820 return implode(
':', $bits );
2840 $reason, $suppress =
false, $u1 =
null, $u2 =
null, &$error =
'',
User $user =
null
2865 $reason, $suppress =
false, $u1 =
null, $u2 =
null, &$error =
'',
User $user =
null,
2866 $tags = [], $logsubtype =
'delete'
2874 if ( $this->mTitle->getDBkey() ===
'' ) {
2875 $status->error(
'cannotdelete',
2882 [ &$this, &
$user, &$reason, &$error, &
$status, $suppress ]
2886 $status->fatal(
'delete-hook-aborted' );
2892 $dbw->startAtomic( __METHOD__ );
2895 $id = $this->
getId();
2901 if ( $id == 0 || $this->
getLatest() != $lockedLatest ) {
2902 $dbw->endAtomic( __METHOD__ );
2904 $status->error(
'cannotdelete',
2910 $namespace = $this->
getTitle()->getNamespace();
2911 $dbKey = $this->
getTitle()->getDBkey();
2922 }
catch ( Exception $ex ) {
2923 wfLogWarning( __METHOD__ .
': failed to load content during deletion! '
2924 . $ex->getMessage() );
2937 $deletionFields = [ $dbw->addQuotes( $bitfield ) .
' AS deleted' ];
2939 $deletionFields = [
'rev_deleted AS deleted' ];
2951 $res = $dbw->select(
2953 array_merge( $fields, $deletionFields ),
2954 [
'rev_page' => $id ],
2960 foreach (
$res as $row ) {
2962 'ar_namespace' => $namespace,
2963 'ar_title' => $dbKey,
2964 'ar_comment' => $row->rev_comment,
2965 'ar_user' => $row->rev_user,
2966 'ar_user_text' => $row->rev_user_text,
2967 'ar_timestamp' => $row->rev_timestamp,
2968 'ar_minor_edit' => $row->rev_minor_edit,
2969 'ar_rev_id' => $row->rev_id,
2970 'ar_parent_id' => $row->rev_parent_id,
2971 'ar_text_id' => $row->rev_text_id,
2974 'ar_len' => $row->rev_len,
2975 'ar_page_id' => $id,
2976 'ar_deleted' => $row->deleted,
2977 'ar_sha1' => $row->rev_sha1,
2979 if ( $wgContentHandlerUseDB ) {
2980 $rowInsert[
'ar_content_model'] = $row->rev_content_model;
2981 $rowInsert[
'ar_content_format'] = $row->rev_content_format;
2983 $rowsInsert[] = $rowInsert;
2986 $dbw->insert(
'archive', $rowsInsert, __METHOD__ );
2988 $archivedRevisionCount = $dbw->affectedRows();
2993 $wikiPageBeforeDelete = clone $this;
2996 $dbw->delete(
'page', [
'page_id' => $id ], __METHOD__ );
2997 $dbw->delete(
'revision', [
'rev_page' => $id ], __METHOD__ );
3000 $logtype = $suppress ?
'suppress' :
'delete';
3003 $logEntry->setPerformer(
$user );
3004 $logEntry->setTarget( $logTitle );
3005 $logEntry->setComment( $reason );
3006 $logEntry->setTags( $tags );
3007 $logid = $logEntry->insert();
3009 $dbw->onTransactionPreCommitOrIdle(
3010 function ()
use ( $dbw, $logEntry, $logid ) {
3012 $logEntry->publish( $logid );
3017 $dbw->endAtomic( __METHOD__ );
3022 &$wikiPageBeforeDelete,
3028 $archivedRevisionCount
3034 $key =
wfMemcKey(
'page-recent-delete', md5( $logTitle->getPrefixedText() ) );
3035 $cache->set( $key, 1, $cache::TTL_DAY );
3051 'page_id' => $this->
getId(),
3054 'page_namespace' => $this->
getTitle()->getNamespace(),
3055 'page_title' => $this->
getTitle()->getDBkey()
3074 }
catch ( Exception $ex ) {
3085 foreach ( $updates
as $update ) {
3093 if ( $this->mTitle->getNamespace() ==
NS_FILE ) {
3100 $this->mTitle, $revision,
null,
wfWikiID()
3142 $resultDetails =
null;
3145 $editErrors = $this->mTitle->getUserPermissionsErrors(
'edit',
$user );
3146 $rollbackErrors = $this->mTitle->getUserPermissionsErrors(
'rollback',
$user );
3147 $errors = array_merge( $editErrors,
wfArrayDiff2( $rollbackErrors, $editErrors ) );
3149 if ( !
$user->matchEditToken( $token,
'rollback' ) ) {
3150 $errors[] = [
'sessionfailure' ];
3153 if (
$user->pingLimiter(
'rollback' ) ||
$user->pingLimiter() ) {
3154 $errors[] = [
'actionthrottledtext' ];
3158 if ( !empty( $errors ) ) {
3186 &$resultDetails,
User $guser, $tags =
null
3193 return [ [
'readonlytext' ] ];
3198 if ( is_null( $current ) ) {
3200 return [ [
'notanarticle' ] ];
3203 $from = str_replace(
'_',
' ', $fromP );
3206 if (
$from != $current->getUserText() ) {
3207 $resultDetails = [
'current' => $current ];
3208 return [ [
'alreadyrolled',
3209 htmlspecialchars( $this->mTitle->getPrefixedText() ),
3210 htmlspecialchars( $fromP ),
3211 htmlspecialchars( $current->getUserText() )
3218 $user_text = $dbw->addQuotes( $current->getUserText(
Revision::RAW ) );
3219 $s = $dbw->selectRow(
'revision',
3220 [
'rev_id',
'rev_timestamp',
'rev_deleted' ],
3221 [
'rev_page' => $current->getPage(),
3222 "rev_user != {$user} OR rev_user_text != {$user_text}"
3224 [
'USE INDEX' =>
'page_timestamp',
3225 'ORDER BY' =>
'rev_timestamp DESC' ]
3227 if (
$s ===
false ) {
3229 return [ [
'cantrollback' ] ];
3234 return [ [
'notvisiblerev' ] ];
3240 if (
$from ==
'' ) {
3249 $target->getUserText(),
$from,
$s->rev_id,
3251 $current->getId(),
$wgContLang->timeanddate( $current->getTimestamp() )
3253 if (
$summary instanceof Message ) {
3268 if ( $guser->
isAllowed(
'minoredit' ) ) {
3272 if ( $bot && ( $guser->
isAllowedAny(
'markbotedits',
'bot' ) ) ) {
3276 $targetContent = $target->getContent();
3277 $changingContentModel = $targetContent->getModel() !== $current->getContentModel();
3293 if ( $bot && $guser->
isAllowed(
'markbotedits' ) ) {
3298 if ( $wgUseRCPatrol ) {
3300 $set[
'rc_patrolled'] = 1;
3303 if ( count( $set ) ) {
3304 $dbw->update(
'recentchanges', $set,
3306 'rc_cur_id' => $current->getPage(),
3307 'rc_user_text' => $current->getUserText(),
3308 'rc_timestamp > ' . $dbw->addQuotes(
$s->rev_timestamp ),
3315 return $status->getErrorsArray();
3319 $statusRev = isset(
$status->value[
'revision'] )
3322 if ( !( $statusRev instanceof
Revision ) ) {
3323 $resultDetails = [
'current' => $current ];
3324 return [ [
'alreadyrolled',
3325 htmlspecialchars( $this->mTitle->getPrefixedText() ),
3326 htmlspecialchars( $fromP ),
3327 htmlspecialchars( $current->getUserText() )
3331 if ( $changingContentModel ) {
3335 $log->setPerformer( $guser );
3336 $log->setTarget( $this->mTitle );
3338 $log->setParameters( [
3339 '4::oldmodel' => $current->getContentModel(),
3340 '5::newmodel' => $targetContent->getModel(),
3343 $logId = $log->insert( $dbw );
3344 $log->publish( $logId );
3347 $revId = $statusRev->getId();
3349 Hooks::run(
'ArticleRollbackComplete', [ $this, $guser, $target, $current ] );
3353 'current' => $current,
3354 'target' => $target,
3374 $other =
$title->getOtherPage();
3376 $other->purgeSquid();
3380 $title->deleteTitleProtection();
3382 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle(
$title );
3402 $other =
$title->getOtherPage();
3404 $other->purgeSquid();
3409 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle(
$title );
3433 $user->setNewtalk(
false );
3454 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle(
$title );
3461 $revid = $revision ? $revision->getId() :
null;
3476 $id = $this->
getId();
3482 $res =
$dbr->select(
'categorylinks',
3483 [
'cl_to AS page_title, ' .
NS_CATEGORY .
' AS page_namespace' ],
3486 [
'cl_from' => $id ],
3500 $id = $this->
getId();
3507 $res =
$dbr->select( [
'categorylinks',
'page_props',
'page' ],
3509 [
'cl_from' => $id,
'pp_page=page_id',
'pp_propname' =>
'hiddencat',
3510 'page_namespace' =>
NS_CATEGORY,
'page_title=cl_to' ],
3513 if (
$res !==
false ) {
3514 foreach (
$res as $row ) {
3538 $oldContent = is_null( $oldtext ) ? null :
$handler->unserializeContent( $oldtext );
3539 $newContent = is_null( $newtext ) ? null :
$handler->unserializeContent( $newtext );
3541 return $handler->getAutosummary( $oldContent, $newContent,
$flags );
3564 $id = $id ?: $this->
getId();
3566 $method = __METHOD__;
3568 $dbw->onTransactionPreCommitOrIdle(
3569 function ()
use ( $dbw, $added, $deleted, $id, $method ) {
3570 $ns = $this->
getTitle()->getNamespace();
3572 $addFields = [
'cat_pages = cat_pages + 1' ];
3573 $removeFields = [
'cat_pages = cat_pages - 1' ];
3575 $addFields[] =
'cat_subcats = cat_subcats + 1';
3576 $removeFields[] =
'cat_subcats = cat_subcats - 1';
3578 $addFields[] =
'cat_files = cat_files + 1';
3579 $removeFields[] =
'cat_files = cat_files - 1';
3582 if ( count( $added ) ) {
3583 $existingAdded = $dbw->selectFieldValues(
3586 [
'cat_title' => $added ],
3593 if ( count( $existingAdded ) ) {
3597 [
'cat_title' => $existingAdded ],
3602 $missingAdded = array_diff( $added, $existingAdded );
3603 if ( count( $missingAdded ) ) {
3605 foreach ( $missingAdded
as $cat ) {
3607 'cat_title' => $cat,
3610 'cat_files' => ( $ns ==
NS_FILE ) ? 1 : 0,
3623 if ( count( $deleted ) ) {
3627 [
'cat_title' => $deleted ],
3632 foreach ( $added
as $catName ) {
3634 Hooks::run(
'CategoryAfterPageAdded', [ $cat, $this ] );
3637 foreach ( $deleted
as $catName ) {
3639 Hooks::run(
'CategoryAfterPageRemoved', [ $cat, $this, $id ] );
3645 if ( count( $deleted ) ) {
3646 $rows = $dbw->select(
3648 [
'cat_id',
'cat_title',
'cat_pages',
'cat_subcats',
'cat_files' ],
3649 [
'cat_title' => $deleted,
'cat_pages <= 0' ],
3652 foreach ( $rows
as $row ) {
3654 $cat->refreshCounts();
3673 if ( !
Hooks::run(
'OpportunisticLinksUpdate',
3682 'isOpportunistic' =>
true,
3686 if ( $this->mTitle->areRestrictionsCascading() ) {
3691 } elseif ( !$config->get(
'MiserMode' ) &&
$parserOutput->hasDynamicContent() ) {
3702 $key =
$cache->makeKey(
'dynamic-linksupdate',
'last', $this->
getId() );
3704 if (
$cache->add( $key, time(), $ttl ) ) {
3728 }
catch ( Exception $ex ) {
3739 $updates =
$content->getDeletionUpdates( $this );