public $mDataLoaded = false; // !< Boolean
public $mIsRedirect = false; // !< Boolean
public $mLatest = false; // !< Integer (false means "not loaded")
- public $mPreparedEdit = false; // !< Array
+ public $mPreparedEdit = false; // !< Array
+ /**@}}*/
/**
* @var Title
*/
protected $mLastRevision = null;
- protected $mTimestamp = ''; // !< String
- protected $mTouched = '19700101000000'; // !< String
- /**@}}*/
+ /**
+ * @var string; timestamp of the current revision or empty string if not loaded
+ */
+ protected $mTimestamp = '';
+
+ /**
+ * @var string
+ */
+ protected $mTouched = '19700101000000';
+
+ /**
+ * @var int|null
+ */
+ protected $mCounter = null;
/**
* Constructor and clear the article
* @return mixed false, Title object of local target, or string with URL
*/
public function getRedirectURL( $rt ) {
- if ( $rt ) {
- if ( $rt->getInterwiki() != '' ) {
- if ( $rt->isLocal() ) {
- // Offsite wikis need an HTTP redirect.
- //
- // This can be hard to reverse and may produce loops,
- // so they may be disabled in the site configuration.
- $source = $this->mTitle->getFullURL( 'redirect=no' );
- return $rt->getFullURL( 'rdfrom=' . urlencode( $source ) );
- }
+ if ( !$rt ) {
+ return false;
+ }
+
+ if ( $rt->isExternal() ) {
+ if ( $rt->isLocal() ) {
+ // Offsite wikis need an HTTP redirect.
+ //
+ // This can be hard to reverse and may produce loops,
+ // so they may be disabled in the site configuration.
+ $source = $this->mTitle->getFullURL( 'redirect=no' );
+ return $rt->getFullURL( 'rdfrom=' . urlencode( $source ) );
} else {
- if ( $rt->isSpecialPage() ) {
- // Gotta handle redirects to special pages differently:
- // Fill the HTTP response "Location" header and ignore
- // the rest of the page we're on.
- //
- // This can be hard to reverse, so they may be disabled.
- if ( $rt->isSpecial( 'Userlogout' ) ) {
- // rolleyes
- } else {
- return $rt->getFullURL();
- }
- }
+ // External pages pages without "local" bit set are not valid
+ // redirect targets
+ return false;
+ }
+ }
- return $rt;
+ if ( $rt->isSpecialPage() ) {
+ // Gotta handle redirects to special pages differently:
+ // Fill the HTTP response "Location" header and ignore
+ // the rest of the page we're on.
+ //
+ // Some pages are not valid targets
+ if ( $rt->isValidRedirectTarget() ) {
+ return $rt->getFullURL();
+ } else {
+ return false;
}
}
- // No or invalid redirect
- return false;
+ return $rt;
}
/**
public function clear() {
$this->mDataLoaded = false;
+ $this->mCounter = null;
$this->mRedirectTarget = null; # Title object if set
$this->mLastRevision = null; # Latest revision
- $this->mTimestamp = '';
$this->mTouched = '19700101000000';
+ $this->mTimestamp = '';
$this->mIsRedirect = false;
$this->mLatest = false;
$this->mPreparedEdit = false;
# Old-fashioned restrictions
$this->mTitle->loadRestrictions( $data->page_restrictions );
+ $this->mCounter = intval( $data->page_counter );
$this->mTouched = wfTimestamp( TS_MW, $data->page_touched );
$this->mIsRedirect = intval( $data->page_is_redirect );
$this->mLatest = intval( $data->page_latest );
}
/**
- * Get the number of views of this page
- *
* @return int The view count for the page
*/
public function getCount() {
- return $this->mTitle->getCount();
+ if ( !$this->mDataLoaded ) {
+ $this->loadPageData();
+ }
+
+ return $this->mCounter;
}
/**
}
}
+ /**
+ * Loads page_touched and returns a value indicating if it should be used
+ * @return boolean true if not a redirect
+ */
+ public function checkTouched() {
+ if ( !$this->mDataLoaded ) {
+ $this->loadPageData();
+ }
+ return !$this->mIsRedirect;
+ }
+
+ /**
+ * Get the page_touched field
+ * @return string containing GMT timestamp
+ */
+ public function getTouched() {
+ if ( !$this->mDataLoaded ) {
+ $this->loadPageData();
+ }
+ return $this->mTouched;
+ }
+
+ /**
+ * Get the page_latest field
+ * @return integer rev_id of current revision
+ */
+ public function getLatest() {
+ if ( !$this->mDataLoaded ) {
+ $this->loadPageData();
+ }
+ return (int)$this->mLatest;
+ }
+
/**
* Loads everything except the text
* This isn't necessary for all uses, so it's only done if needed.
}
}
+ /**
+ * Get the cached timestamp for the last time the page changed.
+ * This is only used to help handle slave lag by comparing to page_touched.
+ * @return string MW timestamp
+ */
+ protected function getCachedLastEditTime() {
+ global $wgMemc;
+ $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
+ return $wgMemc->get( $key );
+ }
+
+ /**
+ * Set the cached timestamp for the last time the page changed.
+ * This is only used to help handle slave lag by comparing to page_touched.
+ * @param $timestamp string
+ * @return void
+ */
+ public function setCachedLastEditTime( $timestamp ) {
+ global $wgMemc;
+ $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
+ $wgMemc->set( $key, wfTimestamp( TS_MW, $timestamp ), 60*15 );
+ }
+
/**
* Get a list of users who have edited this article, not including the user who made
* the most recent revision, which you can get from $article->getUser() if you want it
return new UserArrayFromResult( $res );
}
+ /**
+ * Get the last N authors
+ * @param $num Integer: number of revisions to get
+ * @param $revLatest String: the latest rev_id, selected from the master (optional)
+ * @return array Array of authors, duplicates not removed
+ */
+ public function getLastNAuthors( $num, $revLatest = 0 ) {
+ wfProfileIn( __METHOD__ );
+ // First try the slave
+ // If that doesn't have the latest revision, try the master
+ $continue = 2;
+ $db = wfGetDB( DB_SLAVE );
+
+ do {
+ $res = $db->select( array( 'page', 'revision' ),
+ array( 'rev_id', 'rev_user_text' ),
+ array(
+ 'page_namespace' => $this->mTitle->getNamespace(),
+ 'page_title' => $this->mTitle->getDBkey(),
+ 'rev_page = page_id'
+ ), __METHOD__,
+ array(
+ 'ORDER BY' => 'rev_timestamp DESC',
+ 'LIMIT' => $num
+ )
+ );
+
+ if ( !$res ) {
+ wfProfileOut( __METHOD__ );
+ return array();
+ }
+
+ $row = $db->fetchObject( $res );
+
+ if ( $continue == 2 && $revLatest && $row->rev_id != $revLatest ) {
+ $db = wfGetDB( DB_MASTER );
+ $continue--;
+ } else {
+ $continue = 0;
+ }
+ } while ( $continue );
+
+ $authors = array( $row->rev_user_text );
+
+ foreach ( $res as $row ) {
+ $authors[] = $row->rev_user_text;
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $authors;
+ }
+
/**
* Should the parser cache be used?
*
return $pool->getParserOutput();
}
+ /**
+ * Do standard deferred updates after page view
+ * @param $user User The relevant user
+ */
+ public function doViewUpdates( User $user ) {
+ global $wgDisableCounters;
+ if ( wfReadOnly() ) {
+ return;
+ }
+
+ # Don't update page view counters on views from bot users (bug 14044)
+ if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->mTitle->exists() ) {
+ DeferredUpdates::addUpdate( new ViewCountUpdate( $this->getId() ) );
+ DeferredUpdates::addUpdate( new SiteStatsUpdate( 1, 0, 0 ) );
+ }
+
+ # Update newtalk / watchlist notification status
+ $user->clearNotification( $this->mTitle );
+ }
+
/**
* Perform the actions of a page purging
*/
return $result;
}
- /**
- * Get the cached timestamp for the last time the page changed.
- * This is only used to help handle slave lag by comparing to page_touched.
- * @return string MW timestamp
- */
- protected function getCachedLastEditTime() {
- global $wgMemc;
- $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
- return $wgMemc->get( $key );
- }
-
- /**
- * Set the cached timestamp for the last time the page changed.
- * This is only used to help handle slave lag by comparing to page_touched.
- * @param $timestamp string
- * @return void
- */
- public function setCachedLastEditTime( $timestamp ) {
- global $wgMemc;
- $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
- $wgMemc->set( $key, wfTimestamp( TS_MW, $timestamp ), 60*15 );
- }
-
/**
* Add row to the redirect table if this is a redirect, remove otherwise.
*
// Delete if changing from redirect to non-redirect
$isRedirect = !is_null( $redirectTitle );
- if ( !$isRedirect && !is_null( $lastRevIsRedirect ) && $lastRevIsRedirect === $isRedirect ) {
+ if ( !$isRedirect && $lastRevIsRedirect === false ) {
return true;
}
if ( strval( $section ) == '' ) {
// Whole-page edit; let the whole text through
} else {
- if ( is_null( $edittime ) ) {
- $rev = Revision::newFromTitle( $this->mTitle );
+ // Bug 30711: always use current version when adding a new section
+ if ( is_null( $edittime ) || $section == 'new' ) {
+ $oldtext = $this->getRawText();
+ if ( $oldtext === false ) {
+ wfDebug( __METHOD__ . ": no page text\n" );
+ wfProfileOut( __METHOD__ );
+ return null;
+ }
} else {
$dbw = wfGetDB( DB_MASTER );
$rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
- }
- if ( !$rev ) {
- wfDebug( "WikiPage::replaceSection asked for bogus section (page: " .
- $this->getId() . "; section: $section; edittime: $edittime)\n" );
- wfProfileOut( __METHOD__ );
- return null;
- }
+ if ( !$rev ) {
+ wfDebug( "WikiPage::replaceSection asked for bogus section (page: " .
+ $this->getId() . "; section: $section; edittime: $edittime)\n" );
+ wfProfileOut( __METHOD__ );
+ return null;
+ }
- $oldtext = $rev->getText();
+ $oldtext = $rev->getText();
+ }
if ( $section == 'new' ) {
# Inserting a new section
}
/**
- * Update the article's restriction field, and leave a log entry.
- * This works for protection both existing and non-existing pages.
- *
- * @param $limit Array: set of restriction keys
- * @param $reason String
- * @param &$cascade Integer. Set to false if cascading protection isn't allowed.
- * @param $expiry Array: per restriction type expiration
- * @param $user User The user updating the restrictions
- * @return bool true on success
+ * Get parser options suitable for rendering the primary article wikitext
+ * @param User|string $user User object or 'canonical'
+ * @return ParserOptions
*/
- public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) {
+ public function makeParserOptions( $user ) {
global $wgContLang;
-
- if ( wfReadOnly() ) {
- return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
+ if ( $user instanceof User ) { // settings per user (even anons)
+ $options = ParserOptions::newFromUser( $user );
+ } else { // canonical settings
+ $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
}
+ $options->enableLimitReport(); // show inclusion/loop reports
+ $options->setTidy( true ); // fix bad HTML
+ return $options;
+ }
- $restrictionTypes = $this->mTitle->getRestrictionTypes();
-
- $id = $this->mTitle->getArticleID();
-
- if ( !$cascade ) {
- $cascade = false;
+ /**
+ * Prepare text which is about to be saved.
+ * Returns a stdclass with source, pst and output members
+ */
+ public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
+ global $wgParser, $wgContLang, $wgUser;
+ $user = is_null( $user ) ? $wgUser : $user;
+ // @TODO fixme: check $user->getId() here???
+ if ( $this->mPreparedEdit
+ && $this->mPreparedEdit->newText == $text
+ && $this->mPreparedEdit->revid == $revid
+ ) {
+ // Already prepared
+ return $this->mPreparedEdit;
}
- // Take this opportunity to purge out expired restrictions
- Title::purgeExpiredRestrictions();
+ $popts = ParserOptions::newFromUserAndLang( $user, $wgContLang );
+ wfRunHooks( 'ArticlePrepareTextForEdit', array( $this, $popts ) );
- # @todo FIXME: Same limitations as described in ProtectionForm.php (line 37);
- # we expect a single selection, but the schema allows otherwise.
- $isProtected = false;
- $protect = false;
- $changed = false;
+ $edit = (object)array();
+ $edit->revid = $revid;
+ $edit->newText = $text;
+ $edit->pst = $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
+ $edit->popts = $this->makeParserOptions( 'canonical' );
+ $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $edit->popts, true, true, $revid );
+ $edit->oldText = $this->getRawText();
- $dbw = wfGetDB( DB_MASTER );
+ $this->mPreparedEdit = $edit;
- foreach ( $restrictionTypes as $action ) {
- if ( !isset( $expiry[$action] ) ) {
- $expiry[$action] = $dbw->getInfinity();
- }
- if ( !isset( $limit[$action] ) ) {
- $limit[$action] = '';
- } elseif ( $limit[$action] != '' ) {
- $protect = true;
+ return $edit;
+ }
+
+ /**
+ * Do standard deferred updates after page edit.
+ * Update links tables, site stats, search index and message cache.
+ * Purges pages that include this page if the text was changed here.
+ * Every 100th edit, prune the recent changes table.
+ *
+ * @private
+ * @param $revision Revision object
+ * @param $user User object that did the revision
+ * @param $options Array of options, following indexes are used:
+ * - changed: boolean, whether the revision changed the content (default true)
+ * - created: boolean, whether the revision created the page (default false)
+ * - oldcountable: boolean or null (default null):
+ * - boolean: whether the page was counted as an article before that
+ * revision, only used in changed is true and created is false
+ * - null: don't change the article count
+ */
+ public function doEditUpdates( Revision $revision, User $user, array $options = array() ) {
+ global $wgEnableParserCache;
+
+ wfProfileIn( __METHOD__ );
+
+ $options += array( 'changed' => true, 'created' => false, 'oldcountable' => null );
+ $text = $revision->getText();
+
+ # Parse the text
+ # Be careful not to double-PST: $text is usually already PST-ed once
+ if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
+ wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
+ $editInfo = $this->prepareTextForEdit( $text, $revision->getId(), $user );
+ } else {
+ wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
+ $editInfo = $this->mPreparedEdit;
+ }
+
+ # Save it to the parser cache
+ if ( $wgEnableParserCache ) {
+ $parserCache = ParserCache::singleton();
+ $parserCache->save( $editInfo->output, $this, $editInfo->popts );
+ }
+
+ # Update the links tables
+ $u = new LinksUpdate( $this->mTitle, $editInfo->output );
+ $u->doUpdate();
+
+ wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
+
+ if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
+ if ( 0 == mt_rand( 0, 99 ) ) {
+ // Flush old entries from the `recentchanges` table; we do this on
+ // random requests so as to avoid an increase in writes for no good reason
+ global $wgRCMaxAge;
+
+ $dbw = wfGetDB( DB_MASTER );
+ $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
+ $dbw->delete(
+ 'recentchanges',
+ array( "rc_timestamp < '$cutoff'" ),
+ __METHOD__
+ );
+ }
+ }
+
+ if ( !$this->mTitle->exists() ) {
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+
+ $id = $this->getId();
+ $title = $this->mTitle->getPrefixedDBkey();
+ $shortTitle = $this->mTitle->getDBkey();
+
+ if ( !$options['changed'] ) {
+ $good = 0;
+ $total = 0;
+ } elseif ( $options['created'] ) {
+ $good = (int)$this->isCountable( $editInfo );
+ $total = 1;
+ } elseif ( $options['oldcountable'] !== null ) {
+ $good = (int)$this->isCountable( $editInfo ) - (int)$options['oldcountable'];
+ $total = 0;
+ } else {
+ $good = 0;
+ $total = 0;
+ }
+
+ DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, $good, $total ) );
+ DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $text ) );
+
+ # If this is another user's talk page, update newtalk.
+ # Don't do this if $options['changed'] = false (null-edits) nor if
+ # it's a minor edit and the user doesn't want notifications for those.
+ if ( $options['changed']
+ && $this->mTitle->getNamespace() == NS_USER_TALK
+ && $shortTitle != $user->getTitleKey()
+ && !( $revision->isMinor() && $user->isAllowed( 'nominornewtalk' ) )
+ ) {
+ if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this ) ) ) {
+ $other = User::newFromName( $shortTitle, false );
+ if ( !$other ) {
+ wfDebug( __METHOD__ . ": invalid username\n" );
+ } elseif ( User::isIP( $shortTitle ) ) {
+ // An anonymous user
+ $other->setNewtalk( true );
+ } elseif ( $other->isLoggedIn() ) {
+ $other->setNewtalk( true );
+ } else {
+ wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
+ }
+ }
+ }
+
+ if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+ MessageCache::singleton()->replace( $shortTitle, $text );
+ }
+
+ if( $options['created'] ) {
+ self::onArticleCreate( $this->mTitle );
+ } else {
+ self::onArticleEdit( $this->mTitle );
+ }
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Edit an article without doing all that other stuff
+ * The article must already exist; link tables etc
+ * are not updated, caches are not flushed.
+ *
+ * @param $text String: text submitted
+ * @param $user User The relevant user
+ * @param $comment String: comment submitted
+ * @param $minor Boolean: whereas it's a minor modification
+ */
+ public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
+ wfProfileIn( __METHOD__ );
+
+ $dbw = wfGetDB( DB_MASTER );
+ $revision = new Revision( array(
+ 'page' => $this->getId(),
+ 'text' => $text,
+ 'comment' => $comment,
+ 'minor_edit' => $minor ? 1 : 0,
+ ) );
+ $revision->insertOn( $dbw );
+ $this->updateRevisionOn( $dbw, $revision );
+
+ wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Update the article's restriction field, and leave a log entry.
+ * This works for protection both existing and non-existing pages.
+ *
+ * @param $limit Array: set of restriction keys
+ * @param $reason String
+ * @param &$cascade Integer. Set to false if cascading protection isn't allowed.
+ * @param $expiry Array: per restriction type expiration
+ * @param $user User The user updating the restrictions
+ * @return bool true on success
+ */
+ public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) {
+ global $wgContLang;
+
+ if ( wfReadOnly() ) {
+ return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
+ }
+
+ $restrictionTypes = $this->mTitle->getRestrictionTypes();
+
+ $id = $this->mTitle->getArticleID();
+
+ if ( !$cascade ) {
+ $cascade = false;
+ }
+
+ // Take this opportunity to purge out expired restrictions
+ Title::purgeExpiredRestrictions();
+
+ # @todo FIXME: Same limitations as described in ProtectionForm.php (line 37);
+ # we expect a single selection, but the schema allows otherwise.
+ $isProtected = false;
+ $protect = false;
+ $changed = false;
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ foreach ( $restrictionTypes as $action ) {
+ if ( !isset( $expiry[$action] ) ) {
+ $expiry[$action] = $dbw->getInfinity();
+ }
+ if ( !isset( $limit[$action] ) ) {
+ $limit[$action] = '';
+ } elseif ( $limit[$action] != '' ) {
+ $protect = true;
}
# Get current restrictions on $action
}
/**
- * @return bool whether or not the page surpasses $wgDeleteRevisionsLimit revisions
+ * Back-end article deletion
+ * Deletes the article with database consistency, writes logs, purges caches
+ *
+ * @param $reason string delete reason for deletion log
+ * @param $suppress bitfield
+ * Revision::DELETED_TEXT
+ * Revision::DELETED_COMMENT
+ * Revision::DELETED_USER
+ * Revision::DELETED_RESTRICTED
+ * @param $id int article ID
+ * @param $commit boolean defaults to true, triggers transaction end
+ * @param &$errors Array of errors to append to
+ * @param $user User The relevant user
+ * @return boolean true if successful
*/
- public function isBigDeletion() {
- global $wgDeleteRevisionsLimit;
+ public function doDeleteArticle(
+ $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
+ ) {
+ global $wgUser;
+ $user = is_null( $user ) ? $wgUser : $user;
- if ( $wgDeleteRevisionsLimit ) {
- $revCount = $this->estimateRevisionCount();
+ wfDebug( __METHOD__ . "\n" );
- return $revCount > $wgDeleteRevisionsLimit;
+ if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error ) ) ) {
+ return false;
}
+ $dbw = wfGetDB( DB_MASTER );
+ $t = $this->mTitle->getDBkey();
+ $id = $id ? $id : $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
- return false;
- }
+ if ( $t === '' || $id == 0 ) {
+ return false;
+ }
- /**
- * @return int approximate revision count
- */
- public function estimateRevisionCount() {
- $dbr = wfGetDB( DB_SLAVE );
- return $dbr->estimateRowCount( 'revision', '*',
- array( 'rev_page' => $this->getId() ), __METHOD__ );
- }
-
- /**
- * Get the last N authors
- * @param $num Integer: number of revisions to get
- * @param $revLatest String: the latest rev_id, selected from the master (optional)
- * @return array Array of authors, duplicates not removed
- */
- public function getLastNAuthors( $num, $revLatest = 0 ) {
- wfProfileIn( __METHOD__ );
- // First try the slave
- // If that doesn't have the latest revision, try the master
- $continue = 2;
- $db = wfGetDB( DB_SLAVE );
-
- do {
- $res = $db->select( array( 'page', 'revision' ),
- array( 'rev_id', 'rev_user_text' ),
- array(
- 'page_namespace' => $this->mTitle->getNamespace(),
- 'page_title' => $this->mTitle->getDBkey(),
- 'rev_page = page_id'
- ), __METHOD__,
- array(
- 'ORDER BY' => 'rev_timestamp DESC',
- 'LIMIT' => $num
- )
- );
-
- if ( !$res ) {
- wfProfileOut( __METHOD__ );
- return array();
- }
-
- $row = $db->fetchObject( $res );
-
- if ( $continue == 2 && $revLatest && $row->rev_id != $revLatest ) {
- $db = wfGetDB( DB_MASTER );
- $continue--;
- } else {
- $continue = 0;
- }
- } while ( $continue );
-
- $authors = array( $row->rev_user_text );
-
- foreach ( $res as $row ) {
- $authors[] = $row->rev_user_text;
- }
-
- wfProfileOut( __METHOD__ );
- return $authors;
- }
-
- /**
- * Back-end article deletion
- * Deletes the article with database consistency, writes logs, purges caches
- *
- * @param $reason string delete reason for deletion log
- * @param $suppress bitfield
- * Revision::DELETED_TEXT
- * Revision::DELETED_COMMENT
- * Revision::DELETED_USER
- * Revision::DELETED_RESTRICTED
- * @param $id int article ID
- * @param $commit boolean defaults to true, triggers transaction end
- * @param &$errors Array of errors to append to
- * @param $user User The relevant user
- * @return boolean true if successful
- */
- public function doDeleteArticle(
- $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
- ) {
- global $wgUser;
- $user = is_null( $user ) ? $wgUser : $user;
-
- wfDebug( __METHOD__ . "\n" );
-
- if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error ) ) ) {
- return false;
- }
- $dbw = wfGetDB( DB_MASTER );
- $t = $this->mTitle->getDBkey();
- $id = $id ? $id : $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
-
- if ( $t === '' || $id == 0 ) {
- return false;
- }
-
- DeferredUpdates::addUpdate(
- new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 )
- );
-
- // Bitfields to further suppress the content
- if ( $suppress ) {
- $bitfield = 0;
- // This should be 15...
- $bitfield |= Revision::DELETED_TEXT;
- $bitfield |= Revision::DELETED_COMMENT;
- $bitfield |= Revision::DELETED_USER;
- $bitfield |= Revision::DELETED_RESTRICTED;
- } else {
- $bitfield = 'rev_deleted';
- }
+ // Bitfields to further suppress the content
+ if ( $suppress ) {
+ $bitfield = 0;
+ // This should be 15...
+ $bitfield |= Revision::DELETED_TEXT;
+ $bitfield |= Revision::DELETED_COMMENT;
+ $bitfield |= Revision::DELETED_USER;
+ $bitfield |= Revision::DELETED_RESTRICTED;
+ } else {
+ $bitfield = 'rev_deleted';
+ }
$dbw->begin();
// For now, shunt the revision data into the archive table.
), __METHOD__
);
- # Delete restrictions for it
- $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
-
# 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
return false;
}
+ $this->doDeleteUpdates( $id );
+
+ # Log the deletion, if the page was suppressed, log it at Oversight instead
+ $logtype = $suppress ? 'suppress' : 'delete';
+
+ $logEntry = new ManualLogEntry( $logtype, 'delete' );
+ $logEntry->setPerformer( $user );
+ $logEntry->setTarget( $this->mTitle );
+ $logEntry->setComment( $reason );
+ $logid = $logEntry->insert();
+ $logEntry->publish( $logid );
+
+ if ( $commit ) {
+ $dbw->commit();
+ }
+
+ wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id ) );
+ return true;
+ }
+
+ /**
+ * Do some database updates after deletion
+ *
+ * @param $id Int: page_id value of the page being deleted
+ */
+ public function doDeleteUpdates( $id ) {
+ DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 ) );
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ # Delete restrictions for it
+ $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
+
# Fix category table counts
$cats = array();
$res = $dbw->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
# Clear the cached article id so the interface doesn't act like we exist
$this->mTitle->resetArticleID( 0 );
-
- # Log the deletion, if the page was suppressed, log it at Oversight instead
- $logtype = $suppress ? 'suppress' : 'delete';
-
- $logEntry = new ManualLogEntry( $logtype, 'delete' );
- $logEntry->setPerformer( $user );
- $logEntry->setTarget( $this->mTitle );
- $logEntry->setComment( $reason );
- $logid = $logEntry->insert();
- $logEntry->publish( $logid );
-
- if ( $commit ) {
- $dbw->commit();
- }
-
- wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id ) );
- return true;
}
/**
return array( array( 'notvisiblerev' ) );
}
- $set = array();
- if ( $bot && $guser->isAllowed( 'markbotedits' ) ) {
- # Mark all reverted edits as bot
- $set['rc_bot'] = 1;
- }
-
- if ( $wgUseRCPatrol ) {
- # Mark all reverted edits as patrolled
- $set['rc_patrolled'] = 1;
- }
-
- if ( count( $set ) ) {
- $dbw->update( 'recentchanges', $set,
- array( /* WHERE */
- 'rc_cur_id' => $current->getPage(),
- 'rc_user_text' => $current->getUserText(),
- "rc_timestamp > '{$s->rev_timestamp}'",
- ), __METHOD__
- );
- }
-
- # Generate the edit summary if necessary
- $target = Revision::newFromId( $s->rev_id );
- if ( empty( $summary ) ) {
- if ( $from == '' ) { // no public user name
- $summary = wfMsgForContent( 'revertpage-nouser' );
- } else {
- $summary = wfMsgForContent( 'revertpage' );
- }
- }
-
- # Allow the custom summary to use the same args as the default message
- $args = array(
- $target->getUserText(), $from, $s->rev_id,
- $wgContLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ),
- $current->getId(), $wgContLang->timeanddate( $current->getTimestamp() )
- );
- $summary = wfMsgReplaceArgs( $summary, $args );
-
- # Save
- $flags = EDIT_UPDATE;
-
- if ( $guser->isAllowed( 'minoredit' ) ) {
- $flags |= EDIT_MINOR;
- }
-
- if ( $bot && ( $guser->isAllowedAny( 'markbotedits', 'bot' ) ) ) {
- $flags |= EDIT_FORCE_BOT;
- }
-
- # Actually store the edit
- $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId() );
- if ( !empty( $status->value['revision'] ) ) {
- $revId = $status->value['revision']->getId();
- } else {
- $revId = false;
- }
-
- wfRunHooks( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) );
-
- $resultDetails = array(
- 'summary' => $summary,
- 'current' => $current,
- 'target' => $target,
- 'newid' => $revId
- );
-
- return array();
- }
-
- /**
- * Do standard deferred updates after page view
- * @param $user User The relevant user
- */
- public function doViewUpdates( User $user ) {
- global $wgDisableCounters;
- if ( wfReadOnly() ) {
- return;
- }
-
- # Don't update page view counters on views from bot users (bug 14044)
- if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->mTitle->exists() ) {
- DeferredUpdates::addUpdate( new ViewCountUpdate( $this->getId() ) );
- DeferredUpdates::addUpdate( new SiteStatsUpdate( 1, 0, 0 ) );
- }
-
- # Update newtalk / watchlist notification status
- $user->clearNotification( $this->mTitle );
- }
-
- /**
- * Prepare text which is about to be saved.
- * Returns a stdclass with source, pst and output members
- */
- public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
- global $wgParser, $wgContLang, $wgUser;
- $user = is_null( $user ) ? $wgUser : $user;
- // @TODO fixme: check $user->getId() here???
- if ( $this->mPreparedEdit
- && $this->mPreparedEdit->newText == $text
- && $this->mPreparedEdit->revid == $revid
- ) {
- // Already prepared
- return $this->mPreparedEdit;
- }
-
- $popts = ParserOptions::newFromUserAndLang( $user, $wgContLang );
- wfRunHooks( 'ArticlePrepareTextForEdit', array( $this, $popts ) );
-
- $edit = (object)array();
- $edit->revid = $revid;
- $edit->newText = $text;
- $edit->pst = $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
- $edit->popts = $this->makeParserOptions( 'canonical' );
-
- // Bug 32858: For a CSS/JS page, put a blank parser output into the
- // prepared edit, so that links etc. won't be registered in the
- // database. We could have set $edit->output to false or something if
- // we thought of this bug earlier, but now that would break the
- // interface with AbuseFilter etc.
- if ( $this->mTitle->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) {
- $input = '';
- } else {
- $input = $edit->pst;
- }
- $edit->output = $wgParser->parse( $input, $this->mTitle, $edit->popts, true, true, $revid );
- $edit->oldText = $this->getRawText();
-
- $this->mPreparedEdit = $edit;
-
- return $edit;
- }
-
- /**
- * Do standard deferred updates after page edit.
- * Update links tables, site stats, search index and message cache.
- * Purges pages that include this page if the text was changed here.
- * Every 100th edit, prune the recent changes table.
- *
- * @private
- * @param $revision Revision object
- * @param $user User object that did the revision
- * @param $options Array of options, following indexes are used:
- * - changed: boolean, whether the revision changed the content (default true)
- * - created: boolean, whether the revision created the page (default false)
- * - oldcountable: boolean or null (default null):
- * - boolean: whether the page was counted as an article before that
- * revision, only used in changed is true and created is false
- * - null: don't change the article count
- */
- public function doEditUpdates( Revision $revision, User $user, array $options = array() ) {
- global $wgEnableParserCache;
-
- wfProfileIn( __METHOD__ );
-
- $options += array( 'changed' => true, 'created' => false, 'oldcountable' => null );
- $text = $revision->getText();
-
- # Parse the text
- # Be careful not to double-PST: $text is usually already PST-ed once
- if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
- wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
- $editInfo = $this->prepareTextForEdit( $text, $revision->getId(), $user );
- } else {
- wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
- $editInfo = $this->mPreparedEdit;
- }
-
- # Save it to the parser cache
- if ( $wgEnableParserCache ) {
- $parserCache = ParserCache::singleton();
- $parserCache->save( $editInfo->output, $this, $editInfo->popts );
- }
-
- # Update the links tables
- $u = new LinksUpdate( $this->mTitle, $editInfo->output );
- $u->doUpdate();
-
- wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
-
- if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
- if ( 0 == mt_rand( 0, 99 ) ) {
- // Flush old entries from the `recentchanges` table; we do this on
- // random requests so as to avoid an increase in writes for no good reason
- global $wgRCMaxAge;
-
- $dbw = wfGetDB( DB_MASTER );
- $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
- $dbw->delete(
- 'recentchanges',
- array( "rc_timestamp < '$cutoff'" ),
- __METHOD__
- );
- }
- }
-
- if ( !$this->mTitle->exists() ) {
- wfProfileOut( __METHOD__ );
- return;
- }
-
- $id = $this->getId();
- $title = $this->mTitle->getPrefixedDBkey();
- $shortTitle = $this->mTitle->getDBkey();
-
- if ( !$options['changed'] ) {
- $good = 0;
- $total = 0;
- } elseif ( $options['created'] ) {
- $good = (int)$this->isCountable( $editInfo );
- $total = 1;
- } elseif ( $options['oldcountable'] !== null ) {
- $good = (int)$this->isCountable( $editInfo ) - (int)$options['oldcountable'];
- $total = 0;
- } else {
- $good = 0;
- $total = 0;
- }
-
- DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, $good, $total ) );
- DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $text ) );
-
- # If this is another user's talk page, update newtalk.
- # Don't do this if $options['changed'] = false (null-edits) nor if
- # it's a minor edit and the user doesn't want notifications for those.
- if ( $options['changed']
- && $this->mTitle->getNamespace() == NS_USER_TALK
- && $shortTitle != $user->getTitleKey()
- && !( $revision->isMinor() && $user->isAllowed( 'nominornewtalk' ) )
- ) {
- if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this ) ) ) {
- $other = User::newFromName( $shortTitle, false );
- if ( !$other ) {
- wfDebug( __METHOD__ . ": invalid username\n" );
- } elseif ( User::isIP( $shortTitle ) ) {
- // An anonymous user
- $other->setNewtalk( true );
- } elseif ( $other->isLoggedIn() ) {
- $other->setNewtalk( true );
- } else {
- wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
- }
- }
- }
-
- if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
- MessageCache::singleton()->replace( $shortTitle, $text );
- }
-
- if( $options['created'] ) {
- self::onArticleCreate( $this->mTitle );
- } else {
- self::onArticleEdit( $this->mTitle );
+ $set = array();
+ if ( $bot && $guser->isAllowed( 'markbotedits' ) ) {
+ # Mark all reverted edits as bot
+ $set['rc_bot'] = 1;
}
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * Perform article updates on a special page creation.
- *
- * @param $rev Revision object
- *
- * @todo This is a shitty interface function. Kill it and replace the
- * other shitty functions like doEditUpdates and such so it's not needed
- * anymore.
- * @deprecated since 1.18, use doEditUpdates()
- */
- public function createUpdates( $rev ) {
- wfDeprecated( __METHOD__, '1.18' );
- global $wgUser;
- $this->doEditUpdates( $rev, $wgUser, array( 'created' => true ) );
- }
-
- /**
- * This function is called right before saving the wikitext,
- * so we can do things like signatures and links-in-context.
- *
- * @deprecated in 1.19; use Parser::preSaveTransform() instead
- * @param $text String article contents
- * @param $user User object: user doing the edit
- * @param $popts ParserOptions object: parser options, default options for
- * the user loaded if null given
- * @return string article contents with altered wikitext markup (signatures
- * converted, {{subst:}}, templates, etc.)
- */
- public function preSaveTransform( $text, User $user = null, ParserOptions $popts = null ) {
- global $wgParser, $wgUser;
-
- wfDeprecated( __METHOD__, '1.19' );
+ if ( $wgUseRCPatrol ) {
+ # Mark all reverted edits as patrolled
+ $set['rc_patrolled'] = 1;
+ }
- $user = is_null( $user ) ? $wgUser : $user;
+ if ( count( $set ) ) {
+ $dbw->update( 'recentchanges', $set,
+ array( /* WHERE */
+ 'rc_cur_id' => $current->getPage(),
+ 'rc_user_text' => $current->getUserText(),
+ "rc_timestamp > '{$s->rev_timestamp}'",
+ ), __METHOD__
+ );
+ }
- if ( $popts === null ) {
- $popts = ParserOptions::newFromUser( $user );
+ # Generate the edit summary if necessary
+ $target = Revision::newFromId( $s->rev_id );
+ if ( empty( $summary ) ) {
+ if ( $from == '' ) { // no public user name
+ $summary = wfMsgForContent( 'revertpage-nouser' );
+ } else {
+ $summary = wfMsgForContent( 'revertpage' );
+ }
}
- return $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
- }
+ # Allow the custom summary to use the same args as the default message
+ $args = array(
+ $target->getUserText(), $from, $s->rev_id,
+ $wgContLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ),
+ $current->getId(), $wgContLang->timeanddate( $current->getTimestamp() )
+ );
+ $summary = wfMsgReplaceArgs( $summary, $args );
- /**
- * Loads page_touched and returns a value indicating if it should be used
- * @return boolean true if not a redirect
- */
- public function checkTouched() {
- if ( !$this->mDataLoaded ) {
- $this->loadPageData();
- }
- return !$this->mIsRedirect;
- }
+ # Save
+ $flags = EDIT_UPDATE;
- /**
- * Get the page_touched field
- * @return string containing GMT timestamp
- */
- public function getTouched() {
- if ( !$this->mDataLoaded ) {
- $this->loadPageData();
+ if ( $guser->isAllowed( 'minoredit' ) ) {
+ $flags |= EDIT_MINOR;
}
- return $this->mTouched;
- }
- /**
- * Get the page_latest field
- * @return integer rev_id of current revision
- */
- public function getLatest() {
- if ( !$this->mDataLoaded ) {
- $this->loadPageData();
+ if ( $bot && ( $guser->isAllowedAny( 'markbotedits', 'bot' ) ) ) {
+ $flags |= EDIT_FORCE_BOT;
}
- return (int)$this->mLatest;
- }
- /**
- * Edit an article without doing all that other stuff
- * The article must already exist; link tables etc
- * are not updated, caches are not flushed.
- *
- * @param $text String: text submitted
- * @param $user User The relevant user
- * @param $comment String: comment submitted
- * @param $minor Boolean: whereas it's a minor modification
- */
- public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
- wfProfileIn( __METHOD__ );
+ # Actually store the edit
+ $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId() );
+ if ( !empty( $status->value['revision'] ) ) {
+ $revId = $status->value['revision']->getId();
+ } else {
+ $revId = false;
+ }
- $dbw = wfGetDB( DB_MASTER );
- $revision = new Revision( array(
- 'page' => $this->getId(),
- 'text' => $text,
- 'comment' => $comment,
- 'minor_edit' => $minor ? 1 : 0,
- ) );
- $revision->insertOn( $dbw );
- $this->updateRevisionOn( $dbw, $revision );
+ wfRunHooks( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) );
- wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
+ $resultDetails = array(
+ 'summary' => $summary,
+ 'current' => $current,
+ 'target' => $target,
+ 'newid' => $revId
+ );
- wfProfileOut( __METHOD__ );
+ return array();
}
/**
/**#@-*/
- /**
- * Return a list of templates used by this article.
- * Uses the templatelinks table
- *
- * @return Array of Title objects
- */
- public function getUsedTemplates() {
- $result = array();
- $id = $this->mTitle->getArticleID();
-
- if ( $id == 0 ) {
- return array();
- }
-
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( array( 'templatelinks' ),
- array( 'tl_namespace', 'tl_title' ),
- array( 'tl_from' => $id ),
- __METHOD__ );
-
- if ( $res !== false ) {
- foreach ( $res as $row ) {
- $result[] = Title::makeTitle( $row->tl_namespace, $row->tl_title );
- }
- }
-
- return $result;
- }
-
/**
* Returns a list of hidden categories this page is a member of.
* Uses the page_props and categorylinks tables.
return $reason;
}
- /**
- * Get parser options suitable for rendering the primary article wikitext
- * @param User|string $user User object or 'canonical'
- * @return ParserOptions
- */
- public function makeParserOptions( $user ) {
- global $wgContLang;
- if ( $user instanceof User ) { // settings per user (even anons)
- $options = ParserOptions::newFromUser( $user );
- } else { // canonical settings
- $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
- }
- $options->enableLimitReport(); // show inclusion/loop reports
- $options->setTidy( true ); // fix bad HTML
- return $options;
- }
-
/**
* Update all the appropriate counts in the category table, given that
* we've added the categories $added and deleted the categories $deleted.
}
}
+ /**
+ * Return a list of templates used by this article.
+ * Uses the templatelinks table
+ *
+ * @deprecated in 1.19; use Title::getTemplateLinksFrom()
+ * @return Array of Title objects
+ */
+ public function getUsedTemplates() {
+ return $this->mTitle->getTemplateLinksFrom();
+ }
+
+ /**
+ * Perform article updates on a special page creation.
+ *
+ * @param $rev Revision object
+ *
+ * @todo This is a shitty interface function. Kill it and replace the
+ * other shitty functions like doEditUpdates and such so it's not needed
+ * anymore.
+ * @deprecated since 1.18, use doEditUpdates()
+ */
+ public function createUpdates( $rev ) {
+ wfDeprecated( __METHOD__, '1.18' );
+ global $wgUser;
+ $this->doEditUpdates( $rev, $wgUser, array( 'created' => true ) );
+ }
+
+ /**
+ * This function is called right before saving the wikitext,
+ * so we can do things like signatures and links-in-context.
+ *
+ * @deprecated in 1.19; use Parser::preSaveTransform() instead
+ * @param $text String article contents
+ * @param $user User object: user doing the edit
+ * @param $popts ParserOptions object: parser options, default options for
+ * the user loaded if null given
+ * @return string article contents with altered wikitext markup (signatures
+ * converted, {{subst:}}, templates, etc.)
+ */
+ public function preSaveTransform( $text, User $user = null, ParserOptions $popts = null ) {
+ global $wgParser, $wgUser;
+
+ wfDeprecated( __METHOD__, '1.19' );
+
+ $user = is_null( $user ) ? $wgUser : $user;
+
+ if ( $popts === null ) {
+ $popts = ParserOptions::newFromUser( $user );
+ }
+
+ return $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
+ }
+
+ /**
+ * Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit
+ *
+ * @deprecated in 1.19; use Title::isBigDeletion() instead.
+ * @return bool
+ */
+ public function isBigDeletion() {
+ wfDeprecated( __METHOD__, '1.19' );
+ return $this->mTitle->isBigDeletion();
+ }
+
+ /**
+ * Get the approximate revision count of this page.
+ *
+ * @deprecated in 1.19; use Title::estimateRevisionCount() instead.
+ * @return int
+ */
+ public function estimateRevisionCount() {
+ wfDeprecated( __METHOD__, '1.19' );
+ return $this->mTitle->estimateRevisionCount();
+ }
+
/**
* Update the article's restriction field, and leave a log entry.
*