summaryrefslogtreecommitdiff
path: root/includes/Title.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/Title.php')
-rw-r--r--includes/Title.php528
1 files changed, 301 insertions, 227 deletions
diff --git a/includes/Title.php b/includes/Title.php
index ca66aaec..21872e45 100644
--- a/includes/Title.php
+++ b/includes/Title.php
@@ -225,7 +225,7 @@ class Title {
*
* @param int $id the page_id corresponding to the Title to create
* @param int $flags use Title::GAID_FOR_UPDATE to use master
- * @return Title the new object, or NULL on an error
+ * @return Title|null the new object, or NULL on an error
*/
public static function newFromID( $id, $flags = 0 ) {
$db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
@@ -289,18 +289,23 @@ class Title {
*/
public function loadFromRow( $row ) {
if ( $row ) { // page found
- if ( isset( $row->page_id ) )
+ if ( isset( $row->page_id ) ) {
$this->mArticleID = (int)$row->page_id;
- if ( isset( $row->page_len ) )
+ }
+ if ( isset( $row->page_len ) ) {
$this->mLength = (int)$row->page_len;
- if ( isset( $row->page_is_redirect ) )
+ }
+ if ( isset( $row->page_is_redirect ) ) {
$this->mRedirect = (bool)$row->page_is_redirect;
- if ( isset( $row->page_latest ) )
+ }
+ if ( isset( $row->page_latest ) ) {
$this->mLatestID = (int)$row->page_latest;
- if ( isset( $row->page_content_model ) )
+ }
+ if ( isset( $row->page_content_model ) ) {
$this->mContentModel = strval( $row->page_content_model );
- else
+ } else {
$this->mContentModel = false; # initialized lazily in getContentModel()
+ }
} else { // page not found
$this->mArticleID = 0;
$this->mLength = 0;
@@ -487,6 +492,108 @@ class Title {
}
/**
+ * Utility method for converting a character sequence from bytes to Unicode.
+ *
+ * Primary usecase being converting $wgLegalTitleChars to a sequence usable in
+ * javascript, as PHP uses UTF-8 bytes where javascript uses Unicode code units.
+ *
+ * @param string $byteClass
+ * @return string
+ */
+ public static function convertByteClassToUnicodeClass( $byteClass ) {
+ $length = strlen( $byteClass );
+ // Input token queue
+ $x0 = $x1 = $x2 = '';
+ // Decoded queue
+ $d0 = $d1 = $d2 = '';
+ // Decoded integer codepoints
+ $ord0 = $ord1 = $ord2 = 0;
+ // Re-encoded queue
+ $r0 = $r1 = $r2 = '';
+ // Output
+ $out = '';
+ // Flags
+ $allowUnicode = false;
+ for ( $pos = 0; $pos < $length; $pos++ ) {
+ // Shift the queues down
+ $x2 = $x1;
+ $x1 = $x0;
+ $d2 = $d1;
+ $d1 = $d0;
+ $ord2 = $ord1;
+ $ord1 = $ord0;
+ $r2 = $r1;
+ $r1 = $r0;
+ // Load the current input token and decoded values
+ $inChar = $byteClass[$pos];
+ if ( $inChar == '\\' ) {
+ if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) {
+ $x0 = $inChar . $m[0];
+ $d0 = chr( hexdec( $m[1] ) );
+ $pos += strlen( $m[0] );
+ } elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) {
+ $x0 = $inChar . $m[0];
+ $d0 = chr( octdec( $m[0] ) );
+ $pos += strlen( $m[0] );
+ } elseif ( $pos + 1 >= $length ) {
+ $x0 = $d0 = '\\';
+ } else {
+ $d0 = $byteClass[$pos + 1];
+ $x0 = $inChar . $d0;
+ $pos += 1;
+ }
+ } else {
+ $x0 = $d0 = $inChar;
+ }
+ $ord0 = ord( $d0 );
+ // Load the current re-encoded value
+ if ( $ord0 < 32 || $ord0 == 0x7f ) {
+ $r0 = sprintf( '\x%02x', $ord0 );
+ } elseif ( $ord0 >= 0x80 ) {
+ // Allow unicode if a single high-bit character appears
+ $r0 = sprintf( '\x%02x', $ord0 );
+ $allowUnicode = true;
+ } elseif ( strpos( '-\\[]^', $d0 ) !== false ) {
+ $r0 = '\\' . $d0;
+ } else {
+ $r0 = $d0;
+ }
+ // Do the output
+ if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) {
+ // Range
+ if ( $ord2 > $ord0 ) {
+ // Empty range
+ } elseif ( $ord0 >= 0x80 ) {
+ // Unicode range
+ $allowUnicode = true;
+ if ( $ord2 < 0x80 ) {
+ // Keep the non-unicode section of the range
+ $out .= "$r2-\\x7F";
+ }
+ } else {
+ // Normal range
+ $out .= "$r2-$r0";
+ }
+ // Reset state to the initial value
+ $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = '';
+ } elseif ( $ord2 < 0x80 ) {
+ // ASCII character
+ $out .= $r2;
+ }
+ }
+ if ( $ord1 < 0x80 ) {
+ $out .= $r1;
+ }
+ if ( $ord0 < 0x80 ) {
+ $out .= $r0;
+ }
+ if ( $allowUnicode ) {
+ $out .= '\u0080-\uFFFF';
+ }
+ return $out;
+ }
+
+ /**
* Get a string representation of a title suitable for
* including in a search index
*
@@ -689,7 +796,7 @@ class Title {
$this->mContentModel = ContentHandler::getDefaultModelFor( $this );
}
- if( !$this->mContentModel ) {
+ if ( !$this->mContentModel ) {
throw new MWException( 'Failed to determine content model!' );
}
@@ -752,7 +859,7 @@ class Title {
*/
public function getTalkNsText() {
global $wgContLang;
- return( $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) ) );
+ return $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) );
}
/**
@@ -761,7 +868,7 @@ class Title {
* @return Bool TRUE or FALSE
*/
public function canTalk() {
- return( MWNamespace::canTalk( $this->mNamespace ) );
+ return MWNamespace::canTalk( $this->mNamespace );
}
/**
@@ -939,7 +1046,7 @@ class Title {
* @return Bool
*/
public function isConversionTable() {
- //@todo: ConversionTable should become a separate content model.
+ // @todo ConversionTable should become a separate content model.
return $this->getNamespace() == NS_MEDIAWIKI &&
strpos( $this->getText(), 'Conversiontable/' ) === 0;
@@ -995,10 +1102,11 @@ class Title {
*/
public function getSkinFromCssJsSubpage() {
$subpage = explode( '/', $this->mTextform );
- $subpage = $subpage[ count( $subpage ) - 1 ];
+ $subpage = $subpage[count( $subpage ) - 1];
$lastdot = strrpos( $subpage, '.' );
- if ( $lastdot === false )
+ if ( $lastdot === false ) {
return $subpage; # Never happens: only called for names ending in '.css' or '.js'
+ }
return substr( $subpage, 0, $lastdot );
}
@@ -1269,10 +1377,10 @@ class Title {
*/
public function getSubpageText() {
if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
- return( $this->mTextform );
+ return $this->mTextform;
}
$parts = explode( '/', $this->mTextform );
- return( $parts[count( $parts ) - 1] );
+ return $parts[count( $parts ) - 1];
}
/**
@@ -1312,7 +1420,7 @@ class Title {
public function getSubpageUrlForm() {
$text = $this->getSubpageText();
$text = wfUrlencode( str_replace( ' ', '_', $text ) );
- return( $text );
+ return $text;
}
/**
@@ -1327,8 +1435,8 @@ class Title {
}
/**
- * Helper to fix up the get{Local,Full,Link,Canonical}URL args
- * get{Canonical,Full,Link,Local}URL methods accepted an optional
+ * Helper to fix up the get{Canonical,Full,Link,Local,Internal}URL args
+ * get{Canonical,Full,Link,Local,Internal}URL methods accepted an optional
* second argument named variant. This was deprecated in favor
* of passing an array of option with a "variant" key
* Once $query2 is removed for good, this helper can be dropped
@@ -1340,8 +1448,10 @@ class Title {
* @return String
*/
private static function fixUrlQueryArgs( $query, $query2 = false ) {
- if( $query2 !== false ) {
- wfDeprecated( "Title::get{Canonical,Full,Link,Local} method called with a second parameter is deprecated. Add your parameter to an array passed as the first parameter.", "1.19" );
+ if ( $query2 !== false ) {
+ wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " .
+ "method called with a second parameter is deprecated. Add your " .
+ "parameter to an array passed as the first parameter.", "1.19" );
}
if ( is_array( $query ) ) {
$query = wfArrayToCgi( $query );
@@ -1399,7 +1509,6 @@ class Title {
* Get a URL with no fragment or server name. If this page is generated
* with action=render, $wgServer is prepended.
*
-
* @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).
@@ -1434,7 +1543,7 @@ class Title {
$url = str_replace( '$1', $dbkey, $wgArticlePath );
wfRunHooks( 'GetLocalURL::Article', array( &$this, &$url ) );
} else {
- global $wgVariantArticlePath, $wgActionPaths;
+ global $wgVariantArticlePath, $wgActionPaths, $wgContLang;
$url = false;
$matches = array();
@@ -1456,6 +1565,7 @@ class Title {
if ( $url === false &&
$wgVariantArticlePath &&
+ $wgContLang->getCode() === $this->getPageLanguage()->getCode() &&
$this->getPageLanguage()->hasVariants() &&
preg_match( '/^variant=([^&]*)$/', $query, $matches ) )
{
@@ -1732,6 +1842,10 @@ class Title {
* @return Array list of errors
*/
private function checkQuickPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ if ( !wfRunHooks( 'TitleQuickPermissions', array( $this, $user, $action, &$errors, $doExpensiveQueries, $short ) ) ) {
+ return $errors;
+ }
+
if ( $action == 'create' ) {
if (
( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
@@ -1880,12 +1994,19 @@ class Title {
# 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' )
- && !preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
- if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
- $errors[] = array( 'customcssprotected' );
- } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
- $errors[] = array( 'customjsprotected' );
+ 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' );
+ } elseif ( $this->isJsSubpage() && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) ) {
+ $errors[] = array( 'mycustomjsprotected' );
+ }
+ } else {
+ if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
+ $errors[] = array( 'customcssprotected' );
+ } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
+ $errors[] = array( 'customjsprotected' );
+ }
}
}
@@ -1907,18 +2028,21 @@ class Title {
*/
private function checkPageRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
foreach ( $this->getRestrictions( $action ) as $right ) {
- // Backwards compatibility, rewrite sysop -> protect
+ // Backwards compatibility, rewrite sysop -> editprotected
if ( $right == 'sysop' ) {
- $right = 'protect';
+ $right = 'editprotected';
}
- if ( $right != '' && !$user->isAllowed( $right ) ) {
- // Users with 'editprotected' permission can edit protected pages
- // without cascading option turned on.
- if ( $action != 'edit' || !$user->isAllowed( 'editprotected' )
- || $this->mCascadeRestriction )
- {
- $errors[] = array( 'protectedpagetext', $right );
- }
+ // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
+ if ( $right == 'autoconfirmed' ) {
+ $right = 'editsemiprotected';
+ }
+ if ( $right == '' ) {
+ continue;
+ }
+ if ( !$user->isAllowed( $right ) ) {
+ $errors[] = array( 'protectedpagetext', $right );
+ } elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
+ $errors[] = array( 'protectedpagetext', 'protect' );
}
}
@@ -1950,11 +2074,19 @@ class Title {
# This is only for protection restrictions, not for all actions
if ( isset( $restrictions[$action] ) ) {
foreach ( $restrictions[$action] as $right ) {
- $right = ( $right == 'sysop' ) ? 'protect' : $right;
- if ( $right != '' && !$user->isAllowed( $right ) ) {
+ // Backwards compatibility, rewrite sysop -> editprotected
+ if ( $right == 'sysop' ) {
+ $right = 'editprotected';
+ }
+ // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
+ if ( $right == 'autoconfirmed' ) {
+ $right = 'editsemiprotected';
+ }
+ if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
$pages = '';
- foreach ( $cascadingSources as $page )
+ foreach ( $cascadingSources as $page ) {
$pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
+ }
$errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages );
}
}
@@ -1985,11 +2117,14 @@ class Title {
}
} elseif ( $action == 'create' ) {
$title_protection = $this->getTitleProtection();
- if( $title_protection ) {
- if( $title_protection['pt_create_perm'] == 'sysop' ) {
- $title_protection['pt_create_perm'] = 'protect'; // B/C
+ 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'] == '' ||
+ if ( $title_protection['pt_create_perm'] == '' ||
!$user->isAllowed( $title_protection['pt_create_perm'] ) )
{
$errors[] = array( 'titleprotected', User::whoIs( $title_protection['pt_user'] ), $title_protection['pt_reason'] );
@@ -2034,11 +2169,11 @@ class Title {
private function checkUserBlock( $action, $user, $errors, $doExpensiveQueries, $short ) {
// Account creation blocks handled at userlogin.
// Unblocking handled in SpecialUnblock
- if( !$doExpensiveQueries || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
+ if ( !$doExpensiveQueries || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
return $errors;
}
- global $wgContLang, $wgLang, $wgEmailConfirmToEdit;
+ global $wgEmailConfirmToEdit;
if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) {
$errors[] = array( 'confirmedittext' );
@@ -2047,39 +2182,9 @@ class Title {
if ( ( $action == 'edit' || $action == 'create' ) && !$user->isBlockedFrom( $this ) ) {
// 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 ) {
- $block = $user->getBlock();
-
- // This is from OutputPage::blockedPage
- // Copied at r23888 by werdna
-
- $id = $user->blockedBy();
- $reason = $user->blockedFor();
- if ( $reason == '' ) {
- $reason = wfMessage( 'blockednoreason' )->text();
- }
- $ip = $user->getRequest()->getIP();
-
- if ( is_numeric( $id ) ) {
- $name = User::whoIs( $id );
- } else {
- $name = $id;
- }
-
- $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
- $blockid = $block->getId();
- $blockExpiry = $block->getExpiry();
- $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $block->mTimestamp ), true );
- if ( $blockExpiry == 'infinity' ) {
- $blockExpiry = wfMessage( 'infiniteblock' )->text();
- } else {
- $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
- }
-
- $intended = strval( $block->getTarget() );
-
- $errors[] = array( ( $block->mAuto ? 'autoblockedtext' : 'blockedtext' ), $link, $reason, $ip, $name,
- $blockid, $blockExpiry, $intended, $blockTimestamp );
+ } elseif ( $user->isBlocked() && $user->mBlock->prevents( $action ) !== false ) {
+ // @todo FIXME: Pass the relevant context into this function.
+ $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
}
return $errors;
@@ -2097,34 +2202,10 @@ class Title {
* @return Array list of errors
*/
private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
- global $wgWhitelistRead, $wgWhitelistReadRegexp, $wgRevokePermissions;
- static $useShortcut = null;
-
- # Initialize the $useShortcut boolean, to determine if we can skip quite a bit of code below
- if ( is_null( $useShortcut ) ) {
- $useShortcut = true;
- if ( !User::groupHasPermission( '*', 'read' ) ) {
- # Not a public wiki, so no shortcut
- $useShortcut = false;
- } elseif ( !empty( $wgRevokePermissions ) ) {
- /**
- * Iterate through each group with permissions being revoked (key not included since we don't care
- * what the group name is), then check if the read permission is being revoked. If it is, then
- * we don't use the shortcut below since the user might not be able to read, even though anon
- * reading is allowed.
- */
- foreach ( $wgRevokePermissions as $perms ) {
- if ( !empty( $perms['read'] ) ) {
- # We might be removing the read right from the user, so no shortcut
- $useShortcut = false;
- break;
- }
- }
- }
- }
+ global $wgWhitelistRead, $wgWhitelistReadRegexp;
$whitelisted = false;
- if ( $useShortcut ) {
+ if ( User::isEveryoneAllowed( 'read' ) ) {
# Shortcut for public wikis, allows skipping quite a bit of code
$whitelisted = true;
} elseif ( $user->isAllowed( 'read' ) ) {
@@ -2165,7 +2246,7 @@ class Title {
}
}
- if( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
+ if ( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
$name = $this->getPrefixedText();
// Check for regex whitelisting
foreach ( $wgWhitelistReadRegexp as $listItem ) {
@@ -2250,7 +2331,7 @@ class Title {
}
$errors = array();
- while( count( $checks ) > 0 &&
+ while ( count( $checks ) > 0 &&
!( $short && count( $errors ) > 0 ) ) {
$method = array_shift( $checks );
$errors = $this->$method( $action, $user, $errors, $doExpensiveQueries, $short );
@@ -2261,36 +2342,6 @@ class Title {
}
/**
- * Protect css subpages of user pages: can $wgUser edit
- * this page?
- *
- * @deprecated in 1.19; will be removed in 1.20. Use getUserPermissionsErrors() instead.
- * @return Bool
- */
- public function userCanEditCssSubpage() {
- global $wgUser;
- wfDeprecated( __METHOD__, '1.19' );
- return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'editusercss' ) )
- || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
- }
-
- /**
- * Protect js subpages of user pages: can $wgUser edit
- * this page?
- *
- * @deprecated in 1.19; will be removed in 1.20. Use getUserPermissionsErrors() instead.
- * @return Bool
- */
- public function userCanEditJsSubpage() {
- global $wgUser;
- wfDeprecated( __METHOD__, '1.19' );
- return (
- ( $wgUser->isAllowedAll( 'editusercssjs', 'edituserjs' ) )
- || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform )
- );
- }
-
- /**
* Get a filtered list of all restriction types supported by this wiki.
* @param bool $exists True to get all restriction types that apply to
* titles that do exist, False for all restriction types that apply to
@@ -2371,7 +2422,7 @@ class Title {
/**
* Update the title protection status
*
- * @deprecated in 1.19; will be removed in 1.20. Use WikiPage::doUpdateRestrictions() instead.
+ * @deprecated in 1.19; use WikiPage::doUpdateRestrictions() instead.
* @param $create_perm String Permission required for creation
* @param string $reason Reason for protection
* @param string $expiry Expiry timestamp
@@ -2407,29 +2458,31 @@ class Title {
}
/**
- * Is this page "semi-protected" - the *only* protection is autoconfirm?
+ * Is this page "semi-protected" - the *only* protection levels are listed
+ * in $wgSemiprotectedRestrictionLevels?
*
* @param string $action Action to check (default: edit)
* @return Bool
*/
public function isSemiProtected( $action = 'edit' ) {
- if ( $this->exists() ) {
- $restrictions = $this->getRestrictions( $action );
- if ( count( $restrictions ) > 0 ) {
- foreach ( $restrictions as $restriction ) {
- if ( strtolower( $restriction ) != 'autoconfirmed' ) {
- return false;
- }
- }
- } else {
- # Not protected
- return false;
- }
- return true;
- } else {
- # If it doesn't exist, it can't be protected
+ global $wgSemiprotectedRestrictionLevels;
+
+ $restrictions = $this->getRestrictions( $action );
+ $semi = $wgSemiprotectedRestrictionLevels;
+ if ( !$restrictions || !$semi ) {
+ // Not protected, or all protection is full protection
return false;
}
+
+ // Remap autoconfirmed to editsemiprotected for BC
+ foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
+ $semi[$key] = 'editsemiprotected';
+ }
+ foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
+ $restrictions[$key] = 'editsemiprotected';
+ }
+
+ return !array_diff( $restrictions, $semi );
}
/**
@@ -2445,7 +2498,7 @@ class Title {
$restrictionTypes = $this->getRestrictionTypes();
# Special pages have inherent protection
- if( $this->isSpecialPage() ) {
+ if ( $this->isSpecialPage() ) {
return true;
}
@@ -2692,7 +2745,7 @@ class Title {
$this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
} else {
$restriction = trim( $temp[1] );
- if( $restriction != '' ) { //some old entries are empty
+ if ( $restriction != '' ) { //some old entries are empty
$this->mRestrictions[$temp[0]] = explode( ',', $restriction );
}
}
@@ -2711,8 +2764,9 @@ class Title {
foreach ( $rows as $row ) {
// Don't take care of restrictions types that aren't allowed
- if ( !in_array( $row->pr_type, $restrictionTypes ) )
+ if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
continue;
+ }
// 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
@@ -2798,18 +2852,20 @@ class Title {
return;
}
+ $method = __METHOD__;
$dbw = wfGetDB( DB_MASTER );
- $dbw->delete(
- 'page_restrictions',
- array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
- __METHOD__
- );
-
- $dbw->delete(
- 'protected_titles',
- array( 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
- __METHOD__
- );
+ $dbw->onTransactionIdle( function() use ( $dbw, $method ) {
+ $dbw->delete(
+ 'page_restrictions',
+ array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
+ $method
+ );
+ $dbw->delete(
+ 'protected_titles',
+ array( 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
+ $method
+ );
+ } );
}
/**
@@ -2960,11 +3016,13 @@ class Title {
$linkCache = LinkCache::singleton();
$cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
if ( $cached === null ) {
- // TODO: check the assumption that the cache actually knows about this title
- // and handle this, such as get the title from the database.
- // See https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
- wfDebug( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
- wfDebug( wfBacktrace() );
+ # Trust LinkCache's state over our own
+ # LinkCache is telling us that the page doesn't exist, despite there being cached
+ # data relating to an existing page in $this->mArticleID. Updaters should clear
+ # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
+ # set, then LinkCache will definitely be up to date here, since getArticleID() forces
+ # LinkCache to refresh its data from the master.
+ return $this->mRedirect = false;
}
$this->mRedirect = (bool)$cached;
@@ -2989,11 +3047,9 @@ class Title {
}
$linkCache = LinkCache::singleton();
$cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
- if ( $cached === null ) { # check the assumption that the cache actually knows about this title
- # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
- # as a stop gap, perhaps log this, but don't throw an exception?
- wfDebug( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
- wfDebug( wfBacktrace() );
+ if ( $cached === null ) {
+ # Trust LinkCache's state over our own, as for isRedirect()
+ return $this->mLength = 0;
}
$this->mLength = intval( $cached );
@@ -3005,7 +3061,6 @@ class Title {
* What is the page_latest field for this page?
*
* @param int $flags a bit field; may be Title::GAID_FOR_UPDATE to select for update
- * @throws MWException
* @return Int or 0 if the page doesn't exist
*/
public function getLatestRevID( $flags = 0 ) {
@@ -3019,10 +3074,9 @@ class Title {
$linkCache = LinkCache::singleton();
$linkCache->addLinkObj( $this );
$cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
- if ( $cached === null ) { # check the assumption that the cache actually knows about this title
- # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
- # as a stop gap, perhaps log this, but don't throw an exception?
- throw new MWException( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
+ if ( $cached === null ) {
+ # Trust LinkCache's state over our own, as for isRedirect()
+ return $this->mLatestID = 0;
}
$this->mLatestID = intval( $cached );
@@ -3111,7 +3165,7 @@ class Title {
return false;
}
- if ( false !== strpos( $dbkey, UTF8_REPLACEMENT ) ) {
+ if ( strpos( $dbkey, UTF8_REPLACEMENT ) !== false ) {
# Contained illegal UTF-8 sequences or forbidden Unicode chars.
return false;
}
@@ -3248,6 +3302,7 @@ class Title {
# Can't make a link to a namespace alone... "empty" local links can only be
# self-links with a fragment identifier.
+ # TODO: Why do we exclude NS_MAIN (bug 54044)
if ( $dbkey == '' && $this->mInterwiki == '' && $this->mNamespace != NS_MAIN ) {
return false;
}
@@ -3301,7 +3356,7 @@ class Title {
array(
"{$prefix}_from=page_id",
"{$prefix}_namespace" => $this->getNamespace(),
- "{$prefix}_title" => $this->getDBkey() ),
+ "{$prefix}_title" => $this->getDBkey() ),
__METHOD__,
$options
);
@@ -3366,7 +3421,9 @@ class Title {
$titleField = "{$prefix}_title";
$fields = array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' );
- if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'page_content_model';
+ }
$res = $db->select(
array( $table, 'page' ),
@@ -3465,6 +3522,7 @@ class Title {
}
}
+ wfRunHooks( 'TitleSquidURLs', array( $this, &$urls ) );
return $urls;
}
@@ -3585,7 +3643,13 @@ class Title {
}
} else {
$tp = $nt->getTitleProtection();
- $right = ( $tp['pt_create_perm'] == 'sysop' ) ? 'protect' : $tp['pt_create_perm'];
+ $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' );
}
@@ -3659,6 +3723,8 @@ class Title {
$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 );
@@ -3711,12 +3777,12 @@ class Title {
# 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_page' => $redirid,
+ 'pr_type' => 'pr_type',
+ 'pr_level' => 'pr_level',
'pr_cascade' => 'pr_cascade',
- 'pr_user' => 'pr_user',
- 'pr_expiry' => 'pr_expiry'
+ 'pr_user' => 'pr_user',
+ 'pr_expiry' => 'pr_expiry'
),
array( 'pr_page' => $pageid ),
__METHOD__,
@@ -3733,7 +3799,7 @@ class Title {
$comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
}
// @todo FIXME: $params?
- $log->addEntry( 'move_prot', $nt, $comment, array( $this->getPrefixedText() ) );
+ $log->addEntry( 'move_prot', $nt, $comment, array( $this->getPrefixedText() ), $wgUser );
}
# Update watchlists
@@ -3775,7 +3841,8 @@ class Title {
if ( $createRedirect ) {
$contentHandler = ContentHandler::getForTitle( $this );
- $redirectContent = $contentHandler->makeRedirectContent( $nt );
+ $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.
} else {
@@ -3830,14 +3897,20 @@ class Title {
$dbw->update( 'page',
/* SET */ array(
'page_namespace' => $nt->getNamespace(),
- 'page_title' => $nt->getDBkey(),
+ 'page_title' => $nt->getDBkey(),
),
/* WHERE */ array( 'page_id' => $oldid ),
__METHOD__
);
- $this->resetArticleID( 0 );
+ // 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 );
@@ -3851,17 +3924,17 @@ class Title {
}
# Recreate the redirect, this time in the other direction.
- if ( !$redirectContent ) {
- WikiPage::onArticleDelete( $this );
- } else {
+ 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,
+ 'title' => $this, // for determining the default content model
+ 'page' => $newid,
'comment' => $comment,
- 'content' => $redirectContent ) );
+ 'content' => $redirectContent ) );
$redirectRevision->insertOn( $dbw );
$redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
@@ -3963,7 +4036,9 @@ class Title {
# Is it a redirect?
$fields = array( 'page_is_redirect', 'page_latest', 'page_id' );
- if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'page_content_model';
+ }
$row = $dbw->selectRow( 'page',
$fields,
@@ -4017,7 +4092,7 @@ class Title {
}
# Get the article text
$rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
- if( !is_object( $rev ) ) {
+ if ( !is_object( $rev ) ) {
return false;
}
$content = $rev->getContent();
@@ -4070,8 +4145,8 @@ class Title {
if ( $res->numRows() > 0 ) {
foreach ( $res as $row ) {
- // $data[] = Title::newFromText($wgContLang->getNSText ( NS_CATEGORY ).':'.$row->cl_to);
- $data[$wgContLang->getNSText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
+ // $data[] = Title::newFromText($wgContLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
+ $data[$wgContLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
}
}
return $data;
@@ -4319,7 +4394,7 @@ class Title {
// No DB query needed if $old and $new are the same or successive revisions:
if ( $old->getId() === $new->getId() ) {
return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1;
- } else if ( $old->getId() === $new->getParentId() ) {
+ } elseif ( $old->getId() === $new->getParentId() ) {
if ( $old_cmp === '>' || $new_cmp === '<' ) {
return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1;
}
@@ -4414,7 +4489,7 @@ class Title {
return true; // any interwiki link might be viewable, for all we know
}
- switch( $this->mNamespace ) {
+ switch ( $this->mNamespace ) {
case NS_MEDIA:
case NS_FILE:
// file exists, possibly in a foreign repo
@@ -4500,27 +4575,23 @@ class Title {
* @return Bool true if the update succeeded
*/
public function invalidateCache() {
- global $wgMemc;
if ( wfReadOnly() ) {
return false;
}
- $dbw = wfGetDB( DB_MASTER );
- $success = $dbw->update(
- 'page',
- array( 'page_touched' => $dbw->timestamp() ),
- $this->pageCond(),
- __METHOD__
- );
- HTMLFileCache::clearFileCache( $this );
- // Clear page info.
- $revision = WikiPage::factory( $this )->getRevision();
- if( $revision !== null ) {
- $memcKey = wfMemcKey( 'infoaction', $this->getPrefixedText(), $revision->getId() );
- $success = $success && $wgMemc->delete( $memcKey );
- }
+ $method = __METHOD__;
+ $dbw = wfGetDB( DB_MASTER );
+ $conds = $this->pageCond();
+ $dbw->onTransactionIdle( function() use ( $dbw, $conds, $method ) {
+ $dbw->update(
+ 'page',
+ array( 'page_touched' => $dbw->timestamp() ),
+ $conds,
+ $method
+ );
+ } );
- return $success;
+ return true;
}
/**
@@ -4568,7 +4639,7 @@ class Title {
if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
return $this->mNotificationTimestamp[$uid];
}
- if ( !$uid || !$wgShowUpdatedMarker ) {
+ if ( !$uid || !$wgShowUpdatedMarker || !$user->isAllowed( 'viewmywatchlist' ) ) {
return $this->mNotificationTimestamp[$uid] = false;
}
// Don't cache too much!
@@ -4791,9 +4862,10 @@ class Title {
* they will already be wrapped in paragraphs.
*
* @since 1.21
+ * @param int oldid Revision ID that's being edited
* @return Array
*/
- public function getEditNotices() {
+ public function getEditNotices( $oldid = 0 ) {
$notices = array();
# Optional notices on a per-namespace and per-page basis
@@ -4820,6 +4892,8 @@ class Title {
$notices[$editnoticeText] = $editnoticeMsg->parseAsBlock();
}
}
+
+ wfRunHooks( 'TitleGetEditNotices', array( $this, $oldid, &$notices ) );
return $notices;
}
}