From 8f416baead93a48e5799e44b8bd2e2c4859f4e04 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Fri, 14 Sep 2007 13:18:58 +0200 Subject: auf Version 1.11 aktualisiert; Login-Bug behoben --- includes/Title.php | 338 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 259 insertions(+), 79 deletions(-) (limited to 'includes/Title.php') diff --git a/includes/Title.php b/includes/Title.php index 0ff2e807..c4db4172 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -40,13 +40,14 @@ class Title { * Please use the accessor functions */ - /**#@+ + /**#@+ * @private */ var $mTextform; # Text form (spaces not underscores) of the main part var $mUrlform; # URL-encoded form of the main part var $mDbkeyform; # Main part with underscores + var $mUserCaseDBKey; # DB key with the initial letter in the case specified by the user var $mNamespace; # Namespace index, i.e. one of the NS_xxxx constants var $mInterwiki; # Interwiki prefix (or null string) var $mFragment; # Title fragment (i.e. the bit after the #) @@ -232,7 +233,7 @@ class Title { $t = new Title(); $t->mInterwiki = ''; $t->mFragment = ''; - $t->mNamespace = intval( $ns ); + $t->mNamespace = $ns = intval( $ns ); $t->mDbkeyform = str_replace( ' ', '_', $title ); $t->mArticleID = ( $ns >= 0 ) ? -1 : 0; $t->mUrlform = wfUrlencode( $t->mDbkeyform ); @@ -268,32 +269,33 @@ class Title { } /** - * Create a new Title for a redirect - * @param string $text the redirect title text - * @return Title the new object, or NULL if the text is not a - * valid redirect + * Extract a redirect destination from a string and return the + * Title, or null if the text doesn't contain a valid redirect + * + * @param string $text Text with possible redirect + * @return Title */ public static function newFromRedirect( $text ) { - $mwRedir = MagicWord::get( 'redirect' ); - $rt = NULL; - if ( $mwRedir->matchStart( $text ) ) { + $redir = MagicWord::get( 'redirect' ); + if( $redir->matchStart( $text ) ) { + // Extract the first link and see if it's usable $m = array(); - if ( preg_match( '/\[{2}(.*?)(?:\||\]{2})/', $text, $m ) ) { - # categories are escaped using : for example one can enter: - # #REDIRECT [[:Category:Music]]. Need to remove it. - if ( substr($m[1],0,1) == ':') { - # We don't want to keep the ':' - $m[1] = substr( $m[1], 1 ); - } - - $rt = Title::newFromText( $m[1] ); - # Disallow redirects to Special:Userlogout - if ( !is_null($rt) && $rt->isSpecial( 'Userlogout' ) ) { - $rt = NULL; + if( preg_match( '!\[{2}(.*?)(?:\||\]{2})!', $text, $m ) ) { + // Strip preceding colon used to "escape" categories, etc. + // and URL-decode links + if( strpos( $m[1], '%' ) !== false ) { + // Match behavior of inline link parsing here; + // don't interpret + as " " most of the time! + // It might be safe to just use rawurldecode instead, though. + $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' ) ) + return $title; } } - return $rt; + return null; } #---------------------------------------------------------------------------- @@ -555,6 +557,12 @@ class Title { } return $wgContLang->getNsText( $this->mNamespace ); } + /** + * Get the DB key with the initial letter case as specified by the user + */ + function getUserCaseDBKey() { + return $this->mUserCaseDBKey; + } /** * Get the namespace text of the subject (rather than talk) page * @return string @@ -988,6 +996,23 @@ class Title { return $this->userCan( $action, false ); } + /** + * Determines if $wgUser is unable to edit this page because it has been protected + * by $wgNamespaceProtection. + * + * @return boolean + */ + public function isNamespaceProtected() { + global $wgNamespaceProtection, $wgUser; + if( isset( $wgNamespaceProtection[ $this->mNamespace ] ) ) { + foreach( (array)$wgNamespaceProtection[ $this->mNamespace ] as $right ) { + if( $right != '' && !$wgUser->isAllowed( $right ) ) + return true; + } + } + return false; + } + /** * Can $wgUser perform $action on this page? * @param string $action action that permission needs to be checked for @@ -995,48 +1020,124 @@ class Title { * @return boolean */ public function userCan( $action, $doExpensiveQueries = true ) { + global $wgUser; + return ( $this->getUserPermissionsErrorsInternal( $action, $wgUser, $doExpensiveQueries ) === array()); + } + + /** + * Can $user perform $action on this page? + * @param string $action action that permission needs to be checked for + * @param bool $doExpensiveQueries Set this to false to avoid doing unnecessary queries. + * @return array Array of arrays of the arguments to wfMsg to explain permissions problems. + */ + public function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true ) { + $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries ); + + global $wgContLang; + global $wgLang; + + if ( wfReadOnly() && $action != 'read' ) { + global $wgReadOnly; + $errors[] = array( 'readonlytext', $wgReadOnly ); + } + + global $wgEmailConfirmToEdit, $wgUser; + + if ( $wgEmailConfirmToEdit && !$wgUser->isEmailConfirmed() ) + { + $errors[] = array( 'confirmedittext' ); + } + + if ( $user->isBlockedFrom( $this ) ) { + $block = $user->mBlock; + + // This is from OutputPage::blockedPage + // Copied at r23888 by werdna + + $id = $user->blockedBy(); + $reason = $user->blockedFor(); + $ip = wfGetIP(); + + if ( is_numeric( $id ) ) { + $name = User::whoIs( $id ); + } else { + $name = $id; + } + + $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]"; + $blockid = $block->mId; + $blockExpiry = $user->mBlock->mExpiry; + $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $wgUser->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; + } + } + } else { + $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true ); + } + + $intended = $user->mBlock->mAddress; + + $errors[] = array ( ($block->mAuto ? 'autoblockedtext' : 'blockedtext'), $link, $reason, $ip, $name, $blockid, $blockExpiry, $intended, $blockTimestamp ); + } + + return $errors; + } + + /** + * Can $user perform $action on this page? + * This is an internal function, which checks ONLY that previously checked by userCan (i.e. it leaves out checks on wfReadOnly() and blocks) + * @param string $action action that permission needs to be checked for + * @param bool $doExpensiveQueries Set this to false to avoid doing unnecessary queries. + * @return array Array of arrays of the arguments to wfMsg to explain permissions problems. + */ + private function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries = true ) { $fname = 'Title::userCan'; wfProfileIn( $fname ); - global $wgUser, $wgNamespaceProtection; + $errors = array(); - $result = null; - wfRunHooks( 'userCan', array( &$this, &$wgUser, $action, &$result ) ); - if ( $result !== null ) { - wfProfileOut( $fname ); - return $result; + if ( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) { + return $result ? array() : array( array( 'badaccess-group0' ) ); } if( NS_SPECIAL == $this->mNamespace ) { - wfProfileOut( $fname ); - return false; + $errors[] = array('ns-specialprotected'); } - if ( array_key_exists( $this->mNamespace, $wgNamespaceProtection ) ) { - $nsProt = $wgNamespaceProtection[ $this->mNamespace ]; - if ( !is_array($nsProt) ) $nsProt = array($nsProt); - foreach( $nsProt as $right ) { - if( '' != $right && !$wgUser->isAllowed( $right ) ) { - wfProfileOut( $fname ); - return false; - } - } + 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... - wfProfileOut( $fname ); - return false; + $errors[] = array('badaccess-group0'); } # 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() - && !$wgUser->isAllowed('editinterface') - && !preg_match('/^'.preg_quote($wgUser->getName(), '/').'\//', $this->mTextform) ) { - wfProfileOut( $fname ); - return false; + && !$user->isAllowed('editinterface') + && !preg_match('/^'.preg_quote($user->getName(), '/').'\//', $this->mTextform) ) { + $errors[] = array('customcssjsprotected'); } if ( $doExpensiveQueries && !$this->isCssJsSubpage() ) { @@ -1052,9 +1153,11 @@ class Title { if( $cascadingSources > 0 && isset($restrictions[$action]) ) { foreach( $restrictions[$action] as $right ) { $right = ( $right == 'sysop' ) ? 'protect' : $right; - if( '' != $right && !$wgUser->isAllowed( $right ) ) { - wfProfileOut( $fname ); - return false; + if( '' != $right && !$user->isAllowed( $right ) ) { + $pages = ''; + foreach( $cascadingSources as $page ) + $pages .= '* [[:' . $page->getPrefixedText() . "]]\n"; + $errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages ); } } } @@ -1065,28 +1168,50 @@ class Title { if ( $right == 'sysop' ) { $right = 'protect'; } - if( '' != $right && !$wgUser->isAllowed( $right ) ) { - wfProfileOut( $fname ); - return false; + if( '' != $right && !$user->isAllowed( $right ) ) { + $errors[] = array( 'protectedpagetext' ); } } - if( $action == 'move' && - !( $this->isMovable() && $wgUser->isAllowed( 'move' ) ) ) { - wfProfileOut( $fname ); - return false; - } - if( $action == 'create' ) { - if( ( $this->isTalkPage() && !$wgUser->isAllowed( 'createtalk' ) ) || - ( !$this->isTalkPage() && !$wgUser->isAllowed( 'createpage' ) ) ) { - wfProfileOut( $fname ); - return false; + 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'); + } else if ( !$user->isAllowed( $action ) ) { + $return = null; + $groups = array(); + global $wgGroupPermissions; + foreach( $wgGroupPermissions as $key => $value ) { + if( isset( $value[$action] ) && $value[$action] == true ) { + $groupName = User::getGroupName( $key ); + $groupPage = User::getGroupPage( $key ); + if( $groupPage ) { + $skin = $user->getSkin(); + $groups[] = '[['.$groupPage->getPrefixedText().'|'.$groupName.']]'; + } else { + $groups[] = $groupName; + } + } + } + $n = count( $groups ); + $groups = implode( ', ', $groups ); + switch( $n ) { + case 0: + case 1: + case 2: + $return = array( "badaccess-group$n", $groups ); + break; + default: + $return = array( 'badaccess-groups', $groups ); + } + $errors[] = $return; } wfProfileOut( $fname ); - return true; + return $errors; } /** @@ -1141,7 +1266,7 @@ class Title { return $result; } - if( $wgUser->isAllowed('read') ) { + if( $wgUser->isAllowed( 'read' ) ) { return true; } else { global $wgWhitelistRead; @@ -1153,19 +1278,35 @@ class Title { if( $this->isSpecial( 'Userlogin' ) || $this->isSpecial( 'Resetpass' ) ) { return true; } - - /** some pages are explicitly allowed */ + + /** + * Check for explicit whitelisting + */ $name = $this->getPrefixedText(); - if( $wgWhitelistRead && in_array( $name, $wgWhitelistRead ) ) { + if( $wgWhitelistRead && in_array( $name, $wgWhitelistRead, true ) ) return true; - } - - # Compatibility with old settings + + /** + * Old settings might have the title prefixed with + * a colon for main-namespace pages + */ if( $wgWhitelistRead && $this->getNamespace() == NS_MAIN ) { - if( in_array( ':' . $name, $wgWhitelistRead ) ) { + if( in_array( ':' . $name, $wgWhitelistRead ) ) return true; - } } + + /** + * If it's a special page, ditch the subpage bit + * and check again + */ + if( $this->getNamespace() == NS_SPECIAL ) { + $name = $this->getText(); + list( $name, /* $subpage */) = SpecialPage::resolveAliasWithSubpage( $name ); + $pure = SpecialPage::getTitleFor( $name )->getPrefixedText(); + if( in_array( $pure, $wgWhitelistRead, true ) ) + return true; + } + } return false; } @@ -1191,6 +1332,17 @@ class Title { return false; } } + + /** + * Could this page contain custom CSS or JavaScript, based + * on the title? + * + * @return bool + */ + public function isCssOrJsPage() { + return $this->mNamespace == NS_MEDIAWIKI + && preg_match( '!\.(?:css|js)$!u', $this->mTextform ) > 0; + } /** * Is this a .css or .js subpage of a user page? @@ -1251,7 +1403,7 @@ class Title { * @return bool If the page is subject to cascading restrictions. */ public function isCascadeProtected() { - list( $sources, $restrictions ) = $this->getCascadeProtectionSources( false ); + list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false ); return ( $sources > 0 ); } @@ -1749,6 +1901,7 @@ class Title { * Don't force it for interwikis, since the other * site might be case-sensitive. */ + $this->mUserCaseDBKey = $dbkey; if( $wgCapitalLinks && $this->mInterwiki == '') { $dbkey = $wgContLang->ucfirst( $dbkey ); } @@ -1763,7 +1916,14 @@ class Title { $this->mNamespace != NS_MAIN ) { return false; } - + // Allow IPv6 usernames to start with '::' by canonicalizing IPv6 titles. + // IP names are not allowed for accounts, and can only be referring to + // edits from the IP. Given '::' abbreviations and caps/lowercaps, + // there are numerous ways to present the same IP. Having sp:contribs scan + // them all is silly and having some show the edits and others not is + // inconsistent. Same for talk/userpages. Keep them normalized instead. + $dbkey = ($this->mNamespace == NS_USER || $this->mNamespace == NS_USER_TALK) ? + IP::sanitizeIP( $dbkey ) : $dbkey; // Any remaining initial :s are illegal. if ( $dbkey !== '' && ':' == $dbkey{0} ) { return false; @@ -2270,6 +2430,16 @@ class Title { # Return true if there was no history return $row === false; } + + /** + * Can this title be added to a user's watchlist? + * + * @return bool + */ + public function isWatchable() { + return !$this->isExternal() + && Namespace::isWatchable( $this->getNamespace() ); + } /** * Get categories to which this Title belongs and return an array of @@ -2394,6 +2564,15 @@ class Title { && $this->getNamespace() == $title->getNamespace() && $this->getDbkey() === $title->getDbkey(); } + + /** + * Return a string representation of this title + * + * @return string + */ + public function __toString() { + return $this->getPrefixedText(); + } /** * Check if page exists @@ -2404,14 +2583,15 @@ class Title { } /** - * Should a link should be displayed as a known link, just based on its title? + * Do we know that this title definitely exists, or should we otherwise + * consider that it exists? * - * Currently, a self-link with a fragment and special pages are in - * this category. Special pages never exist in the database. + * @return bool */ public function isAlwaysKnown() { - return $this->isExternal() || ( 0 == $this->mNamespace && "" == $this->mDbkeyform ) - || NS_SPECIAL == $this->mNamespace; + return $this->isExternal() + || ( $this->mNamespace == NS_MAIN && $this->mDbkeyform == '' ) + || ( $this->mNamespace == NS_MEDIAWIKI && wfMsgWeirdKey( $this->mDbkeyform ) ); } /** @@ -2551,4 +2731,4 @@ class Title { } -?> + -- cgit v1.2.3-54-g00ecf