merged latest master
authordaniel <daniel.kinzler@wikimedia.de>
Mon, 14 May 2012 21:24:18 +0000 (23:24 +0200)
committerdaniel <daniel.kinzler@wikimedia.de>
Mon, 14 May 2012 21:24:18 +0000 (23:24 +0200)
35 files changed:
1  2 
includes/Article.php
includes/AutoLoader.php
includes/DefaultSettings.php
includes/EditPage.php
includes/Export.php
includes/ImagePage.php
includes/LinksUpdate.php
includes/Namespace.php
includes/Revision.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/ApiPurge.php
includes/api/ApiQueryRevisions.php
includes/diff/DifferenceEngine.php
includes/installer/Ibm_db2Updater.php
includes/installer/MysqlUpdater.php
includes/installer/OracleUpdater.php
includes/installer/SqliteUpdater.php
includes/job/RefreshLinksJob.php
includes/parser/ParserOutput.php
includes/resourceloader/ResourceLoaderWikiModule.php
includes/specials/SpecialUndelete.php
languages/messages/MessagesEn.php
languages/messages/MessagesQqq.php
maintenance/tables.sql
tests/phpunit/includes/RevisionStorageTest.php
tests/phpunit/includes/TitleMethodsTest.php
tests/phpunit/includes/WikiPageTest.php
tests/phpunit/includes/filerepo/FileBackendTest.php
tests/phpunit/maintenance/DumpTestCase.php
tests/phpunit/phpunit.php

@@@ -196,34 -188,9 +196,33 @@@ class Article extends Page 
         * This function has side effects! Do not use this function if you
         * only want the real revision text if any.
         *
-        * @return string The text of this revision
 +       * @deprecated in 1.WD; use getContentObject() instead
 +       *
+        * @return string Return the text of this revision
         */
        public function getContent() {
-        * @return Content
 +              wfDeprecated( __METHOD__, '1.WD' );
 +              $content = $this->getContentObject();
 +              return ContentHandler::getContentText( $content );
 +      }
 +
 +      /**
 +       * Returns a Content object representing the pages effective display content,
 +     * not necessarily the revision's content!
 +     *
 +     * Note that getContent/loadContent do not follow redirects anymore.
 +       * If you need to fetch redirectable content easily, try
 +       * the shortcut in WikiPage::getRedirectTarget()
 +       *
 +       * This function has side effects! Do not use this function if you
 +       * only want the real revision text if any.
 +       *
++       * @return Content Return the content of this revision
 +       *
 +       * @since 1.WD
 +       */
 +   protected function getContentObject() {
 +              global $wgUser;
                wfProfileIn( __METHOD__ );
  
                if ( $this->mPage->getID() === 0 ) {
                                                if ( $rt ) {
                                                        wfDebug( __METHOD__ . ": showing redirect=no page\n" );
                                                        # Viewing a redirect page (e.g. with parameter redirect=no)
-                                                       $wgOut->addHTML( $this->viewRedirect( $rt ) );
+                                                       $outputPage->addHTML( $this->viewRedirect( $rt ) );
                                                        # Parse just to get categories, displaytitle, etc.
 -                                                      $this->mParserOutput = $wgParser->parse( $text, $this->getTitle(), $parserOptions );
 -                                                      $outputPage->addParserOutputNoText( $this->mParserOutput );
 +                                                      $this->mParserOutput = $content->getParserOutput( $this->getContext(), $oldid, $parserOptions, false );
 +                                                      $wgOut->addParserOutputNoText( $this->mParserOutput );
                                                        $outputDone = true;
                                                }
                                        }
         * Article::view() only, other callers should use the DifferenceEngine class.
         */
        public function showDiffPage() {
-               global $wgRequest, $wgUser;
-               $diff = $wgRequest->getVal( 'diff' );
-               $rcid = $wgRequest->getVal( 'rcid' );
-               $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
-               $purge = $wgRequest->getVal( 'action' ) == 'purge';
-               $unhide = $wgRequest->getInt( 'unhide' ) == 1;
+               $request = $this->getContext()->getRequest();
+               $user = $this->getContext()->getUser();
+               $diff = $request->getVal( 'diff' );
+               $rcid = $request->getVal( 'rcid' );
+               $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) );
+               $purge = $request->getVal( 'action' ) == 'purge';
+               $unhide = $request->getInt( 'unhide' ) == 1;
                $oldid = $this->getOldID();
  
 -              $de = new DifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide );
 +              $contentHandler = ContentHandler::getForTitle( $this->getTitle() );
 +              $de = $contentHandler->createDifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide );
 +
                // DifferenceEngine directly fetched the revision:
                $this->mRevIdFetched = $de->mNewid;
                $de->showDiffPage( $diffOnly );
