Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
37.74% covered (danger)
37.74%
40 / 106
CRAP
72.73% covered (warning)
72.73%
1051 / 1445
WikiPage
0.00% covered (danger)
0.00%
0 / 1
37.74% covered (danger)
37.74%
40 / 106
3924.10
72.73% covered (warning)
72.73%
1051 / 1445
 __construct
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
6 / 6
 __clone
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 factory
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 newFromID
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 newFromRow
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 convertSelectType
0.00% covered (danger)
0.00%
0 / 1
4.03
87.50% covered (warning)
87.50%
7 / 8
 getRevisionStore
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getRevisionRenderer
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getSlotRoleRegistry
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getContentHandlerFactory
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getParserCache
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getDBLoadBalancer
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getActionOverrides
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 getContentHandler
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getTitle
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 clear
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 4
 clearCacheFields
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
9 / 9
 clearPreparedEdit
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 getQueryInfo
0.00% covered (danger)
0.00%
0 / 1
2.03
80.00% covered (warning)
80.00%
4 / 5
 pageData
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
12 / 12
 pageDataFromTitle
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 pageDataFromId
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 loadPageData
0.00% covered (danger)
0.00%
0 / 1
9.73
79.17% covered (warning)
79.17%
19 / 24
 wasLoadedFrom
0.00% covered (danger)
0.00%
0 / 1
3.04
83.33% covered (warning)
83.33%
5 / 6
 loadFromRow
0.00% covered (danger)
0.00%
0 / 1
5.02
91.30% covered (success)
91.30%
21 / 23
 getId
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 exists
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 hasViewableContent
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 isRedirect
0.00% covered (danger)
0.00%
0 / 1
2.15
66.67% covered (warning)
66.67%
2 / 3
 getContentModel
0.00% covered (danger)
0.00%
0 / 1
3.16
73.68% covered (warning)
73.68%
14 / 19
 checkTouched
0.00% covered (danger)
0.00%
0 / 1
3.33
66.67% covered (warning)
66.67%
2 / 3
 getTouched
0.00% covered (danger)
0.00%
0 / 1
2.15
66.67% covered (warning)
66.67%
2 / 3
 getLinksTimestamp
0.00% covered (danger)
0.00%
0 / 1
2.15
66.67% covered (warning)
66.67%
2 / 3
 getLatest
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 getOldestRevision
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 loadLastEdit
0.00% covered (danger)
0.00%
0 / 1
7.06
89.47% covered (warning)
89.47%
17 / 19
 setLastEdit
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 getRevision
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 getRevisionRecord
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 getContent
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 getTimestamp
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 setTimestamp
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 getUser
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 5
 getCreator
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 4
 getUserText
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 5
 getComment
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 5
 getMinorEdit
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 4
 isCountable
100.00% covered (success)
100.00%
1 / 1
7
100.00% covered (success)
100.00%
15 / 15
 getRedirectTarget
0.00% covered (danger)
0.00%
0 / 1
7.06
89.47% covered (warning)
89.47%
17 / 19
 insertRedirect
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 11
 insertRedirectEntry
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
22 / 22
 followRedirect
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 getRedirectURL
0.00% covered (danger)
0.00%
0 / 1
42
0.00% covered (danger)
0.00%
0 / 12
 getContributors
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 22
 shouldCheckParserCache
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 3
 getParserOutput
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
9 / 9
 doViewUpdates
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 8
 doPurge
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 9
 insertOn
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
21 / 21
 updateRevisionOn
0.00% covered (danger)
0.00%
0 / 1
9
95.00% covered (success)
95.00%
38 / 40
 updateRedirectOn
0.00% covered (danger)
0.00%
0 / 1
5.12
83.33% covered (warning)
83.33%
10 / 12
 updateIfNewerOn
0.00% covered (danger)
0.00%
0 / 1
3.01
90.91% covered (success)
90.91%
20 / 22
 hasDifferencesOutsideMainSlot
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 10
 getUndoContent
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 9
 supportsSections
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 replaceSectionContent
0.00% covered (danger)
0.00%
0 / 1
27.67
25.00% covered (danger)
25.00%
3 / 12
 replaceSectionAtRev
