<?php
/**
* File for articles
+ * @file
*/
/**
var $mUser; //!<
var $mUserText; //!<
var $mRedirectTarget; //!<
+ var $mIsRedirect;
/**@}}*/
/**
return $this->mRedirectTarget;
# Query the redirect table
- $dbr = wfGetDb(DB_SLAVE);
+ $dbr = wfGetDB(DB_SLAVE);
$res = $dbr->select('redirect',
array('rd_namespace', 'rd_title'),
array('rd_from' => $this->getID()),
$retval = Title::newFromRedirect($this->getContent());
if(!$retval)
return null;
- $dbw = wfGetDb(DB_MASTER);
- $dbw->insert('redirect', array(
+ $dbw = wfGetDB(DB_MASTER);
+ $dbw->replace('redirect', array('rd_from'), array(
'rd_from' => $this->getID(),
'rd_namespace' => $retval->getNamespace(),
'rd_title' => $retval->getDBKey()
- ));
+ ), __METHOD__);
return $retval;
}
/**
+ * Get the Title object this page redirects to
+ *
* @return mixed false, Title of in-wiki target, or string with URL
*/
function followRedirect() {
- $rt = $this->getRedirectTarget();
+ $text = $this->getContent();
+ $rt = Title::newFromRedirect( $text );
# process if title object is valid and not special:userlogout
if( $rt ) {
# fails we'll have something telling us what we intended.
$t = $this->mTitle->getPrefixedText();
if( $oldid ) {
- $t .= ',oldid='.$oldid;
+ $t .= ' ' . wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid );
}
$this->mContent = wfMsg( 'missingarticle', $t ) ;
*/
function isRedirect( $text = false ) {
if ( $text === false ) {
+ if ( $this->mDataLoaded )
+ return $this->mIsRedirect;
+
+ // Apparently loadPageData was never called
$this->loadContent();
$titleObj = Title::newFromRedirect( $this->fetchContent() );
} else {
* This is the default action of the script: just view the page of
* the given title.
*/
- function view() {
+ function view() {
global $wgUser, $wgOut, $wgRequest, $wgContLang;
global $wgEnableParserCache, $wgStylePath, $wgParser;
global $wgUseTrackbacks, $wgNamespaceRobotPolicies, $wgArticleRobotPolicies;
# Failed to load, replace text with error message
$t = $this->mTitle->getPrefixedText();
if( $oldid ) {
- $t .= ',oldid='.$oldid;
+ $t .= ' ' . wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid );
$text = wfMsg( 'missingarticle', $t );
} else {
- $text = wfMsg( 'noarticletext', $t );
+ $text = wfMsg( 'noarticletext' );
}
}
if ( !$this->mTitle->userCanRead() ) {
$wgOut->loginToUse();
$wgOut->output();
+ wfProfileOut( __METHOD__ );
exit;
}
if( !$this->mRevision->userCan( Revision::DELETED_TEXT ) ) {
$wgOut->addWikiMsg( 'rev-deleted-text-permission' );
$wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
+ wfProfileOut( __METHOD__ );
return;
} else {
$wgOut->addWikiMsg( 'rev-deleted-text-view' );
}
if( !$outputDone ) {
$wgOut->setRevisionId( $this->getRevIdFetched() );
-
+
// Pages containing custom CSS or JavaScript get special treatment
if( $this->mTitle->isCssOrJsPage() || $this->mTitle->isCssJsSubpage() ) {
$wgOut->addHtml( wfMsgExt( 'clearyourcache', 'parse' ) );
$wgOut->addHtml( htmlspecialchars( $this->mContent ) );
$wgOut->addHtml( "\n</pre>\n" );
}
-
- }
-
- elseif ( $rt = $this->getRedirectTarget() ) {
- # Display redirect
- $imageDir = $wgContLang->isRTL() ? 'rtl' : 'ltr';
- $imageUrl = $wgStylePath.'/common/images/redirect' . $imageDir . '.png';
- # Don't overwrite the subtitle if this was an old revision
- if( !$wasRedirected && $this->isCurrent() ) {
- $wgOut->setSubtitle( wfMsgHtml( 'redirectpagesub' ) );
- }
- $link = $sk->makeLinkObj( $rt, $rt->getFullText() );
- $wgOut->addHTML( '<img src="'.$imageUrl.'" alt="#REDIRECT " />' .
- '<span class="redirectText">'.$link.'</span>' );
+ }
+ elseif ( $rt = Title::newFromRedirect( $text ) ) {
+ # Don't overwrite the subtitle if this was an old revision
+ $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() );
$parseout = $wgParser->parse($text, $this->mTitle, ParserOptions::newFromUser($wgUser));
$wgOut->addParserOutputNoText( $parseout );
} else if ( $pcache ) {
# check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
if( $ns == NS_USER_TALK &&
- User::isIP( $this->mTitle->getText() ) ) {
+ IP::isValid( $this->mTitle->getText() ) ) {
$wgOut->addWikiMsg('anontalkpagetext');
}
$this->viewUpdates();
wfProfileOut( __METHOD__ );
}
+
+ protected function viewRedirect( $target, $overwriteSubtitle = true, $forceKnown = false ) {
+ global $wgParser, $wgOut, $wgContLang, $wgStylePath, $wgUser;
+
+ # Display redirect
+ $imageDir = $wgContLang->isRTL() ? 'rtl' : 'ltr';
+ $imageUrl = $wgStylePath.'/common/images/redirect' . $imageDir . '.png';
+
+ if( $overwriteSubtitle ) {
+ $wgOut->setSubtitle( wfMsgHtml( 'redirectpagesub' ) );
+ }
+ $sk = $wgUser->getSkin();
+ if ( $forceKnown )
+ $link = $sk->makeKnownLinkObj( $target, htmlspecialchars( $target->getFullText() ) );
+ else
+ $link = $sk->makeLinkObj( $target, htmlspecialchars( $target->getFullText() ) );
+
+ $wgOut->addHTML( '<img src="'.$imageUrl.'" alt="#REDIRECT " />' .
+ '<span class="redirectText">'.$link.'</span>' );
+
+ }
function addTrackbacks() {
global $wgOut, $wgUser;
$dbw->delete( 'redirect', $where, __METHOD__);
}
+ if( $this->getTitle()->getNamespace() == NS_IMAGE )
+ RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() );
wfProfileOut( __METHOD__ );
return ( $dbw->affectedRows() != 0 );
}
* EDIT_NEW is specified and the article does exist, a duplicate key error will cause an exception
* to be thrown from the Database. These two conditions are also possible with auto-detection due
* to MediaWiki's performance-optimised locking strategy.
+ * @param $baseRevId, the revision ID this edit was based off, if any
*
* @return bool success
*/
- function doEdit( $text, $summary, $flags = 0 ) {
+ function doEdit( $text, $summary, $flags = 0, $baseRevId = false ) {
global $wgUser, $wgDBtransactions;
wfProfileIn( __METHOD__ );
$lastRevision = 0;
$revisionId = 0;
-
+
$changed = ( strcmp( $text, $oldtext ) != 0 );
if ( $changed ) {
# Update page
$ok = $this->updateRevisionOn( $dbw, $revision, $lastRevision );
+
+ wfRunHooks( 'NewRevisionFromEditComplete', array($this, $revision, $baseRevId) );
if( !$ok ) {
/* Belated edit conflict! Run away!! */
# Invalidate cache of this article and all pages using this article
# as a template. Partly deferred.
Article::onArticleEdit( $this->mTitle );
-
+
# Update links tables, site stats, etc.
$this->editUpdates( $text, $summary, $isminor, $now, $revisionId, $changed );
}
# Update the page record with revision data
$this->updateRevisionOn( $dbw, $revision, 0 );
+
+ wfRunHooks( 'NewRevisionFromEditComplete', array($this, $revision, false) );
if( !( $flags & EDIT_SUPPRESS_RC ) ) {
$rcid = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $wgUser, $summary, $bot,
if ( !($wgUseNPPatrol || $wgUseRCPatrol)) {
$wgOut->showErrorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' );
- return;
+ return;
}
# If we haven't been given an rc_id value, we can't do anything
$wgOut->showErrorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' );
return;
}
-
+
# Check permissions
$permission_errors = $this->mTitle->getUserPermissionsErrors( 'patrol', $wgUser );
$updated = Article::flattenRestrictions( $limit );
$changed = ( $current != $updated );
- $changed = $changed || ($this->mTitle->areRestrictionsCascading() != $cascade);
- $changed = $changed || ($this->mTitle->mRestrictionsExpiry != $expiry);
+ $changed = $changed || ($updated && $this->mTitle->areRestrictionsCascading() != $cascade);
+ $changed = $changed || ($updated && $this->mTitle->mRestrictionsExpiry != $expiry);
$protect = ( $updated != '' );
# If nothing's changed, do nothing
foreach( $limit as $action => $restrictions ) {
# Check if the group level required to edit also can protect pages
# Otherwise, people who cannot normally protect can "protect" pages via transclusion
- $cascade = ( $cascade && isset($wgGroupPermissions[$restrictions]['protect']) &&
- $wgGroupPermissions[$restrictions]['protect'] );
+ $cascade = ( $cascade && isset($wgGroupPermissions[$restrictions]['protect']) &&
+ $wgGroupPermissions[$restrictions]['protect'] );
}
-
+
$cascade_description = '';
if ($cascade) {
$cascade_description = ' ['.wfMsg('protect-summary-cascade').']';
$comment .= "$expiry_description";
if ( $cascade )
$comment .= "$cascade_description";
-
+
# Update restrictions table
foreach( $limit as $action => $restrictions ) {
if ($restrictions != '' ) {
'page_id' => $id
), 'Article::protect'
);
+
+ wfRunHooks( 'NewRevisionFromEditComplete', array($this, $nullRevision, false) );
wfRunHooks( 'ArticleProtectComplete', array( &$this, &$wgUser, $limit, $reason ) );
# Update the protection log
$log = new LogPage( 'protect' );
-
-
-
if( $protect ) {
$log->addEntry( $modified ? 'modify' : 'protect', $this->mTitle, trim( $reason . " [$updated]$cascade_description$expiry_description" ) );
} else {
}
return implode( ':', $bits );
}
-
+
/**
* Auto-generates a deletion reason
* @param bool &$hasHistory Whether the page has a history
else
$reason = wfMsgForContent('excontent', '$1');
}
-
+
// Replace newlines with spaces to prevent uglyness
$contents = preg_replace("/[\n\r]/", ' ', $contents);
// Calculate the maximum amount of chars to get
$confirm = $wgRequest->wasPosted() &&
$wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
-
+
$this->DeleteReasonList = $wgRequest->getText( 'wpDeleteReasonList', 'other' );
$this->DeleteReason = $wgRequest->getText( 'wpReason' );
-
+
$reason = $this->DeleteReasonList;
-
+
if ( $reason != 'other' && $this->DeleteReason != '') {
// Entry from drop down menu + additional comment
$reason .= ': ' . $this->DeleteReason;
$reason = $this->DeleteReason;
}
# Flag to hide all contents of the archived revisions
- $suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed('deleterevision');
+ $suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed('hiderevision');
# This code desperately needs to be totally rewritten
$wgOut->readOnlyPage();
return;
}
-
+
# Check permissions
$permission_errors = $this->mTitle->getUserPermissionsErrors( 'delete', $wgUser );
array( 'delete-warning-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
}
}
-
+
return $this->confirmDelete( '', $reason );
}
-
+
/**
* @return bool whether or not the page surpasses $wgDeleteRevisionsLimit revisions
*/
}
return false;
}
-
+
/**
* @return int approximate revision count
*/
$wgOut->setRobotpolicy( 'noindex,nofollow' );
$wgOut->addWikiMsg( 'confirmdeletetext' );
- if( $wgUser->isAllowed( 'deleterevision' ) ) {
+ if( $wgUser->isAllowed( 'hiderevision' ) ) {
$suppress = "<tr id=\"wpDeleteSuppressRow\" name=\"wpDeleteSuppressRow\"><td></td><td>";
$suppress .= Xml::checkLabel( wfMsg( 'revdelete-suppress' ), 'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '2' ) );
$suppress .= "</td></tr>";
"</td>
<td>" .
Xml::listDropDown( 'wpDeleteReasonList',
- wfMsgForContent( 'deletereason-dropdown' ),
+ wfMsgForContent( 'deletereason-dropdown' ),
wfMsgForContent( 'deletereasonotherlist' ), '', 'wpReasonDropDown', 1 ) .
"</td>
</tr>
function doDelete( $reason, $suppress = false ) {
global $wgOut, $wgUser;
wfDebug( __METHOD__."\n" );
+
+ $id = $this->getId();
if (wfRunHooks('ArticleDelete', array(&$this, &$wgUser, &$reason))) {
if ( $this->doDeleteArticle( $reason, $suppress ) ) {
$wgOut->addWikiMsg( 'deletedtext', $deleted, $loglink );
$wgOut->returnToMain( false );
- wfRunHooks('ArticleDeleteComplete', array(&$this, &$wgUser, $reason));
+ wfRunHooks('ArticleDeleteComplete', array(&$this, &$wgUser, $reason, $id));
} else {
- $wgOut->showFatalError( wfMsg( 'cannotdelete' ).'<br/>'.wfMsg('cannotdelete-merge') );
+ $wgOut->showFatalError( wfMsg( 'cannotdelete' ) );
}
}
}
} else {
$bitfield = 'rev_deleted';
}
-
+
+ $dbw->begin();
// For now, shunt the revision data into the archive table.
// Text is *not* removed from the text table; bulk storage
// is left intact to avoid breaking block-compression or
# Now that it's safely backed up, delete it
$dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__);
+ $ok = ( $dbw->affectedRows() > 0 ); // getArticleId() uses slave, could be laggy
+ if( !$ok ) {
+ $dbw->rollback();
+ return false;
+ }
# If using cascading deletes, we can skip some explicit deletes
if ( !$dbw->cascadingDeletes() ) {
if ( !$dbw->cleanupTriggers() ) {
# Clean up recentchanges entries...
- $dbw->delete( 'recentchanges', array( 'rc_namespace' => $ns, 'rc_title' => $t ), __METHOD__ );
+ $dbw->delete( 'recentchanges',
+ array( 'rc_namespace' => $ns, 'rc_title' => $t, 'rc_type != '.RC_LOG ),
+ __METHOD__ );
}
+ $dbw->commit();
# Clear caches
Article::onArticleDelete( $this->mTitle );
# Log the deletion, if the page was suppressed, log it at Oversight instead
$logtype = $suppress ? 'suppress' : 'delete';
$log = new LogPage( $logtype );
- $log->addEntry( 'delete', $this->mTitle, $reason );
-
+
+ # Make sure logging got through
+ $log->addEntry( 'delete', $this->mTitle, $reason, array() );
+
return true;
}
* performs permissions checks on $wgUser, then calls commitRollback()
* to do the dirty work
*
- * @param string $fromP - Name of the user whose edits to rollback.
+ * @param string $fromP - Name of the user whose edits to rollback.
* @param string $summary - Custom summary. Set to default summary if empty.
* @param string $token - Rollback token.
* @param bool $bot - If true, mark all reverted edits as bot.
- *
+ *
* @param array $resultDetails contains result-specific array of additional values
* 'alreadyrolled' : 'current' (rev)
* success : 'summary' (str), 'current' (rev), 'target' (rev)
- *
+ *
* @return array of errors, each error formatted as
* array(messagekey, param1, param2, ...).
* On success, the array is empty. This array can also be passed to
# If there were errors, bail out now
if(!empty($errors))
return $errors;
-
+
return $this->commitRollback($fromP, $summary, $bot, $resultDetails);
}
-
+
/**
* Backend implementation of doRollback(), please refer there for parameter
* and return value documentation
* rollback to the DB Therefore, you should only call this function direct-
* ly if you want to use custom permissions checks. If you don't, use
* doRollback() instead.
- */
+ */
public function commitRollback($fromP, $summary, $bot, &$resultDetails) {
- global $wgUseRCPatrol, $wgUser;
+ global $wgUseRCPatrol, $wgUser, $wgLang;
$dbw = wfGetDB( DB_MASTER );
if( wfReadOnly() ) {
# Only admins can see this text
return array(array('notvisiblerev'));
}
-
+
$set = array();
if ( $bot && $wgUser->isAllowed('markbotedits') ) {
# Mark all reverted edits as bot
# Generate the edit summary if necessary
$target = Revision::newFromId( $s->rev_id );
- if( empty( $summary ) )
- {
- global $wgLang;
- $summary = wfMsgForContent( 'revertpage',
- $target->getUserText(), $from,
- $s->rev_id, $wgLang->timeanddate(wfTimestamp(TS_MW, $s->rev_timestamp), true),
- $current->getId(), $wgLang->timeanddate($current->getTimestamp())
- );
+ if( empty( $summary ) ){
+ $summary = wfMsgForContent( 'revertpage' );
}
+
+ # Allow the custom summary to use the same args as the default message
+ $args = array(
+ $target->getUserText(), $from, $s->rev_id,
+ $wgLang->timeanddate(wfTimestamp(TS_MW, $s->rev_timestamp), true),
+ $current->getId(), $wgLang->timeanddate($current->getTimestamp())
+ );
+ $summary = wfMsgReplaceArgs( $summary, $args );
# Save
$flags = EDIT_UPDATE;
if( $bot && ($wgUser->isAllowed('markbotedits') || $wgUser->isAllowed('bot')) )
$flags |= EDIT_FORCE_BOT;
- $this->doEdit( $target->getText(), $summary, $flags );
+ $this->doEdit( $target->getText(), $summary, $flags, $target->getId() );
wfRunHooks( 'ArticleRollbackComplete', array( $this, $wgUser, $target ) );
* @private
*/
function viewUpdates() {
- global $wgDeferredUpdateList;
+ global $wgDeferredUpdateList, $wgUser;
if ( 0 != $this->getID() ) {
+ # Don't update page view counters on views from bot users (bug 14044)
global $wgDisableCounters;
- if( !$wgDisableCounters ) {
+ if( !$wgDisableCounters && !$wgUser->isAllowed( 'bot' ) ) {
Article::incViewCount( $this->getID() );
$u = new SiteStatsUpdate( 1, 0, 0 );
array_push( $wgDeferredUpdateList, $u );
}
# Update newtalk / watchlist notification status
- global $wgUser;
$wgUser->clearNotification( $this->mTitle );
}
: $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'diff' ), 'diff=next&oldid='.$oldid );
$cdel='';
- if( $wgUser->isAllowed( 'deleterevision' ) ) {
+ if( $wgUser->isAllowed( 'deleterevision' ) ) {
$revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
if( $revision->isCurrent() ) {
// We don't handle top deleted edits too well
- $cdel = wfMsgHtml('rev-delundel');
+ $cdel = wfMsgHtml('rev-delundel');
} else if( !$revision->userCan( Revision::DELETED_RESTRICTED ) ) {
// If revision was hidden from sysops
- $cdel = wfMsgHtml('rev-delundel');
+ $cdel = wfMsgHtml('rev-delundel');
} else {
$cdel = $sk->makeKnownLinkObj( $revdel,
wfMsgHtml('rev-delundel'),
$infomsg = $current && !wfEmptyMsg( 'revision-info-current', $m ) && $m != '-'
? 'revision-info-current'
: 'revision-info';
-
+
$r = "\n\t\t\t\t<div id=\"mw-{$infomsg}\">" . wfMsg( $infomsg, $td, $userlinks ) . "</div>\n" .
"\n\t\t\t\t<div id=\"mw-revision-nav\">" . $cdel . wfMsg( 'revision-nav', $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t";
$printable = $wgRequest->getVal( 'printable' );
$page = $wgRequest->getVal( 'page' );
- //check for non-standard user language; this covers uselang,
+ //check for non-standard user language; this covers uselang,
//and extensions for auto-detecting user language.
- $ulang = $wgLang->getCode();
+ $ulang = $wgLang->getCode();
$clang = $wgContLang->getCode();
$cacheable = $wgUseFileCache
$revision->insertOn( $dbw );
$this->updateRevisionOn( $dbw, $revision );
$dbw->commit();
+
+ wfRunHooks( 'NewRevisionFromEditComplete', array($this, $revision, false) );
wfProfileOut( __METHOD__ );
}
}
$other->invalidateCache();
$other->purgeSquid();
-
+
$title->touchLinks();
$title->purgeSquid();
@unlink( $cm->fileCacheName() );
}
- if( $title->getNamespace() == NS_MEDIAWIKI) {
+ # Messages
+ if( $title->getNamespace() == NS_MEDIAWIKI ) {
$wgMessageCache->replace( $title->getDBkey(), false );
}
+ # Images
if( $title->getNamespace() == NS_IMAGE ) {
$update = new HTMLCacheUpdate( $title, 'imagelinks' );
$update->doUpdate();
}
+ # User talk pages
+ if( $title->getNamespace() == NS_USER_TALK ) {
+ $user = User::newFromName( $title->getText(), false );
+ $user->setNewtalk( false );
+ }
}
/**
// Invalidate the caches of all pages which redirect here
$wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'redirect' );
-
+
# Purge squid for this page only
$title->purgeSquid();
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( array( 'categorylinks', 'page_props', 'page' ),
array( 'cl_to' ),
- array( 'cl_from' => $id, 'pp_page=page_id', 'pp_propname' => 'hiddencat',
+ array( 'cl_from' => $id, 'pp_page=page_id', 'pp_propname' => 'hiddencat',
'page_namespace' => NS_CATEGORY, 'page_title=cl_to'),
'Article:getHiddenCategories' );
if ( false !== $res ) {
* @return string An appropriate autosummary, or an empty string.
*/
public static function getAutosummary( $oldtext, $newtext, $flags ) {
+ global $wgUseAutomaticEditSummaries;
+ if ( !$wgUseAutomaticEditSummaries ) return '';
# This code is UGLY UGLY UGLY.
# Somebody PLEASE come up with a more elegant way to do it.