Simple merge
Simple merge
@@@ -1284,145 -1179,175 +1284,146 @@@ class EditPage 
  
                wfProfileOut( __METHOD__ . '-checks' );
  
-               # If article is new, insert it.
-               $aid = $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
-               $new = ( $aid == 0 );
+               // Use SELECT FOR UPDATE here to avoid transaction collision in
+               // WikiPage::updateRevisionOn() and ending in the self::AS_END case.
+               $this->mArticle->loadPageData( 'forupdate' );
+               $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;
                                }
@@@ -591,19 -641,9 +641,19 @@@ class XmlDumpWriter 
                if ( $row->rev_deleted & Revision::DELETED_COMMENT ) {
                        $out .= "      " . Xml::element( 'comment', array( 'deleted' => 'deleted' ) ) . "\n";
                } elseif ( $row->rev_comment != '' ) {
-                       $out .= "      " . Xml::elementClean( 'comment', null, strval( $row->rev_comment ) ) . "\n";
+                       $out .= "      " . Xml::elementClean( 'comment', array(), strval( $row->rev_comment ) ) . "\n";
                }
  
 +              if ( isset( $row->rev_content_model ) && !is_null( $row->rev_content_model )  ) {
 +                      $name = ContentHandler::getContentModelName( $row->rev_content_model );
 +                      $out .= "      " . Xml::element('model', array( 'name' => $name ), strval( $row->rev_content_model ) ) . "\n";
 +              }
 +
 +              if ( isset( $row->rev_content_format ) && !is_null( $row->rev_content_format ) ) {
 +                      $mime = ContentHandler::getContentFormatMimeType( $row->rev_content_format );
 +                      $out .= "      " . Xml::element('format', array( 'mime' => $mime ), strval( $row->rev_content_format ) ) . "\n";
 +              }
 +
                $text = '';
                if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
                        $out .= "      " . Xml::element( 'text', array( 'deleted' => 'deleted' ) ) . "\n";
@@@ -132,17 -154,15 +154,17 @@@ class ImagePage extends Article 
                        # NS_FILE is in the user language, but this section (the actual wikitext)
                        # should be in page content language
                        $pageLang = $this->getTitle()->getPageLanguage();