0.00% covered (danger)
0.00%
0 / 1
9.88
61.11% covered (warning)
61.11%
11 / 18
 checkFlags
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 5
 newDerivedDataUpdater
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
25 / 25
 getDerivedDataUpdater
100.00% covered (success)
100.00%
1 / 1
9
100.00% covered (success)
100.00%
14 / 14
 newPageUpdater
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
19 / 19
 doEditContent
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 doUserEditContent
0.00% covered (danger)
0.00%
0 / 1
17
97.50% covered (success)
97.50%
39 / 40
 makeParserOptions
0.00% covered (danger)
0.00%
0 / 1
2.06
75.00% covered (warning)
75.00%
3 / 4
 prepareContentForEdit
0.00% covered (danger)
0.00%
0 / 1
8.32
70.00% covered (warning)
70.00%
14 / 20
 doEditUpdates
0.00% covered (danger)
0.00%
0 / 1
4.37
71.43% covered (warning)
71.43%
10 / 14
 updateParserCache
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 10
 doSecondaryDataUpdates
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 11
 doUpdateRestrictions
0.00% covered (danger)
0.00%
0 / 1
38.87
91.56% covered (success)
91.56%
141 / 154
 insertNullProtectionRevision
0.00% covered (danger)
0.00%
0 / 1
6.14
84.38% covered (warning)
84.38%
27 / 32
 formatExpiry
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
10 / 10
 protectDescription
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
12 / 12
 protectDescriptionLog
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
6 / 6
 isBatchedDelete
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 5
 doDeleteArticleReal
0.00% covered (danger)
0.00%
0 / 1
3.33
66.67% covered (warning)
66.67%
6 / 9
 doDeleteArticleBatched
0.00% covered (danger)
0.00%
0 / 1
17.88
65.56% covered (warning)
65.56%
59 / 90
 archiveRevisions
0.00% covered (danger)
0.00%
0 / 1
9
96.61% covered (success)
96.61%
57 / 59
 lockAndGetLatest
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
8 / 8
 doDeleteUpdates
0.00% covered (danger)
0.00%
0 / 1
10.27
75.00% covered (warning)
75.00%
21 / 28
 doRollback
0.00% covered (danger)
0.00%
0 / 1
5.39
75.00% covered (warning)
75.00%
9 / 12
 commitRollback
0.00% covered (danger)
0.00%
0 / 1
54.92
74.67% covered (warning)
74.67%
112 / 150
 onArticleCreate
0.00% covered (danger)
0.00%
0 / 1
2.00
92.86% covered (success)
92.86%
13 / 14
 onArticleDelete
0.00% covered (danger)
0.00%
0 / 1
6.81
58.33% covered (warning)
58.33%
14 / 24
 onArticleEdit
0.00% covered (danger)
0.00%
0 / 1
6.03
90.91% covered (success)
90.91%
20 / 22
 purgeInterwikiCheckKey
0.00% covered (danger)
0.00%
0 / 1
3.54
27.27% covered (danger)
27.27%
3 / 11
 getCategories
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 9
 getHiddenCategories
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 14
 getAutoDeleteReason
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 updateCategoryCounts
0.00% covered (danger)
0.00%
0 / 1
12.37
86.27% covered (warning)
86.27%
44 / 51
 triggerOpportunisticLinksUpdate
0.00% covered (danger)
0.00%
0 / 1
72
0.00% covered (danger)
0.00%
0 / 20
 getDeletionUpdates
0.00% covered (danger)
0.00%
0 / 1
6.56
75.00% covered (warning)
75.00%
21 / 28
 isLocal
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 getWikiDisplayName
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 getSourceURL
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 getMutableCacheKeys
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 __wakeup
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
<?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
 */
