Merge "More specific @return doc in WikiPage::getDeletionUpdates"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 16 Oct 2015 11:46:27 +0000 (11:46 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 16 Oct 2015 11:46:27 +0000 (11:46 +0000)
1  2 
includes/page/WikiPage.php

@@@ -143,8 -143,7 +143,8 @@@ class WikiPage implements Page, IDBAcce
  
                $from = self::convertSelectType( $from );
                $db = wfGetDB( $from === self::READ_LATEST ? DB_MASTER : DB_SLAVE );
 -              $row = $db->selectRow( 'page', self::selectFields(), array( 'page_id' => $id ), __METHOD__ );
 +              $row = $db->selectRow(
 +                      'page', self::selectFields(), array( 'page_id' => $id ), __METHOD__ );
                if ( !$row ) {
                        return null;
                }
  
        /**
         * Fetch a page record with the given conditions
 -       * @param DatabaseBase $dbr
 +       * @param IDatabase $dbr
         * @param array $conditions
         * @param array $options
         * @return object|bool Database result resource, or false on failure
         * Fetch a page record matching the Title object's namespace and title
         * using a sanitized title string
         *
 -       * @param DatabaseBase $dbr
 +       * @param IDatabase $dbr
         * @param Title $title
         * @param array $options
         * @return object|bool Database result resource, or false on failure
        /**
         * Fetch a page record matching the requested ID
         *
 -       * @param DatabaseBase $dbr
 +       * @param IDatabase $dbr
         * @param int $id
         * @param array $options
         * @return object|bool Database result resource, or false on failure
         * Load the object from a database row
         *
         * @since 1.20
 -       * @param object $data Database row containing at least fields returned by selectFields()
 +       * @param object|bool $data DB row containing fields returned by selectFields() or false
         * @param string|int $from One of the following:
         *        - "fromdb" or WikiPage::READ_NORMAL if the data comes from a slave DB
         *        - "fromdbmaster" or WikiPage::READ_LATEST if the data comes from the master DB
                        // SELECT. Thus we need S1 to also gets the revision row FOR UPDATE; otherwise, it
                        // may not find it since a page row UPDATE and revision row INSERT by S2 may have
                        // happened after the first S1 SELECT.
 -                      // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read.
 +                      // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read
                        $flags = Revision::READ_LOCKING;
                } elseif ( $this->mDataLoadedFrom == self::READ_LATEST ) {
                        // Bug T93976: if page_latest was loaded from the master, fetch the
                        $conds[] = "rev_user_text != {$dbr->addQuotes( $this->getUserText() )}";
                }
  
 -              $conds[] = "{$dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER )} = 0"; // username hidden?
 +              // Username hidden?
 +              $conds[] = "{$dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER )} = 0";
  
                $jconds = array(
                        'user' => array( 'LEFT JOIN', 'rev_user = user_id' ),
        public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
  
                $useParserCache = $this->shouldCheckParserCache( $parserOptions, $oldid );
 -              wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
 +              wfDebug( __METHOD__ .
 +                      ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
                if ( $parserOptions->getStubThreshold() ) {
                        wfIncrStats( 'pcache.miss.stub' );
                }
        /**
         * Do standard deferred updates after page view (existing or missing page)
         * @param User $user The relevant user
 -       * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed.
 +       * @param int $oldid Revision id being viewed; if not given or 0, latest revision is assumed
         */
        public function doViewUpdates( User $user, $oldid = 0 ) {
                if ( wfReadOnly() ) {
  
                return true;
        }
 -
        /**
         * Insert a new empty page record for this article.
         * This *must* be followed up by creating a revision
         * or else the record will be left in a funky state.
         * Best if all done inside a transaction.
         *
 -       * @param DatabaseBase $dbw
 +       * @param IDatabase $dbw
         * @return int|bool The newly created page_id key; false if the title already existed
         */
        public function insertOn( $dbw ) {
 -              $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' );
 -              $dbw->insert( 'page', array(
 -                      'page_id'           => $page_id,
 -                      'page_namespace'    => $this->mTitle->getNamespace(),
 -                      'page_title'        => $this->mTitle->getDBkey(),
 -                      'page_restrictions' => '',
 -                      'page_is_redirect'  => 0, // Will set this shortly...
 -                      'page_is_new'       => 1,
 -                      'page_random'       => wfRandom(),
 -                      'page_touched'      => $dbw->timestamp(),
 -                      'page_latest'       => 0, // Fill this in shortly...
 -                      'page_len'          => 0, // Fill this in shortly...
 -              ), __METHOD__, 'IGNORE' );
 -
 -              $affected = $dbw->affectedRows();
 -
 -              if ( $affected ) {
 +              $dbw->insert(
 +                      'page',
 +                      array(
 +                              'page_id'           => $dbw->nextSequenceValue( 'page_page_id_seq' ),
 +                              'page_namespace'    => $this->mTitle->getNamespace(),
 +                              'page_title'        => $this->mTitle->getDBkey(),
 +                              'page_restrictions' => '',
 +                              'page_is_redirect'  => 0, // Will set this shortly...
 +                              'page_is_new'       => 1,
 +                              'page_random'       => wfRandom(),
 +                              'page_touched'      => $dbw->timestamp(),
 +                              'page_latest'       => 0, // Fill this in shortly...
 +                              'page_len'          => 0, // Fill this in shortly...
 +                      ),
 +                      __METHOD__,
 +                      'IGNORE'
 +              );
 +
 +              if ( $dbw->affectedRows() > 0 ) {
                        $newid = $dbw->insertId();
                        $this->mId = $newid;
                        $this->mTitle->resetArticleID( $newid );
  
                        return $newid;
                } else {
 -                      return false;
 +                      return false; // nothing changed
                }
        }
  
        /**
         * Update the page record to point to a newly saved revision.
         *
 -       * @param DatabaseBase $dbw
 +       * @param IDatabase $dbw
         * @param Revision $revision For ID number, and text used to set
         *   length and redirect status fields
         * @param int $lastRevision If given, will not overwrite the page field
                        $this->mLatest = $revision->getId();
                        $this->mIsRedirect = (bool)$rt;
                        // Update the LinkCache.
 -                      LinkCache::singleton()->addGoodLinkObj( $this->getId(), $this->mTitle, $len, $this->mIsRedirect,
 -                                                                                                      $this->mLatest, $revision->getContentModel() );
 +                      LinkCache::singleton()->addGoodLinkObj(
 +                              $this->getId(),
 +                              $this->mTitle,
 +                              $len,
 +                              $this->mIsRedirect,
 +                              $this->mLatest,
 +                              $revision->getContentModel()
 +                      );
                }
  
                return $result;
        /**
         * Add row to the redirect table if this is a redirect, remove otherwise.
         *
 -       * @param DatabaseBase $dbw
 +       * @param IDatabase $dbw
         * @param Title $redirectTitle Title object pointing to the redirect target,
         *   or NULL if this is not a redirect
         * @param null|bool $lastRevIsRedirect If given, will optimize adding and
         *
         * @deprecated since 1.24, use updateRevisionOn instead
         *
 -       * @param DatabaseBase $dbw
 +       * @param IDatabase $dbw
         * @param Revision $revision
         * @return bool
         */
         * @param string $edittime Revision timestamp or null to use the current revision.
         *
         * @throws MWException
 -       * @return string New complete article text, or null if error.
 +       * @return string|null New complete article text, or null if error.
         *
         * @deprecated since 1.21, use replaceSectionAtRev() instead
         */
         * @param string $edittime Revision timestamp or null to use the current revision.
         *
         * @throws MWException
 -       * @return Content New complete article content, or null if error.
 +       * @return Content|null New complete article content, or null if error.
         *
         * @since 1.21
         * @deprecated since 1.24, use replaceSectionAtRev instead
         */
 -      public function replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle = '',
 -              $edittime = null ) {
 +      public function replaceSectionContent(
 +              $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
 +      ) {
  
                $baseRevId = null;
                if ( $edittime && $sectionId !== 'new' ) {
         * @param int|null $baseRevId
         *
         * @throws MWException
 -       * @return Content New complete article content, or null if error.
 +       * @return Content|null New complete article content, or null if error.
         *
         * @since 1.24
         */
         *          Do not log the change in recentchanges
         *      EDIT_FORCE_BOT
         *          Mark the edit a "bot" edit regardless of user rights
 -       *      EDIT_DEFER_UPDATES
 -       *          Defer some of the updates until the end of index.php
         *      EDIT_AUTOSUMMARY
         *          Fill in blank summaries with generated text where possible
         *
         *          Do not log the change in recentchanges
         *      EDIT_FORCE_BOT
         *          Mark the edit a "bot" edit regardless of user rights
 -       *      EDIT_DEFER_UPDATES
 -       *          Defer some of the updates until the end of index.php
         *      EDIT_AUTOSUMMARY
         *          Fill in blank summaries with generated text where possible
         *
                                        return $status;
                                }
  
 -                              Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
 +                              Hooks::run( 'NewRevisionFromEditComplete',
 +                                      array( $this, $revision, $baseRevId, $user ) );
  
                                // Update recentchanges
                                if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
                        Hooks::run( 'PageContentInsertComplete', $hook_args );
                }
  
 -              // Do updates right now unless deferral was requested
 -              if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
 -                      DeferredUpdates::doUpdates();
 -              }
 -
                // Return the new revision (or null) to the caller
                $status->value['revision'] = $revision;
  
         * @since 1.21
         */
        public function prepareContentForEdit(
 -              Content $content, $revision = null, User $user = null, $serialFormat = null, $useCache = true
 +              Content $content, $revision = null, User $user = null,
 +              $serialFormat = null, $useCache = true
        ) {
                global $wgContLang, $wgUser, $wgAjaxEditStash;
  
                                // itself (such as via self-transclusion). In this case, we need to make sure
                                // that any such self-references refer to the newly-saved revision, and not
                                // to the previous one, which could otherwise happen due to slave lag.
 -                              $oldCallback = $edit->popts->setCurrentRevisionCallback(
 -                                      function ( $title, $parser = false ) use ( $revision, &$oldCallback ) {
 +                              $oldCallback = $edit->popts->getCurrentRevisionCallback();
 +                              $edit->popts->setCurrentRevisionCallback(
 +                                      function ( Title $title, $parser = false ) use ( $revision, &$oldCallback ) {
                                                if ( $title->equals( $revision->getTitle() ) ) {
                                                        return $revision;
                                                } else {
 -                                                      return call_user_func(
 -                                                              $oldCallback,
 -                                                              $title,
 -                                                              $parser
 -                                                      );
 +                                                      return call_user_func( $oldCallback, $title, $parser );
                                                }
                                        }
                                );
                $edit->oldContent = $this->getContent( Revision::RAW );
  
                // NOTE: B/C for hooks! don't use these fields!
 -              $edit->newText = $edit->newContent ? ContentHandler::getContentText( $edit->newContent ) : '';
 -              $edit->oldText = $edit->oldContent ? ContentHandler::getContentText( $edit->oldContent ) : '';
 +              $edit->newText = $edit->newContent
 +                      ? ContentHandler::getContentText( $edit->newContent )
 +                      : '';
 +              $edit->oldText = $edit->oldContent
 +                      ? ContentHandler::getContentText( $edit->oldContent )
 +                      : '';
                $edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialFormat ) : '';
  
                $this->mPreparedEdit = $edit;
                        if ( !$recipient ) {
                                wfDebug( __METHOD__ . ": invalid username\n" );
                        } else {
 -                              // Allow extensions to prevent user notification when a new message is added to their talk page
 +                              // Allow extensions to prevent user notification
 +                              // when a new message is added to their talk page
                                if ( Hooks::run( 'ArticleEditUpdateNewTalk', array( &$this, $recipient ) ) ) {
                                        if ( User::isIP( $shortTitle ) ) {
                                                // An anonymous user
         * @param bool $minor Whereas it's a minor modification
         * @param string $serialFormat Format for storing the content in the database
         */
 -      public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = false,
 -              $serialFormat = null
 +      public function doQuickEditContent(
 +              Content $content, User $user, $comment = '', $minor = false, $serialFormat = null
        ) {
  
                $serialized = $content->serialize( $serialFormat );
                                __METHOD__
                        );
  
 -                      Hooks::run( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) );
 +                      Hooks::run( 'NewRevisionFromEditComplete',
 +                              array( $this, $nullRevision, $latest, $user ) );
                        Hooks::run( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) );
                } else { // Protection of non-existing page (also known as "title protection")
                        // Cascade protection is meaningless in this case
                        # with '' filtered out. All possible message keys are listed below:
                        # * protect-level-autoconfirmed
                        # * protect-level-sysop
 -                      $restrictionsText = wfMessage( 'protect-level-' . $restrictions )->inContentLanguage()->text();
 +                      $restrictionsText = wfMessage( 'protect-level-' . $restrictions )
 +                              ->inContentLanguage()->text();
  
                        $expiryText = $this->formatExpiry( $expiry[$action] );
  
  
                foreach ( array_filter( $limit ) as $action => $restrictions ) {
                        $expiryText = $this->formatExpiry( $expiry[$action] );
 -                      $protectDescriptionLog .= $wgContLang->getDirMark() . "[$action=$restrictions] ($expiryText)";
 +                      $protectDescriptionLog .= $wgContLang->getDirMark() .
 +                              "[$action=$restrictions] ($expiryText)";
                }
  
                return trim( $protectDescriptionLog );
         */
        protected static function flattenRestrictions( $limit ) {
                if ( !is_array( $limit ) ) {
 -                      throw new MWException( 'WikiPage::flattenRestrictions given non-array restriction set' );
 +                      throw new MWException( __METHOD__ . ' given non-array restriction set' );
                }
  
                $bits = array();
                $status = Status::newGood();
  
                if ( $this->mTitle->getDBkey() === '' ) {
 -                      $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
 +                      $status->error( 'cannotdelete',
 +                              wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
                        return $status;
                }
  
                $user = is_null( $user ) ? $wgUser : $user;
 -              if ( !Hooks::run( 'ArticleDelete', array( &$this, &$user, &$reason, &$error, &$status ) ) ) {
 +              if ( !Hooks::run( 'ArticleDelete',
 +                      array( &$this, &$user, &$reason, &$error, &$status, $suppress )
 +              ) ) {
                        if ( $status->isOK() ) {
                                // Hook aborted but didn't set a fatal status
                                $status->fatal( 'delete-hook-aborted' );
                $dbw->begin( __METHOD__ );
  
                if ( $id == 0 ) {
 +                      $this->loadPageData( self::READ_LATEST );
 +                      $id = $this->getID();
                        // T98706: lock the page from various other updates but avoid using
                        // WikiPage::READ_LOCKING as that will carry over the FOR UPDATE to
                        // the revisions queries (which also JOIN on user). Only lock the page
                        // row and CAS check on page_latest to see if the trx snapshot matches.
 -                      $latest = $this->lock();
 -
 -                      $this->loadPageData( WikiPage::READ_LATEST );
 -                      $id = $this->getID();
 -                      if ( $id == 0 || $this->getLatest() != $latest ) {
 +                      $lockedLatest = $this->lock();
 +                      if ( $id == 0 || $this->getLatest() != $lockedLatest ) {
                                // Page not there or trx snapshot is stale
                                $dbw->rollback( __METHOD__ );
 -                              $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
 +                              $status->error( 'cannotdelete',
 +                                      wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
                                return $status;
                        }
                }
  
                if ( !$ok ) {
                        $dbw->rollback( __METHOD__ );
 -                      $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
 +                      $status->error( 'cannotdelete',
 +                              wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
                        return $status;
                }
  
  
                $this->doDeleteUpdates( $id, $content );
  
 -              Hooks::run( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) );
 +              Hooks::run( 'ArticleDeleteComplete',
 +                      array( &$this, &$user, $reason, $id, $content, $logEntry ) );
                $status->value = $logid;
                return $status;
        }
  
        /**
 -       * Lock the page row for this title and return page_latest (or 0)
 +       * Lock the page row for this title+id and return page_latest (or 0)
         *
 -       * @return integer
 +       * @return integer Returns 0 if no row was found with this title+id
         */
        protected function lock() {
                return (int)wfGetDB( DB_MASTER )->selectField(
                        'page',
                        'page_latest',
                        array(
 +                              'page_id' => $this->getId(),
 +                              // Typically page_id is enough, but some code might try to do
 +                              // updates assuming the title is the same, so verify that
                                'page_namespace' => $this->getTitle()->getNamespace(),
                                'page_title' => $this->getTitle()->getDBkey()
                        ),
  
                // Delete pagelinks, update secondary indexes, etc
                $updates = $this->getDeletionUpdates( $content );
 -              // Make sure an enqueued jobs run after commit so they see the deletion
 -              wfGetDB( DB_MASTER )->onTransactionIdle( function() use ( $updates ) {
 -                      DataUpdate::runUpdates( $updates, 'enqueue' );
 -              } );
 +              foreach ( $updates as $update ) {
 +                      DeferredUpdates::addUpdate( $update );
 +              }
  
                // Reparse any pages transcluding this page
                LinksUpdate::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks' );
                }
  
                // raise error, when the edit is an edit without a new version
 -              if ( empty( $status->value['revision'] ) ) {
 +              $statusRev = isset( $status->value['revision'] )
 +                      ? $status->value['revision']
 +                      : null;
 +              if ( !( $statusRev instanceof Revision ) ) {
                        $resultDetails = array( 'current' => $current );
                        return array( array( 'alreadyrolled',
                                        htmlspecialchars( $this->mTitle->getPrefixedText() ),
                        ) );
                }
  
 -              $revId = $status->value['revision']->getId();
 +              $revId = $statusRev->getId();
  
                Hooks::run( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) );
  
  
                // Images
                if ( $title->getNamespace() == NS_FILE ) {
 -                      $update = new HTMLCacheUpdate( $title, 'imagelinks' );
 -                      $update->doUpdate();
 +                      DeferredUpdates::addUpdate( new HTMLCacheUpdate( $title, 'imagelinks' ) );
                }
  
                // User talk pages
                        return;
                }
  
 -              if ( !Hooks::run( 'OpportunisticLinksUpdate', array( $this, $this->mTitle, $parserOutput ) ) ) {
 +              if ( !Hooks::run( 'OpportunisticLinksUpdate',
 +                      array( $this, $this->mTitle, $parserOutput )
 +              ) ) {
                        return;
                }
  
         *
         * @param Content|null $content Optional Content object for determining the
         *   necessary updates.
-        * @return array An array of DataUpdates objects
+        * @return DataUpdate[]
         */
        public function getDeletionUpdates( Content $content = null ) {
                if ( !$content ) {
 -                      // load content object, which may be used to determine the necessary updates
 -                      // XXX: the content may not be needed to determine the updates, then this would be overhead.
 +                      // load content object, which may be used to determine the necessary updates.
 +                      // XXX: the content may not be needed to determine the updates.
                        $content = $this->getContent( Revision::RAW );
                }