used to retrieve this type of tokens.
'ArticleAfterFetchContent': after fetching content of an article from
+the database. DEPRECATED, use ArticleAfterFetchContentObject instead.
+$article: the article (object) being loaded from the database
+&$content: the content (string) of the article
+
+'ArticleAfterFetchContentObject': after fetching content of an article from
the database
$article: the article (object) being loaded from the database
-$content: the content (string) of the article
+&$content: the content of the article, as a Content object
'ArticleConfirmDelete': before writing the confirmation form for article
deletion
$title: title (object) used to create the article object
$article: article (object) that will be returned
-'ArticleInsertComplete': After a new article is created
+'ArticleInsertComplete': After a new article is created. DEPRECATED, use ArticleContentInsertComplete
$article: WikiPage created
$user: User creating the article
$text: New content
$flags: Flags passed to Article::doEdit()
$revision: New Revision of the article
+'ArticleContentInsertComplete': After a new article is created
+$article: WikiPage created
+$user: User creating the article
+$content: New content as a Content object
+$summary: Edit summary/comment
+$isMinor: Whether or not the edit was marked as minor
+$isWatch: (No longer used)
+$section: (No longer used)
+$flags: Flags passed to Article::doEdit()
+$revision: New Revision of the article
+
'ArticleMergeComplete': after merging to article using Special:Mergehistory
$targetTitle: target title (object)
$destTitle: destination title (object)
$revision: the revision the page was reverted back to
$current: the reverted revision
-'ArticleSave': before an article is saved
+'ArticleSave': before an article is saved. DEPRECATED, use ArticleContentSave instead
$article: the WikiPage (object) being saved
$user: the user (object) saving the article
$text: the new article text
$iswatch: watch flag
$section: section #
-'ArticleSaveComplete': After an article has been updated
+'ArticleContentSave': before an article is saved.
+$article: the WikiPage (object) being saved
+$user: the user (object) saving the article
+$content: the new article content, as a Content object
+$summary: the article summary (comment)
+$isminor: minor flag
+$iswatch: watch flag
+$section: section #
+
+'ArticleSaveComplete': After an article has been updated. DEPRECATED, use ArticleContentSaveComplete instead.
$article: WikiPage modified
$user: User performing the modification
$text: New content
$status: Status object about to be returned by doEdit()
$baseRevId: the rev ID (or false) this edit was based on
+'ArticleContentSaveComplete': After an article has been updated
+$article: WikiPage modified
+$user: User performing the modification
+$content: New content, as a Content object
+$summary: Edit summary/comment
+$isMinor: Whether or not the edit was marked as minor
+$isWatch: (No longer used)
+$section: (No longer used)
+$flags: Flags passed to Article::doEdit()
+$revision: New Revision of the article
+$status: Status object about to be returned by doEdit()
+$baseRevId: the rev ID (or false) this edit was based on
+
'ArticleUndelete': When one or more revisions of an article are restored
$title: Title corresponding to the article restored
$create: Whether or not the restoration caused the page to be created
follwed an redirect
$article: target article (object)
-'ArticleViewCustom': allows to output the text of the article in a different format than wikitext
+'ArticleViewCustom': allows to output the text of the article in a different format than wikitext.
+DEPRECATED, use ArticleContentViewCustom instead.
+Note that it is preferrable to implement proper handing for a custom data type using the ContentHandler facility.
$text: text of the page
$title: title of the page
$output: reference to $wgOut
+'ArticleContentViewCustom': allows to output the text of the article in a different format than wikitext.
+Note that it is preferrable to implement proper handing for a custom data type using the ContentHandler facility.
+$content: content of the page, as a Content object
+$title: title of the page
+$output: reference to $wgOut
+
'AuthPluginAutoCreate': Called when creating a local account for an user logged
in from an external authentication method
$user: User object created locally
'ConfirmEmailComplete': Called after a user's email has been confirmed successfully
$user: user (object) whose email is being confirmed
+'ContentHandlerDefaultModelFor': Called when the default content model is determiend
+for a given title. May be used to assign a different model for that title.
+$title: the Title in question
+&$model: the model name. Use with CONTENT_MODEL_XXX constants.
+
+'ContentHandlerForModelName': Called when a ContentHandler is requested for a given
+cointent model name, but no entry for that model exists in $wgContentHandlers.
+$modeName: the requested content model name
+&$handler: set this to a ContentHandler object, if desired.
+
'ContribsPager::getQueryInfo': Before the contributions query is about to run
&$pager: Pager object for contributions
&$queryInfo: The query for the contribs Pager
&$error: Error message to return
$summary: Edit summary for page
-'EditFilterMerged': Post-section-merge edit filter
+'EditFilterMerged': Post-section-merge edit filter.
+DEPRECATED, use EditFilterMergedContent instead.
$editor: EditPage instance (object)
$text: content of the edit box
&$error: error message to return
$summary: Edit summary for page
+'EditFilterMergedContent': Post-section-merge edit filter
+$editor: EditPage instance (object)
+$content: content of the edit box, as a Content object
+&$error: error message to return
+$summary: Edit summary for page
+
'EditFormPreloadText': Allows population of the edit form when creating
new pages
&$text: Text to preload with
&$msg: localization message name, overridable. Default is either 'copyrightwarning' or 'copyrightwarning2'
'EditPageGetDiffText': Allow modifying the wikitext that will be used in
-"Show changes"
+"Show changes". DEPRECATED. Use EditPageGetDiffContent instead.
+Note that it is preferrable to implement diff handling for different data types using the ContentHandler facility.
$editPage: EditPage object
&$newtext: wikitext that will be used as "your version"
-'EditPageGetPreviewText': Allow modifying the wikitext that will be previewed
+'EditPageGetDiffContent': Allow modifying the wikitext that will be used in
+"Show changes".
+Note that it is preferrable to implement diff handling for different data types using the ContentHandler facility.
+$editPage: EditPage object
+&$newtext: wikitext that will be used as "your version"
+
+'EditPageGetPreviewText': Allow modifying the wikitext that will be previewed.
+DEPRECATED. Use EditPageGetPreviewContent instead.
+Note that it is preferrable to implement previews for different data types using the COntentHandler facility.
$editPage: EditPage object
&$toparse: wikitext that will be parsed
+'EditPageGetPreviewContent': Allow modifying the wikitext that will be previewed.
+Note that it is preferrable to implement previews for different data types using the COntentHandler facility.
+$editPage: EditPage object
+&$content: Content object to be previewed (may be replaced by hook function)
+
'EditPageNoSuchSection': When a section edit request is given for an non-existent section
&$editpage: The current EditPage object
&$res: the HTML of the error text
$user: user who performed the deletion
$reason: reason
+ 'FileTransformed': When a file is transformed and moved into storage
+ $file: reference to the File object
+ $thumb: the MediaTransformOutput object
+ $tmpThumbPath: The temporary file system path of the transformed file
+ $thumbPath: The permanent storage path of the transformed file
+
'FileUpload': When a file upload occurs
$file : Image object representing the file that was uploaded
$reupload : Boolean indicating if there was a previously another image there or not (since 1.17)
'ShowMissingArticle': Called when generating the output for a non-existent page
$article: The article object corresponding to the page
-'ShowRawCssJs': Customise the output of raw CSS and JavaScript in page views
+'ShowRawCssJs': Customise the output of raw CSS and JavaScript in page views.
+DEPRECATED, use the ContentHandler facility to handle CSS and JavaScript!
$text: Text being shown
$title: Title of the custom script/stylesheet page
$output: Current OutputPage object
'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',
'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
'CachedAction' => 'includes/actions/CachedAction.php',
'CreditsAction' => 'includes/actions/CreditsAction.php',
'ApiFormatDump' => 'includes/api/ApiFormatDump.php',
'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php',
'ApiFormatJson' => 'includes/api/ApiFormatJson.php',
+ 'ApiFormatNone' => 'includes/api/ApiFormatNone.php',
'ApiFormatPhp' => 'includes/api/ApiFormatPhp.php',
'ApiFormatRaw' => 'includes/api/ApiFormatRaw.php',
'ApiFormatTxt' => 'includes/api/ApiFormatTxt.php',
'FileBackend' => 'includes/filerepo/backend/FileBackend.php',
'FileBackendStore' => 'includes/filerepo/backend/FileBackendStore.php',
'FileBackendStoreShardListIterator' => 'includes/filerepo/backend/FileBackendStore.php',
+ 'FileBackendStoreShardDirIterator' => 'includes/filerepo/backend/FileBackendStore.php',
+ 'FileBackendStoreShardFileIterator' => 'includes/filerepo/backend/FileBackendStore.php',
'FileBackendMultiWrite' => 'includes/filerepo/backend/FileBackendMultiWrite.php',
'FSFileBackend' => 'includes/filerepo/backend/FSFileBackend.php',
+ 'FSFileBackendList' => 'includes/filerepo/backend/FSFileBackend.php',
+ 'FSFileBackendDirList' => 'includes/filerepo/backend/FSFileBackend.php',
'FSFileBackendFileList' => 'includes/filerepo/backend/FSFileBackend.php',
'SwiftFileBackend' => 'includes/filerepo/backend/SwiftFileBackend.php',
+ 'SwiftFileBackendList' => 'includes/filerepo/backend/SwiftFileBackend.php',
+ 'SwiftFileBackendDirList' => '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',
'TestFileIterator' => 'tests/testHelpers.inc',
'TestRecorder' => 'tests/testHelpers.inc',
+ # tests/phpunit
+ 'WikitextContentTest' => 'tests/phpunit/includes/WikitextContentTest.php',
+ 'JavascriptContentTest' => 'tests/phpunit/includes/JavascriptContentTest.php',
+ 'DummyContentHandlerForTesting' => 'tests/phpunit/includes/ContentHandlerTest.php',
+ 'DummyContentForTesting' => 'tests/phpunit/includes/ContentHandlerTest.php',
+
# tests/parser
'ParserTest' => 'tests/parser/parserTest.inc',
'ParserTestParserHook' => 'tests/parser/parserTestsParserHook.php',
'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.
/** Same as the above except for edit summaries */
$wgSummarySpamRegex = array();
- /**
- * Similarly you can get a function to do the job. The function will be given
- * the following args:
- * - a Title object for the article the edit is made on
- * - the text submitted in the textarea (wpTextbox1)
- * - the section number.
- * The return should be boolean indicating whether the edit matched some evilness:
- * - true : block it
- * - false : let it through
- *
- * @deprecated since 1.17 Use hooks. See SpamBlacklist extension.
- * @var $wgFilterCallback bool|string|Closure
- */
- $wgFilterCallback = false;
-
/**
* Whether to use DNS blacklists in $wgDnsBlacklistUrls to check for open proxies
* @since 1.16
$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
+ * * 'serializeContent': serializeContent to default format
+ */
+$wgContentHandlerTextFallback = 'ignore';
+
/**
* For really cool vim folding this needs to be at the end:
* vim: foldmarker=@{,@} foldmethod=marker
*/
const AS_HOOK_ERROR = 210;
- /**
- * Status: The filter function set in $wgFilterCallback returned true (= block it)
- */
- const AS_FILTERING = 211;
-
/**
* Status: A hook function returned an error
*/
*/
const AS_IMAGE_REDIRECT_LOGGED = 234;
+ /**
+ * Status: can't parse content
+ */
+ const AS_PARSE_ERROR = 240;
+
/**
* HTML id and name for the beginning of the edit form.
*/
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
}
/**
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() ) ) );
} 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 = '';
}
}
+ $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.WD
*/
- function getContent( $def_text = '' ) {
- global $wgOut, $wgRequest, $wgParser;
+ function getContent( $def_text = false ) { #FIXME: deprecated, replace usage!
+ wfDeprecated( __METHOD__, '1.WD' );
+
+ 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 = ContentHandler::makeContent( $msg, $this->mTitle );
}
- 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->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 {
# If we just undid one rev, use an autosummary
$firstrev = $oldrev->getNext();
- if ( $firstrev->getId() == $undo ) {
+ if ( $firstrev && $firstrev->getId() == $undo ) {
$undoSummary = wfMsgForContent( 'undo-summary', $undo, $undorev->getUserText() );
if ( $this->summary === '' ) {
$this->summary = $undoSummary;
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;
}
/**
*/
private function getOriginalContent() {
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->makeEmptyContent();
}
- return $this->mArticle->getContent();
+ $content = $revision->getContent();
+ 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.WD
* @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->makeEmptyContent();
} 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.WD
*/
public function setPreloadedText( $text ) {
- $this->mPreloadText = $text;
+ wfDeprecated( __METHOD__, "1.WD" );
+
+ $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
+ *
+ * @since 1.WD
+ */
+ public function setPreloadedContent( Content $content ) {
+ $this->mPreloadedContent = $content;
}
/**
* an earlier setPreloadText() or by loading the given page.
*
* @param $preload String: representing the title to preload from.
+ *
* @return String
+ *
+ * @deprecated since 1.WD, use getPreloadedContent() instead
*/
- protected function getPreloadedText( $preload ) {
- global $wgUser, $wgParser;
+ protected function getPreloadedText( $preload ) { #NOTE: B/C only, replace usage!
+ wfDeprecated( __METHOD__, "1.WD" );
+
+ $content = $this->getPreloadedContent( $preload );
+ $text = $content->serialize( $this->content_format ); #XXX: really use serialized form? use ContentHandler::getContentText() instead?!
+
+ return $text;
+ }
+
+ /**
+ * Get the contents to be preloaded into the box, either set by
+ * an earlier setPreloadText() or by loading the given page.
+ *
+ * @param $preload String: representing the title to preload from.
+ *
+ * @return Content
+ *
+ * @since 1.WD
+ */
+ protected function getPreloadedContent( $preload ) { #@todo: use this!
+ global $wgUser;
- if ( !empty( $this->mPreloadText ) ) {
- return $this->mPreloadText;
+ if ( !empty( $this->mPreloadContent ) ) {
+ return $this->mPreloadContent;
}
+ $handler = ContentHandler::getForTitle( $this->getTitle() );
+
if ( $preload === '' ) {
- return '';
+ return $handler->makeEmptyContent();
}
$title = Title::newFromText( $preload );
# Check for existence to avoid getting MediaWiki:Noarticletext
if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
- return '';
+ return $handler->makeEmptyContent();
}
$page = WikiPage::factory( $title );
$title = $page->getRedirectTarget();
# Same as before
if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
- return '';
+ return $handler->makeEmptyContent();
}
$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 );
}
/**
return true;
case self::AS_HOOK_ERROR:
- 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'] : '';
* AS_CONTENT_TOO_BIG and AS_BLOCKED_PAGE_FOR_USER. All that stuff needs to be cleaned up some time.
*/
function internalAttemptSave( &$result, $bot = false ) {
- global $wgFilterCallback, $wgUser, $wgRequest, $wgParser;
- global $wgMaxArticleSize;
+ global $wgUser, $wgRequest, $wgParser, $wgMaxArticleSize;
$status = Status::newGood();
# 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 );
wfProfileOut( __METHOD__ );
return $status;
}
- if ( $wgFilterCallback && is_callable( $wgFilterCallback ) && $wgFilterCallback( $this->mTitle, $this->textbox1, $this->section, $this->hookError, $this->summary ) ) {
- # Error messages or other handling should be performed by the filter function
- $status->setResult( false, self::AS_FILTERING );
- wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
- return $status;
- }
if ( !wfRunHooks( 'EditFilter', array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) ) ) {
# Error messages etc. could be handled within the hook...
$status->fatal( 'hookaborted' );
$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;
- }
+ // 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;
+ }
- $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;
+ $content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format );
- // Jump to the new section
- $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
+ $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 );
- // Create a link to the new section from the edit summary.
- $cleanSummary = $wgParser->stripSectionName( $this->summary );
- $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
+ // Create a link to the new section from the edit summary.
+ $cleanSummary = $wgParser->stripSectionName( $this->summary );
+ $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
+ }
}
- }
- $status->value = self::AS_SUCCESS_NEW_ARTICLE;
+ $status->value = self::AS_SUCCESS_NEW_ARTICLE;
- } else {
+ } else { # not $new
- # Article exists. Check for edit conflict.
+ # Article exists. Check for edit conflict.
- $this->mArticle->clear(); # Force reload of dates, etc.
- $timestamp = $this->mArticle->getTimestamp();
+ $this->mArticle->clear(); # Force reload of dates, etc.
+ $timestamp = $this->mArticle->getTimestamp();
- wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
+ 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.
+ 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;
- 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;
}
- }
-
- // 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" );
+ // 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 {
- $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 = null;
- // 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" );
+ $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 );
+ }
- # 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;
+ 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?
+ $this->isConflict = false;
+ $content = $textbox_content;
+ 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" );
+ }
}
- }
- # 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 ( $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 ) ) ) {
+ # 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 );
+
+ $content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format );
+
+ # 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' );
+
+ // 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 = '';
- $status->value = self::AS_SUCCESS_UPDATE;
- }
+ $status->value = self::AS_SUCCESS_UPDATE;
+ }
- // 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;
- }
+ // 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;
}
}
* @parma $editText string
*
* @return bool
+ * @deprecated since 1.WD, use mergeChangesIntoContent() instead
+ */
+ function mergeChangesInto( &$editText ){
+ wfDebug( __METHOD__, "1.WD" );
+
+ $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.WD
*/
- 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();
+
+ $handler = ContentHandler::getForModelName( $baseContent->getModelName() );
- $result = '';
- if ( wfMerge( $baseText, $editText, $currentText, $result ) ) {
- $editText = $result;
+ $result = $handler->merge3( $baseContent, $editContent, $currentContent );
+
+ if ( $result ) {
+ $editContent = $result;
wfProfileOut( __METHOD__ );
return true;
} else {
}
}
+ #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' => self::EDITFORM_ID, 'name' => self::EDITFORM_ID,
'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ),
'enctype' => 'multipart/form-data' ) ) );
$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 {
# Optional notices on a per-namespace and per-page basis
$editnotice_ns = 'editnotice-' . $this->mTitle->getNamespace();
- $editnotice_ns_message = wfMessage( $editnotice_ns )->inContentLanguage();
+ $editnotice_ns_message = wfMessage( $editnotice_ns );
if ( $editnotice_ns_message->exists() ) {
$wgOut->addWikiText( $editnotice_ns_message->plain() );
}
$editnotice_base = $editnotice_ns;
while ( count( $parts ) > 0 ) {
$editnotice_base .= '-' . array_shift( $parts );
- $editnotice_base_msg = wfMessage( $editnotice_base )->inContentLanguage();
+ $editnotice_base_msg = wfMessage( $editnotice_base );
if ( $editnotice_base_msg->exists() ) {
$wgOut->addWikiText( $editnotice_base_msg->plain() );
}
} else {
# Even if there are no subpages in namespace, we still don't want / in MW ns.
$editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->mTitle->getDBkey() );
- $editnoticeMsg = wfMessage( $editnoticeText )->inContentLanguage();
+ $editnoticeMsg = wfMessage( $editnoticeText );
if ( $editnoticeMsg->exists() ) {
$wgOut->addWikiText( $editnoticeMsg->plain() );
}
$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( $content );
+ $wikitext = $this->safeUnicodeOutput( $text );
if ( strval( $wikitext ) !== '' ) {
// Ensure there's a newline at the end, otherwise adding lines
// is awkward.
$oldtext = $this->mTitle->getDefaultMessageText();
if( $oldtext !== false ) {
$oldtitlemsg = 'defaultmessagetext';
+ $oldContent = ContentHandler::makeContent( $oldtext, $this->mTitle );
+ } else {
+ $oldContent = null;
}
} else {
- $oldtext = $this->mArticle->getRawText();
+ $oldContent = $this->getOriginalContent();
}
- $newtext = $this->mArticle->replaceSection(
- $this->section, $this->textbox1, $this->summary, $this->edittime );
+ $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 );
+
+ # 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 ) );
+
$popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
- $newtext = $wgParser->preSaveTransform( $newtext, $this->mTitle, $wgUser, $popts );
+ $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
- if ( $oldtext !== false || $newtext != '' ) {
+ if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
$oldtitle = wfMsgExt( $oldtitlemsg, array( 'parseinline' ) );
$newtitle = wfMsgExt( 'yourtext', array( 'parseinline' ) );
- $de = new DifferenceEngine( $this->mArticle->getContext() );
- $de->setText( $oldtext, $newtext );
+ $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() );
+ $de->setContent( $oldContent, $newContent );
+
$difftext = $de->getDiff( $oldtitle, $newtitle );
$de->showDiffStyle();
} else {
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->createDifferenceEngine( $this->mArticle->getContext() );
+ $de->setContent( $content2, $content1 );
$de->showDiff( wfMsgExt( 'yourtext', 'parseinline' ), wfMsg( 'storedversion' ) );
$wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
return $parsedNote;
}
- 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' ) .
- ' [[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMsg( 'continue-editing' ) . ']]';
- }
+ $note = '';
- $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
- if ( $this->mTitle->isCssJsSubpage() || !$this->mTitle->isWikitextPage() ) {
- if ( $this->mTitle->isCssJsSubpage() ) {
- $level = 'user';
- } elseif ( $this->mTitle->isCssOrJsPage() ) {
- $level = 'site';
- } else {
- $level = false;
- }
+ try {
+ $content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format );
- # Used messages to make sure grep find them:
- # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
- $class = 'mw-code';
- if ( $level ) {
- if ( preg_match( "/\\.css$/", $this->mTitle->getText() ) ) {
- $previewtext = "<div id='mw-{$level}csspreview'>\n" . wfMsg( "{$level}csspreview" ) . "\n</div>";
- $class .= " mw-css";
- } elseif ( preg_match( "/\\.js$/", $this->mTitle->getText() ) ) {
- $previewtext = "<div id='mw-{$level}jspreview'>\n" . wfMsg( "{$level}jspreview" ) . "\n</div>";
- $class .= " mw-js";
+ if ( $this->mTriedSave && !$this->mTokenOk ) {
+ if ( $this->mTokenOkExceptSuffix ) {
+ $note = wfMsg( 'token_suffix_mismatch' );
} else {
- throw new MWException( 'A CSS/JS (sub)page but which is not css nor js!' );
+ $note = wfMsg( 'session_fail_preview' );
}
- $parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions );
- $previewHTML = $parserOutput->getText();
+ } elseif ( $this->incompleteForm ) {
+ $note = wfMsg( 'edit_form_incomplete' );
} else {
- $previewHTML = '';
- }
+ $note = wfMsg( 'previewnote' ) .
+ ' [[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMsg( 'continue-editing' ) . ']]';
+ }
+
+ $parserOptions = ParserOptions::newFromUser( $wgUser );
+ $parserOptions->setEditSection( false );
+ $parserOptions->setTidy( true );
+ $parserOptions->setIsPreview( true );
+ $parserOptions->setIsSectionPreview( !is_null($this->section) && $this->section !== '' );
+
+ if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
+ # don't parse non-wikitext pages, show message about preview
+ if( $this->mTitle->isCssJsSubpage() ) {
+ $level = 'user';
+ } elseif( $this->mTitle->isCssOrJsPage() ) {
+ $level = 'site';
+ } else {
+ $level = false;
+ }
- $previewHTML .= "<pre class=\"$class\" dir=\"ltr\">\n" . htmlspecialchars( $this->textbox1 ) . "\n</pre>\n";
- } else {
- $toparse = $this->textbox1;
+ if ( $content->getModelName() == CONTENT_MODEL_CSS ) {
+ $format = 'css';
+ } elseif ( $content->getModelName() == CONTENT_MODEL_JAVASCRIPT ) {
+ $format = 'js';
+ } else {
+ $format = false;
+ }
- # 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;
+ # 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' );
}
- wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
-
- $parserOptions->enableLimitReport();
-
- $toparse = $wgParser->preSaveTransform( $toparse, $this->mTitle, $wgUser, $parserOptions );
- $parserOutput = $wgParser->parse( $toparse, $this->mTitle, $parserOptions );
+ $rt = $content->getRedirectChain();
- $rt = Title::newFromRedirectArray( $this->textbox1 );
if ( $rt ) {
$previewHTML = $this->mArticle->viewRedirect( $rt, false );
} else {
- $previewHTML = $parserOutput->getText();
- }
- $this->mParserOutput = $parserOutput;
- $wgOut->addParserOutputNoText( $parserOutput );
+ # 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 ) );
- if ( count( $parserOutput->getWarnings() ) ) {
- $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
+ $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 );
+
+ // TODO: might be a saner way to get a meaningfull context here?
+ $parserOutput = $content->getParserOutput( $this->getArticle()->getContext(), 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->isConflict ) {
$diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
if ( $this->getTitle()->getNamespace() != NS_FILE || ( isset( $diff ) && $diffOnly ) ) {
- return parent::view();
+ parent::view();
+ return;
}
$this->loadFile();
// mTitle is the same as the redirect target so ask Article
// to perform the redirect for us.
$wgRequest->setVal( 'diffonly', 'true' );
- return parent::view();
+ parent::view();
+ return;
} else {
// mTitle is not the same as the redirect target so it is
// probably the redirect page itself. Fake the redirect symbol
$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,
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
*
}
}
- // Strip off subpages
- $pagename = $this->getText();
- if ( strpos( $pagename, '/' ) !== false ) {
- list( $username , ) = explode( '/', $pagename, 2 );
- } else {
- $username = $pagename;
- }
-
if ( $wgContLang->needsGenderDistinction() &&
MWNamespace::hasGenderDistinction( $this->mNamespace ) ) {
- $gender = GenderCache::singleton()->getGenderOf( $username, __METHOD__ );
+ $gender = GenderCache::singleton()->getGenderOf( $this->getText(), __METHOD__ );
return $wgContLang->getGenderNsText( $this->mNamespace, $gender );
}
* @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.
+ *
+ * This method does *not* return true for per-user JS/CSS. Use isCssJsSubpage() for that!
+ *
+ * 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->hasContentModel( CONTENT_MODEL_CSS )
+ || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
}
/**
* @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 ) );
}
/**
* @return WikiPage|null
*/
public static function newFromID( $id ) {
- $t = Title::newFromID( $id );
- if ( $t ) {
- return self::factory( $t );
+ $dbr = wfGetDB( DB_SLAVE );
+ $row = $dbr->selectRow( 'page', self::selectFields(), array( 'page_id' => $id ), __METHOD__ );
+ if ( !$row ) {
+ return null;
}
- return null;
+ return self::newFromRow( $row );
+ }
+
+ /**
+ * Constructor from a database row
+ *
+ * @since 1.20
+ * @param $row object: database row containing at least fields returned
+ * by selectFields().
+ * @return WikiPage
+ */
+ public static function newFromRow( $row ) {
+ $page = self::factory( Title::newFromRow( $row ) );
+ $page->loadFromRow( $row );
+ return $page;
}
/**
* @return Array
*/
public function getActionOverrides() {
- return array();
+ $content_handler = $this->getContentHandler();
+ return $content_handler->getActionOverrides();
+ }
+
+ /**
+ * Returns the ContentHandler instance to be used to deal with the content of this WikiPage.
+ *
+ * Shorthand for ContentHandler::getForModelName( $this->getContentModelName() );
+ *
+ * @return ContentHandler
+ *
+ * @since 1.WD
+ */
+ public function getContentHandler() {
+ return ContentHandler::getForModelName( $this->getContentModelName() );
}
/**
'page_touched',
'page_latest',
'page_len',
+ 'page_content_model',
);
}
}
}
+ $this->loadFromRow( $data );
+ }
+
+ /**
+ * Load the object from a database row
+ *
+ * @since 1.20
+ * @param $data object: database row containing at least fields returned
+ * by selectFields()
+ */
+ public function loadFromRow( $data ) {
$lc = LinkCache::singleton();
if ( $data ) {
* @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
+
+
+ if ( empty( $content ) ) return false;
+ else return $content->isRedirect();
+ }
- return (bool)$this->mIsRedirect;
+ /**
+ * 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
+ *
+ * @since 1.WD
+ */
+ public function getContentModelName() {
+ if ( $this->exists() ) {
+ # look at the revision's actual content model
+ $rev = $this->getRevision();
+ return $rev->getContentModelName();
} else {
- return Title::newFromRedirect( $text ) !== null;
+ # use the default model for this page
+ return $this->mTitle->getContentModelName();
}
}
}
/**
- * Get the text of the current revision. No side-effects...
+ * Get the content of the current revision. No side-effects...
*
* @param $audience Integer: one of:
* Revision::FOR_PUBLIC to be displayed to all users
* Revision::FOR_THIS_USER to be displayed to $wgUser
* Revision::RAW get the text regardless of permissions
- * @return String|bool The text of the current revision. False on failure
+ * @return Content|null The content of the current revision
+ *
+ * @since 1.WD
*/
- public function getText( $audience = Revision::FOR_PUBLIC ) {
+ public function getContent( $audience = Revision::FOR_PUBLIC ) {
$this->loadLastEdit();
if ( $this->mLastRevision ) {
- return $this->mLastRevision->getText( $audience );
+ return $this->mLastRevision->getContent( $audience );
}
- return false;
+ return null;
}
/**
* Get the text of the current revision. No side-effects...
*
- * @return String|bool The text of the current revision. False on failure
+ * @param $audience Integer: one of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::RAW get the text regardless of permissions
+ * @return String|false The text of the current revision
+ * @deprecated as of 1.WD, getContent() should be used instead.
*/
- public function getRawText() {
+ public function getText( $audience = Revision::FOR_PUBLIC ) { #@todo: deprecated, replace usage!
+ wfDeprecated( __METHOD__, '1.WD' );
+
$this->loadLastEdit();
if ( $this->mLastRevision ) {
- return $this->mLastRevision->getRawText();
+ return $this->mLastRevision->getText( $audience );
}
return false;
}
+ /**
+ * Get the text of the current revision. No side-effects...
+ *
+ * @return String|bool The text of the current revision. False on failure
+ * @deprecated as of 1.WD, getContent() should be used instead.
+ */
+ public function getRawText() { #@todo: deprecated, replace usage!
+ wfDeprecated( __METHOD__, '1.WD' );
+
+ return $this->getText( Revision::RAW );
+ }
+
/**
* @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 = $editInfo->pstContent;
+ } else {
+ $content = $this->getContent();
+ }
- if ( $this->isRedirect( $text ) ) {
+ if ( !$content || $content->isRedirect( ) ) {
return false;
}
- switch ( $wgArticleCountMethod ) {
- case 'any':
- return true;
- case 'comma':
- if ( $text === false ) {
- $text = $this->getRawText();
- }
- return strpos( $text, ',' ) !== false;
- case 'link':
+ $hasLinks = null;
+
+ if ( $wgArticleCountMethod === 'link' ) {
+ # nasty special case to avoid re-parsing to detect links
+
if ( $editInfo ) {
// ParserOutput::getLinks() is a 2D array of page links, so
// to be really correct we would need to recurse in the array
// but the main array should only have items in it if there are
// links.
- return (bool)count( $editInfo->output->getLinks() );
+ $hasLinks = (bool)count( $editInfo->output->getLinks() );
} else {
- return (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
+ $hasLinks = (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
array( 'pl_from' => $this->getId() ), __METHOD__ );
}
}
+
+ return $content->isCountable( $hasLinks );
}
/**
*/
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->getContentHandler()->isParserCacheSupported();
}
/**
* @param $parserOptions ParserOptions to use for the parse operation
* @param $oldid Revision ID to get the text from, passing null or 0 will
* get the current revision (default value)
+ * @param $context IContextSource context for parsing
+ *
* @return ParserOutput or false if the revision was not found
*/
- public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
+ public function getParserOutput( ParserOptions $parserOptions, $oldid = null, IContextSource $context = null ) {
wfProfileIn( __METHOD__ );
$useParserCache = $this->isParserCacheUsed( $parserOptions, $oldid );
$oldid = $this->getLatest();
}
- $pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache );
+ $pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache, null, $context );
$pool->execute();
wfProfileOut( __METHOD__ );
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
if ( $this->mTitle->exists() ) {
- $text = $this->getRawText();
+ $text = ContentHandler::getContentText( $this->getContent() );
} 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() );
'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
'page_is_redirect' => $rt !== null ? 1 : 0,
'page_len' => $len,
+ 'page_content_model' => $revision->getContentModelName(),
),
$conditions,
__METHOD__ );
* @param $undo Revision
* @param $undoafter Revision Must be an earlier revision than $undo
* @return mixed string on success, false on failure
+ * @deprecated since 1.WD: 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();
+ wfDeprecated( __METHOD__, '1.WD' );
- if ( $cur_text == $undo_text ) {
- # No use doing a merge if it's just a straight revert.
- return $undoafter_text;
- }
+ $this->loadLastEdit();
- $undone_text = '';
+ if ( $this->mLastRevision ) {
+ if ( is_null( $undoafter ) ) {
+ $undoafter = $undo->getPrevious();
+ }
- if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) ) {
- return false;
+ $handler = $this->getContentHandler();
+ $undone = $handler->getUndoContent( $this->mLastRevision, $undo, $undoafter );
+
+ if ( !$undone ) {
+ return false;
+ } else {
+ return ContentHandler::getContentText( $undone );
+ }
}
- return $undone_text;
+ return false;
}
/**
* @param $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 String new complete article text, or null if error
+ *
+ * @deprecated since 1.WD, use replaceSectionContent() instead
*/
public function replaceSection( $section, $text, $sectionTitle = '', $edittime = null ) {
+ wfDeprecated( __METHOD__, '1.WD' );
+
+ $sectionContent = ContentHandler::makeContent( $text, $this->getTitle() ); #XXX: could make section title, but that's not required.
+
+ $newContent = $this->replaceSectionContent( $section, $sectionContent, $sectionTitle, $edittime );
+
+ return ContentHandler::getContentText( $newContent ); #XXX: unclear what will happen for non-wikitext!
+ }
+
+ /**
+ * @param $section null|bool|int or a section number (0, 1, 2, T1, T2...)
+ * @param $content Content: new content 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 Content new complete article content, or null if error
+ *
+ * @since 1.WD
+ */
+ public function replaceSectionContent( $section, Content $sectionContent, $sectionTitle = '', $edittime = null ) {
wfProfileIn( __METHOD__ );
if ( strval( $section ) == '' ) {
// Whole-page edit; let the whole text through
+ $newContent = $sectionContent;
} else {
// Bug 30711: always use current version when adding a new section
if ( is_null( $edittime ) || $section == 'new' ) {
- $oldtext = $this->getRawText();
- if ( $oldtext === false ) {
+ $oldContent = $this->getContent();
+ if ( ! $oldContent ) {
wfDebug( __METHOD__ . ": no page text\n" );
wfProfileOut( __METHOD__ );
return null;
return null;
}
- $oldtext = $rev->getText();
+ $oldContent = $rev->getContent();
}
- 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;
-
- $text = $wgParser->replaceSection( $oldtext, $section, $text );
- }
+ $newContent = $oldContent->replaceSection( $section, $sectionContent, $sectionTitle );
}
wfProfileOut( __METHOD__ );
- return $text;
+ return $newContent;
}
/**
* revision: The revision object for the inserted revision, or null
*
* Compatibility note: this function previously returned a boolean value indicating success/failure
+ *
+ * @deprecated since 1.WD: use doEditContent() instead.
*/
- public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
+ public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) { #@todo: use doEditContent() instead
+ wfDeprecated( __METHOD__, '1.WD' );
+
+ $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
+ *
+ * @since 1.WD
+ */
+ public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
+ User $user = null, $serialisation_format = null ) {
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,
+ $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,
+ $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()->unserializeContent( $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( __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 ) {
# Update links, etc.
$this->doEditUpdates( $revision, $user, array( 'created' => true ) );
- wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $text, $summary,
+ wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $serialized, $summary,
+ $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
+
+ wfRunHooks( 'ArticleContentInsertComplete', array( &$this, &$user, $content, $summary,
$flags & EDIT_MINOR, null, null, &$flags, $revision ) );
}
// 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,
+ $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
+
+ wfRunHooks( 'ArticleContentSaveComplete', array( &$this, &$user, $content, $summary,
$flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
# Promote user to any groups they meet the criteria for
/**
* Prepare text which is about to be saved.
* Returns a stdclass with source, pst and output members
- * @return bool|object
+ *
+ * @deprecated in 1.WD: use prepareContentForEdit instead.
*/
public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
+ wfDeprecated( __METHOD__, '1.WD' );
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+ return $this->prepareContentForEdit( $content, $revid , $user );
+ }
+
+ /**
+ * Prepare content which is about to be saved.
+ * Returns a stdclass with source, pst and output members
+ *
+ * @param \Content $content
+ * @param null $revid
+ * @param null|\User $user
+ * @param null $serialization_format
+ *
+ * @return bool|object
+ *
+ * @since 1.WD
+ */
+ public function prepareContentForEdit( Content $content, $revid = null, User $user = null, $serialization_format = null ) {
global $wgParser, $wgContLang, $wgUser;
$user = is_null( $user ) ? $wgUser : $user;
// @TODO fixme: check $user->getId() here???
+
if ( $this->mPreparedEdit
- && $this->mPreparedEdit->newText == $text
+ && $this->mPreparedEdit->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();
+
+ // TODO: is there no better way to obtain a context here?
+ $context = RequestContext::getMain();
+ $context->setTitle( $this->mTitle );
+ $edit->output = $edit->pstContent->getParserOutput( $context, $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;
* Purges pages that include this page if the text was changed here.
* Every 100th edit, prune the recent changes table.
*
- * @private
* @param $revision Revision object
* @param $user User object that did the revision
* @param $options Array of options, following indexes are used:
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->getSecondaryDataUpdates( $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 $user User The relevant user
* @param $comment String: comment submitted
* @param $minor Boolean: whereas it's a minor modification
+ *
+ * @deprecated since 1.WD, use doEditContent() instead.
*/
public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
+ wfDeprecated( __METHOD__, "1.WD" );
+
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+ return $this->doQuickEditContent( $content, $user, $comment , $minor );
+ }
+
+ /**
+ * Edit an article without doing all that other stuff
+ * The article must already exist; link tables etc
+ * are not updated, caches are not flushed.
+ *
+ * @param $content Content: content submitted
+ * @param $user User The relevant user
+ * @param $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,
) );
'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'
$this->updateCategoryCounts( array(), $cats );
+ #TODO: move this to an Update object!
+
# If using cascading deletes, we can skip some explicit deletes
if ( !$dbw->cascadingDeletes() ) {
$dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
}
# 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 {
/**
* Return an applicable autosummary if one exists for the given edit.
- * @param $oldtext String: the previous text of the page.
- * @param $newtext String: The submitted text of the page.
+ * @param $oldtext String|null: the previous text of the page.
+ * @param $newtext String|null: 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.WD, use ContentHandler::getAutosummary() instead
*/
public static function getAutosummary( $oldtext, $newtext, $flags ) {
- global $wgContLang;
+ # NOTE: stub for backwards-compatibility. assumes the given text is wikitext. will break horribly if it isn't.
- # Decide what kind of autosummary is needed.
+ wfDeprecated( __METHOD__, '1.WD' );
- # 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 );
- }
-
- # New page autosummaries
- if ( $flags & EDIT_NEW && strlen( $newtext ) ) {
- # If they're making a new article, give its text, truncated, in the summary.
-
- $truncatedtext = $wgContLang->truncate(
- str_replace( "\n", ' ', $newtext ),
- max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new' ) ) ) );
-
- return wfMsgForContent( 'autosumm-new', $truncatedtext );
- }
+ $handler = ContentHandler::getForModelName( CONTENT_MODEL_WIKITEXT );
+ $oldContent = is_null( $oldtext ) ? null : $handler->unserializeContent( $oldtext );
+ $newContent = is_null( $newtext ) ? null : $handler->unserializeContent( $newtext );
- # 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.WD, use ContentHandler::getAutoDeleteReason() instead
*/
public function getAutoDeleteReason( &$hasHistory ) {
+ #NOTE: stub for backwards-compatibility.
+
+ wfDeprecated( __METHOD__, '1.WD' );
+
+ $handler = ContentHandler::getForTitle( $this->getTitle() );
+ $handler->getAutoDeleteReason( $this->getTitle(), $hasHistory );
global $wgContLang;
// Get the last revision
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();
}
* @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
+ * @param $context IContextSource context for parsing
*/
- function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $text = null ) {
+ function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $content = null, IContextSource $context = 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 );
+ }
+
+ if ( is_null( $context ) ) {
+ $context = RequestContext::getMain();
+ #XXX: clone and then set title?
+ }
+
$this->page = $page;
$this->revid = $revid;
+ $this->context = $context;
$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 );
}
* @return bool
*/
function doWork() {
- global $wgParser, $wgUseFileCache;
+ global $wgUseFileCache;
+
+ // @todo: several of the methods called on $this->page are not declared in Page, but present in WikiPage and delegated by Article.
$isCurrent = $this->revid === $this->page->getLatest();
- if ( $this->text !== null ) {
- $text = $this->text;
+ if ( $this->content !== null ) {
+ $content = $this->content;
} elseif ( $isCurrent ) {
- $text = $this->page->getRawText();
+ $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 = - microtime( true );
- $this->parserOutput = $wgParser->parse( $text, $this->page->getTitle(),
- $this->parserOptions, true, true, $this->revid );
+ // TODO: page might not have this method? Hard to tell what page is supposed to be here...
+ $this->parserOutput = $content->getParserOutput( $this->context, $this->revid, $this->parserOptions );
$time += microtime( true );
# Timing hack
public function execute() {
$params = $this->extractRequestParams();
- $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
-
- if ( isset( $params['title'] ) ) {
- $titleObj = Title::newFromText( $params['title'] );
- if ( !$titleObj ) {
- $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
- }
- $pageObj = WikiPage::factory( $titleObj );
- $pageObj->loadPageData( 'fromdbmaster' );
- if ( !$pageObj->exists() ) {
- $this->dieUsageMsg( 'notanarticle' );
- }
- } elseif ( isset( $params['pageid'] ) ) {
- $pageObj = WikiPage::newFromID( $params['pageid'] );
- if ( !$pageObj ) {
- $this->dieUsageMsg( array( 'nosuchpageid', $params['pageid'] ) );
- }
- $titleObj = $pageObj->getTitle();
+ $titleObj = $this->getTitleOrPageId( $params );
+ $pageObj = WikiPage::factory( $titleObj );
+ $pageObj->loadPageData( 'fromdbmaster' );
+ if ( !$pageObj->exists() ) {
+ $this->dieUsageMsg( 'notanarticle' );
}
$reason = ( isset( $params['reason'] ) ? $params['reason'] : null );
// 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() ) );
}
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(),
- $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ),
+ $this->getTitleOrPageIdErrorMessage(),
array(
array( 'invalidtitle', 'title' ),
array( 'nosuchpageid', 'pageid' ),
$this->dieUsageMsg( 'missingtext' );
}
- $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'] ) );
- }
+ $titleObj = $this->getTitleOrPageId( $params );
+ if ( $titleObj->isExternal() ) {
+ $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
$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 );
case EditPage::AS_SPAM_ERROR:
$this->dieUsageMsg( array( 'spamdetected', $result['spam'] ) );
- case EditPage::AS_FILTERING:
- $this->dieUsageMsg( 'filtered' );
-
case EditPage::AS_BLOCKED_PAGE_FOR_USER:
$this->dieUsageMsg( 'blockedtext' );
global $wgMaxArticleSize;
return array_merge( parent::getPossibleErrors(),
- $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ),
+ $this->getTitleOrPageIdErrorMessage(),
array(
array( 'nosuchpageid', 'pageid' ),
array( 'missingtext' ),
array( 'noimageredirect-logged' ),
array( 'spamdetected', 'spam' ),
array( 'summaryrequired' ),
- array( 'filtered' ),
array( 'blockedtext' ),
array( 'contenttoobig', $wgMaxArticleSize ),
array( 'noedit-anon' ),
'sectiontitle' => 'The title for a new section',
'text' => 'Page content',
'token' => array( 'Edit token. You can get one of these through prop=info.',
- 'The token should always be sent as the last parameter, or at least, after the text parameter'
+ "The token should always be sent as the last parameter, or at least, after the {$p}text parameter"
),
- 'summary' => 'Edit summary. Also section title when section=new',
+ 'summary' => "Edit summary. Also section title when {$p}section=new and {$p}sectiontitle is not set",
'minor' => 'Minor edit',
'notminor' => 'Non-minor edit',
'bot' => 'Mark this edit as bot',
'basetimestamp' => array( 'Timestamp of the base revision (obtained through prop=revisions&rvprop=timestamp).',
- 'Used to detect edit conflicts; leave unset to ignore conflicts.'
+ 'Used to detect edit conflicts; leave unset to ignore conflicts'
),
'starttimestamp' => array( 'Timestamp when you obtained the edit token.',
'Used to detect edit conflicts; leave unset to ignore conflicts'
'md5' => array( "The MD5 hash of the {$p}text parameter, or the {$p}prependtext and {$p}appendtext parameters concatenated.",
'If set, the edit won\'t be done unless the hash is correct' ),
'prependtext' => "Add this text to the beginning of the page. Overrides {$p}text",
- 'appendtext' => "Add this text to the end of the page. Overrides {$p}text",
+ 'appendtext' => array( "Add this text to the end of the page. Overrides {$p}text.",
+ "Use {$p}section=new to append a new section" ),
'undo' => "Undo this revision. Overrides {$p}text, {$p}prependtext and {$p}appendtext",
'undoafter' => 'Undo all revisions from undo to this one. If not set, just undo one revision',
'redirect' => 'Automatically resolve redirects',
return;
}
// Not cached (save or load)
- $p_result = $wgParser->parse( $params['pst'] ? $this->pstText : $this->text, $titleObj, $popts );
+ $p_result = $wgParser->parse( $params['pst'] ? $this->pstText : $this->text, $titleObj, $popts ); #FIXME: use Content object¡
}
$result_array = array();
$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 );
// Try the parser cache first
// getParserOutput will save to Parser cache if able
$pout = $page->getParserOutput( $popts );
+ if ( !$pout ) {
+ $this->dieUsage( "There is no revision ID {$page->getLatest()}", 'missingrev' );
+ }
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 );
*/
protected function getContent( $title ) {
if ( $title->getNamespace() === NS_MEDIAWIKI ) {
- $message = wfMessage( $title->getDBkey() )->inContentLanguage();
- return $message->exists() ? $message->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 );
+ return $text === false ? '' : $text;
}
if ( !$title->isCssJsSubpage() && !$title->isCssOrJsPage() ) {
return null;
if ( !$revision ) {
return null;
}
- return $revision->getRawText();
+ return $revision->getRawText(); #FIXME: get raw data from content object after checking the type;
}
/* Methods */
'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.',
'parser-template-loop-warning' => 'Template loop detected: [[$1]]',
'parser-template-recursion-depth-warning' => 'Template recursion depth limit exceeded ($1)',
'language-converter-depth-warning' => 'Language converter depth limit exceeded ($1)',
+ 'node-count-exceeded-category' => 'Pages where node-count is exceeded',
+ 'node-count-exceeded-warning' => 'Page exceeded the node-count',
+ 'expansion-depth-exceeded-category' => 'Pages where expansion depth is exceeded',
+ 'expansion-depth-exceeded-warning' => 'Page exceeded the expansion depth',
# "Undo" feature
'undo-success' => 'The edit can be undone.
* @param $id int The page_id of the redirect
*/
private function fixRedirect( $id ) {
- $title = Title::newFromID( $id );
+ $page = WikiPage::newFromID( $id );
$dbw = wfGetDB( DB_MASTER );
- if ( is_null( $title ) ) {
+ if ( $page === null ) {
// This page doesn't exist (any more)
// Delete any redirect table entry for it
$dbw->delete( 'redirect', array( 'rd_from' => $id ),
return;
}
- $page = WikiPage::factory( $title );
$rt = $page->getRedirectTarget();
if ( $rt === null ) {
- // $title is not a redirect
+ // The page is not a redirect
// Delete any redirect table entry for it
$dbw->delete( 'redirect', array( 'rd_from' => $id ),
__METHOD__ );
public static function fixLinksFromArticle( $id ) {
global $wgParser, $wgContLang;
- $title = Title::newFromID( $id );
- $dbw = wfGetDB( DB_MASTER );
+ $page = WikiPage::newFromID( $id );
LinkCache::singleton()->clear();
- if ( is_null( $title ) ) {
+ if ( $page === null ) {
return;
}
- $revision = Revision::newFromTitle( $title );
- if ( !$revision ) {
- $text = $page->getRawText();
- if ( $text === false ) {
++ $content = $page->getContent( REVISION::RAW );
++ if ( null === false ) {
return;
}
+ $dbw = wfGetDB( DB_MASTER );
$dbw->begin( __METHOD__ );
$options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
- $parserOutput = $wgParser->parse( $revision->getText(), $title, $options, true, true, $revision->getId() );
- $parserOutput = $wgParser->parse( $text, $page->getTitle(), $options, true, true, $page->getLatest() );
- $update = new LinksUpdate( $page->getTitle(), $parserOutput, false );
- $update->doUpdate();
++ $context = RequestContext::getMain();
++
++ $parserOutput = $content->getParserOutput( $context, $page->getLatest(), $options, false );
+
- $updates = $parserOutput->getSecondaryDataUpdates( $title, false );
- SecondaryDataUpdate::runUpdates( $updates );
++ $updates = $parserOutput->getSecondaryDataUpdates( $page->getTitle(), 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__ );
}
require_once( "$IP/maintenance/Maintenance.php" );
class PHPUnitMaintClass extends Maintenance {
+
+ function __construct() {
+ parent::__construct();
+ $this->addOption( 'with-phpunitdir'
+ , 'Directory to include PHPUnit from, for example when using a git fetchout from upstream. Path will be prepended to PHP `include_path`.'
+ , false # not required
+ , true # need arg
+ );
+ }
+
public function finalSetup() {
parent::finalSetup();
$wgLocalisationCacheConf['storeClass'] = 'LCStore_Null';
}
- public function execute() { }
+
+ public function execute() {
+ global $IP;
+
+ # Make sure we have --configuration or PHPUnit might complain
+ if( !in_array( '--configuration', $_SERVER['argv'] ) ) {
+ //Hack to eliminate the need to use the Makefile (which sucks ATM)
+ array_splice( $_SERVER['argv'], 1, 0,
+ array( '--configuration', $IP . '/tests/phpunit/suite.xml' ) );
+ }
+
+ # --with-phpunitdir let us override the default PHPUnit version
+ if( $phpunitDir = $this->getOption( 'with-phpunitdir' ) ) {
+ # Sanity checks
+ if( !is_dir($phpunitDir) ) {
+ $this->error( "--with-phpunitdir should be set to an existing directory", 1 );
+ }
+ if( !is_readable( $phpunitDir."/PHPUnit/Runner/Version.php" ) ) {
+ $this->error( "No usable PHPUnit installation in $phpunitDir.\nAborting.\n", 1 );
+ }
+
+ # Now prepends provided PHPUnit directory
+ $this->output( "Will attempt loading PHPUnit from `$phpunitDir`\n" );
+ set_include_path( $phpunitDir
+ . PATH_SEPARATOR . get_include_path() );
+
+ # Cleanup $args array so the option and its value do not
+ # pollute PHPUnit
+ $key = array_search( '--with-phpunitdir', $_SERVER['argv'] );
+ unset( $_SERVER['argv'][$key] ); // the option
+ unset( $_SERVER['argv'][$key+1] ); // its value
+ $_SERVER['argv'] = array_values( $_SERVER['argv'] );
+
+ }
+ }
+
public function getDbType() {
return Maintenance::DB_ADMIN;
}
$maintClass = 'PHPUnitMaintClass';
require( RUN_MAINTENANCE_IF_MAIN );
- if( !in_array( '--configuration', $_SERVER['argv'] ) ) {
- //Hack to eliminate the need to use the Makefile (which sucks ATM)
- array_splice( $_SERVER['argv'], 1, 0,
- array( '--configuration', $IP . '/tests/phpunit/suite.xml' ) );
- }
-
require_once( 'PHPUnit/Runner/Version.php' );
- if( version_compare( PHPUnit_Runner_Version::id(), '3.5.0', '<' ) ) {
+
+ if( PHPUnit_Runner_Version::id() !== '@package_version@'
+ && version_compare( PHPUnit_Runner_Version::id(), '3.5.0', '<' ) ) {
die( 'PHPUnit 3.5 or later required, you have ' . PHPUnit_Runner_Version::id() . ".\n" );
}
require_once( 'PHPUnit/Autoload.php' );
require_once( "$IP/tests/TestsAutoLoader.php" );
MediaWikiPHPUnitCommand::main();
-