merged master
authordaniel <daniel.kinzler@wikimedia.de>
Mon, 23 Jul 2012 20:07:18 +0000 (22:07 +0200)
committerdaniel <daniel.kinzler@wikimedia.de>
Mon, 23 Jul 2012 20:07:18 +0000 (22:07 +0200)
Change-Id: Iad12ee382d6aeb1fab6fefb611d290b74865ea4b

40 files changed:
1  2 
RELEASE-NOTES-1.20
docs/hooks.txt
includes/Article.php
includes/AutoLoader.php
includes/DefaultSettings.php
includes/Defines.php
includes/EditPage.php
includes/Export.php
includes/ImagePage.php
includes/Import.php
includes/LinksUpdate.php
includes/Namespace.php
includes/OutputPage.php
includes/Revision.php
includes/SqlDataUpdate.php
includes/Title.php
includes/WikiPage.php
includes/api/ApiDelete.php
includes/api/ApiEditPage.php
includes/api/ApiMain.php
includes/api/ApiParse.php
includes/api/ApiQueryRevisions.php
includes/diff/DifferenceEngine.php
includes/filerepo/file/LocalFile.php
includes/job/DoubleRedirectJob.php
includes/parser/Parser.php
includes/parser/ParserOutput.php
includes/resourceloader/ResourceLoaderWikiModule.php
includes/search/SearchEngine.php
includes/specials/SpecialBooksources.php
includes/specials/SpecialUndelete.php
languages/Language.php
languages/messages/MessagesEn.php
languages/messages/MessagesQqq.php
maintenance/cleanupSpam.php
tests/phpunit/includes/WikiPageTest.php
tests/phpunit/includes/filerepo/FileBackendTest.php
tests/phpunit/maintenance/DumpTestCase.php
tests/phpunit/maintenance/backupPrefetchTest.php
tests/phpunit/maintenance/backupTextPassTest.php

@@@ -74,8 -82,23 +82,25 @@@ upgrade PHP if you have not done so pri
  * 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>
+   tags with class "mw-watched". Instead, each line now has a class
+   "mw-changeslist-line-watched" or "mw-changeslist-line-not-watched", and the
+   title itself is surrounded by <span></span> tags with class "mw-title".
+ * Added ContribsPager::reallyDoQuery hook allowing extensions to data to MyContribs
+ * Added new hook ParserAfterParse to allow extensions to affect parsed output
+   after the parse is complete but before block level processing, link holder
+   replacement, and so on.
+ * (bug 34678) Added InternalParseBeforeSanitize hook which gets called during Parser's
+   internalParse method just before the parser removes unwanted/dangerous HTML tags.
+ * (bug 36783) Implement jQuery Promise interface in mediawiki.api module.
+ * Make dates in sortable tables sort according to the page content language
+   instead of the site content language
+ * (bug 37926) Deleterevision will no longer allow users to delete log entries,
+   the new deletelogentry permission is required for this.
  
  === Bug fixes in 1.20 ===
  * (bug 30245) Use the correct way to construct a log page title.
