MediaWiki  REL1_24
MediaWiki.php
Go to the documentation of this file.
00001 <?php
00028 class MediaWiki {
00032     private $context;
00033 
00037     private $config;
00038 
00042     public function __construct( IContextSource $context = null ) {
00043         if ( !$context ) {
00044             $context = RequestContext::getMain();
00045         }
00046 
00047         $this->context = $context;
00048         $this->config = $context->getConfig();
00049     }
00050 
00056     private function parseTitle() {
00057         global $wgContLang;
00058 
00059         $request = $this->context->getRequest();
00060         $curid = $request->getInt( 'curid' );
00061         $title = $request->getVal( 'title' );
00062         $action = $request->getVal( 'action', 'view' );
00063 
00064         if ( $request->getCheck( 'search' ) ) {
00065             // Compatibility with old search URLs which didn't use Special:Search
00066             // Just check for presence here, so blank requests still
00067             // show the search page when using ugly URLs (bug 8054).
00068             $ret = SpecialPage::getTitleFor( 'Search' );
00069         } elseif ( $curid ) {
00070             // URLs like this are generated by RC, because rc_title isn't always accurate
00071             $ret = Title::newFromID( $curid );
00072         } else {
00073             $ret = Title::newFromURL( $title );
00074             // Alias NS_MEDIA page URLs to NS_FILE...we only use NS_MEDIA
00075             // in wikitext links to tell Parser to make a direct file link
00076             if ( !is_null( $ret ) && $ret->getNamespace() == NS_MEDIA ) {
00077                 $ret = Title::makeTitle( NS_FILE, $ret->getDBkey() );
00078             }
00079             // Check variant links so that interwiki links don't have to worry
00080             // about the possible different language variants
00081             if ( count( $wgContLang->getVariants() ) > 1
00082                 && !is_null( $ret ) && $ret->getArticleID() == 0
00083             ) {
00084                 $wgContLang->findVariantLink( $title, $ret );
00085             }
00086         }
00087 
00088         // If title is not provided, always allow oldid and diff to set the title.
00089         // If title is provided, allow oldid and diff to override the title, unless
00090         // we are talking about a special page which might use these parameters for
00091         // other purposes.
00092         if ( $ret === null || !$ret->isSpecialPage() ) {
00093             // We can have urls with just ?diff=,?oldid= or even just ?diff=
00094             $oldid = $request->getInt( 'oldid' );
00095             $oldid = $oldid ? $oldid : $request->getInt( 'diff' );
00096             // Allow oldid to override a changed or missing title
00097             if ( $oldid ) {
00098                 $rev = Revision::newFromId( $oldid );
00099                 $ret = $rev ? $rev->getTitle() : $ret;
00100             }
00101         }
00102 
00103         // Use the main page as default title if nothing else has been provided
00104         if ( $ret === null
00105             && strval( $title ) === ''
00106             && !$request->getCheck( 'curid' )
00107             && $action !== 'delete'
00108         ) {
00109             $ret = Title::newMainPage();
00110         }
00111 
00112         if ( $ret === null || ( $ret->getDBkey() == '' && !$ret->isExternal() ) ) {
00113             $ret = SpecialPage::getTitleFor( 'Badtitle' );
00114         }
00115 
00116         return $ret;
00117     }
00118 
00123     public function getTitle() {
00124         if ( $this->context->getTitle() === null ) {
00125             $this->context->setTitle( $this->parseTitle() );
00126         }
00127         return $this->context->getTitle();
00128     }
00129 
00135     public function getAction() {
00136         static $action = null;
00137 
00138         if ( $action === null ) {
00139             $action = Action::getActionName( $this->context );
00140         }
00141 
00142         return $action;
00143     }
00144 
00157     private function performRequest() {
00158         global $wgTitle;
00159 
00160         wfProfileIn( __METHOD__ );
00161 
00162         $request = $this->context->getRequest();
00163         $requestTitle = $title = $this->context->getTitle();
00164         $output = $this->context->getOutput();
00165         $user = $this->context->getUser();
00166 
00167         if ( $request->getVal( 'printable' ) === 'yes' ) {
00168             $output->setPrintable();
00169         }
00170 
00171         $unused = null; // To pass it by reference
00172         wfRunHooks( 'BeforeInitialize', array( &$title, &$unused, &$output, &$user, $request, $this ) );
00173 
00174         // Invalid titles. Bug 21776: The interwikis must redirect even if the page name is empty.
00175         if ( is_null( $title ) || ( $title->getDBkey() == '' && !$title->isExternal() )
00176             || $title->isSpecial( 'Badtitle' )
00177         ) {
00178             $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
00179             wfProfileOut( __METHOD__ );
00180             throw new BadTitleError();
00181         }
00182 
00183         // Check user's permissions to read this page.
00184         // We have to check here to catch special pages etc.
00185         // We will check again in Article::view().
00186         $permErrors = $title->isSpecial( 'RunJobs' )
00187             ? array() // relies on HMAC key signature alone
00188             : $title->getUserPermissionsErrors( 'read', $user );
00189         if ( count( $permErrors ) ) {
00190             // Bug 32276: allowing the skin to generate output with $wgTitle or
00191             // $this->context->title set to the input title would allow anonymous users to
00192             // determine whether a page exists, potentially leaking private data. In fact, the
00193             // curid and oldid request  parameters would allow page titles to be enumerated even
00194             // when they are not guessable. So we reset the title to Special:Badtitle before the
00195             // permissions error is displayed.
00196             //
00197             // The skin mostly uses $this->context->getTitle() these days, but some extensions
00198             // still use $wgTitle.
00199 
00200             $badTitle = SpecialPage::getTitleFor( 'Badtitle' );
00201             $this->context->setTitle( $badTitle );
00202             $wgTitle = $badTitle;
00203 
00204             wfProfileOut( __METHOD__ );
00205             throw new PermissionsError( 'read', $permErrors );
00206         }
00207 
00208         $pageView = false; // was an article or special page viewed?
00209 
00210         // Interwiki redirects
00211         if ( $title->isExternal() ) {
00212             $rdfrom = $request->getVal( 'rdfrom' );
00213             if ( $rdfrom ) {
00214                 $url = $title->getFullURL( array( 'rdfrom' => $rdfrom ) );
00215             } else {
00216                 $query = $request->getValues();
00217                 unset( $query['title'] );
00218                 $url = $title->getFullURL( $query );
00219             }
00220             // Check for a redirect loop
00221             if ( !preg_match( '/^' . preg_quote( $this->config->get( 'Server' ), '/' ) . '/', $url )
00222                 && $title->isLocal()
00223             ) {
00224                 // 301 so google et al report the target as the actual url.
00225                 $output->redirect( $url, 301 );
00226             } else {
00227                 $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
00228                 wfProfileOut( __METHOD__ );
00229                 throw new BadTitleError();
00230             }
00231         // Redirect loops, no title in URL, $wgUsePathInfo URLs, and URLs with a variant
00232         } elseif ( $request->getVal( 'action', 'view' ) == 'view' && !$request->wasPosted()
00233             && ( $request->getVal( 'title' ) === null
00234                 || $title->getPrefixedDBkey() != $request->getVal( 'title' ) )
00235             && !count( $request->getValueNames( array( 'action', 'title' ) ) )
00236             && wfRunHooks( 'TestCanonicalRedirect', array( $request, $title, $output ) )
00237         ) {
00238             if ( $title->isSpecialPage() ) {
00239                 list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
00240                 if ( $name ) {
00241                     $title = SpecialPage::getTitleFor( $name, $subpage );
00242                 }
00243             }
00244             $targetUrl = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
00245             // Redirect to canonical url, make it a 301 to allow caching
00246             if ( $targetUrl == $request->getFullRequestURL() ) {
00247                 $message = "Redirect loop detected!\n\n" .
00248                     "This means the wiki got confused about what page was " .
00249                     "requested; this sometimes happens when moving a wiki " .
00250                     "to a new server or changing the server configuration.\n\n";
00251 
00252                 if ( $this->config->get( 'UsePathInfo' ) ) {
00253                     $message .= "The wiki is trying to interpret the page " .
00254                         "title from the URL path portion (PATH_INFO), which " .
00255                         "sometimes fails depending on the web server. Try " .
00256                         "setting \"\$wgUsePathInfo = false;\" in your " .
00257                         "LocalSettings.php, or check that \$wgArticlePath " .
00258                         "is correct.";
00259                 } else {
00260                     $message .= "Your web server was detected as possibly not " .
00261                         "supporting URL path components (PATH_INFO) correctly; " .
00262                         "check your LocalSettings.php for a customized " .
00263                         "\$wgArticlePath setting and/or toggle \$wgUsePathInfo " .
00264                         "to true.";
00265                 }
00266                 throw new HttpError( 500, $message );
00267             } else {
00268                 $output->setSquidMaxage( 1200 );
00269                 $output->redirect( $targetUrl, '301' );
00270             }
00271         // Special pages
00272         } elseif ( NS_SPECIAL == $title->getNamespace() ) {
00273             $pageView = true;
00274             // Actions that need to be made when we have a special pages
00275             SpecialPageFactory::executePath( $title, $this->context );
00276         } else {
00277             // ...otherwise treat it as an article view. The article
00278             // may be a redirect to another article or URL.
00279             $article = $this->initializeArticle();
00280             if ( is_object( $article ) ) {
00281                 $pageView = true;
00282                 $this->performAction( $article, $requestTitle );
00283             } elseif ( is_string( $article ) ) {
00284                 $output->redirect( $article );
00285             } else {
00286                 wfProfileOut( __METHOD__ );
00287                 throw new MWException( "Shouldn't happen: MediaWiki::initializeArticle()"
00288                     . " returned neither an object nor a URL" );
00289             }
00290         }
00291 
00292         if ( $pageView ) {
00293             // Promote user to any groups they meet the criteria for
00294             $user->addAutopromoteOnceGroups( 'onView' );
00295         }
00296 
00297         wfProfileOut( __METHOD__ );
00298     }
00299 
00306     private function initializeArticle() {
00307         wfProfileIn( __METHOD__ );
00308 
00309         $title = $this->context->getTitle();
00310         if ( $this->context->canUseWikiPage() ) {
00311             // Try to use request context wiki page, as there
00312             // is already data from db saved in per process
00313             // cache there from this->getAction() call.
00314             $page = $this->context->getWikiPage();
00315             $article = Article::newFromWikiPage( $page, $this->context );
00316         } else {
00317             // This case should not happen, but just in case.
00318             $article = Article::newFromTitle( $title, $this->context );
00319             $this->context->setWikiPage( $article->getPage() );
00320         }
00321 
00322         // NS_MEDIAWIKI has no redirects.
00323         // It is also used for CSS/JS, so performance matters here...
00324         if ( $title->getNamespace() == NS_MEDIAWIKI ) {
00325             wfProfileOut( __METHOD__ );
00326             return $article;
00327         }
00328 
00329         $request = $this->context->getRequest();
00330 
00331         // Namespace might change when using redirects
00332         // Check for redirects ...
00333         $action = $request->getVal( 'action', 'view' );
00334         $file = ( $title->getNamespace() == NS_FILE ) ? $article->getFile() : null;
00335         if ( ( $action == 'view' || $action == 'render' ) // ... for actions that show content
00336             && !$request->getVal( 'oldid' ) // ... and are not old revisions
00337             && !$request->getVal( 'diff' ) // ... and not when showing diff
00338             && $request->getVal( 'redirect' ) != 'no' // ... unless explicitly told not to
00339             // ... and the article is not a non-redirect image page with associated file
00340             && !( is_object( $file ) && $file->exists() && !$file->getRedirected() )
00341         ) {
00342             // Give extensions a change to ignore/handle redirects as needed
00343             $ignoreRedirect = $target = false;
00344 
00345             wfRunHooks( 'InitializeArticleMaybeRedirect',
00346                 array( &$title, &$request, &$ignoreRedirect, &$target, &$article ) );
00347 
00348             // Follow redirects only for... redirects.
00349             // If $target is set, then a hook wanted to redirect.
00350             if ( !$ignoreRedirect && ( $target || $article->isRedirect() ) ) {
00351                 // Is the target already set by an extension?
00352                 $target = $target ? $target : $article->followRedirect();
00353                 if ( is_string( $target ) ) {
00354                     if ( !$this->config->get( 'DisableHardRedirects' ) ) {
00355                         // we'll need to redirect
00356                         wfProfileOut( __METHOD__ );
00357                         return $target;
00358                     }
00359                 }
00360                 if ( is_object( $target ) ) {
00361                     // Rewrite environment to redirected article
00362                     $rarticle = Article::newFromTitle( $target, $this->context );
00363                     $rarticle->loadPageData();
00364                     if ( $rarticle->exists() || ( is_object( $file ) && !$file->isLocal() ) ) {
00365                         $rarticle->setRedirectedFrom( $title );
00366                         $article = $rarticle;
00367                         $this->context->setTitle( $target );
00368                         $this->context->setWikiPage( $article->getPage() );
00369                     }
00370                 }
00371             } else {
00372                 $this->context->setTitle( $article->getTitle() );
00373                 $this->context->setWikiPage( $article->getPage() );
00374             }
00375         }
00376 
00377         wfProfileOut( __METHOD__ );
00378         return $article;
00379     }
00380 
00387     private function performAction( Page $page, Title $requestTitle ) {
00388         wfProfileIn( __METHOD__ );
00389 
00390         $request = $this->context->getRequest();
00391         $output = $this->context->getOutput();
00392         $title = $this->context->getTitle();
00393         $user = $this->context->getUser();
00394 
00395         if ( !wfRunHooks( 'MediaWikiPerformAction',
00396                 array( $output, $page, $title, $user, $request, $this ) )
00397         ) {
00398             wfProfileOut( __METHOD__ );
00399             return;
00400         }
00401 
00402         $act = $this->getAction();
00403 
00404         $action = Action::factory( $act, $page, $this->context );
00405 
00406         if ( $action instanceof Action ) {
00407             # Let Squid cache things if we can purge them.
00408             if ( $this->config->get( 'UseSquid' ) &&
00409                 in_array( $request->getFullRequestURL(), $requestTitle->getSquidURLs() )
00410             ) {
00411                 $output->setSquidMaxage( $this->config->get( 'SquidMaxage' ) );
00412             }
00413 
00414             $action->show();
00415             wfProfileOut( __METHOD__ );
00416             return;
00417         }
00418 
00419         if ( wfRunHooks( 'UnknownAction', array( $request->getVal( 'action', 'view' ), $page ) ) ) {
00420             $output->setStatusCode( 404 );
00421             $output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
00422         }
00423 
00424         wfProfileOut( __METHOD__ );
00425     }
00426 
00431     public function run() {
00432         try {
00433             $this->checkMaxLag();
00434             try {
00435                 $this->main();
00436             } catch ( ErrorPageError $e ) {
00437                 // Bug 62091: while exceptions are convenient to bubble up GUI errors,
00438                 // they are not internal application faults. As with normal requests, this
00439                 // should commit, print the output, do deferred updates, jobs, and profiling.
00440                 wfGetLBFactory()->commitMasterChanges();
00441                 $e->report(); // display the GUI error
00442             }
00443             if ( function_exists( 'fastcgi_finish_request' ) ) {
00444                 fastcgi_finish_request();
00445             }
00446             $this->triggerJobs();
00447             $this->restInPeace();
00448         } catch ( Exception $e ) {
00449             MWExceptionHandler::handle( $e );
00450         }
00451     }
00452 
00458     private function checkMaxLag() {
00459         wfProfileIn( __METHOD__ );
00460         $maxLag = $this->context->getRequest()->getVal( 'maxlag' );
00461         if ( !is_null( $maxLag ) ) {
00462             list( $host, $lag ) = wfGetLB()->getMaxLag();
00463             if ( $lag > $maxLag ) {
00464                 $resp = $this->context->getRequest()->response();
00465                 $resp->header( 'HTTP/1.1 503 Service Unavailable' );
00466                 $resp->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
00467                 $resp->header( 'X-Database-Lag: ' . intval( $lag ) );
00468                 $resp->header( 'Content-Type: text/plain' );
00469                 if ( $this->config->get( 'ShowHostnames' ) ) {
00470                     echo "Waiting for $host: $lag seconds lagged\n";
00471                 } else {
00472                     echo "Waiting for a database server: $lag seconds lagged\n";
00473                 }
00474 
00475                 wfProfileOut( __METHOD__ );
00476 
00477                 exit;
00478             }
00479         }
00480         wfProfileOut( __METHOD__ );
00481         return true;
00482     }
00483 
00484     private function main() {
00485         global $wgTitle;
00486 
00487         wfProfileIn( __METHOD__ );
00488 
00489         $request = $this->context->getRequest();
00490 
00491         // Send Ajax requests to the Ajax dispatcher.
00492         if ( $this->config->get( 'UseAjax' ) && $request->getVal( 'action', 'view' ) == 'ajax' ) {
00493 
00494             // Set a dummy title, because $wgTitle == null might break things
00495             $title = Title::makeTitle( NS_MAIN, 'AJAX' );
00496             $this->context->setTitle( $title );
00497             $wgTitle = $title;
00498 
00499             $dispatcher = new AjaxDispatcher( $this->config );
00500             $dispatcher->performAction( $this->context->getUser() );
00501             wfProfileOut( __METHOD__ );
00502             return;
00503         }
00504 
00505         // Get title from request parameters,
00506         // is set on the fly by parseTitle the first time.
00507         $title = $this->getTitle();
00508         $action = $this->getAction();
00509         $wgTitle = $title;
00510 
00511         // If the user has forceHTTPS set to true, or if the user
00512         // is in a group requiring HTTPS, or if they have the HTTPS
00513         // preference set, redirect them to HTTPS.
00514         // Note: Do this after $wgTitle is setup, otherwise the hooks run from
00515         // isLoggedIn() will do all sorts of weird stuff.
00516         if (
00517             $request->getProtocol() == 'http' &&
00518             (
00519                 $request->getCookie( 'forceHTTPS', '' ) ||
00520                 // check for prefixed version for currently logged in users
00521                 $request->getCookie( 'forceHTTPS' ) ||
00522                 // Avoid checking the user and groups unless it's enabled.
00523                 (
00524                     $this->context->getUser()->isLoggedIn()
00525                     && $this->context->getUser()->requiresHTTPS()
00526                 )
00527             )
00528         ) {
00529             $oldUrl = $request->getFullRequestURL();
00530             $redirUrl = preg_replace( '#^http://#', 'https://', $oldUrl );
00531 
00532             // ATTENTION: This hook is likely to be removed soon due to overall design of the system.
00533             if ( wfRunHooks( 'BeforeHttpsRedirect', array( $this->context, &$redirUrl ) ) ) {
00534 
00535                 if ( $request->wasPosted() ) {
00536                     // This is weird and we'd hope it almost never happens. This
00537                     // means that a POST came in via HTTP and policy requires us
00538                     // redirecting to HTTPS. It's likely such a request is going
00539                     // to fail due to post data being lost, but let's try anyway
00540                     // and just log the instance.
00541                     //
00542                     // @todo FIXME: See if we could issue a 307 or 308 here, need
00543                     // to see how clients (automated & browser) behave when we do
00544                     wfDebugLog( 'RedirectedPosts', "Redirected from HTTP to HTTPS: $oldUrl" );
00545                 }
00546                 // Setup dummy Title, otherwise OutputPage::redirect will fail
00547                 $title = Title::newFromText( NS_MAIN, 'REDIR' );
00548                 $this->context->setTitle( $title );
00549                 $output = $this->context->getOutput();
00550                 // Since we only do this redir to change proto, always send a vary header
00551                 $output->addVaryHeader( 'X-Forwarded-Proto' );
00552                 $output->redirect( $redirUrl );
00553                 $output->output();
00554                 wfProfileOut( __METHOD__ );
00555                 return;
00556             }
00557         }
00558 
00559         if ( $this->config->get( 'UseFileCache' ) && $title->getNamespace() >= 0 ) {
00560             wfProfileIn( 'main-try-filecache' );
00561             if ( HTMLFileCache::useFileCache( $this->context ) ) {
00562                 // Try low-level file cache hit
00563                 $cache = new HTMLFileCache( $title, $action );
00564                 if ( $cache->isCacheGood( /* Assume up to date */ ) ) {
00565                     // Check incoming headers to see if client has this cached
00566                     $timestamp = $cache->cacheTimestamp();
00567                     if ( !$this->context->getOutput()->checkLastModified( $timestamp ) ) {
00568                         $cache->loadFromFileCache( $this->context );
00569                     }
00570                     // Do any stats increment/watchlist stuff
00571                     // Assume we're viewing the latest revision (this should always be the case with file cache)
00572                     $this->context->getWikiPage()->doViewUpdates( $this->context->getUser() );
00573                     // Tell OutputPage that output is taken care of
00574                     $this->context->getOutput()->disable();
00575                     wfProfileOut( 'main-try-filecache' );
00576                     wfProfileOut( __METHOD__ );
00577                     return;
00578                 }
00579             }
00580             wfProfileOut( 'main-try-filecache' );
00581         }
00582 
00583         // Actually do the work of the request and build up any output
00584         $this->performRequest();
00585 
00586         // Either all DB and deferred updates should happen or none.
00587         // The later should not be cancelled due to client disconnect.
00588         ignore_user_abort( true );
00589         // Now commit any transactions, so that unreported errors after
00590         // output() don't roll back the whole DB transaction
00591         wfGetLBFactory()->commitMasterChanges();
00592 
00593         // Output everything!
00594         $this->context->getOutput()->output();
00595 
00596         wfProfileOut( __METHOD__ );
00597     }
00598 
00602     public function restInPeace() {
00603         // Do any deferred jobs
00604         DeferredUpdates::doUpdates( 'commit' );
00605 
00606         // Log profiling data, e.g. in the database or UDP
00607         wfLogProfilingData();
00608 
00609         // Commit and close up!
00610         $factory = wfGetLBFactory();
00611         $factory->commitMasterChanges();
00612         $factory->shutdown();
00613 
00614         wfDebug( "Request ended normally\n" );
00615     }
00616 
00622     protected function triggerJobs() {
00623         $jobRunRate = $this->config->get( 'JobRunRate' );
00624         if ( $jobRunRate <= 0 || wfReadOnly() ) {
00625             return;
00626         } elseif ( $this->getTitle()->isSpecial( 'RunJobs' ) ) {
00627             return; // recursion guard
00628         }
00629 
00630         $section = new ProfileSection( __METHOD__ );
00631 
00632         if ( $jobRunRate < 1 ) {
00633             $max = mt_getrandmax();
00634             if ( mt_rand( 0, $max ) > $max * $jobRunRate ) {
00635                 return; // the higher the job run rate, the less likely we return here
00636             }
00637             $n = 1;
00638         } else {
00639             $n = intval( $jobRunRate );
00640         }
00641 
00642         if ( !$this->config->get( 'RunJobsAsync' ) ) {
00643             // Fall back to running the job here while the user waits
00644             $runner = new JobRunner();
00645             $runner->run( array( 'maxJobs'  => $n ) );
00646             return;
00647         }
00648 
00649         try {
00650             if ( !JobQueueGroup::singleton()->queuesHaveJobs( JobQueueGroup::TYPE_DEFAULT ) ) {
00651                 return; // do not send request if there are probably no jobs
00652             }
00653         } catch ( JobQueueError $e ) {
00654             MWExceptionHandler::logException( $e );
00655             return; // do not make the site unavailable
00656         }
00657 
00658         $query = array( 'title' => 'Special:RunJobs',
00659             'tasks' => 'jobs', 'maxjobs' => $n, 'sigexpiry' => time() + 5 );
00660         $query['signature'] = SpecialRunJobs::getQuerySignature(
00661             $query, $this->config->get( 'SecretKey' ) );
00662 
00663         $errno = $errstr = null;
00664         $info = wfParseUrl( $this->config->get( 'Server' ) );
00665         wfSuppressWarnings();
00666         $sock = fsockopen(
00667             $info['host'],
00668             isset( $info['port'] ) ? $info['port'] : 80,
00669             $errno,
00670             $errstr,
00671             // If it takes more than 100ms to connect to ourselves there
00672             // is a problem elsewhere.
00673             0.1
00674         );
00675         wfRestoreWarnings();
00676         if ( !$sock ) {
00677             wfDebugLog( 'runJobs', "Failed to start cron API (socket error $errno): $errstr\n" );
00678             // Fall back to running the job here while the user waits
00679             $runner = new JobRunner();
00680             $runner->run( array( 'maxJobs'  => $n ) );
00681             return;
00682         }
00683 
00684         $url = wfAppendQuery( wfScript( 'index' ), $query );
00685         $req = "POST $url HTTP/1.1\r\nHost: {$info['host']}\r\nConnection: Close\r\n\r\n";
00686 
00687         wfDebugLog( 'runJobs', "Running $n job(s) via '$url'\n" );
00688         // Send a cron API request to be performed in the background.
00689         // Give up if this takes too long to send (which should be rare).
00690         stream_set_timeout( $sock, 1 );
00691         $bytes = fwrite( $sock, $req );
00692         if ( $bytes !== strlen( $req ) ) {
00693             wfDebugLog( 'runJobs', "Failed to start cron API (socket write error)\n" );
00694         } else {
00695             // Do not wait for the response (the script should handle client aborts).
00696             // Make sure that we don't close before that script reaches ignore_user_abort().
00697             $status = fgets( $sock );
00698             if ( !preg_match( '#^HTTP/\d\.\d 202 #', $status ) ) {
00699                 wfDebugLog( 'runJobs', "Failed to start cron API: received '$status'\n" );
00700             }
00701         }
00702         fclose( $sock );
00703     }
00704 }