Merge branch 'Wikidata' of ssh://gerrit.wikimedia.org:29418/mediawiki/core into Wikidata
authordaniel <daniel.kinzler@wikimedia.de>
Mon, 14 May 2012 21:24:38 +0000 (23:24 +0200)
committerdaniel <daniel.kinzler@wikimedia.de>
Mon, 14 May 2012 21:24:38 +0000 (23:24 +0200)
1  2 
includes/WikiPage.php

diff --combined includes/WikiPage.php
@@@ -1,25 -1,4 +1,25 @@@
  <?php
 +/**
 + * Base representation for a MediaWiki page.
 + *
 + * This program is free software; you can redistribute it and/or modify
 + * it under the terms of the GNU General Public License as published by
 + * the Free Software Foundation; either version 2 of the License, or
 + * (at your option) any later version.
 + *
 + * This program is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 + * GNU General Public License for more details.
 + *
 + * You should have received a copy of the GNU General Public License along
 + * with this program; if not, write to the Free Software Foundation, Inc.,
 + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 + * http://www.gnu.org/copyleft/gpl.html
 + *
 + * @file
 + */
 +
  /**
   * Abstract class for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
   */
@@@ -58,28 -37,6 +58,28 @@@ class WikiPage extends Page 
         */
        const DELETE_NO_REVISIONS = 2;
  
 +      // Constants for $mDataLoadedFrom and related
 +
 +      /**
 +       * Data has not been loaded yet (or the object was cleared)
 +       */
 +      const DATA_NOT_LOADED = 0;
 +
 +      /**
 +       * Data has been loaded from a slave database
 +       */
 +      const DATA_FROM_SLAVE = 1;
 +
 +      /**
 +       * Data has been loaded from the master database
 +       */
 +      const DATA_FROM_MASTER = 2;
 +
 +      /**
 +       * Data has been loaded from the master database using FOR UPDATE
 +       */
 +      const DATA_FOR_UPDATE = 3;
 +
        /**
         * @var Title
         */
        public $mPreparedEdit = false;       // !< Array
        /**@}}*/
  
 +      /**
 +       * @var int; one of the DATA_* constants
 +       */
 +      protected $mDataLoadedFrom = self::DATA_NOT_LOADED;
 +
        /**
         * @var Title
         */
         * Constructor from a page id
         *
         * @param $id Int article ID to load
 +       * @param $from string|int one of the following values:
 +       *        - "fromdb" or self::DATA_FROM_SLAVE to select from a slave database
 +       *        - "fromdbmaster" or self::DATA_FROM_MASTER to select from the master database
         *
         * @return WikiPage|null
         */
 -      public static function newFromID( $id ) {
 -              $dbr = wfGetDB( DB_SLAVE );
 -              $row = $dbr->selectRow( 'page', self::selectFields(), array( 'page_id' => $id ), __METHOD__ );
 +      public static function newFromID( $id, $from = 'fromdb' ) {
 +              $from = self::convertSelectType( $from );
 +              $db = wfGetDB( $from === self::DATA_FROM_MASTER ? DB_MASTER : DB_SLAVE );
 +              $row = $db->selectRow( 'page', self::selectFields(), array( 'page_id' => $id ), __METHOD__ );
                if ( !$row ) {
                        return null;
                }
 -              return self::newFromRow( $row );
 +              return self::newFromRow( $row, $from );
        }
  
        /**
         * @since 1.20
         * @param $row object: database row containing at least fields returned
         *        by selectFields().
 +       * @param $from string|int: source of $data:
 +       *        - "fromdb" or self::DATA_FROM_SLAVE: from a slave DB
 +       *        - "fromdbmaster" or self::DATA_FROM_MASTER: from the master DB
 +       *        - "forupdate" or self::DATA_FOR_UPDATE: from the master DB using SELECT FOR UPDATE
         * @return WikiPage
         */
 -      public static function newFromRow( $row ) {
 +      public static function newFromRow( $row, $from = 'fromdb' ) {
                $page = self::factory( Title::newFromRow( $row ) );
 -              $page->loadFromRow( $row );
 +              $page->loadFromRow( $row, $from );
                return $page;
        }
  
 +      /**
 +       * Convert 'fromdb', 'fromdbmaster' and 'forupdate' to DATA_* constants.
 +       *
 +       * @param $type object|string|int
 +       * @return mixed
 +       */
 +      private static function convertSelectType( $type ) {
 +              switch ( $type ) {
 +              case 'fromdb':
 +                      return self::DATA_FROM_SLAVE;
 +              case 'fromdbmaster':
 +                      return self::DATA_FROM_MASTER;
 +              case 'forupdate':
 +                      return self::DATA_FOR_UPDATE;
 +              default:
 +                      // It may already be an integer or whatever else
 +                      return $type;
 +              }
 +      }
 +
        /**
         * Returns overrides for action handlers.
         * Classes listed here will be used instead of the default one when
         */
        public function clear() {
                $this->mDataLoaded = false;
 +              $this->mDataLoadedFrom = self::DATA_NOT_LOADED;
  
                $this->mCounter = null;
                $this->mRedirectTarget = null; # Title object if set
         * Fetch a page record with the given conditions
         * @param $dbr DatabaseBase object
         * @param $conditions Array
 +       * @param $options Array
         * @return mixed Database result resource, or false on failure
         */
 -      protected function pageData( $dbr, $conditions ) {
 +      protected function pageData( $dbr, $conditions, $options = array() ) {
                $fields = self::selectFields();
  
                wfRunHooks( 'ArticlePageDataBefore', array( &$this, &$fields ) );
  
 -              $row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__ );
 +              $row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__, $options );
  
                wfRunHooks( 'ArticlePageDataAfter', array( &$this, &$row ) );
  
         *
         * @param $dbr DatabaseBase object
         * @param $title Title object
 +       * @param $options Array
         * @return mixed Database result resource, or false on failure
         */
 -      public function pageDataFromTitle( $dbr, $title ) {
 +      public function pageDataFromTitle( $dbr, $title, $options = array() ) {
                return $this->pageData( $dbr, array(
                        'page_namespace' => $title->getNamespace(),
 -                      'page_title'     => $title->getDBkey() ) );
 +                      'page_title'     => $title->getDBkey() ), $options );
        }
  
        /**
         *
         * @param $dbr DatabaseBase
         * @param $id Integer
 +       * @param $options Array
         * @return mixed Database result resource, or false on failure
         */
 -      public function pageDataFromId( $dbr, $id ) {
 -              return $this->pageData( $dbr, array( 'page_id' => $id ) );
 +      public function pageDataFromId( $dbr, $id, $options = array() ) {
 +              return $this->pageData( $dbr, array( 'page_id' => $id ), $options );
        }
  
        /**
         * Set the general counter, title etc data loaded from
         * some source.
         *
 -       * @param $data Object|String One of the following:
 -       *              A DB query result object or...
 -       *              "fromdb" to get from a slave DB or...
 -       *              "fromdbmaster" to get from the master DB
 +       * @param $from object|string|int One of the following:
 +       *        - A DB query result object
 +       *        - "fromdb" or self::DATA_FROM_SLAVE to get from a slave DB
 +       *        - "fromdbmaster" or self::DATA_FROM_MASTER to get from the master DB
 +       *        - "forupdate"  or self::DATA_FOR_UPDATE to get from the master DB using SELECT FOR UPDATE
 +       *
         * @return void
         */
 -      public function loadPageData( $data = 'fromdb' ) {
 -              if ( $data === 'fromdbmaster' ) {
 +      public function loadPageData( $from = 'fromdb' ) {
 +              $from = self::convertSelectType( $from );
 +              if ( is_int( $from ) && $from <= $this->mDataLoadedFrom ) {
 +                      // We already have the data from the correct location, no need to load it twice.
 +                      return;
 +              }
 +
 +              if ( $from === self::DATA_FOR_UPDATE ) {
 +                      $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle, array( 'FOR UPDATE' ) );
 +              } elseif ( $from === self::DATA_FROM_MASTER ) {
                        $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
 -              } elseif ( $data === 'fromdb' ) { // slave
 +              } elseif ( $from === self::DATA_FROM_SLAVE ) {
                        $data = $this->pageDataFromTitle( wfGetDB( DB_SLAVE ), $this->mTitle );
                        # Use a "last rev inserted" timestamp key to dimish the issue of slave lag.
                        # Note that DB also stores the master position in the session and checks it.
                        $touched = $this->getCachedLastEditTime();
                        if ( $touched ) { // key set
                                if ( !$data || $touched > wfTimestamp( TS_MW, $data->page_touched ) ) {
 +                                      $from = self::DATA_FROM_MASTER;
                                        $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
                                }
                        }
 +              } else {
 +                      // No idea from where the caller got this data, assume slave database.
 +                      $data = $from;
 +                      $from = self::DATA_FROM_SLAVE;
                }
  
 -              $this->loadFromRow( $data );
 +              $this->loadFromRow( $data, $from );
        }
  
        /**
         * @since 1.20
         * @param $data object: database row containing at least fields returned
         *        by selectFields()
 +       * @param $from string|int One of the following:
 +       *        - "fromdb" or self::DATA_FROM_SLAVE if the data comes from a slave DB
 +       *        - "fromdbmaster" or self::DATA_FROM_MASTER if the data comes from the master DB
 +       *        - "forupdate"  or self::DATA_FOR_UPDATE if the data comes from from
 +       *          the master DB using SELECT FOR UPDATE
         */
 -      public function loadFromRow( $data ) {
 +      public function loadFromRow( $data, $from ) {
                $lc = LinkCache::singleton();
  
                if ( $data ) {
                }
  
                $this->mDataLoaded = true;
 +              $this->mDataLoadedFrom = self::convertSelectType( $from );
        }
  
        /**
                return (int)$this->mLatest;
        }
  
 +      /**
 +       * Get the Revision object of the oldest revision
 +       * @return Revision|null
 +       */
 +      public function getOldestRevision() {
 +              wfProfileIn( __METHOD__ );
 +
 +              // Try using the slave database first, then try the master
 +              $continue = 2;
 +              $db = wfGetDB( DB_SLAVE );
 +              $revSelectFields = Revision::selectFields();
 +
 +              while ( $continue ) {
 +                      $row = $db->selectRow(
 +                              array( 'page', 'revision' ),
 +                              $revSelectFields,
 +                              array(
 +                                      'page_namespace' => $this->mTitle->getNamespace(),
 +                                      'page_title' => $this->mTitle->getDBkey(),
 +                                      'rev_page = page_id'
 +                              ),
 +                              __METHOD__,
 +                              array(
 +                                      'ORDER BY' => 'rev_timestamp ASC'
 +                              )
 +                      );
 +
 +                      if ( $row ) {
 +                              $continue = 0;
 +                      } else {
 +                              $db = wfGetDB( DB_MASTER );
 +                              $continue--;
 +                      }
 +              }
 +
 +              wfProfileOut( __METHOD__ );
 +              if ( $row ) {
 +                      return Revision::newFromRow( $row );
 +              } else {
 +                      return null;
 +              }
 +      }
 +
        /**
         * Loads everything except the text
         * This isn't necessary for all uses, so it's only done if needed.
                }
        }
  
 +      /**
 +       * Get the User object of the user who created the page
 +       * @param $audience Integer: one of:
 +       *      Revision::FOR_PUBLIC       to be displayed to all users
 +       *      Revision::FOR_THIS_USER    to be displayed to $wgUser
 +       *      Revision::RAW              get the text regardless of permissions
 +       * @return User|null
 +       */
 +      public function getCreator( $audience = Revision::FOR_PUBLIC ) {
 +              $revision = $this->getOldestRevision();
 +              if ( $revision ) {
 +                      $userName = $revision->getUserText( $audience );
 +                      return User::newFromName( $userName, false );
 +              } else {
 +                      return null;
 +              }
 +      }
 +
        /**
         * @param $audience Integer: one of:
         *      Revision::FOR_PUBLIC       to be displayed to all users
                $user = is_null( $user ) ? $wgUser : $user;
                $status = Status::newGood( array() );
  
 -              # Load $this->mTitle->getArticleID() and $this->mLatest if it's not already
 +              // Load the data from the master database if needed.
 +              // The caller may already loaded it from the master or even loaded it using
 +              // SELECT FOR UPDATE, so do not override that using clear().
                $this->loadPageData( 'fromdbmaster' );
  
                $flags = $this->checkFlags( $flags );
                        $changed = !$content->equals( $old_content );
  
                        if ( $changed ) {
+                               // TODO: validate!
+                               if ( $content->isValid() ) {
+                               }
                                $dbw->begin( __METHOD__ );
                                $revisionId = $revision->insertOn( $dbw );
  
                                return $status;
                        }
  
+                       // TODO: create content diff to pass to update objects that might need it
                        # Update links tables, site stats, etc.
-                       $this->doEditUpdates( $revision, $user, array( 'changed' => $changed,
-                               'oldcountable' => $oldcountable ) );
+                       $this->doEditUpdates(
+                               $revision,
+                               $user,
+                               array(
+                                       'changed' => $changed,
+                                       'oldcountable' => $oldcountable
+                               )
+                       );
  
                        if ( !$changed ) {
                                $status->warning( 'edit-no-change' );
         * @param &$cascade Integer. Set to false if cascading protection isn't allowed.
         * @param $expiry Array: per restriction type expiration
         * @param $user User The user updating the restrictions
 -       * @return bool true on success
 +       * @return Status
         */
        public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) {
                global $wgContLang;
                $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
        ) {
                global $wgUser, $wgContentHandlerUseDB;
 -              $user = is_null( $user ) ? $wgUser : $user;
  
                wfDebug( __METHOD__ . "\n" );
  
 +              if ( $this->mTitle->getDBkey() === '' ) {
 +                      return WikiPage::DELETE_NO_PAGE;
 +              }
 +
 +              $user = is_null( $user ) ? $wgUser : $user;
                if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error ) ) ) {
                        return WikiPage::DELETE_HOOK_ABORTED;
                }
 -              $dbw = wfGetDB( DB_MASTER );
 -              $t = $this->mTitle->getDBkey();
 -              $id = $id ? $id : $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
  
 -              if ( $t === '' || $id == 0 ) {
 -                      return WikiPage::DELETE_NO_PAGE;
 +              if ( $id == 0 ) {
 +                      $this->loadPageData( 'forupdate' );
 +                      $id = $this->getID();
 +                      if ( $id == 0 ) {
 +                              return WikiPage::DELETE_NO_PAGE;
 +                      }
                }
  
                // Bitfields to further suppress the content
                        $bitfield = 'rev_deleted';
                }
  
 +              $dbw = wfGetDB( DB_MASTER );
                $dbw->begin( __METHOD__ );
                // For now, shunt the revision data into the archive table.
                // Text is *not* removed from the text table; bulk storage