-                       $wgOut->addHTML( Xml::openElement( 'div', array( 'id' => 'mw-imagepage-content',
+                       $out->addHTML( Xml::openElement( 'div', array( 'id' => 'mw-imagepage-content',
                                'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
                                'class' => 'mw-content-'.$pageLang->getDir() ) ) );
 -                      parent::view();
 -                      $out->addHTML( Xml::closeElement( 'div' ) );
 +
 +            parent::view(); #FIXME: use ContentHandler::makeArticle() !!
 +
 +                      $wgOut->addHTML( Xml::closeElement( 'div' ) );
                } else {
                        # Just need to set the right headers
-                       $wgOut->setArticleFlag( true );
-                       $wgOut->setPageTitle( $this->getTitle()->getPrefixedText() );
+                       $out->setArticleFlag( true );
+                       $out->setPageTitle( $this->getTitle()->getPrefixedText() );
                        $this->mPage->doViewUpdates( $this->getContext()->getUser() );
                }
  
                return $r;
        }
  
 -      /**
 -       * Overloading Article's getContent method.
 -       *
 -       * Omit noarticletext if sharedupload; text will be fetched from the
 -       * shared upload server if possible.
 -       * @return string
 -       */
 -      public function getContent() {
 -              $this->loadFile();
 -              if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getID() ) {
 -                      return '';
 -              }
 -              return parent::getContent();
 -      }
 +    /**
 +     * Overloading Article's getContentObject method.
 +     *
 +     * Omit noarticletext if sharedupload; text will be fetched from the
 +     * shared upload server if possible.
 +     * @return string
 +     */
 +    public function getContentObject() {
 +        $this->loadFile();
 +        if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getID() ) {
 +            return null;
 +        }
 +        return parent::getContentObject();
 +    }
  
        protected function openShowImage() {
-               global $wgOut, $wgUser, $wgImageLimits, $wgRequest,
-                       $wgLang, $wgEnableUploads, $wgSend404Code;
+               global $wgImageLimits, $wgEnableUploads, $wgSend404Code;
  
                $this->loadFile();
+               $out = $this->getContext()->getOutput();
+               $user = $this->getContext()->getUser();
+               $lang = $this->getContext()->getLanguage();
+               $dirmark = $lang->getDirMarkEntity();
+               $request = $this->getContext()->getRequest();
  
-               $sizeSel = intval( $wgUser->getOption( 'imagesize' ) );
+               $sizeSel = intval( $user->getOption( 'imagesize' ) );
                if ( !isset( $wgImageLimits[$sizeSel] ) ) {
                        $sizeSel = User::getDefaultOption( 'imagesize' );
  
   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
   * http://www.gnu.org/copyleft/gpl.html
   *
+  * @file
+  */
+ /**
+  * See docs/deferred.txt
+  *
   * @todo document (e.g. one-sentence top-level class description).
   */
 -class LinksUpdate {
 +class LinksUpdate extends SecondaryDBDataUpdate {
  
        /**@{{
         * @private
                }
        }
  }
- }
 +
 +/**
 + * Update object handling the cleanup of links tables after a page was deleted.
 + **/
 +class LinksDeletionUpdate extends SecondaryDBDataUpdate {
 +
 +      /**@{{
 +       * @private
 +       */
 +      var $mWikiPage;     //!< WikiPage the wikipage that was deleted
 +      /**@}}*/
 +
 +      /**
 +       * 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?
 +       */
 +      function __construct( WikiPage $page ) {
 +              parent::__construct( );
 +
 +              $this->mPage = $page;
 +      }
 +
 +      /**
 +       * Do some database updates after deletion
 +       */
 +      public function doUpdate() {
 +              $title = $this->mPage->getTitle();
 +              $id = $this->mPage->getId();
 +
 +              # Delete restrictions for it
 +              $this->mDb->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
 +
 +              # Fix category table counts
 +              $cats = array();
 +              $res = $this->mDb->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
 +
 +              foreach ( $res as $row ) {
 +                      $cats [] = $row->cl_to;
 +              }
 +
 +              $this->mPage->updateCategoryCounts( array(), $cats );
 +
 +              # If using cascading deletes, we can skip some explicit deletes
 +              if ( !$this->mDb->cascadingDeletes() ) {
 +                      $this->mDb->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
 +
 +                      # Delete outgoing links
 +                      $this->mDb->delete( 'pagelinks', array( 'pl_from' => $id ), __METHOD__ );
 +                      $this->mDb->delete( 'imagelinks', array( 'il_from' => $id ), __METHOD__ );
 +                      $this->mDb->delete( 'categorylinks', array( 'cl_from' => $id ), __METHOD__ );
 +                      $this->mDb->delete( 'templatelinks', array( 'tl_from' => $id ), __METHOD__ );
 +                      $this->mDb->delete( 'externallinks', array( 'el_from' => $id ), __METHOD__ );
 +                      $this->mDb->delete( 'langlinks', array( 'll_from' => $id ), __METHOD__ );
 +                      $this->mDb->delete( 'iwlinks', array( 'iwl_from' => $id ), __METHOD__ );
 +                      $this->mDb->delete( 'redirect', array( 'rd_from' => $id ), __METHOD__ );
 +                      $this->mDb->delete( 'page_props', array( 'pp_page' => $id ), __METHOD__ );
 +              }
 +
 +              # If using cleanup triggers, we can skip some manual deletes
 +              if ( !$this->mDb->cleanupTriggers() ) {
 +                      # Clean up recentchanges entries...
 +                      $this->mDb->delete( 'recentchanges',
 +                                    array( 'rc_type != ' . RC_LOG,
 +                                         'rc_namespace' => $title->getNamespace(),
 +                                         'rc_title' => $title->getDBkey() ),
 +                                    __METHOD__ );
 +                      $this->mDb->delete( 'recentchanges',
 +                                    array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ),
 +                                    __METHOD__ );
 +              }
 +      }
++}
Simple merge
Simple merge
Simple merge
@@@ -2225,8 -2147,7 +2389,7 @@@ class WikiPage extends Page 
        public function doDeleteArticleReal(
                $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
        ) {
 -              global $wgUser;
 +              global $wgUser, $wgContentHandlerUseDB;
-               $user = is_null( $user ) ? $wgUser : $user;
  
                wfDebug( __METHOD__ . "\n" );
  
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -89,12 -89,13 +89,13 @@@ class ApiPurge extends ApiBase 
                                        global $wgParser, $wgEnableParserCache;
  
                                        $popts = ParserOptions::newFromContext( $this->getContext() );
+                                       $popts->setTidy( true );
                                        $p_result = $wgParser->parse( $page->getRawText(), $title, $popts,
 -                                              true, true, $page->getLatest() );
 +                                              true, true, $page->getLatest() ); #FIXME: content!
  
                                        # Update the links tables
 -                                      $u = new LinksUpdate( $title, $p_result );
 -                                      $u->doUpdate();
 +                    $updates = $p_result->getSecondaryDataUpdates( $title );
 +                    SecondaryDataUpdate::runUpdates( $updates );
  
                                        $r['linkupdate'] = '';
  
Simple merge
Simple merge
Simple merge
@@@ -197,13 -211,8 +212,15 @@@ class MysqlUpdater extends DatabaseUpda
                        // 1.20
                        array( 'addTable', 'config',                            'patch-config.sql' ),
                        array( 'addIndex', 'revision', 'page_user_timestamp', 'patch-revision-user-page-index.sql' ),
+                       array( 'addField', 'ipblocks',      'ipb_parent_block_id',           'patch-ipb-parent-block-id.sql' ),
+                       array( 'addIndex', 'ipblocks',      'ipb_parent_block_id',           'patch-ipb-parent-block-id-index.sql' ),
 +
 +                      // 1.WD
 +                      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' ),
                );
        }
  
Simple merge
@@@ -76,13 -90,8 +91,15 @@@ class SqliteUpdater extends DatabaseUpd
                        // 1.20
                        array( 'addTable', 'config',                            'patch-config.sql' ),
                        array( 'addIndex', 'revision', 'page_user_timestamp', 'patch-revision-user-page-index.sql' ),
+                       array( 'addField', 'ipblocks', 'ipb_parent_block_id', 'patch-ipb-parent-block-id.sql' ),
+                       array( 'addIndex', 'ipblocks', 'ipb_parent_block_id', 'patch-ipb-parent-block-id-index.sql' ),
 +
 +                      // 1.WD
 +                      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' ),
                );
        }
  
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -11,18 -10,28 +11,39 @@@ class RevisionStorageTest extends Media
  
        var $the_page;
  
