summaryrefslogtreecommitdiff
path: root/includes/Title.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/Title.php')
-rw-r--r--includes/Title.php270
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, '_', ' ' );
+ }
+
}