From: Jens Ohlig Date: Wed, 11 Apr 2012 12:24:29 +0000 (+0200) Subject: Merge branch 'master' into Wikidata X-Git-Tag: 1.31.0-rc.0~22097^2^2~247^2~7 X-Git-Url: https://git.cyclocoop.org/%27.WWW_URL.%27admin/?a=commitdiff_plain;h=10e91851b2568267e5ccb7bbd0ab24ec4f2a195a;p=lhc%2Fweb%2Fwiklou.git Merge branch 'master' into Wikidata Conflicts: .gitreview includes/Article.php includes/AutoLoader.php includes/EditPage.php includes/LinksUpdate.php includes/WikiPage.php includes/installer/Ibm_db2Updater.php includes/installer/MysqlUpdater.php includes/installer/OracleUpdater.php includes/installer/SqliteUpdater.php maintenance/refreshLinks.php --- 10e91851b2568267e5ccb7bbd0ab24ec4f2a195a diff --cc includes/AutoLoader.php index cd6f7a6fab,a1bbc3c0aa..02ad3db760 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@@ -189,8 -195,7 +195,9 @@@ $wgAutoloadLocalClasses = array 'RevisionList' => 'includes/RevisionList.php', 'RSSFeed' => 'includes/Feed.php', 'Sanitizer' => 'includes/Sanitizer.php', + 'SecondaryDataUpdate' => 'includes/SecondaryDataUpdate.php', + 'SecondaryDBDataUpdate' => 'includes/SecondaryDBDataUpdate.php', + 'ScopedPHPTimeout' => 'includes/ScopedPHPTimeout.php', 'SiteConfiguration' => 'includes/SiteConfiguration.php', 'SiteStats' => 'includes/SiteStats.php', 'SiteStatsInit' => 'includes/SiteStats.php', diff --cc includes/EditPage.php index 3bdab8f37e,69187e488a..afe282117e --- a/includes/EditPage.php +++ b/includes/EditPage.php @@@ -574,11 -568,11 +580,11 @@@ class EditPage // Skip this if wpTextbox2 has input, it indicates that we came // from a conflict page with raw page text, not a custom form // modified by subclasses - wfProfileIn( get_class( $this ) . "::importContentFormData" ); - $textbox1 = $this->importContentFormData( $request ); - if ( isset( $textbox1 ) ) + wfProfileIn( get_class($this)."::importContentFormData" ); + $textbox1 = $this->importContentFormData( $request ); #FIXME: what should this return?? + if ( isset($textbox1) ) $this->textbox1 = $textbox1; - wfProfileOut( get_class($this)."::importContentFormData" ); + wfProfileOut( get_class( $this ) . "::importContentFormData" ); } # Truncate for whole multibyte characters. +5 bytes for ellipsis @@@ -834,10 -802,10 +843,10 @@@ # Sanity check, make sure it's the right page, # the revisions exist and they were not deleted. - # Otherwise, $text will be left as-is. + # Otherwise, $content will be left as-is. if ( !is_null( $undorev ) && !is_null( $oldrev ) && $undorev->getPage() == $oldrev->getPage() && - $undorev->getPage() == $this->mTitle->getArticleId() && + $undorev->getPage() == $this->mTitle->getArticleID() && !$undorev->isDeleted( Revision::DELETED_TEXT ) && !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) { @@@ -1271,154 -1192,173 +1281,218 @@@ $aid = $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE ); $new = ( $aid == 0 ); - if ( $new ) { - // Late check for create permission, just in case *PARANOIA* - if ( !$this->mTitle->userCan( 'create' ) ) { - $status->fatal( 'nocreatetext' ); - $status->value = self::AS_NO_CREATE_PERMISSION; - wfDebug( __METHOD__ . ": no create permission\n" ); - wfProfileOut( __METHOD__ ); - return $status; - } + try { + if ( $new ) { + // Late check for create permission, just in case *PARANOIA* + if ( !$this->mTitle->userCan( 'create' ) ) { + $status->fatal( 'nocreatetext' ); + $status->value = self::AS_NO_CREATE_PERMISSION; + wfDebug( __METHOD__ . ": no create permission\n" ); + wfProfileOut( __METHOD__ ); + return $status; + } - # Don't save a new article if it's blank. - if ( $this->textbox1 == '' ) { - $status->setResult( false, self::AS_BLANK_ARTICLE ); - wfProfileOut( __METHOD__ ); - return $status; - } + # Don't save a new article if it's blank. + if ( $this->textbox1 == '' ) { + $status->setResult( false, self::AS_BLANK_ARTICLE ); + wfProfileOut( __METHOD__ ); + return $status; + } - // Run post-section-merge edit filter - if ( !wfRunHooks( 'EditFilterMerged', array( $this, $this->textbox1, &$this->hookError, $this->summary ) ) ) { - # Error messages etc. could be handled within the hook... - $status->fatal( 'hookaborted' ); - $status->value = self::AS_HOOK_ERROR; - wfProfileOut( __METHOD__ ); - return $status; - } elseif ( $this->hookError != '' ) { - # ...or the hook could be expecting us to produce an error - $status->fatal( 'hookaborted' ); - $status->value = self::AS_HOOK_ERROR_EXPECTED; - wfProfileOut( __METHOD__ ); - return $status; - } ++<<<<<<< HEAD + // Run post-section-merge edit filter + if ( !wfRunHooks( 'EditFilterMerged', array( $this, $this->textbox1, &$this->hookError, $this->summary ) ) ) { + # Error messages etc. could be handled within the hook... + $status->fatal( 'hookaborted' ); + $status->value = self::AS_HOOK_ERROR; + wfProfileOut( __METHOD__ ); + return $status; + } elseif ( $this->hookError != '' ) { + # ...or the hook could be expecting us to produce an error + $status->fatal( 'hookaborted' ); + $status->value = self::AS_HOOK_ERROR_EXPECTED; + wfProfileOut( __METHOD__ ); + return $status; + } + + $content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format ); + # Handle the user preference to force summaries here. Check if it's not a redirect. + if ( !$this->allowBlankSummary && !$content->isRedirect() ) { + if ( md5( $this->summary ) == $this->autoSumm ) { + $this->missingSummary = true; + $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh + $status->value = self::AS_SUMMARY_NEEDED; + wfProfileOut( __METHOD__ ); + return $status; + } ++======= + $text = $this->textbox1; + $result['sectionanchor'] = ''; + if ( $this->section == 'new' ) { + if ( $this->sectiontitle !== '' ) { + // Insert the section title above the content. + $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->sectiontitle ) . "\n\n" . $text; + + // Jump to the new section + $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle ); + + // If no edit summary was specified, create one automatically from the section + // title and have it link to the new section. Otherwise, respect the summary as + // passed. + if ( $this->summary === '' ) { + $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle ); + $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle ); + } + } elseif ( $this->summary !== '' ) { + // Insert the section title above the content. + $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $text; + + // Jump to the new section + $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary ); + + // Create a link to the new section from the edit summary. + $cleanSummary = $wgParser->stripSectionName( $this->summary ); + $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary ); ++>>>>>>> master } - } - $status->value = self::AS_SUCCESS_NEW_ARTICLE; + $result['sectionanchor'] = ''; + if ( $this->section == 'new' ) { + if ( $this->sectiontitle !== '' ) { + // Insert the section title above the content. + $content = $content->addSectionHeader( $this->sectiontitle ); + + // Jump to the new section + $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle ); + + // If no edit summary was specified, create one automatically from the section + // title and have it link to the new section. Otherwise, respect the summary as + // passed. + if ( $this->summary === '' ) { + $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle ); + $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle ); + } + } elseif ( $this->summary !== '' ) { + // Insert the section title above the content. + $content = $content->addSectionHeader( $this->sectiontitle ); + + // Jump to the new section + $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary ); - } else { + // Create a link to the new section from the edit summary. + $cleanSummary = $wgParser->stripSectionName( $this->summary ); + $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary ); + } + } - # Article exists. Check for edit conflict. + $status->value = self::AS_SUCCESS_NEW_ARTICLE; - $this->mArticle->clear(); # Force reload of dates, etc. - $timestamp = $this->mArticle->getTimestamp(); + } else { - wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" ); + # Article exists. Check for edit conflict. - if ( $timestamp != $this->edittime ) { - $this->isConflict = true; - if ( $this->section == 'new' ) { - if ( $this->mArticle->getUserText() == $wgUser->getName() && - $this->mArticle->getComment() == $this->summary ) { - // Probably a duplicate submission of a new comment. - // This can happen when squid resends a request after - // a timeout but the first one actually went through. - wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" ); - } else { - // New comment; suppress conflict. + $this->mArticle->clear(); # Force reload of dates, etc. + $timestamp = $this->mArticle->getTimestamp(); + + wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" ); + + if ( $timestamp != $this->edittime ) { + $this->isConflict = true; + if ( $this->section == 'new' ) { + if ( $this->mArticle->getUserText() == $wgUser->getName() && + $this->mArticle->getComment() == $this->summary ) { + // Probably a duplicate submission of a new comment. + // This can happen when squid resends a request after + // a timeout but the first one actually went through. + wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" ); + } else { + // New comment; suppress conflict. + $this->isConflict = false; + wfDebug( __METHOD__ .": conflict suppressed; new section\n" ); + } + } elseif ( $this->section == '' && $this->userWasLastToEdit( $wgUser->getId(), $this->edittime ) ) { + # Suppress edit conflict with self, except for section edits where merging is required. + wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" ); $this->isConflict = false; ++<<<<<<< HEAD ++======= + wfDebug( __METHOD__ . ": conflict suppressed; new section\n" ); ++>>>>>>> master } - } elseif ( $this->section == '' && $this->userWasLastToEdit( $wgUser->getId(), $this->edittime ) ) { - # Suppress edit conflict with self, except for section edits where merging is required. - wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" ); - $this->isConflict = false; } ++<<<<<<< HEAD + + // If sectiontitle is set, use it, otherwise use the summary as the section title (for + // backwards compatibility with old forms/bots). + if ( $this->sectiontitle !== '' ) { + $sectionTitle = $this->sectiontitle; ++======= + } + + // If sectiontitle is set, use it, otherwise use the summary as the section title (for + // backwards compatibility with old forms/bots). + if ( $this->sectiontitle !== '' ) { + $sectionTitle = $this->sectiontitle; + } else { + $sectionTitle = $this->summary; + } + + if ( $this->isConflict ) { + wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" ); + $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle, $this->edittime ); + } else { + wfDebug( __METHOD__ . ": getting section '$this->section'\n" ); + $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle ); + } + if ( is_null( $text ) ) { + wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" ); + $this->isConflict = true; + $text = $this->textbox1; // do not try to merge here! + } elseif ( $this->isConflict ) { + # Attempt merge + if ( $this->mergeChangesInto( $text ) ) { + // Successful merge! Maybe we should tell the user the good news? + $this->isConflict = false; + wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" ); ++>>>>>>> master } else { - $this->section = ''; - $this->textbox1 = $text; - wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" ); + $sectionTitle = $this->summary; } - } - if ( $this->isConflict ) { - $status->setResult( false, self::AS_CONFLICT_DETECTED ); - wfProfileOut( __METHOD__ ); - return $status; - } + $textbox_content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format ); + $content = false; - // Run post-section-merge edit filter - if ( !wfRunHooks( 'EditFilterMerged', array( $this, $text, &$this->hookError, $this->summary ) ) ) { - # Error messages etc. could be handled within the hook... - $status->fatal( 'hookaborted' ); - $status->value = self::AS_HOOK_ERROR; - wfProfileOut( __METHOD__ ); - return $status; - } elseif ( $this->hookError != '' ) { - # ...or the hook could be expecting us to produce an error - $status->fatal( 'hookaborted' ); - $status->value = self::AS_HOOK_ERROR_EXPECTED; - wfProfileOut( __METHOD__ ); - return $status; - } + if ( $this->isConflict ) { + wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" ); - # Handle the user preference to force summaries here, but not for null edits - if ( $this->section != 'new' && !$this->allowBlankSummary - && $this->getOriginalContent() != $text - && !Title::newFromRedirect( $text ) ) # check if it's not a redirect - { - if ( md5( $this->summary ) == $this->autoSumm ) { - $this->missingSummary = true; - $status->fatal( 'missingsummary' ); - $status->value = self::AS_SUMMARY_NEEDED; - wfProfileOut( __METHOD__ ); - return $status; + $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle, $this->edittime ); + } else { + wfDebug( __METHOD__ . ": getting section '$this->section'\n" ); + + $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle ); } - } - # And a similar thing for new sections - if ( $this->section == 'new' && !$this->allowBlankSummary ) { - if ( trim( $this->summary ) == '' ) { - $this->missingSummary = true; - $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh - $status->value = self::AS_SUMMARY_NEEDED; + if ( is_null( $content ) ) { + wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" ); + $this->isConflict = true; + $content = $textbox_content; // do not try to merge here! + } elseif ( $this->isConflict ) { + # Attempt merge + if ( $this->mergeChangesIntoContent( $textbox_content ) ) { + // Successful merge! Maybe we should tell the user the good news? + $content = $textbox_content; + $this->isConflict = false; + wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" ); + } else { + $this->section = ''; + #$this->textbox1 = $text; #redundant, nothing to do here? + wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" ); + } + } + + if ( $this->isConflict ) { + $status->setResult( false, self::AS_CONFLICT_DETECTED ); wfProfileOut( __METHOD__ ); return $status; } @@@ -1737,21 -1603,21 +1811,21 @@@ # Enabled article-related sidebar, toplinks, etc. $wgOut->setArticleRelated( true ); + $contextTitle = $this->getContextTitle(); if ( $this->isConflict ) { - $wgOut->setPageTitle( wfMessage( 'editconflict', $this->getContextTitle()->getPrefixedText() ) ); - } elseif ( $this->section != '' ) { + $msg = 'editconflict'; + } elseif ( $contextTitle->exists() && $this->section != '' ) { $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection'; - $wgOut->setPageTitle( wfMessage( $msg, $this->getContextTitle()->getPrefixedText() ) ); } else { - # Use the title defined by DISPLAYTITLE magic word when present - if ( isset( $this->mParserOutput ) - && ( $dt = $this->mParserOutput->getDisplayTitle() ) !== false ) { - $title = $dt; - } else { - $title = $this->getContextTitle()->getPrefixedText(); - } - $wgOut->setPageTitle( wfMessage( 'editing', $title ) ); - $msg = $contextTitle->exists() || ( $contextTitle->getNamespace() == NS_MEDIAWIKI && $contextTitle->getDefaultMessageText() !== false ) ? - 'editing' : 'creating'; ++ $msg = $contextTitle->exists() || ( $contextTitle->getNamespace() == NS_MEDIAWIKI && $contextTitle->getDefaultMessageText() !== false ) ? ++ 'editing' : 'creating'; + } + # Use the title defined by DISPLAYTITLE magic word when present - $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false; - if ( $displayTitle === false ) { - $displayTitle = $contextTitle->getPrefixedText(); ++ $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false; ++ if ( $displayTitle === false ) { ++ $displayTitle = $contextTitle->getPrefixedText(); } + $wgOut->setPageTitle( wfMessage( $msg, $displayTitle ) ); } /** @@@ -2364,11 -2254,11 +2468,11 @@@ HTM $this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6, 'readonly' ) ); } - protected function showTextbox( $content, $name, $customAttribs = array() ) { + protected function showTextbox( $text, $name, $customAttribs = array() ) { global $wgOut, $wgUser; - $wikitext = $this->safeUnicodeOutput( $text ); - if ( strval($wikitext) !== '' ) { + $wikitext = $this->safeUnicodeOutput( $content ); + if ( strval( $wikitext ) !== '' ) { // Ensure there's a newline at the end, otherwise adding lines // is awkward. // But don't add a newline if the ext is empty, or Firefox in XHTML @@@ -2443,39 -2333,32 +2547,53 @@@ * save and then make a comparison. */ function showDiff() { - global $wgUser, $wgContLang, $wgParser, $wgOut; + global $wgUser, $wgContLang, $wgOut; + + $oldContent = $this->getOriginalContent(); + + $textboxContent = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), + $this->content_model, $this->content_format ); #XXX: handle parse errors ? + $newContent = $this->mArticle->replaceSectionContent( + $this->section, $textboxContent, + $this->summary, $this->edittime ); + $oldtitlemsg = 'currentrev'; + # if message does not exist, show diff against the preloaded default + if( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) { + $oldtext = $this->mTitle->getDefaultMessageText(); + if( $oldtext !== false ) { + $oldtitlemsg = 'defaultmessagetext'; + } + } else { + $oldtext = $this->mArticle->getRawText(); + } + $newtext = $this->mArticle->replaceSection( + $this->section, $this->textbox1, $this->summary, $this->edittime ); + # hanlde legacy text-based hook + $newtext_orig = $newContent->serialize( $this->content_format ); + $newtext = $newtext_orig; #clone wfRunHooks( 'EditPageGetDiffText', array( $this, &$newtext ) ); + if ( $newtext != $newtext_orig ) { + #if the hook changed the text, create a new Content object accordingly. + $newContent = ContentHandler::makeContent( $newtext, $this->getTitle(), $newContent->getModelName() ); #XXX: handle parse errors ? + } + + wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) ); #FIXME: document new hook + $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang ); - $newtext = $wgParser->preSaveTransform( $newtext, $this->mTitle, $wgUser, $popts ); + $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts ); + if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) { + $oldtitle = wfMsgExt( 'currentrev', array( 'parseinline' ) ); + if ( $oldtext !== false || $newtext != '' ) { + $oldtitle = wfMsgExt( $oldtitlemsg, array( 'parseinline' ) ); $newtitle = wfMsgExt( 'yourtext', array( 'parseinline' ) ); - $de = new DifferenceEngine( $this->mArticle->getContext() ); - $de->setText( $oldtext, $newtext ); + $de = $oldContent->getContentHandler()->getDifferenceEngine( $this->mArticle->getContext() ); + $de->setContent( $oldContent, $newContent ); + $difftext = $de->getDiff( $oldtitle, $newtitle ); $de->showDiffStyle(); } else { @@@ -2690,97 -2569,85 +2808,174 @@@ return $parsedNote; } + try { + $content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format ); + + if ( $this->mTriedSave && !$this->mTokenOk ) { + if ( $this->mTokenOkExceptSuffix ) { + $note = wfMsg( 'token_suffix_mismatch' ); + } else { + $note = wfMsg( 'session_fail_preview' ); + } + } elseif ( $this->incompleteForm ) { + $note = wfMsg( 'edit_form_incomplete' ); + } elseif ( $this->isCssJsSubpage || $this->mTitle->isCssOrJsPage() ) { + # if this is a CSS or JS page used in the UI, show a special notice + # XXX: stupid php bug won't let us use $this->getContextTitle()->isCssJsSubpage() here -- This note has been there since r3530. Sure the bug was fixed time ago? + + if( $this->mTitle->isCssJsSubpage() ) { + $level = 'user'; + } elseif( $this->mTitle->isCssOrJsPage() ) { + $level = 'site'; + } else { + $level = false; + } + + if ( $content->getModelName() == CONTENT_MODEL_CSS ) { + $format = 'css'; + } elseif ( $content->getModelName() == CONTENT_MODEL_JAVASCRIPT ) { + $format = 'js'; + } else { + $format = false; + } + + # Used messages to make sure grep find them: + # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview + if( $level && $format ) { + $note = "
" . wfMsg( "{$level}{$format}preview" ) . "
"; + } else { + $note = wfMsg( 'previewnote' ); + } + } else { + $note = wfMsg( 'previewnote' ); + } + + $parserOptions = ParserOptions::newFromUser( $wgUser ); + $parserOptions->setEditSection( false ); + $parserOptions->setTidy( true ); + $parserOptions->setIsPreview( true ); + $parserOptions->setIsSectionPreview( !is_null($this->section) && $this->section !== '' ); + + $rt = $content->getRedirectChain(); + + if ( $rt ) { + $previewHTML = $this->mArticle->viewRedirect( $rt, false ); + } else { + + # If we're adding a comment, we need to show the + # summary as the headline + if ( $this->section == "new" && $this->summary != "" ) { + $content = $content->addSectionHeader( $this->summary ); + } + + $toparse_orig = $content->serialize( $this->content_format ); + $toparse = $toparse_orig; + wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) ); + + if ( $toparse !== $toparse_orig ) { + #hook changed the text, create new Content object + $content = ContentHandler::makeContent( $toparse, $this->getTitle(), $this->content_model, $this->content_format ); + } + + wfRunHooks( 'EditPageGetPreviewContent', array( $this, &$content ) ); # FIXME: document new hook + + $parserOptions->enableLimitReport(); + + #XXX: For CSS/JS pages, we should have called the ShowRawCssJs hook here. But it's now deprecated, so never mind + $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions ); + $parserOutput = $content->getParserOutput( $this->mTitle, null, $parserOptions ); + + $previewHTML = $parserOutput->getText(); + $this->mParserOutput = $parserOutput; + $wgOut->addParserOutputNoText( $parserOutput ); + + if ( count( $parserOutput->getWarnings() ) ) { + $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() ); + } + } + } catch (MWContentSerializationException $ex) { + $note .= "\n\n" . wfMsg('content-failed-to-parse', $this->content_model, $this->content_format, $ex->getMessage() ); + $previewHTML = ''; + } + if ( $this->mTriedSave && !$this->mTokenOk ) { + if ( $this->mTokenOkExceptSuffix ) { + $note = wfMsg( 'token_suffix_mismatch' ); + } else { + $note = wfMsg( 'session_fail_preview' ); + } + } elseif ( $this->incompleteForm ) { + $note = wfMsg( 'edit_form_incomplete' ); + } else { + $note = wfMsg( 'previewnote' ); + } + + $parserOptions = ParserOptions::newFromUser( $wgUser ); + $parserOptions->setEditSection( false ); + $parserOptions->setTidy( true ); + $parserOptions->setIsPreview( true ); + $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' ); + + # don't parse non-wikitext pages, show message about preview + # XXX: stupid php bug won't let us use $this->getContextTitle()->isCssJsSubpage() here -- This note has been there since r3530. Sure the bug was fixed time ago? + + if ( $this->isCssJsSubpage || !$this->mTitle->isWikitextPage() ) { + if ( $this->mTitle->isCssJsSubpage() ) { + $level = 'user'; + } elseif ( $this->mTitle->isCssOrJsPage() ) { + $level = 'site'; + } else { + $level = false; + } + + # Used messages to make sure grep find them: + # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview + if ( $level ) { + if ( preg_match( "/\\.css$/", $this->mTitle->getText() ) ) { + $previewtext = "
\n" . wfMsg( "{$level}csspreview" ) . "\n
"; + $class = "mw-code mw-css"; + } elseif ( preg_match( "/\\.js$/", $this->mTitle->getText() ) ) { + $previewtext = "
\n" . wfMsg( "{$level}jspreview" ) . "\n
"; + $class = "mw-code mw-js"; + } else { + throw new MWException( 'A CSS/JS (sub)page but which is not css nor js!' ); + } + } + + $parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions ); + $previewHTML = $parserOutput->mText; + $previewHTML .= "
\n" . htmlspecialchars( $this->textbox1 ) . "\n
\n"; + } else { + $toparse = $this->textbox1; - if( $this->isConflict ) { + # If we're adding a comment, we need to show the + # summary as the headline + if ( $this->section == "new" && $this->summary != "" ) { + $toparse = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $toparse; + } + + wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) ); + + $parserOptions->enableLimitReport(); + + $toparse = $wgParser->preSaveTransform( $toparse, $this->mTitle, $wgUser, $parserOptions ); + $parserOutput = $wgParser->parse( $toparse, $this->mTitle, $parserOptions ); + + $rt = Title::newFromRedirectArray( $this->textbox1 ); + if ( $rt ) { + $previewHTML = $this->mArticle->viewRedirect( $rt, false ); + } else { + $previewHTML = $parserOutput->getText(); + } + + $this->mParserOutput = $parserOutput; + $wgOut->addParserOutputNoText( $parserOutput ); + + if ( count( $parserOutput->getWarnings() ) ) { + $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() ); + } + } + + if ( $this->isConflict ) { $conflict = '

' . htmlspecialchars( wfMsg( 'previewconflict' ) ) . "

\n"; } else { $conflict = '
'; @@@ -3369,8 -3232,8 +3567,10 @@@ // Do some sanity checks. These aren't needed for reversability, // but should help keep the breakage down if the editor // breaks one of the entities whilst editing. + if ( ( substr( $invalue, $i, 1 ) == ";" ) and ( strlen( $hexstring ) <= 6 ) ) { + $codepoint = hexdec( $hexstring ); + if ( (substr($invalue,$i,1)==";") and (strlen($hexstring) <= 6) ) { + $codepoint = hexdec($hexstring); $result .= codepointToUtf8( $codepoint ); } else { $result .= "&#x" . $hexstring . substr( $invalue, $i, 1 ); diff --cc includes/FeedUtils.php index d189428c14,d280db5b39..bb474399a7 --- a/includes/FeedUtils.php +++ b/includes/FeedUtils.php @@@ -115,10 -114,10 +114,11 @@@ class FeedUtils # $wgLang->time( $timestamp ) ), # wfMsg( 'currentrev' ) ); + $diffText = ''; // Don't bother generating the diff if we won't be able to show it if ( $wgFeedDiffCutoff > 0 ) { - $de = new DifferenceEngine( $title, $oldid, $newid ); + $contentHandler = ContentHandler::getForTitle( $title ); + $de = $contentHandler->getDifferenceEngine( $title, $oldid, $newid ); $diffText = $de->getDiff( wfMsg( 'previousrevision' ), // hack wfMsg( 'revisionasof', diff --cc includes/LinksUpdate.php index 6c864c1384,716e7d8072..1973d95943 --- a/includes/LinksUpdate.php +++ b/includes/LinksUpdate.php @@@ -45,18 -47,23 +45,22 @@@ class LinksUpdate extends SecondaryDBDa * @param $recursive Boolean: queue jobs for recursive updates? */ function __construct( $title, $parserOutput, $recursive = true ) { - global $wgAntiLockFlags; + parent::__construct( ); - if ( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) { - $this->mOptions = array(); - } else { - $this->mOptions = array( 'FOR UPDATE' ); - } - $this->mDb = wfGetDB( DB_MASTER ); + if ( !is_object( $title ) ) { + throw new MWException( "The calling convention to LinksUpdate::LinksUpdate() has changed. " . + "Please see Article::editUpdates() for an invocation example.\n" ); + } - if ( !is_object( $title ) ) { - throw new MWException( "The calling convention to LinksUpdate::__construct() has changed. " . ++ if ( !is_object( $title ) ) { ++ throw new MWException( "The calling convention to LinksUpdate::__construct() has changed. " . + "Please see WikiPage::doEditUpdates() for an invocation example.\n" ); - } - $this->mTitle = $title; - $this->mId = $title->getArticleID(); ++ } + $this->mTitle = $title; + $this->mId = $title->getArticleID(); + + $this->mParserOutput = $parserOutput; - $this->mParserOutput = $parserOutput; $this->mLinks = $parserOutput->getLinks(); $this->mImages = $parserOutput->getImages(); $this->mTemplates = $parserOutput->getTemplates(); diff --cc includes/WikiPage.php index 174b96e9f8,6cad466414..5ea5d75353 --- a/includes/WikiPage.php +++ b/includes/WikiPage.php @@@ -437,24 -427,16 +461,24 @@@ class WikiPage extends Page /** * Get the text of the current revision. No side-effects... * - * @return String|false The text of the current revision + * @return String|bool The text of the current revision. False on failure */ - public function getRawText() { - $this->loadLastEdit(); - if ( $this->mLastRevision ) { - return $this->mLastRevision->getRawText(); - } - return false; + public function getRawText() { #FIXME: deprecated, replace usage! + return $this->getText( Revision::RAW ); } + /** + * Get the content of the current revision. No side-effects... + * + * @return Contet|false The text of the current revision + */ + protected function getNativeData() { #FIXME: examine all uses carefully! caller must be aware of content model! + $content = $this->getContent( Revision::RAW ); + if ( !$content ) return null; + + return $content->getNativeData(); + } + /** * @return string MW timestamp of last article revision */ @@@ -1415,15 -1323,13 +1441,15 @@@ 'parent_id' => $oldid, 'user' => $user->getId(), 'user_text' => $user->getName(), - 'timestamp' => $now + 'timestamp' => $now, + 'content_model' => $content->getModelName(), + 'content_format' => $serialisation_format, ) ); - $changed = ( strcmp( $text, $oldtext ) != 0 ); + $changed = !$content->equals( $old_content ); if ( $changed ) { - $dbw->begin(); + $dbw->begin( __METHOD__ ); $revisionId = $revision->insertOn( $dbw ); # Update page @@@ -2573,13 -2481,93 +2626,98 @@@ * @param &$hasHistory Boolean: whether the page has a history * @return mixed String containing deletion reason or empty string, or boolean false * if no revision occurred + * @deprecated since 1.20, use ContentHandler::getAutoDeleteReason() instead */ public function getAutoDeleteReason( &$hasHistory ) { + #NOTE: stub for backwards-compatibility. + + $handler = ContentHandler::getForTitle( $this->getTitle() ); + $handler->getAutoDeleteReason( $this->getTitle(), $hasHistory ); + global $wgContLang; + + // Get the last revision + $rev = $this->getRevision(); + + if ( is_null( $rev ) ) { + return false; + } + + // Get the article's contents + $contents = $rev->getText(); + $blank = false; + + // If the page is blank, use the text from the previous revision, + // which can only be blank if there's a move/import/protect dummy revision involved + if ( $contents == '' ) { + $prev = $rev->getPrevious(); + + if ( $prev ) { + $contents = $prev->getText(); + $blank = true; + } + } + + $dbw = wfGetDB( DB_MASTER ); + + // Find out if there was only one contributor + // Only scan the last 20 revisions + $res = $dbw->select( 'revision', 'rev_user_text', + array( 'rev_page' => $this->getID(), $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' ), + __METHOD__, + array( 'LIMIT' => 20 ) + ); + + if ( $res === false ) { + // This page has no revisions, which is very weird + return false; + } + + $hasHistory = ( $res->numRows() > 1 ); + $row = $dbw->fetchObject( $res ); + + if ( $row ) { // $row is false if the only contributor is hidden + $onlyAuthor = $row->rev_user_text; + // Try to find a second contributor + foreach ( $res as $row ) { + if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999 + $onlyAuthor = false; + break; + } + } + } else { + $onlyAuthor = false; + } + + // Generate the summary with a '$1' placeholder + if ( $blank ) { + // The current revision is blank and the one before is also + // blank. It's just not our lucky day + $reason = wfMsgForContent( 'exbeforeblank', '$1' ); + } else { + if ( $onlyAuthor ) { + $reason = wfMsgForContent( 'excontentauthor', '$1', $onlyAuthor ); + } else { + $reason = wfMsgForContent( 'excontent', '$1' ); + } + } + + if ( $reason == '-' ) { + // Allow these UI messages to be blanked out cleanly + return ''; + } + + // Replace newlines with spaces to prevent uglyness + $contents = preg_replace( "/[\n\r]/", ' ', $contents ); + // Calculate the maximum amount of chars to get + // Max content length = max comment length - length of the comment (excl. $1) + $maxLength = 255 - ( strlen( $reason ) - 2 ); + $contents = $wgContLang->truncate( $contents, $maxLength ); + // Remove possible unfinished links + $contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents ); + // Now replace the '$1' placeholder + $reason = str_replace( '$1', $contents, $reason ); + + return $reason; } /** @@@ -2932,12 -2914,13 +3071,16 @@@ class PoolWorkArticleView extends PoolC if ( $rev === null ) { return false; } - $text = $rev->getText(); + $content = $rev->getContent(); #XXX: why use PUBLIC audience here (default), and RAW above? } + $time = - wfTime(); + $this->parserOutput = $content->getParserOutput( $this->page->getTitle(), $this->revid, $this->parserOptions ); + $time += wfTime(); + $time = - microtime( true ); + $this->parserOutput = $wgParser->parse( $text, $this->page->getTitle(), + $this->parserOptions, true, true, $this->revid ); + $time += microtime( true ); # Timing hack if ( $time > 3 ) { diff --cc includes/api/ApiComparePages.php index 3c987aacb3,87f096785f..1fac9962ef --- a/includes/api/ApiComparePages.php +++ b/includes/api/ApiComparePages.php @@@ -32,11 -32,10 +32,11 @@@ class ApiComparePages extends ApiBase public function execute() { $params = $this->extractRequestParams(); - $rev1 = $this->revisionOrTitle( $params['fromrev'], $params['fromtitle'] ); - $rev2 = $this->revisionOrTitle( $params['torev'], $params['totitle'] ); + $rev1 = $this->revisionOrTitleOrId( $params['fromrev'], $params['fromtitle'], $params['fromid'] ); + $rev2 = $this->revisionOrTitleOrId( $params['torev'], $params['totitle'], $params['toid'] ); - $de = new DifferenceEngine( $this->getContext(), + $contentHandler = ContentHandler::getForModelName( $rev1->getContentModelName() ); + $de = $contentHandler->getDifferenceEngine( $this->getContext(), $rev1, $rev2, null, // rcid diff --cc includes/diff/DifferenceEngine.php index 2fb203ef3d,e8f35f0d0a..348f2dc0f3 --- a/includes/diff/DifferenceEngine.php +++ b/includes/diff/DifferenceEngine.php @@@ -705,12 -670,11 +705,13 @@@ class DifferenceEngine extends ContextS /** * Generate a diff, no caching * + * @todo move this to TextDifferenceEngine, make DifferenceEngine abstract. At some point. + * * @param $otext String: old text, must be already segmented * @param $ntext String: new text, must be already segmented + * @return bool|string */ - function generateDiffBody( $otext, $ntext ) { + function generateTextDiffBody( $otext, $ntext ) { global $wgExternalDiffEngine, $wgContLang; wfProfileIn( __METHOD__ ); diff --cc includes/installer/Ibm_db2Updater.php index b29b12fa11,02d7cb1eb7..68573e0356 --- a/includes/installer/Ibm_db2Updater.php +++ b/includes/installer/Ibm_db2Updater.php @@@ -71,13 -70,8 +70,15 @@@ class Ibm_db2Updater extends DatabaseUp array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1.sql' ), array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1.sql' ), + // 1.20 + // content model stuff for WikiData + array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ), + array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ), + array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ), + array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ), + array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ), + // 1.20 + array( 'addTable', 'config', 'patch-config.sql' ), ); } } diff --cc includes/installer/MysqlUpdater.php index 60fdaaf4ff,5e6ae7eafb..8182733df5 --- a/includes/installer/MysqlUpdater.php +++ b/includes/installer/MysqlUpdater.php @@@ -189,17 -188,14 +188,22 @@@ class MysqlUpdater extends DatabaseUpda array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1.sql' ), array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1.sql' ), array( 'addIndex', 'page', 'page_redirect_namespace_len', 'patch-page_redirect_namespace_len.sql' ), - array( 'modifyField', 'user', 'ug_group', 'patch-ug_group-length-increase.sql' ), + array( 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase.sql' ), array( 'addField', 'uploadstash', 'us_chunk_inx', 'patch-uploadstash_chunk.sql' ), array( 'addfield', 'job', 'job_timestamp', 'patch-jobs-add-timestamp.sql' ), + + // 1.20 + // content model stuff for WikiData + array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ), + array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ), + array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ), + array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ), + array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ), + array( 'modifyField', 'user_former_groups', 'ufg_group', 'patch-ufg_group-length-increase.sql' ), + + // 1.20 + array( 'addTable', 'config', 'patch-config.sql' ), + array( 'addIndex', 'revision', 'page_user_timestamp', 'patch-revision-user-page-index.sql' ), ); } diff --cc includes/installer/OracleUpdater.php index 37116d532a,73bbc57751..a79d0b936c --- a/includes/installer/OracleUpdater.php +++ b/includes/installer/OracleUpdater.php @@@ -53,13 -52,8 +52,15 @@@ class OracleUpdater extends DatabaseUpd array( 'addField', 'job', 'job_timestamp', 'patch-job_timestamp_field.sql' ), array( 'addIndex', 'job', 'i02', 'patch-job_timestamp_index.sql' ), + // 1.20 + // content model stuff for WikiData + array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ), + array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ), + array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ), + array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ), + array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ), + //1.20 + array( 'addTable', 'config', 'patch-config.sql' ), // KEEP THIS AT THE BOTTOM!! array( 'doRebuildDuplicateFunction' ), diff --cc includes/installer/SqliteUpdater.php index 19aac94db6,a98c4db83a..89620433f0 --- a/includes/installer/SqliteUpdater.php +++ b/includes/installer/SqliteUpdater.php @@@ -68,17 -67,14 +67,22 @@@ class SqliteUpdater extends DatabaseUpd array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1.sql' ), array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1.sql' ), array( 'addIndex', 'page', 'page_redirect_namespace_len', 'patch-page_redirect_namespace_len.sql' ), - array( 'modifyField', 'user', 'ug_group', 'patch-ug_group-length-increase.sql' ), + array( 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase.sql' ), array( 'addField', 'uploadstash', 'us_chunk_inx', 'patch-uploadstash_chunk.sql' ), array( 'addfield', 'job', 'job_timestamp', 'patch-jobs-add-timestamp.sql' ), + + // 1.20 + // content model stuff for WikiData + array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ), + array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ), + array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ), + array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ), + array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ), + array( 'modifyField', 'user_former_groups', 'ufg_group', 'patch-ug_group-length-increase.sql' ), + + // 1.20 + array( 'addTable', 'config', 'patch-config.sql' ), + array( 'addIndex', 'revision', 'page_user_timestamp', 'patch-revision-user-page-index.sql' ), ); } diff --cc maintenance/refreshLinks.php index d4d803d5e5,7abbc907a9..dd8c8a71f5 --- a/maintenance/refreshLinks.php +++ b/maintenance/refreshLinks.php @@@ -221,11 -221,9 +221,15 @@@ class RefreshLinks extends Maintenance $options = new ParserOptions; $parserOutput = $wgParser->parse( $revision->getText(), $title, $options, true, true, $revision->getId() ); + + $updates = $parserOutput->getLinksUpdateAndOtherUpdates( $title, false ); + SecondaryDataUpdate::runUpdates( $updates ); + + $dbw->commit(); ++ // TODO: We don't know what happens here. + $update = new LinksUpdate( $title, $parserOutput, false ); + $update->doUpdate(); + $dbw->commit( __METHOD__ ); } /**