+       function  __construct( $name = null, array $data = array(), $dataName = '' ) {
+               parent::__construct( $name, $data, $dataName );
+               $this->tablesUsed = array_merge( $this->tablesUsed,
+                                                array( 'page',
+                                                     'revision',
+                                                     'text',
+                                                     'recentchanges',
+                                                     'logging',
+                                                     'page_props',
+                                                     'pagelinks',
+                                                     'categorylinks',
+                                                     'langlinks',
+                                                     'externallinks',
+                                                     'imagelinks',
+                                                     'templatelinks',
+                                                     'iwlinks' ) );
+       }
        public function setUp() {
 +              global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
 +
 +              $wgExtraNamespaces[ 12312 ] = 'Dummy';
 +              $wgExtraNamespaces[ 12313 ] = 'Dummy_talk';
 +
 +              $wgNamespaceContentModels[ 12312 ] = 'DUMMY';
 +              $wgContentHandlers[ 'DUMMY' ] = 'DummyContentHandlerForTesting';
 +
 +              MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
 +              $wgContLang->resetNamespaces(); # reset namespace cache
 +
                if ( !$this->the_page ) {
                        $this->the_page = $this->createPage( 'RevisionStorageTest_the_page', "just a dummy page" );
                }
  
                $this->assertNotEquals( $orig->getId(), $rev->getId(), 'new null revision shold have a different id from the original revision' );
                $this->assertEquals( $orig->getTextId(), $rev->getTextId(), 'new null revision shold have the same text id as the original revision' );
 -              $this->assertEquals( 'some testing text', $rev->getText() );
 +              $this->assertEquals( 'some testing text', $rev->getContent()->getNativeData() );
        }
  }
