From: daniel Date: Mon, 8 Oct 2012 11:58:54 +0000 (+0200) Subject: merge latest master into Wikidata branch X-Git-Tag: 1.31.0-rc.0~22097^2^2~2 X-Git-Url: http://git.cyclocoop.org/%24dirpuce/puce%24spip_lang_rtl.gif?a=commitdiff_plain;h=c546fae8ed67279d21daf0652349975fa034f7d1;p=lhc%2Fweb%2Fwiklou.git merge latest master into Wikidata branch Change-Id: Id4e0f40c03679c13d8934a6add99b5cd86d0437d --- c546fae8ed67279d21daf0652349975fa034f7d1 diff --cc RELEASE-NOTES-1.21 index 74eda0d927,66c3859cd0..e99487afe8 --- a/RELEASE-NOTES-1.21 +++ b/RELEASE-NOTES-1.21 @@@ -11,20 -11,28 +11,32 @@@ MediaWiki 1.21 is an alpha-quality bran production. === Configuration changes in 1.21 === + * (bug 29374) $wgVectorUseSimpleSearch is now enabled by default. + * Deprecated $wgAllowRealName is removed. Use $wgHiddenPrefs[] = 'realname' instead === New features in 1.21 === +* Added ContentHandler facility to allow extensions to support other content than wikitext. + See docs/contenthandler.txt for details. === Bug fixes in 1.21 === + * (bug 40353) SpecialDoubleRedirect should support interwiki redirects. + * (bug 40352) fixDoubleRedirects.php should support interwiki redirects. + * (bug 9237) SpecialBrokenRedirect should not list interwiki redirects. + * (bug 34960) Drop unused fields rc_moved_to_ns and rc_moved_to_title from recentchanges table. + * (bug 32951) Do not register internal externals with absolute protocol, + when server has relative protocol. === API changes in 1.21 === +* prop=revisions can now report the contentmodel and contentformat, see docs/contenthandler.txt +* action=edit and action=parse now support contentmodel and contentformat parameters to control the interpretation of + page content; See docs/contenthandler.txt for details. + * (bug 35693) ApiQueryImageInfo now suppresses errors when unserializing metadata. === Languages updated in 1.21 === + MediaWiki supports over 350 languages. Many localisations are updated + regularly. Below only new and removed languages are listed, as well as + changes to languages because of Bugzilla reports. - === Other changes in 1.21 === == Compatibility == diff --cc includes/Article.php index 54d658cdde,db4444eefb..d16ee01a8d --- a/includes/Article.php +++ b/includes/Article.php @@@ -455,10 -380,10 +455,10 @@@ class Article extends Page // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks. // We should instead work with the Revision object when we need it... - $this->mContentObject = $this->mRevision->getContent( Revision::FOR_THIS_USER ); // Loads if user is allowed - $this->mContent = $this->mRevision->getText( Revision::FOR_THIS_USER, $this->getContext()->getUser() ); // Loads if user is allowed ++ $this->mContentObject = $this->mRevision->getContent( Revision::FOR_THIS_USER, $this->getContext()->getUser() ); // Loads if user is allowed $this->mRevIdFetched = $this->mRevision->getId(); - wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) ); + wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) ); wfProfileOut( __METHOD__ ); diff --cc includes/EditPage.php index 461997f10a,cc85a7daec..a1a2686a5f --- a/includes/EditPage.php +++ b/includes/EditPage.php @@@ -1047,16 -946,16 +1053,16 @@@ class EditPage $title = Title::newFromText( $preload ); # Check for existence to avoid getting MediaWiki:Noarticletext - if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) { + if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) { - return ''; + return $handler->makeEmptyContent(); } $page = WikiPage::factory( $title ); if ( $page->isRedirect() ) { $title = $page->getRedirectTarget(); # Same as before - if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) { + if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) { - return ''; + return $handler->makeEmptyContent(); } $page = WikiPage::factory( $title ); } @@@ -1378,8 -1261,8 +1384,8 @@@ // passed. if ( $this->summary === '' ) { $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle ); - $this->summary = wfMessage( 'newsectionsummary', $cleanSectionTitle ) - ->inContentLanguage()->text() ; + $this->summary = wfMessage( 'newsectionsummary' ) - ->rawParams( $cleanSectionTitle )->inContentLanguage()->text(); ++ ->rawParams( $cleanSectionTitle )->inContentLanguage()->text() ; } } elseif ( $this->summary !== '' ) { // Insert the section title above the content. @@@ -1583,12 -1454,11 +1589,12 @@@ ( ( $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'] = $content->isRedirect(); - $this->commitWatch(); - $result['redirect'] = Title::newFromRedirect( $text ) !== null; ++ $result['redirect'] = $content->isRedirect(); + $this->updateWatchlist(); wfProfileOut( __METHOD__ ); return $status; } else { diff --cc includes/WikiPage.php index b1dd910d1c,5a3a9e6a04..511f83a4f6 --- a/includes/WikiPage.php +++ b/includes/WikiPage.php @@@ -597,41 -554,21 +597,44 @@@ class WikiPage extends Page implements return null; } + /** + * Get the content of the current revision. No side-effects... + * + * @param $audience Integer: one of: + * Revision::FOR_PUBLIC to be displayed to all users + * Revision::FOR_THIS_USER to be displayed to $wgUser + * Revision::RAW get the text regardless of permissions ++ * @param $user User object to check for, only if FOR_THIS_USER is passed ++ * to the $audience parameter + * @return Content|null The content of the current revision + * + * @since 1.21 + */ - public function getContent( $audience = Revision::FOR_PUBLIC ) { ++ public function getContent( $audience = Revision::FOR_PUBLIC, User $user = null ) { + $this->loadLastEdit(); + if ( $this->mLastRevision ) { + return $this->mLastRevision->getContent( $audience ); + } + return null; + } + /** * Get the text of the current revision. No side-effects... * * @param $audience Integer: one of: * Revision::FOR_PUBLIC to be displayed to all users - * Revision::FOR_THIS_USER to be displayed to $wgUser + * Revision::FOR_THIS_USER to be displayed to the given user * Revision::RAW get the text regardless of permissions + * @param $user User object to check for, only if FOR_THIS_USER is passed + * to the $audience parameter - * @return String|bool The text of the current revision. False on failure + * @return String|false The text of the current revision + * @deprecated as of 1.21, getContent() should be used instead. */ - public function getText( $audience = Revision::FOR_PUBLIC ) { #@todo: deprecated, replace usage! - public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) { ++ public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) { #@todo: deprecated, replace usage! + wfDeprecated( __METHOD__, '1.21' ); - $this->loadLastEdit(); if ( $this->mLastRevision ) { - return $this->mLastRevision->getText( $audience ); + return $this->mLastRevision->getText( $audience, $user ); } return false; } diff --cc includes/api/ApiEditPage.php index 506a546531,2b9e849c6a..87302e6c64 --- a/includes/api/ApiEditPage.php +++ b/includes/api/ApiEditPage.php @@@ -82,9 -60,11 +82,9 @@@ class ApiEditPage extends ApiBase if ( $titleObj->isRedirect() ) { $oldTitle = $titleObj; - $titles = Title::newFromRedirectArray( - Revision::newFromTitle( - $oldTitle, false, Revision::READ_LATEST - )->getText( Revision::FOR_THIS_USER, $user ) - ); + $titles = Revision::newFromTitle( $oldTitle, false, Revision::READ_LATEST ) - ->getContent( Revision::FOR_THIS_USER ) ++ ->getContent( Revision::FOR_THIS_USER, $user ) + ->getRedirectChain(); // array_shift( $titles ); $redirValues = array(); diff --cc includes/content/WikitextContent.php index 465a402c42,0000000000..9526520978 mode 100644,000000..100644 --- a/includes/content/WikitextContent.php +++ b/includes/content/WikitextContent.php @@@ -1,289 -1,0 +1,289 @@@ +getNativeData(); + $sect = $wgParser->getSection( $text, $section, false ); + + return new WikitextContent( $sect ); + } + + /** + * @see Content::replaceSection() + */ + public function replaceSection( $section, Content $with, $sectionTitle = '' ) { + wfProfileIn( __METHOD__ ); + + $myModelId = $this->getModel(); + $sectionModelId = $with->getModel(); + + if ( $sectionModelId != $myModelId ) { + throw new MWException( "Incompatible content model for section: " . + "document uses $myModelId but " . + "section uses $sectionModelId." ); + } + + $oldtext = $this->getNativeData(); + $text = $with->getNativeData(); + + if ( $section === '' ) { + return $with; # XXX: copy first? + } if ( $section == 'new' ) { + # Inserting a new section + $subject = $sectionTitle ? wfMessage( 'newsectionheaderdefaultlevel' ) + ->rawParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : ''; + if ( wfRunHooks( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) { + $text = strlen( trim( $oldtext ) ) > 0 + ? "{$oldtext}\n\n{$subject}{$text}" + : "{$subject}{$text}"; + } + } else { + # Replacing an existing section; roll out the big guns + global $wgParser; + + $text = $wgParser->replaceSection( $oldtext, $section, $text ); + } + + $newContent = new WikitextContent( $text ); + + wfProfileOut( __METHOD__ ); + return $newContent; + } + + /** + * Returns a new WikitextContent object with the given section heading + * prepended. + * + * @param $header string + * @return Content + */ + public function addSectionHeader( $header ) { + $text = wfMessage( 'newsectionheaderdefaultlevel' ) - ->inContentLanguage()->params( $header )->text(); ++ ->rawParams( $header )->inContentLanguage()->text(); + $text .= "\n\n"; + $text .= $this->getNativeData(); + + return new WikitextContent( $text ); + } + + /** + * Returns a Content object with pre-save transformations applied using + * Parser::preSaveTransform(). + * + * @param $title Title + * @param $user User + * @param $popts ParserOptions + * @return Content + */ + public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) { + global $wgParser; + + $text = $this->getNativeData(); + $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts ); + + return new WikitextContent( $pst ); + } + + /** + * Returns a Content object with preload transformations applied (or this + * object if no transformations apply). + * + * @param $title Title + * @param $popts ParserOptions + * @return Content + */ + public function preloadTransform( Title $title, ParserOptions $popts ) { + global $wgParser; + + $text = $this->getNativeData(); + $plt = $wgParser->getPreloadText( $text, $title, $popts ); + + return new WikitextContent( $plt ); + } + + /** + * Implement redirect extraction for wikitext. + * + * @return null|Title + * + * @note: migrated here from Title::newFromRedirectInternal() + * + * @see Content::getRedirectTarget + * @see AbstractContent::getRedirectTarget + */ + public function getRedirectTarget() { + global $wgMaxRedirects; + if ( $wgMaxRedirects < 1 ) { + // redirects are disabled, so quit early + return null; + } + $redir = MagicWord::get( 'redirect' ); + $text = trim( $this->getNativeData() ); + 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; + } + + /** + * @see Content::updateRedirect() + * + * This implementation replaces the first link on the page with the given new target + * if this Content object is a redirect. Otherwise, this method returns $this. + * + * @since 1.21 + * + * @param Title $target + * + * @return Content a new Content object with the updated redirect (or $this if this Content object isn't a redirect) + */ + public function updateRedirect( Title $target ) { + if ( !$this->isRedirect() ) { + return $this; + } + + # Fix the text + # Remember that redirect pages can have categories, templates, etc., + # so the regex has to be fairly general + $newText = preg_replace( '/ \[ \[ [^\]]* \] \] /x', + '[[' . $target->getFullText() . ']]', + $this->getNativeData(), 1 ); + + return new WikitextContent( $newText ); + } + + /** + * Returns true if this content is not a redirect, and this content's text + * is countable according to the criteria defined by $wgArticleCountMethod. + * + * @param $hasLinks Bool if it is known whether this content contains + * links, provide this information here, to avoid redundant parsing to + * find out. + * @param $title null|\Title + * + * @internal param \IContextSource $context context for parsing if necessary + * + * @return bool True if the content is countable + */ + public function isCountable( $hasLinks = null, Title $title = null ) { + global $wgArticleCountMethod; + + if ( $this->isRedirect( ) ) { + return false; + } + + $text = $this->getNativeData(); + + switch ( $wgArticleCountMethod ) { + case 'any': + return true; + case 'comma': + return strpos( $text, ',' ) !== false; + case 'link': + if ( $hasLinks === null ) { # not known, find out + if ( !$title ) { + $context = RequestContext::getMain(); + $title = $context->getTitle(); + } + + $po = $this->getParserOutput( $title, null, null, false ); + $links = $po->getLinks(); + $hasLinks = !empty( $links ); + } + + return $hasLinks; + } + + return false; + } + + public function getTextForSummary( $maxlength = 250 ) { + $truncatedtext = parent::getTextForSummary( $maxlength ); + + # clean up unfinished links + # XXX: make this optional? wasn't there in autosummary, but required for + # deletion summary. + $truncatedtext = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $truncatedtext ); + + return $truncatedtext; + } + + /** + * Returns a ParserOutput object resulting from parsing the content's text + * using $wgParser. + * + * @since 1.21 + * + * @param $content Content the content to render + * @param $title \Title + * @param $revId null + * @param $options null|ParserOptions + * @param $generateHtml bool + * + * @internal param \IContextSource|null $context + * @return ParserOutput representing the HTML form of the text + */ + public function getParserOutput( Title $title, + $revId = null, + ParserOptions $options = null, $generateHtml = true + ) { + global $wgParser; + + if ( !$options ) { + //NOTE: use canonical options per default to produce cacheable output + $options = $this->getContentHandler()->makeParserOptions( 'canonical' ); + } + + $po = $wgParser->parse( $this->getNativeData(), $title, $options, true, true, $revId ); + return $po; + } + + protected function getHtml() { + throw new MWException( + "getHtml() not implemented for wikitext. " + . "Use getParserOutput()->getText()." + ); + } + + /** + * @see Content::matchMagicWord() + * + * This implementation calls $word->match() on the this TextContent object's text. + * + * @param MagicWord $word + * + * @return bool whether this Content object matches the given magic word. + */ + public function matchMagicWord( MagicWord $word ) { + return $word->match( $this->getNativeData() ); + } +} diff --cc includes/diff/DifferenceEngine.php index 48723a11d0,31fdc6dc8e..233e593417 --- a/includes/diff/DifferenceEngine.php +++ b/includes/diff/DifferenceEngine.php @@@ -1150,14 -1082,14 +1150,14 @@@ class DifferenceEngine extends ContextS return false; } if ( $this->mOldRev ) { - $this->mOldContent = $this->mOldRev->getContent( Revision::FOR_THIS_USER ); - $this->mOldtext = $this->mOldRev->getText( Revision::FOR_THIS_USER, $this->getUser() ); - if ( $this->mOldtext === false ) { ++ $this->mOldContent = $this->mOldRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ); + if ( $this->mOldContent === false ) { return false; } } if ( $this->mNewRev ) { - $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER ); - $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER, $this->getUser() ); - if ( $this->mNewtext === false ) { ++ $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ); + if ( $this->mNewContent === false ) { return false; } } @@@ -1178,7 -1110,7 +1178,7 @@@ if ( !$this->loadRevisionData() ) { return false; } - $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER ); - $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER, $this->getUser() ); ++ $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ); return true; } } diff --cc includes/installer/MysqlUpdater.php index a52c3a778c,98e13866eb..00009ca523 --- a/includes/installer/MysqlUpdater.php +++ b/includes/installer/MysqlUpdater.php @@@ -217,11 -216,8 +217,13 @@@ class MysqlUpdater extends DatabaseUpda array( 'dropField', 'category', 'cat_hidden', 'patch-cat_hidden.sql' ), // 1.21 + 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( 'dropField', 'site_stats', 'ss_admins', 'patch-drop-ss_admins.sql' ), + array( 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ), ); } diff --cc includes/installer/SqliteUpdater.php index b764335501,95a61c19b2..1c2443157a --- a/includes/installer/SqliteUpdater.php +++ b/includes/installer/SqliteUpdater.php @@@ -96,11 -95,8 +96,13 @@@ class SqliteUpdater extends DatabaseUpd array( 'dropField', 'category', 'cat_hidden', 'patch-cat_hidden.sql' ), // 1.21 + 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( 'dropField', 'site_stats', 'ss_admins', 'patch-drop-ss_admins.sql' ), + array( 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ), ); } diff --cc includes/job/DoubleRedirectJob.php index ebbd8343b6,f9c4b0fff2..b1b96b62ab --- a/includes/job/DoubleRedirectJob.php +++ b/includes/job/DoubleRedirectJob.php @@@ -122,13 -121,17 +121,13 @@@ class DoubleRedirectJob extends Job # Preserve fragment (bug 14904) $newTitle = Title::makeTitle( $newTitle->getNamespace(), $newTitle->getDBkey(), - $currentDest->getFragment() ); + $currentDest->getFragment(), $newTitle->getInterwiki() ); # Fix the text - # Remember that redirect pages can have categories, templates, etc., - # so the regex has to be fairly general - $newText = preg_replace( '/ \[ \[ [^\]]* \] \] /x', - '[[' . $newTitle->getFullText() . ']]', - $text, 1 ); - - if ( $newText === $text ) { - $this->setLastError( 'Text unchanged???' ); + $newContent = $content->updateRedirect( $newTitle ); + + if ( $newContent->equals( $content ) ) { + $this->setLastError( 'Content unchanged???' ); return false; } diff --cc includes/parser/Parser.php index 61b4239a35,9c15985f76..1902443a1e --- a/includes/parser/Parser.php +++ b/includes/parser/Parser.php @@@ -1948,8 -1961,11 +1961,14 @@@ class Parser # 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() ); ++ // XXX: the above check prevents links to sites with identifiers that are not language codes ++ + # Bug 24502: filter duplicates + if ( !isset( $this->mLangLinkLanguages[$iw] ) ) { + $this->mLangLinkLanguages[$iw] = true; + $this->mOutput->addLanguageLink( $nt->getFullText() ); + } ++ $s = rtrim( $s . $prefix ); $s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail; wfProfileOut( __METHOD__."-interwiki" ); diff --cc tests/phpunit/includes/LinksUpdateTest.php index a50a2235e2,8e37b139bd..5267e57232 --- a/tests/phpunit/includes/LinksUpdateTest.php +++ b/tests/phpunit/includes/LinksUpdateTest.php @@@ -143,12 -148,10 +148,12 @@@ class LinksUpdateTest extends MediaWiki #@todo: test recursive, too! - protected function assertLinksUpdate( Title $title, ParserOutput $parserOutput, $table, $fields, $condition, Array $expectedRows ) { + protected function assertLinksUpdate( Title $title, ParserOutput $parserOutput, $table, $fields, $condition, array $expectedRows ) { $update = new LinksUpdate( $title, $parserOutput ); + $update->beginTransaction(); $update->doUpdate(); + $update->commitTransaction(); $this->assertSelect( $table, $fields, $condition, $expectedRows ); }