* however, it does so inefficiently.
* @note Consider using a TitleValue object instead. TitleValue is more lightweight
* and does not rely on global state or the database.
- *
- * @internal documentation reviewed 15 Mar 2010
*/
class Title {
/** @var MapCacheLRU */
/** @var string Interwiki prefix */
public $mInterwiki = '';
+ /** @var bool Was this Title created from a string with a local interwiki prefix? */
+ private $mLocalInterwiki = false;
+
/** @var string Title fragment (i.e. the bit after the #) */
public $mFragment = '';
/** @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? */
/** @var TitleValue A corresponding TitleValue object */
private $mTitleValue = null;
+
+ /** @var bool Would deleting this page be a big deletion? */
+ private $mIsBigDeletion = null;
// @}
/**
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;
}
}
* by a prefix. If you want to force a specific namespace even if
* $text might begin with a namespace prefix, use makeTitle() or
* makeTitleSafe().
- * @throws MWException
+ * @throws InvalidArgumentException
* @return Title|null Title or null on an error.
*/
public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
if ( is_object( $text ) ) {
- throw new MWException( 'Title::newFromText given an object' );
+ throw new InvalidArgumentException( '$text must be a string.' );
+ } elseif ( !is_string( $text ) ) {
+ 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();
$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;
}
/**
# 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;
}
}
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;
$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;
}
}
$t = new Title();
- $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki );
- if ( $t->secureAndSplit() ) {
+ $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki, true );
+
+ try {
+ $t->secureAndSplit();
return $t;
- } else {
+ } catch ( MalformedTitleException $ex ) {
return null;
}
}
* Note that this doesn't pick up many things that could be wrong with titles, but that
* replacing this regex with something valid will make many titles valid.
*
- * @todo: move this into MediaWikiTitleCodec
+ * @deprecated since 1.25, use MediaWikiTitleCodec::getTitleInvalidRegex() instead
*
* @return string Regex string
*/
static function getTitleInvalidRegex() {
- static $rxTc = false;
- if ( !$rxTc ) {
- # Matching titles will be held as illegal.
- $rxTc = '/' .
- # Any character not allowed is forbidden...
- '[^' . self::legalChars() . ']' .
- # URL percent encoding sequences interfere with the ability
- # to round-trip titles -- you can't link to them consistently.
- '|%[0-9A-Fa-f]{2}' .
- # XML/HTML character references produce similar issues.
- '|&[A-Za-z0-9\x80-\xff]+;' .
- '|&#[0-9]+;' .
- '|&#x[0-9A-Fa-f]+;' .
- '/S';
- }
-
- return $rxTc;
+ wfDeprecated( __METHOD__, '1.25' );
+ return MediaWikiTitleCodec::getTitleInvalidRegex();
}
/**
* @param string $title The DB key form the title
* @param string $fragment The link fragment (after the "#")
* @param string $interwiki The interwiki prefix
+ * @param bool $canoncialNamespace If true, use the canonical name for
+ * $ns instead of the localized version.
* @return string The prefixed form of the title
*/
- public static function makeName( $ns, $title, $fragment = '', $interwiki = '' ) {
+ public static function makeName( $ns, $title, $fragment = '', $interwiki = '',
+ $canoncialNamespace = false
+ ) {
global $wgContLang;
- $namespace = $wgContLang->getNsText( $ns );
+ if ( $canoncialNamespace ) {
+ $namespace = MWNamespace::getCanonicalName( $ns );
+ } else {
+ $namespace = $wgContLang->getNsText( $ns );
+ }
$name = $namespace == '' ? $title : "$namespace:$title";
if ( strval( $interwiki ) != '' ) {
$name = "$interwiki:$name";
/**
* Determine whether the object refers to a page within
- * this project.
+ * this project (either this wiki or a wiki with a local
+ * interwiki, see https://www.mediawiki.org/wiki/Manual:Interwiki_table#iw_local )
*
* @return bool True if this is an in-project interwiki link or a wikilink, false otherwise
*/
return $this->mInterwiki;
}
+ /**
+ * Was this a local interwiki link?
+ *
+ * @return bool
+ */
+ public function wasLocalInterwiki() {
+ return $this->mLocalInterwiki;
+ }
+
/**
* Determine whether the object refers to a page within
* this project and is transcludable.
/**
* Get a TitleValue object representing this Title.
*
- * @note: Not all valid Titles have a corresponding valid TitleValue
+ * @note Not all valid Titles have a corresponding valid TitleValue
* (e.g. TitleValues cannot represent page-local links that have a
* fragment but no title text).
*
/**
* 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
*/
- public function getContentModel() {
- if ( !$this->mContentModel ) {
+ public function getContentModel( $flags = 0 ) {
+ if ( !$this->mContentModel && $this->getArticleID( $flags ) ) {
$linkCache = LinkCache::singleton();
+ $linkCache->addLinkObj( $this ); # in case we already had an article ID
$this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
}
$this->mContentModel = ContentHandler::getDefaultModelFor( $this );
}
- if ( !$this->mContentModel ) {
- throw new MWException( 'Failed to determine content model!' );
- }
-
return $this->mContentModel;
}
}
try {
- $formatter = $this->getTitleFormatter();
+ $formatter = self::getTitleFormatter();
return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
- } catch ( InvalidArgumentException $ex ) {
+ } catch ( InvalidArgumentException $ex ) {
wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );
return false;
}
* Is this in a namespace that allows actual pages?
*
* @return bool
- * @internal note -- uses hardcoded namespace index instead of constants
*/
public function canExist() {
return $this->mNamespace >= NS_MAIN;
/**
* Returns true if the title is inside one of the specified namespaces.
*
- * @param ...$namespaces The namespaces to check for
+ * @param int $namespaces,... The namespaces to check for
* @return bool
* @since 1.19
*/
}
$result = true;
- wfRunHooks( 'TitleIsMovable', array( $this, &$result ) );
+ Hooks::run( 'TitleIsMovable', array( $this, &$result ) );
return $result;
}
# @note This hook is also called in ContentHandler::getDefaultModel.
# It's called here again to make sure hook functions can force this
- # method to return true even outside the mediawiki namespace.
+ # method to return true even outside the MediaWiki namespace.
- wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$isCssOrJsPage ) );
+ Hooks::run( 'TitleIsCssOrJsPage', array( $this, &$isCssOrJsPage ), '1.25' );
return $isCssOrJsPage;
}
return Title::makeTitle( $subjectNS, $this->getDBkey() );
}
+ /**
+ * Get the other title for this page, if this is a subject page
+ * get the talk page, if it is a subject page get the talk page
+ *
+ * @since 1.25
+ * @throws MWException
+ * @return Title
+ */
+ public function getOtherPage() {
+ if ( $this->isSpecialPage() ) {
+ throw new MWException( 'Special pages cannot have other pages' );
+ }
+ if ( $this->isTalkPage() ) {
+ return $this->getSubjectPage();
+ } else {
+ return $this->getTalkPage();
+ }
+ }
+
/**
* Get the default namespace index, for when there is no namespace
*
* @param string $fragment Text
*/
public function setFragment( $fragment ) {
- $this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
+ $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' );
}
/**
*/
public function getPrefixedDBkey() {
$s = $this->prefix( $this->mDbkeyform );
- $s = str_replace( ' ', '_', $s );
+ $s = strtr( $s, ' ', '_' );
return $s;
}
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;
*/
public function getSubpageUrlForm() {
$text = $this->getSubpageText();
- $text = wfUrlencode( str_replace( ' ', '_', $text ) );
+ $text = wfUrlencode( strtr( $text, ' ', '_' ) );
return $text;
}
*/
public function getPrefixedURL() {
$s = $this->prefix( $this->mDbkeyform );
- $s = wfUrlencode( str_replace( ' ', '_', $s ) );
+ $s = wfUrlencode( strtr( $s, ' ', '_' ) );
return $s;
}
# Finally, add the fragment.
$url .= $this->getFragmentForURL();
- wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) );
+ Hooks::run( 'GetFullURL', array( &$this, &$url, $query ) );
return $url;
}
* $wgServer is prepended to make an absolute URL.
*
* @see self::getFullURL to always get an absolute URL.
+ * @see self::getLinkURL to always get a URL that's the simplest URL that will be
+ * valid to link, locally, to the current Title.
* @see self::newFromText to produce a Title object.
*
- * @param string|array $query an optional query string,
+ * @param string|array $query An optional query string,
* not used for interwiki links. Can be specified as an associative array as well,
* e.g., array( 'action' => 'edit' ) (keys and values will be URL-escaped).
* Some query patterns will trigger various shorturl path replacements.
$dbkey = wfUrlencode( $this->getPrefixedDBkey() );
if ( $query == '' ) {
$url = str_replace( '$1', $dbkey, $wgArticlePath );
- wfRunHooks( 'GetLocalURL::Article', array( &$this, &$url ) );
+ Hooks::run( 'GetLocalURL::Article', array( &$this, &$url ) );
} else {
global $wgVariantArticlePath, $wgActionPaths, $wgContLang;
$url = false;
}
}
- wfRunHooks( 'GetLocalURL::Internal', array( &$this, &$url, $query ) );
+ Hooks::run( 'GetLocalURL::Internal', array( &$this, &$url, $query ) );
// @todo FIXME: This causes breakage in various places when we
// actually expected a local URL and end up with dupe prefixes.
$url = $wgServer . $url;
}
}
- wfRunHooks( 'GetLocalURL', array( &$this, &$url, $query ) );
+ Hooks::run( 'GetLocalURL', array( &$this, &$url, $query ) );
return $url;
}
* @return string The URL
*/
public function getLinkURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
- wfProfileIn( __METHOD__ );
if ( $this->isExternal() || $proto !== PROTO_RELATIVE ) {
$ret = $this->getFullURL( $query, $query2, $proto );
} elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
} else {
$ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
}
- wfProfileOut( __METHOD__ );
return $ret;
}
$query = self::fixUrlQueryArgs( $query, $query2 );
$server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
$url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
- wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query ) );
+ Hooks::run( 'GetInternalURL', array( &$this, &$url, $query ) );
return $url;
}
public function getCanonicalURL( $query = '', $query2 = false ) {
$query = self::fixUrlQueryArgs( $query, $query2 );
$url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
- wfRunHooks( 'GetCanonicalURL', array( &$this, &$url, $query ) );
+ Hooks::run( 'GetCanonicalURL', array( &$this, &$url, $query ) );
return $url;
}
return $this->mWatched;
}
- /**
- * Can $wgUser read this page?
- *
- * @deprecated since 1.19; use userCan(), quickUserCan() or getUserPermissionsErrors() instead
- * @return bool
- */
- public function userCanRead() {
- wfDeprecated( __METHOD__, '1.19' );
- return $this->userCan( 'read' );
- }
-
/**
* Can $user perform $action on this page?
* This skips potentially expensive cascading permission checks
* @param string $action Action that permission needs to be checked for
* @param User $user User to check (since 1.19); $wgUser will be used if not
* provided.
- * @param bool $doExpensiveQueries Set this to false to avoid doing
- * unnecessary queries.
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @return bool
*/
- public function userCan( $action, $user = null, $doExpensiveQueries = true ) {
+ public function userCan( $action, $user = null, $rigor = 'secure' ) {
if ( !$user instanceof User ) {
global $wgUser;
$user = $wgUser;
}
- return !count( $this->getUserPermissionsErrorsInternal(
- $action, $user, $doExpensiveQueries, true ) );
+ return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $rigor, true ) );
}
/**
*
* @todo FIXME: This *does not* check throttles (User::pingLimiter()).
*
- * @param string $action action that permission needs to be checked for
+ * @param string $action Action that permission needs to be checked for
* @param User $user User to check
- * @param bool $doExpensiveQueries Set this to false to avoid doing unnecessary
- * queries by skipping checks for cascading protections and user blocks.
- * @param array $ignoreErrors of Strings Set this to a list of message keys
+ * @param string $rigor One of (quick,full,secure)
+ * - 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.
*/
- public function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true,
- $ignoreErrors = array()
+ public function getUserPermissionsErrors(
+ $action, $user, $rigor = 'secure', $ignoreErrors = array()
) {
- $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries );
+ $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $rigor );
// Remove the errors being ignored.
foreach ( $errors as $index => $error ) {
/**
* Permissions checks that fail most often, and which are easiest to test.
*
- * @param string $action the action to check
+ * @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkQuickPermissions( $action, $user, $errors,
- $doExpensiveQueries, $short
- ) {
- if ( !wfRunHooks( 'TitleQuickPermissions',
- array( $this, $user, $action, &$errors, $doExpensiveQueries, $short ) )
+ private function checkQuickPermissions( $action, $user, $errors, $rigor, $short ) {
+ if ( !Hooks::run( 'TitleQuickPermissions',
+ array( $this, $user, $action, &$errors, ( $rigor !== 'quick' ), $short ) )
) {
return $errors;
}
* @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkPermissionHooks( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) {
// Use getUserPermissionsErrors instead
$result = '';
- if ( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
+ if ( !Hooks::run( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
return $result ? array() : array( array( 'badaccess-group0' ) );
}
// Check getUserPermissionsErrors hook
- if ( !wfRunHooks( 'getUserPermissionsErrors', array( &$this, &$user, $action, &$result ) ) ) {
+ if ( !Hooks::run( 'getUserPermissionsErrors', array( &$this, &$user, $action, &$result ) ) ) {
$errors = $this->resultToError( $errors, $result );
}
// Check getUserPermissionsErrorsExpensive hook
if (
- $doExpensiveQueries
+ $rigor !== 'quick'
&& !( $short && count( $errors ) > 0 )
- && !wfRunHooks( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) )
+ && !Hooks::run( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) )
) {
$errors = $this->resultToError( $errors, $result );
}
* @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkSpecialsAndNSPermissions( $action, $user, $errors,
- $doExpensiveQueries, $short
- ) {
+ private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) {
# Only 'createaccount' can be performed on special pages,
# which don't actually exist in the DB.
if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
$ns = $this->mNamespace == NS_MAIN ?
wfMessage( 'nstab-main' )->text() : $this->getNsText();
$errors[] = $this->mNamespace == NS_MEDIAWIKI ?
- array( 'protectedinterface' ) : array( 'namespaceprotected', $ns );
+ array( 'protectedinterface', $action ) : array( 'namespaceprotected', $ns, $action );
}
return $errors;
* @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkCSSandJSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short ) {
# Protect css/js subpages of user pages
# XXX: this might be better using restrictions
# XXX: right 'editusercssjs' is deprecated, for backward compatibility only
if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' ) ) {
if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
if ( $this->isCssSubpage() && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) ) {
- $errors[] = array( 'mycustomcssprotected' );
+ $errors[] = array( 'mycustomcssprotected', $action );
} elseif ( $this->isJsSubpage() && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) ) {
- $errors[] = array( 'mycustomjsprotected' );
+ $errors[] = array( 'mycustomjsprotected', $action );
}
} else {
if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
- $errors[] = array( 'customcssprotected' );
+ $errors[] = array( 'customcssprotected', $action );
} elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
- $errors[] = array( 'customjsprotected' );
+ $errors[] = array( 'customjsprotected', $action );
}
}
}
* @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkPageRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkPageRestrictions( $action, $user, $errors, $rigor, $short ) {
foreach ( $this->getRestrictions( $action ) as $right ) {
// Backwards compatibility, rewrite sysop -> editprotected
if ( $right == 'sysop' ) {
continue;
}
if ( !$user->isAllowed( $right ) ) {
- $errors[] = array( 'protectedpagetext', $right );
+ $errors[] = array( 'protectedpagetext', $right, $action );
} elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
- $errors[] = array( 'protectedpagetext', 'protect' );
+ $errors[] = array( 'protectedpagetext', 'protect', $action );
}
}
* @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkCascadingSourcesRestrictions( $action, $user, $errors,
- $doExpensiveQueries, $short
- ) {
- if ( $doExpensiveQueries && !$this->isCssJsSubpage() ) {
+ private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) {
+ if ( $rigor !== 'quick' && !$this->isCssJsSubpage() ) {
# We /could/ use the protection level on the source page, but it's
# fairly ugly as we have to establish a precedence hierarchy for pages
# included by multiple cascade-protected pages. So just restrict
foreach ( $cascadingSources as $page ) {
$pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
}
- $errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages );
+ $errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages, $action );
}
}
}
* @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkActionPermissions( $action, $user, $errors,
- $doExpensiveQueries, $short
- ) {
+ private function checkActionPermissions( $action, $user, $errors, $rigor, $short ) {
global $wgDeleteRevisionsLimit, $wgLang;
if ( $action == 'protect' ) {
- if ( count( $this->getUserPermissionsErrorsInternal( 'edit',
- $user, $doExpensiveQueries, true ) )
- ) {
+ if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
// If they can't edit, they shouldn't protect.
$errors[] = array( 'protect-cantedit' );
}
} elseif ( $action == 'create' ) {
$title_protection = $this->getTitleProtection();
if ( $title_protection ) {
- if ( $title_protection['pt_create_perm'] == 'sysop' ) {
- $title_protection['pt_create_perm'] = 'editprotected'; // B/C
- }
- if ( $title_protection['pt_create_perm'] == 'autoconfirmed' ) {
- $title_protection['pt_create_perm'] = 'editsemiprotected'; // B/C
- }
- if ( $title_protection['pt_create_perm'] == ''
- || !$user->isAllowed( $title_protection['pt_create_perm'] )
+ if ( $title_protection['permission'] == ''
+ || !$user->isAllowed( $title_protection['permission'] )
) {
$errors[] = array(
'titleprotected',
- User::whoIs( $title_protection['pt_user'] ),
- $title_protection['pt_reason']
+ User::whoIs( $title_protection['user'] ),
+ $title_protection['reason']
);
}
}
$errors[] = array( 'immobile-target-page' );
}
} elseif ( $action == 'delete' ) {
- if ( $doExpensiveQueries && $wgDeleteRevisionsLimit
+ $tempErrors = $this->checkPageRestrictions( 'edit', $user, array(), $rigor, true );
+ if ( !$tempErrors ) {
+ $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
+ $user, $tempErrors, $rigor, true );
+ }
+ if ( $tempErrors ) {
+ // If protection keeps them from editing, they shouldn't be able to delete.
+ $errors[] = array( 'deleteprotected' );
+ }
+ if ( $rigor !== 'quick' && $wgDeleteRevisionsLimit
&& !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
) {
$errors[] = array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) );
* @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkUserBlock( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkUserBlock( $action, $user, $errors, $rigor, $short ) {
// Account creation blocks handled at userlogin.
// Unblocking handled in SpecialUnblock
- if ( !$doExpensiveQueries || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
+ if ( $rigor === 'quick' || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
return $errors;
}
$errors[] = array( 'confirmedittext' );
}
- if ( ( $action == 'edit' || $action == 'create' ) && !$user->isBlockedFrom( $this ) ) {
+ $useSlave = ( $rigor !== 'secure' );
+ if ( ( $action == 'edit' || $action == 'create' )
+ && !$user->isBlockedFrom( $this, $useSlave )
+ ) {
// Don't block the user from editing their own talk page unless they've been
// explicitly blocked from that too.
- } elseif ( $user->isBlocked() && $user->mBlock->prevents( $action ) !== false ) {
+ } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
// @todo FIXME: Pass the relevant context into this function.
$errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
}
* @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) {
global $wgWhitelistRead, $wgWhitelistReadRegexp;
$whitelisted = false;
if ( !$whitelisted ) {
# If the title is not whitelisted, give extensions a chance to do so...
- wfRunHooks( 'TitleReadWhitelist', array( $this, $user, &$whitelisted ) );
+ Hooks::run( 'TitleReadWhitelist', array( $this, $user, &$whitelisted ) );
if ( !$whitelisted ) {
$errors[] = $this->missingPermissionError( $action, $short );
}
*
* @param string $action Action that permission needs to be checked for
* @param User $user User to check
- * @param bool $doExpensiveQueries Set this to false to avoid doing unnecessary queries.
+ * @param string $rigor One of (quick,full,secure)
+ * - 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.
* @return array Array of arrays of the arguments to wfMessage to explain permissions problems.
*/
- protected function getUserPermissionsErrorsInternal( $action, $user,
- $doExpensiveQueries = true, $short = false
+ protected function getUserPermissionsErrorsInternal(
+ $action, $user, $rigor = 'secure', $short = false
) {
- wfProfileIn( __METHOD__ );
+ if ( $rigor === true ) {
+ $rigor = 'secure'; // b/c
+ } elseif ( $rigor === false ) {
+ $rigor = 'quick'; // b/c
+ } elseif ( !in_array( $rigor, array( 'quick', 'full', 'secure' ) ) ) {
+ throw new Exception( "Invalid rigor parameter '$rigor'." );
+ }
# Read has special handling
if ( $action == 'read' ) {
'checkPermissionHooks',
'checkReadPermissions',
);
+ # Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions
+ # here as it will lead to duplicate error messages. This is okay to do
+ # since anywhere that checks for create will also check for edit, and
+ # those checks are called for edit.
+ } elseif ( $action == 'create' ) {
+ $checks = array(
+ 'checkQuickPermissions',
+ 'checkPermissionHooks',
+ 'checkPageRestrictions',
+ 'checkCascadingSourcesRestrictions',
+ 'checkActionPermissions',
+ 'checkUserBlock'
+ );
} else {
$checks = array(
'checkQuickPermissions',
while ( count( $checks ) > 0 &&
!( $short && count( $errors ) > 0 ) ) {
$method = array_shift( $checks );
- $errors = $this->$method( $action, $user, $errors, $doExpensiveQueries, $short );
+ $errors = $this->$method( $action, $user, $errors, $rigor, $short );
}
- wfProfileOut( __METHOD__ );
return $errors;
}
$types = array_diff( $types, array( 'upload' ) );
}
- wfRunHooks( 'TitleGetRestrictionTypes', array( $this, &$types ) );
+ Hooks::run( 'TitleGetRestrictionTypes', array( $this, &$types ) );
wfDebug( __METHOD__ . ': applicable restrictions to [[' .
$this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
* @return array|bool An associative array representing any existent title
* protection, or false if there's none.
*/
- private function getTitleProtection() {
+ public function getTitleProtection() {
// Can't protect pages in special namespaces
if ( $this->getNamespace() < 0 ) {
return false;
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select(
'protected_titles',
- array( 'pt_user', 'pt_reason', 'pt_expiry', 'pt_create_perm' ),
+ array(
+ 'user' => 'pt_user',
+ 'reason' => 'pt_reason',
+ 'expiry' => 'pt_expiry',
+ 'permission' => 'pt_create_perm'
+ ),
array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
__METHOD__
);
// fetchRow returns false if there are no rows.
- $this->mTitleProtection = $dbr->fetchRow( $res );
+ $row = $dbr->fetchRow( $res );
+ if ( $row ) {
+ if ( $row['permission'] == 'sysop' ) {
+ $row['permission'] = 'editprotected'; // B/C
+ }
+ if ( $row['permission'] == 'autoconfirmed' ) {
+ $row['permission'] = 'editsemiprotected'; // B/C
+ }
+ $row['expiry'] = $dbr->decodeExpiry( $row['expiry'] );
+ }
+ $this->mTitleProtection = $row;
}
return $this->mTitleProtection;
}
/**
* Does the title correspond to a protected article?
*
- * @param string $action the action the page is protected from,
+ * @param string $action The action the page is protected from,
* by default checks all actions.
* @return bool
*/
* false.
*/
public function getCascadeProtectionSources( $getPages = true ) {
- global $wgContLang;
$pagerestrictions = array();
if ( $this->mCascadeSources !== null && $getPages ) {
return array( $this->mHasCascadingRestrictions, $pagerestrictions );
}
- wfProfileIn( __METHOD__ );
-
$dbr = wfGetDB( DB_SLAVE );
if ( $this->getNamespace() == NS_FILE ) {
$sources = $getPages ? array() : false;
$now = wfTimestampNow();
- $purgeExpired = false;
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;
} else {
$sources = true;
}
- } else {
- // Trigger lazy purge of expired restrictions from the db
- $purgeExpired = true;
}
}
- if ( $purgeExpired ) {
- Title::purgeExpiredRestrictions();
- }
if ( $getPages ) {
$this->mCascadeSources = $sources;
$this->mHasCascadingRestrictions = $sources;
}
- wfProfileOut( __METHOD__ );
return array( $sources, $pagerestrictions );
}
* Accessor/initialisation for mRestrictions
*
* @param string $action Action that permission needs to be checked for
- * @return array Restriction levels needed to take the action. All levels
- * are required.
+ * @return array Restriction levels needed to take the action. All levels are
+ * required. Note that restriction levels are normally user rights, but 'sysop'
+ * and 'autoconfirmed' are also allowed for backwards compatibility. These should
+ * be mapped to 'editprotected' and 'editsemiprotected' respectively.
*/
public function getRestrictions( $action ) {
if ( !$this->mRestrictionsLoaded ) {
* 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
}
}
}
-
- $this->mOldRestrictions = true;
-
}
if ( count( $rows ) ) {
# Current system - load second to make them override.
$now = wfTimestampNow();
- $purgeExpired = false;
# Cycle through all the restrictions.
foreach ( $rows as $row ) {
// 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 ) {
$this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
$this->mCascadeRestriction |= $row->pr_cascade;
- } else {
- // Trigger a lazy purge of expired restrictions
- $purgeExpired = true;
}
}
-
- if ( $purgeExpired ) {
- Title::purgeExpiredRestrictions();
- }
}
$this->mRestrictionsLoaded = true;
* 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' ),
if ( $title_protection ) {
$now = wfTimestampNow();
- $expiry = $wgContLang->formatExpiry( $title_protection['pt_expiry'], TS_MW );
+ $expiry = $dbr->decodeExpiry( $title_protection['expiry'] );
if ( !$expiry || $expiry > $now ) {
// Apply the restrictions
$this->mRestrictionsExpiry['create'] = $expiry;
- $this->mRestrictions['create'] = explode( ',', trim( $title_protection['pt_create_perm'] ) );
+ $this->mRestrictions['create'] = explode( ',', trim( $title_protection['permission'] ) );
} else { // Get rid of the old restrictions
- Title::purgeExpiredRestrictions();
$this->mTitleProtection = false;
}
} else {
- $this->mRestrictionsExpiry['create'] = $wgContLang->formatExpiry( '', TS_MW );
+ $this->mRestrictionsExpiry['create'] = 'infinity';
}
$this->mRestrictionsLoaded = true;
}
$method = __METHOD__;
$dbw = wfGetDB( DB_MASTER );
- $dbw->onTransactionIdle( function() use ( $dbw, $method ) {
+ $dbw->onTransactionIdle( function () use ( $dbw, $method ) {
$dbw->delete(
'page_restrictions',
array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
if ( !is_null( $this->mRedirect ) ) {
return $this->mRedirect;
}
- # Calling getArticleID() loads the field from cache as needed
if ( !$this->getArticleID( $flags ) ) {
$this->mRedirect = false;
return $this->mRedirect;
}
$linkCache = LinkCache::singleton();
+ $linkCache->addLinkObj( $this ); # in case we already had an article ID
$cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
if ( $cached === null ) {
# Trust LinkCache's state over our own
if ( $this->mLength != -1 ) {
return $this->mLength;
}
- # Calling getArticleID() loads the field from cache as needed
if ( !$this->getArticleID( $flags ) ) {
$this->mLength = 0;
return $this->mLength;
}
$linkCache = LinkCache::singleton();
+ $linkCache->addLinkObj( $this ); # in case we already had an article ID
$cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
if ( $cached === null ) {
# Trust LinkCache's state over our own, as for isRedirect()
if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
return intval( $this->mLatestID );
}
- # Calling getArticleID() loads the field from cache as needed
if ( !$this->getArticleID( $flags ) ) {
$this->mLatestID = 0;
return $this->mLatestID;
}
$linkCache = LinkCache::singleton();
- $linkCache->addLinkObj( $this );
+ $linkCache->addLinkObj( $this ); # in case we already had an article ID
$cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
if ( $cached === null ) {
# Trust LinkCache's state over our own, as for isRedirect()
}
$this->mRestrictionsLoaded = false;
$this->mRestrictions = array();
+ $this->mOldRestrictions = false;
$this->mRedirect = null;
$this->mLength = -1;
$this->mLatestID = false;
$this->mEstimateRevisions = null;
$this->mPageLanguage = false;
$this->mDbPageLanguage = null;
+ $this->mIsBigDeletion = null;
+ }
+
+ public static function clearCaches() {
+ $linkCache = LinkCache::singleton();
+ $linkCache->clear();
+
+ $titleCache = self::getTitleCache();
+ $titleCache->clear();
}
/**
* namespace prefixes, sets the other forms, and canonicalizes
* everything.
*
+ * @throws MalformedTitleException On invalid titles
* @return bool True on success
*/
private function secureAndSplit() {
$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.
- $parser = $this->getTitleParser();
- $parts = $parser->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'] );
$this->mInterwiki = $parts['interwiki'];
+ $this->mLocalInterwiki = $parts['local_interwiki'];
$this->mNamespace = $parts['namespace'];
$this->mUserCaseDBKey = $parts['user_case_dbkey'];
$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 ) {
* @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
$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;
}
$urls[] = $this->getInternalUrl( 'action=raw&ctype=text/css' );
}
- wfRunHooks( 'TitleSquidURLs', array( $this, &$urls ) );
+ Hooks::run( 'TitleSquidURLs', array( $this, &$urls ) );
return $urls;
}
/**
* Move this page without authentication
*
+ * @deprecated since 1.25 use MovePage class instead
* @param Title $nt The new page Title
* @return array|bool True on success, getUserPermissionsErrors()-like array on failure
*/
public function moveNoAuth( &$nt ) {
+ wfDeprecated( __METHOD__, '1.25' );
return $this->moveTo( $nt, false );
}
* Check whether a given move operation would be valid.
* Returns true if ok, or a getUserPermissionsErrors()-like array otherwise
*
+ * @deprecated since 1.25, use MovePage's methods instead
* @param Title $nt The new title
- * @param bool $auth Indicates whether $wgUser's permissions
- * should be checked
+ * @param bool $auth Whether to check user permissions (uses $wgUser)
* @param string $reason Is the log summary of the move, used for spam checking
* @return array|bool True on success, getUserPermissionsErrors()-like array on failure
*/
public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
- global $wgUser, $wgContentHandlerUseDB;
+ global $wgUser;
- $errors = array();
- if ( !$nt ) {
+ if ( !( $nt instanceof Title ) ) {
// Normally we'd add this to $errors, but we'll get
// lots of syntax errors if $nt is not an object
return array( array( 'badtitletext' ) );
}
- if ( $this->equals( $nt ) ) {
- $errors[] = array( 'selfmove' );
- }
- if ( !$this->isMovable() ) {
- $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
- }
- if ( $nt->isExternal() ) {
- $errors[] = array( 'immobile-target-namespace-iw' );
- }
- if ( !$nt->isMovable() ) {
- $errors[] = array( 'immobile-target-namespace', $nt->getNsText() );
- }
-
- $oldid = $this->getArticleID();
- $newid = $nt->getArticleID();
-
- if ( strlen( $nt->getDBkey() ) < 1 ) {
- $errors[] = array( 'articleexists' );
- }
- if (
- ( $this->getDBkey() == '' ) ||
- ( !$oldid ) ||
- ( $nt->getDBkey() == '' )
- ) {
- $errors[] = array( 'badarticleerror' );
- }
-
- // Content model checks
- if ( !$wgContentHandlerUseDB &&
- $this->getContentModel() !== $nt->getContentModel() ) {
- // can't move a page if that would change the page's content model
- $errors[] = array(
- 'bad-target-model',
- ContentHandler::getLocalizedName( $this->getContentModel() ),
- ContentHandler::getLocalizedName( $nt->getContentModel() )
- );
- }
-
- // Image-specific checks
- if ( $this->getNamespace() == NS_FILE ) {
- $errors = array_merge( $errors, $this->validateFileMoveOperation( $nt ) );
- }
-
- if ( $nt->getNamespace() == NS_FILE && $this->getNamespace() != NS_FILE ) {
- $errors[] = array( 'nonfile-cannot-move-to-file' );
- }
+ $mp = new MovePage( $this, $nt );
+ $errors = $mp->isValidMove()->getErrorsArray();
if ( $auth ) {
- $errors = wfMergeErrorArrays( $errors,
- $this->getUserPermissionsErrors( 'move', $wgUser ),
- $this->getUserPermissionsErrors( 'edit', $wgUser ),
- $nt->getUserPermissionsErrors( 'move-target', $wgUser ),
- $nt->getUserPermissionsErrors( 'edit', $wgUser ) );
- }
-
- $match = EditPage::matchSummarySpamRegex( $reason );
- if ( $match !== false ) {
- // This is kind of lame, won't display nice
- $errors[] = array( 'spamprotectiontext' );
- }
-
- $err = null;
- if ( !wfRunHooks( 'AbortMove', array( $this, $nt, $wgUser, &$err, $reason ) ) ) {
- $errors[] = array( 'hookaborted', $err );
+ $errors = wfMergeErrorArrays(
+ $errors,
+ $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
+ );
}
- # The move is allowed only if (1) the target doesn't exist, or
- # (2) the target is a redirect to the source, and has no history
- # (so we can undo bad moves right after they're done).
-
- if ( 0 != $newid ) { # Target exists; check for validity
- if ( !$this->isValidMoveTarget( $nt ) ) {
- $errors[] = array( 'articleexists' );
- }
- } else {
- $tp = $nt->getTitleProtection();
- $right = $tp['pt_create_perm'];
- if ( $right == 'sysop' ) {
- $right = 'editprotected'; // B/C
- }
- if ( $right == 'autoconfirmed' ) {
- $right = 'editsemiprotected'; // B/C
- }
- if ( $tp and !$wgUser->isAllowed( $right ) ) {
- $errors[] = array( 'cantmove-titleprotected' );
- }
- }
- if ( empty( $errors ) ) {
- return true;
- }
- return $errors;
+ return $errors ?: true;
}
/**
* Check if the requested move target is a valid file move target
+ * @todo move this to MovePage
* @param Title $nt Target title
* @return array List of errors
*/
$errors = array();
- // wfFindFile( $nt ) / wfLocalFile( $nt ) is not allowed until below
-
- $file = wfLocalFile( $this );
- if ( $file->exists() ) {
- if ( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) {
- $errors[] = array( 'imageinvalidfilename' );
- }
- if ( !File::checkExtensionCompatibility( $file, $nt->getDBkey() ) ) {
- $errors[] = array( 'imagetypemismatch' );
- }
- }
-
- if ( $nt->getNamespace() != NS_FILE ) {
- $errors[] = array( 'imagenocrossnamespace' );
- // From here we want to do checks on a file object, so if we can't
- // create one, we must return.
- return $errors;
- }
-
- // wfFindFile( $nt ) / wfLocalFile( $nt ) is allowed below here
-
$destFile = wfLocalFile( $nt );
- if ( !$wgUser->isAllowed( 'reupload-shared' ) && !$destFile->exists() && wfFindFile( $nt ) ) {
+ $destFile->load( File::READ_LATEST );
+ if ( !$wgUser->isAllowed( 'reupload-shared' )
+ && !$destFile->exists() && wfFindFile( $nt )
+ ) {
$errors[] = array( 'file-exists-sharedrepo' );
}
/**
* Move a title to a new location
*
+ * @deprecated since 1.25, use the MovePage class instead
* @param Title $nt The new title
* @param bool $auth Indicates whether $wgUser's permissions
* should be checked
$createRedirect = true;
}
- wfRunHooks( 'TitleMove', array( $this, $nt, $wgUser ) );
-
- // If it is a file, move it first.
- // It is done before all other moving stuff is done because it's hard to revert.
- $dbw = wfGetDB( DB_MASTER );
- if ( $this->getNamespace() == NS_FILE ) {
- $file = wfLocalFile( $this );
- if ( $file->exists() ) {
- $status = $file->move( $nt );
- if ( !$status->isOk() ) {
- return $status->getErrorsArray();
- }
- }
- // Clear RepoGroup process cache
- RepoGroup::singleton()->clearCache( $this );
- RepoGroup::singleton()->clearCache( $nt ); # clear false negative cache
- }
-
- $dbw->begin( __METHOD__ ); # If $file was a LocalFile, its transaction would have closed our own.
- $pageid = $this->getArticleID( self::GAID_FOR_UPDATE );
- $protected = $this->isProtected();
-
- // Do the actual move
- $this->moveToInternal( $nt, $reason, $createRedirect );
-
- // Refresh the sortkey for this row. Be careful to avoid resetting
- // cl_timestamp, which may disturb time-based lists on some sites.
- $prefixes = $dbw->select(
- 'categorylinks',
- array( 'cl_sortkey_prefix', 'cl_to' ),
- array( 'cl_from' => $pageid ),
- __METHOD__
- );
- foreach ( $prefixes as $prefixRow ) {
- $prefix = $prefixRow->cl_sortkey_prefix;
- $catTo = $prefixRow->cl_to;
- $dbw->update( 'categorylinks',
- array(
- 'cl_sortkey' => Collation::singleton()->getSortKey(
- $nt->getCategorySortkey( $prefix ) ),
- 'cl_timestamp=cl_timestamp' ),
- array(
- 'cl_from' => $pageid,
- 'cl_to' => $catTo ),
- __METHOD__
- );
- }
-
- $redirid = $this->getArticleID();
-
- if ( $protected ) {
- # Protect the redirect title as the title used to be...
- $dbw->insertSelect( 'page_restrictions', 'page_restrictions',
- array(
- 'pr_page' => $redirid,
- 'pr_type' => 'pr_type',
- 'pr_level' => 'pr_level',
- 'pr_cascade' => 'pr_cascade',
- 'pr_user' => 'pr_user',
- 'pr_expiry' => 'pr_expiry'
- ),
- array( 'pr_page' => $pageid ),
- __METHOD__,
- array( 'IGNORE' )
- );
- # Update the protection log
- $log = new LogPage( 'protect' );
- $comment = wfMessage(
- 'prot_1movedto2',
- $this->getPrefixedText(),
- $nt->getPrefixedText()
- )->inContentLanguage()->text();
- if ( $reason ) {
- $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
- }
- // @todo FIXME: $params?
- $logId = $log->addEntry(
- 'move_prot',
- $nt,
- $comment,
- array( $this->getPrefixedText() ),
- $wgUser
- );
-
- // reread inserted pr_ids for log relation
- $insertedPrIds = $dbw->select(
- 'page_restrictions',
- 'pr_id',
- array( 'pr_page' => $redirid ),
- __METHOD__
- );
- $logRelationsValues = array();
- foreach ( $insertedPrIds as $prid ) {
- $logRelationsValues[] = $prid->pr_id;
- }
- $log->addRelations( 'pr_id', $logRelationsValues, $logId );
- }
-
- # Update watchlists
- $oldnamespace = MWNamespace::getSubject( $this->getNamespace() );
- $newnamespace = MWNamespace::getSubject( $nt->getNamespace() );
- $oldtitle = $this->getDBkey();
- $newtitle = $nt->getDBkey();
-
- if ( $oldnamespace != $newnamespace || $oldtitle != $newtitle ) {
- WatchedItem::duplicateEntries( $this, $nt );
- }
-
- $dbw->commit( __METHOD__ );
-
- wfRunHooks( 'TitleMoveComplete', array( &$this, &$nt, &$wgUser, $pageid, $redirid, $reason ) );
- return true;
- }
-
- /**
- * Move page to a title which is either a redirect to the
- * source page or nonexistent
- *
- * @param Title $nt The page to move to, which should be a redirect or nonexistent
- * @param string $reason The reason for the move
- * @param bool $createRedirect Whether to leave a redirect at the old title. Does not check
- * if the user has the suppressredirect right
- * @throws MWException
- */
- private function moveToInternal( &$nt, $reason = '', $createRedirect = true ) {
- global $wgUser, $wgContLang;
-
- if ( $nt->exists() ) {
- $moveOverRedirect = true;
- $logType = 'move_redir';
- } else {
- $moveOverRedirect = false;
- $logType = 'move';
- }
-
- if ( $createRedirect ) {
- if ( $this->getNamespace() == NS_CATEGORY
- && !wfMessage( 'category-move-redirect-override' )->inContentLanguage()->isDisabled()
- ) {
- $redirectContent = new WikitextContent(
- wfMessage( 'category-move-redirect-override' )
- ->params( $nt->getPrefixedText() )->inContentLanguage()->plain() );
- } else {
- $contentHandler = ContentHandler::getForTitle( $this );
- $redirectContent = $contentHandler->makeRedirectContent( $nt,
- wfMessage( 'move-redirect-text' )->inContentLanguage()->plain() );
- }
-
- // NOTE: If this page's content model does not support redirects, $redirectContent will be null.
+ $mp = new MovePage( $this, $nt );
+ $status = $mp->move( $wgUser, $reason, $createRedirect );
+ if ( $status->isOK() ) {
+ return true;
} else {
- $redirectContent = null;
- }
-
- $logEntry = new ManualLogEntry( 'move', $logType );
- $logEntry->setPerformer( $wgUser );
- $logEntry->setTarget( $this );
- $logEntry->setComment( $reason );
- $logEntry->setParameters( array(
- '4::target' => $nt->getPrefixedText(),
- '5::noredir' => $redirectContent ? '0': '1',
- ) );
-
- $formatter = LogFormatter::newFromEntry( $logEntry );
- $formatter->setContext( RequestContext::newExtraneousContext( $this ) );
- $comment = $formatter->getPlainActionText();
- if ( $reason ) {
- $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
- }
- # Truncate for whole multibyte characters.
- $comment = $wgContLang->truncate( $comment, 255 );
-
- $oldid = $this->getArticleID();
-
- $dbw = wfGetDB( DB_MASTER );
-
- $newpage = WikiPage::factory( $nt );
-
- if ( $moveOverRedirect ) {
- $newid = $nt->getArticleID();
- $newcontent = $newpage->getContent();
-
- # Delete the old redirect. We don't save it to history since
- # by definition if we've got here it's rather uninteresting.
- # We have to remove it so that the next step doesn't trigger
- # a conflict on the unique namespace+title index...
- $dbw->delete( 'page', array( 'page_id' => $newid ), __METHOD__ );
-
- $newpage->doDeleteUpdates( $newid, $newcontent );
- }
-
- # Save a null revision in the page's history notifying of the move
- $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true, $wgUser );
- if ( !is_object( $nullRevision ) ) {
- throw new MWException( 'No valid null revision produced in ' . __METHOD__ );
- }
-
- $nullRevision->insertOn( $dbw );
-
- # Change the name of the target page:
- $dbw->update( 'page',
- /* SET */ array(
- 'page_namespace' => $nt->getNamespace(),
- 'page_title' => $nt->getDBkey(),
- ),
- /* WHERE */ array( 'page_id' => $oldid ),
- __METHOD__
- );
-
- // clean up the old title before reset article id - bug 45348
- if ( !$redirectContent ) {
- WikiPage::onArticleDelete( $this );
- }
-
- $this->resetArticleID( 0 ); // 0 == non existing
- $nt->resetArticleID( $oldid );
- $newpage->loadPageData( WikiPage::READ_LOCKING ); // bug 46397
-
- $newpage->updateRevisionOn( $dbw, $nullRevision );
-
- wfRunHooks( 'NewRevisionFromEditComplete',
- array( $newpage, $nullRevision, $nullRevision->getParentId(), $wgUser ) );
-
- $newpage->doEditUpdates( $nullRevision, $wgUser, array( 'changed' => false ) );
-
- if ( !$moveOverRedirect ) {
- WikiPage::onArticleCreate( $nt );
- }
-
- # Recreate the redirect, this time in the other direction.
- if ( $redirectContent ) {
- $redirectArticle = WikiPage::factory( $this );
- $redirectArticle->loadFromRow( false, WikiPage::READ_LOCKING ); // bug 46397
- $newid = $redirectArticle->insertOn( $dbw );
- if ( $newid ) { // sanity
- $this->resetArticleID( $newid );
- $redirectRevision = new Revision( array(
- 'title' => $this, // for determining the default content model
- 'page' => $newid,
- 'user_text' => $wgUser->getName(),
- 'user' => $wgUser->getId(),
- 'comment' => $comment,
- 'content' => $redirectContent ) );
- $redirectRevision->insertOn( $dbw );
- $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
-
- wfRunHooks( 'NewRevisionFromEditComplete',
- array( $redirectArticle, $redirectRevision, false, $wgUser ) );
-
- $redirectArticle->doEditUpdates( $redirectRevision, $wgUser, array( 'created' => true ) );
- }
+ return $status->getErrorsArray();
}
-
- # Log the move
- $logid = $logEntry->insert();
- $logEntry->publish( $logid );
}
/**
* Checks if $this can be moved to a given Title
* - Selects for update, so don't call it unless you mean business
*
+ * @deprecated since 1.25, use MovePage's methods instead
* @param Title $nt The new title to check
* @return bool
*/
# Is it an existing file?
if ( $nt->getNamespace() == NS_FILE ) {
$file = wfLocalFile( $nt );
+ $file->load( File::READ_LATEST );
if ( $file->exists() ) {
wfDebug( __METHOD__ . ": file exists\n" );
return false;
return false;
}
- $revCount = $this->estimateRevisionCount();
- return $revCount > $wgDeleteRevisionsLimit;
+ if ( $this->mIsBigDeletion === null ) {
+ $dbr = wfGetDB( DB_SLAVE );
+
+ $revCount = $dbr->selectRowCount(
+ 'revision',
+ '1',
+ array( 'rev_page' => $this->getArticleID() ),
+ __METHOD__,
+ array( 'LIMIT' => $wgDeleteRevisionsLimit + 1 )
+ );
+
+ $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
+ }
+
+ return $this->mIsBigDeletion;
}
/**
- * Get the approximate revision count of this page.
+ * Get the approximate revision count of this page.
*
* @return int
*/
'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
);
if ( $max !== null ) {
- $res = $dbr->select( 'revision', '1',
+ return $dbr->selectRowCount( 'revision', '1',
$conds,
__METHOD__,
array( 'LIMIT' => $max + 1 ) // extra to detect truncation
);
- return $res->numRows();
} else {
return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
}
}
// No DB query needed if $old and $new are the same or successive revisions:
if ( $old->getId() === $new->getId() ) {
- return ( $old_cmp === '>' && $new_cmp === '<' ) ? array() : array( $old->getRawUserText() );
+ return ( $old_cmp === '>' && $new_cmp === '<' ) ?
+ array() :
+ array( $old->getUserText( Revision::RAW ) );
} elseif ( $old->getId() === $new->getParentId() ) {
if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
- $authors[] = $old->getRawUserText();
- if ( $old->getRawUserText() != $new->getRawUserText() ) {
- $authors[] = $new->getRawUserText();
+ $authors[] = $old->getUserText( Revision::RAW );
+ if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
+ $authors[] = $new->getUserText( Revision::RAW );
}
} elseif ( $old_cmp === '>=' ) {
- $authors[] = $old->getRawUserText();
+ $authors[] = $old->getUserText( Revision::RAW );
} elseif ( $new_cmp === '<=' ) {
- $authors[] = $new->getRawUserText();
+ $authors[] = $new->getUserText( Revision::RAW );
}
return $authors;
}
* 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() {
- return $this->getArticleID() != 0;
+ public function exists( $flags = 0 ) {
+ $exists = $this->getArticleID( $flags ) != 0;
+ Hooks::run( 'TitleExists', array( $this, &$exists ) );
+ return $exists;
}
/**
* @param Title $title
* @param bool|null $isKnown
*/
- wfRunHooks( 'TitleIsAlwaysKnown', array( $this, &$isKnown ) );
+ Hooks::run( 'TitleIsAlwaysKnown', array( $this, &$isKnown ) );
if ( !is_null( $isKnown ) ) {
return $isKnown;
/**
* 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;
}
$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
);
} );
* @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];
}
// on the Title object passed in, and should probably
// tell the users to run updateCollations.php --force
// in order to re-sort existing category relations.
- wfRunHooks( 'GetDefaultSortkey', array( $this, &$unprefixed ) );
+ Hooks::run( 'GetDefaultSortkey', array( $this, &$unprefixed ) );
if ( $prefix !== '' ) {
# Separate with a line feed, so the unprefixed part is only used as
# a tiebreaker when two pages have the exact same prefix.
*/
public function getPageLanguage() {
global $wgLang, $wgLanguageCode;
- wfProfileIn( __METHOD__ );
if ( $this->isSpecialPage() ) {
// special pages are in the user language
- wfProfileOut( __METHOD__ );
return $wgLang;
}
// Checking if DB language is set
if ( $this->mDbPageLanguage ) {
- wfProfileOut( __METHOD__ );
return wfGetLangObj( $this->mDbPageLanguage );
}
$langObj = wfGetLangObj( $this->mPageLanguage[0] );
}
- wfProfileOut( __METHOD__ );
return $langObj;
}
public function getEditNotices( $oldid = 0 ) {
$notices = array();
- # Optional notices on a per-namespace and per-page basis
+ // Optional notice for the entire namespace
$editnotice_ns = 'editnotice-' . $this->getNamespace();
- $editnotice_ns_message = wfMessage( $editnotice_ns );
- if ( $editnotice_ns_message->exists() ) {
- $notices[$editnotice_ns] = $editnotice_ns_message->parseAsBlock();
+ $msg = wfMessage( $editnotice_ns );
+ if ( $msg->exists() ) {
+ $html = $msg->parseAsBlock();
+ // Edit notices may have complex logic, but output nothing (T91715)
+ if ( trim( $html ) !== '' ) {
+ $notices[$editnotice_ns] = Html::rawElement(
+ 'div',
+ array( 'class' => array(
+ 'mw-editnotice',
+ 'mw-editnotice-namespace',
+ Sanitizer::escapeClass( "mw-$editnotice_ns" )
+ ) ),
+ $html
+ );
+ }
}
+
if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
+ // Optional notice for page itself and any parent page
$parts = explode( '/', $this->getDBkey() );
$editnotice_base = $editnotice_ns;
while ( count( $parts ) > 0 ) {
$editnotice_base .= '-' . array_shift( $parts );
- $editnotice_base_msg = wfMessage( $editnotice_base );
- if ( $editnotice_base_msg->exists() ) {
- $notices[$editnotice_base] = $editnotice_base_msg->parseAsBlock();
+ $msg = wfMessage( $editnotice_base );
+ if ( $msg->exists() ) {
+ $html = $msg->parseAsBlock();
+ if ( trim( $html ) !== '' ) {
+ $notices[$editnotice_base] = Html::rawElement(
+ 'div',
+ array( 'class' => array(
+ 'mw-editnotice',
+ 'mw-editnotice-base',
+ Sanitizer::escapeClass( "mw-$editnotice_base" )
+ ) ),
+ $html
+ );
+ }
}
}
} else {
- # Even if there are no subpages in namespace, we still don't want / in MW ns.
- $editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->getDBkey() );
- $editnoticeMsg = wfMessage( $editnoticeText );
- if ( $editnoticeMsg->exists() ) {
- $notices[$editnoticeText] = $editnoticeMsg->parseAsBlock();
+ // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
+ $editnoticeText = $editnotice_ns . '-' . strtr( $this->getDBkey(), '/', '-' );
+ $msg = wfMessage( $editnoticeText );
+ if ( $msg->exists() ) {
+ $html = $msg->parseAsBlock();
+ if ( trim( $html ) !== '' ) {
+ $notices[$editnoticeText] = Html::rawElement(
+ 'div',
+ array( 'class' => array(
+ 'mw-editnotice',
+ 'mw-editnotice-page',
+ Sanitizer::escapeClass( "mw-$editnoticeText" )
+ ) ),
+ $html
+ );
+ }
}
}
- wfRunHooks( 'TitleGetEditNotices', array( $this, $oldid, &$notices ) );
+ 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, '_', ' ' );
+ }
+
}