X-Git-Url: http://git.cyclocoop.org/?a=blobdiff_plain;ds=sidebyside;f=includes%2FTitle.php;h=3a4c3fe657a4126da28472e3467903e0c6bbb0a6;hb=e514478ba52bbae35955dc9b23e9a015aa86cdaf;hp=a48cbae700dabdeadf48e432ee6ee1cac95b6567;hpb=3d161feebf1fce8035bbc55df897bf118848364e;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/Title.php b/includes/Title.php index a48cbae700..3a4c3fe657 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -4,21 +4,6 @@ * @file */ -/** - * @todo: determine if it is really necessary to load this. Appears to be left over from pre-autoloader versions, and - * is only really needed to provide access to constant UTF8_REPLACEMENT, which actually resides in UtfNormalDefines.php - * and is loaded by UtfNormalUtil.php, which is loaded by UtfNormal.php. - */ -if ( !class_exists( 'UtfNormal' ) ) { - require_once( dirname( __FILE__ ) . '/normal/UtfNormal.php' ); -} - -/** - * @deprecated This used to be a define, but was moved to - * Title::GAID_FOR_UPDATE in 1.17. This will probably be removed in 1.18 - */ -define( 'GAID_FOR_UPDATE', Title::GAID_FOR_UPDATE ); - /** * Represents a title within MediaWiki. * Optionally may contain an interwiki designation or namespace. @@ -87,9 +72,8 @@ class Title { /** * Constructor - * @private */ - /* private */ function __construct() { } + /*protected*/ function __construct() { } /** * Create a new Title from a prefixed DB key @@ -728,7 +712,8 @@ class Title { * @return String the prefixed title, with spaces */ public function getPrefixedText() { - if ( empty( $this->mPrefixedText ) ) { // FIXME: bad usage of empty() ? + // @todo FIXME: Bad usage of empty() ? + if ( empty( $this->mPrefixedText ) ) { $s = $this->prefix( $this->mTextform ); $s = str_replace( '_', ' ', $s ); $this->mPrefixedText = $s; @@ -892,33 +877,25 @@ class Title { $url = str_replace( '$1', $dbkey, $wgArticlePath ); } } else { - global $wgActionPaths; $url = false; - $matches = array(); - if ( !empty( $wgActionPaths ) && - preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches ) ) - { - $action = urldecode( $matches[2] ); - if ( isset( $wgActionPaths[$action] ) ) { - $query = $matches[1]; - if ( isset( $matches[4] ) ) { - $query .= $matches[4]; - } - $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] ); - if ( $query != '' ) { - $url = wfAppendQuery( $url, $query ); - } - } + + global $wgActionPaths; + if( !empty( $wgActionPaths ) ) { + $url = Title::resolveActionPath( $dbkey, $query ); } + if ( $url === false ) { if ( $query == '-' ) { $query = ''; } - $url = "{$wgScript}?title={$dbkey}&{$query}"; + #$url = "{$wgScript}?title={$dbkey}&{$query}"; + # forge a nice URL (ex: /wiki/Special:Foo?q=1&r=2 ) + $baseurl = str_replace( '$1', $dbkey, $wgArticlePath ); + $url = wfAppendQuery( $baseurl, $query ); } } - // FIXME: this causes breakage in various places when we + // @todo FIXME: This causes breakage in various places when we // actually expected a local URL and end up with dupe prefixes. if ( $wgRequest->getVal( 'action' ) == 'render' ) { $url = $wgServer . $url; @@ -928,6 +905,38 @@ class Title { return $url; } + /** + * Helper for getLocalUrl() to handles $wgActionPaths + * + * @param $dbkey string Title in database key format + * @param $query string request parameters in CGI format (p=1&q=2&..) + * @return Url resolved or boolean false + */ + private static function resolveActionPath( $dbkey, $query ) { + $url = ''; + + # query parameters are easier to handle using an array: + $queryArray = wfCGIToArray( $query ); + + global $wgActionPaths; + if( !array_key_exists( 'action', $queryArray ) ) { + // Makes the default action 'view' and points to $wgArticlePath + // @todo FIXME: This should be handled in Setup or Wiki! + global $wgArticlePath; + $url = str_replace( '$1', $dbkey, $wgArticlePath ); + } elseif( isset( $wgActionPaths[$queryArray['action']] ) ) { + $url = str_replace( '$1', $dbkey, $wgActionPaths[$queryArray['action']] ); + } else { + # No path found + return false; + } + + # No need to append the action since we have embed it in the path + unset( $queryArray['action'] ); + $url = wfAppendQuery( $url, wfArrayToCGI( $queryArray ) ); + return $url; + } + /** * Get a URL that's the simplest URL that will be valid to link, locally, * to the current Title. It includes the fragment, but does not include @@ -1181,11 +1190,12 @@ class Title { /** * Can $user perform $action on this page? * - * FIXME: This *does not* check throttles (User::pingLimiter()). + * @todo FIXME: This *does not* check throttles (User::pingLimiter()). * * @param $action String action that permission needs to be checked for * @param $user User to check - * @param $doExpensiveQueries Bool Set this to false to avoid doing unnecessary queries. + * @param $doExpensiveQueries Bool Set this to false to avoid doing unnecessary queries by + * skipping checks for cascading protections and user blocks. * @param $ignoreErrors Array of Strings Set this to a list of message keys whose corresponding errors may be ignored. * @return Array of arguments to wfMsg to explain permissions problems. */ @@ -1531,7 +1541,7 @@ class Title { * @return Array list of errors */ private function checkUserBlock( $action, $user, $errors, $doExpensiveQueries, $short ) { - if( $short && count( $errors ) > 0 ) { + if( !$doExpensiveQueries ) { return $errors; } @@ -1541,8 +1551,14 @@ class Title { $errors[] = array( 'confirmedittext' ); } - // Edit blocks should not affect reading. Account creation blocks handled at userlogin. - if ( $action != 'read' && $action != 'createaccount' && $user->isBlockedFrom( $this ) ) { + if ( in_array( $action, array( 'read', 'createaccount', 'unblock' ) ) ){ + // Edit blocks should not affect reading. + // Account creation blocks handled at userlogin. + // Unblocking handled in SpecialUnblock + } elseif( ( $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->mBlock; // This is from OutputPage::blockedPage @@ -1562,29 +1578,16 @@ class Title { } $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]"; - $blockid = $block->mId; + $blockid = $block->getId(); $blockExpiry = $user->mBlock->mExpiry; $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, ':' ) ) - continue; - - list( $show, $value ) = explode( ':', $option ); - - if ( $value == 'infinite' || $value == 'indefinite' ) { - $blockExpiry = $show; - break; - } - } + $blockExpiry = wfMessage( 'infiniteblock' )->text(); } else { $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true ); } - $intended = $user->mBlock->mAddress; + $intended = strval( $user->mBlock->getTarget() ); $errors[] = array( ( $block->mAuto ? 'autoblockedtext' : 'blockedtext' ), $link, $reason, $ip, $name, $blockid, $blockExpiry, $intended, $blockTimestamp ); @@ -1680,10 +1683,10 @@ class Title { $dbw = wfGetDB( DB_MASTER ); - $encodedExpiry = Block::encodeExpiry( $expiry, $dbw ); + $encodedExpiry = $dbw->encodeExpiry( $expiry ); $expiry_description = ''; - if ( $encodedExpiry != 'infinity' ) { + if ( $encodedExpiry != $dbw->getInfinity() ) { $expiry_description = ' (' . wfMsgForContent( 'protect-expiring', $wgContLang->timeanddate( $expiry ), $wgContLang->date( $expiry ) , $wgContLang->time( $expiry ) ) . ')'; } else { @@ -1696,7 +1699,7 @@ class Title { 'pt_namespace' => $namespace, 'pt_title' => $title, 'pt_create_perm' => $create_perm, - 'pt_timestamp' => Block::encodeExpiry( wfTimestampNow(), $dbw ), + 'pt_timestamp' => $dbw->encodeExpiry( wfTimestampNow() ), 'pt_expiry' => $encodedExpiry, 'pt_user' => $wgUser->getId(), 'pt_reason' => $reason, @@ -1767,7 +1770,7 @@ class Title { # 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 @@ -1801,7 +1804,7 @@ class Title { # Always grant access to the login page. # Even anons need to be able to log in. - if ( $this->isSpecial( 'Userlogin' ) || $this->isSpecial( 'Resetpass' ) ) { + if ( $this->isSpecial( 'Userlogin' ) || $this->isSpecial( 'ChangePassword' ) ) { return true; } @@ -1828,7 +1831,7 @@ class Title { # If it's a special page, ditch the subpage bit and check again if ( $this->getNamespace() == NS_SPECIAL ) { $name = $this->getDBkey(); - list( $name, /* $subpage */ ) = SpecialPage::resolveAliasWithSubpage( $name ); + list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name ); if ( $name === false ) { # Invalid special page, but we show standard login required message return false; @@ -1999,7 +2002,7 @@ class Title { */ public function userCanEditCssSubpage() { global $wgUser; - return ( ( $wgUser->isAllowed( 'editusercssjs' ) && $wgUser->isAllowed( 'editusercss' ) ) + return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'editusercss' ) ) || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) ); } @@ -2012,7 +2015,7 @@ class Title { */ public function userCanEditJsSubpage() { global $wgUser; - return ( ( $wgUser->isAllowed( 'editusercssjs' ) && $wgUser->isAllowed( 'edituserjs' ) ) + return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'edituserjs' ) ) || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) ); } @@ -2037,6 +2040,7 @@ class Title { * contains a array of unique groups. */ public function getCascadeProtectionSources( $getPages = true ) { + global $wgContLang; $pagerestrictions = array(); if ( isset( $this->mCascadeSources ) && $getPages ) { @@ -2082,7 +2086,7 @@ class Title { $purgeExpired = false; foreach ( $res as $row ) { - $expiry = Block::decodeExpiry( $row->pr_expiry ); + $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW ); if ( $expiry > $now ) { if ( $getPages ) { $page_id = $row->pr_page; @@ -2163,13 +2167,14 @@ class Title { * restrictions from page table (pre 1.10) */ public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) { + global $wgContLang; $dbr = wfGetDB( DB_SLAVE ); $restrictionTypes = $this->getRestrictionTypes(); foreach ( $restrictionTypes as $type ) { $this->mRestrictions[$type] = array(); - $this->mRestrictionsExpiry[$type] = Block::decodeExpiry( '' ); + $this->mRestrictionsExpiry[$type] = $wgContLang->formatExpiry( '', TS_MW ); } $this->mCascadeRestriction = false; @@ -2212,7 +2217,7 @@ class Title { // 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 - $expiry = Block::decodeExpiry( $row->pr_expiry ); + $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW ); // Only apply the restrictions if they haven't expired! if ( !$expiry || $expiry > $now ) { @@ -2241,12 +2246,17 @@ class Title { * restrictions from page table (pre 1.10) */ public function loadRestrictions( $oldFashionedRestrictions = null ) { + global $wgContLang; if ( !$this->mRestrictionsLoaded ) { if ( $this->exists() ) { $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'page_restrictions', '*', - array( 'pr_page' => $this->getArticleId() ), __METHOD__ ); + $res = $dbr->select( + 'page_restrictions', + '*', + array( 'pr_page' => $this->getArticleId() ), + __METHOD__ + ); $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions ); } else { @@ -2254,7 +2264,7 @@ class Title { if ( $title_protection ) { $now = wfTimestampNow(); - $expiry = Block::decodeExpiry( $title_protection['pt_expiry'] ); + $expiry = $wgContLang->formatExpiry( $title_protection['pt_expiry'], TS_MW ); if ( !$expiry || $expiry > $now ) { // Apply the restrictions @@ -2265,7 +2275,7 @@ class Title { $this->mTitleProtection = false; } } else { - $this->mRestrictionsExpiry['create'] = Block::decodeExpiry( '' ); + $this->mRestrictionsExpiry['create'] = $wgContLang->formatExpiry( '', TS_MW ); } $this->mRestrictionsLoaded = true; } @@ -2507,7 +2517,7 @@ class Title { * @return String the prefixed text * @private */ - /* private */ function prefix( $name ) { + private function prefix( $name ) { $p = ''; if ( $this->mInterwiki != '' ) { $p = $this->mInterwiki . ':'; @@ -2578,8 +2588,6 @@ class Title { global $wgContLang, $wgLocalInterwiki; # Initialisation - $rxTc = self::getTitleInvalidRegex(); - $this->mInterwiki = $this->mFragment = ''; $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN @@ -2681,7 +2689,7 @@ class Title { } $fragment = strstr( $dbkey, '#' ); if ( false !== $fragment ) { - $this->setFragment( preg_replace( '/^#_*/', '#', $fragment ) ); + $this->setFragment( $fragment ); $dbkey = substr( $dbkey, 0, strlen( $dbkey ) - strlen( $fragment ) ); # remove whitespace again: prevents "Foo_bar_#" # becoming "Foo_bar_" @@ -2689,6 +2697,7 @@ class Title { } # Reject illegal characters. + $rxTc = self::getTitleInvalidRegex(); if ( preg_match( $rxTc, $dbkey ) ) { return false; } @@ -2990,7 +2999,7 @@ class Title { if ( $this->getNamespace() == NS_FILE ) { $errors = array_merge( $errors, $this->validateFileMoveOperation( $nt ) ); } - + if ( $nt->getNamespace() == NS_FILE && $this->getNamespace() != NS_FILE ) { $errors[] = array( 'nonfile-cannot-move-to-file' ); } @@ -3034,7 +3043,7 @@ class Title { } return $errors; } - + /** * Check if the requested move target is a valid file move target * @param Title $nt Target title @@ -3042,13 +3051,13 @@ class Title { */ protected function validateFileMoveOperation( $nt ) { global $wgUser; - + $errors = array(); - + if ( $nt->getNamespace() != NS_FILE ) { $errors[] = array( 'imagenocrossnamespace' ); } - + $file = wfLocalFile( $this ); if ( $file->exists() ) { if ( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) { @@ -3058,12 +3067,12 @@ class Title { $errors[] = array( 'imagetypemismatch' ); } } - + $destFile = wfLocalFile( $nt ); - if ( !$wgUser->isAllowed( 'reupload-shared' ) && !$destfile->exists() && wfFindFile( $nt ) ) { + if ( !$wgUser->isAllowed( 'reupload-shared' ) && !$destFile->exists() && wfFindFile( $nt ) ) { $errors[] = array( 'file-exists-sharedrepo' ); } - + return $errors; } @@ -3097,13 +3106,16 @@ class Title { } } - $pageid = $this->getArticleID(); + $dbw->begin(); # If $file was a LocalFile, its transaction would have closed our own. + $pageid = $this->getArticleID( self::GAID_FOR_UPDATE ); $protected = $this->isProtected(); $pageCountChange = ( $createRedirect ? 1 : 0 ) - ( $nt->exists() ? 1 : 0 ); // Do the actual move $err = $this->moveToInternal( $nt, $reason, $createRedirect ); if ( is_array( $err ) ) { + # @todo FIXME: What about the File we have already moved? + $dbw->rollback(); return $err; } @@ -3111,19 +3123,26 @@ class Title { // Refresh the sortkey for this row. Be careful to avoid resetting // cl_timestamp, which may disturb time-based lists on some sites. - $prefix = $dbw->selectField( + $prefixes = $dbw->select( 'categorylinks', - 'cl_sortkey_prefix', + array( 'cl_sortkey_prefix', 'cl_to' ), array( 'cl_from' => $pageid ), __METHOD__ ); - $dbw->update( 'categorylinks', - array( - 'cl_sortkey' => Collation::singleton()->getSortKey( - $nt->getCategorySortkey( $prefix ) ), - 'cl_timestamp=cl_timestamp' ), - array( 'cl_from' => $pageid ), - __METHOD__ ); + foreach ( $prefixes as $prefixRow ) { + $prefix = $prefixRow->cl_sortkey_prefix; + $catTo = $prefixRow->cl_to; + $dbw->update( 'categorylinks', + array( + 'cl_sortkey' => Collation::singleton()->getSortKey( + $nt->getCategorySortkey( $prefix ) ), + 'cl_timestamp=cl_timestamp' ), + array( + 'cl_from' => $pageid, + 'cl_to' => $catTo ), + __METHOD__ + ); + } if ( $protected ) { # Protect the redirect title as the title used to be... @@ -3146,7 +3165,8 @@ class Title { if ( $reason ) { $comment .= wfMsgForContent( 'colon-separator' ) . $reason; } - $log->addEntry( 'move_prot', $nt, $comment, array( $this->getPrefixedText() ) ); // FIXME: $params? + // @todo FIXME: $params? + $log->addEntry( 'move_prot', $nt, $comment, array( $this->getPrefixedText() ) ); } # Update watchlists @@ -3165,6 +3185,8 @@ class Title { $u = new SearchUpdate( $redirid, $this->getPrefixedDBkey(), '' ); $u->doUpdate(); + $dbw->commit(); + # Update site_stats if ( $this->isContentPage() && !$nt->isContentPage() ) { # No longer a content page @@ -3225,12 +3247,15 @@ class Title { if ( $reason ) { $comment .= wfMsgForContent( 'colon-separator' ) . $reason; } - # Truncate for whole multibyte characters. +5 bytes for ellipsis - $comment = $wgContLang->truncate( $comment, 250 ); + # Truncate for whole multibyte characters. + $comment = $wgContLang->truncate( $comment, 255 ); $oldid = $this->getArticleID(); $latest = $this->getLatestRevID(); + $oldns = $this->getNamespace(); + $olddbk = $this->getDBkey(); + $dbw = wfGetDB( DB_MASTER ); if ( $moveOverRedirect ) { @@ -3273,9 +3298,6 @@ class Title { } $nullRevId = $nullRevision->insertOn( $dbw ); - $article = new Article( $this ); - wfRunHooks( 'NewRevisionFromEditComplete', array( $article, $nullRevision, $latest, $wgUser ) ); - # Change the name of the target page: $dbw->update( 'page', /* SET */ array( @@ -3289,6 +3311,9 @@ class Title { ); $nt->resetArticleID( $oldid ); + $article = new Article( $nt ); + wfRunHooks( 'NewRevisionFromEditComplete', array( $article, $nullRevision, $latest, $wgUser ) ); + # Recreate the redirect, this time in the other direction. if ( $createRedirect || !$wgUser->isAllowed( 'suppressredirect' ) ) { $mwRedir = MagicWord::get( 'redirect' ); @@ -3315,6 +3340,17 @@ class Title { __METHOD__ ); $redirectSuppressed = false; } else { + // Get rid of old new page entries in Special:NewPages and RC. + // Needs to be before $this->resetArticleID( 0 ). + $dbw->delete( 'recentchanges', array( + 'rc_timestamp' => $dbw->timestamp( $this->getEarliestRevTime() ), + 'rc_namespace' => $oldns, + 'rc_title' => $olddbk, + 'rc_new' => 1 + ), + __METHOD__ + ); + $this->resetArticleID( 0 ); $redirectSuppressed = true; } @@ -3621,65 +3657,68 @@ class Title { * @return Revision|Null if page doesn't exist */ public function getFirstRevision( $flags = 0 ) { - $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE ); $pageId = $this->getArticleId( $flags ); - if ( !$pageId ) { - return null; - } - $row = $db->selectRow( 'revision', '*', - array( 'rev_page' => $pageId ), - __METHOD__, - array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 ) - ); - if ( !$row ) { - return null; - } else { - return new Revision( $row ); + if ( $pageId ) { + $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE ); + $row = $db->selectRow( 'revision', '*', + array( 'rev_page' => $pageId ), + __METHOD__, + array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 ) + ); + if ( $row ) { + return new Revision( $row ); + } } + return null; } /** - * Check if this is a new page + * Get the oldest revision timestamp of this page * - * @return bool + * @param $flags Int Title::GAID_FOR_UPDATE + * @return String: MW timestamp */ - public function isNewPage() { - $dbr = wfGetDB( DB_SLAVE ); - return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ ); + public function getEarliestRevTime( $flags = 0 ) { + $rev = $this->getFirstRevision( $flags ); + return $rev ? $rev->getTimestamp() : null; } /** - * Get the oldest revision timestamp of this page + * Check if this is a new page * - * @return String: MW timestamp + * @return bool */ - public function getEarliestRevTime() { + public function isNewPage() { $dbr = wfGetDB( DB_SLAVE ); - if ( $this->exists() ) { - $min = $dbr->selectField( 'revision', - 'MIN(rev_timestamp)', - array( 'rev_page' => $this->getArticleId() ), - __METHOD__ ); - return wfTimestampOrNull( TS_MW, $min ); - } - return null; + return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ ); } /** - * Get the number of revisions between the given revision IDs. + * Get the number of revisions between the given revision. * Used for diffs and other things that really need it. * - * @param $old Int Revision ID. - * @param $new Int Revision ID. - * @return Int Number of revisions between these IDs. + * @param $old int|Revision Old revision or rev ID (first before range) + * @param $new int|Revision New revision or rev ID (first after range) + * @return Int Number of revisions between these revisions. */ public function countRevisionsBetween( $old, $new ) { + if ( !( $old instanceof Revision ) ) { + $old = Revision::newFromTitle( $this, (int)$old ); + } + if ( !( $new instanceof Revision ) ) { + $new = Revision::newFromTitle( $this, (int)$new ); + } + if ( !$old || !$new ) { + return 0; // nothing to compare + } $dbr = wfGetDB( DB_SLAVE ); - return (int)$dbr->selectField( 'revision', 'count(*)', array( - 'rev_page' => intval( $this->getArticleId() ), - 'rev_id > ' . intval( $old ), - 'rev_id < ' . intval( $new ) - ), __METHOD__ + return (int)$dbr->selectField( 'revision', 'count(*)', + array( + 'rev_page' => $this->getArticleId(), + 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ), + 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) ) + ), + __METHOD__ ); } @@ -3687,23 +3726,31 @@ class Title { * Get the number of authors between the given revision IDs. * Used for diffs and other things that really need it. * - * @param $fromRevId Int Revision ID (first before range) - * @param $toRevId Int Revision ID (first after range) + * @param $old int|Revision Old revision or rev ID (first before range) + * @param $new int|Revision New revision or rev ID (first after range) * @param $limit Int Maximum number of authors - * @param $flags Int Title::GAID_FOR_UPDATE - * @return Int + * @return Int Number of revision authors between these revisions. */ - public function countAuthorsBetween( $fromRevId, $toRevId, $limit, $flags = 0 ) { - $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE ); - $res = $db->select( 'revision', 'DISTINCT rev_user_text', + public function countAuthorsBetween( $old, $new, $limit ) { + if ( !( $old instanceof Revision ) ) { + $old = Revision::newFromTitle( $this, (int)$old ); + } + if ( !( $new instanceof Revision ) ) { + $new = Revision::newFromTitle( $this, (int)$new ); + } + if ( !$old || !$new ) { + return 0; // nothing to compare + } + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'revision', 'DISTINCT rev_user_text', array( 'rev_page' => $this->getArticleID(), - 'rev_id > ' . (int)$fromRevId, - 'rev_id < ' . (int)$toRevId + 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ), + 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) ) ), __METHOD__, - array( 'LIMIT' => $limit ) + array( 'LIMIT' => $limit + 1 ) // add one so caller knows it was truncated ); - return (int)$db->numRows( $res ); + return (int)$dbr->numRows( $res ); } /** @@ -3784,7 +3831,7 @@ class Title { return (bool)wfFindFile( $this ); case NS_SPECIAL: // valid special page - return SpecialPage::exists( $this->getDBkey() ); + return SpecialPageFactory::exists( $this->getDBkey() ); case NS_MAIN: // selflink, possibly with fragment return $this->mDbkeyform == ''; @@ -4011,7 +4058,7 @@ class Title { */ public function isSpecial( $name ) { if ( $this->getNamespace() == NS_SPECIAL ) { - list( $thisName, /* $subpage */ ) = SpecialPage::resolveAliasWithSubpage( $this->getDBkey() ); + list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() ); if ( $name == $thisName ) { return true; } @@ -4027,9 +4074,9 @@ class Title { */ public function fixSpecialName() { if ( $this->getNamespace() == NS_SPECIAL ) { - $canonicalName = SpecialPage::resolveAlias( $this->mDbkeyform ); + list( $canonicalName, /*...*/ ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform ); if ( $canonicalName ) { - $localName = SpecialPage::getLocalNameFor( $canonicalName ); + $localName = SpecialPageFactory::getLocalNameFor( $canonicalName ); if ( $localName != $this->mDbkeyform ) { return Title::makeTitle( NS_SPECIAL, $localName ); } @@ -4149,15 +4196,15 @@ class Title { } wfRunHooks( 'TitleGetRestrictionTypes', array( $this, &$types ) ); - - wfDebug( __METHOD__ . ': applicable restriction types for ' . + + wfDebug( __METHOD__ . ': applicable restriction types for ' . $this->getPrefixedText() . ' are ' . implode( ',', $types ) . "\n" ); return $types; } /** - * Get a filtered list of all restriction types supported by this wiki. - * @param bool $exists True to get all restriction types that apply to + * 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 * titles that do not exist * @return array @@ -4198,3 +4245,37 @@ class Title { return $unprefixed; } } + +/** + * A BadTitle is generated in MediaWiki::parseTitle() if the title is invalid; the + * software uses this to display an error page. Internally it's basically a Title + * for an empty special page + */ +class BadTitle extends Title { + public function __construct(){ + $this->mTextform = ''; + $this->mUrlform = ''; + $this->mDbkeyform = ''; + $this->mNamespace = NS_SPECIAL; // Stops talk page link, etc, being shown + } + + public function exists(){ + return false; + } + + public function getPrefixedText(){ + return ''; + } + + public function getText(){ + return ''; + } + + public function getPrefixedURL(){ + return ''; + } + + public function getPrefixedDBKey(){ + return ''; + } +}