* (bug 35685) api.php URL and other entry point URLs are now listed on
Special:Version
* Edit notices can now be translated.
- * jQuery upgraded to 1.8.
+ * (bug 35680) jQuery upgraded to 1.7.2.
* jQuery UI upgraded to 1.8.22.
* (bug 35705) QUnit upgraded from v1.2.0 to v1.8.0.
* (bug 37604) jquery.cookie upgraded to 2011 version.
* Added new function getDomain to AuthPlugin for getting a user's domain
* (bug 23427) New magic word {{PAGEID}} which gives the current page ID.
Will be null on previewing a page being created.
+* Added new hook AfterFinalPageOutput to allow modifications to buffered page output before sent
+ to the client.
* (bug 37627) UserNotLoggedIn() exception to show a generic error page whenever
a user is not logged in.
* Watched status in changes lists are no longer indicated by <strong></strong>
* (bug 39376) jquery.form upgraded to 3.14
* SVG files will now show the actual width in the SVG's specified units
in the metadata box.
+ * Added ResourceLoader module "jquery.jStorage".
=== Bug fixes in 1.20 ===
* (bug 30245) Use the correct way to construct a log page title.
'UnlistedSpecialPage' => 'includes/SpecialPage.php',
'UploadSourceAdapter' => 'includes/Import.php',
'UppercaseCollation' => 'includes/Collation.php',
+ 'Uri' => 'includes/Uri.php',
'User' => 'includes/User.php',
'UserArray' => 'includes/UserArray.php',
'UserArrayFromResult' => 'includes/UserArray.php',
'ZipDirectoryReader' => 'includes/ZipDirectoryReader.php',
'ZipDirectoryReaderError' => 'includes/ZipDirectoryReader.php',
+ # content handler
+ 'Content' => 'includes/Content.php',
+ 'AbstractContent' => 'includes/Content.php',
+ 'ContentHandler' => 'includes/ContentHandler.php',
+ 'CssContent' => 'includes/Content.php',
+ 'TextContentHandler' => 'includes/ContentHandler.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',
'TestFileIterator' => 'tests/testHelpers.inc',
'TestRecorder' => 'tests/testHelpers.inc',
-
+ # tests/phpunit
+ 'RevisionStorageTest' => 'tests/phpunit/includes/RevisionStorageTest.php',
+ 'WikiPageTest' => 'tests/phpunit/includes/WikiPageTest.php',
+ '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/phpunit/includes
'GenericArrayObjectTest' => 'tests/phpunit/includes/libs/GenericArrayObjectTest.php',
*/
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->getContentModel();
+
+ $handler = ContentHandler::getForModelID( $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() ) ) );
}
}
+ $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->getModelID() ); #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
+ * @todo: deprecated, replace usage everywhere
*/
- function getContent( $def_text = '' ) {
- global $wgOut, $wgRequest, $wgParser;
+ function getContent( $def_text = false ) {
+ 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 );
+
+ // Note: EditPage should only be used with text based content anyway.
+ return $content->serialize( $this->content_format );
+ }
+
+ private function getContentObject( $def_content = null ) {
+ 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 {
wfMessage( 'undo-' . $undoMsg )->plain() . '</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()->getContentModel();
+ $handler = ContentHandler::getForModelID( $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()->getContentModel();
+ $handler = ContentHandler::getForModelID( $this->content_model );
+
+ return $handler->makeEmptyContent();
} else {
- return $text;
+ # nasty side-effect, but needed for consistency
+ $this->content_model = $rev->getContentModel();
+ $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 ) { #NOTE: B/C only, replace usage!
+ wfDeprecated( __METHOD__, "1.WD" );
+
+ $content = $this->getPreloadedContent( $preload );
+ $text = $content->serialize( $this->content_format );
+
+ 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 getPreloadedText( $preload ) {
- global $wgUser, $wgParser;
+ 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 );
}
/**
case self::AS_HOOK_ERROR:
return false;
+ case self::AS_PARSE_ERROR:
+ $wgOut->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>');
+ return true;
+
case self::AS_SUCCESS_NEW_ARTICLE:
$query = $resultDetails['redirect'] ? 'redirect=no' : '';
$anchor = isset ( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
return $status;
}
+ try {
+ # Construct Content object
+ $textbox_content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(),
+ $this->content_model, $this->content_format );
+ } 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;
+ }
+
# Check image redirect
if ( $this->mTitle->getNamespace() == NS_FILE &&
- Title::newFromRedirect( $this->textbox1 ) instanceof Title &&
+ $textbox_content->isRedirect() &&
!$wgUser->isAllowed( 'upload' ) ) {
$code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
$status->setResult( false, $code );
return $status;
}
- $text = $this->textbox1;
+ $content = $textbox_content;
+
$result['sectionanchor'] = '';
if ( $this->section == 'new' ) {
if ( $this->sectiontitle !== '' ) {
// Insert the section title above the content.
- $text = wfMessage( 'newsectionheaderdefaultlevel', $this->sectiontitle )
- ->inContentLanguage()->text() . "\n\n" . $text;
+ $content = $content->addSectionHeader( $this->sectiontitle );
// Jump to the new section
$result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
if ( $this->summary === '' ) {
$cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
$this->summary = wfMessage( 'newsectionsummary', $cleanSectionTitle )
- ->inContentLanguage()->text();
+ ->inContentLanguage()->text() ;
}
} elseif ( $this->summary !== '' ) {
// Insert the section title above the content.
- $text = wfMessage( 'newsectionheaderdefaultlevel', $this->summary )
- ->inContentLanguage()->text() . "\n\n" . $text;
+ $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 = wfMessage( 'newsectionsummary', $cleanSummary )->inContentLanguage()->text() ;
+ $this->summary = wfMessage( 'newsectionsummary', $cleanSummary )
+ ->inContentLanguage()->text();
}
}
$status->value = self::AS_SUCCESS_NEW_ARTICLE;
- } else {
+ } else { # not $new
# Article exists. Check for edit conflict.
+
+ $this->mArticle->clear(); # Force reload of dates, etc.
$timestamp = $this->mArticle->getTimestamp();
+
wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
if ( $timestamp != $this->edittime ) {
$sectionTitle = $this->summary;
}
+ $content = null;
+
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 );
+ 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" );
- $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle );
+ wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
+ $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle );
}
- if ( is_null( $text ) ) {
+
+ if ( is_null( $content ) ) {
wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
$this->isConflict = true;
- $text = $this->textbox1; // do not try to merge here!
+ $content = $textbox_content; // do not try to merge here!
} elseif ( $this->isConflict ) {
# Attempt merge
- if ( $this->mergeChangesInto( $text ) ) {
+ 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;
+ #$this->textbox1 = $text; #redundant, nothing to do here?
wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
}
}
}
// Run post-section-merge edit filter
- if ( !wfRunHooks( 'EditFilterMerged', array( $this, $text, &$this->hookError, $this->summary ) ) ) {
+ $hook_args = array( $this, $content, &$this->hookError, $this->summary );
+
+ if ( !ContentHandler::runLegacyHooks( 'EditFilterMerged', $hook_args )
+ || !wfRunHooks( 'EditFilterMergedContent', $hook_args ) ) {
# Error messages etc. could be handled within the hook...
$status->fatal( 'hookaborted' );
$status->value = self::AS_HOOK_ERROR;
# 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
+ && !$content->equals( $this->getOriginalContent() )
+ && !$content->isRedirect() ) # check if it's not a redirect
{
if ( md5( $this->summary ) == $this->autoSumm ) {
$this->missingSummary = true;
// 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->textbox1 = $content->serialize( $this->content_format );
$this->section = '';
$status->value = self::AS_SUCCESS_UPDATE;
}
// Check for length errors again now that the section is merged in
- $this->kblength = (int)( strlen( $text ) / 1024 );
+ $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 );
( ( $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;
+ $result['redirect'] = $content->isRedirect();
$this->commitWatch();
wfProfileOut( __METHOD__ );
return $status;
* @param $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 );
+ 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();
- $result = '';
- if ( wfMerge( $baseText, $editText, $currentText, $result ) ) {
- $editText = $result;
+ $handler = ContentHandler::getForModelID( $baseContent->getModel() );
+
+ $result = $handler->merge3( $baseContent, $editContent, $currentContent );
+
+ if ( $result ) {
+ $editContent = $result;
wfProfileOut( __METHOD__ );
return true;
} else {
}
}
+ //@todo: add EditForm plugin interface and use it here!
+ // 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 {
Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) );
if ( $this->isConflict ) {
- $this->showConflict();
+ try {
+ $this->showConflict();
+ } catch ( MWContentSerializationException $ex ) {
+ // this can't really happen, but be nice if it does.
+ $msg = wfMessage( 'content-failed-to-parse', $this->content_model, $this->content_format, $ex->getMessage() );
+ $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>');
+ }
}
$wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
if ( $this->section != '' && $this->section != 'new' ) {
if ( !$this->summary && !$this->preview && !$this->diff ) {
- $sectionTitle = self::extractSectionTitle( $this->textbox1 );
+ $sectionTitle = self::extractSectionTitle( $this->textbox1 ); //FIXME: use Content object
if ( $sectionTitle !== false ) {
$this->summary = "/* $sectionTitle */ ";
}
);
}
}
+ # Add header copyright warning
+ $this->showHeaderCopyrightWarning();
}
+
/**
* Standard summary input and label (wgSummary), abstracted so EditPage
* subclasses may reorganize the form.
$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.
$wgOut->addHTML( '</div>' );
if ( $this->formtype == 'diff' ) {
- $this->showDiff();
+ try {
+ $this->showDiff();
+ } catch ( MWContentSerializationException $ex ) {
+ $msg = wfMessage( 'content-failed-to-parse', $this->content_model, $this->content_format, $ex->getMessage() );
+ $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>');
+ }
}
}
$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 );
- wfRunHooks( 'EditPageGetDiffText', array( $this, &$newtext ) );
+ $textboxContent = ContentHandler::makeContent( $this->textbox1, $this->getTitle(),
+ $this->content_model, $this->content_format );
+
+ $newContent = $this->mArticle->replaceSectionContent(
+ $this->section, $textboxContent,
+ $this->summary, $this->edittime );
+
+ ContentHandler::runLegacyHooks( 'EditPageGetDiffText', array( $this, &$newContent ) );
+ 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 = wfMessage( $oldtitlemsg )->parse();
$newtitle = wfMessage( 'yourtext' )->parse();
- $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 {
$wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
}
+ /**
+ * Show the header copyright warning.
+ */
+ protected function showHeaderCopyrightWarning() {
+ $msg = 'editpage-head-copy-warn';
+ if ( !wfMessage( $msg )->isDisabled() ) {
+ global $wgOut;
+ $wgOut->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
+ 'editpage-head-copy-warn' );
+ }
+ }
+
/**
* Give a chance for site and per-namespace customizations of
* terms of service summary link that might exist separately
// Allow for site and per-namespace customization of contribution/copyright notice.
wfRunHooks( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) );
+ $msg = call_user_func_array( "wfMessage", $copywarnMsg );
return "<div id=\"editpage-copywarn\">\n" .
- call_user_func_array( "wfMsgNoTrans", $copywarnMsg ) . "\n</div>";
+ $msg->plain() . "\n</div>";
}
protected function showStandardInputs( &$tabindex = 2 ) {
if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
$wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
- $de = new DifferenceEngine( $this->mArticle->getContext() );
- $de->setText( $this->textbox2, $this->textbox1 );
- $de->showDiff(
+ $content1 = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format );
+ $content2 = ContentHandler::makeContent( $this->textbox2, $this->getTitle(), $this->content_model, $this->content_format );
+
+ $handler = ContentHandler::getForModelID( $this->content_model );
+ $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
+ $de->setContent( $content2, $content1 );
+ $de->showDiff(
wfMessage( 'yourtext' )->parse(),
wfMessage( 'storedversion' )->text()
);
return $parsedNote;
}
- if ( $this->mTriedSave && !$this->mTokenOk ) {
- if ( $this->mTokenOkExceptSuffix ) {
- $note = wfMessage( 'token_suffix_mismatch' )->plain();
- } else {
- $note = wfMessage( 'session_fail_preview' )->plain();
- }
- } elseif ( $this->incompleteForm ) {
- $note = wfMessage( 'edit_form_incomplete' )->plain();
- } else {
- $note = wfMessage( 'previewnote' )->plain() .
- ' [[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMessage( 'continue-editing' )->text() . ']]';
- }
+ $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;
- }
-
- # 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" . wfMessage( "{$level}csspreview" )->text() . "\n</div>";
- $class .= " mw-css";
- } elseif ( preg_match( "/\\.js$/", $this->mTitle->getText() ) ) {
- $previewtext = "<div id='mw-{$level}jspreview'>\n" . wfMessage( "{$level}jspreview" )->text() . "\n</div>";
- $class .= " mw-js";
+ try {
+ $content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(),
+ $this->content_model, $this->content_format );
+
+ if ( $this->mTriedSave && !$this->mTokenOk ) {
+ if ( $this->mTokenOkExceptSuffix ) {
+ $note = wfMessage( 'token_suffix_mismatch' )->plain() ;
} else {
- throw new MWException( 'A CSS/JS (sub)page but which is not css nor js!' );
+ $note = wfMessage( 'session_fail_preview' )->plain() ;
}
- $parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions );
- $previewHTML = $parserOutput->getText();
+ } elseif ( $this->incompleteForm ) {
+ $note = wfMessage( 'edit_form_incomplete' )->plain() ;
} else {
- $previewHTML = '';
- }
+ $note = wfMessage( 'previewnote' )->plain() .
+ ' [[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMessage( 'continue-editing' )->text() . ']]';
+ }
+
+ $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->isCssOrJsPage() ) {
+ 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->getModel() == CONTENT_MODEL_CSS ) {
+ $format = 'css';
+ } elseif ( $content->getModel() == 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 = wfMessage( 'newsectionheaderdefaultlevel', $this->summary )->inContentLanguage()->text() . "\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'>" . wfMessage( "{$level}{$format}preview" )->text() . "</div>";
+ } else {
+ $note = wfMessage( 'previewnote' )->text() ;
+ }
+ } else {
+ $note = wfMessage( 'previewnote' )->text() ;
}
- wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
+ $rt = $content->getRedirectChain();
- $parserOptions->enableLimitReport();
-
- $toparse = $wgParser->preSaveTransform( $toparse, $this->mTitle, $wgUser, $parserOptions );
- $parserOutput = $wgParser->parse( $toparse, $this->mTitle, $parserOptions );
-
- $rt = Title::newFromRedirectArray( $this->textbox1 );
if ( $rt ) {
$previewHTML = $this->mArticle->viewRedirect( $rt, false );
} else {
- $previewHTML = $parserOutput->getText();
- }
- $this->mParserOutput = $parserOutput;
- $wgOut->addParserOutputNoText( $parserOutput );
+ # If 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 );
+ }
+
+ $hook_args = array( $this, &$content );
+ ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
+ wfRunHooks( 'EditPageGetPreviewContent', $hook_args );
+
+ $parserOptions->enableLimitReport();
+
+ # For CSS/JS pages, we should have called the ShowRawCssJs hook here.
+ # But it's now deprecated, so never mind
- if ( count( $parserOutput->getWarnings() ) ) {
- $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
+ $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
+ $parserOutput = $content->getParserOutput( $this->getArticle()->getTitle(), 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) {
+ $m = wfMessage('content-failed-to-parse', $this->content_model, $this->content_format, $ex->getMessage() );
+ $note .= "\n\n" . $m->parse();
+ $previewHTML = '';
}
if ( $this->isConflict ) {
$cgi = '';
foreach ( $array1 as $key => $value ) {
- if ( !is_null($value) && $value !== false ) {
+ if ( $value !== false ) {
if ( $cgi != '' ) {
$cgi .= '&';
}
} else {
if ( is_object( $value ) ) {
$value = $value->__toString();
+ } elseif( !is_null( $value ) ) {
+ $cgi .= urlencode( $key ) . '=' . urlencode( $value );
+ } else {
+ $cgi .= urlencode( $key );
}
- $cgi .= urlencode( $key ) . '=' . urlencode( $value );
}
}
}
continue;
}
if ( strpos( $bit, '=' ) === false ) {
- // Pieces like &qwerty become 'qwerty' => '' (at least this is what php does)
- $key = $bit;
- $value = '';
+ // Pieces like &qwerty become 'qwerty' => null
+ $key = urldecode( $bit );
+ $value = null;
} else {
list( $key, $value ) = explode( '=', $bit );
+ $key = urldecode( $key );
+ $value = urldecode( $value );
}
- $key = urldecode( $key );
- $value = urldecode( $value );
+
if ( strpos( $key, '[' ) !== false ) {
$keys = array_reverse( explode( '[', $key ) );
$key = array_pop( $keys );
* Append a query string to an existing URL, which may or may not already
* have query string parameters already. If so, they will be combined.
*
+ * @deprecated in 1.20. Use Uri class.
* @param $url String
* @param $query Mixed: string or associative array
* @return string
*/
function wfAppendQuery( $url, $query ) {
- if ( is_array( $query ) ) {
- $query = wfArrayToCgi( $query );
- }
- if( $query != '' ) {
- if( false === strpos( $url, '?' ) ) {
- $url .= '?';
- } else {
- $url .= '&';
- }
- $url .= $query;
- }
- return $url;
+ $obj = new Uri( $url );
+ $obj->extendQuery( $query );
+ return $obj->toString();
}
/**
* @todo Need to integrate this into wfExpandUrl (bug 32168)
*
* @since 1.19
+ * @deprecated
* @param $urlParts Array URL parts, as output from wfParseUrl
* @return string URL assembled from its component parts
*/
function wfAssembleUrl( $urlParts ) {
- $result = '';
-
- if ( isset( $urlParts['delimiter'] ) ) {
- if ( isset( $urlParts['scheme'] ) ) {
- $result .= $urlParts['scheme'];
- }
-
- $result .= $urlParts['delimiter'];
- }
-
- if ( isset( $urlParts['host'] ) ) {
- if ( isset( $urlParts['user'] ) ) {
- $result .= $urlParts['user'];
- if ( isset( $urlParts['pass'] ) ) {
- $result .= ':' . $urlParts['pass'];
- }
- $result .= '@';
- }
-
- $result .= $urlParts['host'];
-
- if ( isset( $urlParts['port'] ) ) {
- $result .= ':' . $urlParts['port'];
- }
- }
-
- if ( isset( $urlParts['path'] ) ) {
- $result .= $urlParts['path'];
- }
-
- if ( isset( $urlParts['query'] ) ) {
- $result .= '?' . $urlParts['query'];
- }
-
- if ( isset( $urlParts['fragment'] ) ) {
- $result .= '#' . $urlParts['fragment'];
- }
-
- return $result;
+ $obj = new Uri( $urlParts );
+ return $obj->toString();
}
/**
* 2) Handles protocols that don't use :// (e.g., mailto: and news: , as well as protocol-relative URLs) correctly
* 3) Adds a "delimiter" element to the array, either '://', ':' or '//' (see (2))
*
+ * @deprecated
* @param $url String: a URL to parse
* @return Array: bits of the URL in an associative array, per PHP docs
*/
function wfParseUrl( $url ) {
- global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
-
- // Protocol-relative URLs are handled really badly by parse_url(). It's so bad that the easiest
- // way to handle them is to just prepend 'http:' and strip the protocol out later
- $wasRelative = substr( $url, 0, 2 ) == '//';
- if ( $wasRelative ) {
- $url = "http:$url";
- }
- wfSuppressWarnings();
- $bits = parse_url( $url );
- wfRestoreWarnings();
- // parse_url() returns an array without scheme for some invalid URLs, e.g.
- // parse_url("%0Ahttp://example.com") == array( 'host' => '%0Ahttp', 'path' => 'example.com' )
- if ( !$bits || !isset( $bits['scheme'] ) ) {
- return false;
- }
-
- // most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
- if ( in_array( $bits['scheme'] . '://', $wgUrlProtocols ) ) {
- $bits['delimiter'] = '://';
- } elseif ( in_array( $bits['scheme'] . ':', $wgUrlProtocols ) ) {
- $bits['delimiter'] = ':';
- // parse_url detects for news: and mailto: the host part of an url as path
- // We have to correct this wrong detection
- if ( isset( $bits['path'] ) ) {
- $bits['host'] = $bits['path'];
- $bits['path'] = '';
- }
- } else {
- return false;
- }
-
- /* Provide an empty host for eg. file:/// urls (see bug 28627) */
- if ( !isset( $bits['host'] ) ) {
- $bits['host'] = '';
-
- /* parse_url loses the third / for file:///c:/ urls (but not on variants) */
- if ( substr( $bits['path'], 0, 1 ) !== '/' ) {
- $bits['path'] = '/' . $bits['path'];
- }
- }
-
- // If the URL was protocol-relative, fix scheme and delimiter
- if ( $wasRelative ) {
- $bits['scheme'] = '';
- $bits['delimiter'] = '//';
- }
- return $bits;
+ $obj = new Uri( $url );
+ return $obj->getComponents();
}
/**
} else {
$d = date_create( "now", $logDBErrorTimeZoneObject );
}
+ $date = $d->format( 'D M j G:i:s T Y' );
+ $date = $d->format( 'D M j G:i:s T Y' );
+
$text = "$date\t$host\t$wiki\t$text";
wfErrorLog( $text, $wgDBerrorLog );
}
* @return null
*/
function wfDeprecated( $function, $version = false, $component = false, $callerOffset = 2 ) {
- static $functionsWarned = array();
-
- MWDebug::deprecated( $function, $version, $component );
-
- if ( !isset( $functionsWarned[$function] ) ) {
- $functionsWarned[$function] = true;
-
- if ( $version ) {
- global $wgDeprecationReleaseLimit;
-
- if ( $wgDeprecationReleaseLimit && $component === false ) {
- # Strip -* off the end of $version so that branches can use the
- # format #.##-branchname to avoid issues if the branch is merged into
- # a version of MediaWiki later than what it was branched from
- $comparableVersion = preg_replace( '/-.*$/', '', $version );
-
- # If the comparableVersion is larger than our release limit then
- # skip the warning message for the deprecation
- if ( version_compare( $wgDeprecationReleaseLimit, $comparableVersion, '<' ) ) {
- return;
- }
- }
-
- $component = $component === false ? 'MediaWiki' : $component;
- wfWarn( "Use of $function was deprecated in $component $version.", $callerOffset );
- } else {
- wfWarn( "Use of $function is deprecated.", $callerOffset );
- }
- }
+ MWDebug::deprecated( $function, $version, $component, $callerOffset + 1 );
}
/**
* is true
*/
function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
- global $wgDevelopmentWarnings;
-
- MWDebug::warning( $msg, $callerOffset + 2 );
-
- $callers = wfDebugBacktrace();
- if ( isset( $callers[$callerOffset + 1] ) ) {
- $callerfunc = $callers[$callerOffset + 1];
- $callerfile = $callers[$callerOffset];
- if ( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ) {
- $file = $callerfile['file'] . ' at line ' . $callerfile['line'];
- } else {
- $file = '(internal function)';
- }
- $func = '';
- if ( isset( $callerfunc['class'] ) ) {
- $func .= $callerfunc['class'] . '::';
- }
- if ( isset( $callerfunc['function'] ) ) {
- $func .= $callerfunc['function'];
- }
- $msg .= " [Called from $func in $file]";
- }
-
- if ( $wgDevelopmentWarnings ) {
- trigger_error( $msg, $level );
- } else {
- wfDebug( "$msg\n" );
- }
+ MWDebug::warning( $msg, $callerOffset + 1, $level );
}
/**
}
} else {
if ( !$suppressCount ) {
- // E_DEPRECATED is undefined in PHP 5.2
- if( !defined( 'E_DEPRECATED' ) ) {
- define( 'E_DEPRECATED', 8192 );
- }
- $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED ) );
+ $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED | E_USER_DEPRECATED ) );
}
++$suppressCount;
}
var $mFragment; // /< Title fragment (i.e. the bit after the #)
var $mArticleID = -1; // /< Article ID, fetched from the link cache on demand
var $mLatestID = false; // /< ID of most recent revision
+ var $mContentModel = false; // /< ID of the page's content model, i.e. one of the CONTENT_MODEL_XXX constants
private $mEstimateRevisions; // /< Estimated number of revisions; null of not loaded
var $mRestrictions = array(); // /< Array of groups allowed to edit this article
var $mOldRestrictions = false;
}
}
+ /**
+ * Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries.
+ * Uses $wgContentHandlerUseDB to determine whether to include page_content_model.
+ *
+ * @return array
+ */
+ protected static function getSelectFields() {
+ global $wgContentHandlerUseDB;
+
+ $fields = array(
+ 'page_namespace', 'page_title', 'page_id',
+ 'page_len', 'page_is_redirect', 'page_latest',
+ );
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'page_content_model';
+ }
+
+ return $fields;
+ }
+
/**
* Create a new Title from an article ID
*
$db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
$row = $db->selectRow(
'page',
- array(
- 'page_namespace', 'page_title', 'page_id',
- 'page_len', 'page_is_redirect', 'page_latest',
- ),
+ self::getSelectFields(),
array( 'page_id' => $id ),
__METHOD__
);
$res = $dbr->select(
'page',
- array(
- 'page_namespace', 'page_title', 'page_id',
- 'page_len', 'page_is_redirect', 'page_latest',
- ),
+ self::getSelectFields(),
array( 'page_id' => $ids ),
__METHOD__
);
$this->mRedirect = (bool)$row->page_is_redirect;
if ( isset( $row->page_latest ) )
$this->mLatestID = (int)$row->page_latest;
+ if ( isset( $row->page_content_model ) )
+ $this->mContentModel = strval( $row->page_content_model );
+ else
+ $this->mContentModel = false; # initialized lazily in getContentModel()
} else { // page not found
$this->mArticleID = 0;
$this->mLength = 0;
$this->mRedirect = false;
$this->mLatestID = 0;
+ $this->mContentModel = false; # initialized lazily in getContentModel()
}
}
$t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
$t->mUrlform = wfUrlencode( $t->mDbkeyform );
$t->mTextform = str_replace( '_', ' ', $title );
+ $t->mContentModel = false; # initialized lazily in getContentModel()
return $t;
}
*
* @param $text String: Text with possible redirect
* @return Title: The corresponding Title
+ * @deprecated since 1.WD, use Content::getRedirectTarget instead.
*/
public static function newFromRedirect( $text ) {
- return self::newFromRedirectInternal( $text );
+ $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
+ return $content->getRedirectTarget();
}
/**
*
* @param $text String Text with possible redirect
* @return Title
+ * @deprecated since 1.WD, use Content::getUltimateRedirectTarget instead.
*/
public static function newFromRedirectRecurse( $text ) {
- $titles = self::newFromRedirectArray( $text );
- return $titles ? array_pop( $titles ) : null;
+ $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
+ return $content->getUltimateRedirectTarget();
}
/**
*
* @param $text String Text with possible redirect
* @return Array of Titles, with the destination last
+ * @deprecated since 1.WD, use Content::getRedirectChain instead.
*/
public static function newFromRedirectArray( $text ) {
- global $wgMaxRedirects;
- $title = self::newFromRedirectInternal( $text );
- if ( is_null( $title ) ) {
- return null;
- }
- // recursive check to follow double redirects
- $recurse = $wgMaxRedirects;
- $titles = array( $title );
- while ( --$recurse > 0 ) {
- if ( $title->isRedirect() ) {
- $page = WikiPage::factory( $title );
- $newtitle = $page->getRedirectTarget();
- } else {
- break;
- }
- // Redirects to some special pages are not permitted
- if ( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
- // the new title passes the checks, so make that our current title so that further recursion can be checked
- $title = $newtitle;
- $titles[] = $newtitle;
- } else {
- break;
- }
- }
- return $titles;
- }
-
- /**
- * Really extract the redirect destination
- * Do not call this function directly, use one of the newFromRedirect* functions above
- *
- * @param $text String Text with possible redirect
- * @return Title
- */
- protected static function newFromRedirectInternal( $text ) {
- global $wgMaxRedirects;
- if ( $wgMaxRedirects < 1 ) {
- //redirects are disabled, so quit early
- return null;
- }
- $redir = MagicWord::get( 'redirect' );
- $text = trim( $text );
- if ( $redir->matchStartAndRemove( $text ) ) {
- // Extract the first link and see if it's usable
- // Ensure that it really does come directly after #REDIRECT
- // Some older redirects included a colon, so don't freak about that!
- $m = array();
- if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}!', $text, $m ) ) {
- // Strip preceding colon used to "escape" categories, etc.
- // and URL-decode links
- if ( strpos( $m[1], '%' ) !== false ) {
- // Match behavior of inline link parsing here;
- $m[1] = rawurldecode( ltrim( $m[1], ':' ) );
- }
- $title = Title::newFromText( $m[1] );
- // If the title is a redirect to bad special pages or is invalid, return null
- if ( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
- return null;
- }
- return $title;
- }
- }
- return null;
+ $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
+ return $content->getRedirectChain();
}
/**
return $this->mNamespace;
}
+ /**
+ * Get the page's content model id, see the CONTENT_MODEL_XXX constants.
+ *
+ * @return String: Content model id
+ */
+ public function getContentModel() {
+ if ( !$this->mContentModel ) {
+ $linkCache = LinkCache::singleton();
+ $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
+ }
+
+ if ( !$this->mContentModel ) {
+ $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
+ }
+
+ if( !$this->mContentModel ) {
+ throw new MWException( "failed to determin content model!" );
+ }
+
+ return $this->mContentModel;
+ }
+
+ /**
+ * Convenience method for checking a title's content model name
+ *
+ * @param int $id
+ * @return Boolean true if $this->getContentModel() == $id
+ */
+ public function hasContentModel( $id ) {
+ return $this->getContentModel() == $id;
+ }
+
/**
* Get the namespace text
*
* @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 ) );
}
/**
if ( !$this->getArticleID( $flags ) ) {
return $this->mRedirect = false;
}
+
$linkCache = LinkCache::singleton();
- $this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this, 'redirect' );
+ $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
+ if ( $cached === null ) { # check the assumption that the cache actually knows about this title
+ # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
+ # as a stop gap, perhaps log this, but don't throw an exception?
+ throw new MWException( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
+ }
+
+ $this->mRedirect = (bool)$cached;
return $this->mRedirect;
}
return $this->mLength = 0;
}
$linkCache = LinkCache::singleton();
- $this->mLength = intval( $linkCache->getGoodLinkFieldObj( $this, 'length' ) );
+ $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
+ if ( $cached === null ) { # check the assumption that the cache actually knows about this title
+ # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
+ # as a stop gap, perhaps log this, but don't throw an exception?
+ throw new MWException( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
+ }
+
+ $this->mLength = intval( $cached );
return $this->mLength;
}
return $this->mLatestID = 0;
}
$linkCache = LinkCache::singleton();
- $this->mLatestID = intval( $linkCache->getGoodLinkFieldObj( $this, 'revision' ) );
+ $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
+ if ( $cached === null ) { # check the assumption that the cache actually knows about this title
+ # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
+ # as a stop gap, perhaps log this, but don't throw an exception?
+ throw new MWException( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
+ }
+
+ $this->mLatestID = intval( $cached );
return $this->mLatestID;
}
$this->mRedirect = null;
$this->mLength = -1;
$this->mLatestID = false;
+ $this->mContentModel = false;
$this->mEstimateRevisions = null;
}
$res = $db->select(
array( 'page', $table ),
- array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
+ self::getSelectFields(),
array(
"{$prefix}_from=page_id",
"{$prefix}_namespace" => $this->getNamespace(),
* @return Array of Title objects linking here
*/
public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
+ global $wgContentHandlerUseDB;
+
$id = $this->getArticleID();
# If the page doesn't exist; there can't be any link from this page
$namespaceFiled = "{$prefix}_namespace";
$titleField = "{$prefix}_title";
+ $fields = array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' );
+ if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
+
$res = $db->select(
array( $table, 'page' ),
- array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
+ $fields,
array( "{$prefix}_from" => $id ),
__METHOD__,
$options,
* @return Bool
*/
public function isSingleRevRedirect() {
+ global $wgContentHandlerUseDB;
+
$dbw = wfGetDB( DB_MASTER );
+
# Is it a redirect?
+ $fields = array( 'page_is_redirect', 'page_latest', 'page_id' );
+ if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
+
$row = $dbw->selectRow( 'page',
- array( 'page_is_redirect', 'page_latest', 'page_id' ),
+ $fields,
$this->pageCond(),
__METHOD__,
array( 'FOR UPDATE' )
$this->mArticleID = $row ? intval( $row->page_id ) : 0;
$this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
$this->mLatestID = $row ? intval( $row->page_latest ) : false;
+ $this->mContentModel = $row && isset( $row->page_content_model ) ? strval( $row->page_content_model ) : false;
if ( !$this->mRedirect ) {
return false;
}
if( !is_object( $rev ) ){
return false;
}
- $text = $rev->getText();
+ $content = $rev->getContent();
# Does the redirect point to the source?
# Or is it a broken self-redirect, usually caused by namespace collisions?
- $m = array();
- if ( preg_match( "/\\[\\[\\s*([^\\]\\|]*)]]/", $text, $m ) ) {
- $redirTitle = Title::newFromText( $m[1] );
- if ( !is_object( $redirTitle ) ||
- ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
- $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) ) {
+ $redirTitle = $content->getRedirectTarget();
+
+ if ( $redirTitle ) {
+ if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
+ $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
wfDebug( __METHOD__ . ": redirect points to other page\n" );
return false;
+ } else {
+ return true;
}
} else {
- # Fail safe
- wfDebug( __METHOD__ . ": failsafe\n" );
+ # Fail safe (not a redirect after all. strange.)
+ wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
+ " is a redirect, but it doesn't contain a valid redirect.\n" );
return false;
}
- return true;
}
/**
return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1;
}
return ( $old->getRawUserText() === $new->getRawUserText() ) ? 1 : 2;
- }
+ }\r
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
array(
if ( $this->isSpecialPage() ) {
// special pages are in the user language
return $wgLang;
- } elseif ( $this->isCssOrJsPage() || $this->isCssJsSubpage() ) {
- // css/js should always be LTR and is, in fact, English
- return wfGetLangObj( 'en' );
} elseif ( $this->getNamespace() == NS_MEDIAWIKI ) {
// Parse mediawiki messages with correct target language
list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $this->getText() );
return wfGetLangObj( $lang );
}
- global $wgContLang;
- // If nothing special, it should be in the wiki content language
- $pageLang = $wgContLang;
+
+ //TODO: use the LinkCache to cache this!
+ //NOTE: ContentHandler::getPageLanguage() may need to load the content to determine the page language!
+ $contentHandler = ContentHandler::getForTitle( $this );
+ $pageLang = $contentHandler->getPageLanguage( $this );
+
// Hook at the end because we don't want to override the above stuff
wfRunHooks( 'PageContentLanguage', array( $this, &$pageLang, $wgLang ) );
return wfGetLangObj( $pageLang );
* @private
*/
var $mOldid, $mNewid;
- var $mOldtext, $mNewtext;
+ var $mOldContent, $mNewContent;
protected $mDiffLang;
/**
# we'll use the application/x-external-editor interface to call
# an external diff tool like kompare, kdiff3, etc.
if ( ExternalEdit::useExternalEngine( $this->getContext(), 'diff' ) ) {
+ //TODO: come up with a good solution for non-text content here.
+ // at least, the content format needs to be passed to the client somehow.
+ // Currently, action=raw will just fail for non-text content.
+
$urls = array(
'File' => array( 'Extension' => 'wiki', 'URL' =>
# This should be mOldPage, but it may not be set, see below.
$out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
$out->setArticleFlag( true );
+ // NOTE: only needed for B/C: custom rendering of JS/CSS via hook
if ( $this->mNewPage->isCssJsSubpage() || $this->mNewPage->isCssOrJsPage() ) {
// Stolen from Article::view --AG 2007-10-11
// Give hooks a chance to customise the output
// @TODO: standardize this crap into one function
- if ( wfRunHooks( 'ShowRawCssJs', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
- // Wrap the whole lot in a <pre> and don't parse
- $m = array();
- preg_match( '!\.(css|js)$!u', $this->mNewPage->getText(), $m );
- $out->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
- $out->addHTML( htmlspecialchars( $this->mNewtext ) );
- $out->addHTML( "\n</pre>\n" );
+ if ( ContentHandler::runLegacyHooks( 'ShowRawCssJs', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
+ // NOTE: deprecated hook, B/C only
+ // use the content object's own rendering
+ $po = $this->mContentObject->getParserOutput();
+ $out->addHTML( $po->getText() );
}
- } elseif ( !wfRunHooks( 'ArticleViewCustom', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
+ } elseif( !wfRunHooks( 'ArticleContentViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
+ // Handled by extension
+ } elseif( !ContentHandler::runLegacyHooks( 'ArticleViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
+ // NOTE: deprecated hook, B/C only
// Handled by extension
} else {
// Normal page
$wikiPage = WikiPage::factory( $this->mNewPage );
}
- $parserOptions = ParserOptions::newFromContext( $this->getContext() );
- $parserOptions->enableLimitReport();
- $parserOptions->setTidy( true );
-
- if ( !$this->mNewRev->isCurrent() ) {
- $parserOptions->setEditSection( false );
- }
-
- $parserOutput = $wikiPage->getParserOutput( $parserOptions, $this->mNewid );
+ $parserOutput = $this->getParserOutput( $wikiPage, $this->mNewRev );
# WikiPage::getParserOutput() should not return false, but just in case
if( $parserOutput ) {
wfProfileOut( __METHOD__ );
}
+ protected function getParserOutput( WikiPage $page, Revision $rev ) {
+ $parserOptions = ParserOptions::newFromContext( $this->getContext() );
+ $parserOptions->enableLimitReport();
+ $parserOptions->setTidy( true );
+
+ if ( !$rev->isCurrent() || !$rev->getTitle()->quickUserCan( "edit" ) ) {
+ $parserOptions->setEditSection( false );
+ }
+
+ $parserOutput = $page->getParserOutput( $parserOptions, $rev->getId() );
+ return $parserOutput;
+ }
+
/**
* Get the diff text, send it to the OutputPage object
* Returns false if the diff could not be generated, otherwise returns true
return false;
}
- $difftext = $this->generateDiffBody( $this->mOldtext, $this->mNewtext );
+ $difftext = $this->generateContentDiffBody( $this->mOldContent, $this->mNewContent );
// Save to cache for 7 days
if ( !wfRunHooks( 'AbortDiffCache', array( &$this ) ) ) {
}
}
+ /**
+ * Generate a diff, no caching.
+ *
+ * Subclasses may override this to provide a
+ *
+ * @param $old Content: old content
+ * @param $new Content: new content
+ *
+ * @since 1.WD
+ */
+ function generateContentDiffBody( Content $old, Content $new ) {
+ if ( !( $old instanceof TextContent ) ) {
+ throw new MWException( "Diff not implemented for " . get_class( $old ) . "; "
+ . "override generateContentDiffBody to fix this." );
+ }
+
+ if ( !( $new instanceof TextContent ) ) {
+ throw new MWException( "Diff not implemented for " . get_class( $new ) . "; "
+ . "override generateContentDiffBody to fix this." );
+ }
+
+ $otext = $old->serialize();
+ $ntext = $new->serialize();
+
+ return $this->generateTextDiffBody( $otext, $ntext );
+ }
+
/**
* Generate a diff, no caching
*
* @param $otext String: old text, must be already segmented
* @param $ntext String: new text, must be already segmented
- * @return bool|string
+ * @deprecated since 1.WD, use generateContentDiffBody() instead!
*/
function generateDiffBody( $otext, $ntext ) {
+ wfDeprecated( __METHOD__, "1.WD" );
+
+ return $this->generateTextDiffBody( $otext, $ntext );
+ }
+
+ /**
+ * Generate a diff, no caching
+ *
+ * @todo move this to TextDifferenceEngine, make DifferenceEngine abstract. At some point.
+ *
+ * @param $otext String: old text, must be already segmented
+ * @param $ntext String: new text, must be already segmented
+ * @return bool|string
+ */
+ function generateTextDiffBody( $otext, $ntext ) {
global $wgExternalDiffEngine, $wgContLang;
wfProfileIn( __METHOD__ );
* the visibility of the revision and a link to edit the page.
* @return String HTML fragment
*/
- private function getRevisionHeader( Revision $rev, $complete = '' ) {
+ protected function getRevisionHeader( Revision $rev, $complete = '' ) {
$lang = $this->getLanguage();
$user = $this->getUser();
$revtimestamp = $rev->getTimestamp();
if ( !$diff && !$otitle ) {
$header .= "
- <tr valign='top'>
+ <tr style='vertical-align: top;'>
<td class='diff-ntitle'>{$ntitle}</td>
</tr>";
$multiColspan = 1;
$multiColspan = 2;
}
$header .= "
- <tr valign='top'>
+ <tr style='vertical-align: top;'>
<td colspan='$colspan' class='diff-otitle'>{$otitle}</td>
<td colspan='$colspan' class='diff-ntitle'>{$ntitle}</td>
</tr>";
}
if ( $multi != '' ) {
- $header .= "<tr><td colspan='{$multiColspan}' align='center' class='diff-multi'>{$multi}</td></tr>";
+ $header .= "<tr><td colspan='{$multiColspan}' style='text-align: center;' class='diff-multi'>{$multi}</td></tr>";
}
if ( $notice != '' ) {
- $header .= "<tr><td colspan='{$multiColspan}' align='center'>{$notice}</td></tr>";
+ $header .= "<tr><td colspan='{$multiColspan}' style='text-align: center;'>{$notice}</td></tr>";
}
return $header . $diff . "</table>";
/**
* Use specified text instead of loading from the database
+ * @deprecated since 1.WD, use setContent() instead.
*/
function setText( $oldText, $newText ) {
- $this->mOldtext = $oldText;
- $this->mNewtext = $newText;
+ wfDeprecated( __METHOD__, "1.WD" );
+
+ $oldContent = ContentHandler::makeContent( $oldText, $this->getTitle() );
+ $newContent = ContentHandler::makeContent( $newText, $this->getTitle() );
+
+ $this->setContent( $oldContent, $newContent );
+ }
+
+ /**
+ * Use specified text instead of loading from the database
+ * @since 1.WD
+ */
+ function setContent( Content $oldContent, Content $newContent ) {
+ $this->mOldContent = $oldContent;
+ $this->mNewContent = $newContent;
+
$this->mTextLoaded = 2;
$this->mRevisionsLoaded = true;
}
return false;
}
if ( $this->mOldRev ) {
- $this->mOldtext = $this->mOldRev->getText( Revision::FOR_THIS_USER );
- if ( $this->mOldtext === false ) {
+ $this->mOldContent = $this->mOldRev->getContent( Revision::FOR_THIS_USER );
+ if ( $this->mOldContent === false ) {
return false;
}
}
if ( $this->mNewRev ) {
- $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER );
- if ( $this->mNewtext === false ) {
+ $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER );
+ if ( $this->mNewContent === false ) {
return false;
}
}
if ( !$this->loadRevisionData() ) {
return false;
}
- $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER );
+ $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER );
return true;
}
}
wfGetLB()->waitFor( $this->params['masterPos'] );
}
- $revision = Revision::newFromTitle( $this->title, 0, Revision::READ_NORMAL );
+ $revision = Revision::newFromTitle( $this->title, false, Revision::READ_NORMAL );
if ( !$revision ) {
$this->error = 'refreshLinks: Article not found "' .
$this->title->getPrefixedDBkey() . '"';
}
public static function runForTitleInternal( Title $title, Revision $revision, $fname ) {
- global $wgParser, $wgContLang;
+ global $wgContLang;
wfProfileIn( $fname . '-parse' );
$options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
- $parserOutput = $wgParser->parse(
- $revision->getText(), $title, $options, true, true, $revision->getId() );
+ $content = $revision->getContent();
+ $parserOutput = $content->getParserOutput( $title, $revision->getId(), $options, false );
wfProfileOut( $fname . '-parse' );
wfProfileIn( $fname . '-update' );
- $updates = $parserOutput->getSecondaryDataUpdates( $title, false );
+ $updates = $content->getSecondaryDataUpdates( $title, null, false, $parserOutput );
DataUpdate::runUpdates( $updates );
wfProfileOut( $fname . '-update' );
}
}
# Re-parse each page that transcludes this page and update their tracking links...
foreach ( $titles as $title ) {
- $revision = Revision::newFromTitle( $title, 0, Revision::READ_NORMAL );
+ $revision = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
if ( !$revision ) {
$this->error = 'refreshLinks: Article not found "' .
$title->getPrefixedDBkey() . '"';
# Interwikis
wfProfileIn( __METHOD__."-interwiki" );
if ( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && Language::fetchLanguageName( $iw, null, 'mw' ) ) {
+ // FIXME: the above check prevents links to sites with identifiers that are not language codes
$this->mOutput->addLanguageLink( $nt->getFullText() );
$s = rtrim( $s . $prefix );
$s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail;
# Get the revision
$rev = $id
? Revision::newFromId( $id )
- : Revision::newFromTitle( $title, 0, Revision::READ_NORMAL );
+ : Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
$rev_id = $rev ? $rev->getId() : 0;
# If there is no current revision, there is no page
if ( $id === false && !$rev ) {
}
if ( $rev ) {
- $text = $rev->getText();
+ $content = $rev->getContent();
+ $text = $content->getWikitextForTransclusion();
+
+ if ( $text === false || $text === null ) {
+ $text = false;
+ break;
+ }
} elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
global $wgContLang;
$message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
$text = false;
break;
}
+ $content = $message->content();
$text = $message->plain();
} else {
break;
}
- if ( $text === false ) {
+ if ( !$content ) {
break;
}
# Redirect?
$finalTitle = $title;
- $title = Title::newFromRedirect( $text );
+ $title = $content->getRedirectTarget();
}
return array(
'text' => $text,
* @var Title
*/
protected $title;
- var $fileStatus;
+
+ /**
+ * @var Status
+ */
+ protected $fileStatus;
+
+ /**
+ * @var Status
+ */
+ protected $revisionStatus;
function __construct( $title ) {
if( is_null( $title ) ) {
* @return ResultWrapper
*/
function listRevisions() {
+ global $wgContentHandlerNoDB;
+
$dbr = wfGetDB( DB_SLAVE );
+
+ $fields = array(
+ 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
+ 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1',
+ );
+
+ if ( !$wgContentHandlerNoDB ) {
+ $fields[] = 'ar_content_format';
+ $fields[] = 'ar_content_model';
+ }
+
$res = $dbr->select( 'archive',
- array(
- 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
- 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1'
- ),
+ $fields,
array( 'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey() ),
__METHOD__,
* @return Revision
*/
function getRevision( $timestamp ) {
+ global $wgContentHandlerNoDB;
+
$dbr = wfGetDB( DB_SLAVE );
+
+ $fields = array(
+ 'ar_rev_id',
+ 'ar_text',
+ 'ar_comment',
+ 'ar_user',
+ 'ar_user_text',
+ 'ar_timestamp',
+ 'ar_minor_edit',
+ 'ar_flags',
+ 'ar_text_id',
+ 'ar_deleted',
+ 'ar_len',
+ 'ar_sha1',
+ );
+
+ if ( !$wgContentHandlerNoDB ) {
+ $fields[] = 'ar_content_format';
+ $fields[] = 'ar_content_model';
+ }
+
$row = $dbr->selectRow( 'archive',
- array(
- 'ar_rev_id',
- 'ar_text',
- 'ar_comment',
- 'ar_user',
- 'ar_user_text',
- 'ar_timestamp',
- 'ar_minor_edit',
- 'ar_flags',
- 'ar_text_id',
- 'ar_deleted',
- 'ar_len',
- 'ar_sha1',
- ),
+ $fields,
array( 'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey(),
'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
if( $restoreFiles && $this->title->getNamespace() == NS_FILE ) {
$img = wfLocalFile( $this->title );
$this->fileStatus = $img->restore( $fileVersions, $unsuppress );
- if ( !$this->fileStatus->isOk() ) {
+ if ( !$this->fileStatus->isOK() ) {
return false;
}
$filesRestored = $this->fileStatus->successCount;
}
if( $restoreText ) {
- $textRestored = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
- if( $textRestored === false ) { // It must be one of UNDELETE_*
+ $this->revisionStatus = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
+ if( !$this->revisionStatus->isOK() ) {
return false;
}
+
+ $textRestored = $this->revisionStatus->getValue();
} else {
$textRestored = 0;
}
* @param $comment String
* @param $unsuppress Boolean: remove all ar_deleted/fa_deleted restrictions of seletected revs
*
- * @return Mixed: number of revisions restored or false on failure
+ * @return Status, containing the number of revisions restored on success
*/
private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
+ global $wgContentHandlerNoDB;
+
if ( wfReadOnly() ) {
- return false;
+ throw new ReadOnlyError();
}
$restoreAll = empty( $timestamps );
$previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp',
array( 'rev_id' => $previousRevId ),
__METHOD__ );
+
if( $previousTimestamp === false ) {
wfDebug( __METHOD__.": existing page refers to a page_latest that does not exist\n" );
- return 0;
+
+ $status = Status::newGood( 0 );
+ $status->warning( 'undeleterevision-missing' );
+
+ return $status;
}
} else {
# Have to create a new article...
$oldones = "ar_timestamp IN ( {$oldts} )";
}
+ $fields = array(
+ 'ar_rev_id',
+ 'ar_text',
+ 'ar_comment',
+ 'ar_user',
+ 'ar_user_text',
+ 'ar_timestamp',
+ 'ar_minor_edit',
+ 'ar_flags',
+ 'ar_text_id',
+ 'ar_deleted',
+ 'ar_page_id',
+ 'ar_len',
+ 'ar_sha1');
+
+ if ( !$wgContentHandlerNoDB ) {
+ $fields[] = 'ar_content_format';
+ $fields[] = 'ar_content_model';
+ }
+
/**
* Select each archived revision...
*/
$result = $dbw->select( 'archive',
- /* fields */ array(
- 'ar_rev_id',
- 'ar_text',
- 'ar_comment',
- 'ar_user',
- 'ar_user_text',
- 'ar_timestamp',
- 'ar_minor_edit',
- 'ar_flags',
- 'ar_text_id',
- 'ar_deleted',
- 'ar_page_id',
- 'ar_len',
- 'ar_sha1' ),
+ $fields,
/* WHERE */ array(
'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey(),
$rev_count = $dbw->numRows( $result );
if( !$rev_count ) {
wfDebug( __METHOD__ . ": no revisions to restore\n" );
- return false; // ???
+
+ $status = Status::newGood( 0 );
+ $status->warning( "undelete-no-results" );
+ return $status;
}
$ret->seek( $rev_count - 1 ); // move to last
$row = $ret->fetchObject(); // get newest archived rev
$ret->seek( 0 ); // move back
+ // grab the content to check consistency with global state before restoring the page.
+ $revision = Revision::newFromArchiveRow( $row,
+ array(
+ 'title' => $article->getTitle(), // used to derive default content model
+ ) );
+
+ $m = $revision->getContentModel();
+
+ $user = User::newFromName( $revision->getRawUserText(), false );
+ $content = $revision->getContent( Revision::RAW );
+
+ //NOTE: article ID may not be known yet. prepareSave() should not modify the database.
+ $status = $content->prepareSave( $article, 0, -1, $user );
+
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
if( $makepage ) {
// Check the state of the newest to-be version...
if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
- return false; // we can't leave the current revision like this!
+ return Status::newFatal( "undeleterevdel" );
}
// Safe to insert now...
$newid = $article->insertOn( $dbw );
if( $row->ar_timestamp > $previousTimestamp ) {
// Check the state of the newest to-be version...
if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
- return false; // we can't leave the current revision like this!
+ return Status::newFatal( "undeleterevdel" );
}
}
}
// Was anything restored at all?
if ( $restored == 0 ) {
- return 0;
+ return Status::newGood( 0 );
}
$created = (bool)$newid;
$update->doUpdate();
}
- return $restored;
+ return Status::newGood( $restored );
}
/**
* @return Status
*/
function getFileStatus() { return $this->fileStatus; }
+
+ /**
+ * @return Status
+ */
+ function getRevisionStatus() { return $this->revisionStatus; }
}
/**
if( $this->mPreview ) {
// Hide [edit]s
+ //FIXME: ContentHandler will have to provide some specialized magic to do this
$popts = $out->parserOptions();
$popts->setEditSection( false );
$out->parserOptions( $popts );
$out->addWikiTextTitleTidy( $rev->getText( Revision::FOR_THIS_USER, $user ), $this->mTargetObj, true );
}
+ //FIXME: ContentHandler will have to provide some specialized magic for reviewing content before undeletion
+
$out->addHTML(
Xml::element( 'textarea', array(
'readonly' => 'readonly',
* @return String: HTML
*/
function showDiff( $previousRev, $currentRev ) {
- $diffEngine = new DifferenceEngine( $this->getContext() );
+ $diffEngine = $currentRev->getContentHandler()->createDifferenceEngine( $this->getContext() );
$diffEngine->showDiffStyle();
$this->getOutput()->addHTML(
"<div>" .
"<col class='diff-marker' />" .
"<col class='diff-content' />" .
"<tr>" .
- "<td colspan='2' width='50%' align='center' class='diff-otitle'>" .
+ "<td colspan='2' width='50%' style='text-align: center' class='diff-otitle'>" .
$this->diffHeader( $previousRev, 'o' ) .
"</td>\n" .
- "<td colspan='2' width='50%' align='center' class='diff-ntitle'>" .
+ "<td colspan='2' width='50%' style='text-align: center' class='diff-ntitle'>" .
$this->diffHeader( $currentRev, 'n' ) .
"</td>\n" .
"</tr>" .
- $diffEngine->generateDiffBody(
- $previousRev->getText( Revision::FOR_THIS_USER, $this->getUser() ),
- $currentRev->getText( Revision::FOR_THIS_USER, $this->getUser() ) ) .
+ $diffEngine->generateContentDiffBody(
+ $previousRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ),
+ $currentRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ) ) .
"</table>" .
"</div>\n"
);
$out->addHTML( $this->msg( 'undeletedpage' )->rawParams( $link )->parse() );
} else {
$out->setPageTitle( $this->msg( 'undelete-error' ) );
- $out->addWikiMsg( 'cannotundelete' );
- $out->addWikiMsg( 'undeleterevdel' );
}
- // Show file deletion warnings and errors
+ // Show revision undeletion warnings and errors
+ $status = $archive->getRevisionStatus();
+ if( $status && !$status->isGood() ) {
+ $out->addWikiText( '<div class="error">' . $status->getWikiText( 'cannotundelete', 'cannotundelete' ) . '</div>' );
+ }
+
+ // Show file undeletion warnings and errors
$status = $archive->getFileStatus();
if( $status && !$status->isGood() ) {
$out->addWikiText( '<div class="error">' . $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) . '</div>' );
'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.',
'yourdiff' => 'Differences',
'copyrightwarning' => "Please note that all contributions to {{SITENAME}} are considered to be released under the $2 (see $1 for details).
If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.<br />
-You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.
+You are also promising us that you wrote this yourself, or copied editpageit from a public domain or similar free resource.
'''Do not submit copyrighted work without permission!'''",
'copyrightwarning2' => "Please note that all contributions to {{SITENAME}} may be edited, altered, or removed by other contributors.
If you do not want your writing to be edited mercilessly, then do not submit it here.<br />
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource (see $1 for details).
'''Do not submit copyrighted work without permission!'''",
+ 'editpage-head-copy-warn' => '-', # do not translate or duplicate this message to other languages
'editpage-tos-summary' => '-', # do not translate or duplicate this message to other languages
'longpage-hint' => '-', # do not translate or duplicate this message to other languages
'longpageerror' => "'''Error: The text you have submitted is {{PLURAL:$1|one kilobyte|$1 kilobytes}} long, which is longer than the maximum of {{PLURAL:$2|one kilobyte|$2 kilobytes}}.'''
'addsection-preload' => '', # do not translate or duplicate this message to other languages
'addsection-editintro' => '', # do not translate or duplicate this message to other languages
'defaultmessagetext' => 'Default message text',
+'invalid-content-data' => 'Invalid content data',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''Warning:''' This page contains too many expensive parser function calls.
'undeletedrevisions' => '{{PLURAL:$1|1 revision|$1 revisions}} restored',
'undeletedrevisions-files' => '{{PLURAL:$1|1 revision|$1 revisions}} and {{PLURAL:$2|1 file|$2 files}} restored',
'undeletedfiles' => '{{PLURAL:$1|1 file|$1 files}} restored',
-'cannotundelete' => 'Undelete failed;
-someone else may have undeleted the page first.',
+'cannotundelete' => 'Undelete failed:
+$1',
'undeletedpage' => "'''$1 has been restored'''
Consult the [[Special:Log/delete|deletion log]] for a record of recent deletions and restorations.",
'duration-centuries' => '$1 {{PLURAL:$1|century|centuries}}',
'duration-millennia' => '$1 {{PLURAL:$1|millennium|millennia}}',
+# Content model IDs for the ContentHandler facility; used by ContentHandler::getContentModel()
+'content-model-wikitext' => 'wikitext',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
+'content-model-text' => 'plain text',
+
);
<?php
/**
+ * @group medium
+ * ^---- causes phpunit to use a higher timeout threshold
+ *
* @group FileRepo
* @group FileBackend
*/
"Correct file size of '$path'" );
$this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 10,
"Correct file timestamp of '$path'" );
+
+ $this->backend->clearCache( array( $path ) );
+
+ $size = $this->backend->getFileSize( array( 'src' => $path ) );
+
+ $this->assertEquals( strlen( $content ), $size,
+ "Correct file size of '$path'" );
+
+ $this->backend->preloadCache( array( $path ) );
+
+ $size = $this->backend->getFileSize( array( 'src' => $path ) );
+
+ $this->assertEquals( strlen( $content ), $size,
+ "Correct file size of '$path'" );
} else {
$size = $this->backend->getFileSize( array( 'src' => $path ) );
$time = $this->backend->getFileTimestamp( array( 'src' => $path ) );