define ( 'GAID_FOR_UPDATE', 1 );
-/**
- * Title::newFromText maintains a cache to avoid expensive re-normalization of
- * commonly used titles. On a batch operation this can become a memory leak
- * if not bounded. After hitting this many titles reset the cache.
- */
-define( 'MW_TITLECACHE_MAX', 1000 );
/**
* Constants for pr_cascade bitfield
static private $interwikiCache=array();
//@}
+ /**
+ * Title::newFromText maintains a cache to avoid expensive re-normalization of
+ * commonly used titles. On a batch operation this can become a memory leak
+ * if not bounded. After hitting this many titles reset the cache.
+ */
+ const CACHE_MAX = 1000;
+
+
/**
* @name Private member variables
* Please use the accessor functions instead.
var $mRestrictions = array(); ///< Array of groups allowed to edit this article
var $mOldRestrictions = false;
var $mCascadeRestriction; ///< Cascade restrictions on this page to included templates and images?
- var $mRestrictionsExpiry; ///< When do the restrictions on this page expire?
+ var $mRestrictionsExpiry = array(); ///< When do the restrictions on this page expire?
var $mHasCascadingRestrictions; ///< Are cascading restrictions in effect on this page?
- var $mCascadeRestrictionSources; ///< Where are the cascading restrictions coming from on this page?
+ var $mCascadeSources; ///< Where are the cascading restrictions coming from on this page?
var $mRestrictionsLoaded = false; ///< Boolean for initialisation on demand
var $mPrefixedText; ///< Text form including namespace/interwiki, initialised on demand
# Don't change the following default, NS_MAIN is hardcoded in several
static $cachedcount = 0 ;
if( $t->secureAndSplit() ) {
if( $defaultNamespace == NS_MAIN ) {
- if( $cachedcount >= MW_TITLECACHE_MAX ) {
+ if( $cachedcount >= self::CACHE_MAX ) {
# Avoid memory leaks on mass operations...
Title::$titleCache = array();
$cachedcount=0;
/**
* Make an array of titles from an array of IDs
- * @param $ids \arrayof{\int} Array of IDs
- * @return \arrayof{Title} Array of Titles
+ * @param $ids \type{\arrayof{\int}} Array of IDs
+ * @return \type{\arrayof{Title}} Array of Titles
*/
public static function newFromIDs( $ids ) {
if ( !count( $ids ) ) {
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 );
*/
public function getLocalURL( $query = '', $variant = false ) {
global $wgArticlePath, $wgScript, $wgServer, $wgRequest;
- global $wgVariantArticlePath, $wgContLang, $wgUser;
+ global $wgVariantArticlePath, $wgContLang, $wgUser, $wgArticlePathForCurid;
if( is_array( $query ) ) {
$query = wfArrayToCGI( $query );
}
} else {
$dbkey = wfUrlencode( $this->getPrefixedDBkey() );
- if ( $query == '' ) {
+ if ( $query == '' || ($wgArticlePathForCurid && substr_count( $query, '&' ) == 0 && strpos( $query, 'curid=' ) === 0 ) ) {
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;
* there's a fragment but the prefixed text is empty, we just return a link
* to the fragment.
*
- * @param $query \arrayof{\string} An associative array of key => value pairs for the
+ * @param $query \type{\arrayof{\string}} An associative array of key => value pairs for the
* query string. Keys and values will be escaped.
* @param $variant \type{\string} Language variant of URL (for sr, zh..). Ignored
* for external links. Default is "false" (same variant as current page,
/**
* Can $wgUser perform $action on this page?
- * @param \type{\string} $action action that permission needs to be checked for
+ * @param $action \type{\string} action that permission needs to be checked for
* @param $doExpensiveQueries \type{\bool} Set this to false to avoid doing unnecessary queries.
* @return \type{\bool}
*/
* @param $action \type{\string}action that permission needs to be checked for
* @param $user \type{User} user to check
* @param $doExpensiveQueries \type{\bool} Set this to false to avoid doing unnecessary queries.
- * @param $ignoreErrors \arrayof{\string} Set this to a list of message keys whose corresponding errors may be ignored.
+ * @param $ignoreErrors \type{\arrayof{\string}} Set this to a list of message keys whose corresponding errors may be ignored.
* @return \type{\array} Array of arrays of the arguments to wfMsg to explain permissions problems.
*/
public function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true, $ignoreErrors = array() ) {
}
$errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries );
- global $wgContLang;
- global $wgLang;
- global $wgEmailConfirmToEdit;
+ global $wgContLang, $wgLang, $wgEmailConfirmToEdit;
if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() && $action != 'createaccount' ) {
$errors[] = array( 'confirmedittext' );
}
- if ( $user->isBlockedFrom( $this ) && $action != 'createaccount' ) {
+ // Edit blocks should not affect reading. Account creation blocks handled at userlogin.
+ if ( $user->isBlockedFrom( $this ) && $action != 'read' && $action != 'createaccount' ) {
$block = $user->mBlock;
// This is from OutputPage::blockedPage
$blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $user->mBlock->mTimestamp ), true );
if ( $blockExpiry == 'infinity' ) {
- // 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;
- }
- }
+ $blockExpiry = wfMsg( 'ipbinfinite' );
} 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();
$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;
}
global $wgUser,$wgContLang;
if ($create_perm == implode(',',$this->getRestrictions('create'))
- && $expiry == $this->mRestrictionsExpiry) {
+ && $expiry == $this->mRestrictionsExpiry['create']) {
// No change
return true;
}
if ( $encodedExpiry != 'infinity' ) {
$expiry_description = ' (' . wfMsgForContent( 'protect-expiring', $wgContLang->timeanddate( $expiry ) ).')';
}
-
+ else {
+ $expiry_description .= ' (' . wfMsgForContent( 'protect-expiry-indefinite' ).')';
+ }
+
# Update protection table
if ($create_perm != '' ) {
$dbw->replace( 'protected_titles', array(array('pt_namespace', 'pt_title')),
$log = new LogPage( 'protect' );
if( $create_perm ) {
- $log->addEntry( $this->mRestrictions['create'] ? 'modify' : 'protect', $this, trim( $reason . " [create=$create_perm] $expiry_description" ) );
+ $params = array("[create=$create_perm] $expiry_description",'');
+ $log->addEntry( $this->mRestrictions['create'] ? 'modify' : 'protect', $this, trim( $reason ), $params );
} else {
$log->addEntry( 'unprotect', $this, $reason );
}
* Cascading protection: Get the source of any cascading restrictions on this page.
*
* @param $get_pages \type{\bool} Whether or not to retrieve the actual pages that the restrictions have come from.
- * @return \arrayof{mixed title array, restriction array} Array of the Title objects of the pages from
+ * @return \type{\arrayof{mixed title array, restriction array}} Array of the Title objects of the pages from
* which cascading restrictions have come, false for none, or true if such restrictions exist, but $get_pages was not set.
* The restriction array is an array of each type, each of which contains an array of unique groups.
*/
} else {
$this->mHasCascadingRestrictions = $sources;
}
-
return array( $sources, $pagerestrictions );
}
foreach( $wgRestrictionTypes as $type ){
$this->mRestrictions[$type] = array();
+ $this->mRestrictionsExpiry[$type] = Block::decodeExpiry('');
}
$this->mCascadeRestriction = false;
- $this->mRestrictionsExpiry = Block::decodeExpiry('');
# Backwards-compatibility: also load the restrictions from the page record (old format).
// Only apply the restrictions if they haven't expired!
if ( !$expiry || $expiry > $now ) {
- $this->mRestrictionsExpiry = $expiry;
+ $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
$this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
$this->mCascadeRestriction |= $row->pr_cascade;
if (!$expiry || $expiry > $now) {
// Apply the restrictions
- $this->mRestrictionsExpiry = $expiry;
+ $this->mRestrictionsExpiry['create'] = $expiry;
$this->mRestrictions['create'] = explode(',', trim($pt_create_perm) );
} else { // Get rid of the old restrictions
Title::purgeExpiredRestrictions();
}
} else {
- $this->mRestrictionsExpiry = Block::decodeExpiry('');
+ $this->mRestrictionsExpiry['create'] = Block::decodeExpiry('');
}
$this->mRestrictionsLoaded = true;
}
* Accessor/initialisation for mRestrictions
*
* @param $action \type{\string} action that permission needs to be checked for
- * @return \arrayof{\string} the array of groups allowed to edit this article
+ * @return \type{\arrayof{\string}} the array of groups allowed to edit this article
*/
public function getRestrictions( $action ) {
if( !$this->mRestrictionsLoaded ) {
: array();
}
+ /**
+ * Get the expiry time for the restriction against a given action
+ * @return 14-char timestamp, or 'infinity' if the page is protected forever
+ * or not protected at all, or false if the action is not recognised.
+ */
+ public function getRestrictionExpiry( $action ) {
+ if( !$this->mRestrictionsLoaded ) {
+ $this->loadRestrictions();
+ }
+ return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
+ }
+
/**
* Is there a version of this page in the deletion archive?
* @return \type{\int} the number of archived revisions
*/
public function getArticleID( $flags = 0 ) {
$linkCache = LinkCache::singleton();
- if ( $flags & GAID_FOR_UPDATE ) {
+ if( $flags & GAID_FOR_UPDATE ) {
$oldUpdate = $linkCache->forUpdate( true );
+ $linkCache->clearLink( $this );
$this->mArticleID = $linkCache->addLinkObj( $this );
$linkCache->forUpdate( $oldUpdate );
} else {
- if ( -1 == $this->mArticleID ) {
+ if( -1 == $this->mArticleID ) {
$this->mArticleID = $linkCache->addLinkObj( $this );
}
}
* @return \type{\int}
*/
public function getLatestRevID( $flags = 0 ) {
- if ($this->mLatestID !== false)
+ if( $this->mLatestID !== false )
return $this->mLatestID;
$db = ($flags & GAID_FOR_UPDATE) ? wfGetDB(DB_MASTER) : wfGetDB(DB_SLAVE);
- return $this->mLatestID = $db->selectField( 'revision',
- "max(rev_id)",
- array('rev_page' => $this->getArticleID($flags)),
- 'Title::getLatestRevID' );
+ $this->mLatestID = $db->selectField( 'page', 'page_latest',
+ array( 'page_namespace' => $this->getNamespace(), 'page_title' => $this->getDBKey() ),
+ __METHOD__ );
+ return $this->mLatestID;
}
/**
}
/**
- * Set the fragment for this title
- * This is kind of bad, since except for this rarely-used function, Title objects
- * are immutable. The reason this is here is because it's better than setting the
- * members directly, which is what Linker::formatComment was doing previously.
+ * Set the fragment for this title. Removes the first character from the
+ * specified fragment before setting, so it assumes you're passing it with
+ * an initial "#".
+ *
+ * Deprecated for public use, use Title::makeTitle() with fragment parameter.
+ * Still in active use privately.
*
* @param $fragment \type{\string} text
- * @todo clarify whether access is supposed to be public (was marked as "kind of public")
*/
public function setFragment( $fragment ) {
$this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
* On heavily-used templates it will max out the memory.
*
* @param $options \type{\string} may be FOR UPDATE
- * @return \arrayof{Title} the Title objects linking here
+ * @return \type{\arrayof{Title}} the Title objects linking here
*/
public function getLinksTo( $options = '', $table = 'pagelinks', $prefix = 'pl' ) {
$linkCache = LinkCache::singleton();
* On heavily-used templates it will max out the memory.
*
* @param $options \type{\string} may be FOR UPDATE
- * @return \arrayof{Title} the Title objects linking here
+ * @return \type{\arrayof{Title}} the Title objects linking here
*/
public function getTemplateLinksTo( $options = '' ) {
return $this->getLinksTo( $options, 'templatelinks', 'tl' );
*
* @todo check if needed (used only in SpecialBrokenRedirects.php, and should use redirect table in this case)
* @param $options \type{\string} may be FOR UPDATE
- * @return \arrayof{Title} the Title objects
+ * @return \type{\arrayof{Title}} the Title objects
*/
public function getBrokenLinksFrom( $options = '' ) {
if ( $this->getArticleId() == 0 ) {
* Get a list of URLs to purge from the Squid cache when this
* page changes
*
- * @return \arrayof{\string} the URLs
+ * @return \type{\arrayof{\string}} the URLs
*/
public function getSquidURLs() {
global $wgContLang;
if ( $auth ) {
global $wgUser;
- $errors = array_merge($errors,
+ $errors = wfArrayMerge($errors,
$this->getUserPermissionsErrors('move', $wgUser),
$this->getUserPermissionsErrors('edit', $wgUser),
$nt->getUserPermissionsErrors('move', $wgUser),
$nt->getUserPermissionsErrors('edit', $wgUser));
}
+ $match = EditPage::matchSpamRegex( $reason );
+ if( $match !== false ) {
+ // This is kind of lame, won't display nice
+ $errors[] = array('spamprotectiontext');
+ }
+
global $wgUser;
$err = null;
if( !wfRunHooks( 'AbortMove', array( $this, $nt, $wgUser, &$err, $reason ) ) ) {
}
$pageid = $this->getArticleID();
+ $protected = $this->isProtected();
if( $nt->exists() ) {
$err = $this->moveOverExistingRedirect( $nt, $reason, $createRedirect );
$pageCountChange = ($createRedirect ? 0 : -1);
'cl_sortkey' => $this->getPrefixedText() ),
__METHOD__ );
- # Update watchlists
+ 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 = wfMsgForContent('1movedto2',$this->getPrefixedText(), $nt->getPrefixedText() );
+ $log->addEntry( 'protect', $nt, $comment, array() ); // FIXME: $params?
+ }
+ # Update watchlists
$oldnamespace = $this->getNamespace() & ~1;
$newnamespace = $nt->getNamespace() & ~1;
$oldtitle = $this->getDBkey();
$latest = $this->getLatestRevID();
$dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
# Delete the old redirect. We don't save it to history since
# by definition if we've got here it's rather uninteresting.
}
}
}
- $dbw->commit();
# Log the move
$log = new LogPage( 'move' );
$fname = 'MovePageForm::moveToNewTitle';
$comment = wfMsgForContent( '1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() );
if ( $reason ) {
- $comment .= ": $reason";
+ $comment .= wfMsgExt( 'colon-separator',
+ array( 'escapenoentities', 'content' ) );
+ $comment .= $reason;
}
$newid = $nt->getArticleID();
$latest = $this->getLatestRevId();
$dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
$now = $dbw->timestamp();
# Save a null revision in the page's history notifying of the move
}
}
}
- $dbw->commit();
# Log the move
$log = new LogPage( 'move' );
/**
* Get the last touched timestamp
+ * @param Database $db, optional db
* @return \type{\string} Last touched timestamp
*/
- public function getTouched() {
- $dbr = wfGetDB( DB_SLAVE );
- $touched = $dbr->selectField( 'page', 'page_touched',
+ 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()
* @return \type{\string} Trackback URL
*/
public function trackbackURL() {
- global $wgTitle, $wgScriptPath, $wgServer;
+ global $wgScriptPath, $wgServer;
return "$wgServer$wgScriptPath/trackback.php?article="
- . htmlspecialchars(urlencode($wgTitle->getPrefixedDBkey()));
+ . htmlspecialchars(urlencode($this->getPrefixedDBkey()));
}
/**
*
* @param $ns \twotypes{\int,\null} Single namespace to consider;
* NULL to consider all namespaces
- * @return \arrayof{Title} Redirects to this title
+ * @return \type{\arrayof{Title}} Redirects to this title
*/
public function getRedirectsHere( $ns = null ) {
$redirs = array();