diff --cc docs/hooks.txt
Simple merge
Simple merge
Simple merge
@@@ -655,30 -710,20 +710,30 @@@ $wgTrustedMediaFormats = array
   * Each entry in the array maps a MIME type to a class name
   */
  $wgMediaHandlers = array(
-       'image/jpeg' => 'JpegHandler',
-       'image/png' => 'PNGHandler',
-       'image/gif' => 'GIFHandler',
-       'image/tiff' => 'TiffHandler',
+       'image/jpeg'     => 'JpegHandler',
+       'image/png'      => 'PNGHandler',
+       'image/gif'      => 'GIFHandler',
+       'image/tiff'     => 'TiffHandler',
        'image/x-ms-bmp' => 'BmpHandler',
-       'image/x-bmp' => 'BmpHandler',
-       'image/x-xcf' => 'XCFHandler',
-       'image/svg+xml' => 'SvgHandler', // official
-       'image/svg' => 'SvgHandler', // compat
+       'image/x-bmp'    => 'BmpHandler',
+       'image/x-xcf'    => 'XCFHandler',
+       'image/svg+xml'  => 'SvgHandler', // official
+       'image/svg'      => 'SvgHandler', // compat
        'image/vnd.djvu' => 'DjVuHandler', // official
-       'image/x.djvu' => 'DjVuHandler', // compat
-       'image/x-djvu' => 'DjVuHandler', // compat
+       'image/x.djvu'   => 'DjVuHandler', // compat
+       'image/x-djvu'   => 'DjVuHandler', // compat
  );
  
 +/**
 + * Plugins for page content model handling.
 + * Each entry in the array maps a model id to a class name
 + */
 +$wgContentHandlers = array(
 +      CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler', // the usual case
 +      CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler', // dumb version, no syntax highlighting
 +      CONTENT_MODEL_CSS => 'CssContentHandler', // dumb version, no syntax highlighting
 +);
 +
  /**
   * Resizing can be done using PHP's internal image libraries or using
   * ImageMagick or another third-party converter, e.g. GraphicMagick.
Simple merge
@@@ -1306,146 -1201,175 +1306,146 @@@ class EditPage 
  
                wfProfileOut( __METHOD__ . '-checks' );
  
-               // Use SELECT FOR UPDATE here to avoid transaction collision in
-               // WikiPage::updateRevisionOn() and ending in the self::AS_END case.
-               $this->mArticle->loadPageData( 'forupdate' );
+               # Load the page data from the master. If anything changes in the meantime,
+               # we detect it by using page_latest like a token in a 1 try compare-and-swap.
+               $this->mArticle->loadPageData( 'fromdbmaster' );
                $new = !$this->mArticle->exists();
  
 -              if ( $new ) {
 -                      // Late check for create permission, just in case *PARANOIA*
 -                      if ( !$this->mTitle->userCan( 'create' ) ) {
 -                              $status->fatal( 'nocreatetext' );
 -                              $status->value = self::AS_NO_CREATE_PERMISSION;
 -                              wfDebug( __METHOD__ . ": no create permission\n" );
 -                              wfProfileOut( __METHOD__ );
 -                              return $status;
 -                      }
 +              try {
 +                      if ( $new ) {
 +                              // Late check for create permission, just in case *PARANOIA*
 +                              if ( !$this->mTitle->userCan( 'create' ) ) {
 +                                      $status->fatal( 'nocreatetext' );
 +                                      $status->value = self::AS_NO_CREATE_PERMISSION;
 +                                      wfDebug( __METHOD__ . ": no create permission\n" );
 +                                      wfProfileOut( __METHOD__ );
 +                                      return $status;
 +                              }
  
 -                      # Don't save a new article if it's blank.
 -                      if ( $this->textbox1 == '' ) {
 -                              $status->setResult( false, self::AS_BLANK_ARTICLE );
 -                              wfProfileOut( __METHOD__ );
 -                              return $status;
 -                      }
 +                              # Don't save a new article if it's blank.
 +                              if ( $this->textbox1 == '' ) {
 +                                      $status->setResult( false, self::AS_BLANK_ARTICLE );
 +                                      wfProfileOut( __METHOD__ );
 +                                      return $status;
 +                              }
  
 -                      // Run post-section-merge edit filter
 -                      if ( !wfRunHooks( 'EditFilterMerged', array( $this, $this->textbox1, &$this->hookError, $this->summary ) ) ) {
 -                              # Error messages etc. could be handled within the hook...
 -                              $status->fatal( 'hookaborted' );
 -                              $status->value = self::AS_HOOK_ERROR;
 -                              wfProfileOut( __METHOD__ );
 -                              return $status;
 -                      } elseif ( $this->hookError != '' ) {
 -                              # ...or the hook could be expecting us to produce an error
 -                              $status->fatal( 'hookaborted' );
 -                              $status->value = self::AS_HOOK_ERROR_EXPECTED;
 -                              wfProfileOut( __METHOD__ );
 -                              return $status;
 -                      }
 +                              // Run post-section-merge edit filter
 +                              if ( !wfRunHooks( 'EditFilterMerged', array( $this, $this->textbox1, &$this->hookError, $this->summary ) ) ) {
 +                                      # Error messages etc. could be handled within the hook...
 +                                      $status->fatal( 'hookaborted' );
 +                                      $status->value = self::AS_HOOK_ERROR;
 +                                      wfProfileOut( __METHOD__ );
 +                                      return $status;
 +                              } elseif ( $this->hookError != '' ) {
 +                                      # ...or the hook could be expecting us to produce an error
 +                                      $status->fatal( 'hookaborted' );
 +                                      $status->value = self::AS_HOOK_ERROR_EXPECTED;
 +                                      wfProfileOut( __METHOD__ );
 +                                      return $status;
 +                              }
  
 -                      $text = $this->textbox1;
 -                      $result['sectionanchor'] = '';
 -                      if ( $this->section == 'new' ) {
 -                              if ( $this->sectiontitle !== '' ) {
 -                                      // Insert the section title above the content.
 -                                      $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->sectiontitle ) . "\n\n" . $text;
 -
 -                                      // Jump to the new section
 -                                      $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
 -
 -                                      // If no edit summary was specified, create one automatically from the section
 -                                      // title and have it link to the new section. Otherwise, respect the summary as
 -                                      // passed.
 -                                      if ( $this->summary === '' ) {
 -                                              $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
 -                                              $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
 -                                      }
 -                              } elseif ( $this->summary !== '' ) {
 -                                      // Insert the section title above the content.
 -                                      $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $text;
 +                              $content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format );
  
 -                                      // Jump to the new section
 -                                      $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
 +                              $result['sectionanchor'] = '';
 +                              if ( $this->section == 'new' ) {
 +                                      if ( $this->sectiontitle !== '' ) {
 +                                              // Insert the section title above the content.
 +                                              $content = $content->addSectionHeader( $this->sectiontitle );
 +
 +                                              // Jump to the new section
 +                                              $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
 +
 +                                              // If no edit summary was specified, create one automatically from the section
 +                                              // title and have it link to the new section. Otherwise, respect the summary as
 +                                              // passed.
 +                                              if ( $this->summary === '' ) {
 +                                                      $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
 +                                                      $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
 +                                              }
 +                                      } elseif ( $this->summary !== '' ) {
 +                                              // Insert the section title above the content.
 +                                              $content = $content->addSectionHeader( $this->sectiontitle );
  
 -                                      // Create a link to the new section from the edit summary.
 -                                      $cleanSummary = $wgParser->stripSectionName( $this->summary );
 -                                      $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
 +                                              // 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 );
 +                                      }
                                }
 -                      }
  
 -                      $status->value = self::AS_SUCCESS_NEW_ARTICLE;
 +                              $status->value = self::AS_SUCCESS_NEW_ARTICLE;
  
 -              } else {
 +                      } else { # not $new
  
 -                      # Article exists. Check for edit conflict.
 -                      $timestamp = $this->mArticle->getTimestamp();
 -                      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;
 -                                              wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
                                        }
 -                              } elseif ( $this->section == '' && $this->userWasLastToEdit( $wgUser->getId(), $this->edittime ) ) {
 -                                      # Suppress edit conflict with self, except for section edits where merging is required.
 -                                      wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
 -                                      $this->isConflict = false;
                                }
 -                      }
 -
 -                      // If sectiontitle is set, use it, otherwise use the summary as the section title (for
 -                      // backwards compatibility with old forms/bots).
 -                      if ( $this->sectiontitle !== '' ) {
 -                              $sectionTitle = $this->sectiontitle;
 -                      } else {
 -                              $sectionTitle = $this->summary;
 -                      }
  
 -                      if ( $this->isConflict ) {
 -                              wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" );
 -                              $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle, $this->edittime );
 -                      } else {
 -                              wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
 -                              $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle );
 -                      }
 -                      if ( is_null( $text ) ) {
 -                              wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
 -                              $this->isConflict = true;
 -                              $text = $this->textbox1; // do not try to merge here!
 -                      } elseif ( $this->isConflict ) {
 -                              # Attempt merge
 -                              if ( $this->mergeChangesInto( $text ) ) {
 -                                      // Successful merge! Maybe we should tell the user the good news?
 -                                      $this->isConflict = false;
 -                                      wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
 +                              // If sectiontitle is set, use it, otherwise use the summary as the section title (for
 +                              // backwards compatibility with old forms/bots).
 +                              if ( $this->sectiontitle !== '' ) {
 +                                      $sectionTitle = $this->sectiontitle;
                                } else {
 -                                      $this->section = '';
 -                                      $this->textbox1 = $text;
 -                                      wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
 +                                      $sectionTitle = $this->summary;
                                }
 -                      }
  
 -                      if ( $this->isConflict ) {
 -                              $status->setResult( false, self::AS_CONFLICT_DETECTED );
 -                              wfProfileOut( __METHOD__ );
 -                              return $status;
 -                      }
 +                              $textbox_content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format );
 +                              $content = null;
  
 -                      // Run post-section-merge edit filter
 -                      if ( !wfRunHooks( 'EditFilterMerged', array( $this, $text, &$this->hookError, $this->summary ) ) ) {
 -                              # Error messages etc. could be handled within the hook...
 -                              $status->fatal( 'hookaborted' );
 -                              $status->value = self::AS_HOOK_ERROR;
 -                              wfProfileOut( __METHOD__ );
 -                              return $status;
 -                      } elseif ( $this->hookError != '' ) {
 -                              # ...or the hook could be expecting us to produce an error
 -                              $status->fatal( 'hookaborted' );
 -                              $status->value = self::AS_HOOK_ERROR_EXPECTED;
 -                              wfProfileOut( __METHOD__ );
 -                              return $status;
 -                      }
 +                              if ( $this->isConflict ) {
 +                                      wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" );
 +                                      $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle, $this->edittime );
 +                              } else {
 +                                      wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
 +                                      $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle );
 +                              }
  
 -                      # Handle the user preference to force summaries here, but not for null edits
 -                      if ( $this->section != 'new' && !$this->allowBlankSummary
 -                              && $this->getOriginalContent() != $text
 -                              && !Title::newFromRedirect( $text ) ) # check if it's not a redirect
 -                      {
 -                              if ( md5( $this->summary ) == $this->autoSumm ) {
 -                                      $this->missingSummary = true;
 -                                      $status->fatal( 'missingsummary' );
 -                                      $status->value = self::AS_SUMMARY_NEEDED;
 -                                      wfProfileOut( __METHOD__ );
 -                                      return $status;
 +                              if ( is_null( $content ) ) {
 +                                      wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
 +                                      $this->isConflict = true;
 +                                      $content = $textbox_content; // do not try to merge here!
 +                              } elseif ( $this->isConflict ) {
 +                                      # Attempt merge
 +                                      if ( $this->mergeChangesIntoContent( $textbox_content ) ) {
 +                                              // Successful merge! Maybe we should tell the user the good news?
 +                                              $this->isConflict = false;
 +                                              $content = $textbox_content;
 +                                              wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
 +                                      } else {
 +                                              $this->section = '';
 +                                              #$this->textbox1 = $text; #redundant, nothing to do here?
 +                                              wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
 +                                      }
                                }
 -                      }
  
 -                      # And a similar thing for new sections
 -                      if ( $this->section == 'new' && !$this->allowBlankSummary ) {
 -                              if ( trim( $this->summary ) == '' ) {
 -                                      $this->missingSummary = true;
 -                                      $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
 -                                      $status->value = self::AS_SUMMARY_NEEDED;
 +                              if ( $this->isConflict ) {
 +                                      $status->setResult( false, self::AS_CONFLICT_DETECTED );
                                        wfProfileOut( __METHOD__ );
                                        return $status;
                                }
         * @private
         * @todo document
         *
-        * @parma $editText string
+        * @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 ); #XXX: really serialize?!
 +                      return true;
 +              } else {
 +                      return false;
 +              }
 +      }
 +
 +      /**
 +       * @private
 +       * @todo document
 +       *
 +       * @parma $editText string
 +       *
 +       * @return bool
 +       * @since since 1.WD
         */
 -      function mergeChangesInto( &$editText ) {
 +      private function mergeChangesIntoContent( &$editContent ){
                wfProfileIn( __METHOD__ );
  
                $db = wfGetDB( DB_MASTER );
@@@ -58,6 -58,14 +58,14 @@@ class WikiExporter 
         */
        var $sink;
  
 -              return "0.7";
+       /**
+        * Returns the export schema version.
+        * @return string
+        */
+       public static function schemaVersion() {
++              return "0.7"; #FIXME: bump this when pushing ContentHandler additions.
+       }
        /**
         * If using WikiExporter::STREAM to stream a large amount of data,
         * provide a database connection which is not managed by
@@@ -155,11 -155,9 +155,11 @@@ class ImagePage extends Article 
                        # should be in page content language
                        $pageLang = $this->getTitle()->getPageLanguage();
                        $out->addHTML( Xml::openElement( 'div', array( 'id' => 'mw-imagepage-content',
-                               'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
+                               'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(),
                                'class' => 'mw-content-'.$pageLang->getDir() ) ) );
 +
                        parent::view();
 +
                        $out->addHTML( Xml::closeElement( 'div' ) );
                } else {
                        # Just need to set the right headers
Simple merge
@@@ -821,18 -822,12 +821,16 @@@ class LinksDeletionUpdate extends SqlDa
        /**
         * Constructor
         *
-        * @param $title Title of the page we're updating
-        * @param $parserOutput ParserOutput: output from a full parse of this page
-        * @param $recursive Boolean: queue jobs for recursive updates?
+        * @param $page WikiPage Page we are updating
         */
 -      function __construct( WikiPage $page ) {
 +      function __construct( Title $title ) {
                parent::__construct( );
  
 -              $this->mPage = $page;
 +              $this->mTitle = $title;
 +
 +              if ( !$title->getArticleID() ) {
 +                      throw new MWException( "The Title object did not provide an article ID. Perhaps the page doesn't exist?" );
 +              }
        }
  
        /**
Simple merge
Simple merge
@@@ -1200,10 -1024,8 +1227,10 @@@ class Revision implements IDBAccessObje
  
                wfProfileIn( __METHOD__ );
  
 +              $this->checkContentModel();
 +
                $data = $this->mText;
-               $flags = Revision::compressRevisionText( $data );
+               $flags = self::compressRevisionText( $data );
  
                # Write to external storage if required
                if( $wgDefaultExternalStore ) {
                $rev_id = isset( $this->mId )
                        ? $this->mId
                        : $dbw->nextSequenceValue( 'revision_rev_id_seq' );
 -              $dbw->insert( 'revision',
 -                      array(
 -                              'rev_id'         => $rev_id,
 -                              'rev_page'       => $this->mPage,
 -                              'rev_text_id'    => $this->mTextId,
 -                              'rev_comment'    => $this->mComment,
 -                              'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
 -                              'rev_user'       => $this->mUser,
 -                              'rev_user_text'  => $this->mUserText,
 -                              'rev_timestamp'  => $dbw->timestamp( $this->mTimestamp ),
 -                              'rev_deleted'    => $this->mDeleted,
 -                              'rev_len'        => $this->mSize,
 -                              'rev_parent_id'  => is_null( $this->mParentId )
 -                                      ? $this->getPreviousRevisionId( $dbw )
 -                                      : $this->mParentId,
 -                              'rev_sha1'       => is_null( $this->mSha1 )
 -                                      ? self::base36Sha1( $this->mText )
 -                                      : $this->mSha1
 -                      ), __METHOD__
 +              $row = array(
 +                      'rev_id'         => $rev_id,
 +                      'rev_page'       => $this->mPage,
 +                      'rev_text_id'    => $this->mTextId,
 +                      'rev_comment'    => $this->mComment,
 +                      'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
 +                      'rev_user'       => $this->mUser,
 +                      'rev_user_text'  => $this->mUserText,
 +                      'rev_timestamp'  => $dbw->timestamp( $this->mTimestamp ),
 +                      'rev_deleted'    => $this->mDeleted,
 +                      'rev_len'        => $this->mSize,
 +                      'rev_parent_id'  => is_null( $this->mParentId )
 +                              ? $this->getPreviousRevisionId( $dbw )
 +                              : $this->mParentId,
 +                      'rev_sha1'       => is_null( $this->mSha1 )
 +                              ? Revision::base36Sha1( $this->mText )
 +                              : $this->mSha1,
                );
  
 +              if ( $wgContentHandlerUseDB ) {
 +                      //NOTE: Store null for the default model and format, to save space.
 +                      //XXX: Makes the DB sensitive to changed defaults. Make this behaviour optional? Only in miser mode?
 +
 +                      $model = $this->getContentModel();
 +                      $format = $this->getContentFormat();
 +
 +                      $defaultModel = ContentHandler::getDefaultModelFor( $this->getTitle() );
 +                      $defaultFormat = ContentHandler::getForModelID( $defaultModel )->getDefaultFormat();
 +
 +                      $row[ 'rev_content_model' ] = ( $model === $defaultModel ) ? null : $model;
 +                      $row[ 'rev_content_format' ] = ( $format === $defaultFormat ) ? null : $format;
 +              }
 +
 +              $dbw->insert( 'revision', $row, __METHOD__ );
 +
                $this->mId = !is_null( $rev_id ) ? $rev_id : $dbw->insertId();
  
                wfRunHooks( 'RevisionInsertComplete', array( &$this, $data, $flags ) );
Simple merge
Simple merge
@@@ -1724,6 -1494,10 +1707,10 @@@ class WikiPage extends Page 
  
                                wfProfileOut( __METHOD__ );
                                return $status;
 -                      } elseif ( $oldtext === false ) {
++                      } elseif ( !$old_content ) {
+                               # Sanity check for bug 37225
+                               wfProfileOut( __METHOD__ );
+                               throw new MWException( "Could not find text for current revision {$oldid}." );
                        }
  
                        $revision = new Revision( array(
                                'parent_id'  => $oldid,
                                'user'       => $user->getId(),
                                'user_text'  => $user->getName(),
 -                              'timestamp'  => $now
 -                      ) );
 +                              'timestamp'  => $now,
 +                              'content_model' => $content->getModel(),
 +                              'content_format' => $serialisation_format,
 +                      ) ); #XXX: pass content object?!
 +
+                       # Bug 37225: use accessor to get the text as Revision may trim it.
+                       # After trimming, the text may be a duplicate of the current text.
 -                      $text = $revision->getText(); // sanity; EditPage should trim already
++                      $content = $revision->getContent(); // sanity; EditPage should trim already
 -                      $changed = ( strcmp( $text, $oldtext ) != 0 );
 +                      $changed = !$content->equals( $old_content );
  
                        if ( $changed ) {
 +                              if ( !$content->isValid() ) {
 +                                      throw new MWException( "New content failed validity check!" );
 +                              }
 +
                                $dbw->begin( __METHOD__ );
 +
 +                              $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
 +                              $status->merge( $prepStatus );
 +
 +                              if ( !$status->isOK() ) {
 +                                      $dbw->rollback();
 +
 +                                      wfProfileOut( __METHOD__ );
 +                                      return $status;
 +                              }
 +
                                $revisionId = $revision->insertOn( $dbw );
  
                                # Update page
  
                if ( !$ok ) {
                        $dbw->rollback( __METHOD__ );
-                       return WikiPage::DELETE_NO_REVISIONS;
+                       $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
+                       return $status;
                }
  
 -              $this->doDeleteUpdates( $id );
 +              $this->doDeleteUpdates( $id, $content );
  
                # Log the deletion, if the page was suppressed, log it at Oversight instead
                $logtype = $suppress ? 'suppress' : 'delete';
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -1456,11 -1456,11 +1456,11 @@@ class LocalFile extends File 
         */
        function getDescriptionText() {
                global $wgParser;
-               $revision = Revision::newFromTitle( $this->title );
+               $revision = Revision::newFromTitle( $this->title, false, Revision::AVOID_MASTER );
                if ( !$revision ) return false;
 -              $text = $revision->getText();
 -              if ( !$text ) return false;
 -              $pout = $wgParser->parse( $text, $this->title, new ParserOptions() );
 +              $content = $revision->getContent();
 +              if ( !$content ) return false;
 +              $pout = $content->getParserOutput( $this->title, null, new ParserOptions() );
                return $pout->getText();
        }
  
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -143,8 -143,8 +143,8 @@@ class SpecialBookSources extends Specia
                $page = $this->msg( 'booksources' )->inContentLanguage()->text();
                $title = Title::makeTitleSafe( NS_PROJECT, $page ); # Show list in content language
                if( is_object( $title ) && $title->exists() ) {
-                       $rev = Revision::newFromTitle( $title );
+                       $rev = Revision::newFromTitle( $title, false, Revision::AVOID_MASTER );
 -                      $this->getOutput()->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $rev->getText() ) );
 +                      $this->getOutput()->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $rev->getText() ) ); #FIXME: need a way to do this via ContentHandler (or enforce flat text-based content)
                        return true;
                }
  
@@@ -940,8 -920,9 +951,9 @@@ class SpecialUndelete extends SpecialPa
                                $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(), $currentRev->getContent() ) .
++                              $previousRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ),
++                              $currentRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ) ) .
                        "</table>" .
                        "</div>\n"
                );
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -614,19 -478,7 +614,19 @@@ more stuf
                $this->assertEquals( $expected, $text );
        }
  
-       /* @FIXME: fix this!
 +      /**
 +       * @dataProvider dataReplaceSection
 +       */
 +      public function testReplaceSectionContent( $title, $text, $section, $with, $sectionTitle, $expected ) {
 +              $page = $this->createPage( $title, $text );
 +
 +              $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() );
 +              $c = $page->replaceSectionContent( $section, $content, $sectionTitle );
 +
 +              $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) );
 +      }
 +      
+       /* @todo FIXME: fix this!
        public function testGetUndoText() {
                global $wgDiff3;