MediaWiki
REL1_22
|
00001 <?php 00028 class MediaWiki { 00029 00034 private $context; 00035 00040 public function request( WebRequest $x = null ) { 00041 $old = $this->context->getRequest(); 00042 $this->context->setRequest( $x ); 00043 return $old; 00044 } 00045 00050 public function output( OutputPage $x = null ) { 00051 $old = $this->context->getOutput(); 00052 $this->context->setOutput( $x ); 00053 return $old; 00054 } 00055 00059 public function __construct( IContextSource $context = null ) { 00060 if ( !$context ) { 00061 $context = RequestContext::getMain(); 00062 } 00063 00064 $this->context = $context; 00065 } 00066 00072 private function parseTitle() { 00073 global $wgContLang; 00074 00075 $request = $this->context->getRequest(); 00076 $curid = $request->getInt( 'curid' ); 00077 $title = $request->getVal( 'title' ); 00078 $action = $request->getVal( 'action', 'view' ); 00079 00080 if ( $request->getCheck( 'search' ) ) { 00081 // Compatibility with old search URLs which didn't use Special:Search 00082 // Just check for presence here, so blank requests still 00083 // show the search page when using ugly URLs (bug 8054). 00084 $ret = SpecialPage::getTitleFor( 'Search' ); 00085 } elseif ( $curid ) { 00086 // URLs like this are generated by RC, because rc_title isn't always accurate 00087 $ret = Title::newFromID( $curid ); 00088 } else { 00089 $ret = Title::newFromURL( $title ); 00090 // Alias NS_MEDIA page URLs to NS_FILE...we only use NS_MEDIA 00091 // in wikitext links to tell Parser to make a direct file link 00092 if ( !is_null( $ret ) && $ret->getNamespace() == NS_MEDIA ) { 00093 $ret = Title::makeTitle( NS_FILE, $ret->getDBkey() ); 00094 } 00095 // Check variant links so that interwiki links don't have to worry 00096 // about the possible different language variants 00097 if ( count( $wgContLang->getVariants() ) > 1 00098 && !is_null( $ret ) && $ret->getArticleID() == 0 ) 00099 { 00100 $wgContLang->findVariantLink( $title, $ret ); 00101 } 00102 } 00103 00104 // If title is not provided, always allow oldid and diff to set the title. 00105 // If title is provided, allow oldid and diff to override the title, unless 00106 // we are talking about a special page which might use these parameters for 00107 // other purposes. 00108 if ( $ret === null || !$ret->isSpecialPage() ) { 00109 // We can have urls with just ?diff=,?oldid= or even just ?diff= 00110 $oldid = $request->getInt( 'oldid' ); 00111 $oldid = $oldid ? $oldid : $request->getInt( 'diff' ); 00112 // Allow oldid to override a changed or missing title 00113 if ( $oldid ) { 00114 $rev = Revision::newFromId( $oldid ); 00115 $ret = $rev ? $rev->getTitle() : $ret; 00116 } 00117 } 00118 00119 // Use the main page as default title if nothing else has been provided 00120 if ( $ret === null && strval( $title ) === '' && $action !== 'delete' ) { 00121 $ret = Title::newMainPage(); 00122 } 00123 00124 if ( $ret === null || ( $ret->getDBkey() == '' && $ret->getInterwiki() == '' ) ) { 00125 $ret = SpecialPage::getTitleFor( 'Badtitle' ); 00126 } 00127 00128 return $ret; 00129 } 00130 00135 public function getTitle() { 00136 if ( $this->context->getTitle() === null ) { 00137 $this->context->setTitle( $this->parseTitle() ); 00138 } 00139 return $this->context->getTitle(); 00140 } 00141 00147 public function getAction() { 00148 static $action = null; 00149 00150 if ( $action === null ) { 00151 $action = Action::getActionName( $this->context ); 00152 } 00153 00154 return $action; 00155 } 00156 00165 public static function articleFromTitle( $title, IContextSource $context ) { 00166 wfDeprecated( __METHOD__, '1.18' ); 00167 return Article::newFromTitle( $title, $context ); 00168 } 00169 00182 private function performRequest() { 00183 global $wgServer, $wgUsePathInfo, $wgTitle; 00184 00185 wfProfileIn( __METHOD__ ); 00186 00187 $request = $this->context->getRequest(); 00188 $requestTitle = $title = $this->context->getTitle(); 00189 $output = $this->context->getOutput(); 00190 $user = $this->context->getUser(); 00191 00192 if ( $request->getVal( 'printable' ) === 'yes' ) { 00193 $output->setPrintable(); 00194 } 00195 00196 $unused = null; // To pass it by reference 00197 wfRunHooks( 'BeforeInitialize', array( &$title, &$unused, &$output, &$user, $request, $this ) ); 00198 00199 // Invalid titles. Bug 21776: The interwikis must redirect even if the page name is empty. 00200 if ( is_null( $title ) || ( $title->getDBkey() == '' && $title->getInterwiki() == '' ) || 00201 $title->isSpecial( 'Badtitle' ) ) 00202 { 00203 $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) ); 00204 wfProfileOut( __METHOD__ ); 00205 throw new BadTitleError(); 00206 } 00207 00208 // Check user's permissions to read this page. 00209 // We have to check here to catch special pages etc. 00210 // We will check again in Article::view(). 00211 $permErrors = $title->getUserPermissionsErrors( 'read', $user ); 00212 if ( count( $permErrors ) ) { 00213 // Bug 32276: allowing the skin to generate output with $wgTitle or 00214 // $this->context->title set to the input title would allow anonymous users to 00215 // determine whether a page exists, potentially leaking private data. In fact, the 00216 // curid and oldid request parameters would allow page titles to be enumerated even 00217 // when they are not guessable. So we reset the title to Special:Badtitle before the 00218 // permissions error is displayed. 00219 // 00220 // The skin mostly uses $this->context->getTitle() these days, but some extensions 00221 // still use $wgTitle. 00222 00223 $badTitle = SpecialPage::getTitleFor( 'Badtitle' ); 00224 $this->context->setTitle( $badTitle ); 00225 $wgTitle = $badTitle; 00226 00227 wfProfileOut( __METHOD__ ); 00228 throw new PermissionsError( 'read', $permErrors ); 00229 } 00230 00231 $pageView = false; // was an article or special page viewed? 00232 00233 // Interwiki redirects 00234 if ( $title->getInterwiki() != '' ) { 00235 $rdfrom = $request->getVal( 'rdfrom' ); 00236 if ( $rdfrom ) { 00237 $url = $title->getFullURL( array( 'rdfrom' => $rdfrom ) ); 00238 } else { 00239 $query = $request->getValues(); 00240 unset( $query['title'] ); 00241 $url = $title->getFullURL( $query ); 00242 } 00243 // Check for a redirect loop 00244 if ( !preg_match( '/^' . preg_quote( $wgServer, '/' ) . '/', $url ) 00245 && $title->isLocal() ) 00246 { 00247 // 301 so google et al report the target as the actual url. 00248 $output->redirect( $url, 301 ); 00249 } else { 00250 $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) ); 00251 wfProfileOut( __METHOD__ ); 00252 throw new BadTitleError(); 00253 } 00254 // Redirect loops, no title in URL, $wgUsePathInfo URLs, and URLs with a variant 00255 } elseif ( $request->getVal( 'action', 'view' ) == 'view' && !$request->wasPosted() 00256 && ( $request->getVal( 'title' ) === null || 00257 $title->getPrefixedDBkey() != $request->getVal( 'title' ) ) 00258 && !count( $request->getValueNames( array( 'action', 'title' ) ) ) 00259 && wfRunHooks( 'TestCanonicalRedirect', array( $request, $title, $output ) ) ) 00260 { 00261 if ( $title->isSpecialPage() ) { 00262 list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() ); 00263 if ( $name ) { 00264 $title = SpecialPage::getTitleFor( $name, $subpage ); 00265 } 00266 } 00267 $targetUrl = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ); 00268 // Redirect to canonical url, make it a 301 to allow caching 00269 if ( $targetUrl == $request->getFullRequestURL() ) { 00270 $message = "Redirect loop detected!\n\n" . 00271 "This means the wiki got confused about what page was " . 00272 "requested; this sometimes happens when moving a wiki " . 00273 "to a new server or changing the server configuration.\n\n"; 00274 00275 if ( $wgUsePathInfo ) { 00276 $message .= "The wiki is trying to interpret the page " . 00277 "title from the URL path portion (PATH_INFO), which " . 00278 "sometimes fails depending on the web server. Try " . 00279 "setting \"\$wgUsePathInfo = false;\" in your " . 00280 "LocalSettings.php, or check that \$wgArticlePath " . 00281 "is correct."; 00282 } else { 00283 $message .= "Your web server was detected as possibly not " . 00284 "supporting URL path components (PATH_INFO) correctly; " . 00285 "check your LocalSettings.php for a customized " . 00286 "\$wgArticlePath setting and/or toggle \$wgUsePathInfo " . 00287 "to true."; 00288 } 00289 throw new HttpError( 500, $message ); 00290 } else { 00291 $output->setSquidMaxage( 1200 ); 00292 $output->redirect( $targetUrl, '301' ); 00293 } 00294 // Special pages 00295 } elseif ( NS_SPECIAL == $title->getNamespace() ) { 00296 $pageView = true; 00297 // Actions that need to be made when we have a special pages 00298 SpecialPageFactory::executePath( $title, $this->context ); 00299 } else { 00300 // ...otherwise treat it as an article view. The article 00301 // may be a redirect to another article or URL. 00302 $article = $this->initializeArticle(); 00303 if ( is_object( $article ) ) { 00304 $pageView = true; 00309 global $wgArticle; 00310 $wgArticle = new DeprecatedGlobal( 'wgArticle', $article, '1.18' ); 00311 00312 $this->performAction( $article, $requestTitle ); 00313 } elseif ( is_string( $article ) ) { 00314 $output->redirect( $article ); 00315 } else { 00316 wfProfileOut( __METHOD__ ); 00317 throw new MWException( "Shouldn't happen: MediaWiki::initializeArticle()" 00318 . " returned neither an object nor a URL" ); 00319 } 00320 } 00321 00322 if ( $pageView ) { 00323 // Promote user to any groups they meet the criteria for 00324 $user->addAutopromoteOnceGroups( 'onView' ); 00325 } 00326 00327 wfProfileOut( __METHOD__ ); 00328 } 00329 00336 private function initializeArticle() { 00337 global $wgDisableHardRedirects; 00338 00339 wfProfileIn( __METHOD__ ); 00340 00341 $title = $this->context->getTitle(); 00342 if ( $this->context->canUseWikiPage() ) { 00343 // Try to use request context wiki page, as there 00344 // is already data from db saved in per process 00345 // cache there from this->getAction() call. 00346 $page = $this->context->getWikiPage(); 00347 $article = Article::newFromWikiPage( $page, $this->context ); 00348 } else { 00349 // This case should not happen, but just in case. 00350 $article = Article::newFromTitle( $title, $this->context ); 00351 $this->context->setWikiPage( $article->getPage() ); 00352 } 00353 00354 // NS_MEDIAWIKI has no redirects. 00355 // It is also used for CSS/JS, so performance matters here... 00356 if ( $title->getNamespace() == NS_MEDIAWIKI ) { 00357 wfProfileOut( __METHOD__ ); 00358 return $article; 00359 } 00360 00361 $request = $this->context->getRequest(); 00362 00363 // Namespace might change when using redirects 00364 // Check for redirects ... 00365 $action = $request->getVal( 'action', 'view' ); 00366 $file = ( $title->getNamespace() == NS_FILE ) ? $article->getFile() : null; 00367 if ( ( $action == 'view' || $action == 'render' ) // ... for actions that show content 00368 && !$request->getVal( 'oldid' ) && // ... and are not old revisions 00369 !$request->getVal( 'diff' ) && // ... and not when showing diff 00370 $request->getVal( 'redirect' ) != 'no' && // ... unless explicitly told not to 00371 // ... and the article is not a non-redirect image page with associated file 00372 !( is_object( $file ) && $file->exists() && !$file->getRedirected() ) ) 00373 { 00374 // Give extensions a change to ignore/handle redirects as needed 00375 $ignoreRedirect = $target = false; 00376 00377 wfRunHooks( 'InitializeArticleMaybeRedirect', 00378 array( &$title, &$request, &$ignoreRedirect, &$target, &$article ) ); 00379 00380 // Follow redirects only for... redirects. 00381 // If $target is set, then a hook wanted to redirect. 00382 if ( !$ignoreRedirect && ( $target || $article->isRedirect() ) ) { 00383 // Is the target already set by an extension? 00384 $target = $target ? $target : $article->followRedirect(); 00385 if ( is_string( $target ) ) { 00386 if ( !$wgDisableHardRedirects ) { 00387 // we'll need to redirect 00388 wfProfileOut( __METHOD__ ); 00389 return $target; 00390 } 00391 } 00392 if ( is_object( $target ) ) { 00393 // Rewrite environment to redirected article 00394 $rarticle = Article::newFromTitle( $target, $this->context ); 00395 $rarticle->loadPageData(); 00396 if ( $rarticle->exists() || ( is_object( $file ) && !$file->isLocal() ) ) { 00397 $rarticle->setRedirectedFrom( $title ); 00398 $article = $rarticle; 00399 $this->context->setTitle( $target ); 00400 $this->context->setWikiPage( $article->getPage() ); 00401 } 00402 } 00403 } else { 00404 $this->context->setTitle( $article->getTitle() ); 00405 $this->context->setWikiPage( $article->getPage() ); 00406 } 00407 } 00408 00409 wfProfileOut( __METHOD__ ); 00410 return $article; 00411 } 00412 00419 private function performAction( Page $page, Title $requestTitle ) { 00420 global $wgUseSquid, $wgSquidMaxage; 00421 00422 wfProfileIn( __METHOD__ ); 00423 00424 $request = $this->context->getRequest(); 00425 $output = $this->context->getOutput(); 00426 $title = $this->context->getTitle(); 00427 $user = $this->context->getUser(); 00428 00429 if ( !wfRunHooks( 'MediaWikiPerformAction', 00430 array( $output, $page, $title, $user, $request, $this ) ) ) 00431 { 00432 wfProfileOut( __METHOD__ ); 00433 return; 00434 } 00435 00436 $act = $this->getAction(); 00437 00438 $action = Action::factory( $act, $page, $this->context ); 00439 00440 if ( $action instanceof Action ) { 00441 # Let Squid cache things if we can purge them. 00442 if ( $wgUseSquid && 00443 in_array( $request->getFullRequestURL(), $requestTitle->getSquidURLs() ) 00444 ) { 00445 $output->setSquidMaxage( $wgSquidMaxage ); 00446 } 00447 00448 $action->show(); 00449 wfProfileOut( __METHOD__ ); 00450 return; 00451 } 00452 00453 if ( wfRunHooks( 'UnknownAction', array( $request->getVal( 'action', 'view' ), $page ) ) ) { 00454 $output->showErrorPage( 'nosuchaction', 'nosuchactiontext' ); 00455 } 00456 00457 wfProfileOut( __METHOD__ ); 00458 } 00459 00464 public function run() { 00465 try { 00466 $this->checkMaxLag(); 00467 $this->main(); 00468 $this->restInPeace(); 00469 } catch ( Exception $e ) { 00470 MWExceptionHandler::handle( $e ); 00471 } 00472 } 00473 00479 private function checkMaxLag() { 00480 global $wgShowHostnames; 00481 00482 wfProfileIn( __METHOD__ ); 00483 $maxLag = $this->context->getRequest()->getVal( 'maxlag' ); 00484 if ( !is_null( $maxLag ) ) { 00485 list( $host, $lag ) = wfGetLB()->getMaxLag(); 00486 if ( $lag > $maxLag ) { 00487 $resp = $this->context->getRequest()->response(); 00488 $resp->header( 'HTTP/1.1 503 Service Unavailable' ); 00489 $resp->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) ); 00490 $resp->header( 'X-Database-Lag: ' . intval( $lag ) ); 00491 $resp->header( 'Content-Type: text/plain' ); 00492 if ( $wgShowHostnames ) { 00493 echo "Waiting for $host: $lag seconds lagged\n"; 00494 } else { 00495 echo "Waiting for a database server: $lag seconds lagged\n"; 00496 } 00497 00498 wfProfileOut( __METHOD__ ); 00499 00500 exit; 00501 } 00502 } 00503 wfProfileOut( __METHOD__ ); 00504 return true; 00505 } 00506 00507 private function main() { 00508 global $wgUseFileCache, $wgTitle, $wgUseAjax; 00509 00510 wfProfileIn( __METHOD__ ); 00511 00512 $request = $this->context->getRequest(); 00513 00514 // Send Ajax requests to the Ajax dispatcher. 00515 if ( $wgUseAjax && $request->getVal( 'action', 'view' ) == 'ajax' ) { 00516 00517 // Set a dummy title, because $wgTitle == null might break things 00518 $title = Title::makeTitle( NS_MAIN, 'AJAX' ); 00519 $this->context->setTitle( $title ); 00520 $wgTitle = $title; 00521 00522 $dispatcher = new AjaxDispatcher(); 00523 $dispatcher->performAction(); 00524 wfProfileOut( __METHOD__ ); 00525 return; 00526 } 00527 00528 // Get title from request parameters, 00529 // is set on the fly by parseTitle the first time. 00530 $title = $this->getTitle(); 00531 $action = $this->getAction(); 00532 $wgTitle = $title; 00533 00534 // If the user has forceHTTPS set to true, or if the user 00535 // is in a group requiring HTTPS, or if they have the HTTPS 00536 // preference set, redirect them to HTTPS. 00537 // Note: Do this after $wgTitle is setup, otherwise the hooks run from 00538 // isLoggedIn() will do all sorts of weird stuff. 00539 if ( 00540 ( 00541 $request->getCookie( 'forceHTTPS', '' ) || 00542 // check for prefixed version for currently logged in users 00543 $request->getCookie( 'forceHTTPS' ) || 00544 // Avoid checking the user and groups unless it's enabled. 00545 ( 00546 $this->context->getUser()->isLoggedIn() 00547 && $this->context->getUser()->requiresHTTPS() 00548 ) 00549 ) && 00550 $request->detectProtocol() == 'http' 00551 ) { 00552 $oldUrl = $request->getFullRequestURL(); 00553 $redirUrl = str_replace( 'http://', 'https://', $oldUrl ); 00554 00555 if ( $request->wasPosted() ) { 00556 // This is weird and we'd hope it almost never happens. This 00557 // means that a POST came in via HTTP and policy requires us 00558 // redirecting to HTTPS. It's likely such a request is going 00559 // to fail due to post data being lost, but let's try anyway 00560 // and just log the instance. 00561 // 00562 // @todo @fixme See if we could issue a 307 or 308 here, need 00563 // to see how clients (automated & browser) behave when we do 00564 wfDebugLog( 'RedirectedPosts', "Redirected from HTTP to HTTPS: $oldUrl" ); 00565 } 00566 00567 // Setup dummy Title, otherwise OutputPage::redirect will fail 00568 $title = Title::newFromText( NS_MAIN, 'REDIR' ); 00569 $this->context->setTitle( $title ); 00570 $output = $this->context->getOutput(); 00571 // Since we only do this redir to change proto, always send a vary header 00572 $output->addVaryHeader( 'X-Forwarded-Proto' ); 00573 $output->redirect( $redirUrl ); 00574 $output->output(); 00575 wfProfileOut( __METHOD__ ); 00576 return; 00577 } 00578 00579 if ( $wgUseFileCache && $title->getNamespace() >= 0 ) { 00580 wfProfileIn( 'main-try-filecache' ); 00581 if ( HTMLFileCache::useFileCache( $this->context ) ) { 00582 // Try low-level file cache hit 00583 $cache = HTMLFileCache::newFromTitle( $title, $action ); 00584 if ( $cache->isCacheGood( /* Assume up to date */ ) ) { 00585 // Check incoming headers to see if client has this cached 00586 $timestamp = $cache->cacheTimestamp(); 00587 if ( !$this->context->getOutput()->checkLastModified( $timestamp ) ) { 00588 $cache->loadFromFileCache( $this->context ); 00589 } 00590 // Do any stats increment/watchlist stuff 00591 $this->context->getWikiPage()->doViewUpdates( $this->context->getUser() ); 00592 // Tell OutputPage that output is taken care of 00593 $this->context->getOutput()->disable(); 00594 wfProfileOut( 'main-try-filecache' ); 00595 wfProfileOut( __METHOD__ ); 00596 return; 00597 } 00598 } 00599 wfProfileOut( 'main-try-filecache' ); 00600 } 00601 00602 $this->performRequest(); 00603 00604 // Now commit any transactions, so that unreported errors after 00605 // output() don't roll back the whole DB transaction 00606 wfGetLBFactory()->commitMasterChanges(); 00607 00608 // Output everything! 00609 $this->context->getOutput()->output(); 00610 00611 wfProfileOut( __METHOD__ ); 00612 } 00613 00617 public function restInPeace() { 00618 // Do any deferred jobs 00619 DeferredUpdates::doUpdates( 'commit' ); 00620 00621 // Execute a job from the queue 00622 $this->doJobs(); 00623 00624 // Log profiling data, e.g. in the database or UDP 00625 wfLogProfilingData(); 00626 00627 // Commit and close up! 00628 $factory = wfGetLBFactory(); 00629 $factory->commitMasterChanges(); 00630 $factory->shutdown(); 00631 00632 wfDebug( "Request ended normally\n" ); 00633 } 00634 00638 private function doJobs() { 00639 global $wgJobRunRate, $wgPhpCli, $IP; 00640 00641 if ( $wgJobRunRate <= 0 || wfReadOnly() ) { 00642 return; 00643 } 00644 00645 if ( $wgJobRunRate < 1 ) { 00646 $max = mt_getrandmax(); 00647 if ( mt_rand( 0, $max ) > $max * $wgJobRunRate ) { 00648 return; // the higher $wgJobRunRate, the less likely we return here 00649 } 00650 $n = 1; 00651 } else { 00652 $n = intval( $wgJobRunRate ); 00653 } 00654 00655 if ( !wfShellExecDisabled() && is_executable( $wgPhpCli ) ) { 00656 // Start a background process to run some of the jobs 00657 wfProfileIn( __METHOD__ . '-exec' ); 00658 $retVal = 1; 00659 $cmd = wfShellWikiCmd( "$IP/maintenance/runJobs.php", array( '--maxjobs', $n ) ); 00660 $cmd .= " >" . wfGetNull() . " 2>&1"; // don't hang PHP on pipes 00661 if ( wfIsWindows() ) { 00662 // Using START makes this async and also works around a bug where using 00663 // wfShellExec() with a quoted script name causes a filename syntax error. 00664 $cmd = "START /B \"bg\" $cmd"; 00665 } else { 00666 $cmd = "$cmd &"; 00667 } 00668 wfShellExec( $cmd, $retVal ); 00669 wfProfileOut( __METHOD__ . '-exec' ); 00670 } else { 00671 try { 00672 // Fallback to running the jobs here while the user waits 00673 $group = JobQueueGroup::singleton(); 00674 do { 00675 $job = $group->pop( JobQueueGroup::USE_CACHE ); // job from any queue 00676 if ( $job ) { 00677 $output = $job->toString() . "\n"; 00678 $t = - microtime( true ); 00679 wfProfileIn( __METHOD__ . '-' . get_class( $job ) ); 00680 $success = $job->run(); 00681 wfProfileOut( __METHOD__ . '-' . get_class( $job ) ); 00682 $group->ack( $job ); // done 00683 $t += microtime( true ); 00684 $t = round( $t * 1000 ); 00685 if ( $success === false ) { 00686 $output .= "Error: " . $job->getLastError() . ", Time: $t ms\n"; 00687 } else { 00688 $output .= "Success, Time: $t ms\n"; 00689 } 00690 wfDebugLog( 'jobqueue', $output ); 00691 } 00692 } while ( --$n && $job ); 00693 } catch ( MWException $e ) { 00694 // We don't want exceptions thrown during job execution to 00695 // be reported to the user since the output is already sent. 00696 // Instead we just log them. 00697 MWExceptionHandler::logException( $e ); 00698 } 00699 } 00700 } 00701 }