use MediaWiki\Config\ServiceOptions;
use MediaWiki\Content\ContentHandlerFactory;
use MediaWiki\Content\IContentHandlerFactory;
use MediaWiki\Debug\DeprecatablePropertyArray;
use MediaWiki\Edit\PreparedEdit;
use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use MediaWiki\Page\ParserOutputAccess;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\RevisionRenderer;
use MediaWiki\Revision\RevisionStore;
use MediaWiki\Revision\SlotRecord;
use MediaWiki\Revision\SlotRoleRegistry;
use MediaWiki\Storage\DerivedPageDataUpdater;
use MediaWiki\Storage\EditResult;
use MediaWiki\Storage\EditResultCache;
use MediaWiki\Storage\PageUpdater;
use MediaWiki\Storage\RevisionSlotsUpdate;
use Wikimedia\Assert\Assert;
use Wikimedia\IPUtils;
use Wikimedia\NonSerializable\NonSerializableTrait;
use Wikimedia\Rdbms\FakeResultWrapper;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\LoadBalancer;
/**
 * Class representing a MediaWiki article and history.
 *
 * Some fields are public only for backwards-compatibility. Use accessors.
 * In the past, this class was part of Article.php and everything was public.
 */
class WikiPage implements Page, IDBAccessObject {
    use NonSerializableTrait;
    use ProtectedHookAccessorTrait;
    // Constants for $mDataLoadedFrom and related
    /**
     * @var Title
     * @todo make protected
     * @note for access by subclasses only
     */
    public $mTitle = null;
    /**
     * @var bool
     * @todo make protected
     * @note for access by subclasses only
     */
    public $mDataLoaded = false;
    /**
     * @var bool
     * @todo make protected
     * @note for access by subclasses only
     */
    public $mIsRedirect = false;
    /**
     * @var int|false False means "not loaded"
     * @todo make protected
     * @note for access by subclasses only
     */
    public $mLatest = false;
    /**
     * @var PreparedEdit|false Map of cache fields (text, parser output, ect) for a proposed/new edit
     * @todo make protected
     * @note for access by subclasses only
     */
    public $mPreparedEdit = false;
    /**
     * @var int
     */
    protected $mId = null;
    /**
     * @var int One of the READ_* constants
     */
    protected $mDataLoadedFrom = self::READ_NONE;
    /**
     * @var Title
     */
    protected $mRedirectTarget = null;
    /**
     * @var RevisionRecord
     */
    private $mLastRevision = null;
    /**
     * @var string Timestamp of the current revision or empty string if not loaded
     */
    protected $mTimestamp = '';
    /**
     * @var string
     */
    protected $mTouched = '19700101000000';
    /**
     * @var string
     */
    protected $mLinksUpdated = '19700101000000';
    /**
     * @var DerivedPageDataUpdater|null
     */
    private $derivedDataUpdater = null;
    /**
     * @param Title $title
     */
    public function __construct( Title $title ) {
        if ( !$title->canExist() ) {
            // TODO: In order to allow WikiPage to implement ProperPageIdentity,
            //       throw here to prevent construction of a WikiPage that doesn't
            //       represent a proper page.
            wfDeprecatedMsg(
                "WikiPage constructed on a Title that cannot exist as a page: $title",
                '1.36'
            );
        }
        $this->mTitle = $title;
    }
    /**
     * Makes sure that the mTitle object is cloned
     * to the newly cloned WikiPage.
     */
    public function __clone() {
        $this->mTitle = clone $this->mTitle;
    }
    /**
     * Create a WikiPage object of the appropriate class for the given title.
     *
     * @param Title $title
     *
     * @throws MWException
     * @return WikiPage|WikiCategoryPage|WikiFilePage
     * @deprecated since 1.36, use WikiPageFactory::newFromTitle instead
     */
    public static function factory( Title $title ) {
        return MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title );
    }
    /**
     * Constructor from a page id
     *
     * @param int $id Article ID to load
     * @param string|int $from One of the following values:
     *        - "fromdb" or WikiPage::READ_NORMAL to select from a replica DB
     *        - "fromdbmaster" or WikiPage::READ_LATEST to select from the master database
     *
     * @return WikiPage|null
     * @deprecated since 1.36, use WikiPageFactory::newFromID instead
     */
    public static function newFromID( $id, $from = 'fromdb' ) {
        return MediaWikiServices::getInstance()->getWikiPageFactory()->newFromID( $id, $from );
    }
    /**
     * Constructor from a database row
     *
     * @since 1.20
     * @param stdClass $row Database row containing at least fields returned by getQueryInfo().
     * @param string|int $from Source of $data:
     *        - "fromdb" or WikiPage::READ_NORMAL: from a replica DB
     *        - "fromdbmaster" or WikiPage::READ_LATEST: from the master DB
     *        - "forupdate" or WikiPage::READ_LOCKING: from the master DB using SELECT FOR UPDATE
     * @return WikiPage
     * @deprecated since 1.36, use WikiPageFactory::newFromRow instead
     */
    public static function newFromRow( $row, $from = 'fromdb' ) {
        return MediaWikiServices::getInstance()->getWikiPageFactory()->newFromRow( $row, $from );
    }
    /**
     * Convert 'fromdb', 'fromdbmaster' and 'forupdate' to READ_* constants.
     *
     * @param stdClass|string|int $type
     * @return mixed
     */
    public static function convertSelectType( $type ) {
        switch ( $type ) {
            case 'fromdb':
                return self::READ_NORMAL;
            case 'fromdbmaster':
                return self::READ_LATEST;
            case 'forupdate':
                return self::READ_LOCKING;
            default:
                // It may already be an integer or whatever else
                return $type;
        }
    }
    /**
     * @return RevisionStore
     */
    private function getRevisionStore() {
        return MediaWikiServices::getInstance()->getRevisionStore();
    }
    /**
     * @return RevisionRenderer
     */
    private function getRevisionRenderer() {
        return MediaWikiServices::getInstance()->getRevisionRenderer();
    }
    /**
     * @return SlotRoleRegistry
     */
    private function getSlotRoleRegistry() {
        return MediaWikiServices::getInstance()->getSlotRoleRegistry();
    }
    /**
     * @return ContentHandlerFactory
     */
    private function getContentHandlerFactory(): IContentHandlerFactory {
        return MediaWikiServices::getInstance()->getContentHandlerFactory();
    }
    /**
     * @return ParserCache
     */
    private function getParserCache() {
        return MediaWikiServices::getInstance()->getParserCache();
    }
    /**
     * @return LoadBalancer
     */
    private function getDBLoadBalancer() {
        return MediaWikiServices::getInstance()->getDBLoadBalancer();
    }
    /**
     * @todo Move this UI stuff somewhere else
     *
     * @see ContentHandler::getActionOverrides
     * @return array
     */
    public function getActionOverrides() {
        return $this->getContentHandler()->getActionOverrides();
    }
    /**
     * Returns the ContentHandler instance to be used to deal with the content of this WikiPage.
     *
     * Shorthand for ContentHandler::getForModelID( $this->getContentModel() );
     *
     * @return ContentHandler
     *
     * @since 1.21
     */
    public function getContentHandler() {
        return $this->getContentHandlerFactory()
            ->getContentHandler( $this->getContentModel() );
    }
    /**
     * Get the title object of the article
     * @return Title Title object of this page
     */
    public function getTitle() {
        return $this->mTitle;
    }
    /**
     * Clear the object
     * @return void
     */
    public function clear() {
        $this->mDataLoaded = false;
        $this->mDataLoadedFrom = self::READ_NONE;
        $this->clearCacheFields();
    }
    /**
     * Clear the object cache fields
     * @return void
     */
    protected function clearCacheFields() {
        $this->mId = null;
        $this->mRedirectTarget = null; // Title object if set
        $this->mLastRevision = null; // Latest revision
        $this->mTouched = '19700101000000';
        $this->mLinksUpdated = '19700101000000';
        $this->mTimestamp = '';
        $this->mIsRedirect = false;
        $this->mLatest = false;
        // T59026: do not clear $this->derivedDataUpdater since getDerivedDataUpdater() already
        // checks the requested rev ID and content against the cached one. For most
        // content types, the output should not change during the lifetime of this cache.
        // Clearing it can cause extra parses on edit for no reason.
    }
    /**
     * Clear the mPreparedEdit cache field, as may be needed by mutable content types
     * @return void
     * @since 1.23
     */
    public function clearPreparedEdit() {
        $this->mPreparedEdit = false;
    }
    /**
     * Return the tables, fields, and join conditions to be selected to create
     * a new page object.
     * @since 1.31
     * @return array With three keys:
     *   - tables: (string[]) to include in the `$table` to `IDatabase->select()`
     *   - fields: (string[]) to include in the `$vars` to `IDatabase->select()`
     *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
     */
    public static function getQueryInfo() {
        global $wgPageLanguageUseDB;
        $ret = [
            'tables' => [ 'page' ],
            'fields' => [
                'page_id',
                'page_namespace',
                'page_title',
                'page_restrictions',
                'page_is_redirect',
                'page_is_new',
                'page_random',
                'page_touched',
                'page_links_updated',
                'page_latest',
                'page_len',
                'page_content_model',
            ],
            'joins' => [],
        ];
        if ( $wgPageLanguageUseDB ) {
            $ret['fields'][] = 'page_lang';
        }
        return $ret;
    }
    /**
     * Fetch a page record with the given conditions
     * @param IDatabase $dbr
     * @param array $conditions
     * @param array $options
     * @return stdClass|false Database result resource, or false on failure
     */
    protected function pageData( $dbr, $conditions, $options = [] ) {
        $pageQuery = self::getQueryInfo();
        $this->getHookRunner()->onArticlePageDataBefore(
            $this, $pageQuery['fields'], $pageQuery['tables'], $pageQuery['joins'] );
        $row = $dbr->selectRow(
            $pageQuery['tables'],
            $pageQuery['fields'],
            $conditions,
            __METHOD__,
            $options,
            $pageQuery['joins']
        );
        $this->getHookRunner()->onArticlePageDataAfter( $this, $row );
        return $row;
    }
    /**
     * Fetch a page record matching the Title object's namespace and title
     * using a sanitized title string
     *
     * @param IDatabase $dbr
     * @param Title $title
     * @param array $options
     * @return stdClass|false Database result resource, or false on failure
     */
    public function pageDataFromTitle( $dbr, $title, $options = [] ) {
        return $this->pageData( $dbr, [
            'page_namespace' => $title->getNamespace(),
            'page_title' => $title->getDBkey() ], $options );
    }
    /**
     * Fetch a page record matching the requested ID
     *
     * @param IDatabase $dbr
     * @param int $id
     * @param array $options
     * @return stdClass|false Database result resource, or false on failure
     */
    public function pageDataFromId( $dbr, $id, $options = [] ) {
        return $this->pageData( $dbr, [ 'page_id' => $id ], $options );
    }
    /**
     * Load the object from a given source by title
     *
     * @param stdClass|string|int $from One of the following:
     *   - A DB query result object.
     *   - "fromdb" or WikiPage::READ_NORMAL to get from a replica DB.
     *   - "fromdbmaster" or WikiPage::READ_LATEST to get from the master DB.
     *   - "forupdate"  or WikiPage::READ_LOCKING to get from the master DB
     *     using SELECT FOR UPDATE.
     *
     * @return void
     */
    public function loadPageData( $from = 'fromdb' ) {
        if ( !$this->mTitle->canExist() ) {
            // NOTE: If and when WikiPage implements PageIdentity but not yet ProperPageIdentity,
            //       throw here to prevent usage of a WikiPage that doesn't
            //       represent a proper page.
            // NOTE: The constructor will already have triggered a warning, but seeing how
            //       bad instances of WikiPage are used will be helpful.
            wfDeprecatedMsg(
                "Accessing WikiPage that cannot exist as a page: {$this->mTitle}",
                '1.36'
            );
        }
        $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 ( is_int( $from ) ) {
            list( $index, $opts ) = DBAccessObjectUtils::getDBOptions( $from );
            $loadBalancer = $this->getDBLoadBalancer();
            $db = $loadBalancer->getConnection( $index );
            $data = $this->pageDataFromTitle( $db, $this->mTitle, $opts );
            if ( !$data
                && $index == DB_REPLICA
                && $loadBalancer->getServerCount() > 1
                && $loadBalancer->