diff options
Diffstat (limited to 'includes/Title.php')
-rw-r--r-- | includes/Title.php | 270 |
1 files changed, 150 insertions, 120 deletions
diff --git a/includes/Title.php b/includes/Title.php index d8976635..b347edbb 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -96,7 +96,7 @@ class Title { /** @var array Array of groups allowed to edit this article */ public $mRestrictions = array(); - /** @var bool */ + /** @var string|bool */ protected $mOldRestrictions = false; /** @var bool Cascade restrictions on this page to included templates and images? */ @@ -225,9 +225,11 @@ class Title { public static function newFromDBkey( $key ) { $t = new Title(); $t->mDbkeyform = $key; - if ( $t->secureAndSplit() ) { + + try { + $t->secureAndSplit(); return $t; - } else { + } catch ( MalformedTitleException $ex ) { return null; } } @@ -263,7 +265,34 @@ class Title { if ( is_object( $text ) ) { throw new InvalidArgumentException( '$text must be a string.' ); } elseif ( !is_string( $text ) ) { - wfWarn( __METHOD__ . ': $text must be a string. This will throw an InvalidArgumentException in future.' ); + wfDebugLog( 'T76305', wfGetAllCallers( 5 ) ); + wfWarn( __METHOD__ . ': $text must be a string. This will throw an InvalidArgumentException in future.', 2 ); + } + + try { + return Title::newFromTextThrow( $text, $defaultNamespace ); + } catch ( MalformedTitleException $ex ) { + return null; + } + } + + /** + * Like Title::newFromText(), but throws MalformedTitleException when the title is invalid, + * rather than returning null. + * + * The exception subclasses encode detailed information about why the title is invalid. + * + * @see Title::newFromText + * + * @since 1.25 + * @param string $text Title text to check + * @param int $defaultNamespace + * @throws MalformedTitleException If the title is invalid + * @return Title + */ + public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) { + if ( is_object( $text ) ) { + throw new MWException( 'Title::newFromTextThrow given an object' ); } $cache = self::getTitleCache(); @@ -284,17 +313,14 @@ class Title { $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text ); $t = new Title(); - $t->mDbkeyform = str_replace( ' ', '_', $filteredText ); + $t->mDbkeyform = strtr( $filteredText, ' ', '_' ); $t->mDefaultNamespace = intval( $defaultNamespace ); - if ( $t->secureAndSplit() ) { - if ( $defaultNamespace == NS_MAIN ) { - $cache->set( $text, $t ); - } - return $t; - } else { - return null; + $t->secureAndSplit(); + if ( $defaultNamespace == NS_MAIN ) { + $cache->set( $text, $t ); } + return $t; } /** @@ -319,13 +345,15 @@ class Title { # but some URLs used it as a space replacement and they still come # from some external search tools. if ( strpos( self::legalChars(), '+' ) === false ) { - $url = str_replace( '+', ' ', $url ); + $url = strtr( $url, '+', ' ' ); } - $t->mDbkeyform = str_replace( ' ', '_', $url ); - if ( $t->secureAndSplit() ) { + $t->mDbkeyform = strtr( $url, ' ', '_' ); + + try { + $t->secureAndSplit(); return $t; - } else { + } catch ( MalformedTitleException $ex ) { return null; } } @@ -451,6 +479,9 @@ class Title { if ( isset( $row->page_lang ) ) { $this->mDbPageLanguage = (string)$row->page_lang; } + if ( isset( $row->page_restrictions ) ) { + $this->mOldRestrictions = $row->page_restrictions; + } } else { // page not found $this->mArticleID = 0; $this->mLength = 0; @@ -478,10 +509,10 @@ class Title { $t->mInterwiki = $interwiki; $t->mFragment = $fragment; $t->mNamespace = $ns = intval( $ns ); - $t->mDbkeyform = str_replace( ' ', '_', $title ); + $t->mDbkeyform = strtr( $title, ' ', '_' ); $t->mArticleID = ( $ns >= 0 ) ? -1 : 0; $t->mUrlform = wfUrlencode( $t->mDbkeyform ); - $t->mTextform = str_replace( '_', ' ', $title ); + $t->mTextform = strtr( $title, '_', ' ' ); $t->mContentModel = false; # initialized lazily in getContentModel() return $t; } @@ -504,9 +535,11 @@ class Title { $t = new Title(); $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki, true ); - if ( $t->secureAndSplit() ) { + + try { + $t->secureAndSplit(); return $t; - } else { + } catch ( MalformedTitleException $ex ) { return null; } } @@ -937,7 +970,6 @@ class Title { /** * Get the page's content model id, see the CONTENT_MODEL_XXX constants. * - * @throws MWException * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update * @return string Content model id */ @@ -952,10 +984,6 @@ class Title { $this->mContentModel = ContentHandler::getDefaultModelFor( $this ); } - if ( !$this->mContentModel ) { - throw new MWException( 'Failed to determine content model!' ); - } - return $this->mContentModel; } @@ -1391,7 +1419,7 @@ class Title { * @param string $fragment Text */ public function setFragment( $fragment ) { - $this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) ); + $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' ); } /** @@ -1421,7 +1449,7 @@ class Title { */ public function getPrefixedDBkey() { $s = $this->prefix( $this->mDbkeyform ); - $s = str_replace( ' ', '_', $s ); + $s = strtr( $s, ' ', '_' ); return $s; } @@ -1434,7 +1462,7 @@ class Title { public function getPrefixedText() { if ( $this->mPrefixedText === null ) { $s = $this->prefix( $this->mTextform ); - $s = str_replace( '_', ' ', $s ); + $s = strtr( $s, '_', ' ' ); $this->mPrefixedText = $s; } return $this->mPrefixedText; @@ -1582,7 +1610,7 @@ class Title { */ public function getSubpageUrlForm() { $text = $this->getSubpageText(); - $text = wfUrlencode( str_replace( ' ', '_', $text ) ); + $text = wfUrlencode( strtr( $text, ' ', '_' ) ); return $text; } @@ -1593,7 +1621,7 @@ class Title { */ public function getPrefixedURL() { $s = $this->prefix( $this->mDbkeyform ); - $s = wfUrlencode( str_replace( ' ', '_', $s ) ); + $s = wfUrlencode( strtr( $s, ' ', '_' ) ); return $s; } @@ -1911,7 +1939,6 @@ class Title { * - quick : does cheap permission checks from slaves (usable for GUI creation) * - full : does cheap and expensive checks possibly from a slave * - secure : does cheap and expensive checks, using the master as needed - * @param bool $short Set this to true to stop after the first permission error. * @param array $ignoreErrors Array of Strings Set this to a list of message keys * whose corresponding errors may be ignored. * @return array Array of arguments to wfMessage to explain permissions problems. @@ -2574,6 +2601,7 @@ class Title { if ( $row['permission'] == 'autoconfirmed' ) { $row['permission'] = 'editsemiprotected'; // B/C } + $row['expiry'] = $dbr->decodeExpiry( $row['expiry'] ); } $this->mTitleProtection = $row; } @@ -2711,7 +2739,6 @@ class Title { * false. */ public function getCascadeProtectionSources( $getPages = true ) { - global $wgContLang; $pagerestrictions = array(); if ( $this->mCascadeSources !== null && $getPages ) { @@ -2754,7 +2781,7 @@ class Title { $now = wfTimestampNow(); foreach ( $res as $row ) { - $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW ); + $expiry = $dbr->decodeExpiry( $row->pr_expiry ); if ( $expiry > $now ) { if ( $getPages ) { $page_id = $row->pr_page; @@ -2887,28 +2914,29 @@ class Title { * restrictions from page table (pre 1.10) */ public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) { - global $wgContLang; $dbr = wfGetDB( DB_SLAVE ); $restrictionTypes = $this->getRestrictionTypes(); foreach ( $restrictionTypes as $type ) { $this->mRestrictions[$type] = array(); - $this->mRestrictionsExpiry[$type] = $wgContLang->formatExpiry( '', TS_MW ); + $this->mRestrictionsExpiry[$type] = 'infinity'; } $this->mCascadeRestriction = false; # Backwards-compatibility: also load the restrictions from the page record (old format). + if ( $oldFashionedRestrictions !== null ) { + $this->mOldRestrictions = $oldFashionedRestrictions; + } - if ( $oldFashionedRestrictions === null ) { - $oldFashionedRestrictions = $dbr->selectField( 'page', 'page_restrictions', + if ( $this->mOldRestrictions === false ) { + $this->mOldRestrictions = $dbr->selectField( 'page', 'page_restrictions', array( 'page_id' => $this->getArticleID() ), __METHOD__ ); } - if ( $oldFashionedRestrictions != '' ) { - - foreach ( explode( ':', trim( $oldFashionedRestrictions ) ) as $restrict ) { + if ( $this->mOldRestrictions != '' ) { + foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) { $temp = explode( '=', trim( $restrict ) ); if ( count( $temp ) == 1 ) { // old old format should be treated as edit/move restriction @@ -2921,9 +2949,6 @@ class Title { } } } - - $this->mOldRestrictions = true; - } if ( count( $rows ) ) { @@ -2940,7 +2965,7 @@ class Title { // This code should be refactored, now that it's being used more generally, // But I don't really see any harm in leaving it in Block for now -werdna - $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW ); + $expiry = $dbr->decodeExpiry( $row->pr_expiry ); // Only apply the restrictions if they haven't expired! if ( !$expiry || $expiry > $now ) { @@ -2962,11 +2987,9 @@ class Title { * restrictions from page table (pre 1.10) */ public function loadRestrictions( $oldFashionedRestrictions = null ) { - global $wgContLang; if ( !$this->mRestrictionsLoaded ) { + $dbr = wfGetDB( DB_SLAVE ); if ( $this->exists() ) { - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'page_restrictions', array( 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ), @@ -2980,7 +3003,7 @@ class Title { if ( $title_protection ) { $now = wfTimestampNow(); - $expiry = $wgContLang->formatExpiry( $title_protection['expiry'], TS_MW ); + $expiry = $dbr->decodeExpiry( $title_protection['expiry'] ); if ( !$expiry || $expiry > $now ) { // Apply the restrictions @@ -2990,7 +3013,7 @@ class Title { $this->mTitleProtection = false; } } else { - $this->mRestrictionsExpiry['create'] = $wgContLang->formatExpiry( '', TS_MW ); + $this->mRestrictionsExpiry['create'] = 'infinity'; } $this->mRestrictionsLoaded = true; } @@ -3274,6 +3297,7 @@ class Title { } $this->mRestrictionsLoaded = false; $this->mRestrictions = array(); + $this->mOldRestrictions = false; $this->mRedirect = null; $this->mLength = -1; $this->mLatestID = false; @@ -3318,6 +3342,7 @@ class Title { * namespace prefixes, sets the other forms, and canonicalizes * everything. * + * @throws MalformedTitleException On invalid titles * @return bool True on success */ private function secureAndSplit() { @@ -3328,15 +3353,12 @@ class Title { $dbkey = $this->mDbkeyform; - try { - // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share - // the parsing code with Title, while avoiding massive refactoring. - // @todo: get rid of secureAndSplit, refactor parsing code. - $titleParser = self::getTitleParser(); - $parts = $titleParser->splitTitleString( $dbkey, $this->getDefaultNamespace() ); - } catch ( MalformedTitleException $ex ) { - return false; - } + // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share + // the parsing code with Title, while avoiding massive refactoring. + // @todo: get rid of secureAndSplit, refactor parsing code. + $titleParser = self::getTitleParser(); + // MalformedTitleException can be thrown here + $parts = $titleParser->splitTitleString( $dbkey, $this->getDefaultNamespace() ); # Fill fields $this->setFragment( '#' . $parts['fragment'] ); @@ -3347,7 +3369,7 @@ class Title { $this->mDbkeyform = $parts['dbkey']; $this->mUrlform = wfUrlencode( $this->mDbkeyform ); - $this->mTextform = str_replace( '_', ' ', $this->mDbkeyform ); + $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' ); # We already know that some pages won't be in the database! if ( $this->isExternal() || $this->mNamespace == NS_SPECIAL ) { @@ -3428,8 +3450,6 @@ class Title { * @return array Array of Title objects linking here */ public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) { - global $wgContentHandlerUseDB; - $id = $this->getArticleID(); # If the page doesn't exist; there can't be any link from this page @@ -3443,49 +3463,36 @@ class Title { $db = wfGetDB( DB_SLAVE ); } - $namespaceFiled = "{$prefix}_namespace"; - $titleField = "{$prefix}_title"; - - $fields = array( - $namespaceFiled, - $titleField, - 'page_id', - 'page_len', - 'page_is_redirect', - 'page_latest' - ); - - if ( $wgContentHandlerUseDB ) { - $fields[] = 'page_content_model'; - } + $blNamespace = "{$prefix}_namespace"; + $blTitle = "{$prefix}_title"; $res = $db->select( array( $table, 'page' ), - $fields, + array_merge( + array( $blNamespace, $blTitle ), + WikiPage::selectFields() + ), array( "{$prefix}_from" => $id ), __METHOD__, $options, array( 'page' => array( 'LEFT JOIN', - array( "page_namespace=$namespaceFiled", "page_title=$titleField" ) + array( "page_namespace=$blNamespace", "page_title=$blTitle" ) ) ) ); $retVal = array(); - if ( $res->numRows() ) { - $linkCache = LinkCache::singleton(); - foreach ( $res as $row ) { - $titleObj = Title::makeTitle( $row->$namespaceFiled, $row->$titleField ); - if ( $titleObj ) { - if ( $row->page_id ) { - $linkCache->addGoodLinkObjFromRow( $titleObj, $row ); - } else { - $linkCache->addBadLinkObj( $titleObj ); - } - $retVal[] = $titleObj; - } + $linkCache = LinkCache::singleton(); + foreach ( $res as $row ) { + if ( $row->page_id ) { + $titleObj = Title::newFromRow( $row ); + } else { + $titleObj = Title::makeTitle( $row->$blNamespace, $row->$blTitle ); + $linkCache->addBadLinkObj( $titleObj ); } + $retVal[] = $titleObj; } + return $retVal; } @@ -3624,7 +3631,7 @@ class Title { ); } - return $errors ? : true; + return $errors ?: true; } /** @@ -4236,10 +4243,12 @@ class Title { * If you want to know if a title can be meaningfully viewed, you should * probably call the isKnown() method instead. * + * @param int $flags An optional bit field; may be Title::GAID_FOR_UPDATE to check + * from master/for update * @return bool */ - public function exists() { - $exists = $this->getArticleID() != 0; + public function exists( $flags = 0 ) { + $exists = $this->getArticleID( $flags ) != 0; Hooks::run( 'TitleExists', array( $this, &$exists ) ); return $exists; } @@ -4370,9 +4379,10 @@ class Title { /** * Updates page_touched for this page; called from LinksUpdate.php * + * @param integer $purgeTime TS_MW timestamp [optional] * @return bool True if the update succeeded */ - public function invalidateCache() { + public function invalidateCache( $purgeTime = null ) { if ( wfReadOnly() ) { return false; } @@ -4384,11 +4394,13 @@ class Title { $method = __METHOD__; $dbw = wfGetDB( DB_MASTER ); $conds = $this->pageCond(); - $dbw->onTransactionIdle( function () use ( $dbw, $conds, $method ) { + $dbw->onTransactionIdle( function () use ( $dbw, $conds, $method, $purgeTime ) { + $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() ); + $dbw->update( 'page', - array( 'page_touched' => $dbw->timestamp() ), - $conds, + array( 'page_touched' => $dbTimestamp ), + $conds + array( 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ), $method ); } ); @@ -4432,35 +4444,29 @@ class Title { * @return string|null */ public function getNotificationTimestamp( $user = null ) { - global $wgUser, $wgShowUpdatedMarker; + global $wgUser; + // Assume current user if none given if ( !$user ) { $user = $wgUser; } // Check cache first $uid = $user->getId(); + if ( !$uid ) { + return false; + } // avoid isset here, as it'll return false for null entries if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) { return $this->mNotificationTimestamp[$uid]; } - if ( !$uid || !$wgShowUpdatedMarker || !$user->isAllowed( 'viewmywatchlist' ) ) { - $this->mNotificationTimestamp[$uid] = false; - return $this->mNotificationTimestamp[$uid]; - } // Don't cache too much! if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) { $this->mNotificationTimestamp = array(); } - $dbr = wfGetDB( DB_SLAVE ); - $this->mNotificationTimestamp[$uid] = $dbr->selectField( 'watchlist', - 'wl_notificationtimestamp', - array( - 'wl_user' => $user->getId(), - 'wl_namespace' => $this->getNamespace(), - 'wl_title' => $this->getDBkey(), - ), - __METHOD__ - ); + + $watchedItem = WatchedItem::fromUserTitle( $user, $this ); + $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp(); + return $this->mNotificationTimestamp[$uid]; } @@ -4540,15 +4546,17 @@ class Title { public function isValidRedirectTarget() { global $wgInvalidRedirectTargets; - // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here - if ( $this->isSpecial( 'Userlogout' ) ) { - return false; - } - - foreach ( $wgInvalidRedirectTargets as $target ) { - if ( $this->isSpecial( $target ) ) { + if ( $this->isSpecialPage() ) { + // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here + if ( $this->isSpecial( 'Userlogout' ) ) { return false; } + + foreach ( $wgInvalidRedirectTargets as $target ) { + if ( $this->isSpecial( $target ) ) { + return false; + } + } } return true; @@ -4731,7 +4739,7 @@ class Title { } } else { // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys - $editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->getDBkey() ); + $editnoticeText = $editnotice_ns . '-' . strtr( $this->getDBkey(), '/', '-' ); $msg = wfMessage( $editnoticeText ); if ( $msg->exists() ) { $html = $msg->parseAsBlock(); @@ -4752,4 +4760,26 @@ class Title { Hooks::run( 'TitleGetEditNotices', array( $this, $oldid, &$notices ) ); return $notices; } + + /** + * @return array + */ + public function __sleep() { + return array( + 'mNamespace', + 'mDbkeyform', + 'mFragment', + 'mInterwiki', + 'mLocalInterwiki', + 'mUserCaseDBKey', + 'mDefaultNamespace', + ); + } + + public function __wakeup() { + $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0; + $this->mUrlform = wfUrlencode( $this->mDbkeyform ); + $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' ); + } + } |