From 4ac9fa081a7c045f6a9f1cfc529d82423f485b2e Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Sun, 8 Dec 2013 09:55:49 +0100 Subject: Update to MediaWiki 1.22.0 --- includes/Title.php | 528 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 301 insertions(+), 227 deletions(-) (limited to 'includes/Title.php') 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; @@ -486,6 +491,108 @@ class Title { return $rxTc; } + /** + * 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 ); @@ -2260,36 +2341,6 @@ class Title { return $errors; } - /** - * 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 @@ -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; } } -- cgit v1.2.2