- ?>
@@@ -102,48 -75,6 +102,47 @@@ class TitleMethodsTest extends MediaWik
                $this->assertEquals( $expectedBool, $title->hasSubjectNamespace( $ns ) );
        }
  
 +      public function dataGetContentModel() {
 +              return array(
 +                      array( 'Foo', CONTENT_MODEL_WIKITEXT ),
 +                      array( 'Foo.js', CONTENT_MODEL_WIKITEXT ),
 +                      array( 'Foo/bar.js', CONTENT_MODEL_WIKITEXT ),
 +                      array( 'User:Foo', CONTENT_MODEL_WIKITEXT ),
 +                      array( 'User:Foo.js', CONTENT_MODEL_WIKITEXT ),
 +                      array( 'User:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ),
 +                      array( 'User:Foo/bar.css', CONTENT_MODEL_CSS ),
 +                      array( 'User talk:Foo/bar.css', CONTENT_MODEL_WIKITEXT ),
 +                      array( 'User:Foo/bar.js.xxx', CONTENT_MODEL_WIKITEXT ),
 +                      array( 'User:Foo/bar.xxx', CONTENT_MODEL_WIKITEXT ),
 +                      array( 'MediaWiki:Foo.js', CONTENT_MODEL_JAVASCRIPT ),
 +                      array( 'MediaWiki:Foo.css', CONTENT_MODEL_CSS ),
 +                      array( 'MediaWiki:Foo/bar.css', CONTENT_MODEL_CSS ),
 +                      array( 'MediaWiki:Foo.JS', CONTENT_MODEL_WIKITEXT ),
 +                      array( 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT ),
 +                      array( 'MediaWiki:Foo.css.xxx', CONTENT_MODEL_WIKITEXT ),
 +                      array( 'TEST-JS:Foo', CONTENT_MODEL_JAVASCRIPT ),
 +                      array( 'TEST-JS:Foo.js', CONTENT_MODEL_JAVASCRIPT ),
 +                      array( 'TEST-JS:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ),
 +                      array( 'TEST-JS_TALK:Foo.js', CONTENT_MODEL_WIKITEXT ),
 +              );
 +      }
 +
 +      /**
 +       * @dataProvider dataGetContentModel
 +       */
 +      public function testGetContentModel( $title, $expectedModelId ) {
 +              $title = Title::newFromText( $title );
 +              $this->assertEquals( $expectedModelId, $title->getContentModel() );
 +      }
 +
 +      /**
 +       * @dataProvider dataGetContentModel
 +       */
 +      public function testHasContentModel( $title, $expectedModelId ) {
 +              $title = Title::newFromText( $title );
 +              $this->assertTrue( $title->hasContentModel( $expectedModelId ) );
 +      }
 +
        public function dataIsCssOrJsPage() {
                return array(
                        array( 'Foo', false ),
@@@ -9,6 -8,27 +9,27 @@@ class WikiPageTest extends MediaWikiTes
  
        var $pages_to_delete;
  
 -
+       function  __construct( $name = null, array $data = array(), $dataName = '' ) {
+               parent::__construct( $name, $data, $dataName );
+               $this->tablesUsed = array_merge ( $this->tablesUsed,
+                                                 array( 'page',
+                                                      'revision',
+                                                      'text',
+                                                      'recentchanges',
+                                                      'logging',
+                                                      'page_props',
+                                                      'pagelinks',
+                                                      'categorylinks',
+                                                      'langlinks',
+                                                      'externallinks',
+                                                      'imagelinks',
+                                                      'templatelinks',
+                                                      'iwlinks' ) );
+       }
++      
        public function setUp() {
                $this->pages_to_delete = array();
        }
                return $page;
        }
  
 +      public function testDoEditContent() {
 +              $title = Title::newFromText( "WikiPageTest_testDoEditContent" );
 +
 +              $page = $this->newPage( $title );
 +
 +              $content = ContentHandler::makeContent( "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam "
 +                                                      . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.",
 +                                                      $title );
 +
 +              $page->doEditContent( $content, "testing 1" );
 +
 +              $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" );
 +              $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" );
 +
 +              $id = $page->getId();
 +
 +              # ------------------------
 +              $page = new WikiPage( $title );
 +
 +              $retrieved = $page->getContent();
 +              $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' );
 +
 +              # ------------------------
 +              $content = ContentHandler::makeContent( "At vero eos et accusam et justo duo [[dolores]] et ea rebum. "
 +                                                      . "Stet clita kasd [[gubergren]], no sea takimata sanctus est.",
 +                                                      $title );
 +
 +              $page->doEditContent( $content, "testing 2" );
 +
 +              # ------------------------
 +              $page = new WikiPage( $title );
 +
 +              $retrieved = $page->getContent();
 +              $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' );
 +
 +              # ------------------------
 +              $dbr = wfGetDB( DB_SLAVE );
 +              $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) );
 +              $n = $res->numRows();
 +              $res->free();
 +
 +              $this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' );
 +      }
++      
        public function testDoEdit() {
                $title = Title::newFromText( "WikiPageTest_testDoEdit" );
  
                $this->assertEquals( $text, $page->getText() );
        }
  
 +      public function testDoQuickEditContent() {
 +              global $wgUser;
 +
 +              $page = $this->createPage( "WikiPageTest_testDoQuickEditContent", "original text" );
 +
 +              $content = ContentHandler::makeContent( "quick text", $page->getTitle() );
 +              $page->doQuickEditContent( $content, $wgUser, "testing q" );
 +
 +              # ---------------------
 +              $page = new WikiPage( $page->getTitle() );
 +              $this->assertTrue( $content->equals( $page->getContent() ) );
 +      }
++      
        public function testDoDeleteArticle() {
                $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo" );
                $id = $page->getId();
                $page->doDeleteArticle( "testing deletion" );
  
                $this->assertFalse( $page->exists(), "WikiPage::exists should return false after page was deleted" );
 +              $this->assertNull( $page->getContent(), "WikiPage::getContent should return null after page was deleted" );
                $this->assertFalse( $page->getText(), "WikiPage::getText should return false after page was deleted" );
  
                $t = Title::newFromText( $page->getTitle()->getPrefixedText() );
        public function testGetRedirectTarget( $title, $text, $target ) {
                $page = $this->createPage( $title, $text );
  
 +              # sanity check, because this test seems to fail for no reason for some people.
 +              $c = $page->getContent();
 +              $this->assertEquals( 'WikitextContent', get_class( $c ) );
++              
                # now, test the actual redirect
                $t = $page->getRedirectTarget();
                $this->assertEquals( $target, is_null( $t ) ? null : $t->getPrefixedText() );
@@@ -531,18 -461,6 +551,18 @@@ more stuf
                $this->assertEquals( $expected, $text );
        }
  
 +      /**
 +       * @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() ) );
 +      }
++      
        /* @FIXME: fix this!
        public function testGetUndoText() {
                global $wgDiff3;
  
                $page = new WikiPage( $page->getTitle() );
                $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(), "rollback did not revert to the correct revision" );
 -              $this->assertEquals( "one\n\ntwo", $page->getText() );
 +              $this->assertEquals( "one\n\ntwo", $page->getContent()->getNativeData() );
        }
  
 -              $page->doEdit( $text, "section one", EDIT_NEW, false, $admin );
+       /**
+        * @todo FIXME: the above rollback test is better, but it keeps failing in jenkins for some reason.
+        */
+       public function testDoRollback() {
+               $admin = new User();
+               $admin->setName("Admin");
+               $text = "one";
+               $page = $this->newPage( "WikiPageTest_testDoRollback" );
 -              $page->doEdit( $text, "adding section two", 0, false, $user1 );
++              $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "section one", EDIT_NEW, false, $admin );
+               $rev1 = $page->getRevision();
+               $user1 = new User();
+               $user1->setName( "127.0.1.11" );
+               $text .= "\n\ntwo";
+               $page = new WikiPage( $page->getTitle() );
 -              $this->assertEquals( "one", $page->getText() );
++              $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section two", 0, false, $user1 );
+               # now, try the rollback
+               $admin->addGroup( "sysop" ); #XXX: make the test user a sysop...
+               $token = $admin->getEditToken( array( $page->getTitle()->getPrefixedText(), $user1->getName() ), null );
+               $errors = $page->doRollback( $user1->getName(), "testing revert", $token, false, $details, $admin );
+               if ( $errors ) {
+                       $this->fail( "Rollback failed:\n" . print_r( $errors, true ) . ";\n" . print_r( $details, true ) );
+               }
+               $page = new WikiPage( $page->getTitle() );
+               $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(), "rollback did not revert to the correct revision" );
++              $this->assertEquals( "one", $page->getContent()->getNativeData() );
+       }
        public function dataGetAutosummary( ) {
                return array(
                        array(
@@@ -1,6 -1,6 +1,9 @@@
  <?php
  
  /**
++ * @group medium
++ * ^---- causes phpunit to use a higher timeout threshold
++ * 
   * @group FileRepo
   * @group FileBackend
   */
@@@ -289,6 -313,6 +313,18 @@@ abstract class DumpTestCase extends Med
                $this->skipWhitespace();
  
                $this->assertTextNode( "comment", $summary );
++              $this->skipWhitespace();
++
++              if ( $this->xml->name == "model" ) { // model tag is optional
++                      $this->assertTextNode( "model", CONTENT_MODEL_WIKITEXT ); //@todo: make this a test parameter
++                      $this->skipWhitespace();
++              }
++
++
++              if ( $this->xml->name == "format" ) { // format tag is optional
++                      $this->assertTextNode( "format", CONTENT_FORMAT_WIKITEXT ); //@todo: make this a test parameter
++                      $this->skipWhitespace();
++              }
  
                $this->assertNodeStart( "text", false );
                if ( $text_bytes !== false ) {