var $mWatched = null; ///< Is $wgUser watching this page? null if unfilled, accessed through userIsWatching()
var $mLength = -1; ///< The page length, 0 for special pages
var $mRedirect = null; ///< Is the article at this title a redirect?
+ var $mNotificationTimestamp = array(); ///< Associative array of user ID -> timestamp/false
//@}
}
/**
- * Create a new Title from text, such as what one would
- * find in a link. Decodes any HTML entities in the text.
+ * Create a new Title from text, such as what one would find in a link. De-
+ * codes any HTML entities in the text.
*
- * @param $text \type{\string} the link text; spaces, prefixes,
- * and an initial ':' indicating the main namespace
- * are accepted
- * @param $defaultNamespace \type{\int} the namespace to use if
- * none is specified by a prefix
- * @return \type{Title} the new object, or NULL on an error
+ * @param $text string The link text; spaces, prefixes, and an
+ * initial ':' indicating the main namespace are accepted.
+ * @param $defaultNamespace int The namespace to use if none is speci-
+ * fied by a prefix. If you want to force a specific namespace even if
+ * $text might begin with a namespace prefix, use makeTitle() or
+ * makeTitleSafe().
+ * @return Title The new object, or null on an error.
*/
public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
if( is_object( $text ) ) {
$m[1] = urldecode( ltrim( $m[1], ':' ) );
}
$title = Title::newFromText( $m[1] );
- // Redirects to Special:Userlogout are not permitted
- if( $title instanceof Title && !$title->isSpecial( 'Userlogout' ) )
+ // Redirects to some special pages are not permitted
+ if( $title instanceof Title
+ && !$title->isSpecial( 'Userlogout' )
+ && !$title->isSpecial( 'Filepath' ) )
+ {
return $title;
+ }
}
}
return null;
public static function nameOf( $id ) {
$dbr = wfGetDB( DB_SLAVE );
- $s = $dbr->selectRow( 'page', array( 'page_namespace','page_title' ), array( 'page_id' => $id ), __METHOD__ );
+ $s = $dbr->selectRow( 'page',
+ array( 'page_namespace','page_title' ),
+ array( 'page_id' => $id ),
+ __METHOD__ );
if ( $s === false ) { return NULL; }
$n = self::makeName( $s->page_namespace, $s->page_title );
$t = preg_replace( "/\\s+/", ' ', $t );
- if ( $ns == NS_IMAGE ) {
+ if ( $ns == NS_FILE ) {
$t = preg_replace( "/ (png|gif|jpg|jpeg|ogg)$/", "", $t );
}
return trim( $t );
* @return \type{\string} the associated URL, containing "$1",
* which should be replaced by an article title
* @static (arguably)
+ * @deprecated See Interwiki class
*/
public function getInterwikiLink( $key ) {
- global $wgMemc, $wgInterwikiExpiry;
- global $wgInterwikiCache, $wgContLang;
- $fname = 'Title::getInterwikiLink';
-
- if ( count( Title::$interwikiCache ) >= self::CACHE_MAX ) {
- // Don't use infinite memory
- reset( Title::$interwikiCache );
- unset( Title::$interwikiCache[ key( Title::$interwikiCache ) ] );
- }
-
- $key = $wgContLang->lc( $key );
-
- $k = wfMemcKey( 'interwiki', $key );
- if( array_key_exists( $k, Title::$interwikiCache ) ) {
- return Title::$interwikiCache[$k]->iw_url;
- }
-
- if ($wgInterwikiCache) {
- return Title::getInterwikiCached( $key );
- }
-
- $s = $wgMemc->get( $k );
- # Ignore old keys with no iw_local
- if( $s && isset( $s->iw_local ) && isset($s->iw_trans)) {
- Title::$interwikiCache[$k] = $s;
- return $s->iw_url;
- }
-
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'interwiki',
- array( 'iw_url', 'iw_local', 'iw_trans' ),
- array( 'iw_prefix' => $key ), $fname );
- if( !$res ) {
- return '';
- }
-
- $s = $dbr->fetchObject( $res );
- if( !$s ) {
- # Cache non-existence: create a blank object and save it to memcached
- $s = (object)false;
- $s->iw_url = '';
- $s->iw_local = 0;
- $s->iw_trans = 0;
- }
- $wgMemc->set( $k, $s, $wgInterwikiExpiry );
- Title::$interwikiCache[$k] = $s;
-
- return $s->iw_url;
- }
-
- /**
- * Fetch interwiki prefix data from local cache in constant database.
- *
- * @note More logic is explained in DefaultSettings.
- *
- * @param $key \type{\string} Database key
- * @return \type{\string} URL of interwiki site
- */
- public static function getInterwikiCached( $key ) {
- global $wgInterwikiCache, $wgInterwikiScopes, $wgInterwikiFallbackSite;
- static $db, $site;
-
- if (!$db)
- $db=dba_open($wgInterwikiCache,'r','cdb');
- /* Resolve site name */
- if ($wgInterwikiScopes>=3 and !$site) {
- $site = dba_fetch('__sites:' . wfWikiID(), $db);
- if ($site=="")
- $site = $wgInterwikiFallbackSite;
- }
- $value = dba_fetch( wfMemcKey( $key ), $db);
- if ($value=='' and $wgInterwikiScopes>=3) {
- /* try site-level */
- $value = dba_fetch("_{$site}:{$key}", $db);
- }
- if ($value=='' and $wgInterwikiScopes>=2) {
- /* try globals */
- $value = dba_fetch("__global:{$key}", $db);
- }
- if ($value=='undef')
- $value='';
- $s = (object)false;
- $s->iw_url = '';
- $s->iw_local = 0;
- $s->iw_trans = 0;
- if ($value!='') {
- list($local,$url)=explode(' ',$value,2);
- $s->iw_url=$url;
- $s->iw_local=(int)$local;
- }
- Title::$interwikiCache[wfMemcKey( 'interwiki', $key )] = $s;
- return $s->iw_url;
+ return Interwiki::fetch( $key )->getURL( );
}
/**
*/
public function isLocal() {
if ( $this->mInterwiki != '' ) {
- # Make sure key is loaded into cache
- $this->getInterwikiLink( $this->mInterwiki );
- $k = wfMemcKey( 'interwiki', $this->mInterwiki );
- return (bool)(Title::$interwikiCache[$k]->iw_local);
+ return Interwiki::fetch( $this->mInterwiki )->isLocal();
} else {
return true;
}
public function isTrans() {
if ($this->mInterwiki == '')
return false;
- # Make sure key is loaded into cache
- $this->getInterwikiLink( $this->mInterwiki );
- $k = wfMemcKey( 'interwiki', $this->mInterwiki );
- return (bool)(Title::$interwikiCache[$k]->iw_trans);
+
+ return Interwiki::fetch( $this->mInterwiki )->isTranscludable();
}
/**
* Escape a text fragment, say from a link, for a URL
*/
static function escapeFragmentForURL( $fragment ) {
- $fragment = str_replace( ' ', '_', $fragment );
- $fragment = urlencode( Sanitizer::decodeCharReferences( $fragment ) );
- $replaceArray = array(
- '%3A' => ':',
- '%' => '.'
- );
- return strtr( $fragment, $replaceArray );
+ return Sanitizer::escapeId( $fragment, Sanitizer::NONE );
}
#----------------------------------------------------------------------------
public function getSubpageUrlForm() {
$text = $this->getSubpageText();
$text = wfUrlencode( str_replace( ' ', '_', $text ) );
- $text = str_replace( '%28', '(', str_replace( '%29', ')', $text ) ); # Clean up the URL; per below, this might not be safe
return( $text );
}
*/
public function getPrefixedURL() {
$s = $this->prefix( $this->mDbkeyform );
- $s = str_replace( ' ', '_', $s );
-
- $s = wfUrlencode ( $s ) ;
-
- # Cleaning up URL to make it look nice -- is this safe?
- $s = str_replace( '%28', '(', $s );
- $s = str_replace( '%29', ')', $s );
-
+ $s = wfUrlencode( str_replace( ' ', '_', $s ) );
return $s;
}
$query = wfArrayToCGI( $query );
}
- if ( '' == $this->mInterwiki ) {
+ $interwiki = Interwiki::fetch( $this->mInterwiki );
+ if ( !$interwiki ) {
$url = $this->getLocalUrl( $query, $variant );
// Ugly quick hack to avoid duplicate prefixes (bug 4571 etc)
$url = $wgServer . $url;
}
} else {
- $baseUrl = $this->getInterwikiLink( $this->mInterwiki );
+ $baseUrl = $interwiki->getURL( );
$namespace = wfUrlencode( $this->getNsText() );
if ( '' != $namespace ) {
*/
public function getLocalURL( $query = '', $variant = false ) {
global $wgArticlePath, $wgScript, $wgServer, $wgRequest;
- global $wgVariantArticlePath, $wgContLang, $wgUser, $wgArticlePathForCurid;
+ global $wgVariantArticlePath, $wgContLang, $wgUser;
if( is_array( $query ) ) {
$query = wfArrayToCGI( $query );
}
} else {
$dbkey = wfUrlencode( $this->getPrefixedDBkey() );
- if ( $query == '' || ($wgArticlePathForCurid && substr_count( $query, '&' ) == 0 && strpos( $query, 'curid=' ) === 0 ) ) {
+ if ( $query == '' ) {
if( $variant != false && $wgContLang->hasVariants() ) {
if( $wgVariantArticlePath == false ) {
$variantArticlePath = "$wgScript?title=$1&variant=$2"; // default
} else {
$url = str_replace( '$1', $dbkey, $wgArticlePath );
}
- $url = wfAppendQuery( $url, $query );
} else {
global $wgActionPaths;
$url = false;
}
$errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries );
- global $wgContLang, $wgLang, $wgEmailConfirmToEdit;
+ global $wgContLang;
+ global $wgLang;
+ global $wgEmailConfirmToEdit;
if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() && $action != 'createaccount' ) {
$errors[] = array( 'confirmedittext' );
$blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $user->mBlock->mTimestamp ), true );
if ( $blockExpiry == 'infinity' ) {
- $blockExpiry = wfMsg( 'ipbinfinite' );
+ // Entry in database (table ipblocks) is 'infinity' but 'ipboptions' uses 'infinite' or 'indefinite'
+ $scBlockExpiryOptions = wfMsg( 'ipboptions' );
+
+ foreach ( explode( ',', $scBlockExpiryOptions ) as $option ) {
+ if ( strpos( $option, ':' ) == false )
+ continue;
+
+ list ($show, $value) = explode( ":", $option );
+
+ if ( $value == 'infinite' || $value == 'indefinite' ) {
+ $blockExpiry = $show;
+ break;
+ }
+ }
} else {
$blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
}
$errors[] = array( ($block->mAuto ? 'autoblockedtext' : 'blockedtext'), $link, $reason, $ip, $name,
$blockid, $blockExpiry, $intended, $blockTimestamp );
}
-
+
// Remove the errors being ignored.
-
+
foreach( $errors as $index => $error ) {
$error_key = is_array($error) ? $error[0] : $error;
* @return \type{\array} Array of arrays of the arguments to wfMsg to explain permissions problems.
*/
private function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries = true ) {
- global $wgLang;
-
wfProfileIn( __METHOD__ );
$errors = array();
// Use getUserPermissionsErrors instead
- if ( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
+ if( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
wfProfileOut( __METHOD__ );
return $result ? array() : array( array( 'badaccess-group0' ) );
}
- if (!wfRunHooks( 'getUserPermissionsErrors', array( &$this, &$user, $action, &$result ) ) ) {
- if ($result != array() && is_array($result) && !is_array($result[0]))
+ if( !wfRunHooks( 'getUserPermissionsErrors', array(&$this,&$user,$action,&$result) ) ) {
+ if( is_array($result) && count($result) && !is_array($result[0]) )
$errors[] = $result; # A single array representing an error
- else if (is_array($result) && is_array($result[0]))
+ else if( is_array($result) && is_array($result[0]) )
$errors = array_merge( $errors, $result ); # A nested array representing multiple errors
- else if ($result != '' && $result != null && $result !== true && $result !== false)
+ else if( $result !== '' && is_string($result) )
$errors[] = array($result); # A string representing a message-id
- else if ($result === false )
+ else if( $result === false )
$errors[] = array('badaccess-group0'); # a generic "We don't want them to do that"
}
- if ($doExpensiveQueries && !wfRunHooks( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) ) ) {
- if ($result != array() && is_array($result) && !is_array($result[0]))
+ if( $doExpensiveQueries && !wfRunHooks( 'getUserPermissionsErrorsExpensive', array(&$this,&$user,$action,&$result) ) ) {
+ if( is_array($result) && count($result) && !is_array($result[0]) )
$errors[] = $result; # A single array representing an error
- else if (is_array($result) && is_array($result[0]))
+ else if( is_array($result) && is_array($result[0]) )
$errors = array_merge( $errors, $result ); # A nested array representing multiple errors
- else if ($result != '' && $result != null && $result !== true && $result !== false)
+ else if( $result !== '' && is_string($result) )
$errors[] = array($result); # A string representing a message-id
- else if ($result === false )
+ else if( $result === false )
$errors[] = array('badaccess-group0'); # a generic "We don't want them to do that"
}
+ // TODO: document
$specialOKActions = array( 'createaccount', 'execute' );
if( NS_SPECIAL == $this->mNamespace && !in_array( $action, $specialOKActions) ) {
$errors[] = array('ns-specialprotected');
}
- if ( $this->isNamespaceProtected() ) {
- $ns = $this->getNamespace() == NS_MAIN
- ? wfMsg( 'nstab-main' )
- : $this->getNsText();
- $errors[] = (NS_MEDIAWIKI == $this->mNamespace
- ? array('protectedinterface')
- : array( 'namespaceprotected', $ns ) );
- }
-
- if( $this->mDbkeyform == '_' ) {
- # FIXME: Is this necessary? Shouldn't be allowed anyway...
- $errors[] = array('badaccess-group0');
+ if( $this->isNamespaceProtected() ) {
+ $ns = $this->getNamespace() == NS_MAIN ?
+ wfMsg( 'nstab-main' ) : $this->getNsText();
+ $errors[] = NS_MEDIAWIKI == $this->mNamespace ?
+ array('protectedinterface') : array( 'namespaceprotected', $ns );
}
# protect css/js subpages of user pages
# XXX: this might be better using restrictions
# XXX: Find a way to work around the php bug that prevents using $this->userCanEditCssJsSubpage() from working
- if( $this->isCssJsSubpage()
- && !$user->isAllowed('editusercssjs')
- && !preg_match('/^'.preg_quote($user->getName(), '/').'\//', $this->mTextform) ) {
+ if( $this->isCssJsSubpage() && !$user->isAllowed('editusercssjs')
+ && !preg_match('/^'.preg_quote($user->getName(), '/').'\//', $this->mTextform) )
+ {
$errors[] = array('customcssjsprotected');
}
- if ( $doExpensiveQueries && !$this->isCssJsSubpage() ) {
+ if( $doExpensiveQueries && !$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 it to people with 'protect' permission,
foreach( $this->getRestrictions($action) as $right ) {
// Backwards compatibility, rewrite sysop -> protect
- if ( $right == 'sysop' ) {
+ if( $right == 'sysop' ) {
$right = 'protect';
}
if( '' != $right && !$user->isAllowed( $right ) ) {
- //Users with 'editprotected' permission can edit protected pages
+ // Users with 'editprotected' permission can edit protected pages
if( $action=='edit' && $user->isAllowed( 'editprotected' ) ) {
- //Users with 'editprotected' permission cannot edit protected pages
- //with cascading option turned on.
- if($this->mCascadeRestriction) {
+ // Users with 'editprotected' permission cannot edit protected pages
+ // with cascading option turned on.
+ if( $this->mCascadeRestriction ) {
$errors[] = array( 'protectedpagetext', $right );
} else {
- //Nothing, user can edit!
+ // Nothing, user can edit!
}
} else {
$errors[] = array( 'protectedpagetext', $right );
}
}
- if ($action == 'protect') {
- if ($this->getUserPermissionsErrors('edit', $user) != array()) {
+ if( $action == 'protect' ) {
+ if( $this->getUserPermissionsErrors('edit', $user) != array() ) {
$errors[] = array( 'protect-cantedit' ); // If they can't edit, they shouldn't protect.
}
}
- if ($action == 'create') {
+ if( $action == 'create' ) {
$title_protection = $this->getTitleProtection();
+ if( is_array($title_protection) ) {
+ extract($title_protection); // is this extract() really needed?
- if (is_array($title_protection)) {
- extract($title_protection);
-
- if ($pt_create_perm == 'sysop')
- $pt_create_perm = 'protect';
-
- if ($pt_create_perm == '' || !$user->isAllowed($pt_create_perm)) {
- $errors[] = array ( 'titleprotected', User::whoIs($pt_user), $pt_reason );
+ if( $pt_create_perm == 'sysop' ) {
+ $pt_create_perm = 'protect'; // B/C
+ }
+ if( $pt_create_perm == '' || !$user->isAllowed($pt_create_perm) ) {
+ $errors[] = array( 'titleprotected', User::whoIs($pt_user), $pt_reason );
}
}
- if( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
- ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) ) {
+ if( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
+ ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) )
+ {
$errors[] = $user->isAnon() ? array ('nocreatetext') : array ('nocreate-loggedin');
}
- } elseif( $action == 'move' && !( $this->isMovable() && $user->isAllowed( 'move' ) ) ) {
- $errors[] = $user->isAnon() ? array ( 'movenologintext' ) : array ('movenotallowed');
- } elseif ( !$user->isAllowed( $action ) ) {
+ } elseif( $action == 'move' ) {
+ if( !$user->isAllowed( 'move' ) ) {
+ // User can't move anything
+ $errors[] = $user->isAnon() ? array ( 'movenologintext' ) : array ('movenotallowed');
+ } elseif( !$user->isAllowed( 'move-rootuserpages' )
+ && $this->getNamespace() == NS_USER && !$this->isSubpage() )
+ {
+ // Show user page-specific message only if the user can move other pages
+ $errors[] = array( 'cant-move-user-page' );
+ }
+ // Check for immobile pages
+ if( !MWNamespace::isMovable( $this->getNamespace() ) ) {
+ // Specific message for this case
+ $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
+ } elseif( !$this->isMovable() ) {
+ // Less specific message for rarer cases
+ $errors[] = array( 'immobile-page' );
+ }
+ } elseif( $action == 'move-target' ) {
+ if( !$user->isAllowed( 'move' ) ) {
+ // User can't move anything
+ $errors[] = $user->isAnon() ? array ( 'movenologintext' ) : array ('movenotallowed');
+ } elseif( !$user->isAllowed( 'move-rootuserpages' )
+ && $this->getNamespace() == NS_USER && !$this->isSubpage() )
+ {
+ // Show user page-specific message only if the user can move other pages
+ $errors[] = array( 'cant-move-to-user-page' );
+ }
+ if( !MWNamespace::isMovable( $this->getNamespace() ) ) {
+ $errors[] = array( 'immobile-target-namespace', $this->getNsText() );
+ } elseif( !$this->isMovable() ) {
+ $errors[] = array( 'immobile-target-page' );
+ }
+ } elseif( !$user->isAllowed( $action ) ) {
$return = null;
$groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
User::getGroupsWithPermission( $action ) );
- if ( $groups ) {
+ if( $groups ) {
$return = array( 'badaccess-groups',
- array(
- implode( ', ', $groups ),
- count( $groups ) ) );
- }
- else {
+ array( implode( ', ', $groups ), count( $groups ) ) );
+ } else {
$return = array( "badaccess-group0" );
}
$errors[] = $return;
}
- // Check per-user restrictions
- if( $action != 'read' ) {
- $r = $user->getRestrictionForPage( $this );
- if( !$r )
- $r = $user->getRestrictionForNamespace( $this->getNamespace() );
- if( $r ) {
- $start = $wgLang->timeanddate( $r->getTimestamp() );
- $end = $r->getExpiry() == 'infinity' ?
- wfMsg( 'ipbinfinite' ) :
- $wgLang->timeanddate( $r->getExpiry() );
- if( $r->isPage() )
- $errors[] = array( 'userrestricted-page', $this->getFullText(),
- $r->getBlockerText(), $r->getReason(), $start, $end );
- elseif( $r->isNamespace() ) {
- $errors[] = array( 'userrestricted-namespace', $wgLang->getDisplayNsText( $this->getNamespace() ),
- $r->getBlockerText(), $r->getReason(), $start, $end );
- }
- }
- }
-
wfProfileOut( __METHOD__ );
return $errors;
}
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'protected_titles', '*',
- array ('pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey()) );
+ array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
+ __METHOD__ );
if ($row = $dbr->fetchRow( $res )) {
return $row;
$dbw = wfGetDB( DB_MASTER );
$dbw->delete( 'protected_titles',
- array ('pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey()), __METHOD__ );
+ array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
+ __METHOD__ );
}
/**
* @return \type{\bool} TRUE or FALSE
*/
public function isMovable() {
- return MWNamespace::isMovable( $this->getNamespace() )
- && $this->getInterwiki() == '';
+ return MWNamespace::isMovable( $this->getNamespace() ) && $this->getInterwiki() == '';
}
/**
wfProfileIn( __METHOD__ );
- $dbr = wfGetDb( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
- if ( $this->getNamespace() == NS_IMAGE ) {
+ if ( $this->getNamespace() == NS_FILE ) {
$tables = array ('imagelinks', 'page_restrictions');
$where_clauses = array(
'il_to' => $this->getDBkey(),
$dbr = wfGetDB( DB_SLAVE );
$n = $dbr->selectField( 'archive', 'COUNT(*)', array( 'ar_namespace' => $this->getNamespace(),
'ar_title' => $this->getDBkey() ), $fname );
- if( $this->getNamespace() == NS_IMAGE ) {
+ if( $this->getNamespace() == NS_FILE ) {
$n += $dbr->selectField( 'filearchive', 'COUNT(*)',
array( 'fa_name' => $this->getDBkey() ), $fname );
}
* @return \type{\int} the ID
*/
public function getArticleID( $flags = 0 ) {
+ if( $this->getNamespace() < 0 ) {
+ return $this->mArticleID = 0;
+ }
$linkCache = LinkCache::singleton();
if( $flags & GAID_FOR_UPDATE ) {
$oldUpdate = $linkCache->forUpdate( true );
public function isRedirect( $flags = 0 ) {
if( !is_null($this->mRedirect) )
return $this->mRedirect;
- # Zero for special pages.
- # Also, calling getArticleID() loads the field from cache!
- if( !$this->getArticleID($flags) || $this->getNamespace() == NS_SPECIAL ) {
- return false;
+ # Calling getArticleID() loads the field from cache as needed
+ if( !$this->getArticleID($flags) ) {
+ return $this->mRedirect = false;
}
$linkCache = LinkCache::singleton();
$this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this, 'redirect' );
public function getLength( $flags = 0 ) {
if( $this->mLength != -1 )
return $this->mLength;
- # Zero for special pages.
- # Also, calling getArticleID() loads the field from cache!
- if( !$this->getArticleID($flags) || $this->getNamespace() == NS_SPECIAL ) {
- return 0;
+ # Calling getArticleID() loads the field from cache as needed
+ if( !$this->getArticleID($flags) ) {
+ return $this->mLength = 0;
}
$linkCache = LinkCache::singleton();
$this->mLength = intval( $linkCache->getGoodLinkFieldObj( $this, 'length' ) );
return $this->mLatestID;
$db = ($flags & GAID_FOR_UPDATE) ? wfGetDB(DB_MASTER) : wfGetDB(DB_SLAVE);
- $this->mLatestID = $db->selectField( 'page', 'page_latest',
- array( 'page_namespace' => $this->getNamespace(), 'page_title' => $this->getDBKey() ),
- __METHOD__ );
+ $this->mLatestID = $db->selectField( 'page', 'page_latest', $this->pageCond(), __METHOD__ );
return $this->mLatestID;
}
*/
public function invalidateCache() {
global $wgUseFileCache;
-
- if ( wfReadOnly() ) {
+ if( wfReadOnly() ) {
return;
}
-
$dbw = wfGetDB( DB_MASTER );
$success = $dbw->update( 'page',
- array( /* SET */
- 'page_touched' => $dbw->timestamp()
- ), array( /* WHERE */
- 'page_namespace' => $this->getNamespace() ,
- 'page_title' => $this->getDBkey()
- ), 'Title::invalidateCache'
+ array( 'page_touched' => $dbw->timestamp() ),
+ $this->pageCond(),
+ __METHOD__
);
-
- if ($wgUseFileCache) {
- $cache = new HTMLFileCache($this);
- @unlink($cache->fileCacheName());
+ if( $wgUseFileCache) {
+ $cache = new HTMLFileCache( $this );
+ @unlink( $cache->fileCacheName() );
}
-
return $success;
}
# Strip Unicode bidi override characters.
# Sometimes they slip into cut-n-pasted page titles, where the
# override chars get included in list displays.
- $dbkey = str_replace( "\xE2\x80\x8E", '', $dbkey ); // 200E LEFT-TO-RIGHT MARK
- $dbkey = str_replace( "\xE2\x80\x8F", '', $dbkey ); // 200F RIGHT-TO-LEFT MARK
+ $dbkey = preg_replace( '/\xE2\x80[\x8E\x8F\xAA-\xAE]/S', '', $dbkey );
# Clean up whitespace
#
# Ordinary namespace
$dbkey = $m[2];
$this->mNamespace = $ns;
- } elseif( $this->getInterwikiLink( $p ) ) {
+ } elseif( Interwiki::isValidInterwiki( $p ) ) {
if( !$firstPass ) {
# Can't make a local interwiki link to an interwiki link.
# That's just crazy!
* @return \type{Title} the object for the subject page
*/
public function getSubjectPage() {
- return Title::makeTitle( MWNamespace::getSubject( $this->getNamespace() ), $this->getDBkey() );
+ // Is this the same title?
+ $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
+ if( $this->getNamespace() == $subjectNS ) {
+ return $this;
+ }
+ return Title::makeTitle( $subjectNS, $this->getDBkey() );
}
/**
* @return \type{\mixed} True on success, getUserPermissionsErrors()-like array on failure
*/
public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
+ global $wgUser;
+
$errors = array();
if( !$nt ) {
// Normally we'd add this to $errors, but we'll get
if( $this->equals( $nt ) ) {
$errors[] = array('selfmove');
}
- if( !$this->isMovable() || !$nt->isMovable() ) {
- $errors[] = array('immobile_namespace');
+ if( !$this->isMovable() ) {
+ $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
+ }
+ if ( $nt->getInterwiki() != '' ) {
+ $errors[] = array( 'immobile-target-namespace-iw' );
+ }
+ if ( !$nt->isMovable() ) {
+ $errors[] = array('immobile-target-namespace', $nt->getNsText() );
}
$oldid = $this->getArticleID();
}
// Image-specific checks
- if( $this->getNamespace() == NS_IMAGE ) {
+ if( $this->getNamespace() == NS_FILE ) {
$file = wfLocalFile( $this );
if( $file->exists() ) {
- if( $nt->getNamespace() != NS_IMAGE ) {
+ if( $nt->getNamespace() != NS_FILE ) {
$errors[] = array('imagenocrossnamespace');
}
if( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) {
$errors[] = array('imageinvalidfilename');
}
- if( !File::checkExtensionCompatibility( $file, $nt->getDbKey() ) ) {
+ if( !File::checkExtensionCompatibility( $file, $nt->getDBKey() ) ) {
$errors[] = array('imagetypemismatch');
}
}
}
if ( $auth ) {
- global $wgUser;
- $errors = wfArrayMerge($errors,
- $this->getUserPermissionsErrors('move', $wgUser),
- $this->getUserPermissionsErrors('edit', $wgUser),
- $nt->getUserPermissionsErrors('move', $wgUser),
- $nt->getUserPermissionsErrors('edit', $wgUser));
+ $errors = wfMergeErrorArrays( $errors,
+ $this->getUserPermissionsErrors('move', $wgUser),
+ $this->getUserPermissionsErrors('edit', $wgUser),
+ $nt->getUserPermissionsErrors('move-target', $wgUser),
+ $nt->getUserPermissionsErrors('edit', $wgUser) );
}
$match = EditPage::matchSpamRegex( $reason );
$errors[] = array('spamprotectiontext');
}
- global $wgUser;
$err = null;
if( !wfRunHooks( 'AbortMove', array( $this, $nt, $wgUser, &$err, $reason ) ) ) {
$errors[] = array('hookaborted', $err);
);
# Update the protection log
$log = new LogPage( 'protect' );
- $comment = wfMsgForContent('1movedto2',$this->getPrefixedText(), $nt->getPrefixedText() );
- $log->addEntry( 'protect', $nt, $comment, array() ); // FIXME: $params?
+ $comment = wfMsgForContent('prot_1movedto2',$this->getPrefixedText(), $nt->getPrefixedText() );
+ if( $reason ) $comment .= ': ' . $reason;
+ $log->addEntry( 'move_prot', $nt, $comment, array($this->getPrefixedText()) ); // FIXME: $params?
}
# Update watchlists
$nullRevId = $nullRevision->insertOn( $dbw );
$article = new Article( $this );
- wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest) );
+ wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $wgUser) );
# Change the name of the target page:
$dbw->update( 'page',
$redirectRevision->insertOn( $dbw );
$redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
- wfRunHooks( 'NewRevisionFromEditComplete', array($redirectArticle, $redirectRevision, false) );
+ wfRunHooks( 'NewRevisionFromEditComplete', array($redirectArticle, $redirectRevision, false, $wgUser) );
# Now, we record the link from the redirect to the new title.
# It should have no other outgoing links...
}
# Move an image if this is a file
- if( $this->getNamespace() == NS_IMAGE ) {
+ if( $this->getNamespace() == NS_FILE ) {
$file = wfLocalFile( $this );
if( $file->exists() ) {
$status = $file->move( $nt );
$nullRevId = $nullRevision->insertOn( $dbw );
$article = new Article( $this );
- wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest) );
+ wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $wgUser) );
# Rename page entry
$dbw->update( 'page',
$redirectRevision->insertOn( $dbw );
$redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
- wfRunHooks( 'NewRevisionFromEditComplete', array($redirectArticle, $redirectRevision, false) );
+ wfRunHooks( 'NewRevisionFromEditComplete', array($redirectArticle, $redirectRevision, false, $wgUser) );
# Record the just-created redirect's linking to the page
$dbw->insert( 'pagelinks',
}
# Move an image if this is a file
- if( $this->getNamespace() == NS_IMAGE ) {
+ if( $this->getNamespace() == NS_FILE ) {
$file = wfLocalFile( $this );
if( $file->exists() ) {
$status = $file->move( $nt );
$this->purgeSquid();
}
+
+ /**
+ * Checks if this page is just a one-rev redirect.
+ * Adds lock, so don't use just for light purposes.
+ *
+ * @return \type{\bool} TRUE or FALSE
+ */
+ public function isSingleRevRedirect() {
+ $dbw = wfGetDB( DB_MASTER );
+ # Is it a redirect?
+ $row = $dbw->selectRow( 'page',
+ array( 'page_is_redirect', 'page_latest', 'page_id' ),
+ $this->pageCond(),
+ __METHOD__,
+ 'FOR UPDATE'
+ );
+ # Cache some fields we may want
+ $this->mArticleID = $row ? intval($row->page_id) : 0;
+ $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
+ $this->mLatestID = $row ? intval($row->page_latest) : false;
+ if( !$this->mRedirect ) {
+ return false;
+ }
+ # Does the article have a history?
+ $row = $dbw->selectField( array( 'page', 'revision'),
+ 'rev_id',
+ array( 'page_namespace' => $this->getNamespace(),
+ 'page_title' => $this->getDBkey(),
+ 'page_id=rev_page',
+ 'page_latest != rev_id'
+ ),
+ __METHOD__,
+ 'FOR UPDATE'
+ );
+ # Return true if there was no history
+ return ($row === false);
+ }
/**
* Checks if $this can be moved to a given Title
* @return \type{\bool} TRUE or FALSE
*/
public function isValidMoveTarget( $nt ) {
-
- $fname = 'Title::isValidMoveTarget';
$dbw = wfGetDB( DB_MASTER );
-
# Is it an existsing file?
- if( $nt->getNamespace() == NS_IMAGE ) {
+ if( $nt->getNamespace() == NS_FILE ) {
$file = wfLocalFile( $nt );
if( $file->exists() ) {
wfDebug( __METHOD__ . ": file exists\n" );
return false;
}
}
-
- # Is it a redirect?
- $id = $nt->getArticleID();
- $obj = $dbw->selectRow( array( 'page', 'revision', 'text'),
- array( 'page_is_redirect','old_text','old_flags' ),
- array( 'page_id' => $id, 'page_latest=rev_id', 'rev_text_id=old_id' ),
- $fname, 'FOR UPDATE' );
-
- if ( !$obj || 0 == $obj->page_is_redirect ) {
- # Not a redirect
- wfDebug( __METHOD__ . ": not a redirect\n" );
+ # Is it a redirect with no history?
+ if( !$nt->isSingleRevRedirect() ) {
+ wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
return false;
}
- $text = Revision::getRevisionText( $obj );
-
+ # Get the article text
+ $rev = Revision::newFromTitle( $nt );
+ $text = $rev->getText();
# Does the redirect point to the source?
# Or is it a broken self-redirect, usually caused by namespace collisions?
$m = array();
wfDebug( __METHOD__ . ": failsafe\n" );
return false;
}
-
- # Does the article have a history?
- $row = $dbw->selectRow( array( 'page', 'revision'),
- array( 'rev_id' ),
- array( 'page_namespace' => $nt->getNamespace(),
- 'page_title' => $nt->getDBkey(),
- 'page_id=rev_page AND page_latest != rev_id'
- ), $fname, 'FOR UPDATE'
- );
-
- # Return true if there was no history
- return $row === false;
+ return true;
}
/**
* @return \type{\bool} TRUE or FALSE
*/
public function isWatchable() {
- return !$this->isExternal()
- && MWNamespace::isWatchable( $this->getNamespace() );
+ return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
}
/**
* @return \type{\array} Selection array
*/
public function pageCond() {
- return array( 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform );
+ if( $this->mArticleID > 0 ) {
+ // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
+ return array( 'page_id' => $this->mArticleID );
+ } else {
+ return array( 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform );
+ }
}
/**
array( 'ORDER BY' => 'rev_id' )
);
}
+
+ /**
+ * Check if this is a new page
+ *
+ * @return bool
+ */
+ public function isNewPage() {
+ $dbr = wfGetDB( DB_SLAVE );
+ return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
+ }
+
+ /**
+ * Get the oldest revision timestamp of this page
+ *
+ * @return string, MW timestamp
+ */
+ public function getEarliestRevTime() {
+ $dbr = wfGetDB( DB_SLAVE );
+ if( $this->exists() ) {
+ $min = $dbr->selectField( 'revision',
+ 'MIN(rev_timestamp)',
+ array( 'rev_page' => $this->getArticleId() ),
+ __METHOD__ );
+ return wfTimestampOrNull( TS_MW, $min );
+ }
+ return null;
+ }
/**
* Get the number of revisions between the given revision IDs.
}
/**
- * Check if page exists
+ * Check if page exists. For historical reasons, this function simply
+ * checks for the existence of the title in the page table, and will
+ * thus return false for interwiki links, special pages and the like.
+ * If you want to know if a title can be meaningfully viewed, you should
+ * probably call the isKnown() method instead.
+ *
* @return \type{\bool} TRUE or FALSE
*/
public function exists() {
}
/**
- * Do we know that this title definitely exists, or should we otherwise
- * consider that it exists?
+ * Should links to this title be shown as potentially viewable (i.e. as
+ * "bluelinks"), even if there's no record by this title in the page
+ * table?
+ *
+ * This function is semi-deprecated for public use, as well as somewhat
+ * misleadingly named. You probably just want to call isKnown(), which
+ * calls this function internally.
+ *
+ * (ISSUE: Most of these checks are cheap, but the file existence check
+ * can potentially be quite expensive. Including it here fixes a lot of
+ * existing code, but we might want to add an optional parameter to skip
+ * it and any other expensive checks.)
*
* @return \type{\bool} TRUE or FALSE
*/
public function isAlwaysKnown() {
- // If the page is form Mediawiki:message/lang, calling wfMsgWeirdKey causes
- // the full l10n of that language to be loaded. That takes much memory and
- // isn't needed. So we strip the language part away.
- // Also, extension messages which are not loaded, are shown as red, because
- // we don't call MessageCache::loadAllMessages.
- list( $basename, /* rest */ ) = explode( '/', $this->mDbkeyform, 2 );
- return $this->isExternal()
- || ( $this->mNamespace == NS_MAIN && $this->mDbkeyform == '' )
- || ( $this->mNamespace == NS_MEDIAWIKI && wfMsgWeirdKey( $basename ) );
+ if( $this->mInterwiki != '' ) {
+ return true; // any interwiki link might be viewable, for all we know
+ }
+ switch( $this->mNamespace ) {
+ case NS_MEDIA:
+ case NS_FILE:
+ return wfFindFile( $this ); // file exists, possibly in a foreign repo
+ case NS_SPECIAL:
+ return SpecialPage::exists( $this->getDBKey() ); // valid special page
+ case NS_MAIN:
+ return $this->mDbkeyform == ''; // selflink, possibly with fragment
+ case NS_MEDIAWIKI:
+ // If the page is form Mediawiki:message/lang, calling wfMsgWeirdKey causes
+ // the full l10n of that language to be loaded. That takes much memory and
+ // isn't needed. So we strip the language part away.
+ // Also, extension messages which are not loaded, are shown as red, because
+ // we don't call MessageCache::loadAllMessages.
+ list( $basename, /* rest */ ) = explode( '/', $this->mDbkeyform, 2 );
+ return wfMsgWeirdKey( $basename ); // known system message
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Does this title refer to a page that can (or might) be meaningfully
+ * viewed? In particular, this function may be used to determine if
+ * links to the title should be rendered as "bluelinks" (as opposed to
+ * "redlinks" to non-existent pages).
+ *
+ * @return \type{\bool} TRUE or FALSE
+ */
+ public function isKnown() {
+ return $this->exists() || $this->isAlwaysKnown();
}
/**
*/
public function getTouched( $db = NULL ) {
$db = isset($db) ? $db : wfGetDB( DB_SLAVE );
- $touched = $db->selectField( 'page', 'page_touched',
- array(
- 'page_namespace' => $this->getNamespace(),
- 'page_title' => $this->getDBkey()
- ), __METHOD__
- );
+ $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
return $touched;
}
+ /**
+ * Get the timestamp when this page was updated since the user last saw it.
+ * @param User $user
+ * @return mixed string/NULL
+ */
+ public function getNotificationTimestamp( $user = NULL ) {
+ global $wgUser, $wgShowUpdatedMarker;
+ // Assume current user if none given
+ if( !$user ) $user = $wgUser;
+ // Check cache first
+ $uid = $user->getId();
+ if( isset($this->mNotificationTimestamp[$uid]) ) {
+ return $this->mNotificationTimestamp[$uid];
+ }
+ if( !$uid || !$wgShowUpdatedMarker ) {
+ return $this->mNotificationTimestamp[$uid] = false;
+ }
+ // 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_namespace' => $this->getNamespace(),
+ 'wl_title' => $this->getDBkey(),
+ 'wl_user' => $user->getId()
+ ),
+ __METHOD__
+ );
+ return $this->mNotificationTimestamp[$uid];
+ }
+
/**
* Get the trackback URL for this page
* @return \type{\string} Trackback URL
case NS_PROJECT:
case NS_PROJECT_TALK:
return 'nstab-project';
- case NS_IMAGE:
- case NS_IMAGE_TALK:
+ case NS_FILE:
+ case NS_FILE_TALK:
return 'nstab-image';
case NS_MEDIAWIKI:
case NS_MEDIAWIKI_TALK: