*/
public $mParserOptions;
- var $mContent; // !<
+ var $mContent; // !< #BC cruft
+
+ /**
+ * @var Content
+ */
+ var $mContentObject;
+
var $mContentLoaded = false; // !<
var $mOldId; // !<
if ( !$page ) {
switch( $title->getNamespace() ) {
case NS_FILE:
- $page = new ImagePage( $title );
+ $page = new ImagePage( $title ); #FIXME: teach ImagePage to use ContentHandler
break;
case NS_CATEGORY:
- $page = new CategoryPage( $title );
+ $page = new CategoryPage( $title ); #FIXME: teach ImagePage to use ContentHandler
break;
default:
- $page = new Article( $title );
+ $handler = ContentHandler::getForTitle( $title );
+ $page = $handler->createArticle( $title );
}
}
$page->setContext( $context );
* This function has side effects! Do not use this function if you
* only want the real revision text if any.
*
- * @return string Return the text of this revision
+ * @deprecated in 1.20; use getContentObject() instead
+ *
+ * @return string The text of this revision
*/
public function getContent() {
+ wfDeprecated( __METHOD__, '1.20' );
+ $content = $this->getContentObject();
+ return ContentHandler::getContentText( $content );
+ }
+
+ /**
+ * Note that getContent/loadContent do not follow redirects anymore.
+ * If you need to fetch redirectable content easily, try
+ * the shortcut in WikiPage::getRedirectTarget()
+ *
+ * This function has side effects! Do not use this function if you
+ * only want the real revision text if any.
+ *
+ * @return Content
+ */
+ public function getContentObject() {
global $wgUser;
wfProfileIn( __METHOD__ );
if ( $text === false ) {
$text = '';
}
+
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
} else {
- $text = wfMsgExt( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon', 'parsemag' );
+ $content = new MessageContent( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon', null, 'parsemag' );
}
wfProfileOut( __METHOD__ );
- return $text;
+ return $content;
} else {
- $this->fetchContent();
+ $this->fetchContentObject();
wfProfileOut( __METHOD__ );
- return $this->mContent;
+ return $this->mContentObject;
}
}
if ( $oldid !== 0 ) {
# Load the given revision and check whether the page is another one.
# In that case, update this instance to reflect the change.
- $this->mRevision = Revision::newFromId( $oldid );
- if ( $this->mRevision !== null ) {
- // Revision title doesn't match the page title given?
- if ( $this->mPage->getID() != $this->mRevision->getPage() ) {
- $function = array( get_class( $this->mPage ), 'newFromID' );
- $this->mPage = call_user_func( $function, $this->mRevision->getPage() );
+ if ( $oldid === $this->mPage->getLatest() ) {
+ $this->mRevision = $this->mPage->getRevision();
+ } else {
+ $this->mRevision = Revision::newFromId( $oldid );
+ if ( $this->mRevision !== null ) {
+ // Revision title doesn't match the page title given?
+ if ( $this->mPage->getID() != $this->mRevision->getPage() ) {
+ $function = array( get_class( $this->mPage ), 'newFromID' );
+ $this->mPage = call_user_func( $function, $this->mRevision->getPage() );
+ }
}
}
}
* Does *NOT* follow redirects.
*
* @return mixed string containing article contents, or false if null
+ * @deprecated in 1.20, use getContentObject() instead
*/
- function fetchContent() {
- if ( $this->mContentLoaded ) {
+ protected function fetchContent() { #BC cruft!
+ wfDeprecated( __METHOD__, '1.20' );
+
+ if ( $this->mContentLoaded && $this->mContent ) {
return $this->mContent;
}
wfProfileIn( __METHOD__ );
+ $content = $this->fetchContentObject();
+
+ $this->mContent = ContentHandler::getContentText( $content ); #FIXME: get rid of mContent everywhere!
+ wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) ); #BC cruft!
+
+ wfProfileOut( __METHOD__ );
+
+ return $this->mContent;
+ }
+
+
+ /**
+ * Get text content object
+ * Does *NOT* follow redirects.
+ * TODO: when is this null?
+ *
+ * @return Content|null
+ */
+ protected function fetchContentObject() {
+ if ( $this->mContentLoaded ) {
+ return $this->mContentObject;
+ }
+
+ wfProfileIn( __METHOD__ );
+
$this->mContentLoaded = true;
+ $this->mContent = null;
$oldid = $this->getOldID();
# fails we'll have something telling us what we intended.
$t = $this->getTitle()->getPrefixedText();
$d = $oldid ? wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid ) : '';
- $this->mContent = wfMsgNoTrans( 'missing-article', $t, $d ) ;
+ $this->mContentObject = new MessageContent( 'missing-article', array($t, $d), array() ) ;
if ( $oldid ) {
# $this->mRevision might already be fetched by getOldIDFromRequest()
}
$this->mRevision = $this->mPage->getRevision();
+
if ( !$this->mRevision ) {
wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n" );
wfProfileOut( __METHOD__ );
// @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
// We should instead work with the Revision object when we need it...
- $this->mContent = $this->mRevision->getText( Revision::FOR_THIS_USER ); // Loads if user is allowed
+ $this->mContentObject = $this->mRevision->getContent( Revision::FOR_THIS_USER ); // Loads if user is allowed
$this->mRevIdFetched = $this->mRevision->getId();
- wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
+ wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) ); #FIXME: register new hook
wfProfileOut( __METHOD__ );
- return $this->mContent;
+ return $this->mContentObject;
}
/**
* @return Revision|null
*/
public function getRevisionFetched() {
- $this->fetchContent();
+ $this->fetchContentObject();
return $this->mRevision;
}
if ( $wgOut->isPrintable() ) {
$parserOptions->setIsPrintable( true );
$parserOptions->setEditSection( false );
- } elseif ( !$this->getTitle()->quickUserCan( 'edit' ) ) {
+ } elseif ( !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit' ) ) {
$parserOptions->setEditSection( false );
}
break;
case 3:
# This will set $this->mRevision if needed
- $this->fetchContent();
+ $this->fetchContentObject();
# Are we looking at an old revision
if ( $oldid && $this->mRevision ) {
wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
$this->showCssOrJsPage();
$outputDone = true;
- } elseif( !wfRunHooks( 'ArticleViewCustom', array( $this->mContent, $this->getTitle(), $wgOut ) ) ) {
+ } elseif( !wfRunHooks( 'ArticleContentViewCustom', array( $this->fetchContentObject(), $this->getTitle(), $wgOut ) ) ) { #FIXME: document new hook!
+ # Allow extensions do their own custom view for certain pages
+ $outputDone = true;
+ } elseif( Hooks::isRegistered( 'ArticleViewCustom' ) && !wfRunHooks( 'ArticleViewCustom', array( $this->fetchContent(), $this->getTitle(), $wgOut ) ) ) { #FIXME: fetchContent() is deprecated! #FIXME: deprecate hook!
# Allow extensions do their own custom view for certain pages
$outputDone = true;
} else {
- $text = $this->getContent();
- $rt = Title::newFromRedirectArray( $text );
+ $content = $this->getContentObject();
+ $rt = $content->getRedirectChain();
if ( $rt ) {
wfDebug( __METHOD__ . ": showing redirect=no page\n" );
# Viewing a redirect page (e.g. with parameter redirect=no)
$wgOut->addHTML( $this->viewRedirect( $rt ) );
# Parse just to get categories, displaytitle, etc.
- $this->mParserOutput = $wgParser->parse( $text, $this->getTitle(), $parserOptions );
+ $this->mParserOutput = $content->getParserOutput( $this->getTitle(), $oldid, $parserOptions );
$wgOut->addParserOutputNoText( $this->mParserOutput );
$outputDone = true;
}
wfDebug( __METHOD__ . ": doing uncached parse\n" );
$poolArticleView = new PoolWorkArticleView( $this, $parserOptions,
- $this->getRevIdFetched(), $useParserCache, $this->getContent() );
+ $this->getRevIdFetched(), $useParserCache, $this->getContentObject() );
if ( !$poolArticleView->execute() ) {
$error = $poolArticleView->getError();
$unhide = $wgRequest->getInt( 'unhide' ) == 1;
$oldid = $this->getOldID();
- $de = new DifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide );
+ $contentHandler = ContentHandler::getForTitle( $this->getTitle() );
+ $de = $contentHandler->getDifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide );
+
// DifferenceEngine directly fetched the revision:
$this->mRevIdFetched = $de->mNewid;
$de->showDiffPage( $diffOnly );
* This is hooked by SyntaxHighlight_GeSHi to do syntax highlighting of these
* page views.
*/
- protected function showCssOrJsPage() {
+ protected function showCssOrJsPage( $showCacheHint = true ) {
global $wgOut;
- $dir = $this->getContext()->getLanguage()->getDir();
- $lang = $this->getContext()->getLanguage()->getCode();
+ if ( $showCacheHint ) {
+ $dir = $this->getContext()->getLanguage()->getDir();
+ $lang = $this->getContext()->getLanguage()->getCode();
- $wgOut->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
- 'clearyourcache' );
+ $wgOut->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
+ 'clearyourcache' );
+ }
// Give hooks a chance to customise the output
- if ( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->getTitle(), $wgOut ) ) ) {
- // Wrap the whole lot in a <pre> and don't parse
- $m = array();
- preg_match( '!\.(css|js)$!u', $this->getTitle()->getText(), $m );
- $wgOut->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
- $wgOut->addHTML( htmlspecialchars( $this->mContent ) );
- $wgOut->addHTML( "\n</pre>\n" );
+ if ( !Hooks::isRegistered('ShowRawCssJs') || wfRunHooks( 'ShowRawCssJs', array( $this->fetchContent(), $this->getTitle(), $wgOut ) ) ) { #FIXME: fetchContent() is deprecated #FIXME: hook is deprecated
+ $po = $this->mContentObject->getParserOutput();
+ $wgOut->addHTML( $po->getText() );
}
}
'msgKey' => array( 'moveddeleted-notice' ) )
);
+ if ( !$this->mPage->hasViewableContent() && $wgSend404Code ) {
+ // If there's no backing content, send a 404 Not Found
+ // for better machine handling of broken links.
+ $wgRequest->response()->header( "HTTP/1.1 404 Not Found" );
+ }
+
+ $hookResult = wfRunHooks( 'BeforeDisplayNoArticleText', array( $this ) );
+
+ if ( ! $hookResult ) {
+ return;
+ }
+
# Show error message
$oldid = $this->getOldID();
if ( $oldid ) {
}
$text = "<div class='noarticletext'>\n$text\n</div>";
- if ( !$this->mPage->hasViewableContent() && $wgSend404Code ) {
- // If there's no backing content, send a 404 Not Found
- // for better machine handling of broken links.
- $wgRequest->response()->header( "HTTP/1.1 404 Not Found" );
- }
-
$wgOut->addWikiText( $text );
}
# Cascade unhide param in links for easy deletion browsing
$extraParams = array();
- if ( $wgRequest->getVal( 'unhide' ) ) {
+ if ( $unhide ) {
$extraParams['unhide'] = 1;
}
- $revision = Revision::newFromId( $oldid );
+ if ( $this->mRevision && $this->mRevision->getId() === $oldid ) {
+ $revision = $this->mRevision;
+ } else {
+ $revision = Revision::newFromId( $oldid );
+ }
+
$timestamp = $revision->getTimestamp();
$current = ( $oldid == $this->mPage->getLatest() );
// Generate deletion reason
$hasHistory = false;
if ( !$reason ) {
- $reason = $this->generateReason( $hasHistory );
+ try {
+ $reason = $this->generateReason( $hasHistory );
+ } catch (MWException $e) {
+ # if a page is horribly broken, we still want to be able to delete it. so be lenient about errors here.
+ wfDebug("Error while building auto delete summary: $e");
+ $reason = '';
+ }
}
// If the page has a history, insert a warning
/**
* Get parser options suitable for rendering the primary article wikitext
- * @return ParserOptions|false
+ * @return ParserOptions
*/
public function getParserOptions() {
global $wgUser;
*
* @param $fname String Name of called method
* @param $args Array Arguments to the method
+ * @return mixed
*/
public function __call( $fname, $args ) {
if ( is_callable( array( $this->mPage, $fname ) ) ) {
* @return mixed
*/
public function generateReason( &$hasHistory ) {
- return $this->mPage->getAutoDeleteReason( $hasHistory );
+ $title = $this->mPage->getTitle();
+ $handler = ContentHandler::getForTitle( $title );
+ return $handler->getAutoDeleteReason( $title, $hasHistory );
}
// ****** B/C functions for static methods ( __callStatic is PHP>=5.3 ) ****** //
* @param $newtext
* @param $flags
* @return string
+ * @deprecated since 1.20, use ContentHandler::getAutosummary() instead
*/
public static function getAutosummary( $oldtext, $newtext, $flags ) {
return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
'ConfEditorToken' => 'includes/ConfEditor.php',
'Cookie' => 'includes/Cookie.php',
'CookieJar' => 'includes/Cookie.php',
+ 'MWCryptRand' => 'includes/CryptRand.php',
'CurlHttpRequest' => 'includes/HttpFunctions.php',
+ // 'DBDataObject' => 'includes/DBDataObject.php',
+ // 'DBTable' => 'includes/DBTable.php',
'DeferrableUpdate' => 'includes/DeferredUpdates.php',
'DeferredUpdates' => 'includes/DeferredUpdates.php',
+ 'DeprecatedGlobal' => 'includes/DeprecatedGlobal.php',
'DerivativeRequest' => 'includes/WebRequest.php',
'DiffHistoryBlob' => 'includes/HistoryBlob.php',
'FormAction' => 'includes/Action.php',
'FormOptions' => 'includes/FormOptions.php',
'FormSpecialPage' => 'includes/SpecialPage.php',
+ 'GitInfo' => 'includes/GitInfo.php',
'HashtableReplacer' => 'includes/StringUtils.php',
'HistoryBlob' => 'includes/HistoryBlob.php',
'HistoryBlobCurStub' => 'includes/HistoryBlob.php',
'Pager' => 'includes/Pager.php',
'PasswordError' => 'includes/User.php',
'PathRouter' => 'includes/PathRouter.php',
+ 'PathRouterPatternReplacer' => 'includes/PathRouter.php',
'PermissionsError' => 'includes/Exception.php',
'PhpHttpRequest' => 'includes/HttpFunctions.php',
'PoolCounter' => 'includes/PoolCounter.php',
'RevisionList' => 'includes/RevisionList.php',
'RSSFeed' => 'includes/Feed.php',
'Sanitizer' => 'includes/Sanitizer.php',
+ 'SecondaryDataUpdate' => 'includes/SecondaryDataUpdate.php',
+ 'SecondaryDBDataUpdate' => 'includes/SecondaryDBDataUpdate.php',
+ 'ScopedPHPTimeout' => 'includes/ScopedPHPTimeout.php',
'SiteConfiguration' => 'includes/SiteConfiguration.php',
'SiteStats' => 'includes/SiteStats.php',
'SiteStatsInit' => 'includes/SiteStats.php',
'ZhClient' => 'includes/ZhClient.php',
'ZipDirectoryReader' => 'includes/ZipDirectoryReader.php',
+ # content handler
+ 'Content' => 'includes/Content.php',
+ 'ContentHandler' => 'includes/ContentHandler.php',
+ 'CssContent' => 'includes/Content.php',
+ 'CssContentHandler' => 'includes/ContentHandler.php',
+ 'JavaScriptContent' => 'includes/Content.php',
+ 'JavaScriptContentHandler' => 'includes/ContentHandler.php',
+ 'MessageContent' => 'includes/Content.php',
+ 'TextContent' => 'includes/Content.php',
+ 'WikitextContent' => 'includes/Content.php',
+ 'WikitextContentHandler' => 'includes/ContentHandler.php',
+
# includes/actions
'CreditsAction' => 'includes/actions/CreditsAction.php',
'DeleteAction' => 'includes/actions/DeleteAction.php',
'ApiResult' => 'includes/api/ApiResult.php',
'ApiRollback' => 'includes/api/ApiRollback.php',
'ApiRsd' => 'includes/api/ApiRsd.php',
+ 'ApiTokens' => 'includes/api/ApiTokens.php',
'ApiUnblock' => 'includes/api/ApiUnblock.php',
'ApiUndelete' => 'includes/api/ApiUndelete.php',
'ApiUpload' => 'includes/api/ApiUpload.php',
# includes/filerepo/backend
'FileBackendGroup' => 'includes/filerepo/backend/FileBackendGroup.php',
'FileBackend' => 'includes/filerepo/backend/FileBackend.php',
- 'FileBackendStore' => 'includes/filerepo/backend/FileBackend.php',
+ 'FileBackendStore' => 'includes/filerepo/backend/FileBackendStore.php',
+ 'FileBackendStoreShardListIterator' => 'includes/filerepo/backend/FileBackendStore.php',
'FileBackendMultiWrite' => 'includes/filerepo/backend/FileBackendMultiWrite.php',
- 'FileBackendStoreShardListIterator' => 'includes/filerepo/backend/FileBackend.php',
'FSFileBackend' => 'includes/filerepo/backend/FSFileBackend.php',
'FSFileBackendFileList' => 'includes/filerepo/backend/FSFileBackend.php',
'SwiftFileBackend' => 'includes/filerepo/backend/SwiftFileBackend.php',
'SwiftFileBackendFileList' => 'includes/filerepo/backend/SwiftFileBackend.php',
+ 'FileJournal' => 'includes/filerepo/backend/filejournal/FileJournal.php',
+ 'DBFileJournal' => 'includes/filerepo/backend/filejournal/DBFileJournal.php',
+ 'NullFileJournal' => 'includes/filerepo/backend/filejournal/FileJournal.php',
'LockManagerGroup' => 'includes/filerepo/backend/lockmanager/LockManagerGroup.php',
'LockManager' => 'includes/filerepo/backend/lockmanager/LockManager.php',
'ScopedLock' => 'includes/filerepo/backend/lockmanager/LockManager.php',
'MySqlLockManager'=> 'includes/filerepo/backend/lockmanager/DBLockManager.php',
'NullLockManager' => 'includes/filerepo/backend/lockmanager/LockManager.php',
'FileOp' => 'includes/filerepo/backend/FileOp.php',
- 'FileOpScopedPHPTimeout' => 'includes/filerepo/backend/FileOp.php',
'StoreFileOp' => 'includes/filerepo/backend/FileOp.php',
'CopyFileOp' => 'includes/filerepo/backend/FileOp.php',
'MoveFileOp' => 'includes/filerepo/backend/FileOp.php',
'ResourceLoaderNoscriptModule' => 'includes/resourceloader/ResourceLoaderNoscriptModule.php',
'ResourceLoaderSiteModule' => 'includes/resourceloader/ResourceLoaderSiteModule.php',
'ResourceLoaderStartUpModule' => 'includes/resourceloader/ResourceLoaderStartUpModule.php',
+ 'ResourceLoaderUserCSSPrefsModule' => 'includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php',
'ResourceLoaderUserGroupsModule' => 'includes/resourceloader/ResourceLoaderUserGroupsModule.php',
'ResourceLoaderUserModule' => 'includes/resourceloader/ResourceLoaderUserModule.php',
'ResourceLoaderUserOptionsModule' => 'includes/resourceloader/ResourceLoaderUserOptionsModule.php',
'ResourceLoaderUserTokensModule' => 'includes/resourceloader/ResourceLoaderUserTokensModule.php',
+ 'ResourceLoaderLanguageDataModule' => 'includes/resourceloader/ResourceLoaderLanguageDataModule.php',
'ResourceLoaderWikiModule' => 'includes/resourceloader/ResourceLoaderWikiModule.php',
# includes/revisiondelete
/** @endcond */
/** MediaWiki version number */
- $wgVersion = '1.19alpha';
+ $wgVersion = '1.20alpha';
/** Name of the site. It must be changed in LocalSettings.php */
$wgSitename = 'MediaWiki';
* This feature is experimental and broken as of r81612.
*/
$wgAllowAsyncCopyUploads = false;
+ /**
+ * A list of domains copy uploads can come from
+ */
+ $wgCopyUploadsDomains = array();
/**
* Max size for uploads, in bytes. If not set to an array, applies to all
'image/x-djvu' => 'DjVuHandler', // compat
);
+/**
+ * Plugins for page content model handling.
+ * Each entry in the array maps a model name type to a class name
+ */
+$wgContentHandlers = array(
+ CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler', // the usual case
+ CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler', // dumb version, no syntax highlighting
+ CONTENT_MODEL_CSS => 'CssContentHandler', // dumb version, no syntax highlighting
+ CONTENT_MODEL_TEXT => 'TextContentHandler', // dumb plain text in <pre>
+);
+
/**
* Resizing can be done using PHP's internal image libraries or using
* ImageMagick or another third-party converter, e.g. GraphicMagick.
*/
$wgParserCacheType = CACHE_ANYTHING;
+ /**
+ * The cache type for storing language conversion tables,
+ * which are used when parsing certain text and interface messages.
+ *
+ * For available types see $wgMainCacheType.
+ */
+ $wgLanguageConverterCacheType = CACHE_ANYTHING;
+
/**
* Advanced object cache configuration.
*
* Use this to define the class names and constructor parameters which are used
* for the various cache types. Custom cache types may be defined here and
- * referenced from $wgMainCacheType, $wgMessageCacheType or $wgParserCacheType.
+ * referenced from $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType,
+ * or $wgLanguageConverterCacheType.
*
* The format is an associative array where the key is a cache identifier, and
* the value is an associative array of parameters. The "class" parameter is the
* This will cache static pages for non-logged-in users to reduce
* database traffic on public sites.
* Must set $wgShowIPinHeader = false
+ * ResourceLoader requests to default language and skins are cached
+ * as well as single module requests.
*/
$wgUseFileCache = false;
/**
* When using the file cache, we can store the cached HTML gzipped to save disk
* space. Pages will then also be served compressed to clients that support it.
- * THIS IS NOT COMPATIBLE with ob_gzhandler which is now enabled if supported in
- * the default LocalSettings.php! If you enable this, remove that setting first.
*
* Requires zlib support enabled in PHP.
*/
/** Maximum number of titles to purge in any one client operation */
$wgMaxSquidPurgeTitles = 400;
+ /**
+ * Routing configuration for HTCP multicast purging. Add elements here to
+ * enable HTCP and determine which purges are sent where. If set to an empty
+ * array, HTCP is disabled.
+ *
+ * Each key in this array is a regular expression to match against the purged
+ * URL, or an empty string to match all URLs. The purged URL is matched against
+ * the regexes in the order specified, and the first rule whose regex matches
+ * is used.
+ *
+ * Example configuration to send purges for upload.wikimedia.org to one
+ * multicast group and all other purges to another:
+ * $wgHTCPMulticastRouting = array(
+ * '|^https?://upload\.wikimedia\.org|' => array(
+ * 'host' => '239.128.0.113',
+ * 'port' => 4827,
+ * ),
+ * '' => array(
+ * 'host' => '239.128.0.112',
+ * 'port' => 4827,
+ * ),
+ * );
+ *
+ * @see $wgHTCPMulticastTTL
+ */
+ $wgHTCPMulticastRouting = array();
+
/**
* HTCP multicast address. Set this to a multicast IP address to enable HTCP.
*
* Note that MediaWiki uses the old non-RFC compliant HTCP format, which was
* present in the earliest Squid implementations of the protocol.
+ *
+ * This setting is DEPRECATED in favor of $wgHTCPMulticastRouting , and kept
+ * for backwards compatibility only. If $wgHTCPMulticastRouting is set, this
+ * setting is ignored. If $wgHTCPMulticastRouting is not set and this setting
+ * is, it is used to populate $wgHTCPMulticastRouting.
+ *
+ * @deprecated in favor of $wgHTCPMulticastRouting
*/
$wgHTCPMulticastAddress = false;
/**
* HTCP multicast port.
+ * @deprecated in favor of $wgHTCPMulticastRouting
* @see $wgHTCPMulticastAddress
*/
$wgHTCPPort = 4827;
/**
* HTCP multicast TTL.
- * @see $wgHTCPMulticastAddress
+ * @see $wgHTCPMulticastRouting
*/
$wgHTCPMulticastTTL = 1;
*/
$wgLocalTZoffset = null;
+ /**
+ * If set to true, this will roll back a few bug fixes introduced in 1.19,
+ * emulating the 1.18 behaviour, to avoid introducing bug 34832. In 1.19,
+ * language variant conversion is disabled in interface messages. Setting this
+ * to true re-enables it.
+ *
+ * This variable should be removed (implicitly false) in 1.20 or earlier.
+ */
+ $wgBug34832TransitionalRollback = true;
+
+
/** @} */ # End of language/charset settings
/*************************************************************************//**
/**
* Show IP address, for non-logged in users. It's necessary to switch this off
* for some forms of caching.
+ * Will disable file cache.
*/
$wgShowIPinHeader = true;
),
);
- /**
- * Whether to embed private modules inline with HTML output or to bypass
- * caching and check the user parameter against $wgUser to prevent
- * unauthorized access to private modules.
- */
- $wgResourceLoaderInlinePrivateModules = true;
-
/**
* The default debug mode (on/off) for of ResourceLoader requests. This will still
* be overridden when the debug URL parameter is used.
'gender' => 'unknown',
'hideminor' => 0,
'hidepatrolled' => 0,
- 'highlightbroken' => 1,
'imagesize' => 2,
'justify' => 0,
'math' => 1,
*/
$wgDebugComments = false;
+ /**
+ * Extensive database transaction state debugging
+ */
+ $wgDebugDBTransactions = false;
+
/**
* Write SQL queries to the debug log
*/
* development warnings will not be generated for deprecations added in releases
* after the limit.
*/
- $wgDeprecationReleaseLimit = '1.17';
+ $wgDeprecationReleaseLimit = false;
/** Only record profiling info for pages that took longer than this */
$wgProfileLimit = 0.0;
* );
*/
$wgParserTestRemote = false;
-
+
/**
* Allow running of javascript test suites via [[Special:JavaScriptTest]] (such as QUnit).
*/
*/
$wgJavaScriptTestConfig = array(
'qunit' => array(
+ // Page where documentation can be found relevant to the QUnit test suite being ran.
+ // Used in the intro paragraph on [[Special:JavaScriptTest/qunit]] for the
+ // documentation link in the "javascripttest-qunit-intro" message.
'documentation' => '//www.mediawiki.org/wiki/Manual:JavaScript_unit_testing',
+ // If you are submitting the QUnit test suite to a TestSwarm instance,
+ // point this to the "inject.js" script of that instance. This is was registers
+ // the QUnit hooks to extract the test results and push them back up into the
+ // TestSwarm database.
+ // @example 'http://localhost/testswarm/js/inject.js'
+ // @example '//integration.mediawiki.org/testswarm/js/inject.js'
+ 'testswarm-injectjs' => false,
),
);
/**
* Display the new debugging toolbar. This also enables profiling on database
* queries and other useful output.
+ * Will disable file cache.
*
* @since 1.19
*/
$wgDBtestuser = ''; //db user that has permission to create and drop the test databases only
$wgDBtestpassword = '';
+/**
+ * Associative array mapping namespace IDs to the name of the content model pages in that namespace should have by
+ * default (use the CONTENT_MODEL_XXX constants). If no special content type is defined for a given namespace,
+ * pages in that namespace will use the CONTENT_MODEL_WIKITEXT (except for the special case of JS and CS pages).
+ */
+$wgNamespaceContentModels = array();
+
+/**
+ * How to react if a plain text version of a non-text Content object is requested using ContentHandler::getContentText():
+ *
+ * * 'ignore': return null
+ * * 'fail': throw an MWException
+ * * 'serialize': serialize to default format
+ */
+$wgContentHandlerTextFallback = 'ignore';
+
/**
* For really cool vim folding this needs to be at the end:
* vim: foldmarker=@{,@} foldmethod=marker
* @file
*/
+ /**
+ * @defgroup Constants
+ */
+
/**
* Version constants for the benefit of extensions
*/
define( 'PROTO_CURRENT', null );
define( 'PROTO_CANONICAL', 1 );
define( 'PROTO_INTERNAL', 2 );
+
+/**
+ * Content model names, used by Content and ContentHandler
+ */
+define('CONTENT_MODEL_WIKITEXT', 'wikitext');
+define('CONTENT_MODEL_JAVASCRIPT', 'javascript');
+define('CONTENT_MODEL_CSS', 'css');
+define('CONTENT_MODEL_TEXT', 'text');
+
const AS_HOOK_ERROR = 210;
/**
- * Status: The filter function set in $wgFilterCallback returned true (= block it)
+ * Status: The filter function set in $wgFilterCallback returned true (= block it)
*/
const AS_FILTERING = 211;
*/
const AS_IMAGE_REDIRECT_LOGGED = 234;
+ /**
+ * Status: can't parse content
+ */
+ const AS_PARSE_ERROR = 240;
+
/**
* @var Article
*/
*/
var $mParserOutput;
+ /**
+ * Has a summary been preset using GET parameter &summary= ?
+ * @var Bool
+ */
+ var $hasPresetSummary = false;
+
var $mBaseRevision = false;
var $mShowSummaryField = true;
var $textbox1 = '', $textbox2 = '', $summary = '', $nosummary = false;
var $edittime = '', $section = '', $sectiontitle = '', $starttime = '';
var $oldid = 0, $editintro = '', $scrolltop = null, $bot = true;
+ var $content_model = null, $content_format = null;
# Placeholders for text injection by hooks (must be HTML)
# extensions should take care to _append_ to the present value
public $editFormTextBottom = '';
public $editFormTextAfterContent = '';
public $previewTextAfterContent = '';
- public $mPreloadText = '';
+ public $mPreloadContent = null;
/* $didSave should be set to true whenever an article was succesfully altered. */
public $didSave = false;
public function __construct( Article $article ) {
$this->mArticle = $article;
$this->mTitle = $article->getTitle();
+
+ $this->content_model = $this->mTitle->getContentModelName();
+
+ $handler = ContentHandler::getForModelName( $this->content_model );
+ $this->content_format = $handler->getDefaultFormat(); #NOTE: should be overridden by format of actual revision
}
/**
}
wfProfileIn( __METHOD__ );
- wfDebug( __METHOD__.": enter\n" );
+ wfDebug( __METHOD__ . ": enter\n" );
// If they used redlink=1 and the page exists, redirect to the main article
if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) {
return;
}
- wfProfileIn( __METHOD__."-business-end" );
+ wfProfileIn( __METHOD__ . "-business-end" );
$this->isConflict = false;
// css / js subpages of user pages get a special treatment
if ( 'save' == $this->formtype ) {
if ( !$this->attemptSave() ) {
- wfProfileOut( __METHOD__."-business-end" );
+ wfProfileOut( __METHOD__ . "-business-end" );
wfProfileOut( __METHOD__ );
return;
}
if ( 'initial' == $this->formtype || $this->firsttime ) {
if ( $this->initialiseForm() === false ) {
$this->noSuchSectionPage();
- wfProfileOut( __METHOD__."-business-end" );
+ wfProfileOut( __METHOD__ . "-business-end" );
wfProfileOut( __METHOD__ );
return;
}
- if ( !$this->mTitle->getArticleId() )
+ if ( !$this->mTitle->getArticleID() )
wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) );
else
wfRunHooks( 'EditFormInitialText', array( $this ) );
}
$this->showEditForm();
- wfProfileOut( __METHOD__."-business-end" );
+ wfProfileOut( __METHOD__ . "-business-end" );
wfProfileOut( __METHOD__ );
}
}
# Ignore some permissions errors when a user is just previewing/viewing diffs
$remove = array();
- foreach( $permErrors as $error ) {
+ foreach ( $permErrors as $error ) {
if ( ( $this->preview || $this->diff ) &&
( $error[0] == 'blockedtext' || $error[0] == 'autoblockedtext' ) )
{
return;
}
- $content = $this->getContent();
+ $content = $this->getContentObject();
# Use the normal message if there's nothing to display
- if ( $this->firsttime && $content === '' ) {
+ if ( $this->firsttime && $content->isEmpty() ) {
$action = $this->mTitle->exists() ? 'edit' :
( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
throw new PermissionsError( $action, $permErrors );
# If the user made changes, preserve them when showing the markup
# (This happens when a user is blocked during edit, for instance)
if ( !$this->firsttime ) {
- $content = $this->textbox1;
+ $text = $this->textbox1;
$wgOut->addWikiMsg( 'viewyourtext' );
} else {
+ $text = $content->serialize( $this->content_format );
$wgOut->addWikiMsg( 'viewsourcetext' );
}
- $this->showTextbox( $content, 'wpTextbox1', array( 'readonly' ) );
+ $this->showTextbox( $text, 'wpTextbox1', array( 'readonly' ) );
$wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
Linker::formatTemplates( $this->getTemplates() ) ) );
// Standard preference behaviour
return true;
} elseif ( !$this->mTitle->exists() &&
- isset($wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()]) &&
+ isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] ) &&
$wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] )
{
// Categories are special
* @return bool
*/
protected function isWrongCaseCssJsPage() {
- if( $this->mTitle->isCssJsSubpage() ) {
+ if ( $this->mTitle->isCssJsSubpage() ) {
$name = $this->mTitle->getSkinFromCssJsSubpage();
$skins = array_merge(
array_keys( Skin::getSkinNames() ),
# Also remove trailing whitespace, but don't remove _initial_
# whitespace from the text boxes. This may be significant formatting.
$this->textbox1 = $this->safeUnicodeInput( $request, 'wpTextbox1' );
- if ( !$request->getCheck('wpTextbox2') ) {
+ if ( !$request->getCheck( 'wpTextbox2' ) ) {
// Skip this if wpTextbox2 has input, it indicates that we came
// from a conflict page with raw page text, not a custom form
// modified by subclasses
- wfProfileIn( get_class( $this ) . "::importContentFormData" );
- $textbox1 = $this->importContentFormData( $request );
- if ( isset( $textbox1 ) )
+ wfProfileIn( get_class($this)."::importContentFormData" );
+ $textbox1 = $this->importContentFormData( $request ); #FIXME: what should this return??
+ if ( isset($textbox1) )
$this->textbox1 = $textbox1;
- wfProfileOut( get_class($this)."::importContentFormData" );
+ wfProfileOut( get_class( $this ) . "::importContentFormData" );
}
# Truncate for whole multibyte characters. +5 bytes for ellipsis
{
$this->allowBlankSummary = true;
} else {
- $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' ) || !$wgUser->getOption( 'forceeditsummary');
+ $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' ) || !$wgUser->getOption( 'forceeditsummary' );
}
$this->autoSumm = $request->getText( 'wpAutoSummary' );
} else {
# Not a posted form? Start with nothing.
wfDebug( __METHOD__ . ": Not a posted form.\n" );
- $this->textbox1 = '';
+ $this->textbox1 = ''; #FIXME: track content object
$this->summary = '';
$this->sectiontitle = '';
$this->edittime = '';
}
elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
$this->summary = $request->getText( 'summary' );
+ if ( $this->summary !== '' ) {
+ $this->hasPresetSummary = true;
+ }
}
if ( $request->getVal( 'minor' ) ) {
}
}
+ $this->oldid = $request->getInt( 'oldid' );
+
$this->bot = $request->getBool( 'bot', true );
$this->nosummary = $request->getBool( 'nosummary' );
- $this->oldid = $request->getInt( 'oldid' );
+ $content_handler = ContentHandler::getForTitle( $this->mTitle );
+ $this->content_model = $request->getText( 'model', $content_handler->getModelName() ); #may be overridden by revision
+ $this->content_format = $request->getText( 'format', $content_handler->getDefaultFormat() ); #may be overridden by revision
+
+ #TODO: check if the desired model is allowed in this namespace, and if a transition from the page's current model to the new model is allowed
+ #TODO: check if the desired content model supports the given content format!
$this->live = $request->getCheck( 'live' );
$this->editintro = $request->getText( 'editintro',
function initialiseForm() {
global $wgUser;
$this->edittime = $this->mArticle->getTimestamp();
- $this->textbox1 = $this->getContent( false );
+
+ $content = $this->getContentObject( false ); #TODO: track content object?!
+ $this->textbox1 = $content->serialize( $this->content_format );
+
// activate checkboxes if user wants them to be always active
# Sort out the "watch" checkbox
if ( $wgUser->getOption( 'watchdefault' ) ) {
* @param $def_text string
* @return mixed string on success, $def_text for invalid sections
* @private
+ * @deprecated since 1.20
*/
- function getContent( $def_text = '' ) {
- global $wgOut, $wgRequest, $wgParser;
+ function getContent( $def_text = false ) { #FIXME: deprecated, replace usage!
+ if ( $def_text !== null && $def_text !== false && $def_text !== '' ) {
+ $def_content = ContentHandler::makeContent( $def_text, $this->getTitle() );
+ } else {
+ $def_content = false;
+ }
+
+ $content = $this->getContentObject( $def_content );
+
+ return $content->serialize( $this->content_format ); #XXX: really use serialized form? use ContentHandler::getContentText() instead?
+ }
+
+ private function getContentObject( $def_content = null ) { #FIXME: use this!
+ global $wgOut, $wgRequest;
wfProfileIn( __METHOD__ );
- $text = false;
+ $content = false;
// For message page not locally set, use the i18n message.
// For other non-existent articles, use preload text if any.
if ( !$this->mTitle->exists() || $this->section == 'new' ) {
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
# If this is a system message, get the default text.
- $text = $this->mTitle->getDefaultMessageText();
+ $msg = $this->mTitle->getDefaultMessageText();
+
+ $content = new WikitextContent($msg); //XXX: really hardcode wikitext here?
}
- if ( $text === false ) {
+ if ( $content === false ) {
# If requested, preload some text.
$preload = $wgRequest->getVal( 'preload',
// Custom preload text for new sections
$this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
- $text = $this->getPreloadedText( $preload );
+
+ $content = $this->getPreloadedContent( $preload );
}
// For existing pages, get text based on "undo" or section parameters.
} else {
if ( $this->section != '' ) {
// Get section edit text (returns $def_text for invalid sections)
- $text = $wgParser->getSection( $this->getOriginalContent(), $this->section, $def_text );
+ $orig = $this->getOriginalContent();
+ $content = $orig ? $orig->getSection( $this->section ) : null;
+
+ if ( !$content ) $content = $def_content;
} else {
$undoafter = $wgRequest->getInt( 'undoafter' );
$undo = $wgRequest->getInt( 'undo' );
# Sanity check, make sure it's the right page,
# the revisions exist and they were not deleted.
- # Otherwise, $text will be left as-is.
+ # Otherwise, $content will be left as-is.
if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
$undorev->getPage() == $oldrev->getPage() &&
- $undorev->getPage() == $this->mTitle->getArticleId() &&
+ $undorev->getPage() == $this->mTitle->getArticleID() &&
!$undorev->isDeleted( Revision::DELETED_TEXT ) &&
!$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
- $text = $this->mArticle->getUndoText( $undorev, $oldrev );
- if ( $text === false ) {
+ $content = $this->mArticle->getUndoContent( $undorev, $oldrev );
+
+ if ( $content === false ) {
# Warn the user that something went wrong
$undoMsg = 'failure';
} else {
$undoMsg = 'norev';
}
- $this->editFormPageTop .= $wgOut->parse( "<div class=\"error mw-undo-{$undoMsg}\">" .
+ $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
+ $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
wfMsgNoTrans( 'undo-' . $undoMsg ) . '</div>', true, /* interface */true );
}
- if ( $text === false ) {
- $text = $this->getOriginalContent();
+ if ( $content === false ) {
+ $content = $this->getOriginalContent();
}
}
}
wfProfileOut( __METHOD__ );
- return $text;
+ return $content;
}
/**
* @since 1.19
* @return string
*/
- private function getOriginalContent() {
+ private function getOriginalContent() { #FIXME: use Content! set content_model and content_format!
if ( $this->section == 'new' ) {
- return $this->getCurrentText();
+ return $this->getCurrentContent();
}
$revision = $this->mArticle->getRevisionFetched();
if ( $revision === null ) {
- return '';
+ if ( !$this->content_model ) $this->content_model = $this->getTitle()->getContentModelName();
+ $handler = ContentHandler::getForModelName( $this->content_model );
+
+ return $handler->emptyContent();
}
- return $this->mArticle->getContent();
+
+ $content = $this->mArticle->getContentObject();
+ return $content;
}
/**
- * Get the actual text of the page. This is basically similar to
- * WikiPage::getRawText() except that when the page doesn't exist an empty
- * string is returned instead of false.
+ * Get the current content of the page. This is basically similar to
+ * WikiPage::getContent( Revision::RAW ) except that when the page doesn't exist an empty
+ * content object is returned instead of null.
*
- * @since 1.19
+ * @since 1.20
* @return string
*/
- private function getCurrentText() {
- $text = $this->mArticle->getRawText();
- if ( $text === false ) {
- return '';
+ private function getCurrentContent() {
+ $rev = $this->mArticle->getRevision();
+ $content = $rev ? $rev->getContent( Revision::RAW ) : null;
+
+ if ( $content === false || $content === null ) {
+ if ( !$this->content_model ) $this->content_model = $this->getTitle()->getContentModelName();
+ $handler = ContentHandler::getForModelName( $this->content_model );
+
+ return $handler->emptyContent();
} else {
- return $text;
+ #FIXME: nasty side-effect!
+ $this->content_model = $rev->getContentModelName();
+ $this->content_format = $rev->getContentFormat();
+
+ return $content;
}
}
* Use this method before edit() to preload some text into the edit box
*
* @param $text string
+ * @deprecated since 1.20
+ */
+ public function setPreloadedText( $text ) { #FIXME: deprecated, use setPreloadedContent()
+ wfDeprecated( __METHOD__, "1.20" );
+
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+
+ $this->setPreloadedContent( $content );
+ }
+
+ /**
+ * Use this method before edit() to preload some content into the edit box
+ *
+ * @param $content Content
*/
- public function setPreloadedText( $text ) {
- $this->mPreloadText = $text;
+ public function setPreloadedContent( Content $content ) { #FIXME: use this!
+ $this->mPreloadedContent = $content;
}
/**
*
* @param $preload String: representing the title to preload from.
* @return String
+ * @deprecated since 1.20
*/
- protected function getPreloadedText( $preload ) {
- global $wgUser, $wgParser;
+ protected function getPreloadedText( $preload ) { #FIXME: B/C only, replace usage!
+ wfDeprecated( __METHOD__, "1.20" );
- if ( !empty( $this->mPreloadText ) ) {
- return $this->mPreloadText;
+ $content = $this->getPreloadedContent( $preload );
+ $text = $content->serialize( $this->content_format ); #XXX: really use serialized form? use ContentHandler::getContentText() instead?!
+
+ return $text;
+ }
+
+ protected function getPreloadedContent( $preload ) { #FIXME: use this!
+ global $wgUser;
+
+ if ( !empty( $this->mPreloadContent ) ) {
+ return $this->mPreloadContent;
}
+ $handler = ContentHandler::getForTitle( $this->getTitle() );
+
if ( $preload === '' ) {
- return '';
+ return $handler->emptyContent();
}
$title = Title::newFromText( $preload );
# Check for existence to avoid getting MediaWiki:Noarticletext
if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
- return '';
+ return $handler->emptyContent();
}
$page = WikiPage::factory( $title );
$title = $page->getRedirectTarget();
# Same as before
if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
- return '';
+ return $handler->emptyContent();
}
$page = WikiPage::factory( $title );
}
$parserOptions = ParserOptions::newFromUser( $wgUser );
- return $wgParser->getPreloadText( $page->getRawText(), $title, $parserOptions );
+ $content = $page->getContent( Revision::RAW );
+
+ return $content->preloadTransform( $title, $parserOptions );
}
/**
case self::AS_FILTERING:
return false;
+ case self::AS_PARSE_ERROR:
+ $wgOut->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>');
+ #FIXME: cause editform to be shown again, not just an error!
+ return false;
+
case self::AS_SUCCESS_NEW_ARTICLE:
$query = $resultDetails['redirect'] ? 'redirect=no' : '';
$anchor = isset ( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
return false;
case self::AS_BLOCKED_PAGE_FOR_USER:
- throw new UserBlockedError( $wgUser->mBlock );
+ throw new UserBlockedError( $wgUser->getBlock() );
case self::AS_IMAGE_REDIRECT_ANON:
case self::AS_IMAGE_REDIRECT_LOGGED:
# Check image redirect
if ( $this->mTitle->getNamespace() == NS_FILE &&
- Title::newFromRedirect( $this->textbox1 ) instanceof Title &&
+ Title::newFromRedirect( $this->textbox1 ) instanceof Title && #FIXME: use content handler to check for redirect
!$wgUser->isAllowed( 'upload' ) ) {
$code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
$status->setResult( false, $code );
$aid = $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
$new = ( $aid == 0 );
- if ( $new ) {
- // Late check for create permission, just in case *PARANOIA*
- if ( !$this->mTitle->userCan( 'create' ) ) {
- $status->fatal( 'nocreatetext' );
- $status->value = self::AS_NO_CREATE_PERMISSION;
- wfDebug( __METHOD__ . ": no create permission\n" );
- wfProfileOut( __METHOD__ );
- return $status;
- }
+ try {
+ if ( $new ) {
+ // Late check for create permission, just in case *PARANOIA*
+ if ( !$this->mTitle->userCan( 'create' ) ) {
+ $status->fatal( 'nocreatetext' );
+ $status->value = self::AS_NO_CREATE_PERMISSION;
+ wfDebug( __METHOD__ . ": no create permission\n" );
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
- # Don't save a new article if it's blank.
- if ( $this->textbox1 == '' ) {
- $status->setResult( false, self::AS_BLANK_ARTICLE );
- wfProfileOut( __METHOD__ );
- return $status;
- }
+ # Don't save a new article if it's blank.
+ if ( $this->textbox1 == '' ) {
+ $status->setResult( false, self::AS_BLANK_ARTICLE );
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
- // Run post-section-merge edit filter
- if ( !wfRunHooks( 'EditFilterMerged', array( $this, $this->textbox1, &$this->hookError, $this->summary ) ) ) {
- # Error messages etc. could be handled within the hook...
- $status->fatal( 'hookaborted' );
- $status->value = self::AS_HOOK_ERROR;
- wfProfileOut( __METHOD__ );
- return $status;
- } elseif ( $this->hookError != '' ) {
- # ...or the hook could be expecting us to produce an error
- $status->fatal( 'hookaborted' );
- $status->value = self::AS_HOOK_ERROR_EXPECTED;
- wfProfileOut( __METHOD__ );
- return $status;
- }
++<<<<<<< HEAD
+ // Run post-section-merge edit filter
+ if ( !wfRunHooks( 'EditFilterMerged', array( $this, $this->textbox1, &$this->hookError, $this->summary ) ) ) {
+ # Error messages etc. could be handled within the hook...
+ $status->fatal( 'hookaborted' );
+ $status->value = self::AS_HOOK_ERROR;
+ wfProfileOut( __METHOD__ );
+ return $status;
+ } elseif ( $this->hookError != '' ) {
+ # ...or the hook could be expecting us to produce an error
+ $status->fatal( 'hookaborted' );
+ $status->value = self::AS_HOOK_ERROR_EXPECTED;
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ $content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format );
+ # Handle the user preference to force summaries here. Check if it's not a redirect.
+ if ( !$this->allowBlankSummary && !$content->isRedirect() ) {
+ if ( md5( $this->summary ) == $this->autoSumm ) {
+ $this->missingSummary = true;
+ $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
+ $status->value = self::AS_SUMMARY_NEEDED;
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
++=======
+ $text = $this->textbox1;
+ $result['sectionanchor'] = '';
+ if ( $this->section == 'new' ) {
+ if ( $this->sectiontitle !== '' ) {
+ // Insert the section title above the content.
+ $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->sectiontitle ) . "\n\n" . $text;
+
+ // Jump to the new section
+ $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
+
+ // If no edit summary was specified, create one automatically from the section
+ // title and have it link to the new section. Otherwise, respect the summary as
+ // passed.
+ if ( $this->summary === '' ) {
+ $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
+ $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
+ }
+ } elseif ( $this->summary !== '' ) {
+ // Insert the section title above the content.
+ $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $text;
+
+ // Jump to the new section
+ $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
+
+ // Create a link to the new section from the edit summary.
+ $cleanSummary = $wgParser->stripSectionName( $this->summary );
+ $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
++>>>>>>> master
}
- }
- $status->value = self::AS_SUCCESS_NEW_ARTICLE;
+ $result['sectionanchor'] = '';
+ if ( $this->section == 'new' ) {
+ if ( $this->sectiontitle !== '' ) {
+ // Insert the section title above the content.
+ $content = $content->addSectionHeader( $this->sectiontitle );
+
+ // Jump to the new section
+ $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
+
+ // If no edit summary was specified, create one automatically from the section
+ // title and have it link to the new section. Otherwise, respect the summary as
+ // passed.
+ if ( $this->summary === '' ) {
+ $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
+ $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
+ }
+ } elseif ( $this->summary !== '' ) {
+ // Insert the section title above the content.
+ $content = $content->addSectionHeader( $this->sectiontitle );
+
+ // Jump to the new section
+ $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
- } else {
+ // Create a link to the new section from the edit summary.
+ $cleanSummary = $wgParser->stripSectionName( $this->summary );
+ $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
+ }
+ }
- # Article exists. Check for edit conflict.
+ $status->value = self::AS_SUCCESS_NEW_ARTICLE;
- $this->mArticle->clear(); # Force reload of dates, etc.
- $timestamp = $this->mArticle->getTimestamp();
+ } else {
- wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
+ # Article exists. Check for edit conflict.
- if ( $timestamp != $this->edittime ) {
- $this->isConflict = true;
- if ( $this->section == 'new' ) {
- if ( $this->mArticle->getUserText() == $wgUser->getName() &&
- $this->mArticle->getComment() == $this->summary ) {
- // Probably a duplicate submission of a new comment.
- // This can happen when squid resends a request after
- // a timeout but the first one actually went through.
- wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" );
- } else {
- // New comment; suppress conflict.
+ $this->mArticle->clear(); # Force reload of dates, etc.
+ $timestamp = $this->mArticle->getTimestamp();
+
+ wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
+
+ if ( $timestamp != $this->edittime ) {
+ $this->isConflict = true;
+ if ( $this->section == 'new' ) {
+ if ( $this->mArticle->getUserText() == $wgUser->getName() &&
+ $this->mArticle->getComment() == $this->summary ) {
+ // Probably a duplicate submission of a new comment.
+ // This can happen when squid resends a request after
+ // a timeout but the first one actually went through.
+ wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" );
+ } else {
+ // New comment; suppress conflict.
+ $this->isConflict = false;
+ wfDebug( __METHOD__ .": conflict suppressed; new section\n" );
+ }
+ } elseif ( $this->section == '' && $this->userWasLastToEdit( $wgUser->getId(), $this->edittime ) ) {
+ # Suppress edit conflict with self, except for section edits where merging is required.
+ wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
$this->isConflict = false;
++<<<<<<< HEAD
++=======
+ wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
++>>>>>>> master
}
- } elseif ( $this->section == '' && $this->userWasLastToEdit( $wgUser->getId(), $this->edittime ) ) {
- # Suppress edit conflict with self, except for section edits where merging is required.
- wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
- $this->isConflict = false;
}
++<<<<<<< HEAD
+
+ // If sectiontitle is set, use it, otherwise use the summary as the section title (for
+ // backwards compatibility with old forms/bots).
+ if ( $this->sectiontitle !== '' ) {
+ $sectionTitle = $this->sectiontitle;
++=======
+ }
+
+ // If sectiontitle is set, use it, otherwise use the summary as the section title (for
+ // backwards compatibility with old forms/bots).
+ if ( $this->sectiontitle !== '' ) {
+ $sectionTitle = $this->sectiontitle;
+ } else {
+ $sectionTitle = $this->summary;
+ }
+
+ if ( $this->isConflict ) {
+ wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" );
+ $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle, $this->edittime );
+ } else {
+ wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
+ $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle );
+ }
+ if ( is_null( $text ) ) {
+ wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
+ $this->isConflict = true;
+ $text = $this->textbox1; // do not try to merge here!
+ } elseif ( $this->isConflict ) {
+ # Attempt merge
+ if ( $this->mergeChangesInto( $text ) ) {
+ // Successful merge! Maybe we should tell the user the good news?
+ $this->isConflict = false;
+ wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
++>>>>>>> master
} else {
- $this->section = '';
- $this->textbox1 = $text;
- wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
+ $sectionTitle = $this->summary;
}
- }
- if ( $this->isConflict ) {
- $status->setResult( false, self::AS_CONFLICT_DETECTED );
- wfProfileOut( __METHOD__ );
- return $status;
- }
+ $textbox_content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format );
+ $content = false;
- // Run post-section-merge edit filter
- if ( !wfRunHooks( 'EditFilterMerged', array( $this, $text, &$this->hookError, $this->summary ) ) ) {
- # Error messages etc. could be handled within the hook...
- $status->fatal( 'hookaborted' );
- $status->value = self::AS_HOOK_ERROR;
- wfProfileOut( __METHOD__ );
- return $status;
- } elseif ( $this->hookError != '' ) {
- # ...or the hook could be expecting us to produce an error
- $status->fatal( 'hookaborted' );
- $status->value = self::AS_HOOK_ERROR_EXPECTED;
- wfProfileOut( __METHOD__ );
- return $status;
- }
+ if ( $this->isConflict ) {
+ wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" );
- # Handle the user preference to force summaries here, but not for null edits
- if ( $this->section != 'new' && !$this->allowBlankSummary
- && $this->getOriginalContent() != $text
- && !Title::newFromRedirect( $text ) ) # check if it's not a redirect
- {
- if ( md5( $this->summary ) == $this->autoSumm ) {
- $this->missingSummary = true;
- $status->fatal( 'missingsummary' );
- $status->value = self::AS_SUMMARY_NEEDED;
- wfProfileOut( __METHOD__ );
- return $status;
+ $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle, $this->edittime );
+ } else {
+ wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
+
+ $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle );
}
- }
- # And a similar thing for new sections
- if ( $this->section == 'new' && !$this->allowBlankSummary ) {
- if ( trim( $this->summary ) == '' ) {
- $this->missingSummary = true;
- $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
- $status->value = self::AS_SUMMARY_NEEDED;
+ if ( is_null( $content ) ) {
+ wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
+ $this->isConflict = true;
+ $content = $textbox_content; // do not try to merge here!
+ } elseif ( $this->isConflict ) {
+ # Attempt merge
+ if ( $this->mergeChangesIntoContent( $textbox_content ) ) {
+ // Successful merge! Maybe we should tell the user the good news?
+ $content = $textbox_content;
+ $this->isConflict = false;
+ wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
+ } else {
+ $this->section = '';
+ #$this->textbox1 = $text; #redundant, nothing to do here?
+ wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
+ }
+ }
+
+ if ( $this->isConflict ) {
+ $status->setResult( false, self::AS_CONFLICT_DETECTED );
wfProfileOut( __METHOD__ );
return $status;
}
- }
- # All's well
- wfProfileIn( __METHOD__ . '-sectionanchor' );
- $sectionanchor = '';
- if ( $this->section == 'new' ) {
- if ( $this->textbox1 == '' ) {
- $this->missingComment = true;
- $status->fatal( 'missingcommenttext' );
- $status->value = self::AS_TEXTBOX_EMPTY;
- wfProfileOut( __METHOD__ . '-sectionanchor' );
+ // Run post-section-merge edit filter
+ if ( !wfRunHooks( 'EditFilterMerged', array( $this, $content->serialize( $this->content_format ), &$this->hookError, $this->summary ) )
+ || !wfRunHooks( 'EditFilterMergedContent', array( $this, $content, &$this->hookError, $this->summary ) ) ) { #FIXME: document new hook
+ # Error messages etc. could be handled within the hook...
+ $status->fatal( 'hookaborted' );
+ $status->value = self::AS_HOOK_ERROR;
+ wfProfileOut( __METHOD__ );
+ return $status;
+ } elseif ( $this->hookError != '' ) {
+ # ...or the hook could be expecting us to produce an error
+ $status->fatal( 'hookaborted' );
+ $status->value = self::AS_HOOK_ERROR_EXPECTED;
wfProfileOut( __METHOD__ );
return $status;
}
- if ( $this->sectiontitle !== '' ) {
- $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
- // If no edit summary was specified, create one automatically from the section
- // title and have it link to the new section. Otherwise, respect the summary as
- // passed.
- if ( $this->summary === '' ) {
- $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
- $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
+
+ # Handle the user preference to force summaries here, but not for null edits
+ if ( $this->section != 'new' && !$this->allowBlankSummary
+ && !$content->equals( $this->getOriginalContent() )
+ && !$content->isRedirect() ) # check if it's not a redirect
+ {
+ if ( md5( $this->summary ) == $this->autoSumm ) {
+ $this->missingSummary = true;
+ $status->fatal( 'missingsummary' );
+ $status->value = self::AS_SUMMARY_NEEDED;
+ wfProfileOut( __METHOD__ );
+ return $status;
}
- } elseif ( $this->summary !== '' ) {
- $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
- # This is a new section, so create a link to the new section
- # in the revision summary.
- $cleanSummary = $wgParser->stripSectionName( $this->summary );
- $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
}
- } elseif ( $this->section != '' ) {
- # Try to get a section anchor from the section source, redirect to edited section if header found
- # XXX: might be better to integrate this into Article::replaceSection
- # for duplicate heading checking and maybe parsing
- $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
- # we can't deal with anchors, includes, html etc in the header for now,
- # headline would need to be parsed to improve this
- if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
- $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] );
+
+ # And a similar thing for new sections
+ if ( $this->section == 'new' && !$this->allowBlankSummary ) {
+ if ( trim( $this->summary ) == '' ) {
+ $this->missingSummary = true;
+ $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
+ $status->value = self::AS_SUMMARY_NEEDED;
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
}
- }
- $result['sectionanchor'] = $sectionanchor;
- wfProfileOut( __METHOD__ . '-sectionanchor' );
- // Save errors may fall down to the edit form, but we've now
- // merged the section into full text. Clear the section field
- // so that later submission of conflict forms won't try to
- // replace that into a duplicated mess.
- $this->textbox1 = $text;
- $this->section = '';
+ # All's well
+ wfProfileIn( __METHOD__ . '-sectionanchor' );
+ $sectionanchor = '';
+ if ( $this->section == 'new' ) {
+ if ( $this->textbox1 == '' ) {
+ $this->missingComment = true;
+ $status->fatal( 'missingcommenttext' );
+ $status->value = self::AS_TEXTBOX_EMPTY;
+ wfProfileOut( __METHOD__ . '-sectionanchor' );
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+ if ( $this->sectiontitle !== '' ) {
+ $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
+ // If no edit summary was specified, create one automatically from the section
+ // title and have it link to the new section. Otherwise, respect the summary as
+ // passed.
+ if ( $this->summary === '' ) {
+ $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
+ $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
+ }
+ } elseif ( $this->summary !== '' ) {
+ $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
+ # This is a new section, so create a link to the new section
+ # in the revision summary.
+ $cleanSummary = $wgParser->stripSectionName( $this->summary );
+ $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
+ }
+ } elseif ( $this->section != '' ) {
+ # Try to get a section anchor from the section source, redirect to edited section if header found
+ # XXX: might be better to integrate this into Article::replaceSection
+ # for duplicate heading checking and maybe parsing
+ $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
+ # we can't deal with anchors, includes, html etc in the header for now,
+ # headline would need to be parsed to improve this
+ if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
+ $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] );
+ }
+ }
+ $result['sectionanchor'] = $sectionanchor;
+ wfProfileOut( __METHOD__ . '-sectionanchor' );
- $status->value = self::AS_SUCCESS_UPDATE;
- }
+ // Save errors may fall down to the edit form, but we've now
+ // merged the section into full text. Clear the section field
+ // so that later submission of conflict forms won't try to
+ // replace that into a duplicated mess.
+ $this->textbox1 = $content->serialize( $this->content_format );
+ $this->section = '';
- // Check for length errors again now that the section is merged in
- $this->kblength = (int)( strlen( $text ) / 1024 );
- if ( $this->kblength > $wgMaxArticleSize ) {
- $this->tooBig = true;
- $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
- wfProfileOut( __METHOD__ );
- return $status;
- }
+ $status->value = self::AS_SUCCESS_UPDATE;
+ }
+
+ // Check for length errors again now that the section is merged in
+ $this->kblength = (int)( strlen( $content->serialize( $this->content_format ) ) / 1024 );
+ if ( $this->kblength > $wgMaxArticleSize ) {
+ $this->tooBig = true;
+ $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
- $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
- ( $new ? EDIT_NEW : EDIT_UPDATE ) |
- ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
- ( $bot ? EDIT_FORCE_BOT : 0 );
+ $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
+ ( $new ? EDIT_NEW : EDIT_UPDATE ) |
+ ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
+ ( $bot ? EDIT_FORCE_BOT : 0 );
- $doEditStatus = $this->mArticle->doEdit( $text, $this->summary, $flags );
+ $doEditStatus = $this->mArticle->doEditContent( $content, $this->summary, $flags, false, null, $this->content_format );
- if ( $doEditStatus->isOK() ) {
- $result['redirect'] = Title::newFromRedirect( $text ) !== null;
- $this->commitWatch();
+ if ( $doEditStatus->isOK() ) {
+ $result['redirect'] = $content->isRedirect();
+ $this->commitWatch();
+ wfProfileOut( __METHOD__ );
+ return $status;
+ } else {
+ $this->isConflict = true;
+ $doEditStatus->value = self::AS_END; // Destroys data doEdit() put in $status->value but who cares
+ wfProfileOut( __METHOD__ );
+ return $doEditStatus;
+ }
+ } catch (MWContentSerializationException $ex) {
+ $status->fatal( 'content-failed-to-parse', $this->content_model, $this->content_format, $ex->getMessage() );
+ $status->value = self::AS_PARSE_ERROR;
wfProfileOut( __METHOD__ );
return $status;
- } else {
- $this->isConflict = true;
- $doEditStatus->value = self::AS_END; // Destroys data doEdit() put in $status->value but who cares
- wfProfileOut( __METHOD__ );
- return $doEditStatus;
}
}
global $wgUser;
if ( $this->watchthis xor $this->mTitle->userIsWatching() ) {
$dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
+ $dbw->begin( __METHOD__ );
if ( $this->watchthis ) {
WatchAction::doWatch( $this->mTitle, $wgUser );
} else {
WatchAction::doUnwatch( $this->mTitle, $wgUser );
}
- $dbw->commit();
+ $dbw->commit( __METHOD__ );
}
}
* @return bool
*/
protected function userWasLastToEdit( $id, $edittime ) {
- if( !$id ) return false;
+ if ( !$id ) return false;
$dbw = wfGetDB( DB_MASTER );
$res = $dbw->select( 'revision',
'rev_user',
array(
- 'rev_page' => $this->mTitle->getArticleId(),
- 'rev_timestamp > '.$dbw->addQuotes( $dbw->timestamp($edittime) )
+ 'rev_page' => $this->mTitle->getArticleID(),
+ 'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $edittime ) )
),
__METHOD__,
array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ) );
foreach ( $res as $row ) {
- if( $row->rev_user != $id ) {
+ if ( $row->rev_user != $id ) {
return false;
}
}
* @parma $editText string
*
* @return bool
+ * @deprecated since 1.20
+ */
+ function mergeChangesInto( &$editText ){
+ wfDebug( __METHOD__, "1.20" );
+
+ $editContent = ContentHandler::makeContent( $editText, $this->getTitle(), $this->content_model, $this->content_format );
+
+ $ok = $this->mergeChangesIntoContent( $editContent );
+
+ if ( $ok ) {
+ $editText = $editContent->serialize( $this->content_format ); #XXX: really serialize?!
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @private
+ * @todo document
+ *
+ * @parma $editText string
+ *
+ * @return bool
+ * @since since 1.20
*/
- function mergeChangesInto( &$editText ) {
+ private function mergeChangesIntoContent( &$editContent ){
wfProfileIn( __METHOD__ );
$db = wfGetDB( DB_MASTER );
wfProfileOut( __METHOD__ );
return false;
}
- $baseText = $baseRevision->getText();
+ $baseContent = $baseRevision->getContent();
// The current state, we want to merge updates into it
$currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
wfProfileOut( __METHOD__ );
return false;
}
- $currentText = $currentRevision->getText();
+ $currentContent = $currentRevision->getContent();
- $result = '';
- if ( wfMerge( $baseText, $editText, $currentText, $result ) ) {
- $editText = $result;
+ $handler = ContentHandler::getForModelName( $baseContent->getModelName() );
+
+ $result = $handler->merge3( $baseContent, $editContent, $currentContent );
+
+ if ( $result ) {
+ $editContent = $result;
wfProfileOut( __METHOD__ );
return true;
} else {
*
* @param $text string
*
- * @return string|false matching string or false
+ * @return string|bool matching string or false
*/
public static function matchSpamRegex( $text ) {
global $wgSpamRegex;
*
* @parma $text string
*
- * @return string|false matching string or false
+ * @return string|bool matching string or false
*/
public static function matchSummarySpamRegex( $text ) {
global $wgSummarySpamRegex;
* @return bool|string
*/
protected static function matchSpamRegexInternal( $text, $regexes ) {
- foreach( $regexes as $regex ) {
+ foreach ( $regexes as $regex ) {
$matches = array();
- if( preg_match( $regex, $text, $matches ) ) {
+ if ( preg_match( $regex, $text, $matches ) ) {
return $matches[0];
}
}
# Enabled article-related sidebar, toplinks, etc.
$wgOut->setArticleRelated( true );
+ $contextTitle = $this->getContextTitle();
if ( $this->isConflict ) {
- $wgOut->setPageTitle( wfMessage( 'editconflict', $this->getContextTitle()->getPrefixedText() ) );
- } elseif ( $this->section != '' ) {
+ $msg = 'editconflict';
+ } elseif ( $contextTitle->exists() && $this->section != '' ) {
$msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
- $wgOut->setPageTitle( wfMessage( $msg, $this->getContextTitle()->getPrefixedText() ) );
} else {
- # Use the title defined by DISPLAYTITLE magic word when present
- if ( isset( $this->mParserOutput )
- && ( $dt = $this->mParserOutput->getDisplayTitle() ) !== false ) {
- $title = $dt;
- } else {
- $title = $this->getContextTitle()->getPrefixedText();
- }
- $wgOut->setPageTitle( wfMessage( 'editing', $title ) );
- $msg = $contextTitle->exists() || ( $contextTitle->getNamespace() == NS_MEDIAWIKI && $contextTitle->getDefaultMessageText() !== false ) ?\r
- 'editing' : 'creating';\r
++ $msg = $contextTitle->exists() || ( $contextTitle->getNamespace() == NS_MEDIAWIKI && $contextTitle->getDefaultMessageText() !== false ) ?
++ 'editing' : 'creating';
+ }
+ # Use the title defined by DISPLAYTITLE magic word when present
- $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false;\r
- if ( $displayTitle === false ) {\r
- $displayTitle = $contextTitle->getPrefixedText();\r
++ $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false;
++ if ( $displayTitle === false ) {
++ $displayTitle = $contextTitle->getPrefixedText();
}
+ $wgOut->setPageTitle( wfMessage( $msg, $displayTitle ) );
}
/**
if ( $namespace == NS_MEDIAWIKI ) {
# Show a warning if editing an interface message
$wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
+ } else if( $namespace == NS_FILE ) {
+ # Show a hint to shared repo
+ $file = wfFindFile( $this->mTitle );
+ if( $file && !$file->isLocal() ) {
+ $descUrl = $file->getDescriptionUrl();
+ # there must be a description url to show a hint to shared repo
+ if( $descUrl ) {
+ if( !$this->mTitle->exists() ) {
+ $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", array (
+ 'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
+ ) );
+ } else {
+ $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", array(
+ 'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
+ ) );
+ }
+ }
+ }
}
# Show a warning message when someone creates/edits a user (talk) page but the user does not exist
$username = $parts[0];
$user = User::newFromName( $username, false /* allow IP users*/ );
$ip = User::isIP( $username );
- if ( !($user && $user->isLoggedIn()) && !$ip ) { # User does not exist
+ if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
$wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) );
} elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
'', array( 'lim' => 10,
'conds' => array( "log_action != 'revision'" ),
'showIfEmpty' => false,
- 'msgKey' => array( 'recreate-moveddeleted-warn') )
+ 'msgKey' => array( 'recreate-moveddeleted-warn' ) )
);
}
}
wfProfileIn( __METHOD__ );
- #need to parse the preview early so that we know which templates are used,
- #otherwise users with "show preview after edit box" will get a blank list
- #we parse this near the beginning so that setHeaders can do the title
- #setting work instead of leaving it in getPreviewText
+ # need to parse the preview early so that we know which templates are used,
+ # otherwise users with "show preview after edit box" will get a blank list
+ # we parse this near the beginning so that setHeaders can do the title
+ # setting work instead of leaving it in getPreviewText
$previewOutput = '';
if ( $this->formtype == 'preview' ) {
$previewOutput = $this->getPreviewText();
}
- wfRunHooks( 'EditPage::showEditForm:initial', array( &$this ) );
+ wfRunHooks( 'EditPage::showEditForm:initial', array( &$this, &$wgOut ) );
$this->setHeaders();
}
}
+ #FIXME: add EditForm plugin interface and use it here! #FIXME: search for textarea1 and textares2, and allow EditForm to override all uses.
$wgOut->addHTML( Html::openElement( 'form', array( 'id' => 'editform', 'name' => 'editform',
'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ),
'enctype' => 'multipart/form-data' ) ) );
);
}
+ # When the summary is hidden, also hide them on preview/show changes
+ if( $this->nosummary ) {
+ $wgOut->addHTML( Html::hidden( 'nosummary', true ) );
+ }
+
# If a blank edit summary was previously provided, and the appropriate
# user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
# user being bounced back more than once in the event that a summary
$wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
}
+ if ( $this->hasPresetSummary ) {
+ // If a summary has been preset using &summary= we dont want to prompt for
+ // a different summary. Only prompt for a summary if the summary is blanked.
+ // (Bug 17416)
+ $this->autoSumm = md5( '' );
+ }
+
$autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
$wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
$wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
+ $wgOut->addHTML( Html::hidden( 'format', $this->content_format ) );
+ $wgOut->addHTML( Html::hidden( 'model', $this->content_model ) );
+
if ( $this->section == 'new' ) {
$this->showSummaryInput( true, $this->summary );
$wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
// resolved between page source edits and custom ui edits using the
// custom edit ui.
$this->textbox2 = $this->textbox1;
- $this->textbox1 = $this->getCurrentText();
+
+ $content = $this->getCurrentContent();
+ $this->textbox1 = $content->serialize( $this->content_format );
$this->showTextbox1();
} else {
* @return Mixed|string or false
*/
public static function extractSectionTitle( $text ) {
- preg_match( "/^(=+)(.+)\\1(\n|$)/i", $text, $matches );
+ preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches );
if ( !empty( $matches[2] ) ) {
global $wgParser;
- return $wgParser->stripSectionName(trim($matches[2]));
+ return $wgParser->stripSectionName( trim( $matches[2] ) );
} else {
return false;
}
}
# Optional notices on a per-namespace and per-page basis
- $editnotice_ns = 'editnotice-'.$this->mTitle->getNamespace();
+ $editnotice_ns = 'editnotice-' . $this->mTitle->getNamespace();
$editnotice_ns_message = wfMessage( $editnotice_ns )->inContentLanguage();
if ( $editnotice_ns_message->exists() ) {
$wgOut->addWikiText( $editnotice_ns_message->plain() );
$parts = explode( '/', $this->mTitle->getDBkey() );
$editnotice_base = $editnotice_ns;
while ( count( $parts ) > 0 ) {
- $editnotice_base .= '-'.array_shift( $parts );
+ $editnotice_base .= '-' . array_shift( $parts );
$editnotice_base_msg = wfMessage( $editnotice_base )->inContentLanguage();
if ( $editnotice_base_msg->exists() ) {
- $wgOut->addWikiText( $editnotice_base_msg->plain() );
+ $wgOut->addWikiText( $editnotice_base_msg->plain() );
}
}
} else {
}
if ( $this->mTitle->isCascadeProtected() ) {
# Is this page under cascading protection from some source pages?
- list($cascadeSources, /* $restrictions */) = $this->mTitle->getCascadeProtectionSources();
+ list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
$notice = "<div class='mw-cascadeprotectedwarning'>\n$1\n";
$cascadeSourcesCount = count( $cascadeSources );
if ( $cascadeSourcesCount > 0 ) {
# Explain, and list the titles responsible
- foreach( $cascadeSources as $page ) {
+ foreach ( $cascadeSources as $page ) {
$notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
}
}
}
if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
- array( 'lim' => 1,
+ array( 'lim' => 1,
'showIfEmpty' => false,
'msgKey' => array( 'titleprotectedwarning' ),
'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ) );
$wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
array( 'longpageerror', $wgLang->formatNum( $this->kblength ), $wgLang->formatNum( $wgMaxArticleSize ) ) );
} else {
- if( !wfMessage('longpage-hint')->isDisabled() ) {
+ if ( !wfMessage( 'longpage-hint' )->isDisabled() ) {
$wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
array( 'longpage-hint', $wgLang->formatSize( strlen( $this->textbox1 ) ), strlen( $this->textbox1 ) )
);
*
* @return array An array in the format array( $label, $input )
*/
- function getSummaryInput($summary = "", $labelText = null, $inputAttrs = null, $spanLabelAttrs = null) {
- //Note: the maxlength is overriden in JS to 250 and to make it use UTF-8 bytes, not characters.
- $inputAttrs = ( is_array($inputAttrs) ? $inputAttrs : array() ) + array(
+ function getSummaryInput( $summary = "", $labelText = null, $inputAttrs = null, $spanLabelAttrs = null ) {
+ // Note: the maxlength is overriden in JS to 250 and to make it use UTF-8 bytes, not characters.
+ $inputAttrs = ( is_array( $inputAttrs ) ? $inputAttrs : array() ) + array(
'id' => 'wpSummary',
'maxlength' => '200',
'tabindex' => '1',
'spellcheck' => 'true',
) + Linker::tooltipAndAccesskeyAttribs( 'summary' );
- $spanLabelAttrs = ( is_array($spanLabelAttrs) ? $spanLabelAttrs : array() ) + array(
+ $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : array() ) + array(
'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
'id' => "wpSummaryLabel"
);
}
$summary = $wgContLang->recodeForEdit( $summary );
$labelText = wfMsgExt( $isSubjectPreview ? 'subject' : 'summary', 'parseinline' );
- list($label, $input) = $this->getSummaryInput($summary, $labelText, array( 'class' => $summaryClass ), array());
- $wgOut->addHTML("{$label} {$input}");
+ list( $label, $input ) = $this->getSummaryInput( $summary, $labelText, array( 'class' => $summaryClass ), array() );
+ $wgOut->addHTML( "{$label} {$input}" );
}
/**
HTML
);
if ( !$this->checkUnicodeCompliantBrowser() )
- $wgOut->addHTML(Html::hidden( 'safemode', '1' ));
+ $wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
}
protected function showFormAfterText() {
* The $textoverride method can be used by subclasses overriding showContentForm
* to pass back to this method.
*
- * @param $customAttribs An array of html attributes to use in the textarea
+ * @param $customAttribs array of html attributes to use in the textarea
* @param $textoverride String: optional text to override $this->textarea1 with
*/
protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
$this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6, 'readonly' ) );
}
- protected function showTextbox( $content, $name, $customAttribs = array() ) {
+ protected function showTextbox( $text, $name, $customAttribs = array() ) {
global $wgOut, $wgUser;
- $wikitext = $this->safeUnicodeOutput( $text );
- if ( strval($wikitext) !== '' ) {
+ $wikitext = $this->safeUnicodeOutput( $content );
+ if ( strval( $wikitext ) !== '' ) {
// Ensure there's a newline at the end, otherwise adding lines
// is awkward.
// But don't add a newline if the ext is empty, or Firefox in XHTML
$wgOut->addHTML( '</div>' );
- if ( $this->formtype == 'diff') {
+ if ( $this->formtype == 'diff' ) {
$this->showDiff();
}
}
*/
protected function showPreview( $text ) {
global $wgOut;
- if ( $this->mTitle->getNamespace() == NS_CATEGORY) {
+ if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
$this->mArticle->openShowCategory();
}
# This hook seems slightly odd here, but makes things more
# consistent for extensions.
- wfRunHooks( 'OutputPageBeforeHTML',array( &$wgOut, &$text ) );
+ wfRunHooks( 'OutputPageBeforeHTML', array( &$wgOut, &$text ) );
$wgOut->addHTML( $text );
if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
$this->mArticle->closeShowCategory();
* save and then make a comparison.
*/
function showDiff() {
- global $wgUser, $wgContLang, $wgParser, $wgOut;
+ global $wgUser, $wgContLang, $wgOut;
+
+ $oldContent = $this->getOriginalContent();
+
+ $textboxContent = ContentHandler::makeContent( $this->textbox1, $this->getTitle(),
+ $this->content_model, $this->content_format ); #XXX: handle parse errors ?
+ $newContent = $this->mArticle->replaceSectionContent(
+ $this->section, $textboxContent,
+ $this->summary, $this->edittime );
+ $oldtitlemsg = 'currentrev';
+ # if message does not exist, show diff against the preloaded default
+ if( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
+ $oldtext = $this->mTitle->getDefaultMessageText();
+ if( $oldtext !== false ) {
+ $oldtitlemsg = 'defaultmessagetext';
+ }
+ } else {
+ $oldtext = $this->mArticle->getRawText();
+ }
+ $newtext = $this->mArticle->replaceSection(
+ $this->section, $this->textbox1, $this->summary, $this->edittime );
+ # hanlde legacy text-based hook
+ $newtext_orig = $newContent->serialize( $this->content_format );
+ $newtext = $newtext_orig; #clone
wfRunHooks( 'EditPageGetDiffText', array( $this, &$newtext ) );
+ if ( $newtext != $newtext_orig ) {
+ #if the hook changed the text, create a new Content object accordingly.
+ $newContent = ContentHandler::makeContent( $newtext, $this->getTitle(), $newContent->getModelName() ); #XXX: handle parse errors ?
+ }
+
+ wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) ); #FIXME: document new hook
+
$popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
- $newtext = $wgParser->preSaveTransform( $newtext, $this->mTitle, $wgUser, $popts );
+ $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
+ if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
+ $oldtitle = wfMsgExt( 'currentrev', array( 'parseinline' ) );
+ if ( $oldtext !== false || $newtext != '' ) {
+ $oldtitle = wfMsgExt( $oldtitlemsg, array( 'parseinline' ) );
$newtitle = wfMsgExt( 'yourtext', array( 'parseinline' ) );
- $de = new DifferenceEngine( $this->mArticle->getContext() );
- $de->setText( $oldtext, $newtext );
+ $de = $oldContent->getContentHandler()->getDifferenceEngine( $this->mArticle->getContext() );
+ $de->setContent( $oldContent, $newContent );
+
$difftext = $de->getDiff( $oldtitle, $newtitle );
$de->showDiffStyle();
} else {
protected function showTosSummary() {
$msg = 'editpage-tos-summary';
wfRunHooks( 'EditPageTosSummary', array( $this->mTitle, &$msg ) );
- if( !wfMessage( $msg )->isDisabled() ) {
+ if ( !wfMessage( $msg )->isDisabled() ) {
global $wgOut;
$wgOut->addHTML( '<div class="mw-tos-summary">' );
$wgOut->addWikiMsg( $msg );
wfRunHooks( 'EditPageCopyrightWarning', array( $this->mTitle, &$copywarnMsg ) );
return "<div id=\"editpage-copywarn\">\n" .
- call_user_func_array("wfMsgNoTrans", $copywarnMsg) . "\n</div>";
+ call_user_func_array( "wfMsgNoTrans", $copywarnMsg ) . "\n</div>";
}
protected function showStandardInputs( &$tabindex = 2 ) {
$cancel .= wfMsgExt( 'pipe-separator' , 'escapenoentities' );
}
$edithelpurl = Skin::makeInternalOrExternalUrl( wfMsgForContent( 'edithelppage' ) );
- $edithelp = '<a target="helpwindow" href="'.$edithelpurl.'">'.
- htmlspecialchars( wfMsg( 'edithelp' ) ).'</a> '.
+ $edithelp = '<a target="helpwindow" href="' . $edithelpurl . '">' .
+ htmlspecialchars( wfMsg( 'edithelp' ) ) . '</a> ' .
htmlspecialchars( wfMsg( 'newwindow' ) );
$wgOut->addHTML( " <span class='editHelp'>{$cancel}{$edithelp}</span>\n" );
$wgOut->addHTML( "</div><!-- editButtons -->\n</div><!-- editOptions -->\n" );
if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
$wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
- $de = new DifferenceEngine( $this->mArticle->getContext() );
- $de->setText( $this->textbox2, $this->textbox1 );
+ $content1 = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format ); #XXX: handle parse errors?
+ $content2 = ContentHandler::makeContent( $this->textbox2, $this->getTitle(), $this->content_model, $this->content_format ); #XXX: handle parse errors?
+
+ $handler = ContentHandler::getForModelName( $this->content_model );
+ $de = $handler->getDifferenceEngine( $this->mArticle->getContext() );
+ $de->setContent( $content2, $content1 );
$de->showDiff( wfMsgExt( 'yourtext', 'parseinline' ), wfMsg( 'storedversion' ) );
$wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' )
);
// Quick paranoid permission checks...
- if( is_object( $data ) ) {
- if( $data->log_deleted & LogPage::DELETED_USER )
+ if ( is_object( $data ) ) {
+ if ( $data->log_deleted & LogPage::DELETED_USER )
$data->user_name = wfMsgHtml( 'rev-deleted-user' );
- if( $data->log_deleted & LogPage::DELETED_COMMENT )
+ if ( $data->log_deleted & LogPage::DELETED_COMMENT )
$data->log_comment = wfMsgHtml( 'rev-deleted-comment' );
}
return $data;
return $parsedNote;
}
+ try {
+ $content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format );
+
+ if ( $this->mTriedSave && !$this->mTokenOk ) {
+ if ( $this->mTokenOkExceptSuffix ) {
+ $note = wfMsg( 'token_suffix_mismatch' );
+ } else {
+ $note = wfMsg( 'session_fail_preview' );
+ }
+ } elseif ( $this->incompleteForm ) {
+ $note = wfMsg( 'edit_form_incomplete' );
+ } elseif ( $this->isCssJsSubpage || $this->mTitle->isCssOrJsPage() ) {
+ # if this is a CSS or JS page used in the UI, show a special notice
+ # XXX: stupid php bug won't let us use $this->getContextTitle()->isCssJsSubpage() here -- This note has been there since r3530. Sure the bug was fixed time ago?
+
+ if( $this->mTitle->isCssJsSubpage() ) {
+ $level = 'user';
+ } elseif( $this->mTitle->isCssOrJsPage() ) {
+ $level = 'site';
+ } else {
+ $level = false;
+ }
+
+ if ( $content->getModelName() == CONTENT_MODEL_CSS ) {
+ $format = 'css';
+ } elseif ( $content->getModelName() == CONTENT_MODEL_JAVASCRIPT ) {
+ $format = 'js';
+ } else {
+ $format = false;
+ }
+
+ # Used messages to make sure grep find them:
+ # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
+ if( $level && $format ) {
+ $note = "<div id='mw-{$level}{$format}preview'>" . wfMsg( "{$level}{$format}preview" ) . "</div>";
+ } else {
+ $note = wfMsg( 'previewnote' );
+ }
+ } else {
+ $note = wfMsg( 'previewnote' );
+ }
+
+ $parserOptions = ParserOptions::newFromUser( $wgUser );
+ $parserOptions->setEditSection( false );
+ $parserOptions->setTidy( true );
+ $parserOptions->setIsPreview( true );
+ $parserOptions->setIsSectionPreview( !is_null($this->section) && $this->section !== '' );
+
+ $rt = $content->getRedirectChain();
+
+ if ( $rt ) {
+ $previewHTML = $this->mArticle->viewRedirect( $rt, false );
+ } else {
+
+ # If we're adding a comment, we need to show the
+ # summary as the headline
+ if ( $this->section == "new" && $this->summary != "" ) {
+ $content = $content->addSectionHeader( $this->summary );
+ }
+
+ $toparse_orig = $content->serialize( $this->content_format );
+ $toparse = $toparse_orig;
+ wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
+
+ if ( $toparse !== $toparse_orig ) {
+ #hook changed the text, create new Content object
+ $content = ContentHandler::makeContent( $toparse, $this->getTitle(), $this->content_model, $this->content_format );
+ }
+
+ wfRunHooks( 'EditPageGetPreviewContent', array( $this, &$content ) ); # FIXME: document new hook
+
+ $parserOptions->enableLimitReport();
+
+ #XXX: For CSS/JS pages, we should have called the ShowRawCssJs hook here. But it's now deprecated, so never mind
+ $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
+ $parserOutput = $content->getParserOutput( $this->mTitle, null, $parserOptions );
+
+ $previewHTML = $parserOutput->getText();
+ $this->mParserOutput = $parserOutput;
+ $wgOut->addParserOutputNoText( $parserOutput );
+
+ if ( count( $parserOutput->getWarnings() ) ) {
+ $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
+ }
+ }
+ } catch (MWContentSerializationException $ex) {
+ $note .= "\n\n" . wfMsg('content-failed-to-parse', $this->content_model, $this->content_format, $ex->getMessage() );
+ $previewHTML = '';
+ }
+ if ( $this->mTriedSave && !$this->mTokenOk ) {
+ if ( $this->mTokenOkExceptSuffix ) {
+ $note = wfMsg( 'token_suffix_mismatch' );
+ } else {
+ $note = wfMsg( 'session_fail_preview' );
+ }
+ } elseif ( $this->incompleteForm ) {
+ $note = wfMsg( 'edit_form_incomplete' );
+ } else {
+ $note = wfMsg( 'previewnote' );
+ }
+
+ $parserOptions = ParserOptions::newFromUser( $wgUser );
+ $parserOptions->setEditSection( false );
+ $parserOptions->setTidy( true );
+ $parserOptions->setIsPreview( true );
+ $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
+
+ # don't parse non-wikitext pages, show message about preview
+ # XXX: stupid php bug won't let us use $this->getContextTitle()->isCssJsSubpage() here -- This note has been there since r3530. Sure the bug was fixed time ago?
+
+ if ( $this->isCssJsSubpage || !$this->mTitle->isWikitextPage() ) {
+ if ( $this->mTitle->isCssJsSubpage() ) {
+ $level = 'user';
+ } elseif ( $this->mTitle->isCssOrJsPage() ) {
+ $level = 'site';
+ } else {
+ $level = false;
+ }
+
+ # Used messages to make sure grep find them:
+ # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
+ if ( $level ) {
+ if ( preg_match( "/\\.css$/", $this->mTitle->getText() ) ) {
+ $previewtext = "<div id='mw-{$level}csspreview'>\n" . wfMsg( "{$level}csspreview" ) . "\n</div>";
+ $class = "mw-code mw-css";
+ } elseif ( preg_match( "/\\.js$/", $this->mTitle->getText() ) ) {
+ $previewtext = "<div id='mw-{$level}jspreview'>\n" . wfMsg( "{$level}jspreview" ) . "\n</div>";
+ $class = "mw-code mw-js";
+ } else {
+ throw new MWException( 'A CSS/JS (sub)page but which is not css nor js!' );
+ }
+ }
+
+ $parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions );
+ $previewHTML = $parserOutput->mText;
+ $previewHTML .= "<pre class=\"$class\" dir=\"ltr\">\n" . htmlspecialchars( $this->textbox1 ) . "\n</pre>\n";
+ } else {
+ $toparse = $this->textbox1;
- if( $this->isConflict ) {
+ # If we're adding a comment, we need to show the
+ # summary as the headline
+ if ( $this->section == "new" && $this->summary != "" ) {
+ $toparse = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $toparse;
+ }
+
+ wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
+
+ $parserOptions->enableLimitReport();
+
+ $toparse = $wgParser->preSaveTransform( $toparse, $this->mTitle, $wgUser, $parserOptions );
+ $parserOutput = $wgParser->parse( $toparse, $this->mTitle, $parserOptions );
+
+ $rt = Title::newFromRedirectArray( $this->textbox1 );
+ if ( $rt ) {
+ $previewHTML = $this->mArticle->viewRedirect( $rt, false );
+ } else {
+ $previewHTML = $parserOutput->getText();
+ }
+
+ $this->mParserOutput = $parserOutput;
+ $wgOut->addParserOutputNoText( $parserOutput );
+
+ if ( count( $parserOutput->getWarnings() ) ) {
+ $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
+ }
+ }
+
+ if ( $this->isConflict ) {
$conflict = '<h2 id="mw-previewconflict">' . htmlspecialchars( wfMsg( 'previewconflict' ) ) . "</h2>\n";
} else {
$conflict = '<hr />';
$pageLang = $this->mTitle->getPageLanguage();
$attribs = array( 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
- 'class' => 'mw-content-'.$pageLang->getDir() );
+ 'class' => 'mw-content-' . $pageLang->getDir() );
$previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
wfProfileOut( __METHOD__ );
if ( !isset( $this->mParserOutput ) ) {
return $templates;
}
- foreach( $this->mParserOutput->getTemplates() as $ns => $template) {
- foreach( array_keys( $template ) as $dbk ) {
- $templates[] = Title::makeTitle($ns, $dbk);
+ foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) {
+ foreach ( array_keys( $template ) as $dbk ) {
+ $templates[] = Title::makeTitle( $ns, $dbk );
}
}
return $templates;
'tip' => wfMsg( 'media_tip' ),
'key' => 'M'
) : false,
- $wgUseTeX ? array(
+ $wgUseTeX ? array(
'image' => $wgLang->getImageFile( 'button-math' ),
'id' => 'mw-editbutton-math',
'open' => "<math>",
* Returns an array of html code of the following checkboxes:
* minor and watch
*
- * @param $tabindex Current tabindex
+ * @param $tabindex int Current tabindex
* @param $checked Array of checkbox => bool, where bool indicates the checked
* status of the checkbox
*
* Returns an array of html code of the following buttons:
* save, diff, preview and live
*
- * @param $tabindex Current tabindex
+ * @param $tabindex int Current tabindex
*
* @return array
*/
'tabindex' => ++$tabindex,
'value' => wfMsg( 'savearticle' ),
'accesskey' => wfMsg( 'accesskey-save' ),
- 'title' => wfMsg( 'tooltip-save' ).' ['.wfMsg( 'accesskey-save' ).']',
+ 'title' => wfMsg( 'tooltip-save' ) . ' [' . wfMsg( 'accesskey-save' ) . ']',
);
- $buttons['save'] = Xml::element('input', $temp, '');
+ $buttons['save'] = Xml::element( 'input', $temp, '' );
++$tabindex; // use the same for preview and live preview
$temp = array(
wfDeprecated( __METHOD__, '1.19' );
global $wgUser;
- throw new UserBlockedError( $wgUser->mBlock );
+ throw new UserBlockedError( $wgUser->getBlock() );
}
/**
/**
* Produce the stock "your edit contains spam" page
*
- * @param $match Text which triggered one or more filters
+ * @param $match string Text which triggered one or more filters
* @deprecated since 1.17 Use method spamPageWithContent() instead
*/
static function spamPage( $match = false ) {
/**
* Show "your edit contains spam" page with your diff and text
*
- * @param $match Text which triggered one or more filters
+ * @param $match string|Array|bool Text (or array of texts) which triggered one or more filters
*/
public function spamPageWithContent( $match = false ) {
- global $wgOut;
+ global $wgOut, $wgLang;
$this->textbox2 = $this->textbox1;
+ if( is_array( $match ) ){
+ $match = $wgLang->listToText( $match );
+ }
$wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
$wgOut->addHTML( '<div id="spamprotected">' );
$wgOut->addHTML( '</div>' );
$wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
- $this->showDiff();
+
+ $handler = ContentHandler::getForTitle( $this->getTitle() );
+ $de = $handler->getDifferenceEngine( $this->mArticle->getContext() );
+
+ $content2 = ContentHandler::makeContent( $this->textbox2, $this->getTitle(), $this->content_model, $this->content_format ); #XXX: handle parse errors?
+ $de->setContent( $this->getCurrentContent(), $content2 );
+
+ $de->showDiff( wfMsg( "storedversion" ), wfMsgExt( 'yourtext', 'parseinline' ) );
$wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
$this->showTextbox2();
}
$currentbrowser = $_SERVER["HTTP_USER_AGENT"];
foreach ( $wgBrowserBlackList as $browser ) {
- if ( preg_match($browser, $currentbrowser) ) {
+ if ( preg_match( $browser, $currentbrowser ) ) {
return false;
}
}
$bytesleft = 0;
$result = "";
$working = 0;
- for( $i = 0; $i < strlen( $invalue ); $i++ ) {
+ for ( $i = 0; $i < strlen( $invalue ); $i++ ) {
$bytevalue = ord( $invalue[$i] );
- if ( $bytevalue <= 0x7F ) { //0xxx xxxx
+ if ( $bytevalue <= 0x7F ) { // 0xxx xxxx
$result .= chr( $bytevalue );
$bytesleft = 0;
- } elseif ( $bytevalue <= 0xBF ) { //10xx xxxx
+ } elseif ( $bytevalue <= 0xBF ) { // 10xx xxxx
$working = $working << 6;
- $working += ($bytevalue & 0x3F);
+ $working += ( $bytevalue & 0x3F );
$bytesleft--;
if ( $bytesleft <= 0 ) {
$result .= "&#x" . strtoupper( dechex( $working ) ) . ";";
}
- } elseif ( $bytevalue <= 0xDF ) { //110x xxxx
+ } elseif ( $bytevalue <= 0xDF ) { // 110x xxxx
$working = $bytevalue & 0x1F;
$bytesleft = 1;
- } elseif ( $bytevalue <= 0xEF ) { //1110 xxxx
+ } elseif ( $bytevalue <= 0xEF ) { // 1110 xxxx
$working = $bytevalue & 0x0F;
$bytesleft = 2;
- } else { //1111 0xxx
+ } else { // 1111 0xxx
$working = $bytevalue & 0x07;
$bytesleft = 3;
}
*/
function unmakesafe( $invalue ) {
$result = "";
- for( $i = 0; $i < strlen( $invalue ); $i++ ) {
- if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i+3] != '0' ) ) {
+ for ( $i = 0; $i < strlen( $invalue ); $i++ ) {
+ if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i + 3] != '0' ) ) {
$i += 3;
$hexstring = "";
do {
$hexstring .= $invalue[$i];
$i++;
- } while( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
+ } while ( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
// Do some sanity checks. These aren't needed for reversability,
// but should help keep the breakage down if the editor
// breaks one of the entities whilst editing.
+ if ( ( substr( $invalue, $i, 1 ) == ";" ) and ( strlen( $hexstring ) <= 6 ) ) {
+ $codepoint = hexdec( $hexstring );
+ if ( (substr($invalue,$i,1)==";") and (strlen($hexstring) <= 6) ) {
+ $codepoint = hexdec($hexstring);
$result .= codepointToUtf8( $codepoint );
} else {
$result .= "&#x" . $hexstring . substr( $invalue, $i, 1 );
$timestamp = wfTimestamp( TS_MW, $row->rc_timestamp );
$actiontext = '';
if( $row->rc_type == RC_LOG ) {
- if( $row->rc_deleted & LogPage::DELETED_ACTION ) {
- $actiontext = wfMsgHtml('rev-deleted-event');
- } else {
- $actiontext = LogPage::actionText( $row->rc_log_type, $row->rc_log_action,
- $titleObj, RequestContext::getMain()->getSkin(), LogPage::extractParams($row->rc_params,true,true) );
- }
+ $rcRow = (array)$row; // newFromRow() only accepts arrays for RC rows
+ $actiontext = LogFormatter::newFromRow( $rcRow )->getActionText();
}
return self::formatDiffRow( $titleObj,
$row->rc_last_oldid, $row->rc_this_oldid,
$timestamp,
- ($row->rc_deleted & Revision::DELETED_COMMENT) ? wfMsgHtml('rev-deleted-comment') : $row->rc_comment,
- $actiontext );
+ ($row->rc_deleted & Revision::DELETED_COMMENT)
+ ? wfMsgHtml('rev-deleted-comment')
+ : $row->rc_comment,
+ $actiontext
+ );
}
/**
# $wgLang->time( $timestamp ) ),
# wfMsg( 'currentrev' ) );
+ $diffText = '';
// Don't bother generating the diff if we won't be able to show it
if ( $wgFeedDiffCutoff > 0 ) {
- $de = new DifferenceEngine( $title, $oldid, $newid );
+ $contentHandler = ContentHandler::getForTitle( $title );
+ $de = $contentHandler->getDifferenceEngine( $title, $oldid, $newid );
$diffText = $de->getDiff(
wfMsg( 'previousrevision' ), // hack
wfMsg( 'revisionasof',
* @param $title Title object: used to generate the diff URL
* @param $newid Integer newid for this diff
* @param $oldid Integer|null oldid for the diff. Null means it is a new article
+ * @return string
*/
protected static function getDiffLink( Title $title, $newid, $oldid = null ) {
$queryParameters = ($oldid == null)
/**
* Constructor from a page id
* @param $id Int article ID to load
+ * @returnImagePage|null
*/
public static function newFromID( $id ) {
$t = Title::newFromID( $id );
}
}
- $this->showRedirectedFromHeader();
-
if ( $wgShowEXIF && $this->displayImg->exists() ) {
// @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
$formattedMetadata = $this->displayImg->formatMetadata();
$wgOut->addHTML( Xml::openElement( 'div', array( 'id' => 'mw-imagepage-content',
'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
'class' => 'mw-content-'.$pageLang->getDir() ) ) );
- parent::view();
+
+ parent::view(); #FIXME: use ContentHandler::makeArticle() !!
+
$wgOut->addHTML( Xml::closeElement( 'div' ) );
} else {
# Just need to set the right headers
return $r;
}
- /**
- * Overloading Article's getContent method.
- *
- * Omit noarticletext if sharedupload; text will be fetched from the
- * shared upload server if possible.
- * @return string
- */
- public function getContent() {
- $this->loadFile();
- if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getID() ) {
- return '';
- }
- return parent::getContent();
- }
+ /**
+ * Overloading Article's getContentObject method.
+ *
+ * Omit noarticletext if sharedupload; text will be fetched from the
+ * shared upload server if possible.
+ * @return string
+ */
+ public function getContentObject() {
+ $this->loadFile();
+ if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getID() ) {
+ return null;
+ }
+ return parent::getContentObject();
+ }
protected function openShowImage() {
global $wgOut, $wgUser, $wgImageLimits, $wgRequest,
$max = $wgImageLimits[$sizeSel];
$maxWidth = $max[0];
$maxHeight = $max[1];
- $dirmark = $wgLang->getDirMark();
+ $dirmark = $wgLang->getDirMarkEntity();
if ( $this->displayImg->exists() ) {
# image
if ( !$this->displayImg->isSafeFile() ) {
$warning = wfMsgNoTrans( 'mediawarning' );
+ // dirmark is needed here to separate the file name, which
+ // most likely ends in Latin characters, from the description,
+ // which may begin with the file type. In RTL environment
+ // this will get messy.
+ // The dirmark, however, must not be immediately adjacent
+ // to the filename, because it can get copied with it.
+ // See bug 25277.
$wgOut->addWikiText( <<<EOT
- <div class="fullMedia"><span class="dangerousLink">{$medialink}</span>$dirmark <span class="fileInfo">$longDesc</span></div>
+ <div class="fullMedia"><span class="dangerousLink">{$medialink}</span> $dirmark<span class="fileInfo">$longDesc</span></div>
<div class="mediaWarning">$warning</div>
EOT
);
} else {
$wgOut->addWikiText( <<<EOT
- <div class="fullMedia">{$medialink}{$dirmark} <span class="fileInfo">$longDesc</span>
+ <div class="fullMedia">{$medialink} {$dirmark}<span class="fileInfo">$longDesc</span>
</div>
EOT
);
}
} else {
# Image does not exist
+ if ( !$this->getID() ) {
+ # No article exists either
+ # Show deletion log to be consistent with normal articles
+ LogEventsList::showLogExtract(
+ $wgOut,
+ array( 'delete', 'move' ),
+ $this->getTitle()->getPrefixedText(),
+ '',
+ array( 'lim' => 10,
+ 'conds' => array( "log_action != 'revision'" ),
+ 'showIfEmpty' => false,
+ 'msgKey' => array( 'moveddeleted-notice' )
+ )
+ );
+ }
+
if ( $wgEnableUploads && $wgUser->isAllowed( 'upload' ) ) {
// Only show an upload link if the user can upload
$uploadTitle = SpecialPage::getTitleFor( 'Upload' );
*
* @todo document (e.g. one-sentence top-level class description).
*/
-class LinksUpdate {
+class LinksUpdate extends SecondaryDBDataUpdate {
/**@{{
* @private
*/
var $mId, //!< Page ID of the article linked from
- $mTitle, //!< Title object of the article linked from
- $mParserOutput, //!< Parser output
- $mLinks, //!< Map of title strings to IDs for the links in the document
+ $mTitle, //!< Title object of the article linked from
+ $mParserOutput, //!< Whether to queue jobs for recursive update
+ $mLinks, //!< Map of title strings to IDs for the links in the document
$mImages, //!< DB keys of the images used, in the array key only
$mTemplates, //!< Map of title strings to IDs for the template references, including broken ones
$mExternals, //!< URLs of external links, array key only
$mCategories, //!< Map of category names to sort keys
$mInterlangs, //!< Map of language codes to titles
$mProperties, //!< Map of arbitrary name to value
- $mDb, //!< Database connection reference
- $mOptions, //!< SELECT options to be used (array)
$mRecursive; //!< Whether to queue jobs for recursive updates
/**@}}*/
* @param $recursive Boolean: queue jobs for recursive updates?
*/
function __construct( $title, $parserOutput, $recursive = true ) {
- global $wgAntiLockFlags;
+ parent::__construct( );
- if ( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) {
- $this->mOptions = array();
- } else {
- $this->mOptions = array( 'FOR UPDATE' );
- }
- $this->mDb = wfGetDB( DB_MASTER );
+ if ( !is_object( $title ) ) {
+ throw new MWException( "The calling convention to LinksUpdate::LinksUpdate() has changed. " .
+ "Please see Article::editUpdates() for an invocation example.\n" );
+ }
- if ( !is_object( $title ) ) {
- throw new MWException( "The calling convention to LinksUpdate::__construct() has changed. " .
++ if ( !is_object( $title ) ) {
++ throw new MWException( "The calling convention to LinksUpdate::__construct() has changed. " .
+ "Please see WikiPage::doEditUpdates() for an invocation example.\n" );
- }
- $this->mTitle = $title;
- $this->mId = $title->getArticleID();
++ }
+ $this->mTitle = $title;
+ $this->mId = $title->getArticleID();
+
+ $this->mParserOutput = $parserOutput;
- $this->mParserOutput = $parserOutput;
$this->mLinks = $parserOutput->getLinks();
$this->mImages = $parserOutput->getImages();
$this->mTemplates = $parserOutput->getTemplates();
wfProfileOut( __METHOD__ );
}
- /**
- * Invalidate the cache of a list of pages from a single namespace
- *
- * @param $namespace Integer
- * @param $dbkeys Array
- */
- function invalidatePages( $namespace, $dbkeys ) {
- if ( !count( $dbkeys ) ) {
- return;
- }
-
- /**
- * Determine which pages need to be updated
- * This is necessary to prevent the job queue from smashing the DB with
- * large numbers of concurrent invalidations of the same page
- */
- $now = $this->mDb->timestamp();
- $ids = array();
- $res = $this->mDb->select( 'page', array( 'page_id' ),
- array(
- 'page_namespace' => $namespace,
- 'page_title IN (' . $this->mDb->makeList( $dbkeys ) . ')',
- 'page_touched < ' . $this->mDb->addQuotes( $now )
- ), __METHOD__
- );
- foreach ( $res as $row ) {
- $ids[] = $row->page_id;
- }
- if ( !count( $ids ) ) {
- return;
- }
-
- /**
- * Do the update
- * We still need the page_touched condition, in case the row has changed since
- * the non-locking select above.
- */
- $this->mDb->update( 'page', array( 'page_touched' => $now ),
- array(
- 'page_id IN (' . $this->mDb->makeList( $ids ) . ')',
- 'page_touched < ' . $this->mDb->addQuotes( $now )
- ), __METHOD__
- );
- }
-
/**
* @param $cats
*/
$this->invalidatePages( NS_FILE, array_keys( $images ) );
}
- /**
- * @param $table
- * @param $insertions
- * @param $fromField
- */
- private function dumbTableUpdate( $table, $insertions, $fromField ) {
- $this->mDb->delete( $table, array( $fromField => $this->mId ), __METHOD__ );
- if ( count( $insertions ) ) {
- # The link array was constructed without FOR UPDATE, so there may
- # be collisions. This may cause minor link table inconsistencies,
- # which is better than crippling the site with lock contention.
- $this->mDb->insert( $table, $insertions, __METHOD__, array( 'IGNORE' ) );
- }
- }
+ /**
+ * @param $table
+ * @param $insertions
+ * @param $fromField
+ */
+ private function dumbTableUpdate( $table, $insertions, $fromField ) {
+ $this->mDb->delete( $table, array( $fromField => $this->mId ), __METHOD__ );
+ if ( count( $insertions ) ) {
+ # The link array was constructed without FOR UPDATE, so there may
+ # be collisions. This may cause minor link table inconsistencies,
+ # which is better than crippling the site with lock contention.
+ $this->mDb->insert( $table, $insertions, __METHOD__, array( 'IGNORE' ) );
+ }
+ }
/**
* Update a table by doing a delete query then an insert query
return $arr;
}
- /**
- * Return the title object of the page being updated
- * @return Title
- */
- public function getTitle() {
- return $this->mTitle;
- }
-
- /**
- * Returns parser output
- * @since 1.19
- * @return ParserOutput
- */
- public function getParserOutput() {
- return $this->mParserOutput;
- }
+ /**
+ * Return the title object of the page being updated
+ * @return Title
+ */
+ public function getTitle() {
+ return $this->mTitle;
+ }
+
+ /**
+ * Returns parser output
+ * @since 1.19
+ * @return ParserOutput
+ */
+ public function getParserOutput() {
+ return $this->mParserOutput;
+ }
/**
* Return the list of images used as generated by the parser
protected $mTextRow;
protected $mTitle;
protected $mCurrent;
+ protected $mContentModelName;
+ protected $mContentFormat;
+ protected $mContent;
+ protected $mContentHandler;
const DELETED_TEXT = 1;
const DELETED_COMMENT = 2;
'deleted' => $row->ar_deleted,
'len' => $row->ar_len,
'sha1' => isset( $row->ar_sha1 ) ? $row->ar_sha1 : null,
+ 'content_model' => isset( $row->ar_content_model ) ? $row->ar_content_model : null,
+ 'content_format' => isset( $row->ar_content_format ) ? $row->ar_content_format : null,
);
if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
// Pre-1.5 ar_text row
/**
* Return the list of revision fields that should be selected to create
* a new revision.
+ * @return array
*/
public static function selectFields() {
return array(
'rev_deleted',
'rev_len',
'rev_parent_id',
- 'rev_sha1'
+ 'rev_sha1',
+ 'rev_content_format',
+ 'rev_content_model'
);
}
/**
* Return the list of text fields that should be selected to read the
* revision text
+ * @return array
*/
public static function selectTextFields() {
return array(
/**
* Return the list of page fields that should be selected from page table
+ * @return array
*/
public static function selectPageFields() {
return array(
/**
* Return the list of user fields that should be selected from user table
+ * @return array
*/
public static function selectUserFields() {
return array( 'user_name' );
$this->mTitle = null;
}
+ if( !isset( $row->rev_content_model ) || is_null( $row->rev_content_model ) ) {
+ $this->mContentModelName = null; # determine on demand if needed
+ } else {
+ $this->mContentModelName = strval( $row->rev_content_model );
+ }
+
+ if( !isset( $row->rev_content_format ) || is_null( $row->rev_content_format ) ) {
+ $this->mContentFormat = null; # determine on demand if needed
+ } else {
+ $this->mContentFormat = strval( $row->rev_content_format );
+ }
+
// Lazy extraction...
$this->mText = null;
if( isset( $row->old_text ) ) {
// Build a new revision to be saved...
global $wgUser; // ugh
+
+ # if we have a content object, use it to set the model and type
+ if ( !empty( $row['content'] ) ) {
+ if ( !empty( $row['text_id'] ) ) { #FIXME: when is that set? test with external store setup! check out insertOn()
+ throw new MWException( "Text already stored in external store (id {$row['text_id']}), can't serialize content object" );
+ }
+
+ $row['content_model'] = $row['content']->getModelName();
+ # note: mContentFormat is initializes later accordingly
+ # note: content is serialized later in this method!
+ # also set text to null?
+ }
+
$this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
$this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
$this->mTextId = isset( $row['text_id'] ) ? intval( $row['text_id'] ) : null;
$this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
$this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
+ $this->mContentModelName = isset( $row['content_model'] ) ? strval( $row['content_model'] ) : null;
+ $this->mContentFormat = isset( $row['content_format'] ) ? strval( $row['content_format'] ) : null;
+
// Enforce spacing trimming on supplied text
$this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
$this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
$this->mCurrent = false;
# If we still have no length, see it we have the text to figure it out
if ( !$this->mSize ) {
+ #XXX: my be inconsistent with the notion of "size" use for the present content model
$this->mSize = is_null( $this->mText ) ? null : strlen( $this->mText );
}
# Same for sha1
if ( $this->mSha1 === null ) {
$this->mSha1 = is_null( $this->mText ) ? null : self::base36Sha1( $this->mText );
}
+
+ $this->getContentModelName(); # force lazy init
+ $this->getContentFormat(); # force lazy init
+
+ # if we have a content object, serialize it, overriding mText
+ if ( !empty( $row['content'] ) ) {
+ $handler = $this->getContentHandler();
+ $this->mText = $handler->serialize( $row['content'], $this->getContentFormat() );
+ }
} else {
throw new MWException( 'Revision constructor passed invalid row format.' );
}
$this->mUnpatrolled = null;
+
+ #FIXME: add patch for ar_content_format, ar_content_model, rev_content_format, rev_content_model to installer
+ #FIXME: add support for ar_content_format, ar_content_model, rev_content_format, rev_content_model to API
}
/**
/**
* Get parent revision ID (the original previous page revision)
*
- * @return Integer
+ * @return Integer|null
*/
public function getParentId() {
return $this->mParentId;
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow(
array( 'page', 'revision' ),
- array( 'page_namespace', 'page_title' ),
+ self::selectPageFields(),
array( 'page_id=rev_page',
'rev_id' => $this->mId ),
- 'Revision::getTitle' );
- if( $row ) {
- $this->mTitle = Title::makeTitle( $row->page_namespace, $row->page_title );
+ __METHOD__ );
+ if ( $row ) {
+ $this->mTitle = Title::newFromRow( $row );
}
return $this->mTitle;
}
* @param $user User object to check for, only if FOR_THIS_USER is passed
* to the $audience parameter
* @return String
+ * @deprectaed in 1.20, use getContent() instead
*/
- public function getText( $audience = self::FOR_PUBLIC, User $user = null ) {
- if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
- return '';
- } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
- return '';
- } else {
- return $this->getRawText();
- }
+ public function getText( $audience = self::FOR_PUBLIC, User $user = null ) { #FIXME: deprecated, replace usage! #FIXME: used a LOT!
+ wfDeprecated( __METHOD__, '1.20' );
+
+ $content = $this->getContent();
+ return ContentHandler::getContentText( $content ); # returns the raw content text, if applicable
}
+ /**
+ * Fetch revision content if it's available to the specified audience.
+ * If the specified audience does not have the ability to view this
+ * revision, null will be returned.
+ *
+ * @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
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
+ * @return Content
+ */
+ public function getContent( $audience = self::FOR_PUBLIC, User $user = null ) {
+ if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
+ return null;
+ } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
+ return null;
+ } else {
+ return $this->getContentInternal();
+ }
+ }
+
/**
* Alias for getText(Revision::FOR_THIS_USER)
*
*
* @return String
*/
- public function getRawText() {
- if( is_null( $this->mText ) ) {
- // Revision text is immutable. Load on demand:
- $this->mText = $this->loadText();
- }
- return $this->mText;
+ public function getRawText() { #FIXME: deprecated, replace usage!
+ return $this->getText( self::RAW );
}
+ protected function getContentInternal() {
+ if( is_null( $this->mContent ) ) {
+ // Revision is immutable. Load on demand:
+
+ $handler = $this->getContentHandler();
+ $format = $this->getContentFormat();
+ $title = $this->getTitle();
+
+ if( is_null( $this->mText ) ) {
+ // Load text on demand:
+ $this->mText = $this->loadText();
+ }
+
+ $this->mContent = is_null( $this->mText ) ? null : $handler->unserialize( $this->mText, $format );
+ }
+
+ return $this->mContent;
+ }
+
+ public function getContentModelName() {
+ if ( !$this->mContentModelName ) {
+ $title = $this->getTitle();
+ $this->mContentModelName = ( $title ? $title->getContentModelName() : CONTENT_MODEL_WIKITEXT );
+ }
+
+ return $this->mContentModelName;
+ }
+
+ public function getContentFormat() {
+ if ( !$this->mContentFormat ) {
+ $handler = $this->getContentHandler();
+ $this->mContentFormat = $handler->getDefaultFormat();
+ }
+
+ return $this->mContentFormat;
+ }
+
+ public function getContentHandler() {
+ if ( !$this->mContentHandler ) {
+ $title = $this->getTitle();
+
+ if ( $title ) $model = $title->getContentModelName();
+ else $model = CONTENT_MODEL_WIKITEXT;
+
+ $this->mContentHandler = ContentHandler::getForModelName( $model );
+
+ #XXX: do we need to verify that mContentHandler supports mContentFormat?
+ # otherwise, a fixed content format may cause problems on insert.
+ }
+
+ return $this->mContentHandler;
+ }
+
/**
* @return String
*/
$rev_id = isset( $this->mId )
? $this->mId
: $dbw->nextSequenceValue( 'revision_rev_id_seq' );
- $dbw->insert( 'revision',
- array(
- 'rev_id' => $rev_id,
- 'rev_page' => $this->mPage,
- 'rev_text_id' => $this->mTextId,
- 'rev_comment' => $this->mComment,
- 'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
- 'rev_user' => $this->mUser,
- 'rev_user_text' => $this->mUserText,
- 'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
- 'rev_deleted' => $this->mDeleted,
- 'rev_len' => $this->mSize,
- 'rev_parent_id' => is_null( $this->mParentId )
- ? $this->getPreviousRevisionId( $dbw )
- : $this->mParentId,
- 'rev_sha1' => is_null( $this->mSha1 )
- ? Revision::base36Sha1( $this->mText )
- : $this->mSha1
- ), __METHOD__
- );
+
+ $row = array(
+ 'rev_id' => $rev_id,
+ 'rev_page' => $this->mPage,
+ 'rev_text_id' => $this->mTextId,
+ 'rev_comment' => $this->mComment,
+ 'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
+ 'rev_user' => $this->mUser,
+ 'rev_user_text' => $this->mUserText,
+ 'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
+ 'rev_deleted' => $this->mDeleted,
+ 'rev_len' => $this->mSize,
+ 'rev_parent_id' => is_null( $this->mParentId )
+ ? $this->getPreviousRevisionId( $dbw )
+ : $this->mParentId,
+ 'rev_sha1' => is_null( $this->mSha1 )
+ ? Revision::base36Sha1( $this->mText )
+ : $this->mSha1,
+ 'rev_content_model' => $this->getContentModelName(),
+ 'rev_content_format' => $this->getContentFormat(),
+ );
+
+ $dbw->insert( 'revision', $row, __METHOD__ );
$this->mId = !is_null( $rev_id ) ? $rev_id : $dbw->insertId();
$current = $dbw->selectRow(
array( 'page', 'revision' ),
- array( 'page_latest', 'rev_text_id', 'rev_len', 'rev_sha1' ),
+ array( 'page_latest', 'rev_text_id', 'rev_len', 'rev_sha1',
+ 'rev_content_model', 'rev_content_format' ),
array(
'page_id' => $pageId,
'page_latest=rev_id',
'text_id' => $current->rev_text_id,
'parent_id' => $current->page_latest,
'len' => $current->rev_len,
- 'sha1' => $current->rev_sha1
+ 'sha1' => $current->rev_sha1,
+ 'content_model' => $current->rev_content_model,
+ 'content_format' => $current->rev_content_format
) );
} else {
$revision = null;
$id = 0;
}
$conds = array( 'rev_id' => $id );
- $conds['rev_page'] = $title->getArticleId();
+ $conds['rev_page'] = $title->getArticleID();
$timestamp = $dbr->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
if ( $timestamp === false && wfGetLB()->getServerCount() > 1 ) {
# Not in slave, try master
* @return Integer
*/
static function countByTitle( $db, $title ) {
- $id = $title->getArticleId();
+ $id = $title->getArticleID();
if( $id ) {
return Revision::countByPageId( $db, $id );
}
* Load Title object fields from a DB row.
* If false is given, the title will be treated as non-existing.
*
- * @param $row Object|false database row
- * @return void
+ * @param $row Object|bool database row
*/
public function loadFromRow( $row ) {
if ( $row ) { // page found
if ( isset( $row->page_is_redirect ) )
$this->mRedirect = (bool)$row->page_is_redirect;
if ( isset( $row->page_latest ) )
- $this->mLatestID = (int)$row->page_latest;
+ $this->mLatestID = (int)$row->page_latest; # FIXME: whene3ver page_latest is updated, also update page_content_model
+ if ( isset( $row->page_content_model ) )
+ $this->mContentModelName = $row->page_content_model;
+ else
+ $this->mContentModelName = null; # initialized lazily in getContentModelName()
} else { // page not found
$this->mArticleID = 0;
$this->mLength = 0;
$this->mRedirect = false;
$this->mLatestID = 0;
+ $this->mContentModelName = null; # initialized lazily in getContentModelName()
}
}
$t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
$t->mUrlform = wfUrlencode( $t->mDbkeyform );
$t->mTextform = str_replace( '_', ' ', $title );
+ $t->mContentModelName = null; # initialized lazily in getContentModelName()
return $t;
}
return $this->mNamespace;
}
+ /**
+ * Get the page's content model name
+ *
+ * @return Integer: Namespace index
+ */
+ public function getContentModelName() {
+ if ( empty( $this->mContentModelName ) ) {
+ $this->mContentModelName = ContentHandler::getDefaultModelFor( $this );
+ }
+
+ return $this->mContentModelName;
+ }
+
+ /**
+ * Conveniance method for checking a title's content model name
+ *
+ * @param $name
+ * @return true if $this->getContentModelName() == $name
+ */
+ public function hasContentModel( $name ) {
+ return $this->getContentModelName() == $name;
+ }
+
/**
* Get the namespace text
*
* This is MUCH simpler than individually testing for equivilance
* against both NS_USER and NS_USER_TALK, and is also forward compatible.
* @since 1.19
+ * @return bool
*/
public function hasSubjectNamespace( $ns ) {
return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
* @return Bool
*/
public function isWikitextPage() {
- $retval = !$this->isCssOrJsPage() && !$this->isCssJsSubpage();
- wfRunHooks( 'TitleIsWikitextPage', array( $this, &$retval ) );
- return $retval;
+ return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
}
/**
- * Could this page contain custom CSS or JavaScript, based
- * on the title?
+ * Could this page contain custom CSS or JavaScript for the global UI.
+ * This is generally true for pages in the MediaWiki namespace having CONTENT_MODEL_CSS
+ * or CONTENT_MODEL_JAVASCRIPT.
+ *
+ * Note that this method should not return true for pages that contain and show "inactive" CSS or JS.
*
* @return Bool
*/
public function isCssOrJsPage() {
- $retval = $this->mNamespace == NS_MEDIAWIKI
- && preg_match( '!\.(?:css|js)$!u', $this->mTextform ) > 0;
- wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$retval ) );
- return $retval;
+ $isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace
+ && ( $this->hasContentModel( CONTENT_MODEL_CSS )
+ || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
+
+ #NOTE: this hook is also called in ContentHandler::getDefaultModel. It's called here again to make sure
+ # hook funktions can force this method to return true even outside the mediawiki namespace.
+
+ wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$isCssOrJsPage ) );
+
+ return $isCssOrJsPage;
}
/**
* @return Bool
*/
public function isCssJsSubpage() {
- return ( NS_USER == $this->mNamespace and preg_match( "/\\/.*\\.(?:css|js)$/", $this->mTextform ) );
+ return ( NS_USER == $this->mNamespace && $this->isSubpage()
+ && $this->isCssOrJsPage() );
}
/**
* @return Bool
*/
public function isCssSubpage() {
- return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.css$/", $this->mTextform ) );
+ return ( NS_USER == $this->mNamespace && $this->isSubpage()
+ && $this->hasContentModel( CONTENT_MODEL_CSS ) );
}
/**
* @return Bool
*/
public function isJsSubpage() {
- return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.js$/", $this->mTextform ) );
+ return ( NS_USER == $this->mNamespace && $this->isSubpage()
+ && $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
}
/**
* andthe wfArrayToCGI moved to getLocalURL();
*
* @since 1.19 (r105919)
+ * @return String
*/
private static function fixUrlQueryArgs( $query, $query2 = false ) {
if( $query2 !== false ) {
* with action=render, $wgServer is prepended.
*
- * @param $query \twotypes{\string,\array} an optional query string,
+ * @param $query string|array an optional query string,
* not used for interwiki links. Can be specified as an associative array as well,
* e.g., array( 'action' => 'edit' ) (keys and values will be URL-escaped).
* Some query patterns will trigger various shorturl path replacements.
*
* @see self::getLocalURL
* @since 1.18
+ * @return string
*/
public function escapeCanonicalURL( $query = '', $query2 = false ) {
wfDeprecated( __METHOD__, '1.19' );
// Don't block the user from editing their own talk page unless they've been
// explicitly blocked from that too.
} elseif( $user->isBlocked() && $user->mBlock->prevents( $action ) !== false ) {
- $block = $user->mBlock;
+ $block = $user->getBlock();
// This is from OutputPage::blockedPage
// Copied at r23888 by werdna
$link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
$blockid = $block->getId();
- $blockExpiry = $user->mBlock->mExpiry;
- $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $user->mBlock->mTimestamp ), true );
+ $blockExpiry = $block->getExpiry();
+ $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $block->mTimestamp ), true );
if ( $blockExpiry == 'infinity' ) {
$blockExpiry = wfMessage( 'infiniteblock' )->text();
} else {
$blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
}
- $intended = strval( $user->mBlock->getTarget() );
+ $intended = strval( $block->getTarget() );
$errors[] = array( ( $block->mAuto ? 'autoblockedtext' : 'blockedtext' ), $link, $reason, $ip, $name,
$blockid, $blockExpiry, $intended, $blockTimestamp );
$name = $this->getPrefixedText();
$dbName = $this->getPrefixedDBKey();
- // Check with and without underscores
+ // Check for explicit whitelisting with and without underscores
if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
- # Check for explicit whitelisting
$whitelisted = true;
} elseif ( $this->getNamespace() == NS_MAIN ) {
# Old settings might have the title prefixed with
if ( $oldFashionedRestrictions === null ) {
$oldFashionedRestrictions = $dbr->selectField( 'page', 'page_restrictions',
- array( 'page_id' => $this->getArticleId() ), __METHOD__ );
+ array( 'page_id' => $this->getArticleID() ), __METHOD__ );
}
if ( $oldFashionedRestrictions != '' ) {
$res = $dbr->select(
'page_restrictions',
'*',
- array( 'pr_page' => $this->getArticleId() ),
+ array( 'pr_page' => $this->getArticleID() ),
__METHOD__
);
* @return Array of Title objects linking here
*/
public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
- $id = $this->getArticleId();
+ $id = $this->getArticleID();
# If the page doesn't exist; there can't be any link from this page
if ( !$id ) {
* @return Array of Title the Title objects
*/
public function getBrokenLinksFrom() {
- if ( $this->getArticleId() == 0 ) {
+ if ( $this->getArticleID() == 0 ) {
# All links from article ID 0 are false positives
return array();
}
array( 'page', 'pagelinks' ),
array( 'pl_namespace', 'pl_title' ),
array(
- 'pl_from' => $this->getArticleId(),
+ 'pl_from' => $this->getArticleID(),
'page_namespace IS NULL'
),
__METHOD__, array(),
RepoGroup::singleton()->clearCache( $nt ); # clear false negative cache
}
- $dbw->begin(); # If $file was a LocalFile, its transaction would have closed our own.
+ $dbw->begin( __METHOD__ ); # If $file was a LocalFile, its transaction would have closed our own.
$pageid = $this->getArticleID( self::GAID_FOR_UPDATE );
$protected = $this->isProtected();
$err = $this->moveToInternal( $nt, $reason, $createRedirect );
if ( is_array( $err ) ) {
# @todo FIXME: What about the File we have already moved?
- $dbw->rollback();
+ $dbw->rollback( __METHOD__ );
return $err;
}
WatchedItem::duplicateEntries( $this, $nt );
}
- $dbw->commit();
+ $dbw->commit( __METHOD__ );
wfRunHooks( 'TitleMoveComplete', array( &$this, &$nt, &$wgUser, $pageid, $redirid ) );
return true;
$comment = $wgContLang->truncate( $comment, 255 );
$oldid = $this->getArticleID();
- $latest = $this->getLatestRevID();
$dbw = wfGetDB( DB_MASTER );
$newpage->updateRevisionOn( $dbw, $nullRevision );
wfRunHooks( 'NewRevisionFromEditComplete',
- array( $newpage, $nullRevision, $latest, $wgUser ) );
+ array( $newpage, $nullRevision, $nullRevision->getParentId(), $wgUser ) );
$newpage->doEditUpdates( $nullRevision, $wgUser, array( 'changed' => false ) );
+ if ( !$moveOverRedirect ) {
+ WikiPage::onArticleCreate( $nt );
+ }
+
# Recreate the redirect, this time in the other direction.
if ( $redirectSuppressed ) {
WikiPage::onArticleDelete( $this );
// We don't know whether this function was called before
// or after moving the root page, so check both
// $this and $nt
- if ( $oldSubpage->getArticleId() == $this->getArticleId() ||
- $oldSubpage->getArticleID() == $nt->getArticleId() )
+ if ( $oldSubpage->getArticleID() == $this->getArticleID() ||
+ $oldSubpage->getArticleID() == $nt->getArticleID() )
{
// When moving a page to a subpage of itself,
// don't move it twice
$data = array();
- $titleKey = $this->getArticleId();
+ $titleKey = $this->getArticleID();
if ( $titleKey === 0 ) {
return $data;
$db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
return $db->selectField( 'revision', 'rev_id',
array(
- 'rev_page' => $this->getArticleId( $flags ),
+ 'rev_page' => $this->getArticleID( $flags ),
'rev_id < ' . intval( $revId )
),
__METHOD__,
$db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
return $db->selectField( 'revision', 'rev_id',
array(
- 'rev_page' => $this->getArticleId( $flags ),
+ 'rev_page' => $this->getArticleID( $flags ),
'rev_id > ' . intval( $revId )
),
__METHOD__,
* @return Revision|Null if page doesn't exist
*/
public function getFirstRevision( $flags = 0 ) {
- $pageId = $this->getArticleId( $flags );
+ $pageId = $this->getArticleID( $flags );
if ( $pageId ) {
$db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
$row = $db->selectRow( 'revision', '*',
if ( $this->mEstimateRevisions === null ) {
$dbr = wfGetDB( DB_SLAVE );
$this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
- array( 'rev_page' => $this->getArticleId() ), __METHOD__ );
+ array( 'rev_page' => $this->getArticleID() ), __METHOD__ );
}
return $this->mEstimateRevisions;
$dbr = wfGetDB( DB_SLAVE );
return (int)$dbr->selectField( 'revision', 'count(*)',
array(
- 'rev_page' => $this->getArticleId(),
+ 'rev_page' => $this->getArticleID(),
'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
),
* @return Bool
*/
public function exists() {
- return $this->getArticleId() != 0;
+ return $this->getArticleID() != 0;
}
/**
* @return Bool
*/
public function isAlwaysKnown() {
+ $isKnown = null;
+
+ /**
+ * Allows overriding default behaviour for determining if a page exists.
+ * If $isKnown is kept as null, regular checks happen. If it's
+ * a boolean, this value is returned by the isKnown method.
+ *
+ * @since 1.20
+ *
+ * @param Title $title
+ * @param boolean|null $isKnown
+ */
+ wfRunHooks( 'TitleIsAlwaysKnown', array( $this, &$isKnown ) );
+
+ if ( !is_null( $isKnown ) ) {
+ return $isKnown;
+ }
+
if ( $this->mInterwiki != '' ) {
return true; // any interwiki link might be viewable, for all we know
}
+
switch( $this->mNamespace ) {
case NS_MEDIA:
case NS_FILE:
* viewed? In particular, this function may be used to determine if
* links to the title should be rendered as "bluelinks" (as opposed to
* "redlinks" to non-existent pages).
+ * Adding something else to this function will cause inconsistency
+ * since LinkHolderArray calls isAlwaysKnown() and does its own
+ * page existence check.
*
* @return Bool
*/
if ( $this->isSpecialPage() ) {
// special pages are in the user language
return $wgLang;
- } elseif ( $this->isCssOrJsPage() ) {
+ } elseif ( $this->isCssOrJsPage() || $this->isCssJsSubpage() ) {
// css/js should always be LTR and is, in fact, English
return wfGetLangObj( 'en' );
} elseif ( $this->getNamespace() == NS_MEDIAWIKI ) {
* @internal documentation reviewed 15 Mar 2010
*/
class WikiPage extends Page {
+ // doDeleteArticleReal() return values. Values less than zero indicate fatal errors,
+ // values greater than zero indicate that there were problems not resulting in page
+ // not being deleted
+
+ /**
+ * Delete operation aborted by hook
+ */
+ const DELETE_HOOK_ABORTED = -1;
+
+ /**
+ * Deletion successful
+ */
+ const DELETE_SUCCESS = 0;
+
+ /**
+ * Page not found
+ */
+ const DELETE_NO_PAGE = 1;
+
+ /**
+ * No revisions found to delete
+ */
+ const DELETE_NO_REVISIONS = 2;
+
/**
* @var Title
*/
*
* @param $id Int article ID to load
*
- * @return WikiPage
+ * @return WikiPage|null
*/
public static function newFromID( $id ) {
$t = Title::newFromID( $id );
'page_touched',
'page_latest',
'page_len',
+ 'page_content_model',
);
}
* @return bool
*/
public function isRedirect( $text = false ) {
- if ( $text === false ) {
- if ( !$this->mDataLoaded ) {
- $this->loadPageData();
- }
+ if ( $text === false ) $content = $this->getContent();
+ else $content = ContentHandler::makeContent( $text, $this->mTitle ); # TODO: allow model and format to be provided; or better, expect a Content object
- return (bool)$this->mIsRedirect;
- } else {
- return Title::newFromRedirect( $text ) !== null;
- }
+
+ if ( empty( $content ) ) return false;
+ else return $content->isRedirect();
}
+ /**
+ * Returns the page's content model name. Will use the revisions actual content model if the page exists,
+ * and the page's default if the page doesn't exist yet.
+ *
+ * @return int
+ */
+ public function getContentModelName() {
+ if ( $this->exists() ) {
+ # look at the revision's actual content model
+ $content = $this->getContent();
+ return $content->getModelName();
+ } else {
+ # use the default model for this page
+ return $this->mTitle->getContentModelName();
+ }
+ }
+
/**
* Loads page_touched and returns a value indicating if it should be used
* @return boolean true if not a redirect
return null;
}
+ /**
+ * 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 Content|null The content of the current revision
+ */
+ public function getContent( $audience = Revision::FOR_PUBLIC ) {
+ $this->loadLastEdit();
+ if ( $this->mLastRevision ) {
+ return $this->mLastRevision->getContent( $audience );
+ }
+ return false;
+ }
+
/**
* Get the text of the current revision. No side-effects...
*
* 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
+ * @return String|false The text of the current revision
+ * @deprecated as of 1.20, getContent() should be used instead.
*/
- public function getText( $audience = Revision::FOR_PUBLIC ) {
+ public function getText( $audience = Revision::FOR_PUBLIC ) { #FIXME: deprecated, replace usage!
+ wfDeprecated( __METHOD__, '1.20' );
$this->loadLastEdit();
if ( $this->mLastRevision ) {
return $this->mLastRevision->getText( $audience );
/**
* Get the text of the current revision. No side-effects...
*
- * @return String|false The text of the current revision
+ * @return String|bool The text of the current revision. False on failure
*/
- public function getRawText() {
- $this->loadLastEdit();
- if ( $this->mLastRevision ) {
- return $this->mLastRevision->getRawText();
- }
- return false;
+ public function getRawText() { #FIXME: deprecated, replace usage!
+ return $this->getText( Revision::RAW );
}
+ /**
+ * Get the content of the current revision. No side-effects...
+ *
+ * @return Contet|false The text of the current revision
+ */
+ protected function getNativeData() { #FIXME: examine all uses carefully! caller must be aware of content model!
+ $content = $this->getContent( Revision::RAW );
+ if ( !$content ) return null;
+
+ return $content->getNativeData();
+ }
+
/**
* @return string MW timestamp of last article revision
*/
if ( !$this->mTimestamp ) {
$this->loadLastEdit();
}
+
return wfTimestamp( TS_MW, $this->mTimestamp );
}
return false;
}
- $text = $editInfo ? $editInfo->pst : false;
+ if ( $editInfo ) {
+ $content = ContentHandler::makeContent( $editInfo->pst, $this->mTitle );
+ # TODO: take model and format from edit info!
+ } 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':
- 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() );
- } else {
- return (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
- array( 'pl_from' => $this->getId() ), __METHOD__ );
- }
- }
+ $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.
+ $hasLinks = (bool)count( $editInfo->output->getLinks() );
+ } else {
+ $hasLinks = (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
+ array( 'pl_from' => $this->getId() ), __METHOD__ );
+ }
+ }
+
+ return $content->isCountable( $hasLinks );
}
/**
*/
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;
}
&& $parserOptions->getStubThreshold() == 0
&& $this->mTitle->exists()
&& ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() )
- && $this->mTitle->isWikitextPage();
+ && $this->mTitle->isWikitextPage(); #FIXME: ask ContentHandler if cachable!
}
/**
/**
* Perform the actions of a page purging
+ * @return bool
*/
public function doPurge() {
global $wgUseSquid;
if ( $wgUseSquid ) {
// Commit the transaction before the purge is sent
$dbw = wfGetDB( DB_MASTER );
- $dbw->commit();
+ $dbw->commit( __METHOD__ );
// Send purge
$update = SquidUpdate::newSimplePurge( $this->mTitle );
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
if ( $this->mTitle->exists() ) {
- $text = $this->getRawText();
+ $text = $this->getNativeData(); #FIXME: may not be a string. check Content model!
} else {
$text = false;
}
public function updateRevisionOn( $dbw, $revision, $lastRevision = null, $lastRevIsRedirect = null ) {
wfProfileIn( __METHOD__ );
- $text = $revision->getText();
- $len = strlen( $text );
- $rt = Title::newFromRedirectRecurse( $text );
+ $content = $revision->getContent();
+ $len = $content->getSize();
+ $rt = $content->getUltimateRedirectTarget();
$conditions = array( 'page_id' => $this->getId() );
* @param $dbw DatabaseBase
* @param $redirectTitle Title object pointing to the redirect target,
* or NULL if this is not a redirect
- * @param $lastRevIsRedirect If given, will optimize adding and
+ * @param $lastRevIsRedirect null|bool If given, will optimize adding and
* removing rows in redirect table.
* @return bool true on success, false on failure
* @private
* If the given revision is newer than the currently set page_latest,
* update the page record. Otherwise, do nothing.
*
- * @param $dbw Database object
+ * @param $dbw DatabaseBase object
* @param $revision Revision object
* @return mixed
*/
* @param $undo Revision
* @param $undoafter Revision Must be an earlier revision than $undo
* @return mixed string on success, false on failure
+ * @deprecated since 1.20: 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();
+ public function getUndoText( Revision $undo, Revision $undoafter = null ) { #FIXME: replace usages.
+ $this->loadLastEdit();
- if ( $cur_text == $undo_text ) {
- # No use doing a merge if it's just a straight revert.
- return $undoafter_text;
- }
+ if ( $this->mLastRevision ) {
+ $handler = ContentHandler::getForTitle( $this->getTitle() );
+ $undone = $handler->getUndoContent( $this->mLastRevision, $undo, $undoafter );
- $undone_text = '';
+ if ( !$undone ) {
+ return false;
+ } else {
+ return ContentHandler::getContentText( $undone );
+ }
+ }
- if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) ) {
- return false;
- }
-
- return $undone_text;
+ return false;
}
/**
- * @param $section empty/null/false or a section number (0, 1, 2, T1, T2...)
+ * @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
+ * @return Content new complete article content, or null if error
+ * @deprected since 1.20, use replaceSectionContent() instead
*/
- public function replaceSection( $section, $text, $sectionTitle = '', $edittime = null ) {
- wfProfileIn( __METHOD__ );
+ public function replaceSection( $section, $text, $sectionTitle = '', $edittime = null ) { #FIXME: use replaceSectionContent() instead!
+ wfDeprecated( __METHOD__, '1.20' );
- if ( strval( $section ) == '' ) {
- // Whole-page edit; let the whole text through
- } else {
- // 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 );
+ $sectionContent = ContentHandler::makeContent( $text, $this->getTitle() ); #XXX: could make section title, but that's not required.
- if ( !$rev ) {
- wfDebug( "WikiPage::replaceSection asked for bogus section (page: " .
- $this->getId() . "; section: $section; edittime: $edittime)\n" );
- wfProfileOut( __METHOD__ );
- return null;
- }
+ $newContent = $this->replaceSectionContent( $section, $sectionContent, $sectionTitle, $edittime );
- $oldtext = $rev->getText();
- }
+ return ContentHandler::getContentText( $newContent ); #XXX: unclear what will happen for non-wikitext!
+ }
- if ( $section == 'new' ) {
- # Inserting a new section
- $subject = $sectionTitle ? wfMsgForContent( 'newsectionheaderdefaultlevel', $sectionTitle ) . "\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;
+ public function replaceSectionContent( $section, Content $sectionContent, $sectionTitle = '', $edittime = null ) {
+ wfProfileIn( __METHOD__ );
- $text = $wgParser->replaceSection( $oldtext, $section, $text );
- }
- }
+ if ( strval( $section ) == '' ) {
+ // Whole-page edit; let the whole text through
+ $newContent = $sectionContent;
+ } else {
+ // Bug 30711: always use current version when adding a new section
+ if ( is_null( $edittime ) || $section == 'new' ) {
+ $oldContent = $this->getContent();
+ if ( ! $oldContent ) {
+ wfDebug( __METHOD__ . ": no page text\n" );
+ wfProfileOut( __METHOD__ );
+ return null;
+ }
+ } else {
+ $dbw = wfGetDB( DB_MASTER );
+ $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
- wfProfileOut( __METHOD__ );
- return $text;
- }
+ if ( !$rev ) {
+ wfDebug( "WikiPage::replaceSection asked for bogus section (page: " .
+ $this->getId() . "; section: $section; edittime: $edittime)\n" );
+ wfProfileOut( __METHOD__ );
+ return null;
+ }
+
+ $oldContent = $rev->getContent();
+ }
+
+ $newContent = $oldContent->replaceSection( $section, $sectionContent, $sectionTitle );
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $newContent;
+ }
/**
* Check flags and add EDIT_NEW or EDIT_UPDATE to them as needed.
* 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 $baseRevId the revision ID this edit was based off, if any
+ * @param $baseRevId int the revision ID this edit was based off, if any
* @param $user User the user doing the edit
*
* @return Status object. Possible errors:
* revision: The revision object for the inserted revision, or null
*
* Compatibility note: this function previously returned a boolean value indicating success/failure
- */
- public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
+ * @deprecated since 1.20: use doEditContent() instead.
+ */
+ public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) { #FIXME: use doEditContent() instead
+ #TODO: log use of deprecated function
+ $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 $summary String: 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 $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
+ *
+ * @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
+ *
+ * Compatibility note: this function previously returned a boolean value indicating success/failure
+ */
+ public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
+ User $user = null, $serialisation_format = null ) { #FIXME: use this
global $wgUser, $wgDBtransactions, $wgUseAutomaticEditSummaries;
# Low-level sanity check
$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" );
+ # call legacy hook
+ $hook_ok = wfRunHooks( 'ArticleContentSave', array( &$this, &$user, &$content, &$summary, #FIXME: document new hook!
+ $flags & EDIT_MINOR, null, null, &$flags, &$status ) );
+
+ if ( $hook_ok && !empty( $wgHooks['ArticleSave'] ) ) { # avoid serialization overhead if the hook isn't present
+ $content_text = $content->serialize();
+ $txt = $content_text; # clone
+
+ $hook_ok = wfRunHooks( 'ArticleSave', array( &$this, &$user, &$txt, &$summary, #FIXME: deprecate legacy hook!
+ $flags & EDIT_MINOR, null, null, &$flags, &$status ) );
+
+ if ( $txt !== $content_text ) {
+ # if the text changed, unserialize the new version to create an updated Content object.
+ $content = $content->getContentHandler()->unserialize( $txt );
+ }
+ }
+
+ if ( !$hook_ok ) {
+ wfDebug( __METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n" );
if ( $status->isOK() ) {
$status->fatal( 'edit-hook-aborted' );
$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();
+ $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();
'page' => $this->getId(),
'comment' => $summary,
'minor_edit' => $isminor,
- 'text' => $text,
+ 'text' => $serialized,
+ 'len' => $newsize,
'parent_id' => $oldid,
'user' => $user->getId(),
'user_text' => $user->getName(),
- 'timestamp' => $now
+ 'timestamp' => $now,
+ 'content_model' => $content->getModelName(),
+ 'content_format' => $serialisation_format,
) );
- $changed = ( strcmp( $text, $oldtext ) != 0 );
+ $changed = !$content->equals( $old_content );
if ( $changed ) {
- $dbw->begin();
+ $dbw->begin( __METHOD__ );
$revisionId = $revision->insertOn( $dbw );
# Update page
}
$revisionId = 0;
- $dbw->rollback();
+ $dbw->rollback( __METHOD__ );
} else {
global $wgUseRCPatrol;
wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
# Log auto-patrolled edits
if ( $patrolled ) {
- PatrolLog::record( $rc, true );
+ PatrolLog::record( $rc, true, $user );
}
}
$user->incEditCount();
- $dbw->commit();
+ $dbw->commit( __METHOD__ );
}
} else {
// Bug 32948: revision ID must be set to page {{REVISIONID}} and
# Create new article
$status->value['new'] = true;
- $dbw->begin();
+ $dbw->begin( __METHOD__ );
# 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 ) {
- $dbw->rollback();
+ $dbw->rollback( __METHOD__ );
$status->fatal( 'edit-already-exists' );
wfProfileOut( __METHOD__ );
'page' => $newid,
'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->getModelName(),
+ 'content_format' => $serialisation_format,
) );
$revisionId = $revision->insertOn( $dbw );
$this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
# Add RC row to the DB
$rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
- '', strlen( $text ), $revisionId, $patrolled );
+ '', $content->getSize(), $revisionId, $patrolled );
# Log auto-patrolled edits
if ( $patrolled ) {
- PatrolLog::record( $rc, true );
+ PatrolLog::record( $rc, true, $user );
}
}
$user->incEditCount();
- $dbw->commit();
+ $dbw->commit( __METHOD__ );
# Update links, etc.
$this->doEditUpdates( $revision, $user, array( 'created' => true ) );
- wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $text, $summary,
+ wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $serialized, $summary, #FIXME: deprecate legacy hook
$flags & EDIT_MINOR, null, null, &$flags, $revision ) );
+
+ wfRunHooks( 'ArticleContentInsertComplete', array( &$this, &$user, $content, $summary, #FIXME: document new hook
+ $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
}
# Do updates right now unless deferral was requested
// Return the new revision (or null) to the caller
$status->value['revision'] = $revision;
- wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $text, $summary,
+ wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $serialized, $summary, #FIXME: deprecate legacy hook
$flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
+ wfRunHooks( 'ArticleContentSaveComplete', array( &$this, &$user, $content, $summary, #FIXME: document new hook
+ $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
+
# Promote user to any groups they meet the criteria for
$user->addAutopromoteOnceGroups( 'onEdit' );
/**
* Prepare text which is about to be saved.
* Returns a stdclass with source, pst and output members
- * @return bool|object
- */
- public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
+ * @deprecated in 1.20: use prepareContentForEdit instead.
+ */
+ public function prepareTextForEdit( $text, $revid = null, User $user = null ) { #FIXME: use prepareContentForEdit() instead #XXX: who uses this?!
+ #TODO: log use of deprecated function
+ $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
+ */
+ public function prepareContentForEdit( Content $content, $revid = null, User $user = null, $serialization_format = null ) { #FIXME: use this #XXX: really public?!
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->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;
$edit = (object)array();
$edit->revid = $revid;
- $edit->newText = $text;
- $edit->pst = $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
+
+ $edit->pstContent = $content->preSaveTransform( $this->mTitle, $user, $popts );
+ $edit->pst = $edit->pstContent->serialize( $serialization_format );
+ $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->getParserOutput( $this->mTitle, $revid, $edit->popts );
+
+ $edit->newContent = $content;
+ $edit->oldContent = $this->getContent( Revision::RAW );
+
+ $edit->newText = ContentHandler::getContentText( $edit->newContent ); #FIXME: B/C only! don't use this field!
+ $edit->oldText = $edit->oldContent ? ContentHandler::getContentText( $edit->oldContent ) : ''; #FIXME: B/C only! don't use this field!
$this->mPreparedEdit = $edit;
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
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;
$parserCache->save( $editInfo->output, $this, $editInfo->popts );
}
- # Update the links tables
- $u = new LinksUpdate( $this->mTitle, $editInfo->output );
- $u->doUpdate();
+ # Update the links tables and other secondary data
+ $updates = $editInfo->output->getLinksUpdateAndOtherUpdates( $this->mTitle );
+ SecondaryDataUpdate::runUpdates( $updates );
wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
}
DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, $good, $total ) );
- DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $text ) );
+ DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $content->getTextForSearchIndex() ) );
# If this is another user's talk page, update newtalk.
# Don't do this if $options['changed'] = false (null-edits) nor if
}
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
- MessageCache::singleton()->replace( $shortTitle, $text );
+ $msgtext = ContentHandler::getContentText( $content ); #XXX: could skip pseudo-messages like js/css here, based on content model.
+ if ( $msgtext === false || $msgtext === null ) $msgtext = '';
+
+ MessageCache::singleton()->replace( $shortTitle, $msgtext );
}
if( $options['created'] ) {
* @param $comment String: comment submitted
* @param $minor Boolean: whereas it's a minor modification
*/
- public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
+ public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
+ #TODO: log use of deprecated function
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+ return $this->doQuickEdit( $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 $comment String: 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(
'page' => $this->getId(),
- 'text' => $text,
+ 'text' => $serialized,
+ 'length' => $content->getSize(),
'comment' => $comment,
'minor_edit' => $minor ? 1 : 0,
) );
}
/**
- * Back-end article deletion
+ * Same as doDeleteArticleReal(), but returns more detailed success/failure status
* Deletes the article with database consistency, writes logs, purges caches
*
* @param $reason string delete reason for deletion log
- * @param $suppress bitfield
+ * @param $suppress int 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
+ * @param &$error Array of errors to append to
+ * @param $user User The deleting user
* @return boolean true if successful
*/
public function doDeleteArticle(
$reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
+ ) {
+ return $this->doDeleteArticleReal( $reason, $suppress, $id, $commit, $error, $user )
+ == WikiPage::DELETE_SUCCESS;
+ }
+
+ /**
+ * Back-end article deletion
+ * Deletes the article with database consistency, writes logs, purges caches
+ *
+ * @param $reason string delete reason for deletion log
+ * @param $suppress int 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 &$error Array of errors to append to
+ * @param $user User The deleting user
+ * @return int: One of WikiPage::DELETE_* constants
+ */
+ public function doDeleteArticleReal(
+ $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;
+ return WikiPage::DELETE_HOOK_ABORTED;
}
$dbw = wfGetDB( DB_MASTER );
$t = $this->mTitle->getDBkey();
$id = $id ? $id : $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
if ( $t === '' || $id == 0 ) {
- return false;
+ return WikiPage::DELETE_NO_PAGE;
}
// Bitfields to further suppress the content
$bitfield = 'rev_deleted';
}
- $dbw->begin();
+ $dbw->begin( __METHOD__ );
// For now, shunt the revision data into the archive table.
// Text is *not* removed from the text table; bulk storage
// is left intact to avoid breaking block-compression or
'ar_len' => 'rev_len',
'ar_page_id' => 'page_id',
'ar_deleted' => $bitfield,
- 'ar_sha1' => 'rev_sha1'
+ 'ar_sha1' => 'rev_content_model',
+ 'ar_content_format' => 'rev_content_format',
+ 'ar_content_format' => 'rev_sha1'
), array(
'page_id' => $id,
'page_id = rev_page'
# 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
+ $ok = ( $dbw->affectedRows() > 0 ); // getArticleID() uses slave, could be laggy
if ( !$ok ) {
- $dbw->rollback();
- return false;
+ $dbw->rollback( __METHOD__ );
+ return WikiPage::DELETE_NO_REVISIONS;
}
$this->doDeleteUpdates( $id );
$logEntry->publish( $logid );
if ( $commit ) {
- $dbw->commit();
+ $dbw->commit( __METHOD__ );
}
wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id ) );
- return true;
+ return WikiPage::DELETE_SUCCESS;
}
/**
# Clear caches
self::onArticleDelete( $this->mTitle );
+ # Reset this object
+ $this->clear();
+
# Clear the cached article id so the interface doesn't act like we exist
$this->mTitle->resetArticleID( 0 );
}
*
* @param $resultDetails Array: contains result-specific array of additional values
* @param $guser User The user performing the rollback
+ * @return array
*/
public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser ) {
global $wgUseRCPatrol, $wgContLang;
}
# Get the last editor
- $current = Revision::newFromTitle( $this->mTitle );
+ $current = $this->getRevision();
if ( is_null( $current ) ) {
# Something wrong... no page?
return array( array( 'notanarticle' ) );
}
# Actually store the edit
- $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId(), $guser );
+ $status = $this->doEditContent( $target->getContent(), $summary, $flags, $target->getId(), $guser );
if ( !empty( $status->value['revision'] ) ) {
$revId = $status->value['revision']->getId();
} else {
* @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.
+ * @deprecated since 1.20, 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, 250
- - strlen( wfMsgForContent( 'autoredircomment' ) )
- - strlen( $rt->getFullText() )
- ) );
- return wfMsgForContent( 'autoredircomment', $rt->getFullText(), $truncatedtext );
- }
+ # NOTE: stub for backwards-compatibility. assumes the given text is wikitext. will break horribly if it isn't.
- # New page autosummaries
- if ( $flags & EDIT_NEW && strlen( $newtext ) ) {
- # If they're making a new article, give its text, truncated, in the summary.
+ $handler = ContentHandler::getForModelName( CONTENT_MODEL_WIKITEXT );
+ $oldContent = $oldtext ? $handler->unserialize( $oldtext ) : null;
+ $newContent = $newtext ? $handler->unserialize( $newtext ) : null;
- $truncatedtext = $wgContLang->truncate(
- str_replace( "\n", ' ', $newtext ),
- max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new' ) ) ) );
-
- return wfMsgForContent( 'autosumm-new', $truncatedtext );
- }
-
- # Blanking autosummaries
- if ( $oldtext != '' && $newtext == '' ) {
- return wfMsgForContent( 'autosumm-blank' );
- } elseif ( strlen( $oldtext ) > 10 * strlen( $newtext ) && strlen( $newtext ) < 500 ) {
- # Removing more than 90% of the article
-
- $truncatedtext = $wgContLang->truncate(
- $newtext,
- max( 0, 200 - strlen( wfMsgForContent( 'autosumm-replace' ) ) ) );
-
- return wfMsgForContent( 'autosumm-replace', $truncatedtext );
- }
-
- # 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 );
}
/**
* @param &$hasHistory Boolean: whether the page has a history
* @return mixed String containing deletion reason or empty string, or boolean false
* if no revision occurred
+ * @deprecated since 1.20, use ContentHandler::getAutoDeleteReason() instead
*/
public function getAutoDeleteReason( &$hasHistory ) {
+ #NOTE: stub for backwards-compatibility.
+
+ $handler = ContentHandler::getForTitle( $this->getTitle() );
+ $handler->getAutoDeleteReason( $this->getTitle(), $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 = wfMsgForContent( 'exbeforeblank', '$1' );
+ } else {
+ if ( $onlyAuthor ) {
+ $reason = wfMsgForContent( 'excontentauthor', '$1', $onlyAuthor );
+ } else {
+ $reason = wfMsgForContent( 'excontent', '$1' );
+ }
+ }
+
+ 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;
}
/**
if ( count( $templates_diff ) > 0 ) {
# Whee, link updates time.
+ # Note: we are only interested in links here. We don't need to get other SecondaryDataUpdate items from the parser output.
$u = new LinksUpdate( $this->mTitle, $parserOutput, false );
$u->doUpdate();
}
/**
* @deprecated since 1.18
+ * @return bool
*/
public function useParserCache( $oldid ) {
wfDeprecated( __METHOD__, '1.18' );
private $text;
/**
- * @var ParserOutput|false
+ * @var ParserOutput|bool
*/
private $parserOutput = false;
private $isDirty = false;
/**
- * @var Status|false
+ * @var Status|bool
*/
private $error = false;
* @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
+ $modelName = $page->getRevision()->getContentModelName();
+ $format = $page->getRevision()->getContentFormat();
+ $content = ContentHandler::makeContent( $content, $page->getTitle(), $modelName, $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 );
}
/**
* Get a Status object in case of error or false otherwise
*
- * @return Status|false
+ * @return Status|bool
*/
public function getError() {
return $this->error;
$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();
+ $content = $this->page->getContent( Revision::RAW ); #XXX: why use RAW audience here, and PUBLIC (default) below?
} else {
$rev = Revision::newFromTitle( $this->page->getTitle(), $this->revid );
if ( $rev === null ) {
return false;
}
- $text = $rev->getText();
+ $content = $rev->getContent(); #XXX: why use PUBLIC audience here (default), and RAW above?
}
+ $time = - wfTime();
+ $this->parserOutput = $content->getParserOutput( $this->page->getTitle(), $this->revid, $this->parserOptions );
+ $time += wfTime();
+ $time = - microtime( true );
+ $this->parserOutput = $wgParser->parse( $text, $this->page->getTitle(),
+ $this->parserOptions, true, true, $this->revid );
+ $time += microtime( true );
# Timing hack
if ( $time > 3 ) {
/**
* @param $status Status
+ * @return bool
*/
function error( $status ) {
$this->error = $status;
// If it's a MediaWiki message we can just hit the message cache
if ( $request->getBool( 'usemsgcache' ) && $title->getNamespace() == NS_MEDIAWIKI ) {
- $key = $title->getDBkey();
- $msg = wfMessage( $key )->inContentLanguage();
- # If the message doesn't exist, return a blank
- $text = !$msg->exists() ? '' : $msg->plain();
+ // The first "true" is to use the database, the second is to use the content langue
+ // and the last one is to specify the message key already contains the language in it ("/de", etc.)
+ $text = MessageCache::singleton()->get( $title->getDBkey(), true, true, true );
+ // If the message doesn't exist, return a blank
+ if ( $text === false ) {
+ $text = '';
+ }
} else {
// Get it from the DB
$rev = Revision::newFromTitle( $title, $this->getOldId() );
$request->response()->header( "Last-modified: $lastmod" );
// Public-only due to cache headers
- $text = $rev->getText();
+ $content = $rev->getContent();
+
+ if ( !$content instanceof TextContent ) {
+ wfHttpError( 406, "Not Acceptable", "The requeste page uses the content model `"
+ . $content->getModelName() . "` which is not supported via this interface." );
+ die();
+ }
+
$section = $request->getIntOrNull( 'section' );
if ( $section !== null ) {
- $text = $wgParser->getSection( $text, $section );
+ $content = $content->getSection( $section );
}
+
+ $text = $content->getNativeData();
}
}
# output previous revision, or nothing if there isn't one
if( !$oldid ) {
# get the current revision so we can get the penultimate one
- $oldid = $this->getTitle()->getLatestRevID();
+ $oldid = $this->page->getLatest();
}
$prev = $this->getTitle()->getPreviousRevisionId( $oldid );
$oldid = $prev ? $prev : -1 ;
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
* @file
- * @ingroup Action
+ * @ingroup Actions
*/
/**
* User interface for the rollback action
*
- * @ingroup Action
+ * @ingroup Actions
*/
class RollbackAction extends FormlessAction {
$current = $details['current'];
if ( $current->getComment() != '' ) {
- $this->getOutput()->addHTML( wfMessage( 'editcomment' )->rawParams(
+ $this->getOutput()->addHTML( $this->msg( 'editcomment' )->rawParams(
Linker::formatComment( $current->getComment() ) )->parse() );
}
}
$this->getOutput()->setRobotPolicy( 'noindex,nofollow' );
if ( $current->getUserText() === '' ) {
- $old = wfMsg( 'rev-deleted-user' );
+ $old = $this->msg( 'rev-deleted-user' )->escaped();
} else {
$old = Linker::userLink( $current->getUser(), $current->getUserText() )
. Linker::userToolLinks( $current->getUser(), $current->getUserText() );
$new = Linker::userLink( $target->getUser(), $target->getUserText() )
. Linker::userToolLinks( $target->getUser(), $target->getUserText() );
- $this->getOutput()->addHTML( wfMsgExt( 'rollback-success', array( 'parse', 'replaceafter' ), $old, $new ) );
+ $this->getOutput()->addHTML( $this->msg( 'rollback-success' )->rawParams( $old, $new )->parseAsBlock() );
$this->getOutput()->returnToMain( false, $this->getTitle() );
if ( !$request->getBool( 'hidediff', false ) && !$this->getUser()->getBoolOption( 'norollbackdiff', false ) ) {
- $de = new DifferenceEngine( $this->getContext(), $current->getId(), $newId, false, true );
+ $contentHandler = ContentHandler::getForTitle( $this->getTitle() );
+ $de = $contentHandler->getDifferenceEngine( $this->getContext(), $current->getId(), $newId, false, true );
$de->showDiff( '', '' );
}
}
public function execute() {
$params = $this->extractRequestParams();
- $rev1 = $this->revisionOrTitle( $params['fromrev'], $params['fromtitle'] );
- $rev2 = $this->revisionOrTitle( $params['torev'], $params['totitle'] );
+ $rev1 = $this->revisionOrTitleOrId( $params['fromrev'], $params['fromtitle'], $params['fromid'] );
+ $rev2 = $this->revisionOrTitleOrId( $params['torev'], $params['totitle'], $params['toid'] );
- $de = new DifferenceEngine( $this->getContext(),
+ $contentHandler = ContentHandler::getForModelName( $rev1->getContentModelName() );
+ $de = $contentHandler->getDifferenceEngine( $this->getContext(),
$rev1,
$rev2,
null, // rcid
if ( isset( $params['fromtitle'] ) ) {
$vals['fromtitle'] = $params['fromtitle'];
}
+ if ( isset( $params['fromid'] ) ) {
+ $vals['fromid'] = $params['fromid'];
+ }
$vals['fromrevid'] = $rev1;
if ( isset( $params['totitle'] ) ) {
$vals['totitle'] = $params['totitle'];
}
+ if ( isset( $params['toid'] ) ) {
+ $vals['toid'] = $params['toid'];
+ }
$vals['torevid'] = $rev2;
$difftext = $de->getDiffBody();
/**
* @param $revision int
* @param $titleText string
+ * @param $titleId int
* @return int
*/
- private function revisionOrTitle( $revision, $titleText ) {
+ private function revisionOrTitleOrId( $revision, $titleText, $titleId ) {
if( $revision ){
return $revision;
} elseif( $titleText ) {
$this->dieUsageMsg( array( 'invalidtitle', $titleText ) );
}
return $title->getLatestRevID();
+ } elseif ( $titleId ) {
+ $title = Title::newFromID( $titleId );
+ if( !$title ) {
+ $this->dieUsageMsg( array( 'nosuchpageid', $titleId ) );
+ }
+ return $title->getLatestRevID();
}
- $this->dieUsage( 'inputneeded', 'A title or a revision number is needed for both the from and the to parameters' );
+ $this->dieUsage( 'inputneeded', 'A title, a page ID, or a revision number is needed for both the from and the to parameters' );
}
public function getAllowedParams() {
return array(
'fromtitle' => null,
+ 'fromid' => array(
+ ApiBase::PARAM_TYPE => 'integer'
+ ),
'fromrev' => array(
ApiBase::PARAM_TYPE => 'integer'
),
'totitle' => null,
+ 'toid' => array(
+ ApiBase::PARAM_TYPE => 'integer'
+ ),
'torev' => array(
ApiBase::PARAM_TYPE => 'integer'
),
public function getParamDescription() {
return array(
'fromtitle' => 'First title to compare',
+ 'fromid' => 'First page ID to compare',
'fromrev' => 'First revision to compare',
'totitle' => 'Second title to compare',
+ 'toid' => 'Second page ID to compare',
'torev' => 'Second revision to compare',
);
}
public function getDescription() {
return array(
'Get the difference between 2 pages',
- 'You must pass a revision number or a page title for each part (1 and 2)'
+ 'You must pass a revision number or a page title or a page ID id for each part (1 and 2)'
);
}
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'inputneeded', 'info' => 'A title or a revision is needed' ),
array( 'invalidtitle', 'title' ),
+ array( 'nosuchpageid', 'pageid' ),
array( 'code' => 'baddiff', 'info' => 'The diff cannot be retrieved. Maybe one or both revisions do not exist or you do not have permission to view them.' ),
) );
}
// Need to pass a throwaway variable because generateReason expects
// a reference
$hasHistory = false;
- $reason = $page->getAutoDeleteReason( $hasHistory );
+ $reason = $page->getAutoDeleteReason( $hasHistory ); #FIXME: use ContentHandler::getAutoDeleteReason()
if ( $reason === false ) {
return array( array( 'cannotdelete', $title->getPrefixedText() ) );
}
* @param $oldimage
* @param $reason
* @param $suppress bool
- * @return \type|array|Title
+ * @return array|Title
*/
public static function deleteFile( Page $page, User $user, $token, $oldimage, &$reason = null, $suppress = false ) {
$title = $page->getTitle();
$this->dieUsageMsg( 'missingtext' );
}
- $titleObj = Title::newFromText( $params['title'] );
- if ( !$titleObj || $titleObj->isExternal() ) {
- $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+ $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
+
+ if ( isset( $params['title'] ) ) {
+ $titleObj = Title::newFromText( $params['title'] );
+ if ( !$titleObj || $titleObj->isExternal() ) {
+ $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+ }
+ } elseif ( isset( $params['pageid'] ) ) {
+ $titleObj = Title::newFromID( $params['pageid'] );
+ if ( !$titleObj ) {
+ $this->dieUsageMsg( array( 'nosuchpageid', $params['pageid'] ) );
+ }
}
$apiResult = $this->getResult();
// We do want getContent()'s behavior for non-existent
// MediaWiki: pages, though
if ( $articleObj->getID() == 0 && $titleObj->getNamespace() != NS_MEDIAWIKI ) {
- $content = '';
+ $content = null;
+ $text = '';
} else {
- $content = $articleObj->getContent();
+ $content = $articleObj->getContentObject();
+ $text = ContentHandler::getContentText( $content ); #FIXME: serialize?! get format from params?...
}
if ( !is_null( $params['section'] ) ) {
// Process the content for section edits
- global $wgParser;
$section = intval( $params['section'] );
- $content = $wgParser->getSection( $content, $section, false );
- if ( $content === false ) {
+ $sectionContent = $content->getSection( $section );
+ $text = ContentHandler::getContentText( $sectionContent ); #FIXME: serialize?! get format from params?...
+ if ( $text === false || $text === null ) {
$this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
}
}
- $params['text'] = $params['prependtext'] . $content . $params['appendtext'];
+ $params['text'] = $params['prependtext'] . $text . $params['appendtext'];
$toMD5 = $params['prependtext'] . $params['appendtext'];
}
// TODO: Make them not or check if they still do
$wgTitle = $titleObj;
- $ep = new EditPage( $articleObj );
+ $handler = ContentHandler::getForTitle( $titleObj );
+ $ep = $handler->createEditPage( $articleObj );
+
$ep->setContextTitle( $titleObj );
$ep->importFormData( $req );
public function getPossibleErrors() {
global $wgMaxArticleSize;
- return array_merge( parent::getPossibleErrors(), array(
- array( 'missingtext' ),
- array( 'invalidtitle', 'title' ),
- array( 'createonly-exists' ),
- array( 'nocreate-missing' ),
- array( 'nosuchrevid', 'undo' ),
- array( 'nosuchrevid', 'undoafter' ),
- array( 'revwrongpage', 'id', 'text' ),
- array( 'undo-failure' ),
- array( 'hashcheckfailed' ),
- array( 'hookaborted' ),
- array( 'noimageredirect-anon' ),
- array( 'noimageredirect-logged' ),
- array( 'spamdetected', 'spam' ),
- array( 'summaryrequired' ),
- array( 'filtered' ),
- array( 'blockedtext' ),
- array( 'contenttoobig', $wgMaxArticleSize ),
- array( 'noedit-anon' ),
- array( 'noedit' ),
- array( 'actionthrottledtext' ),
- array( 'wasdeleted' ),
- array( 'nocreate-loggedin' ),
- array( 'blankpage' ),
- array( 'editconflict' ),
- array( 'emptynewsection' ),
- array( 'unknownerror', 'retval' ),
- array( 'code' => 'nosuchsection', 'info' => 'There is no section section.' ),
- array( 'code' => 'invalidsection', 'info' => 'The section parameter must be set to an integer or \'new\'' ),
- array( 'customcssprotected' ),
- array( 'customjsprotected' ),
- ) );
+ return array_merge( parent::getPossibleErrors(),
+ $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ),
+ array(
+ array( 'nosuchpageid', 'pageid' ),
+ array( 'missingtext' ),
+ array( 'invalidtitle', 'title' ),
+ array( 'createonly-exists' ),
+ array( 'nocreate-missing' ),
+ array( 'nosuchrevid', 'undo' ),
+ array( 'nosuchrevid', 'undoafter' ),
+ array( 'revwrongpage', 'id', 'text' ),
+ array( 'undo-failure' ),
+ array( 'hashcheckfailed' ),
+ array( 'hookaborted' ),
+ array( 'noimageredirect-anon' ),
+ array( 'noimageredirect-logged' ),
+ array( 'spamdetected', 'spam' ),
+ array( 'summaryrequired' ),
+ array( 'filtered' ),
+ array( 'blockedtext' ),
+ array( 'contenttoobig', $wgMaxArticleSize ),
+ array( 'noedit-anon' ),
+ array( 'noedit' ),
+ array( 'actionthrottledtext' ),
+ array( 'wasdeleted' ),
+ array( 'nocreate-loggedin' ),
+ array( 'blankpage' ),
+ array( 'editconflict' ),
+ array( 'emptynewsection' ),
+ array( 'unknownerror', 'retval' ),
+ array( 'code' => 'nosuchsection', 'info' => 'There is no section section.' ),
+ array( 'code' => 'invalidsection', 'info' => 'The section parameter must be set to an integer or \'new\'' ),
+ array( 'customcssprotected' ),
+ array( 'customjsprotected' ),
+ )
+ );
}
public function getAllowedParams() {
return array(
'title' => array(
ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
+ ),
+ 'pageid' => array(
+ ApiBase::PARAM_TYPE => 'integer',
),
'section' => null,
'sectiontitle' => array(
public function getParamDescription() {
$p = $this->getModulePrefix();
return array(
- 'title' => 'Page title',
+ 'title' => "Title of the page you want to edit. Cannot be used together with {$p}pageid",
+ 'pageid' => "Page ID of the page you want to edit. Cannot be used together with {$p}title",
'section' => 'Section number. 0 for the top section, \'new\' for a new section',
'sectiontitle' => 'The title for a new section',
'text' => 'Page content',
$page = WikiPage::factory( $titleObj );
- if ( $this->section !== false ) {
+ if ( $this->section !== false ) { #FIXME: get section Content, get parser output, ...
$this->text = $this->getSectionText( $page->getRawText(), !is_null( $pageId )
- ? 'page id ' . $pageId : $titleObj->getText() );
+ ? 'page id ' . $pageId : $titleObj->getText() ); #FIXME: get section...
// Not cached (save or load)
return $wgParser->parse( $this->text, $titleObj, $popts );
// getParserOutput will save to Parser cache if able
$pout = $page->getParserOutput( $popts );
if ( $getWikitext ) {
- $this->text = $page->getRawText();
+ $this->content = $page->getContent( Revision::RAW ); #FIXME: use $this->content everywhere
+ $this->text = ContentHandler::getContentText( $this->content ); #FIXME: serialize, get format from params; or use object structure in result?
}
return $pout;
}
}
- private function getSectionText( $text, $what ) {
+ private function getSectionText( $text, $what ) { #FIXME: replace with Content::getSection
global $wgParser;
// Not cached (save or load)
$text = $wgParser->getSection( $text, $this->section, false );
$langs = array();
foreach ( $languages as $l ) {
$nt = Title::newFromText( $l );
- $text = $wgContLang->getLanguageName( $nt->getInterwiki() );
+ $text = Language::fetchLanguageName( $nt->getInterwiki() );
$langs[] = Html::element( 'a',
array( 'href' => $nt->getFullURL(), 'title' => $nt->getText(), 'class' => "external" ),
* @private
* @ingroup DifferenceEngine
*/
-class _DiffOp {
+class _DiffOp { #FIXME: no longer private!
var $type;
var $orig;
var $closing;
* @private
* @ingroup DifferenceEngine
*/
-class _DiffOp_Copy extends _DiffOp {
+class _DiffOp_Copy extends _DiffOp { #FIXME: no longer private!
var $type = 'copy';
function __construct( $orig, $closing = false ) {
* @private
* @ingroup DifferenceEngine
*/
-class _DiffOp_Delete extends _DiffOp {
+class _DiffOp_Delete extends _DiffOp { #FIXME: no longer private!
var $type = 'delete';
function __construct( $lines ) {
* @private
* @ingroup DifferenceEngine
*/
-class _DiffOp_Add extends _DiffOp {
+class _DiffOp_Add extends _DiffOp { #FIXME: no longer private!
var $type = 'add';
function __construct( $lines ) {
* @private
* @ingroup DifferenceEngine
*/
-class _DiffOp_Change extends _DiffOp {
+class _DiffOp_Change extends _DiffOp { #FIXME: no longer private!
var $type = 'change';
function __construct( $orig, $closing ) {
* @private
* @ingroup DifferenceEngine
*/
-class _DiffEngine {
+class _DiffEngine { #FIXME: no longer private!
const MAX_XREF_LENGTH = 10000;
$edits = array();
$xi = $yi = 0;
while ( $xi < $n_from || $yi < $n_to ) {
- assert( $yi < $n_to || $this->xchanged[$xi] );
- assert( $xi < $n_from || $this->ychanged[$yi] );
+ assert( '$yi < $n_to || $this->xchanged[$xi]' );
+ assert( '$xi < $n_from || $this->ychanged[$yi]' );
// Skip matching "snake".
$copy = array();
while ( list( , $y ) = each( $matches ) ) {
if ( empty( $this->in_seq[$y] ) ) {
$k = $this->_lcs_pos( $y );
- assert( $k > 0 );
+ assert( '$k > 0' );
$ymids[$k] = $ymids[$k -1];
break;
}
}
while ( list ( , $y ) = each( $matches ) ) {
if ( $y > $this->seq[$k -1] ) {
- assert( $y < $this->seq[$k] );
+ assert( '$y < $this->seq[$k]' );
// Optimization: this is a common case:
// next match is just replacing previous match.
$this->in_seq[$this->seq[$k]] = false;
$this->in_seq[$y] = 1;
} elseif ( empty( $this->in_seq[$y] ) ) {
$k = $this->_lcs_pos( $y );
- assert( $k > 0 );
+ assert( '$k > 0' );
$ymids[$k] = $ymids[$k -1];
}
}
}
}
- assert( $ypos != $this->seq[$end] );
+ assert( '$ypos != $this->seq[$end]' );
$this->in_seq[$this->seq[$end]] = false;
$this->seq[$end] = $ypos;
* @private
* @ingroup DifferenceEngine
*/
-class Diff {
+class Diff extends DiffResult {
var $edits;
/**
* @param $from_lines array An array of strings.
* (Typically these are lines from a file.)
* @param $to_lines array An array of strings.
+ * @param $eng _DiffEngine|null The diff engine to use.
*/
- function __construct( $from_lines, $to_lines ) {
- $eng = new _DiffEngine;
- $this->edits = $eng->diff( $from_lines, $to_lines );
- // $this->_check($from_lines, $to_lines);
+ function __construct( $from_lines, $to_lines, $eng = null ) {
+ if ( !$eng ) {
+ $eng = new _DiffEngine();
+ }
+
+ $edits = $eng->diff( $from_lines, $to_lines );
+
+ parent::__construct( $edits );
+
+ //$this->_check( $from_lines, $to_lines );
+ }
+}
+
+/**
+ * Class representing the result of 'diffin' two sequences of strings.
+ * @todo document
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class DiffResult {
+
+ /**
+ * Constructor.
+ *
+ * @param $edits array An array of Edit.
+ */
+ function __construct( $edits ) {
+ $this->edits = $edits;
}
/**
*
* $diff = new Diff($lines1, $lines2);
* $rev = $diff->reverse();
- * @return object A Diff object representing the inverse of the
+ * @return Object A Diff object representing the inverse of the
* original diff.
*/
function reverse() {
$mapped_from_lines, $mapped_to_lines ) {
wfProfileIn( __METHOD__ );
- assert( sizeof( $from_lines ) == sizeof( $mapped_from_lines ) );
- assert( sizeof( $to_lines ) == sizeof( $mapped_to_lines ) );
+ assert( 'sizeof( $from_lines ) == sizeof( $mapped_from_lines )' );
+ assert( 'sizeof( $to_lines ) == sizeof( $mapped_to_lines )' );
parent::__construct( $mapped_from_lines, $mapped_to_lines );
$this->_flushLine( $tag );
$word = substr( $word, 1 );
}
- assert( !strstr( $word, "\n" ) );
+ assert( '!strstr( $word, "\n" )' );
$this->_group .= $word;
}
}
* @private
*/
var $mOldid, $mNewid;
- var $mOldtext, $mNewtext;
+ var $mOldContent, $mNewContent;
protected $mDiffLang;
/**
$samePage = false;
}
- if ( $samePage && $this->mNewPage->userCan( 'edit', $user ) ) {
+ if ( $samePage && $this->mNewPage->quickUserCan( 'edit', $user ) ) {
if ( $this->mNewRev->isCurrent() && $this->mNewPage->userCan( 'rollback', $user ) ) {
$out->preventClickjacking();
$rollback = '   ' . Linker::generateRollback( $this->mNewRev );
if ( $this->mMarkPatrolledLink === null ) {
// Prepare a change patrol link, if applicable
- if ( $wgUseRCPatrol && $this->mNewPage->userCan( 'patrol', $this->getUser() ) ) {
+ if ( $wgUseRCPatrol && $this->mNewPage->quickUserCan( 'patrol', $this->getUser() ) ) {
// If we've been given an explicit change identifier, use it; saves time
if ( $this->mRcidMarkPatrolled ) {
$rcid = $this->mRcidMarkPatrolled;
$out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
$out->setArticleFlag( true );
- if ( $this->mNewPage->isCssJsSubpage() || $this->mNewPage->isCssOrJsPage() ) {
+ if ( $this->mNewPage->isCssJsSubpage() || $this->mNewPage->isCssOrJsPage() ) { #NOTE: only needed for B/C: custom rendering of JS/CSS via hook
// Stolen from Article::view --AG 2007-10-11
// Give hooks a chance to customise the output
// @TODO: standardize this crap into one function
- if ( wfRunHooks( 'ShowRawCssJs', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
- // Wrap the whole lot in a <pre> and don't parse
- $m = array();
- preg_match( '!\.(css|js)$!u', $this->mNewPage->getText(), $m );
- $out->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
- $out->addHTML( htmlspecialchars( $this->mNewtext ) );
- $out->addHTML( "\n</pre>\n" );
+ if ( !Hook::isRegistered( 'ShowRawCssJs' )
+ || wfRunHooks( 'ShowRawCssJs', array( ContentHandler::getContentText( $this->mNewContent ), $this->mNewPage, $out ) ) ) { #NOTE: deperecated hook, B/C only
+ // use the content object's own rendering
+ $po = $this->mContentObject->getParserOutput();
+ $out->addHTML( $po->getText() );
}
- } elseif ( !wfRunHooks( 'ArticleViewCustom', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
- // Handled by extension
+ } elseif( !wfRunHooks( 'ArticleContentViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
+ // Handled by extension
+ } elseif( Hooks::isRegistered( 'ArticleViewCustom' )
+ && !wfRunHooks( 'ArticleViewCustom', array( ContentHandler::getContentText( $this->mNewContent ), $this->mNewPage, $out ) ) ) { #NOTE: deperecated hook, B/C only
+ // Handled by extension
} else {
// Normal page
if ( $this->getTitle()->equals( $this->mNewPage ) ) {
return false;
}
- $difftext = $this->generateDiffBody( $this->mOldtext, $this->mNewtext );
+ #TODO: make sure both Content objects have the same content model. What do we do if they don't?
+
+ $difftext = $this->generateContentDiffBody( $this->mOldContent, $this->mNewContent );
// Save to cache for 7 days
if ( !wfRunHooks( 'AbortDiffCache', array( &$this ) ) ) {
}
}
+ /**
+ * Generate a diff, no caching.
+ *
+ * Subclasses may override this to provide a
+ *
+ * @param $old Content: old content
+ * @param $new Content: new content
+ */
+ function generateContentDiffBody( Content $old, Content $new ) {
+ #XXX: generate a warning if $old or $new are not instances of TextContent?
+ #XXX: fail if $old and $new don't have the same content model? or what?
+
+ $otext = $old->serialize();
+ $ntext = $new->serialize();
+
+ #XXX: text should be "already segmented". what does that mean?
+ return $this->generateTextDiffBody( $otext, $ntext );
+ }
+
+ /**
+ * Generate a diff, no caching
+ *
+ * @param $otext String: old text, must be already segmented
+ * @param $ntext String: new text, must be already segmented
+ * @deprecated since 1.20, use generateContentDiffBody() instead!
+ */
+ function generateDiffBody( $otext, $ntext ) {
+ wfDeprecated( __METHOD__, "1.20" );
+
+ return $this->generateTextDiffBody( $otext, $ntext );
+ }
+
/**
* Generate a diff, no caching
*
+ * @todo move this to TextDifferenceEngine, make DifferenceEngine abstract. At some point.
+ *
* @param $otext String: old text, must be already segmented
* @param $ntext String: new text, must be already segmented
+ * @return bool|string
*/
- function generateDiffBody( $otext, $ntext ) {
+ function generateTextDiffBody( $otext, $ntext ) {
global $wgExternalDiffEngine, $wgContLang;
wfProfileIn( __METHOD__ );
/**
* Generate a debug comment indicating diff generating time,
* server node, and generator backend.
+ * @return string
*/
protected function debug( $generator = "internal" ) {
global $wgShowHostnames;
/**
* Replace line numbers with the text in the user's language
+ * @return mixed
*/
function localiseLineNumbers( $text ) {
return preg_replace_callback( '/<!--LINE (\d+)-->/',
$editQuery['oldid'] = $rev->getID();
}
- $msg = $this->msg( $title->userCan( 'edit', $user ) ? 'editold' : 'viewsourceold' )->escaped();
+ $msg = $this->msg( $title->quickUserCan( 'edit', $user ) ? 'editold' : 'viewsourceold' )->escaped();
$header .= ' (' . Linker::linkKnown( $title, $msg, array(), $editQuery ) . ')';
if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
$header = Html::rawElement( 'span', array( 'class' => 'history-deleted' ), $header );
/**
* Use specified text instead of loading from the database
+ * @deprecated since 1.20
*/
- function setText( $oldText, $newText ) {
- $this->mOldtext = $oldText;
- $this->mNewtext = $newText;
- $this->mTextLoaded = 2;
- $this->mRevisionsLoaded = true;
- }
+ function setText( $oldText, $newText ) { #FIXME: no longer use this, use setContent()!
+ wfDeprecated( __METHOD__, "1.20" );
+
+ $oldContent = ContentHandler::makeContent( $oldText, $this->getTitle() );
+ $newContent = ContentHandler::makeContent( $newText, $this->getTitle() );
+
+ $this->setContent( $oldContent, $newContent );
+ }
+
+ /**
+ * Use specified text instead of loading from the database
+ * @since 1.20
+ */
+ function setContent( Content $oldContent, Content $newContent ) {
+ $this->mOldContent = $oldContent;
+ $this->mNewContent = $newContent;
+
+ $this->mTextLoaded = 2;
+ $this->mRevisionsLoaded = true;
+ }
/**
* Set the language in which the diff text is written
return false;
}
if ( $this->mOldRev ) {
- $this->mOldtext = $this->mOldRev->getText( Revision::FOR_THIS_USER );
- if ( $this->mOldtext === false ) {
+ $this->mOldContent = $this->mOldRev->getContent( Revision::FOR_THIS_USER );
+ if ( $this->mOldContent === false ) {
return false;
}
}
if ( $this->mNewRev ) {
- $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER );
- if ( $this->mNewtext === false ) {
+ $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER );
+ if ( $this->mNewContent === false ) {
return false;
}
}
if ( !$this->loadRevisionData() ) {
return false;
}
- $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER );
+ $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER );
return true;
}
}
array( 'addField', 'categorylinks', 'cl_sortkey_prefix', 'patch-cl_sortkey_prefix-field.sql' ),
array( 'addField', 'categorylinks', 'cl_collation', 'patch-cl_collation-field.sql' ),
array( 'addField', 'categorylinks', 'cl_type', 'patch-cl_type-field.sql' ),
-
+
//1.18
array( 'doUserNewTalkTimestampNotNull' ),
array( 'addIndex', 'user', 'user_email', 'patch-user_email_index.sql' ),
array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
array( 'addTable', 'user_former_groups', 'patch-user_former_groups.sql'),
array( 'doRebuildLocalisationCache' ),
-
+
// 1.19
- array( 'addTable', 'config', 'patch-config.sql' ),
array( 'addIndex', 'logging', 'type_action', 'patch-logging-type-action-index.sql'),
array( 'dropField', 'user', 'user_options', 'patch-drop-user_options.sql' ),
array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1.sql' ),
array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1.sql' ),
+ // 1.20
+ // content model stuff for WikiData
+ array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ),
+ array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ),
+ array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
+ array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
+ array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
+ // 1.20
+ array( 'addTable', 'config', 'patch-config.sql' ),
);
}
}
array( 'addTable', 'user_former_groups', 'patch-user_former_groups.sql'),
// 1.19
- array( 'addTable', 'config', 'patch-config.sql' ),
array( 'addIndex', 'logging', 'type_action', 'patch-logging-type-action-index.sql'),
array( 'doMigrateUserOptions' ),
array( 'dropField', 'user', 'user_options', 'patch-drop-user_options.sql' ),
array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1.sql' ),
array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1.sql' ),
array( 'addIndex', 'page', 'page_redirect_namespace_len', 'patch-page_redirect_namespace_len.sql' ),
- array( 'modifyField', 'user', 'ug_group', 'patch-ug_group-length-increase.sql' ),
+ array( 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase.sql' ),
array( 'addField', 'uploadstash', 'us_chunk_inx', 'patch-uploadstash_chunk.sql' ),
array( 'addfield', 'job', 'job_timestamp', 'patch-jobs-add-timestamp.sql' ),
+
+ // 1.20
+ // content model stuff for WikiData
+ array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ),
+ array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ),
+ array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
+ array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
+ array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
+ array( 'modifyField', 'user_former_groups', 'ufg_group', 'patch-ufg_group-length-increase.sql' ),
+
+ // 1.20
+ array( 'addTable', 'config', 'patch-config.sql' ),
+ array( 'addIndex', 'revision', 'page_user_timestamp', 'patch-revision-user-page-index.sql' ),
);
}
array( 'doRecentchangesFK2Cascade' ),
//1.19
- array( 'addTable', 'config', 'patch-config.sql' ),
array( 'addIndex', 'logging', 'i05', 'patch-logging_type_action_index.sql'),
array( 'addTable', 'globaltemplatelinks', 'patch-globaltemplatelinks.sql' ),
array( 'addTable', 'globalnamespaces', 'patch-globalnamespaces.sql' ),
array( 'addField', 'job', 'job_timestamp', 'patch-job_timestamp_field.sql' ),
array( 'addIndex', 'job', 'i02', 'patch-job_timestamp_index.sql' ),
+ // 1.20
+ // content model stuff for WikiData
+ array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ),
+ array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ),
+ array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
+ array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
+ array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
+ //1.20
+ array( 'addTable', 'config', 'patch-config.sql' ),
// KEEP THIS AT THE BOTTOM!!
array( 'doRebuildDuplicateFunction' ),
array( 'addTable', 'user_former_groups', 'patch-user_former_groups.sql'),
// 1.19
- array( 'addTable', 'config', 'patch-config.sql' ),
array( 'addIndex', 'logging', 'type_action', 'patch-logging-type-action-index.sql'),
array( 'doMigrateUserOptions' ),
array( 'dropField', 'user', 'user_options', 'patch-drop-user_options.sql' ),
array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1.sql' ),
array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1.sql' ),
array( 'addIndex', 'page', 'page_redirect_namespace_len', 'patch-page_redirect_namespace_len.sql' ),
- array( 'modifyField', 'user', 'ug_group', 'patch-ug_group-length-increase.sql' ),
+ array( 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase.sql' ),
array( 'addField', 'uploadstash', 'us_chunk_inx', 'patch-uploadstash_chunk.sql' ),
array( 'addfield', 'job', 'job_timestamp', 'patch-jobs-add-timestamp.sql' ),
+
+ // 1.20
+ // content model stuff for WikiData
+ array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ),
+ array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ),
+ array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
+ array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
+ array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
+ array( 'modifyField', 'user_former_groups', 'ufg_group', 'patch-ug_group-length-increase.sql' ),
+
+ // 1.20
+ array( 'addTable', 'config', 'patch-config.sql' ),
+ array( 'addIndex', 'revision', 'page_user_timestamp', 'patch-revision-user-page-index.sql' ),
);
}
*/
function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); }
- /**
+ /**abstract
* Sets the number of seconds after which this object should expire.
* This value is used with the ParserCache.
* If called with a value greater than the value provided at any previous call,
* The value returned by getCacheExpiry is smaller or equal to the smallest number
* that was provided to a call of updateCacheExpiry(), and smaller or equal to the
* value of $wgParserCacheExpireTime.
+ * @return int|mixed|null
*/
function getCacheExpiry() {
global $wgParserCacheExpireTime;
$mProperties = array(), # Name/value pairs to be cached in the DB
$mTOCHTML = '', # HTML of the TOC
$mTimestamp; # Timestamp of the revision
- private $mIndexPolicy = ''; # 'index' or 'noindex'? Any other value will result in no change.
- private $mAccessedOptions = array(); # List of ParserOptions (stored in the keys)
+ private $mIndexPolicy = ''; # 'index' or 'noindex'? Any other value will result in no change.
+ private $mAccessedOptions = array(); # List of ParserOptions (stored in the keys)
+ private $mSecondaryDataUpdates = array(); # List of instances of SecondaryDataObject(), used to cause some information extracted from the page in a custom place.
const EDITSECTION_REGEX = '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#';
/**
* callback used by getText to replace editsection tokens
* @private
+ * @return mixed
*/
function replaceEditSectionLinksCallback( $m ) {
global $wgOut, $wgLang;
* Register a file dependency for this output
* @param $name string Title dbKey
* @param $timestamp string MW timestamp of file creation (or false if non-existing)
- * @param $sha string base 36 SHA-1 of file (or false if non-existing)
+ * @param $sha1 string base 36 SHA-1 of file (or false if non-existing)
* @return void
*/
function addImage( $name, $timestamp = null, $sha1 = null ) {
function recordOption( $option ) {
$this->mAccessedOptions[$option] = true;
}
+
+ /**
+ * Adds an update job to the output. Any update jobs added to the output will eventually bexecuted in order to
+ * store any secondary information extracted from the page's content.
+ *
+ * @param SecondaryDataUpdate $update
+ */
+ public function addSecondaryDataUpdate( SecondaryDataUpdate $update ) {
+ $this->mSecondaryDataUpdates[] = $update;
+ }
+
+ /**
+ * Returns any SecondaryDataUpdate jobs to be executed in order to store secondary information
+ * extracted from the page's content.
+ *
+ * This does not automatically include an LinksUpdate object for the links in this ParserOutput instance.
+ * Use getLinksUpdateAndOtherUpdates() if you want that.
+ *
+ * @return array an array of instances of SecondaryDataUpdate
+ */
+ public function getSecondaryDataUpdates() {
+ return $this->mSecondaryDataUpdates;
+ }
+
+ /**
+ * Conveniance method that returns any SecondaryDataUpdate jobs to be executed in order
+ * to store secondary information extracted from the page's content, including the LinksUpdate object
+ * for all links stopred in this ParserOutput object.
+ *
+ * @param $title Title of the page we're updating. If not given, a title object will be created based on $this->getTitleText()
+ * @param $recursive Boolean: queue jobs for recursive updates?
+ *
+ * @return array an array of instances of SecondaryDataUpdate
+ */
+ public function getLinksUpdateAndOtherUpdates( Title $title = null, $recursive = true ) {
+ if ( empty( $title ) ) {
+ $title = Title::newFromText( $this->getTitleText() );
+ }
+
+ $linksUpdate = new LinksUpdate( $title, $this, $recursive );
+
+ if ( empty( $this->mSecondaryDataUpdates ) ) {
+ return array( $linksUpdate );
+ } else {
+ $updates = array_merge( $this->mSecondaryDataUpdates, array( $linksUpdate ) );
+ }
+
+ return $updates;
+ }
}
if ( !$revision ) {
return null;
}
- return $revision->getRawText();
+ return $revision->getRawText(); #FIXME: get raw data from content object after checking the type;
}
/* Methods */
if ( count( $mtimes ) ) {
$modifiedTime = max( $modifiedTime, max( $mtimes ) );
}
+ $modifiedTime = max( $modifiedTime, $this->getMsgBlobMtime( $context->getLanguage() ) );
return $modifiedTime;
}
$res = $dbr->select( 'archive',
array(
'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
- 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1'
+ 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1',
+ 'ar_content_format', 'ar_content_model'
),
array( 'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey() ),
'ar_deleted',
'ar_len',
'ar_sha1',
+ 'ar_content_format',
+ 'ar_content_model',
),
array( 'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey(),
'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
__METHOD__ );
if( $row ) {
- return Revision::newFromArchiveRow( $row, array( 'page' => $this->title->getArticleId() ) );
+ return Revision::newFromArchiveRow( $row, array( 'page' => $this->title->getArticleID() ) );
} else {
return null;
}
$article->loadPageData( 'fromdbmaster' );
$oldcountable = $article->isCountable();
- $options = 'FOR UPDATE'; // lock page
$page = $dbw->selectRow( 'page',
array( 'page_id', 'page_latest' ),
array( 'page_namespace' => $this->title->getNamespace(),
'page_title' => $this->title->getDBkey() ),
__METHOD__,
- $options
+ array( 'FOR UPDATE' ) // lock page
);
if( $page ) {
$makepage = false;
'ar_deleted',
'ar_page_id',
'ar_len',
- 'ar_sha1' ),
+ 'ar_sha1',
+ 'ar_content_format',
+ 'ar_content_model' ),
/* WHERE */ array(
'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey(),
$out->addHTML( "<ul>\n" );
foreach ( $result as $row ) {
$title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
- $link = Linker::linkKnown(
- $undelete,
- htmlspecialchars( $title->getPrefixedText() ),
- array(),
- array( 'target' => $title->getPrefixedText() )
- );
+ if ( $title !== null ) {
+ $item = Linker::linkKnown(
+ $undelete,
+ htmlspecialchars( $title->getPrefixedText() ),
+ array(),
+ array( 'target' => $title->getPrefixedText() )
+ );
+ } else {
+ // The title is no longer valid, show as text
+ $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
+ $item = htmlspecialchars( $title->getPrefixedText() );
+ }
$revs = $this->msg( 'undeleterevisions' )->numParams( $row->count )->parse();
- $out->addHTML( "<li>{$link} ({$revs})</li>\n" );
+ $out->addHTML( "<li>{$item} ({$revs})</li>\n" );
}
$result->free();
$out->addHTML( "</ul>\n" );
* @return String: HTML
*/
function showDiff( $previousRev, $currentRev ) {
- $diffEngine = new DifferenceEngine( $this->getContext() );
+ $contentHandler = ContentHandler::getForTitle( $this->getTitle() );
+ $diffEngine = $contentHandler->getDifferenceEngine( $this->getContext() );
$diffEngine->showDiffStyle();
$this->getOutput()->addHTML(
"<div>" .
$this->diffHeader( $currentRev, 'n' ) .
"</td>\n" .
"</tr>" .
- $diffEngine->generateDiffBody(
- $previousRev->getText(), $currentRev->getText() ) .
+ $diffEngine->generateContentDiffBody(
+ $previousRev->getContent(), $currentRev->getContent() ) .
"</table>" .
"</div>\n"
);
private function formatRevisionRow( $row, $earliestLiveTime, $remaining ) {
$rev = Revision::newFromArchiveRow( $row,
- array( 'page' => $this->mTargetObj->getArticleId() ) );
+ array( 'page' => $this->mTargetObj->getArticleID() ) );
$stxt = '';
$ts = wfTimestamp( TS_MW, $row->ar_timestamp );
// Build checkboxen...
$pageLink = $this->getLanguage()->userTimeAndDate( $ts, $user );
}
$userLink = $this->getFileUser( $file );
- $data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text() .
- ' (' . $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() . ')';
- $data = htmlspecialchars( $data );
+ $data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text();
+ $bytes = $this->msg( 'parentheses' )->rawParams( $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() )->plain();
+ $data = htmlspecialchars( $data . ' ' . $bytes );
$comment = $this->getFileComment( $file );
// Add show/hide deletion links if available
* Fetch revision text link if it's available to all users
*
* @param $rev Revision
+ * @param $titleObj Title
+ * @param $ts string Timestamp
* @return string
*/
function getPageLink( $rev, $titleObj, $ts ) {
* Fetch image view link if it's available to all users
*
* @param $file File
+ * @param $titleObj Title
+ * @param $ts string A timestamp
+ * @param $key String: a storage key
+ *
* @return String: HTML fragment
*/
function getFileLink( $file, $titleObj, $ts, $key ) {
// Show file deletion warnings and errors
$status = $archive->getFileStatus();
if( $status && !$status->isGood() ) {
- $out->addWikiText( $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) );
+ $out->addWikiText( '<div class="error">' . $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) . '</div>' );
}
}
}
'AddALL' => 'http://www.addall.com/New/Partner.cgi?query=$1&type=ISBN',
'PriceSCAN' => 'http://www.pricescan.com/books/bookDetail.asp?isbn=$1',
'Barnes & Noble' => 'http://search.barnesandnoble.com/bookSearch/isbnInquiry.asp?isbn=$1',
- 'Amazon.com' => 'http://www.amazon.com/exec/obidos/ISBN=$1'
+ 'Amazon.com' => 'http://www.amazon.com/gp/search/?field-isbn=$1'
);
/**
'pagenamee' => array( 1, 'PAGENAMEE' ),
'namespace' => array( 1, 'NAMESPACE' ),
'namespacee' => array( 1, 'NAMESPACEE' ),
+ 'namespacenumber' => array( 1, 'NAMESPACENUMBER' ),
'talkspace' => array( 1, 'TALKSPACE' ),
'talkspacee' => array( 1, 'TALKSPACEE' ),
'subjectspace' => array( 1, 'SUBJECTSPACE', 'ARTICLESPACE' ),
'padleft' => array( 0, 'PADLEFT' ),
'padright' => array( 0, 'PADRIGHT' ),
'special' => array( 0, 'special', ),
+ 'speciale' => array( 0, 'speciale', ),
'defaultsort' => array( 1, 'DEFAULTSORT:', 'DEFAULTSORTKEY:', 'DEFAULTCATEGORYSORT:' ),
'filepath' => array( 0, 'FILEPATH:' ),
'tag' => array( 0, 'tag' ),
'accesskey-ca-history',
'accesskey-ca-nstab-main',
'accesskey-ca-talk',
+ 'accesskey-ca-view',
'accesskey-n-currentevents',
'accesskey-n-help',
'accesskey-n-mainpage-description',
'accesskey-n-portal',
'accesskey-n-randompage',
'accesskey-n-recentchanges',
- 'accesskey-n-sitesupport',
'accesskey-p-logo',
'accesskey-pt-login',
'accesskey-search',
'accesskey-search-fulltext',
'accesskey-search-go',
'accesskey-t-permalink',
- 'accesskey-t-print',
'accesskey-t-recentchangeslinked',
'accesskey-t-specialpages',
'accesskey-t-whatlinkshere',
+ 'actions',
'anonnotice',
- 'colon-separator',
'currentevents',
'currentevents-url',
'disclaimerpage',
'disclaimers',
'edit',
+ 'editsection',
+ 'editsection-brackets',
+ 'editsectionhint',
'help',
'helppage',
- 'history_short',
'jumpto',
'jumptonavigation',
'jumptosearch',
'lastmodifiedat',
'mainpage',
'mainpage-description',
- 'nav-login-createaccount',
+ 'mainpage-nstab',
+ 'namespaces',
'navigation',
+ 'nav-login-createaccount',
'nstab-main',
+ 'nstab-talk',
'opensearch-desc',
'pagecategories',
'pagecategorieslink',
'randompage',
'randompage-url',
'recentchanges',
- 'recentchanges-url',
'recentchangeslinked-toolbox',
+ 'recentchanges-url',
'retrievedfrom',
'search',
'searcharticle',
'searchbutton',
'sidebar',
'site-atom-feed',
- 'site-rss-feed',
'sitenotice',
'specialpages',
'tagline',
'tooltip-ca-history',
'tooltip-ca-nstab-main',
'tooltip-ca-talk',
+ 'tooltip-ca-view',
'tooltip-n-currentevents',
'tooltip-n-help',
'tooltip-n-mainpage-description',
'tooltip-n-portal',
'tooltip-n-randompage',
'tooltip-n-recentchanges',
- 'tooltip-n-sitesupport',
'tooltip-p-logo',
'tooltip-p-navigation',
+ 'tooltip-p-tb',
'tooltip-pt-login',
'tooltip-search',
'tooltip-search-fulltext',
'tooltip-search-go',
'tooltip-t-permalink',
- 'tooltip-t-print',
'tooltip-t-recentchangeslinked',
'tooltip-t-specialpages',
'tooltip-t-whatlinkshere',
+ 'variants',
+ 'vector-view-edit',
+ 'vector-view-history',
+ 'vector-view-view',
+ 'viewcount',
'views',
'whatlinkshere',
);
# User preference toggles
'tog-underline' => 'Link underlining:',
- 'tog-highlightbroken' => 'Format broken links <a href="" class="new">like this</a> (alternative: Like this<a href="" class="internal">?</a>)',
'tog-justify' => 'Justify paragraphs',
'tog-hideminor' => 'Hide minor edits in recent changes',
'tog-hidepatrolled' => 'Hide patrolled edits in recent changes',
'portal-url' => 'Project:Community portal',
'privacy' => 'Privacy policy',
'privacypage' => 'Project:Privacy policy',
+'content-failed-to-parse' => "Failed to parse $2 content for $1 model: $3",
'badaccess' => 'Permission error',
'badaccess-group0' => 'You are not allowed to execute the action you have requested.',
'directorycreateerror' => 'Could not create directory "$1".',
'filenotfound' => 'Could not find file "$1".',
'fileexistserror' => 'Unable to write to file "$1": File exists.',
+ 'filereadonlyerror' => 'Unable to modify the file "$1" because the file repository "$2" is in read-only mode.
+
+ The administrator who locked it offered this explanation: "$3".',
'unexpected' => 'Unexpected value: "$1"="$2".',
'formerror' => 'Error: Could not submit form.',
'badarticleerror' => 'This action cannot be performed on this page.',
'userloginnocreate' => 'Log in',
'logout' => 'Log out',
'userlogout' => 'Log out',
+ 'userlogout-summary' => '', # do not translate or duplicate this message to other languages
'notloggedin' => 'Not logged in',
'nologin' => "Don't have an account? $1.",
'nologinlink' => 'Create an account',
'invalidemailaddress' => 'The e-mail address cannot be accepted as it appears to have an invalid format.
Please enter a well-formatted address or empty that field.',
'cannotchangeemail' => 'Account e-mail addresses cannot be changed on this wiki.',
+ 'emaildisabled' => 'This site cannot send e-mails.',
'accountcreated' => 'Account created',
'accountcreatedtext' => 'The user account for $1 has been created.',
'createaccount-title' => 'Account creation for {{SITENAME}}',
# Special:ChangeEmail
'changeemail' => 'Change e-mail address',
+ 'changeemail-summary' => '', # do not translate or duplicate this message to other languages
'changeemail-header' => 'Change account e-mail address',
'changeemail-text' => 'Complete this form to change your e-mail address. You will need to enter your password to confirm this change.',
'changeemail-no-info' => 'You must be logged in to access this page directly.',
Your current IP address is $3, and the block ID is #$5.
Please include all above details in any queries you make.",
'autoblockedtext' => 'Your IP address has been automatically blocked because it was used by another user, who was blocked by $1.
- The reason given is this:
+ The reason given is:
:\'\'$2\'\'
'updated' => '(Updated)',
'note' => "'''Note:'''",
'previewnote' => "'''Remember that this is only a preview.'''
- Your changes have not yet been saved!",
+ Your changes have not yet been saved! [[#editform|→ Continue editing]]",
'previewconflict' => 'This preview reflects the text in the upper text editing area as it will appear if you choose to save.',
'session_fail_preview' => "'''Sorry! We could not process your edit due to a loss of session data.'''
Please try again.
This sometimes happens when you are using a buggy web-based anonymous proxy service.",
'edit_form_incomplete' => "'''Some parts of the edit form did not reach the server; double-check that your edits are intact and try again.'''",
'editing' => 'Editing $1',
+ 'creating' => 'Creating $1',
'editingsection' => 'Editing $1 (section)',
'editingcomment' => 'Editing $1 (new section)',
'editconflict' => 'Edit conflict: $1',
It already exists.',
'addsection-preload' => '', # do not translate or duplicate this message to other languages
'addsection-editintro' => '', # do not translate or duplicate this message to other languages
+ 'defaultmessagetext' => 'Default message text',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''Warning:''' This page contains too many expensive parser function calls.
'prefsnologin' => 'Not logged in',
'prefsnologintext' => 'You must be <span class="plainlinks">[{{fullurl:{{#Special:UserLogin}}|returnto=$1}} logged in]</span> to set user preferences.',
'changepassword' => 'Change password',
+ 'changepassword-summary' => '', # do not translate or duplicate this message to other languages
'prefs-skin' => 'Skin',
'skin-preview' => 'Preview',
'datedefault' => 'No preference',
'newsectionsummary' => '/* $1 */ new section',
'rc-enhanced-expand' => 'Show details (requires JavaScript)',
'rc-enhanced-hide' => 'Hide details',
+ 'rc-old-title' => 'originally created as "$1"',
# Recent changes linked
'recentchangeslinked' => 'Related changes',
'upload-warning-subj' => 'Upload warning',
'upload-warning-msg' => 'There was a problem with your upload from [$2]. You may return to the [[Special:Upload/stash/$1|upload form]] to correct this problem.',
- 'upload-proto-error' => 'Incorrect protocol',
- 'upload-proto-error-text' => 'Remote upload requires URLs beginning with <code>http://</code> or <code>ftp://</code>.',
- 'upload-file-error' => 'Internal error',
- 'upload-file-error-text' => 'An internal error occurred when attempting to create a temporary file on the server.
+ 'upload-proto-error' => 'Incorrect protocol',
+ 'upload-proto-error-text' => 'Remote upload requires URLs beginning with <code>http://</code> or <code>ftp://</code>.',
+ 'upload-file-error' => 'Internal error',
+ 'upload-file-error-text' => 'An internal error occurred when attempting to create a temporary file on the server.
Please contact an [[Special:ListUsers/sysop|administrator]].',
- 'upload-misc-error' => 'Unknown upload error',
- 'upload-misc-error-text' => 'An unknown error occurred during the upload.
+ 'upload-misc-error' => 'Unknown upload error',
+ 'upload-misc-error-text' => 'An unknown error occurred during the upload.
Please verify that the URL is valid and accessible and try again.
If the problem persists, contact an [[Special:ListUsers/sysop|administrator]].',
- 'upload-too-many-redirects' => 'The URL contained too many redirects',
- 'upload-unknown-size' => 'Unknown size',
- 'upload-http-error' => 'An HTTP error occured: $1',
+ 'upload-too-many-redirects' => 'The URL contained too many redirects',
+ 'upload-unknown-size' => 'Unknown size',
+ 'upload-http-error' => 'An HTTP error occured: $1',
+ 'upload-copy-upload-invalid-domain' => 'Copy uploads are not available from this domain.',
# File backend
'backend-fail-stream' => 'Could not stream file $1.',
'backend-fail-closetemp' => 'Could not close temporary file.',
'backend-fail-read' => 'Could not read file $1.',
'backend-fail-create' => 'Could not create file $1.',
- 'backend-fail-readonly' => 'The backend "$1" is currently read-only. The reason given is: "$2"',
- 'backend-fail-synced' => 'The file "$1" is in an inconsistent state within the internal backends',
- 'backend-fail-connect' => 'Could not connect to file backend "$1".',
- 'backend-fail-internal' => 'An unknown error occurred in file backend "$1".',
- 'backend-fail-contenttype' => 'Could not determine the content type of file to store at "$1".',
- 'backend-fail-batchsize' => 'Backend given a batch of $1 file {{PLURAL:$1|operation|operations}}; the limit is $2 {{PLURAL:$2|operation|operations}}.',
+ 'backend-fail-maxsize' => 'Could not create file $1 because it is larger than {{PLURAL:$2|one byte|$2 bytes}}.',
+ 'backend-fail-readonly' => 'The storage backend "$1" is currently read-only. The reason given is: "\'\'$2\'\'"',
+ 'backend-fail-synced' => 'The file "$1" is in an inconsistent state within the internal storage backends',
+ 'backend-fail-connect' => 'Could not connect to storage backend "$1".',
+ 'backend-fail-internal' => 'An unknown error occurred in storage backend "$1".',
+ 'backend-fail-contenttype' => 'Could not determine the content type of the file to store at "$1".',
+ 'backend-fail-batchsize' => 'Storage backend given a batch of $1 file {{PLURAL:$1|operation|operations}}; the limit is $2 {{PLURAL:$2|operation|operations}}.',
+
+ # File journal
+ 'filejournal-fail-dbconnect' => 'Could not connect to the journal database for storage backend "$1".',
+ 'filejournal-fail-dbquery' => 'Could not update the journal database for storage backend "$1".',
# Lock manager
'lockmanager-notlocked' => 'Could not unlock "$1"; it is not locked.',
'img-auth-nopathinfo' => 'Missing PATH_INFO.
Your server is not set up to pass this information.
It may be CGI-based and cannot support img_auth.
- [//www.mediawiki.org/wiki/Manual:Image_Authorization See image authorization.]',
+ See https://www.mediawiki.org/wiki/Manual:Image_Authorization.',
'img-auth-notindir' => 'Requested path is not in the configured upload directory.',
'img-auth-badtitle' => 'Unable to construct a valid title from "$1".',
'img-auth-nologinnWL' => 'You are not logged in and "$1" is not in the whitelist.',
Please see the [$2 file description page] for further information.',
'sharedupload-desc-here' => 'This file is from $1 and may be used by other projects.
The description on its [$2 file description page] there is shown below.',
+ 'sharedupload-desc-edit' => 'This file is from $1 and may be used by other projects.
+ Maybe you want to edit the description on its [$2 file description page] there.',
+ 'sharedupload-desc-create' => 'This file is from $1 and may be used by other projects.
+ Maybe you want to edit the description on its [$2 file description page] there.',
'shareddescriptionfollows' => '-', # do not translate or duplicate this message to other languages
'filepage-nofile' => 'No file by this name exists.',
'filepage-nofile-link' => 'No file by this name exists, but you can [$1 upload it].',
'uncategorizedtemplates' => 'Uncategorized templates',
'uncategorizedtemplates-summary' => '', # do not translate or duplicate this message to other languages
'unusedcategories' => 'Unused categories',
+ 'unusedcategories-summary' => '', # do not translate or duplicate this message to other languages
'unusedimages' => 'Unused files',
+ 'unusedimages-summary' => '', # do not translate or duplicate this message to other languages
'popularpages' => 'Popular pages',
'popularpages-summary' => '', # do not translate or duplicate this message to other languages
'wantedcategories' => 'Wanted categories',
'allpagesbadtitle' => 'The given page title was invalid or had an inter-language or inter-wiki prefix.
It may contain one or more characters which cannot be used in titles.',
'allpages-bad-ns' => '{{SITENAME}} does not have namespace "$1".',
+ 'allpages-hide-redirects' => 'Hide redirects',
# Special:Categories
'categories' => 'Categories',
# Special:DeletedContributions
'deletedcontributions' => 'Deleted user contributions',
+ 'deletedcontributions-summary' => '', # do not translate or duplicate this message to other languages
'deletedcontributions-title' => 'Deleted user contributions',
'sp-deletedcontributions-contribs' => 'contributions',
# Special:LinkSearch
- 'linksearch' => 'External links search',
- 'linksearch-pat' => 'Search pattern:',
- 'linksearch-ns' => 'Namespace:',
- 'linksearch-ok' => 'Search',
- 'linksearch-text' => 'Wildcards such as "*.wikipedia.org" may be used.
+ 'linksearch' => 'External links search',
+ 'linksearch-summary' => '', # do not translate or duplicate this message to other languages
+ 'linksearch-pat' => 'Search pattern:',
+ 'linksearch-ns' => 'Namespace:',
+ 'linksearch-ok' => 'Search',
+ 'linksearch-text' => 'Wildcards such as "*.wikipedia.org" may be used.
Needs at least a top-level domain, for example "*.org".<br />
Supported protocols: <tt>$1</tt> (do not add any of these in your search).',
- 'linksearch-line' => '$1 is linked from $2',
- 'linksearch-error' => 'Wildcards may appear only at the start of the hostname.',
+ 'linksearch-line' => '$1 is linked from $2',
+ 'linksearch-error' => 'Wildcards may appear only at the start of the hostname.',
# Special:ListUsers
'listusersfrom' => 'Display users starting at:',
'mailnologin' => 'No send address',
'mailnologintext' => 'You must be [[Special:UserLogin|logged in]] and have a valid e-mail address in your [[Special:Preferences|preferences]] to send e-mail to other users.',
'emailuser' => 'E-mail this user',
+ 'emailuser-summary' => '', # do not translate or duplicate this message to other languages
'emailpage' => 'E-mail user',
'emailpagetext' => 'You can use the form below to send an e-mail message to this user.
The e-mail address you entered in [[Special:Preferences|your user preferences]] will appear as the "From" address of the e-mail, so the recipient will be able to reply directly to you.',
# Watchlist
'watchlist' => 'My watchlist',
+ 'watchlist-summary' => '', # do not translate or duplicate this message to other languages
'mywatchlist' => 'My watchlist',
'watchlistfor2' => 'For $1 $2',
'nowatchlist' => 'You have no items on your watchlist.',
# Undelete
'undelete' => 'View deleted pages',
+ 'undelete-summary' => '', # do not translate or duplicate this message to other languages
'undeletepage' => 'View and restore deleted pages',
'undeletepagetitle' => "'''The following consists of deleted revisions of [[:$1|$1]]'''.",
'viewdeletedpage' => 'View deleted pages',
'blanknamespace' => '(Main)',
# Contributions
- 'contributions' => 'User contributions',
- 'contributions-title' => 'User contributions for $1',
- 'mycontris' => 'My contributions',
- 'contribsub2' => 'For $1 ($2)',
- 'nocontribs' => 'No changes were found matching these criteria.',
- 'uctop' => '(top)',
- 'month' => 'From month (and earlier):',
- 'year' => 'From year (and earlier):',
+ 'contributions' => 'User contributions',
+ 'contributions-summary' => '', # do not translate or duplicate this message to other languages
+ 'contributions-title' => 'User contributions for $1',
+ 'mycontris' => 'My contributions',
+ 'contribsub2' => 'For $1 ($2)',
+ 'nocontribs' => 'No changes were found matching these criteria.',
+ 'uctop' => '(top)',
+ 'month' => 'From month (and earlier):',
+ 'year' => 'From year (and earlier):',
'sp-contributions-newbies' => 'Show contributions of new accounts only',
'sp-contributions-newbies-sub' => 'For new accounts',
'sp-contributions-explain' => '', # only translate this message to other languages if you have to change it
'sp-contributions-footer' => '-', # do not translate or duplicate this message to other languages
'sp-contributions-footer-anon' => '-', # do not translate or duplicate this message to other languages
+ 'sp-contributions-footer-newbies' => '-', # do not translate or duplicate this message to other languages
# What links here
'whatlinkshere' => 'What links here',
'autoblockid' => 'Autoblock #$1',
'block' => 'Block user',
'unblock' => 'Unblock user',
+ 'unblock-summary' => '', # do not translate or duplicate this message to other languages
'blockip' => 'Block user',
'blockip-title' => 'Block user',
'blockip-legend' => 'Block user',
'contribslink' => 'contribs',
'emaillink' => 'send e-mail',
'autoblocker' => 'Autoblocked because your IP address has been recently used by "[[User:$1|$1]]".
- The reason given for $1\'s block is: "$2"',
+ The reason given for $1\'s block is "\'\'$2\'\'"',
'blocklogpage' => 'Block log',
'blocklog-showlog' => 'This user has been blocked previously.
The block log is provided below for reference:',
# Move page
'move-page' => 'Move $1',
+ 'movepage-summary' => '', # do not translate or duplicate this message to other languages
'move-page-legend' => 'Move page',
'movepagetext' => "Using the form below will rename a page, moving all of its history to the new name.
The old title will become a redirect page to the new title.
# Export
'export' => 'Export pages',
+ 'export-summary' => '', # do not translate or duplicate this message to other languages
'exporttext' => 'You can export the text and editing history of a particular page or set of pages wrapped in some XML.
This can be imported into another wiki using MediaWiki via the [[Special:Import|import page]].
# Special:Import
'import' => 'Import pages',
+ 'import-summary' => '', # do not translate or duplicate this message to other languages
'importinterwiki' => 'Transwiki import',
'import-interwiki-text' => "Select a wiki and page title to import.
Revision dates and editors' names will be preserved.
# JavaScriptTest
'javascripttest' => 'JavaScript testing',
'javascripttest-backlink' => '< $1', # do not translate or duplicate this message to other languages
- 'javascripttest-disabled' => 'This function is disabled.',
+ 'javascripttest-disabled' => 'This function has not been enabled on this wiki.',
'javascripttest-title' => 'Running $1 tests',
'javascripttest-pagetext-noframework' => 'This page is reserved for running JavaScript tests.',
'javascripttest-pagetext-unknownframework' => 'Unknown testing framework "$1".',
'lag-warn-high' => 'Due to high database server lag, changes newer than $1 {{PLURAL:$1|second|seconds}} may not be shown in this list.',
# Watchlist editor
+ 'editwatchlist-summary' => '', # do not translate or duplicate this message to other languages
'watchlistedit-numitems' => 'Your watchlist contains {{PLURAL:$1|1 title|$1 titles}}, excluding talk pages.',
'watchlistedit-noitems' => 'Your watchlist contains no titles.',
'watchlistedit-normal-title' => 'Edit watchlist',
# Special:Version
'version' => 'Version',
+ 'version-summary' => '', # do not translate or duplicate this message to other languages
'version-extensions' => 'Installed extensions',
'version-specialpages' => 'Special pages',
'version-parserhooks' => 'Parser hooks',
# Special:Tags
'tags' => 'Valid change tags',
+ 'tags-summary' => '', # do not translate or duplicate this message to other languages
'tag-filter' => '[[Special:Tags|Tag]] filter:',
'tag-filter-submit' => 'Filter',
'tags-title' => 'Tags',
# Special:ComparePages
'comparepages' => 'Compare pages',
+ 'comparepages-summary' => '', # do not translate or duplicate this message to other languages
'compare-selector' => 'Compare page revisions',
'compare-page1' => 'Page 1',
'compare-page2' => 'Page 2',
'sqlite-no-fts' => '$1 without full-text search support',
# New logging system
- 'logentry-delete-delete' => '$1 {{GENDER:$2|deleted}} page $3',
- 'logentry-delete-restore' => '$1 {{GENDER:$2|restored}} page $3',
- 'logentry-delete-event' => '$1 {{GENDER:$2|changed}} visibility of {{PLURAL:$5|a log event|$5 log events}} on $3: $4',
- 'logentry-delete-revision' => '$1 {{GENDER:$2|changed}} visibility of {{PLURAL:$5|a revision|$5 revisions}} on page $3: $4',
- 'logentry-delete-event-legacy' => '$1 {{GENDER:$2|changed}} visibility of log events on $3',
- 'logentry-delete-revision-legacy' => '$1 {{GENDER:$2|changed}} visibility of revisions on page $3',
- 'logentry-suppress-delete' => '$1 {{GENDER:$2|suppressed}} page $3',
- 'logentry-suppress-event' => '$1 secretly {{GENDER:$2|changed}} visibility of {{PLURAL:$5|a log event|$5 log events}} on $3: $4',
- 'logentry-suppress-revision' => '$1 secretly {{GENDER:$2|changed}} visibility of {{PLURAL:$5|a revision|$5 revisions}} on page $3: $4',
- 'logentry-suppress-event-legacy' => '$1 secretly {{GENDER:$2|changed}} visibility of log events on $3',
- 'logentry-suppress-revision-legacy' => '$1 secretly {{GENDER:$2|changed}} visibility of revisions on page $3',
+ 'logentry-delete-delete' => '$1 deleted page $3',
+ 'logentry-delete-restore' => '$1 restored page $3',
+ 'logentry-delete-event' => '$1 changed visibility of {{PLURAL:$5|a log event|$5 log events}} on $3: $4',
+ 'logentry-delete-revision' => '$1 changed visibility of {{PLURAL:$5|a revision|$5 revisions}} on page $3: $4',
+ 'logentry-delete-event-legacy' => '$1 changed visibility of log events on $3',
+ 'logentry-delete-revision-legacy' => '$1 changed visibility of revisions on page $3',
+ 'logentry-suppress-delete' => '$1 suppressed page $3',
+ 'logentry-suppress-event' => '$1 secretly changed visibility of {{PLURAL:$5|a log event|$5 log events}} on $3: $4',
+ 'logentry-suppress-revision' => '$1 secretly changed visibility of {{PLURAL:$5|a revision|$5 revisions}} on page $3: $4',
+ 'logentry-suppress-event-legacy' => '$1 secretly changed visibility of log events on $3',
+ 'logentry-suppress-revision-legacy' => '$1 secretly changed visibility of revisions on page $3',
'revdelete-content-hid' => 'content hidden',
'revdelete-summary-hid' => 'edit summary hidden',
'revdelete-uname-hid' => 'username hidden',
'revdelete-uname-unhid' => 'username unhidden',
'revdelete-restricted' => 'applied restrictions to administrators',
'revdelete-unrestricted' => 'removed restrictions for administrators',
- 'logentry-move-move' => '$1 {{GENDER:$2|moved}} page $3 to $4',
- 'logentry-move-move-noredirect' => '$1 {{GENDER:$2|moved}} page $3 to $4 without leaving a redirect',
- 'logentry-move-move_redir' => '$1 {{GENDER:$2|moved}} page $3 to $4 over redirect',
- 'logentry-move-move_redir-noredirect' => '$1 {{GENDER:$2|moved}} page $3 to $4 over a redirect without leaving a redirect',
- 'logentry-patrol-patrol' => '$1 {{GENDER:$2|marked}} revision $4 of page $3 patrolled',
- 'logentry-patrol-patrol-auto' => '$1 automatically {{GENDER:$2|marked}} revision $4 of page $3 patrolled',
- 'logentry-newusers-newusers' => '$1 {{GENDER:$2|created}} a user account',
- 'logentry-newusers-create' => '$1 {{GENDER:$2|created}} a user account',
- 'logentry-newusers-create2' => '$1 {{GENDER:$2|created}} {{GENDER:$4|a user account}} $3',
- 'logentry-newusers-autocreate' => 'Account $1 was {{GENDER:$2|created}} automatically',
+ 'logentry-move-move' => '$1 moved page $3 to $4',
+ 'logentry-move-move-noredirect' => '$1 moved page $3 to $4 without leaving a redirect',
+ 'logentry-move-move_redir' => '$1 moved page $3 to $4 over redirect',
+ 'logentry-move-move_redir-noredirect' => '$1 moved page $3 to $4 over a redirect without leaving a redirect',
+ 'logentry-patrol-patrol' => '$1 marked revision $4 of page $3 patrolled',
+ 'logentry-patrol-patrol-auto' => '$1 automatically marked revision $4 of page $3 patrolled',
+ 'logentry-newusers-newusers' => '$1 created a user account',
+ 'logentry-newusers-create' => '$1 created a user account',
+ 'logentry-newusers-create2' => '$1 created a user account $3',
+ 'logentry-newusers-autocreate' => 'Account $1 was created automatically',
'newuserlog-byemail' => 'password sent by e-mail',
+ # For IRC, see bug 34508. Do not change
+ 'revdelete-logentry' => 'changed revision visibility of "[[$1]]"', # do not translate or duplicate this message to other languages
+ 'logdelete-logentry' => 'changed event visibility of "[[$1]]"', # do not translate or duplicate this message to other languages
+ 'revdelete-content' => 'content', # do not translate or duplicate this message to other languages
+ 'revdelete-summary' => 'edit summary', # do not translate or duplicate this message to other languages
+ 'revdelete-uname' => 'username', # do not translate or duplicate this message to other languages
+ 'revdelete-hid' => 'hid $1', # do not translate or duplicate this message to other languages
+ 'revdelete-unhid' => 'unhid $1', # do not translate or duplicate this message to other languages
+ 'revdelete-log-message' => '$1 for $2 {{PLURAL:$2|revision|revisions}}', # do not translate or duplicate this message to other languages
+ 'logdelete-log-message' => '$1 for $2 {{PLURAL:$2|event|events}}', # do not translate or duplicate this message to other languages
+ 'deletedarticle' => 'deleted "[[$1]]"', # do not translate or duplicate this message to other languages
+ 'suppressedarticle' => 'suppressed "[[$1]]"', # do not translate or duplicate this message to other languages
+ 'undeletedarticle' => 'restored "[[$1]]"', # do not translate or duplicate this message to other languages
+ 'patrol-log-line' => 'marked $1 of $2 patrolled $3', # do not translate or duplicate this message to other languages
+ 'patrol-log-auto' => '(automatic)', # do not translate or duplicate this message to other languages
+ 'patrol-log-diff' => 'revision $1', # do not translate or duplicate this message to other languages
+ '1movedto2' => 'moved [[$1]] to [[$2]]', # do not translate or duplicate this message to other languages
+ '1movedto2_redir' => 'moved [[$1]] to [[$2]] over redirect', # do not translate or duplicate this message to other languages
+ 'move-redirect-suppressed' => 'redirect suppressed', # do not translate or duplicate this message to other languages
+ 'newuserlog-create-entry' => 'New user account', # do not translate or duplicate this message to other languages
+ 'newuserlog-create2-entry' => 'created new account $1', # do not translate or duplicate this message to other languages
+ 'newuserlog-autocreate-entry' => 'Account created automatically', # do not translate or duplicate this message to other languages
+
# Feedback
'feedback-bugornote' => 'If you are ready to describe a technical problem in detail please [$1 report a bug].
Otherwise, you can use the easy form below. Your comment will be added to the page "[$3 $2]", along with your username and what browser you are using.',
return;
}
- $dbw->begin();
+ $dbw->begin( __METHOD__ );
$options = new ParserOptions;
$parserOutput = $wgParser->parse( $revision->getText(), $title, $options, true, true, $revision->getId() );
+
+ $updates = $parserOutput->getLinksUpdateAndOtherUpdates( $title, false );
+ SecondaryDataUpdate::runUpdates( $updates );
+
+ $dbw->commit();
++ // TODO: We don't know what happens here.
+ $update = new LinksUpdate( $title, $parserOutput, false );
+ $update->doUpdate();
+ $dbw->commit( __METHOD__ );
}
/**
* Removes non-existing links from pages from pagelinks, imagelinks,
* categorylinks, templatelinks, externallinks, interwikilinks, langlinks and redirect tables.
*
- * @param $maxLag
- * @param $batchSize The size of deletion batches
+ * @param $maxLag int
+ * @param $batchSize int The size of deletion batches
*
* @author Merlijn van Deen <valhallasw@arctus.nl>
*/
-- in early 2002 after a lot of trouble with the fields
-- auto-updating.
--
- -- The Postgres backend uses DATETIME fields for timestamps,
+ -- The Postgres backend uses TIMESTAMPTZ fields for timestamps,
-- and we will migrate the MySQL definitions at some point as
-- well.
--
CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
-- Stores the groups the user has once belonged to.
- -- The user may still belong these groups. Check user_groups.
+ -- The user may still belong to these groups (check user_groups).
+ -- Users are not autopromoted to groups from which they were removed.
CREATE TABLE /*_*/user_former_groups (
-- Key to user_id
ufg_user int unsigned NOT NULL default 0,
- ufg_group varbinary(16) NOT NULL default ''
+ ufg_group varbinary(32) NOT NULL default ''
) /*$wgDBTableOptions*/;
CREATE UNIQUE INDEX /*i*/ufg_user_group ON /*_*/user_former_groups (ufg_user,ufg_group);
page_latest int unsigned NOT NULL,
-- Uncompressed length in bytes of the page's current source text.
- page_len int unsigned NOT NULL
+ page_len int unsigned NOT NULL,
+
+ -- content model
+ page_content_model varbinary(32) default NULL
) /*$wgDBTableOptions*/;
CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
rev_parent_id int unsigned default NULL,
-- SHA-1 text content hash in base-36
- rev_sha1 varbinary(32) NOT NULL default ''
+ rev_sha1 varbinary(32) NOT NULL default '',
+
+ -- content model
+ rev_content_model varbinary(32) default NULL,
+
+ -- content format (mime type)
+ rev_content_format varbinary(64) default NULL
) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
-- In case tables are created as MyISAM, use row hints for MySQL <5.0 to avoid 4GB limit
CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
+ CREATE INDEX /*i*/page_user_timestamp ON /*_*/revision (rev_page,rev_user,rev_timestamp);
--
-- Holds text of individual page revisions.
ar_parent_id int unsigned default NULL,
-- SHA-1 text content hash in base-36
- ar_sha1 varbinary(32) NOT NULL default ''
+ ar_sha1 varbinary(32) NOT NULL default '',
+
+ -- content model
+ ar_content_model varbinary(32) default NULL,
+
+ -- content format (mime type)
+ ar_content_format varbinary(64) default NULL
+
) /*$wgDBTableOptions*/;
CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
-- moved into the actual filestore
--
CREATE TABLE /*_*/uploadstash (
- us_id int unsigned NOT NULL PRIMARY KEY auto_increment,
+ us_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
- -- the user who uploaded the file.
- us_user int unsigned NOT NULL,
+ -- the user who uploaded the file.
+ us_user int unsigned NOT NULL,
- -- file key. this is how applications actually search for the file.
- -- this might go away, or become the primary key.
- us_key varchar(255) NOT NULL,
+ -- file key. this is how applications actually search for the file.
+ -- this might go away, or become the primary key.
+ us_key varchar(255) NOT NULL,
- -- the original path
- us_orig_path varchar(255) NOT NULL,
+ -- the original path
+ us_orig_path varchar(255) NOT NULL,
- -- the temporary path at which the file is actually stored
- us_path varchar(255) NOT NULL,
+ -- the temporary path at which the file is actually stored
+ us_path varchar(255) NOT NULL,
- -- which type of upload the file came from (sometimes)
- us_source_type varchar(50),
+ -- which type of upload the file came from (sometimes)
+ us_source_type varchar(50),
- -- the date/time on which the file was added
- us_timestamp varbinary(14) not null,
+ -- the date/time on which the file was added
+ us_timestamp varbinary(14) NOT NULL,
- us_status varchar(50) not null,
+ us_status varchar(50) NOT NULL,
- -- chunk counter starts at 0, current offset is stored in us_size
- us_chunk_inx int unsigned NULL,
+ -- chunk counter starts at 0, current offset is stored in us_size
+ us_chunk_inx int unsigned NULL,
- -- file properties from File::getPropsFromPath. these may prove unnecessary.
- --
- us_size int unsigned NOT NULL,
- -- this hash comes from File::sha1Base36(), and is 31 characters
- us_sha1 varchar(31) NOT NULL,
- us_mime varchar(255),
- -- Media type as defined by the MEDIATYPE_xxx constants, should duplicate definition in the image table
- us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
- -- image-specific properties
- us_image_width int unsigned,
- us_image_height int unsigned,
- us_image_bits smallint unsigned
+ -- file properties from File::getPropsFromPath. these may prove unnecessary.
+ --
+ us_size int unsigned NOT NULL,
+ -- this hash comes from File::sha1Base36(), and is 31 characters
+ us_sha1 varchar(31) NOT NULL,
+ us_mime varchar(255),
+ -- Media type as defined by the MEDIATYPE_xxx constants, should duplicate definition in the image table
+ us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ -- image-specific properties
+ us_image_width int unsigned,
+ us_image_height int unsigned,
+ us_image_bits smallint unsigned
) /*$wgDBTableOptions*/;
CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
- CREATE INDEX /*i*/type_action ON /*_*/logging(log_type, log_action, log_timestamp);
+ CREATE INDEX /*i*/type_action ON /*_*/logging (log_type, log_action, log_timestamp);
CREATE TABLE /*_*/log_search (
) /*$wgDBTableOptions*/;
CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
- CREATE INDEX /*i*/job_timestamp ON /*_*/job(job_timestamp);
+ CREATE INDEX /*i*/job_timestamp ON /*_*/job (job_timestamp);
-- Details of updates to cached special pages
) /*$wgDBTableOptions*/;
CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key);
- -- Table for storing JSON message blobs for the resource loader
+ -- Table for caching JSON message blobs for the resource loader
CREATE TABLE /*_*/msg_resource (
-- Resource name
mr_resource varbinary(255) NOT NULL,
) /*$wgDBTableOptions*/;
CREATE UNIQUE INDEX /*i*/mrl_message_resource ON /*_*/msg_resource_links (mrl_message, mrl_resource);
- -- Table for tracking which local files a module depends on that aren't
- -- registered directly.
+ -- Table caching which local files a module depends on that aren't
+ -- registered directly, used for fast retrieval of file dependency.
-- Currently only used for tracking images that CSS depends on
CREATE TABLE /*_*/module_deps (
-- Module name