Allow extensions to set $wgDisableCounters.
[lhc/web/wiklou.git] / includes / MediaWiki.php
index 62ab667..932dea2 100644 (file)
@@ -114,7 +114,7 @@ class MediaWiki {
                        // If we get here, we definitely don't have a valid title; throw an exception.
                        // Try to get detailed invalid title exception first, fall back to MalformedTitleException.
                        Title::newFromTextThrow( $title );
-                       throw new MalformedTitleException( null, $title );
+                       throw new MalformedTitleException( 'badtitletext', $title );
                }
 
                return $ret;
@@ -239,63 +239,97 @@ class MediaWiki {
                                }
                                throw new BadTitleError();
                        }
-               // Redirect loops, no title in URL, $wgUsePathInfo URLs, and URLs with a variant
-               } elseif ( $request->getVal( 'action', 'view' ) == 'view' && !$request->wasPosted()
-                       && ( $request->getVal( 'title' ) === null
-                               || $title->getPrefixedDBkey() != $request->getVal( 'title' ) )
-                       && !count( $request->getValueNames( array( 'action', 'title' ) ) )
-                       && Hooks::run( 'TestCanonicalRedirect', array( $request, $title, $output ) )
-               ) {
-                       if ( $title->isSpecialPage() ) {
-                               list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
-                               if ( $name ) {
-                                       $title = SpecialPage::getTitleFor( $name, $subpage );
-                               }
-                       }
-                       $targetUrl = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
-                       // Redirect to canonical url, make it a 301 to allow caching
-                       if ( $targetUrl == $request->getFullRequestURL() ) {
-                               $message = "Redirect loop detected!\n\n" .
-                                       "This means the wiki got confused about what page was " .
-                                       "requested; this sometimes happens when moving a wiki " .
-                                       "to a new server or changing the server configuration.\n\n";
-
-                               if ( $this->config->get( 'UsePathInfo' ) ) {
-                                       $message .= "The wiki is trying to interpret the page " .
-                                               "title from the URL path portion (PATH_INFO), which " .
-                                               "sometimes fails depending on the web server. Try " .
-                                               "setting \"\$wgUsePathInfo = false;\" in your " .
-                                               "LocalSettings.php, or check that \$wgArticlePath " .
-                                               "is correct.";
+               // Handle any other redirects.
+               // Redirect loops, titleless URL, $wgUsePathInfo URLs, and URLs with a variant
+               } elseif ( !$this->tryNormaliseRedirect( $title ) ) {
+
+                       // Special pages
+                       if ( NS_SPECIAL == $title->getNamespace() ) {
+                               // Actions that need to be made when we have a special pages
+                               SpecialPageFactory::executePath( $title, $this->context );
+                       } else {
+                               // ...otherwise treat it as an article view. The article
+                               // may still be a wikipage redirect to another article or URL.
+                               $article = $this->initializeArticle();
+                               if ( is_object( $article ) ) {
+                                       $this->performAction( $article, $requestTitle );
+                               } elseif ( is_string( $article ) ) {
+                                       $output->redirect( $article );
                                } else {
-                                       $message .= "Your web server was detected as possibly not " .
-                                               "supporting URL path components (PATH_INFO) correctly; " .
-                                               "check your LocalSettings.php for a customized " .
-                                               "\$wgArticlePath setting and/or toggle \$wgUsePathInfo " .
-                                               "to true.";
+                                       throw new MWException( "Shouldn't happen: MediaWiki::initializeArticle()"
+                                               . " returned neither an object nor a URL" );
                                }
-                               throw new HttpError( 500, $message );
-                       } else {
-                               $output->setSquidMaxage( 1200 );
-                               $output->redirect( $targetUrl, '301' );
                        }
-               // Special pages
-               } elseif ( NS_SPECIAL == $title->getNamespace() ) {
-                       // Actions that need to be made when we have a special pages
-                       SpecialPageFactory::executePath( $title, $this->context );
-               } else {
-                       // ...otherwise treat it as an article view. The article
-                       // may be a redirect to another article or URL.
-                       $article = $this->initializeArticle();
-                       if ( is_object( $article ) ) {
-                               $this->performAction( $article, $requestTitle );
-                       } elseif ( is_string( $article ) ) {
-                               $output->redirect( $article );
+               }
+       }
+
+       /**
+        * Handle redirects for uncanonical title requests.
+        *
+        * Handles:
+        * - Redirect loops.
+        * - No title in URL.
+        * - $wgUsePathInfo URLs.
+        * - URLs with a variant.
+        * - Other non-standard URLs (as long as they have no extra query parameters).
+        *
+        * Behaviour:
+        * - Normalise title values:
+        *   /wiki/Foo%20Bar -> /wiki/Foo_Bar
+        * - Normalise empty title:
+        *   /wiki/ -> /wiki/Main
+        *   /w/index.php?title= -> /wiki/Main
+        * - Don't redirect anything with query parameters other than 'title' or 'action=view'.
+        *
+        * @return bool True if a redirect was set.
+        */
+       private function tryNormaliseRedirect( $title ) {
+               $request = $this->context->getRequest();
+               $output = $this->context->getOutput();
+
+               if ( $request->getVal( 'action', 'view' ) != 'view'
+                       || $request->wasPosted()
+                       || ( $request->getVal( 'title' ) !== null
+                               && $title->getPrefixedDBkey() == $request->getVal( 'title' ) )
+                       || count( $request->getValueNames( array( 'action', 'title' ) ) )
+                       || !Hooks::run( 'TestCanonicalRedirect', array( $request, $title, $output ) )
+               ) {
+                       return false;
+               }
+
+               if ( $title->isSpecialPage() ) {
+                       list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
+                       if ( $name ) {
+                               $title = SpecialPage::getTitleFor( $name, $subpage );
+                       }
+               }
+               // Redirect to canonical url, make it a 301 to allow caching
+               $targetUrl = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
+               if ( $targetUrl == $request->getFullRequestURL() ) {
+                       $message = "Redirect loop detected!\n\n" .
+                               "This means the wiki got confused about what page was " .
+                               "requested; this sometimes happens when moving a wiki " .
+                               "to a new server or changing the server configuration.\n\n";
+
+                       if ( $this->config->get( 'UsePathInfo' ) ) {
+                               $message .= "The wiki is trying to interpret the page " .
+                                       "title from the URL path portion (PATH_INFO), which " .
+                                       "sometimes fails depending on the web server. Try " .
+                                       "setting \"\$wgUsePathInfo = false;\" in your " .
+                                       "LocalSettings.php, or check that \$wgArticlePath " .
+                                       "is correct.";
                        } else {
-                               throw new MWException( "Shouldn't happen: MediaWiki::initializeArticle()"
-                                       . " returned neither an object nor a URL" );
+                               $message .= "Your web server was detected as possibly not " .
+                                       "supporting URL path components (PATH_INFO) correctly; " .
+                                       "check your LocalSettings.php for a customized " .
+                                       "\$wgArticlePath setting and/or toggle \$wgUsePathInfo " .
+                                       "to true.";
                        }
+                       throw new HttpError( 500, $message );
                }
+               $output->setSquidMaxage( 1200 );
+               $output->redirect( $targetUrl, '301' );
+               return true;
        }
 
        /**
@@ -433,37 +467,68 @@ class MediaWiki {
                                // Bug 62091: while exceptions are convenient to bubble up GUI errors,
                                // they are not internal application faults. As with normal requests, this
                                // should commit, print the output, do deferred updates, jobs, and profiling.
-                               wfGetLBFactory()->commitMasterChanges();
+                               $this->doPreOutputCommit();
                                $e->report(); // display the GUI error
                        }
                } catch ( Exception $e ) {
                        MWExceptionHandler::handleException( $e );
                }
 
-               if ( function_exists( 'register_postsend_function' ) ) {
-                       // https://github.com/facebook/hhvm/issues/1230
-                       register_postsend_function( array( $this, 'postSendUpdates' ) );
-               } elseif ( function_exists( 'fastcgi_finish_request' ) ) {
-                       fastcgi_finish_request();
-                       $this->postSendUpdates();
-               } else {
-                       $this->postSendUpdates();
-               }
+               $this->doPostOutputShutdown( 'normal' );
+       }
+
+       /**
+        * This function commits all DB changes as needed before
+        * the user can receive a response (in case commit fails)
+        *
+        * @since 1.26
+        */
+       public function doPreOutputCommit() {
+               // Either all DBs should commit or none
+               ignore_user_abort( true );
+               wfGetLBFactory()->commitMasterChanges();
        }
 
        /**
         * This function does work that can be done *after* the
         * user gets the HTTP response so they don't block on it
         *
+        * @param string $mode Use 'fast' to always skip job running
         * @since 1.26
         */
-       public function postSendUpdates() {
-               try {
-                       JobQueueGroup::pushLazyJobs();
-                       $this->triggerJobs();
-                       $this->restInPeace();
-               } catch ( Exception $e ) {
-                       MWExceptionHandler::handleException( $e );
+       public function doPostOutputShutdown( $mode = 'normal' ) {
+               // Show profiling data if enabled
+               Profiler::instance()->logDataPageOutputOnly();
+
+               $that = $this;
+               $callback = function () use ( $that, $mode ) {
+                       try {
+                               // Assure deferred updates are not in the main transaction
+                               wfGetLBFactory()->commitMasterChanges();
+                               // Run jobs occasionally, if enabled
+                               if ( $mode === 'normal' ) {
+                                       $that->triggerJobs();
+                               }
+                               // Do deferred updates and job insertion and final commit
+                               $that->restInPeace();
+                       } catch ( Exception $e ) {
+                               MWExceptionHandler::handleException( $e );
+                       }
+               };
+
+               if ( function_exists( 'register_postsend_function' ) ) {
+                       // https://github.com/facebook/hhvm/issues/1230
+                       register_postsend_function( $callback );
+               } else {
+                       if ( function_exists( 'fastcgi_finish_request' ) ) {
+                               fastcgi_finish_request();
+                       } else {
+                               // Either all DB and deferred updates should happen or none.
+                               // The later should not be cancelled due to client disconnect.
+                               ignore_user_abort( true );
+                       }
+
+                       $callback();
                }
        }
 
@@ -478,7 +543,7 @@ class MediaWiki {
                        list( $host, $lag ) = wfGetLB()->getMaxLag();
                        if ( $lag > $maxLag ) {
                                $resp = $this->context->getRequest()->response();
-                               $resp->header( 'HTTP/1.1 503 Service Unavailable' );
+                               $resp->statusHeader( 503 );
                                $resp->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
                                $resp->header( 'X-Database-Lag: ' . intval( $lag ) );
                                $resp->header( 'Content-Type: text/plain' );
@@ -602,16 +667,13 @@ class MediaWiki {
                // Actually do the work of the request and build up any output
                $this->performRequest();
 
-               // Either all DB and deferred updates should happen or none.
-               // The later should not be cancelled due to client disconnect.
-               ignore_user_abort( true );
                // Now commit any transactions, so that unreported errors after
-               // output() don't roll back the whole DB transaction
-               wfGetLBFactory()->commitMasterChanges();
+               // output() don't roll back the whole DB transaction and so that
+               // we avoid having both success and error text in the response
+               $this->doPreOutputCommit();
 
                // Output everything!
                $this->context->getOutput()->output();
-
        }
 
        /**
@@ -644,7 +706,7 @@ class MediaWiki {
         * to run a specified number of jobs. This registers a callback to cleanup
         * the socket once it's done.
         */
-       protected function triggerJobs() {
+       public function triggerJobs() {
                $jobRunRate = $this->config->get( 'JobRunRate' );
                if ( $jobRunRate <= 0 || wfReadOnly() ) {
                        return;
@@ -687,7 +749,7 @@ class MediaWiki {
 
                $errno = $errstr = null;
                $info = wfParseUrl( $this->config->get( 'Server' ) );
-               wfSuppressWarnings();
+               MediaWiki\suppressWarnings();
                $sock = fsockopen(
                        $info['host'],
                        isset( $info['port'] ) ? $info['port'] : 80,
@@ -697,7 +759,7 @@ class MediaWiki {
                        // is a problem elsewhere.
                        0.1
                );
-               wfRestoreWarnings();
+               MediaWiki\restoreWarnings();
                if ( !$sock ) {
                        $runJobsLogger->error( "Failed to start cron API (socket error $errno): $errstr" );
                        // Fall back to running the job here while the user waits