summaryrefslogtreecommitdiff
path: root/includes/WikiPage.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/WikiPage.php')
-rw-r--r--includes/WikiPage.php1320
1 files changed, 806 insertions, 514 deletions
diff --git a/includes/WikiPage.php b/includes/WikiPage.php
index bc8766de..de881ef6 100644
--- a/includes/WikiPage.php
+++ b/includes/WikiPage.php
@@ -23,7 +23,7 @@
/**
* Abstract class for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
*/
-abstract class Page {}
+interface Page {}
/**
* Class representing a MediaWiki article and history.
@@ -33,7 +33,7 @@ abstract class Page {}
*
* @internal documentation reviewed 15 Mar 2010
*/
-class WikiPage extends Page implements IDBAccessObject {
+class WikiPage implements Page, IDBAccessObject {
// Constants for $mDataLoadedFrom and related
/**
@@ -121,8 +121,8 @@ class WikiPage extends Page implements IDBAccessObject {
/**
* Constructor from a page id
*
- * @param $id Int article ID to load
- * @param $from string|int one of the following values:
+ * @param int $id article ID to load
+ * @param string|int $from one of the following values:
* - "fromdb" or WikiPage::READ_NORMAL to select from a slave database
* - "fromdbmaster" or WikiPage::READ_LATEST to select from the master database
*
@@ -144,7 +144,7 @@ class WikiPage extends Page implements IDBAccessObject {
* @since 1.20
* @param $row object: database row containing at least fields returned
* by selectFields().
- * @param $from string|int: source of $data:
+ * @param string|int $from source of $data:
* - "fromdb" or WikiPage::READ_NORMAL: from a slave DB
* - "fromdbmaster" or WikiPage::READ_LATEST: from the master DB
* - "forupdate" or WikiPage::READ_LOCKING: from the master DB using SELECT FOR UPDATE
@@ -187,7 +187,21 @@ class WikiPage extends Page implements IDBAccessObject {
* @return Array
*/
public function getActionOverrides() {
- return array();
+ $content_handler = $this->getContentHandler();
+ return $content_handler->getActionOverrides();
+ }
+
+ /**
+ * Returns the ContentHandler instance to be used to deal with the content of this WikiPage.
+ *
+ * Shorthand for ContentHandler::getForModelID( $this->getContentModel() );
+ *
+ * @return ContentHandler
+ *
+ * @since 1.21
+ */
+ public function getContentHandler() {
+ return ContentHandler::getForModelID( $this->getContentModel() );
}
/**
@@ -215,8 +229,8 @@ class WikiPage extends Page implements IDBAccessObject {
*/
protected function clearCacheFields() {
$this->mCounter = null;
- $this->mRedirectTarget = null; # Title object if set
- $this->mLastRevision = null; # Latest revision
+ $this->mRedirectTarget = null; // Title object if set
+ $this->mLastRevision = null; // Latest revision
$this->mTouched = '19700101000000';
$this->mTimestamp = '';
$this->mIsRedirect = false;
@@ -231,7 +245,9 @@ class WikiPage extends Page implements IDBAccessObject {
* @return array
*/
public static function selectFields() {
- return array(
+ global $wgContentHandlerUseDB;
+
+ $fields = array(
'page_id',
'page_namespace',
'page_title',
@@ -244,6 +260,12 @@ class WikiPage extends Page implements IDBAccessObject {
'page_latest',
'page_len',
);
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'page_content_model';
+ }
+
+ return $fields;
}
/**
@@ -317,8 +339,8 @@ class WikiPage extends Page implements IDBAccessObject {
$data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
} elseif ( $from === self::READ_NORMAL ) {
$data = $this->pageDataFromTitle( wfGetDB( DB_SLAVE ), $this->mTitle );
- # Use a "last rev inserted" timestamp key to dimish the issue of slave lag.
- # Note that DB also stores the master position in the session and checks it.
+ // Use a "last rev inserted" timestamp key to diminish the issue of slave lag.
+ // Note that DB also stores the master position in the session and checks it.
$touched = $this->getCachedLastEditTime();
if ( $touched ) { // key set
if ( !$data || $touched > wfTimestamp( TS_MW, $data->page_touched ) ) {
@@ -341,7 +363,7 @@ class WikiPage extends Page implements IDBAccessObject {
* @since 1.20
* @param $data object: database row containing at least fields returned
* by selectFields()
- * @param $from string|int One of the following:
+ * @param string|int $from One of the following:
* - "fromdb" or WikiPage::READ_NORMAL if the data comes from a slave DB
* - "fromdbmaster" or WikiPage::READ_LATEST if the data comes from the master DB
* - "forupdate" or WikiPage::READ_LOCKING if the data comes from from
@@ -349,13 +371,14 @@ class WikiPage extends Page implements IDBAccessObject {
*/
public function loadFromRow( $data, $from ) {
$lc = LinkCache::singleton();
+ $lc->clearLink( $this->mTitle );
if ( $data ) {
$lc->addGoodLinkObjFromRow( $this->mTitle, $data );
$this->mTitle->loadFromRow( $data );
- # Old-fashioned restrictions
+ // Old-fashioned restrictions
$this->mTitle->loadRestrictions( $data->page_restrictions );
$this->mCounter = intval( $data->page_counter );
@@ -418,21 +441,42 @@ class WikiPage extends Page implements IDBAccessObject {
}
/**
- * Tests if the article text represents a redirect
+ * Tests if the article content represents a redirect
*
- * @param $text mixed string containing article contents, or boolean
* @return bool
*/
- public function isRedirect( $text = false ) {
- if ( $text === false ) {
- if ( !$this->mDataLoaded ) {
- $this->loadPageData();
- }
+ public function isRedirect() {
+ $content = $this->getContent();
+ if ( !$content ) return false;
- return (bool)$this->mIsRedirect;
- } else {
- return Title::newFromRedirect( $text ) !== null;
+ return $content->isRedirect();
+ }
+
+ /**
+ * Returns the page's content model id (see the CONTENT_MODEL_XXX constants).
+ *
+ * Will use the revisions actual content model if the page exists,
+ * and the page's default if the page doesn't exist yet.
+ *
+ * @return String
+ *
+ * @since 1.21
+ */
+ public function getContentModel() {
+ if ( $this->exists() ) {
+ // look at the revision's actual content model
+ $rev = $this->getRevision();
+
+ if ( $rev !== null ) {
+ return $rev->getContentModel();
+ } else {
+ $title = $this->mTitle->getPrefixedDBkey();
+ wfWarn( "Page $title exists but has no (visible) revisions!" );
+ }
}
+
+ // use the default model for this page
+ return $this->mTitle->getContentModel();
}
/**
@@ -555,36 +599,61 @@ class WikiPage extends Page implements IDBAccessObject {
}
/**
- * Get the text of the current revision. No side-effects...
+ * Get the content of the current revision. No side-effects...
*
* @param $audience Integer: one of:
* Revision::FOR_PUBLIC to be displayed to all users
* Revision::FOR_THIS_USER to be displayed to $wgUser
* Revision::RAW get the text regardless of permissions
- * @return String|bool The text of the current revision. False on failure
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
+ * @return Content|null The content of the current revision
+ *
+ * @since 1.21
*/
- public function getText( $audience = Revision::FOR_PUBLIC ) {
+ public function getContent( $audience = Revision::FOR_PUBLIC, User $user = null ) {
$this->loadLastEdit();
if ( $this->mLastRevision ) {
- return $this->mLastRevision->getText( $audience );
+ return $this->mLastRevision->getContent( $audience, $user );
}
- return false;
+ return null;
}
/**
* Get the text of the current revision. No side-effects...
*
- * @return String|bool The text of the current revision. False on failure
+ * @param $audience Integer: one of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to the given user
+ * Revision::RAW get the text regardless of permissions
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
+ * @return String|false The text of the current revision
+ * @deprecated as of 1.21, getContent() should be used instead.
*/
- public function getRawText() {
+ public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) { // @todo: deprecated, replace usage!
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
$this->loadLastEdit();
if ( $this->mLastRevision ) {
- return $this->mLastRevision->getRawText();
+ return $this->mLastRevision->getText( $audience, $user );
}
return false;
}
/**
+ * Get the text of the current revision. No side-effects...
+ *
+ * @return String|bool The text of the current revision. False on failure
+ * @deprecated as of 1.21, getContent() should be used instead.
+ */
+ public function getRawText() {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ return $this->getText( Revision::RAW );
+ }
+
+ /**
* @return string MW timestamp of last article revision
*/
public function getTimestamp() {
@@ -598,7 +667,7 @@ class WikiPage extends Page implements IDBAccessObject {
/**
* Set the page timestamp (use only to avoid DB queries)
- * @param $ts string MW timestamp of last article revision
+ * @param string $ts MW timestamp of last article revision
* @return void
*/
public function setTimestamp( $ts ) {
@@ -608,14 +677,16 @@ class WikiPage extends Page implements IDBAccessObject {
/**
* @param $audience Integer: one of:
* Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::FOR_THIS_USER to be displayed to the given user
* Revision::RAW get the text regardless of permissions
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @return int user ID for the user that made the last article revision
*/
- public function getUser( $audience = Revision::FOR_PUBLIC ) {
+ public function getUser( $audience = Revision::FOR_PUBLIC, User $user = null ) {
$this->loadLastEdit();
if ( $this->mLastRevision ) {
- return $this->mLastRevision->getUser( $audience );
+ return $this->mLastRevision->getUser( $audience, $user );
} else {
return -1;
}
@@ -625,14 +696,16 @@ class WikiPage extends Page implements IDBAccessObject {
* Get the User object of the user who created the page
* @param $audience Integer: one of:
* Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::FOR_THIS_USER to be displayed to the given user
* Revision::RAW get the text regardless of permissions
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @return User|null
*/
- public function getCreator( $audience = Revision::FOR_PUBLIC ) {
+ public function getCreator( $audience = Revision::FOR_PUBLIC, User $user = null ) {
$revision = $this->getOldestRevision();
if ( $revision ) {
- $userName = $revision->getUserText( $audience );
+ $userName = $revision->getUserText( $audience, $user );
return User::newFromName( $userName, false );
} else {
return null;
@@ -642,14 +715,16 @@ class WikiPage extends Page implements IDBAccessObject {
/**
* @param $audience Integer: one of:
* Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::FOR_THIS_USER to be displayed to the given user
* Revision::RAW get the text regardless of permissions
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @return string username of the user that made the last article revision
*/
- public function getUserText( $audience = Revision::FOR_PUBLIC ) {
+ public function getUserText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
$this->loadLastEdit();
if ( $this->mLastRevision ) {
- return $this->mLastRevision->getUserText( $audience );
+ return $this->mLastRevision->getUserText( $audience, $user );
} else {
return '';
}
@@ -658,14 +733,16 @@ class WikiPage extends Page implements IDBAccessObject {
/**
* @param $audience Integer: one of:
* Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::FOR_THIS_USER to be displayed to the given user
* Revision::RAW get the text regardless of permissions
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @return string Comment stored for the last article revision
*/
- public function getComment( $audience = Revision::FOR_PUBLIC ) {
+ public function getComment( $audience = Revision::FOR_PUBLIC, User $user = null ) {
$this->loadLastEdit();
if ( $this->mLastRevision ) {
- return $this->mLastRevision->getComment( $audience );
+ return $this->mLastRevision->getComment( $audience, $user );
} else {
return '';
}
@@ -723,32 +800,34 @@ class WikiPage extends Page implements IDBAccessObject {
return false;
}
- $text = $editInfo ? $editInfo->pst : false;
+ if ( $editInfo ) {
+ $content = $editInfo->pstContent;
+ } else {
+ $content = $this->getContent();
+ }
- if ( $this->isRedirect( $text ) ) {
+ if ( !$content || $content->isRedirect() ) {
return false;
}
- switch ( $wgArticleCountMethod ) {
- case 'any':
- return true;
- case 'comma':
- if ( $text === false ) {
- $text = $this->getRawText();
- }
- return strpos( $text, ',' ) !== false;
- case 'link':
+ $hasLinks = null;
+
+ if ( $wgArticleCountMethod === 'link' ) {
+ // nasty special case to avoid re-parsing to detect links
+
if ( $editInfo ) {
// ParserOutput::getLinks() is a 2D array of page links, so
// to be really correct we would need to recurse in the array
// but the main array should only have items in it if there are
// links.
- return (bool)count( $editInfo->output->getLinks() );
+ $hasLinks = (bool)count( $editInfo->output->getLinks() );
} else {
- return (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
+ $hasLinks = (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
array( 'pl_from' => $this->getId() ), __METHOD__ );
}
}
+
+ return $content->isCountable( $hasLinks );
}
/**
@@ -767,7 +846,7 @@ class WikiPage extends Page implements IDBAccessObject {
return $this->mRedirectTarget;
}
- # Query the redirect table
+ // Query the redirect table
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'redirect',
array( 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ),
@@ -782,7 +861,7 @@ class WikiPage extends Page implements IDBAccessObject {
$row->rd_fragment, $row->rd_interwiki );
}
- # This page doesn't have an entry in the redirect table
+ // This page doesn't have an entry in the redirect table
return $this->mRedirectTarget = $this->insertRedirect();
}
@@ -794,7 +873,8 @@ class WikiPage extends Page implements IDBAccessObject {
*/
public function insertRedirect() {
// recurse through to only get the final target
- $retval = Title::newFromRedirectRecurse( $this->getRawText() );
+ $content = $this->getContent();
+ $retval = $content ? $content->getUltimateRedirectTarget() : null;
if ( !$retval ) {
return null;
}
@@ -879,7 +959,7 @@ class WikiPage extends Page implements IDBAccessObject {
* @return UserArrayFromResult
*/
public function getContributors() {
- # @todo FIXME: This is expensive; cache this info somewhere.
+ // @todo FIXME: This is expensive; cache this info somewhere.
$dbr = wfGetDB( DB_SLAVE );
@@ -927,7 +1007,7 @@ class WikiPage extends Page implements IDBAccessObject {
/**
* 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)
+ * @param string $revLatest the latest rev_id, selected from the master (optional)
* @return array Array of authors, duplicates not removed
*/
public function getLastNAuthors( $num, $revLatest = 0 ) {
@@ -990,7 +1070,7 @@ class WikiPage extends Page implements IDBAccessObject {
&& $parserOptions->getStubThreshold() == 0
&& $this->mTitle->exists()
&& ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() )
- && $this->mTitle->isWikitextPage();
+ && $this->getContentHandler()->isParserCacheSupported();
}
/**
@@ -1001,6 +1081,7 @@ class WikiPage extends Page implements IDBAccessObject {
* @param $parserOptions ParserOptions to use for the parse operation
* @param $oldid Revision ID to get the text from, passing null or 0 will
* get the current revision (default value)
+ *
* @return ParserOutput or false if the revision was not found
*/
public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
@@ -1042,13 +1123,13 @@ class WikiPage extends Page implements IDBAccessObject {
return;
}
- # Don't update page view counters on views from bot users (bug 14044)
+ // 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
+ // Update newtalk / watchlist notification status
$user->clearNotification( $this->mTitle );
}
@@ -1059,7 +1140,7 @@ class WikiPage extends Page implements IDBAccessObject {
public function doPurge() {
global $wgUseSquid;
- if( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ){
+ if( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ) {
return false;
}
@@ -1078,8 +1159,16 @@ class WikiPage extends Page implements IDBAccessObject {
}
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+ // @todo: move this logic to MessageCache
+
if ( $this->mTitle->exists() ) {
- $text = $this->getRawText();
+ // NOTE: use transclusion text for messages.
+ // This is consistent with MessageCache::getMsgFromNamespace()
+
+ $content = $this->getContent();
+ $text = $content === null ? null : $content->getWikitextForTransclusion();
+
+ if ( $text === null ) $text = false;
} else {
$text = false;
}
@@ -1109,12 +1198,12 @@ class WikiPage extends Page implements IDBAccessObject {
'page_title' => $this->mTitle->getDBkey(),
'page_counter' => 0,
'page_restrictions' => '',
- 'page_is_redirect' => 0, # Will set this shortly...
+ 'page_is_redirect' => 0, // Will set this shortly...
'page_is_new' => 1,
'page_random' => wfRandom(),
'page_touched' => $dbw->timestamp(),
- 'page_latest' => 0, # Fill this in shortly...
- 'page_len' => 0, # Fill this in shortly...
+ 'page_latest' => 0, // Fill this in shortly...
+ 'page_len' => 0, // Fill this in shortly...
), __METHOD__, 'IGNORE' );
$affected = $dbw->affectedRows();
@@ -1144,28 +1233,36 @@ class WikiPage extends Page implements IDBAccessObject {
* @private
*/
public function updateRevisionOn( $dbw, $revision, $lastRevision = null, $lastRevIsRedirect = null ) {
+ global $wgContentHandlerUseDB;
+
wfProfileIn( __METHOD__ );
- $text = $revision->getText();
- $len = strlen( $text );
- $rt = Title::newFromRedirectRecurse( $text );
+ $content = $revision->getContent();
+ $len = $content ? $content->getSize() : 0;
+ $rt = $content ? $content->getUltimateRedirectTarget() : null;
$conditions = array( 'page_id' => $this->getId() );
if ( !is_null( $lastRevision ) ) {
- # An extra check against threads stepping on each other
+ // An extra check against threads stepping on each other
$conditions['page_latest'] = $lastRevision;
}
$now = wfTimestampNow();
+ $row = array( /* SET */
+ 'page_latest' => $revision->getId(),
+ 'page_touched' => $dbw->timestamp( $now ),
+ 'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
+ 'page_is_redirect' => $rt !== null ? 1 : 0,
+ 'page_len' => $len,
+ );
+
+ if ( $wgContentHandlerUseDB ) {
+ $row[ 'page_content_model' ] = $revision->getContentModel();
+ }
+
$dbw->update( 'page',
- array( /* SET */
- 'page_latest' => $revision->getId(),
- 'page_touched' => $dbw->timestamp( $now ),
- 'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
- 'page_is_redirect' => $rt !== null ? 1 : 0,
- 'page_len' => $len,
- ),
+ $row,
$conditions,
__METHOD__ );
@@ -1176,8 +1273,9 @@ class WikiPage extends Page implements IDBAccessObject {
$this->setCachedLastEditTime( $now );
$this->mLatest = $revision->getId();
$this->mIsRedirect = (bool)$rt;
- # Update the LinkCache.
- LinkCache::singleton()->addGoodLinkObj( $this->getId(), $this->mTitle, $len, $this->mIsRedirect, $this->mLatest );
+ // Update the LinkCache.
+ LinkCache::singleton()->addGoodLinkObj( $this->getId(), $this->mTitle, $len, $this->mIsRedirect,
+ $this->mLatest, $revision->getContentModel() );
}
wfProfileOut( __METHOD__ );
@@ -1249,7 +1347,7 @@ class WikiPage extends Page implements IDBAccessObject {
$prev = $row->rev_id;
$lastRevIsRedirect = (bool)$row->page_is_redirect;
} else {
- # No or missing previous revision; mark the page as new
+ // No or missing previous revision; mark the page as new
$prev = 0;
$lastRevIsRedirect = null;
}
@@ -1261,56 +1359,119 @@ class WikiPage extends Page implements IDBAccessObject {
}
/**
+ * Get the content that needs to be saved in order to undo all revisions
+ * between $undo and $undoafter. Revisions must belong to the same page,
+ * must exist and must not be deleted
+ * @param $undo Revision
+ * @param $undoafter Revision Must be an earlier revision than $undo
+ * @return mixed string on success, false on failure
+ * @since 1.21
+ * Before we had the Content object, this was done in getUndoText
+ */
+ public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
+ $handler = $undo->getContentHandler();
+ return $handler->getUndoContent( $this->getRevision(), $undo, $undoafter );
+ }
+
+ /**
* Get the text that needs to be saved in order to undo all revisions
* between $undo and $undoafter. Revisions must belong to the same page,
* must exist and must not be deleted
* @param $undo Revision
* @param $undoafter Revision Must be an earlier revision than $undo
* @return mixed string on success, false on failure
+ * @deprecated since 1.21: use ContentHandler::getUndoContent() instead.
*/
public function getUndoText( Revision $undo, Revision $undoafter = null ) {
- $cur_text = $this->getRawText();
- if ( $cur_text === false ) {
- return false; // no page
- }
- $undo_text = $undo->getText();
- $undoafter_text = $undoafter->getText();
+ ContentHandler::deprecated( __METHOD__, '1.21' );
- if ( $cur_text == $undo_text ) {
- # No use doing a merge if it's just a straight revert.
- return $undoafter_text;
- }
+ $this->loadLastEdit();
- $undone_text = '';
+ if ( $this->mLastRevision ) {
+ if ( is_null( $undoafter ) ) {
+ $undoafter = $undo->getPrevious();
+ }
- if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) ) {
- return false;
+ $handler = $this->getContentHandler();
+ $undone = $handler->getUndoContent( $this->mLastRevision, $undo, $undoafter );
+
+ if ( !$undone ) {
+ return false;
+ } else {
+ return ContentHandler::getContentText( $undone );
+ }
}
- return $undone_text;
+ return false;
}
/**
* @param $section null|bool|int or a section number (0, 1, 2, T1, T2...)
- * @param $text String: new text of the section
- * @param $sectionTitle String: new section's subject, only if $section is 'new'
- * @param $edittime String: revision timestamp or null to use the current revision
- * @return string Complete article text, or null if error
+ * @param string $text new text of the section
+ * @param string $sectionTitle new section's subject, only if $section is 'new'
+ * @param string $edittime revision timestamp or null to use the current revision
+ * @throws MWException
+ * @return String new complete article text, or null if error
+ *
+ * @deprecated since 1.21, use replaceSectionContent() instead
*/
public function replaceSection( $section, $text, $sectionTitle = '', $edittime = null ) {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ if ( strval( $section ) == '' ) { //NOTE: keep condition in sync with condition in replaceSectionContent!
+ // Whole-page edit; let the whole text through
+ return $text;
+ }
+
+ if ( !$this->supportsSections() ) {
+ throw new MWException( "sections not supported for content model " . $this->getContentHandler()->getModelID() );
+ }
+
+ // could even make section title, but that's not required.
+ $sectionContent = ContentHandler::makeContent( $text, $this->getTitle() );
+
+ $newContent = $this->replaceSectionContent( $section, $sectionContent, $sectionTitle, $edittime );
+
+ return ContentHandler::getContentText( $newContent );
+ }
+
+ /**
+ * Returns true iff this page's content model supports sections.
+ *
+ * @return boolean whether sections are supported.
+ *
+ * @todo: the skin should check this and not offer section functionality if sections are not supported.
+ * @todo: the EditPage should check this and not offer section functionality if sections are not supported.
+ */
+ public function supportsSections() {
+ return $this->getContentHandler()->supportsSections();
+ }
+
+ /**
+ * @param $section null|bool|int or a section number (0, 1, 2, T1, T2...)
+ * @param $sectionContent Content: new content of the section
+ * @param string $sectionTitle new section's subject, only if $section is 'new'
+ * @param string $edittime revision timestamp or null to use the current revision
+ *
+ * @throws MWException
+ * @return Content new complete article content, or null if error
+ *
+ * @since 1.21
+ */
+ public function replaceSectionContent( $section, Content $sectionContent, $sectionTitle = '', $edittime = null ) {
wfProfileIn( __METHOD__ );
if ( strval( $section ) == '' ) {
// Whole-page edit; let the whole text through
+ $newContent = $sectionContent;
} else {
+ if ( !$this->supportsSections() ) {
+ throw new MWException( "sections not supported for content model " . $this->getContentHandler()->getModelID() );
+ }
+
// 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;
- }
+ $oldContent = $this->getContent();
} else {
$dbw = wfGetDB( DB_MASTER );
$rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
@@ -1322,28 +1483,21 @@ class WikiPage extends Page implements IDBAccessObject {
return null;
}
- $oldtext = $rev->getText();
+ $oldContent = $rev->getContent();
}
- if ( $section == 'new' ) {
- # Inserting a new section
- $subject = $sectionTitle ? wfMessage( 'newsectionheaderdefaultlevel' )
- ->rawParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : '';
- if ( wfRunHooks( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) {
- $text = strlen( trim( $oldtext ) ) > 0
- ? "{$oldtext}\n\n{$subject}{$text}"
- : "{$subject}{$text}";
- }
- } else {
- # Replacing an existing section; roll out the big guns
- global $wgParser;
-
- $text = $wgParser->replaceSection( $oldtext, $section, $text );
+ if ( ! $oldContent ) {
+ wfDebug( __METHOD__ . ": no page text\n" );
+ wfProfileOut( __METHOD__ );
+ return null;
}
+
+ // FIXME: $oldContent might be null?
+ $newContent = $oldContent->replaceSection( $section, $sectionContent, $sectionTitle );
}
wfProfileOut( __METHOD__ );
- return $text;
+ return $newContent;
}
/**
@@ -1367,8 +1521,8 @@ class WikiPage extends Page implements IDBAccessObject {
* Change an existing article or create a new article. Updates RC and all necessary caches,
* optionally via the deferred update array.
*
- * @param $text String: new text
- * @param $summary String: edit summary
+ * @param string $text new text
+ * @param string $summary edit summary
* @param $flags Integer bitfield:
* EDIT_NEW
* Article is known or assumed to be non-existent, create a new one
@@ -1409,17 +1563,83 @@ class WikiPage extends Page implements IDBAccessObject {
* revision: The revision object for the inserted revision, or null
*
* Compatibility note: this function previously returned a boolean value indicating success/failure
+ *
+ * @deprecated since 1.21: use doEditContent() instead.
*/
public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+
+ return $this->doEditContent( $content, $summary, $flags, $baseRevId, $user );
+ }
+
+ /**
+ * Change an existing article or create a new article. Updates RC and all necessary caches,
+ * optionally via the deferred update array.
+ *
+ * @param $content Content: new content
+ * @param string $summary edit summary
+ * @param $flags Integer bitfield:
+ * EDIT_NEW
+ * Article is known or assumed to be non-existent, create a new one
+ * EDIT_UPDATE
+ * Article is known or assumed to be pre-existing, update it
+ * EDIT_MINOR
+ * Mark this edit minor, if the user is allowed to do so
+ * EDIT_SUPPRESS_RC
+ * Do not log the change in recentchanges
+ * EDIT_FORCE_BOT
+ * Mark the edit a "bot" edit regardless of user rights
+ * EDIT_DEFER_UPDATES
+ * Defer some of the updates until the end of index.php
+ * EDIT_AUTOSUMMARY
+ * Fill in blank summaries with generated text where possible
+ *
+ * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
+ * If EDIT_UPDATE is specified and the article doesn't exist, the function will return an
+ * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an
+ * edit-already-exists error will be returned. These two conditions are also possible with
+ * auto-detection due to MediaWiki's performance-optimised locking strategy.
+ *
+ * @param bool|\the $baseRevId the revision ID this edit was based off, if any
+ * @param $user User the user doing the edit
+ * @param $serialisation_format String: format for storing the content in the database
+ *
+ * @throws MWException
+ * @return Status object. Possible errors:
+ * edit-hook-aborted: The ArticleSave hook aborted the edit but didn't set the fatal flag of $status
+ * edit-gone-missing: In update mode, but the article didn't exist
+ * edit-conflict: In update mode, the article changed unexpectedly
+ * edit-no-change: Warning that the text was the same as before
+ * edit-already-exists: In creation mode, but the article already exists
+ *
+ * Extensions may define additional errors.
+ *
+ * $return->value will contain an associative array with members as follows:
+ * new: Boolean indicating if the function attempted to create a new article
+ * revision: The revision object for the inserted revision, or null
+ *
+ * @since 1.21
+ */
+ public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
+ User $user = null, $serialisation_format = null ) {
global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol;
- # Low-level sanity check
+ // Low-level sanity check
if ( $this->mTitle->getText() === '' ) {
throw new MWException( 'Something is trying to edit an article with an empty title' );
}
wfProfileIn( __METHOD__ );
+ if ( !$content->getContentHandler()->canBeUsedOn( $this->getTitle() ) ) {
+ wfProfileOut( __METHOD__ );
+ return Status::newFatal( 'content-not-allowed-here',
+ ContentHandler::getLocalizedName( $content->getModel() ),
+ $this->getTitle()->getPrefixedText() );
+ }
+
$user = is_null( $user ) ? $wgUser : $user;
$status = Status::newGood( array() );
@@ -1430,10 +1650,14 @@ class WikiPage extends Page implements IDBAccessObject {
$flags = $this->checkFlags( $flags );
- if ( !wfRunHooks( 'ArticleSave', array( &$this, &$user, &$text, &$summary,
- $flags & EDIT_MINOR, null, null, &$flags, &$status ) ) )
- {
- wfDebug( __METHOD__ . ": ArticleSave hook aborted save!\n" );
+ // handle hook
+ $hook_args = array( &$this, &$user, &$content, &$summary,
+ $flags & EDIT_MINOR, null, null, &$flags, &$status );
+
+ if ( !wfRunHooks( 'PageContentSave', $hook_args )
+ || !ContentHandler::runLegacyHooks( 'ArticleSave', $hook_args ) ) {
+
+ wfDebug( __METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n" );
if ( $status->isOK() ) {
$status->fatal( 'edit-hook-aborted' );
@@ -1443,77 +1667,98 @@ class WikiPage extends Page implements IDBAccessObject {
return $status;
}
- # Silently ignore EDIT_MINOR if not allowed
+ // Silently ignore EDIT_MINOR if not allowed
$isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' );
$bot = $flags & EDIT_FORCE_BOT;
- $oldtext = $this->getRawText(); // current revision
- $oldsize = strlen( $oldtext );
+ $old_content = $this->getContent( Revision::RAW ); // current revision's content
+
+ $oldsize = $old_content ? $old_content->getSize() : 0;
$oldid = $this->getLatest();
$oldIsRedirect = $this->isRedirect();
$oldcountable = $this->isCountable();
- # Provide autosummaries if one is not provided and autosummaries are enabled.
+ $handler = $content->getContentHandler();
+
+ // Provide autosummaries if one is not provided and autosummaries are enabled.
if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
- $summary = self::getAutosummary( $oldtext, $text, $flags );
+ if ( !$old_content ) $old_content = null;
+ $summary = $handler->getAutosummary( $old_content, $content, $flags );
}
- $editInfo = $this->prepareTextForEdit( $text, null, $user );
- $text = $editInfo->pst;
- $newsize = strlen( $text );
+ $editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format );
+ $serialized = $editInfo->pst;
+ $content = $editInfo->pstContent;
+ $newsize = $content->getSize();
$dbw = wfGetDB( DB_MASTER );
$now = wfTimestampNow();
$this->mTimestamp = $now;
if ( $flags & EDIT_UPDATE ) {
- # Update article, but only if changed.
+ // Update article, but only if changed.
$status->value['new'] = false;
if ( !$oldid ) {
- # Article gone missing
+ // Article gone missing
wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
$status->fatal( 'edit-gone-missing' );
wfProfileOut( __METHOD__ );
return $status;
- } elseif ( $oldtext === false ) {
- # Sanity check for bug 37225
+ } elseif ( !$old_content ) {
+ // Sanity check for bug 37225
wfProfileOut( __METHOD__ );
throw new MWException( "Could not find text for current revision {$oldid}." );
}
$revision = new Revision( array(
'page' => $this->getId(),
+ 'title' => $this->getTitle(), // for determining the default content model
'comment' => $summary,
'minor_edit' => $isminor,
- 'text' => $text,
+ 'text' => $serialized,
+ 'len' => $newsize,
'parent_id' => $oldid,
'user' => $user->getId(),
'user_text' => $user->getName(),
- 'timestamp' => $now
- ) );
- # Bug 37225: use accessor to get the text as Revision may trim it.
- # After trimming, the text may be a duplicate of the current text.
- $text = $revision->getText(); // sanity; EditPage should trim already
+ 'timestamp' => $now,
+ 'content_model' => $content->getModel(),
+ 'content_format' => $serialisation_format,
+ ) ); // XXX: pass content object?!
- $changed = ( strcmp( $text, $oldtext ) != 0 );
+ $changed = !$content->equals( $old_content );
if ( $changed ) {
+ if ( !$content->isValid() ) {
+ throw new MWException( "New content failed validity check!" );
+ }
+
$dbw->begin( __METHOD__ );
+
+ $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+ $status->merge( $prepStatus );
+
+ if ( !$status->isOK() ) {
+ $dbw->rollback( __METHOD__ );
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
$revisionId = $revision->insertOn( $dbw );
- # Update page
- #
- # Note that we use $this->mLatest instead of fetching a value from the master DB
- # during the course of this function. This makes sure that EditPage can detect
- # edit conflicts reliably, either by $ok here, or by $article->getTimestamp()
- # before this function is called. A previous function used a separate query, this
- # creates a window where concurrent edits can cause an ignored edit conflict.
+ // Update page
+ //
+ // Note that we use $this->mLatest instead of fetching a value from the master DB
+ // during the course of this function. This makes sure that EditPage can detect
+ // edit conflicts reliably, either by $ok here, or by $article->getTimestamp()
+ // before this function is called. A previous function used a separate query, this
+ // creates a window where concurrent edits can cause an ignored edit conflict.
$ok = $this->updateRevisionOn( $dbw, $revision, $oldid, $oldIsRedirect );
if ( !$ok ) {
- # Belated edit conflict! Run away!!
+ // Belated edit conflict! Run away!!
$status->fatal( 'edit-conflict' );
$dbw->rollback( __METHOD__ );
@@ -1523,18 +1768,18 @@ class WikiPage extends Page implements IDBAccessObject {
}
wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
- # Update recentchanges
+ // Update recentchanges
if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
- # Mark as patrolled if the user can do so
+ // Mark as patrolled if the user can do so
$patrolled = $wgUseRCPatrol && !count(
$this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
- # Add RC row to the DB
+ // Add RC row to the DB
$rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary,
$oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
$revisionId, $patrolled
);
- # Log auto-patrolled edits
+ // Log auto-patrolled edits
if ( $patrolled ) {
PatrolLog::record( $rc, true, $user );
}
@@ -1547,9 +1792,15 @@ class WikiPage extends Page implements IDBAccessObject {
$revision->setId( $this->getLatest() );
}
- # Update links tables, site stats, etc.
- $this->doEditUpdates( $revision, $user, array( 'changed' => $changed,
- 'oldcountable' => $oldcountable ) );
+ // Update links tables, site stats, etc.
+ $this->doEditUpdates(
+ $revision,
+ $user,
+ array(
+ 'changed' => $changed,
+ 'oldcountable' => $oldcountable
+ )
+ );
if ( !$changed ) {
$status->warning( 'edit-no-change' );
@@ -1559,13 +1810,25 @@ class WikiPage extends Page implements IDBAccessObject {
$this->mTitle->invalidateCache();
}
} else {
- # Create new article
+ // Create new article
$status->value['new'] = true;
$dbw->begin( __METHOD__ );
- # Add the page record; stake our claim on this title!
- # This will return false if the article already exists
+ $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+ $status->merge( $prepStatus );
+
+ if ( !$status->isOK() ) {
+ $dbw->rollback( __METHOD__ );
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ $status->merge( $prepStatus );
+
+ // Add the page record; stake our claim on this title!
+ // This will return false if the article already exists
$newid = $this->insertOn( $dbw );
if ( $newid === false ) {
@@ -1576,36 +1839,44 @@ class WikiPage extends Page implements IDBAccessObject {
return $status;
}
- # Save the revision text...
+ // Save the revision text...
$revision = new Revision( array(
'page' => $newid,
+ 'title' => $this->getTitle(), // for determining the default content model
'comment' => $summary,
'minor_edit' => $isminor,
- 'text' => $text,
+ 'text' => $serialized,
+ 'len' => $newsize,
'user' => $user->getId(),
'user_text' => $user->getName(),
- 'timestamp' => $now
+ 'timestamp' => $now,
+ 'content_model' => $content->getModel(),
+ 'content_format' => $serialisation_format,
) );
$revisionId = $revision->insertOn( $dbw );
- # Bug 37225: use accessor to get the text as Revision may trim it
- $text = $revision->getText(); // sanity; EditPage should trim already
+ // Bug 37225: use accessor to get the text as Revision may trim it
+ $content = $revision->getContent(); // sanity; get normalized version
- # Update the page record with revision data
+ if ( $content ) {
+ $newsize = $content->getSize();
+ }
+
+ // Update the page record with revision data
$this->updateRevisionOn( $dbw, $revision, 0 );
wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
- # Update recentchanges
+ // Update recentchanges
if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
- # Mark as patrolled if the user can do so
+ // Mark as patrolled if the user can do so
$patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count(
$this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
- # Add RC row to the DB
+ // Add RC row to the DB
$rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
- '', strlen( $text ), $revisionId, $patrolled );
+ '', $newsize, $revisionId, $patrolled );
- # Log auto-patrolled edits
+ // Log auto-patrolled edits
if ( $patrolled ) {
PatrolLog::record( $rc, true, $user );
}
@@ -1613,14 +1884,17 @@ class WikiPage extends Page implements IDBAccessObject {
$user->incEditCount();
$dbw->commit( __METHOD__ );
- # Update links, etc.
+ // Update links, etc.
$this->doEditUpdates( $revision, $user, array( 'created' => true ) );
- wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $text, $summary,
- $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
+ $hook_args = array( &$this, &$user, $content, $summary,
+ $flags & EDIT_MINOR, null, null, &$flags, $revision );
+
+ ContentHandler::runLegacyHooks( 'ArticleInsertComplete', $hook_args );
+ wfRunHooks( 'PageContentInsertComplete', $hook_args );
}
- # Do updates right now unless deferral was requested
+ // Do updates right now unless deferral was requested
if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
DeferredUpdates::doUpdates();
}
@@ -1628,10 +1902,13 @@ class WikiPage extends Page implements IDBAccessObject {
// Return the new revision (or null) to the caller
$status->value['revision'] = $revision;
- wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $text, $summary,
- $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
+ $hook_args = array( &$this, &$user, $content, $summary,
+ $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId );
- # Promote user to any groups they meet the criteria for
+ ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args );
+ wfRunHooks( 'PageContentSaveComplete', $hook_args );
+
+ // Promote user to any groups they meet the criteria for
$user->addAutopromoteOnceGroups( 'onEdit' );
wfProfileOut( __METHOD__ );
@@ -1641,6 +1918,8 @@ class WikiPage extends Page implements IDBAccessObject {
/**
* Get parser options suitable for rendering the primary article wikitext
*
+ * @see ContentHandler::makeParserOptions
+ *
* @param IContextSource|User|string $context One of the following:
* - IContextSource: Use the User and the Language of the provided
* context
@@ -1651,38 +1930,52 @@ class WikiPage extends Page implements IDBAccessObject {
* @return ParserOptions
*/
public function makeParserOptions( $context ) {
- global $wgContLang;
-
- if ( $context instanceof IContextSource ) {
- $options = ParserOptions::newFromContext( $context );
- } elseif ( $context instanceof User ) { // settings per user (even anons)
- $options = ParserOptions::newFromUser( $context );
- } else { // canonical settings
- $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
- }
+ $options = $this->getContentHandler()->makeParserOptions( $context );
if ( $this->getTitle()->isConversionTable() ) {
+ //@todo: ConversionTable should become a separate content model, so we don't need special cases like this one.
$options->disableContentConversion();
}
- $options->enableLimitReport(); // show inclusion/loop reports
- $options->setTidy( true ); // fix bad HTML
-
return $options;
}
/**
* Prepare text which is about to be saved.
* Returns a stdclass with source, pst and output members
- * @return bool|object
+ *
+ * @deprecated in 1.21: use prepareContentForEdit instead.
*/
public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
- global $wgParser, $wgContLang, $wgUser;
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+ return $this->prepareContentForEdit( $content, $revid, $user );
+ }
+
+ /**
+ * Prepare content which is about to be saved.
+ * Returns a stdclass with source, pst and output members
+ *
+ * @param \Content $content
+ * @param null $revid
+ * @param null|\User $user
+ * @param null $serialization_format
+ *
+ * @return bool|object
+ *
+ * @since 1.21
+ */
+ public function prepareContentForEdit( Content $content, $revid = null, User $user = null, $serialization_format = null ) {
+ global $wgContLang, $wgUser;
$user = is_null( $user ) ? $wgUser : $user;
- // @TODO fixme: check $user->getId() here???
+ //XXX: check $user->getId() here???
+
if ( $this->mPreparedEdit
- && $this->mPreparedEdit->newText == $text
+ && $this->mPreparedEdit->newContent
+ && $this->mPreparedEdit->newContent->equals( $content )
&& $this->mPreparedEdit->revid == $revid
+ && $this->mPreparedEdit->format == $serialization_format
+ // XXX: also check $user here?
) {
// Already prepared
return $this->mPreparedEdit;
@@ -1693,14 +1986,22 @@ class WikiPage extends Page implements IDBAccessObject {
$edit = (object)array();
$edit->revid = $revid;
- $edit->newText = $text;
- $edit->pst = $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
+
+ $edit->pstContent = $content ? $content->preSaveTransform( $this->mTitle, $user, $popts ) : null;
+
+ $edit->format = $serialization_format;
$edit->popts = $this->makeParserOptions( 'canonical' );
- $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $edit->popts, true, true, $revid );
- $edit->oldText = $this->getRawText();
+ $edit->output = $edit->pstContent ? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts ) : null;
- $this->mPreparedEdit = $edit;
+ $edit->newContent = $content;
+ $edit->oldContent = $this->getContent( Revision::RAW );
+ // NOTE: B/C for hooks! don't use these fields!
+ $edit->newText = $edit->newContent ? ContentHandler::getContentText( $edit->newContent ) : '';
+ $edit->oldText = $edit->oldContent ? ContentHandler::getContentText( $edit->oldContent ) : '';
+ $edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialization_format ) : '';
+
+ $this->mPreparedEdit = $edit;
return $edit;
}
@@ -1710,10 +2011,9 @@ class WikiPage extends Page implements IDBAccessObject {
* 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:
+ * @param array $options 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):
@@ -1727,27 +2027,29 @@ class WikiPage extends Page implements IDBAccessObject {
wfProfileIn( __METHOD__ );
$options += array( 'changed' => true, 'created' => false, 'oldcountable' => null );
- $text = $revision->getText();
+ $content = $revision->getContent();
- # Parse the text
- # Be careful not to double-PST: $text is usually already PST-ed once
+ // 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 );
+ $editInfo = $this->prepareContentForEdit( $content, $revision->getId(), $user );
} else {
wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
$editInfo = $this->mPreparedEdit;
}
- # Save it to the parser cache
+ // Save it to the parser cache
if ( $wgEnableParserCache ) {
$parserCache = ParserCache::singleton();
$parserCache->save( $editInfo->output, $this, $editInfo->popts );
}
- # Update the links tables and other secondary data
- $updates = $editInfo->output->getSecondaryDataUpdates( $this->mTitle );
- DataUpdate::runUpdates( $updates );
+ // Update the links tables and other secondary data
+ if ( $content ) {
+ $updates = $content->getSecondaryDataUpdates( $this->getTitle(), null, true, $editInfo->output );
+ DataUpdate::runUpdates( $updates );
+ }
wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
@@ -1761,7 +2063,7 @@ class WikiPage extends Page implements IDBAccessObject {
$cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
$dbw->delete(
'recentchanges',
- array( "rc_timestamp < '$cutoff'" ),
+ array( 'rc_timestamp < ' . $dbw->addQuotes( $cutoff ) ),
__METHOD__
);
}
@@ -1791,11 +2093,12 @@ class WikiPage extends Page implements IDBAccessObject {
}
DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, $good, $total ) );
- DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $text ) );
+ DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $content->getTextForSearchIndex() ) );
+ // @TODO: let the search engine decide what to do with the content object
- # 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 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()
@@ -1817,7 +2120,11 @@ class WikiPage extends Page implements IDBAccessObject {
}
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
- MessageCache::singleton()->replace( $shortTitle, $text );
+ // XXX: could skip pseudo-messages like js/css here, based on content model.
+ $msgtext = $content ? $content->getWikitextForTransclusion() : null;
+ if ( $msgtext === false || $msgtext === null ) $msgtext = '';
+
+ MessageCache::singleton()->replace( $shortTitle, $msgtext );
}
if( $options['created'] ) {
@@ -1834,21 +2141,45 @@ class WikiPage extends Page implements IDBAccessObject {
* The article must already exist; link tables etc
* are not updated, caches are not flushed.
*
- * @param $text String: text submitted
+ * @param string $text text submitted
* @param $user User The relevant user
- * @param $comment String: comment submitted
+ * @param string $comment comment submitted
* @param $minor Boolean: whereas it's a minor modification
+ *
+ * @deprecated since 1.21, use doEditContent() instead.
*/
public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
+ ContentHandler::deprecated( __METHOD__, "1.21" );
+
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+ return $this->doQuickEditContent( $content, $user, $comment, $minor );
+ }
+
+ /**
+ * 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 $content Content: content submitted
+ * @param $user User The relevant user
+ * @param string $comment comment submitted
+ * @param $serialisation_format String: format for storing the content in the database
+ * @param $minor Boolean: whereas it's a minor modification
+ */
+ public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = 0, $serialisation_format = null ) {
wfProfileIn( __METHOD__ );
+ $serialized = $content->serialize( $serialisation_format );
+
$dbw = wfGetDB( DB_MASTER );
$revision = new Revision( array(
+ 'title' => $this->getTitle(), // for determining the default content model
'page' => $this->getId(),
- 'text' => $text,
+ 'text' => $serialized,
+ 'length' => $content->getSize(),
'comment' => $comment,
'minor_edit' => $minor ? 1 : 0,
- ) );
+ ) ); // XXX: set the content object?
$revision->insertOn( $dbw );
$this->updateRevisionOn( $dbw, $revision );
@@ -1861,10 +2192,10 @@ class WikiPage extends Page implements IDBAccessObject {
* 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 array $limit 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 array $expiry per restriction type expiration
* @param $user User The user updating the restrictions
* @return Status
*/
@@ -1886,8 +2217,8 @@ class WikiPage extends Page implements IDBAccessObject {
// 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.
+ // @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;
@@ -1904,7 +2235,7 @@ class WikiPage extends Page implements IDBAccessObject {
$protect = true;
}
- # Get current restrictions on $action
+ // Get current restrictions on $action
$current = implode( '', $this->mTitle->getRestrictions( $action ) );
if ( $current != '' ) {
$isProtected = true;
@@ -1913,9 +2244,9 @@ class WikiPage extends Page implements IDBAccessObject {
if ( $limit[$action] != $current ) {
$changed = true;
} elseif ( $limit[$action] != '' ) {
- # Only check expiry change if the action is actually being
- # protected, since expiry does nothing on an not-protected
- # action.
+ // Only check expiry change if the action is actually being
+ // protected, since expiry does nothing on an not-protected
+ // action.
if ( $this->mTitle->getRestrictionExpiry( $action ) != $expiry[$action] ) {
$changed = true;
}
@@ -1926,12 +2257,12 @@ class WikiPage extends Page implements IDBAccessObject {
$changed = true;
}
- # If nothing's changed, do nothing
+ // If nothing has changed, do nothing
if ( !$changed ) {
return Status::newGood();
}
- if ( !$protect ) { # No protection at all means unprotection
+ if ( !$protect ) { // No protection at all means unprotection
$revCommentMsg = 'unprotectedarticle';
$logAction = 'unprotect';
} elseif ( $isProtected ) {
@@ -1944,42 +2275,64 @@ class WikiPage extends Page implements IDBAccessObject {
$encodedExpiry = array();
$protectDescription = '';
+ # Some bots may parse IRC lines, which are generated from log entries which contain plain
+ # protect description text. Keep them in old format to avoid breaking compatibility.
+ # TODO: Fix protection log to store structured description and format it on-the-fly.
+ $protectDescriptionLog = '';
foreach ( $limit as $action => $restrictions ) {
$encodedExpiry[$action] = $dbw->encodeExpiry( $expiry[$action] );
if ( $restrictions != '' ) {
- $protectDescription .= $wgContLang->getDirMark() . "[$action=$restrictions] (";
+ $protectDescriptionLog .= $wgContLang->getDirMark() . "[$action=$restrictions] (";
+ # $action is one of $wgRestrictionTypes = array( 'create', 'edit', 'move', 'upload' ).
+ # All possible message keys are listed here for easier grepping:
+ # * restriction-create
+ # * restriction-edit
+ # * restriction-move
+ # * restriction-upload
+ $actionText = wfMessage( 'restriction-' . $action )->inContentLanguage()->text();
+ # $restrictions is one of $wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' ),
+ # with '' filtered out. All possible message keys are listed below:
+ # * protect-level-autoconfirmed
+ # * protect-level-sysop
+ $restrictionsText = wfMessage( 'protect-level-' . $restrictions )->inContentLanguage()->text();
if ( $encodedExpiry[$action] != 'infinity' ) {
- $protectDescription .= wfMessage(
+ $expiryText = wfMessage(
'protect-expiring',
- $wgContLang->timeanddate( $expiry[$action], false, false ) ,
- $wgContLang->date( $expiry[$action], false, false ) ,
+ $wgContLang->timeanddate( $expiry[$action], false, false ),
+ $wgContLang->date( $expiry[$action], false, false ),
$wgContLang->time( $expiry[$action], false, false )
)->inContentLanguage()->text();
} else {
- $protectDescription .= wfMessage( 'protect-expiry-indefinite' )
+ $expiryText = wfMessage( 'protect-expiry-indefinite' )
->inContentLanguage()->text();
}
- $protectDescription .= ') ';
+ if ( $protectDescription !== '' ) {
+ $protectDescription .= wfMessage( 'word-separator' )->inContentLanguage()->text();
+ }
+ $protectDescription .= wfMessage( 'protect-summary-desc' )
+ ->params( $actionText, $restrictionsText, $expiryText )
+ ->inContentLanguage()->text();
+ $protectDescriptionLog .= $expiryText . ') ';
}
}
- $protectDescription = trim( $protectDescription );
+ $protectDescriptionLog = trim( $protectDescriptionLog );
- if ( $id ) { # Protection of existing page
+ if ( $id ) { // Protection of existing page
if ( !wfRunHooks( 'ArticleProtect', array( &$this, &$user, $limit, $reason ) ) ) {
return Status::newGood();
}
- # Only restrictions with the 'protect' right can cascade...
- # Otherwise, people who cannot normally protect can "protect" pages via transclusion
+ // Only restrictions with the 'protect' right can cascade...
+ // Otherwise, people who cannot normally protect can "protect" pages via transclusion
$editrestriction = isset( $limit['edit'] ) ? array( $limit['edit'] ) : $this->mTitle->getRestrictions( 'edit' );
- # The schema allows multiple restrictions
+ // The schema allows multiple restrictions
if ( !in_array( 'protect', $editrestriction ) && !in_array( 'sysop', $editrestriction ) ) {
$cascade = false;
}
- # Update restrictions table
+ // Update restrictions table
foreach ( $limit as $action => $restrictions ) {
if ( $restrictions != '' ) {
$dbw->replace( 'page_restrictions', array( array( 'pr_page', 'pr_type' ) ),
@@ -1997,7 +2350,7 @@ class WikiPage extends Page implements IDBAccessObject {
}
}
- # Prepare a null revision to be added to the history
+ // Prepare a null revision to be added to the history
$editComment = $wgContLang->ucfirst(
wfMessage(
$revCommentMsg,
@@ -2005,23 +2358,25 @@ class WikiPage extends Page implements IDBAccessObject {
)->inContentLanguage()->text()
);
if ( $reason ) {
- $editComment .= ": $reason";
+ $editComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
}
if ( $protectDescription ) {
- $editComment .= " ($protectDescription)";
+ $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
+ $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )->inContentLanguage()->text();
}
if ( $cascade ) {
- // FIXME: Should use 'brackets' message.
- $editComment .= ' [' . wfMessage( 'protect-summary-cascade' )
- ->inContentLanguage()->text() . ']';
+ $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
+ $editComment .= wfMessage( 'brackets' )->params(
+ wfMessage( 'protect-summary-cascade' )->inContentLanguage()->text()
+ )->inContentLanguage()->text();
}
- # Insert a null revision
+ // Insert a null revision
$nullRevision = Revision::newNullRevision( $dbw, $id, $editComment, true );
$nullRevId = $nullRevision->insertOn( $dbw );
$latest = $this->getLatest();
- # Update page record
+ // Update page record
$dbw->update( 'page',
array( /* SET */
'page_touched' => $dbw->timestamp(),
@@ -2034,8 +2389,8 @@ class WikiPage extends Page implements IDBAccessObject {
wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) );
wfRunHooks( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) );
- } else { # Protection of non-existing page (also known as "title protection")
- # Cascade protection is meaningless in this case
+ } else { // Protection of non-existing page (also known as "title protection")
+ // Cascade protection is meaningless in this case
$cascade = false;
if ( $limit['create'] != '' ) {
@@ -2066,10 +2421,10 @@ class WikiPage extends Page implements IDBAccessObject {
if ( $logAction == 'unprotect' ) {
$logParams = array();
} else {
- $logParams = array( $protectDescription, $cascade ? 'cascade' : '' );
+ $logParams = array( $protectDescriptionLog, $cascade ? 'cascade' : '' );
}
- # Update the protection log
+ // Update the protection log
$log = new LogPage( 'protect' );
$log->addEntry( $logAction, $this->mTitle, trim( $reason ), $logParams, $user );
@@ -2107,10 +2462,10 @@ class WikiPage extends Page implements IDBAccessObject {
*
* Deletes the article with database consistency, writes logs, purges caches
*
- * @param $reason string delete reason for deletion log
+ * @param string $reason delete reason for deletion log
* @param $suppress boolean suppress all revisions and log the deletion in
* the suppression log instead of the deletion log
- * @param $id int article ID
+ * @param int $id article ID
* @param $commit boolean defaults to true, triggers transaction end
* @param &$error Array of errors to append to
* @param $user User The deleting user
@@ -2129,9 +2484,10 @@ class WikiPage extends Page implements IDBAccessObject {
*
* @since 1.19
*
- * @param $reason string delete reason for deletion log
+ * @param string $reason delete reason for deletion log
* @param $suppress boolean suppress all revisions and log the deletion in
* the suppression log instead of the deletion log
+ * @param int $id article ID
* @param $commit boolean defaults to true, triggers transaction end
* @param &$error Array of errors to append to
* @param $user User The deleting user
@@ -2142,7 +2498,7 @@ class WikiPage extends Page implements IDBAccessObject {
public function doDeleteArticleReal(
$reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
) {
- global $wgUser;
+ global $wgUser, $wgContentHandlerUseDB;
wfDebug( __METHOD__ . "\n" );
@@ -2183,6 +2539,9 @@ class WikiPage extends Page implements IDBAccessObject {
$bitfield = 'rev_deleted';
}
+ // we need to remember the old content so we can use it to generate all deletion updates.
+ $content = $this->getContent( Revision::RAW );
+
$dbw = wfGetDB( DB_MASTER );
$dbw->begin( __METHOD__ );
// For now, shunt the revision data into the archive table.
@@ -2195,31 +2554,40 @@ class WikiPage extends Page implements IDBAccessObject {
//
// In the future, we may keep revisions and mark them with
// the rev_deleted field, which is reserved for this purpose.
+
+ $row = array(
+ 'ar_namespace' => 'page_namespace',
+ 'ar_title' => 'page_title',
+ 'ar_comment' => 'rev_comment',
+ 'ar_user' => 'rev_user',
+ 'ar_user_text' => 'rev_user_text',
+ 'ar_timestamp' => 'rev_timestamp',
+ 'ar_minor_edit' => 'rev_minor_edit',
+ 'ar_rev_id' => 'rev_id',
+ 'ar_parent_id' => 'rev_parent_id',
+ 'ar_text_id' => 'rev_text_id',
+ 'ar_text' => '\'\'', // Be explicit to appease
+ 'ar_flags' => '\'\'', // MySQL's "strict mode"...
+ 'ar_len' => 'rev_len',
+ 'ar_page_id' => 'page_id',
+ 'ar_deleted' => $bitfield,
+ 'ar_sha1' => 'rev_sha1',
+ );
+
+ if ( $wgContentHandlerUseDB ) {
+ $row[ 'ar_content_model' ] = 'rev_content_model';
+ $row[ 'ar_content_format' ] = 'rev_content_format';
+ }
+
$dbw->insertSelect( 'archive', array( 'page', 'revision' ),
+ $row,
array(
- 'ar_namespace' => 'page_namespace',
- 'ar_title' => 'page_title',
- 'ar_comment' => 'rev_comment',
- 'ar_user' => 'rev_user',
- 'ar_user_text' => 'rev_user_text',
- 'ar_timestamp' => 'rev_timestamp',
- 'ar_minor_edit' => 'rev_minor_edit',
- 'ar_rev_id' => 'rev_id',
- 'ar_parent_id' => 'rev_parent_id',
- 'ar_text_id' => 'rev_text_id',
- 'ar_text' => '\'\'', // Be explicit to appease
- 'ar_flags' => '\'\'', // MySQL's "strict mode"...
- 'ar_len' => 'rev_len',
- 'ar_page_id' => 'page_id',
- 'ar_deleted' => $bitfield,
- 'ar_sha1' => 'rev_sha1'
- ), array(
'page_id' => $id,
'page_id = rev_page'
), __METHOD__
);
- # Now that it's safely backed up, delete it
+ // 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
@@ -2229,9 +2597,9 @@ class WikiPage extends Page implements IDBAccessObject {
return $status;
}
- $this->doDeleteUpdates( $id );
+ $this->doDeleteUpdates( $id, $content );
- # Log the deletion, if the page was suppressed, log it at Oversight instead
+ // Log the deletion, if the page was suppressed, log it at Oversight instead
$logtype = $suppress ? 'suppress' : 'delete';
$logEntry = new ManualLogEntry( $logtype, 'delete' );
@@ -2245,7 +2613,7 @@ class WikiPage extends Page implements IDBAccessObject {
$dbw->commit( __METHOD__ );
}
- wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id ) );
+ wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) );
$status->value = $logid;
return $status;
}
@@ -2253,36 +2621,28 @@ class WikiPage extends Page implements IDBAccessObject {
/**
* Do some database updates after deletion
*
- * @param $id Int: page_id value of the page being deleted (B/C, currently unused)
+ * @param int $id page_id value of the page being deleted (B/C, currently unused)
+ * @param $content Content: optional page content to be used when determining the required updates.
+ * This may be needed because $this->getContent() may already return null when the page proper was deleted.
*/
- public function doDeleteUpdates( $id ) {
- # update site status
+ public function doDeleteUpdates( $id, Content $content = null ) {
+ // update site status
DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 ) );
- # remove secondary indexes, etc
- $updates = $this->getDeletionUpdates( );
+ // remove secondary indexes, etc
+ $updates = $this->getDeletionUpdates( $content );
DataUpdate::runUpdates( $updates );
- # Clear caches
+ // Clear caches
WikiPage::onArticleDelete( $this->mTitle );
- # Reset this object
+ // Reset this object
$this->clear();
- # Clear the cached article id so the interface doesn't act like we exist
+ // Clear the cached article id so the interface doesn't act like we exist
$this->mTitle->resetArticleID( 0 );
}
- public function getDeletionUpdates() {
- $updates = array(
- new LinksDeletionUpdate( $this ),
- );
-
- //@todo: make a hook to add update objects
- //NOTE: deletion updates will be determined by the ContentHandler in the future
- return $updates;
- }
-
/**
* Roll back the most recent consecutive set of edits to a page
* from the same user; fails if there are no eligible edits to
@@ -2290,14 +2650,14 @@ class WikiPage extends Page implements IDBAccessObject {
* performs permissions checks on $user, then calls commitRollback()
* to do the dirty work
*
- * @todo: seperate the business/permission stuff out from backend code
+ * @todo: separate the business/permission stuff out from backend code
*
- * @param $fromP String: Name of the user whose edits to rollback.
- * @param $summary String: Custom summary. Set to default summary if empty.
- * @param $token String: Rollback token.
+ * @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 $bot Boolean: If true, mark all reverted edits as bot.
*
- * @param $resultDetails Array: contains result-specific array of additional values
+ * @param array $resultDetails contains result-specific array of additional values
* 'alreadyrolled' : 'current' (rev)
* success : 'summary' (str), 'current' (rev), 'target' (rev)
*
@@ -2312,7 +2672,7 @@ class WikiPage extends Page implements IDBAccessObject {
) {
$resultDetails = null;
- # Check permissions
+ // Check permissions
$editErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $user );
$rollbackErrors = $this->mTitle->getUserPermissionsErrors( 'rollback', $user );
$errors = array_merge( $editErrors, wfArrayDiff2( $rollbackErrors, $editErrors ) );
@@ -2325,7 +2685,7 @@ class WikiPage extends Page implements IDBAccessObject {
$errors[] = array( 'actionthrottledtext' );
}
- # If there were errors, bail out now
+ // If there were errors, bail out now
if ( !empty( $errors ) ) {
return $errors;
}
@@ -2341,11 +2701,11 @@ class WikiPage extends Page implements IDBAccessObject {
* 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.
- * @param $fromP String: Name of the user whose edits to rollback.
- * @param $summary String: Custom summary. Set to default summary if empty.
+ * @param string $fromP Name of the user whose edits to rollback.
+ * @param string $summary Custom summary. Set to default summary if empty.
* @param $bot Boolean: If true, mark all reverted edits as bot.
*
- * @param $resultDetails Array: contains result-specific array of additional values
+ * @param array $resultDetails contains result-specific array of additional values
* @param $guser User The user performing the rollback
* @return array
*/
@@ -2358,16 +2718,16 @@ class WikiPage extends Page implements IDBAccessObject {
return array( array( 'readonlytext' ) );
}
- # Get the last editor
+ // Get the last editor
$current = $this->getRevision();
if ( is_null( $current ) ) {
- # Something wrong... no page?
+ // Something wrong... no page?
return array( array( 'notanarticle' ) );
}
$from = str_replace( '_', ' ', $fromP );
- # User name given should match up with the top revision.
- # If the user was deleted then $from should be empty.
+ // User name given should match up with the top revision.
+ // If the user was deleted then $from should be empty.
if ( $from != $current->getUserText() ) {
$resultDetails = array( 'current' => $current );
return array( array( 'alreadyrolled',
@@ -2377,8 +2737,8 @@ class WikiPage extends Page implements IDBAccessObject {
) );
}
- # Get the last edit not by this guy...
- # Note: these may not be public values
+ // Get the last edit not by this guy...
+ // Note: these may not be public values
$user = intval( $current->getRawUser() );
$user_text = $dbw->addQuotes( $current->getRawUserText() );
$s = $dbw->selectRow( 'revision',
@@ -2390,21 +2750,21 @@ class WikiPage extends Page implements IDBAccessObject {
'ORDER BY' => 'rev_timestamp DESC' )
);
if ( $s === false ) {
- # No one else ever edited this page
+ // No one else ever edited this page
return array( array( 'cantrollback' ) );
} elseif ( $s->rev_deleted & Revision::DELETED_TEXT || $s->rev_deleted & Revision::DELETED_USER ) {
- # Only admins can see this text
+ // Only admins can see this text
return array( array( 'notvisiblerev' ) );
}
$set = array();
if ( $bot && $guser->isAllowed( 'markbotedits' ) ) {
- # Mark all reverted edits as bot
+ // Mark all reverted edits as bot
$set['rc_bot'] = 1;
}
if ( $wgUseRCPatrol ) {
- # Mark all reverted edits as patrolled
+ // Mark all reverted edits as patrolled
$set['rc_patrolled'] = 1;
}
@@ -2418,7 +2778,7 @@ class WikiPage extends Page implements IDBAccessObject {
);
}
- # Generate the edit summary if necessary
+ // Generate the edit summary if necessary
$target = Revision::newFromId( $s->rev_id );
if ( empty( $summary ) ) {
if ( $from == '' ) { // no public user name
@@ -2428,7 +2788,7 @@ class WikiPage extends Page implements IDBAccessObject {
}
}
- # Allow the custom summary to use the same args as the default message
+ // 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 ) ),
@@ -2440,10 +2800,13 @@ class WikiPage extends Page implements IDBAccessObject {
$summary = wfMsgReplaceArgs( $summary, $args );
}
- # Truncate for whole multibyte characters.
+ // Trim spaces on user supplied text
+ $summary = trim( $summary );
+
+ // Truncate for whole multibyte characters.
$summary = $wgContLang->truncate( $summary, 255 );
- # Save
+ // Save
$flags = EDIT_UPDATE;
if ( $guser->isAllowed( 'minoredit' ) ) {
@@ -2454,8 +2817,13 @@ class WikiPage extends Page implements IDBAccessObject {
$flags |= EDIT_FORCE_BOT;
}
- # Actually store the edit
- $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId(), $guser );
+ // Actually store the edit
+ $status = $this->doEditContent( $target->getContent(), $summary, $flags, $target->getId(), $guser );
+
+ if ( !$status->isOK() ) {
+ return $status->getErrorsArray();
+ }
+
if ( !empty( $status->value['revision'] ) ) {
$revId = $status->value['revision']->getId();
} else {
@@ -2486,7 +2854,7 @@ class WikiPage extends Page implements IDBAccessObject {
* @param $title Title object
*/
public static function onArticleCreate( $title ) {
- # Update existence markers on article/talk tabs...
+ // Update existence markers on article/talk tabs...
if ( $title->isTalkPage() ) {
$other = $title->getSubjectPage();
} else {
@@ -2507,7 +2875,7 @@ class WikiPage extends Page implements IDBAccessObject {
* @param $title Title
*/
public static function onArticleDelete( $title ) {
- # Update existence markers on article/talk tabs...
+ // Update existence markers on article/talk tabs...
if ( $title->isTalkPage() ) {
$other = $title->getSubjectPage();
} else {
@@ -2520,21 +2888,21 @@ class WikiPage extends Page implements IDBAccessObject {
$title->touchLinks();
$title->purgeSquid();
- # File cache
+ // File cache
HTMLFileCache::clearFileCache( $title );
- # Messages
+ // Messages
if ( $title->getNamespace() == NS_MEDIAWIKI ) {
MessageCache::singleton()->replace( $title->getDBkey(), false );
}
- # Images
+ // Images
if ( $title->getNamespace() == NS_FILE ) {
$update = new HTMLCacheUpdate( $title, 'imagelinks' );
$update->doUpdate();
}
- # User talk pages
+ // User talk pages
if ( $title->getNamespace() == NS_USER_TALK ) {
$user = User::newFromName( $title->getText(), false );
if ( $user ) {
@@ -2542,7 +2910,7 @@ class WikiPage extends Page implements IDBAccessObject {
}
}
- # Image redirects
+ // Image redirects
RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
}
@@ -2556,14 +2924,13 @@ class WikiPage extends Page implements IDBAccessObject {
// Invalidate caches of articles which include this page
DeferredUpdates::addHTMLCacheUpdate( $title, 'templatelinks' );
-
// Invalidate the caches of all pages which redirect here
DeferredUpdates::addHTMLCacheUpdate( $title, 'redirect' );
- # Purge squid for this page only
+ // Purge squid for this page only
$title->purgeSquid();
- # Clear file cache for this page only
+ // Clear file cache for this page only
HTMLFileCache::clearFileCache( $title );
}
@@ -2600,61 +2967,24 @@ class WikiPage extends Page implements IDBAccessObject {
}
/**
- * Return an applicable autosummary if one exists for the given edit.
- * @param $oldtext String: the previous text of the page.
- * @param $newtext String: The submitted text of the page.
- * @param $flags Int bitmask: a bitmask of flags submitted for the edit.
- * @return string An appropriate autosummary, or an empty string.
- */
+ * Return an applicable autosummary if one exists for the given edit.
+ * @param string|null $oldtext the previous text of the page.
+ * @param string|null $newtext The submitted text of the page.
+ * @param int $flags bitmask: a bitmask of flags submitted for the edit.
+ * @return string An appropriate autosummary, or an empty string.
+ *
+ * @deprecated since 1.21, use ContentHandler::getAutosummary() instead
+ */
public static function getAutosummary( $oldtext, $newtext, $flags ) {
- global $wgContLang;
-
- # Decide what kind of autosummary is needed.
-
- # Redirect autosummaries
- $ot = Title::newFromRedirect( $oldtext );
- $rt = Title::newFromRedirect( $newtext );
-
- if ( is_object( $rt ) && ( !is_object( $ot ) || !$rt->equals( $ot ) || $ot->getFragment() != $rt->getFragment() ) ) {
- $truncatedtext = $wgContLang->truncate(
- str_replace( "\n", ' ', $newtext ),
- max( 0, 255
- - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() )
- - strlen( $rt->getFullText() )
- ) );
- return wfMessage( 'autoredircomment', $rt->getFullText() )
- ->rawParams( $truncatedtext )->inContentLanguage()->text();
- }
-
- # New page autosummaries
- if ( $flags & EDIT_NEW && strlen( $newtext ) ) {
- # If they're making a new article, give its text, truncated, in the summary.
-
- $truncatedtext = $wgContLang->truncate(
- str_replace( "\n", ' ', $newtext ),
- max( 0, 200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) ) );
-
- return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext )
- ->inContentLanguage()->text();
- }
+ // NOTE: stub for backwards-compatibility. assumes the given text is wikitext. will break horribly if it isn't.
- # Blanking autosummaries
- if ( $oldtext != '' && $newtext == '' ) {
- return wfMessage( 'autosumm-blank' )->inContentLanguage()->text();
- } elseif ( strlen( $oldtext ) > 10 * strlen( $newtext ) && strlen( $newtext ) < 500 ) {
- # Removing more than 90% of the article
+ ContentHandler::deprecated( __METHOD__, '1.21' );
- $truncatedtext = $wgContLang->truncate(
- $newtext,
- max( 0, 200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) ) );
+ $handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT );
+ $oldContent = is_null( $oldtext ) ? null : $handler->unserializeContent( $oldtext );
+ $newContent = is_null( $newtext ) ? null : $handler->unserializeContent( $newtext );
- return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext )
- ->inContentLanguage()->text();
- }
-
- # If we reach this point, there's no applicable autosummary for our case, so our
- # autosummary is empty.
- return '';
+ return $handler->getAutosummary( $oldContent, $newContent, $flags );
}
/**
@@ -2665,117 +2995,29 @@ class WikiPage extends Page implements IDBAccessObject {
* if no revision occurred
*/
public function getAutoDeleteReason( &$hasHistory ) {
- global $wgContLang;
-
- // Get the last revision
- $rev = $this->getRevision();
-
- if ( is_null( $rev ) ) {
- return false;
- }
-
- // Get the article's contents
- $contents = $rev->getText();
- $blank = false;
-
- // If the page is blank, use the text from the previous revision,
- // which can only be blank if there's a move/import/protect dummy revision involved
- if ( $contents == '' ) {
- $prev = $rev->getPrevious();
-
- if ( $prev ) {
- $contents = $prev->getText();
- $blank = true;
- }
- }
-
- $dbw = wfGetDB( DB_MASTER );
-
- // Find out if there was only one contributor
- // Only scan the last 20 revisions
- $res = $dbw->select( 'revision', 'rev_user_text',
- array( 'rev_page' => $this->getID(), $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' ),
- __METHOD__,
- array( 'LIMIT' => 20 )
- );
-
- if ( $res === false ) {
- // This page has no revisions, which is very weird
- return false;
- }
-
- $hasHistory = ( $res->numRows() > 1 );
- $row = $dbw->fetchObject( $res );
-
- if ( $row ) { // $row is false if the only contributor is hidden
- $onlyAuthor = $row->rev_user_text;
- // Try to find a second contributor
- foreach ( $res as $row ) {
- if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999
- $onlyAuthor = false;
- break;
- }
- }
- } else {
- $onlyAuthor = false;
- }
-
- // Generate the summary with a '$1' placeholder
- if ( $blank ) {
- // The current revision is blank and the one before is also
- // blank. It's just not our lucky day
- $reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text();
- } else {
- if ( $onlyAuthor ) {
- $reason = wfMessage(
- 'excontentauthor',
- '$1',
- $onlyAuthor
- )->inContentLanguage()->text();
- } else {
- $reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text();
- }
- }
-
- if ( $reason == '-' ) {
- // Allow these UI messages to be blanked out cleanly
- return '';
- }
-
- // Replace newlines with spaces to prevent uglyness
- $contents = preg_replace( "/[\n\r]/", ' ', $contents );
- // Calculate the maximum amount of chars to get
- // Max content length = max comment length - length of the comment (excl. $1)
- $maxLength = 255 - ( strlen( $reason ) - 2 );
- $contents = $wgContLang->truncate( $contents, $maxLength );
- // Remove possible unfinished links
- $contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
- // Now replace the '$1' placeholder
- $reason = str_replace( '$1', $contents, $reason );
-
- return $reason;
+ return $this->getContentHandler()->getAutoDeleteReason( $this->getTitle(), $hasHistory );
}
/**
* Update all the appropriate counts in the category table, given that
* we've added the categories $added and deleted the categories $deleted.
*
- * @param $added array The names of categories that were added
- * @param $deleted array The names of categories that were deleted
+ * @param array $added The names of categories that were added
+ * @param array $deleted The names of categories that were deleted
*/
public function updateCategoryCounts( $added, $deleted ) {
$ns = $this->mTitle->getNamespace();
$dbw = wfGetDB( DB_MASTER );
- # First make sure the rows exist. If one of the "deleted" ones didn't
- # exist, we might legitimately not create it, but it's simpler to just
- # create it and then give it a negative value, since the value is bogus
- # anyway.
- #
- # Sometimes I wish we had INSERT ... ON DUPLICATE KEY UPDATE.
+ // First make sure the rows exist. If one of the "deleted" ones didn't
+ // exist, we might legitimately not create it, but it's simpler to just
+ // create it and then give it a negative value, since the value is bogus
+ // anyway.
+ //
+ // Sometimes I wish we had INSERT ... ON DUPLICATE KEY UPDATE.
$insertCats = array_merge( $added, $deleted );
if ( !$insertCats ) {
- # Okay, nothing to do
+ // Okay, nothing to do
return;
}
@@ -2789,14 +3031,14 @@ class WikiPage extends Page implements IDBAccessObject {
}
$dbw->insert( 'category', $insertRows, __METHOD__, 'IGNORE' );
- $addFields = array( 'cat_pages = cat_pages + 1' );
+ $addFields = array( 'cat_pages = cat_pages + 1' );
$removeFields = array( 'cat_pages = cat_pages - 1' );
if ( $ns == NS_CATEGORY ) {
- $addFields[] = 'cat_subcats = cat_subcats + 1';
+ $addFields[] = 'cat_subcats = cat_subcats + 1';
$removeFields[] = 'cat_subcats = cat_subcats - 1';
} elseif ( $ns == NS_FILE ) {
- $addFields[] = 'cat_files = cat_files + 1';
+ $addFields[] = 'cat_files = cat_files + 1';
$removeFields[] = 'cat_files = cat_files - 1';
}
@@ -2817,6 +3059,15 @@ class WikiPage extends Page implements IDBAccessObject {
__METHOD__
);
}
+
+ foreach( $added as $catName ) {
+ $cat = Category::newFromName( $catName );
+ wfRunHooks( 'CategoryAfterPageAdded', array( $cat, $this ) );
+ }
+ foreach( $deleted as $catName ) {
+ $cat = Category::newFromName( $catName );
+ wfRunHooks( 'CategoryAfterPageRemoved', array( $cat, $this ) );
+ }
}
/**
@@ -2836,7 +3087,7 @@ class WikiPage extends Page implements IDBAccessObject {
// that cascaded protections apply as soon as the changes
// are visible.
- # Get templates from templatelinks
+ // Get templates from templatelinks
$id = $this->mTitle->getArticleID();
$tlTemplates = array();
@@ -2852,7 +3103,7 @@ class WikiPage extends Page implements IDBAccessObject {
$tlTemplates["{$row->tl_namespace}:{$row->tl_title}"] = true;
}
- # Get templates from parser output.
+ // Get templates from parser output.
$poTemplates = array();
foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
foreach ( $templates as $dbk => $id ) {
@@ -2860,12 +3111,12 @@ class WikiPage extends Page implements IDBAccessObject {
}
}
- # Get the diff
+ // Get the diff
$templates_diff = array_diff_key( $poTemplates, $tlTemplates );
if ( count( $templates_diff ) > 0 ) {
- # Whee, link updates time.
- # Note: we are only interested in links here. We don't need to get other DataUpdate items from the parser output.
+ // Whee, link updates time.
+ // Note: we are only interested in links here. We don't need to get other DataUpdate items from the parser output.
$u = new LinksUpdate( $this->mTitle, $parserOutput, false );
$u->doUpdate();
}
@@ -2903,7 +3154,7 @@ class WikiPage extends Page implements IDBAccessObject {
* 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 string $text 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
@@ -2950,10 +3201,10 @@ class WikiPage extends Page implements IDBAccessObject {
* Update the article's restriction field, and leave a log entry.
*
* @deprecated since 1.19
- * @param $limit Array: set of restriction keys
+ * @param array $limit 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 array $expiry per restriction type expiration
* @param $user User The user updating the restrictions
* @return bool true on success
*/
@@ -2995,6 +3246,31 @@ class WikiPage extends Page implements IDBAccessObject {
global $wgUser;
return $this->isParserCacheUsed( ParserOptions::newFromUser( $wgUser ), $oldid );
}
+
+ /**
+ * Returns a list of updates to be performed when this page is deleted. The updates should remove any information
+ * about this page from secondary data stores such as links tables.
+ *
+ * @param Content|null $content optional Content object for determining the necessary updates
+ * @return Array an array of DataUpdates objects
+ */
+ public function getDeletionUpdates( Content $content = null ) {
+ if ( !$content ) {
+ // load content object, which may be used to determine the necessary updates
+ // XXX: the content may not be needed to determine the updates, then this would be overhead.
+ $content = $this->getContent( Revision::RAW );
+ }
+
+ if ( !$content ) {
+ $updates = array();
+ } else {
+ $updates = $content->getDeletionUpdates( $this );
+ }
+
+ wfRunHooks( 'WikiPageDeletionUpdates', array( $this, $content, &$updates ) );
+ return $updates;
+ }
+
}
class PoolWorkArticleView extends PoolCounterWork {
@@ -3020,9 +3296,9 @@ class PoolWorkArticleView extends PoolCounterWork {
private $parserOptions;
/**
- * @var string|null
+ * @var Content|null
*/
- private $text;
+ private $content = null;
/**
* @var ParserOutput|bool
@@ -3046,14 +3322,20 @@ class PoolWorkArticleView extends PoolCounterWork {
* @param $revid Integer: ID of the revision being parsed
* @param $useParserCache Boolean: whether to use the parser cache
* @param $parserOptions parserOptions to use for the parse operation
- * @param $text String: text to parse or null to load it
+ * @param $content Content|String: content to parse or null to load it; may also be given as a wikitext string, for BC
*/
- function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $text = null ) {
+ function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $content = null ) {
+ if ( is_string( $content ) ) { // BC: old style call
+ $modelId = $page->getRevision()->getContentModel();
+ $format = $page->getRevision()->getContentFormat();
+ $content = ContentHandler::makeContent( $content, $page->getTitle(), $modelId, $format );
+ }
+
$this->page = $page;
$this->revid = $revid;
$this->cacheable = $useParserCache;
$this->parserOptions = $parserOptions;
- $this->text = $text;
+ $this->content = $content;
$this->cacheKey = ParserCache::singleton()->getKey( $page, $parserOptions );
parent::__construct( 'ArticleView', $this->cacheKey . ':revid:' . $revid );
}
@@ -3089,28 +3371,38 @@ class PoolWorkArticleView extends PoolCounterWork {
* @return bool
*/
function doWork() {
- global $wgParser, $wgUseFileCache;
+ global $wgUseFileCache;
+
+ // @todo: several of the methods called on $this->page are not declared in Page, but present
+ // in WikiPage and delegated by Article.
$isCurrent = $this->revid === $this->page->getLatest();
- if ( $this->text !== null ) {
- $text = $this->text;
+ if ( $this->content !== null ) {
+ $content = $this->content;
} elseif ( $isCurrent ) {
- $text = $this->page->getRawText();
+ // XXX: why use RAW audience here, and PUBLIC (default) below?
+ $content = $this->page->getContent( Revision::RAW );
} else {
$rev = Revision::newFromTitle( $this->page->getTitle(), $this->revid );
+
if ( $rev === null ) {
- return false;
+ $content = null;
+ } else {
+ // XXX: why use PUBLIC audience here (default), and RAW above?
+ $content = $rev->getContent();
}
- $text = $rev->getText();
+ }
+
+ if ( $content === null ) {
+ return false;
}
$time = - microtime( true );
- $this->parserOutput = $wgParser->parse( $text, $this->page->getTitle(),
- $this->parserOptions, true, true, $this->revid );
+ $this->parserOutput = $content->getParserOutput( $this->page->getTitle(), $this->revid, $this->parserOptions );
$time += microtime( true );
- # Timing hack
+ // Timing hack
if ( $time > 3 ) {
wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
$this->page->getTitle()->getPrefixedDBkey() ) );